diff --git a/packages/demo/src/vision/index.ts b/packages/demo/src/vision/index.ts index 27923d679..bdedc2e25 100644 --- a/packages/demo/src/vision/index.ts +++ b/packages/demo/src/vision/index.ts @@ -102,8 +102,8 @@ context.use(HOOKS.VE_SETTING_FIELD_VARIABLE_SETTER, VariableSetter); const externals = ['react', 'react-dom', 'prop-types', 'react-router', 'react-router-dom', '@ali/recore']; async function loadAssets() { - const legaoAssets = await editor.utils.get('./raxAssets.json'); - // const legaoAssets = await editor.utils.get('./legao-assets.json'); + // const legaoAssets = await editor.utils.get('./raxAssets.json'); + const legaoAssets = await editor.utils.get('./legao-assets.json'); const assets = upgradeAssetsBundle(legaoAssets); @@ -145,11 +145,11 @@ async function loadAssets() { } async function loadSchema() { - const schema = await editor.utils.get('./rax.json'); - // const schema = await editor.utils.get('./schema.json'); + // const schema = await editor.utils.get('./rax.json'); + const schema = await editor.utils.get('./schema.json'); editor.set('schema', schema); - editor.set('renderEnv', 'rax'); - editor.set('clientTypes', ['mobile']); + // editor.set('renderEnv', 'rax'); + // editor.set('clientTypes', ['mobile']); } diff --git a/packages/editor-skeleton/package.json b/packages/editor-skeleton/package.json index 44bdc7458..75a7ae809 100644 --- a/packages/editor-skeleton/package.json +++ b/packages/editor-skeleton/package.json @@ -26,7 +26,10 @@ "@alifd/next": "^1.20.12", "classnames": "^2.2.6", "react": "^16.8.1", - "react-dom": "^16.8.1" + "react-dom": "^16.8.1", + "events": "^3.2.0", + "@ali/ve-icons": "latest", + "@ali/ve-less-variables": "^2.0.0" }, "devDependencies": { "@alib/build-scripts": "^0.1.3", diff --git a/packages/editor-skeleton/src/components/settings/settings-pane.tsx b/packages/editor-skeleton/src/components/settings/settings-pane.tsx index 5b75bddfa..ac4c1af22 100644 --- a/packages/editor-skeleton/src/components/settings/settings-pane.tsx +++ b/packages/editor-skeleton/src/components/settings/settings-pane.tsx @@ -147,8 +147,13 @@ export function createSettingFieldView(item: SettingField | CustomView, field: S } } +export type SettingsPaneProps = { + target: SettingTopEntry | SettingField; + usePopup?: boolean; +}; + @observer -export class SettingsPane extends Component<{ target: SettingTopEntry | SettingField }> { +export class SettingsPane extends Component { static contextType = SkeletonContext; shouldComponentUpdate() { return false; @@ -160,6 +165,8 @@ export class SettingsPane extends Component<{ target: SettingTopEntry | SettingF private handleClick = (e: MouseEvent) => { // compatiable vision stageBox // TODO: optimize these codes + const { usePopup = true } = this.props; + if (!usePopup) return; const pane = e.currentTarget as HTMLDivElement; let entry: any; function getTarget(node: any): any { diff --git a/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx b/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx index 02a49f112..28ee7cc55 100644 --- a/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx +++ b/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx @@ -4,6 +4,8 @@ import { Title, observer, Editor, obx, globalContext } from '@ali/lowcode-editor import { Node, isSettingField, SettingField, Designer } from '@ali/lowcode-designer'; import { SettingsMain } from './main'; import { SettingsPane } from './settings-pane'; +import { StageBox } from '../stage-box'; +import { SkeletonContext } from '../../context'; import { createIcon } from '@ali/lowcode-utils'; @observer @@ -114,7 +116,18 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor }> {
{this.renderBreadcrumb()}
- + + {(skeleton) => { + if (skeleton) { + return ( + + + + ); + } + return null; + }} +
); @@ -127,7 +140,18 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor }> { } return ( } key={field.name}> - + + {(skeleton) => { + if (skeleton) { + return ( + + + + ); + } + return null; + }} + ); }); diff --git a/packages/editor-skeleton/src/components/stage-box/index.less b/packages/editor-skeleton/src/components/stage-box/index.less new file mode 100644 index 000000000..156294182 --- /dev/null +++ b/packages/editor-skeleton/src/components/stage-box/index.less @@ -0,0 +1,74 @@ +@import "~@ali/ve-less-variables/index.less"; + +.skeleton-stagebox { + overflow: hidden; + position: relative; + min-height: 50px; + .skeleton-stagebox-stage { + height: auto; + overflow: hidden; + + transition: transform 0.2s; + + &.skeleton-stagebox-refer { + position: absolute; + top: 0; + left: 0; + right: 0; + height: auto; + } + + &.skeleton-stagebox-stageout-left, &.skeleton-stagebox-stagein-right { + transform: translateX(-100%); + } + + &.skeleton-stagebox-stageout-right, &.skeleton-stagebox-stagein-left { + transform: translateX(100%); + } + + .skeleton-stagebox-stagebacker { + cursor: pointer; + height: 30px; + display: flex; + align-items: center; + background: var(--color-block-background-light, @normal-alpha-9); + justify-content: center; + position: relative; + + .skeleton-stagebox-stage-arrow { + position: absolute; + left: 3px; + top: 50%; + transform: translateY(-50%) rotate(90deg); + opacity: 0.6; + } + .skeleton-stagebox-stage-title { + font-weight: bold; + } + &:hover { + background: var(--color-block-background-dark, @normal-alpha-7); + .skeleton-stagebox-stage-arrow { + opacity: 1; + } + } + .skeleton-stagebox-stage-exit { + position: absolute; + right: 3px; + top: 50%; + transform: translateY(-50%); + opacity: 0.6; + } + } + + .skeleton-stagebox-stage-content { + overflow: hidden; + box-sizing: border-box; + } + + &.skeleton-stagebox-has-backer { + .skeleton-stagebox-stage-content { + padding-top: 30px; + } + } + } +} diff --git a/packages/editor-skeleton/src/components/stage-box/index.ts b/packages/editor-skeleton/src/components/stage-box/index.ts new file mode 100644 index 000000000..ee6dc3b8e --- /dev/null +++ b/packages/editor-skeleton/src/components/stage-box/index.ts @@ -0,0 +1,4 @@ +import StageBox from './stage-box'; +import './index.less'; + +export { StageBox }; diff --git a/packages/editor-skeleton/src/components/stage-box/stage-box.tsx b/packages/editor-skeleton/src/components/stage-box/stage-box.tsx new file mode 100644 index 000000000..e1cea1082 --- /dev/null +++ b/packages/editor-skeleton/src/components/stage-box/stage-box.tsx @@ -0,0 +1,118 @@ +import React, { Component } from 'react'; +import classNames from 'classnames'; +import { observer } from '@ali/lowcode-editor-core'; +import { SettingTopEntry, SettingField } from '@ali/lowcode-designer'; +import StageChain from './stage-chain'; +import Stage from './stage'; +import { Skeleton } from '../../skeleton'; +import { Stage as StageWidget } from '../../widget/stage'; + +export const StageBoxDefaultProps = {}; + +export type StageBoxProps = typeof StageBoxDefaultProps & { + stageChain?: StageChain; + className?: string; + children: React.ReactNode; + skeleton: Skeleton; + // @todo to remove + target?: SettingTopEntry | SettingField; +}; + +type WillDetachMember = () => void; + +@observer +export default class StageBox extends Component { + static defaultProps = StageBoxDefaultProps; + + static displayName = 'StageBox'; + + private stageChain: StageChain; + private willDetach: WillDetachMember[] = []; + private shell: HTMLElement | null; + + constructor(props: StageBoxProps) { + super(props); + const { stageChain, children, skeleton } = this.props; + if (stageChain) { + this.stageChain = stageChain; + } else { + const stateName = skeleton.createStage({ + content: children, + }); + this.stageChain = new StageChain(skeleton.getStage(stateName as string) as StageWidget); + } + this.willDetach.push(this.stageChain.onStageChange(() => this.forceUpdate())); + } + + componentDidMount() { + const shell = this.shell; + + /** + * 向上层递归寻找 target + * @param node 节点 + * @returns 节点的 dataset.stageTarget 信息 + */ + const getTarget = (node: HTMLElement | null): null | string => { + if (!node || !shell?.contains(node) || (node.nodeName === 'A' && node.getAttribute('href'))) { + return null; + } + + const target = node.dataset ? node.dataset.stageTarget : null; + if (target) { + return target; + } + return getTarget(node.parentNode as HTMLElement); + }; + + const click = (e: MouseEvent) => { + const target = getTarget(e.target as HTMLElement); + if (!target) { + return; + } + + if (target === 'stageback') { + this.stageChain.stageBack(); + } else { + const { skeleton } = this.props; + this.stageChain.stagePush(skeleton.getStage(target)); + } + }; + + shell?.addEventListener('click', click, false); + this.willDetach.push(() => shell?.removeEventListener('click', click, false)); + } + + componentWillUnmount() { + if (this.willDetach) { + this.willDetach.forEach((off: () => void) => off()); + } + } + + render() { + const className = classNames('skeleton-stagebox', this.props.className); + const stage = this.stageChain.getCurrentStage(); + const refer = stage?.getRefer(); + + let contentCurrent = null; + let contentRefer = null; + + if (refer) { + contentCurrent = ; + contentRefer = ; + } else { + contentCurrent = ; + } + + return ( +
{ + this.shell = ref; + }} + className={className} + > + {contentRefer} + {contentCurrent} +
+ ); + } +} diff --git a/packages/editor-skeleton/src/components/stage-box/stage-chain.ts b/packages/editor-skeleton/src/components/stage-box/stage-chain.ts new file mode 100644 index 000000000..4653e0fe7 --- /dev/null +++ b/packages/editor-skeleton/src/components/stage-box/stage-chain.ts @@ -0,0 +1,52 @@ +import { EventEmitter } from 'events'; +import { Stage as StageWidget } from '../../widget/stage'; + +export default class StageChain { + private emitter: EventEmitter; + private stage: StageWidget; + + constructor(stage: StageWidget) { + this.emitter = new EventEmitter(); + this.stage = stage; + } + + stagePush(stage: StageWidget | null) { + if (!stage) return; + stage.setPrevious(this.stage); + stage.setReferLeft(this.stage); + this.stage = stage; + this.emitter.emit('stagechange'); + } + + stageBack() { + const stage = this.stage.getPrevious(); + if (!stage) return; + stage.setReferRight(this.stage); + this.stage = stage; + this.emitter.emit('stagechange'); + } + + /** + * 回到最开始 + */ + stageBackToRoot() { + while (!this.stage.isRoot) { + const stage = this.stage.getPrevious(); + if (!stage) return; + stage.setReferRight(this.stage); + this.stage = stage; + } + this.emitter.emit('stagechange'); + } + + getCurrentStage() { + return this.stage; + } + + onStageChange(func: () => void) { + this.emitter.on('stagechange', func); + return () => { + this.emitter.removeListener('stagechange', func); + }; + } +} diff --git a/packages/editor-skeleton/src/components/stage-box/stage.tsx b/packages/editor-skeleton/src/components/stage-box/stage.tsx new file mode 100644 index 000000000..859dd77a7 --- /dev/null +++ b/packages/editor-skeleton/src/components/stage-box/stage.tsx @@ -0,0 +1,93 @@ +// @todo 改成 hooks +import React, { Component } from 'react'; +import classNames from 'classnames'; +import Icons from '@ali/ve-icons'; +import { Stage as StageWidget } from '../../widget/stage'; + +export const StageDefaultProps = { + current: false, +}; + +export type StageProps = typeof StageDefaultProps & { + stage?: StageWidget; + current: boolean; + direction?: string; +}; + +export default class Stage extends Component { + static defaultProps = StageDefaultProps; + + private timer: number; + private additionClassName: string | null; + private shell: any; + + componentDidMount() { + this.doSkate(); + } + + componentDidUpdate() { + this.doSkate(); + } + + componentWillUnmount() { + window.clearTimeout(this.timer); + } + + doSkate() { + window.clearTimeout(this.timer); + if (this.additionClassName) { + this.timer = window.setTimeout(() => { + const elem = this.shell; + if (elem) { + if (this.props.current) { + elem.classList.remove(this.additionClassName); + } else { + elem.classList.add(this.additionClassName); + } + this.additionClassName = null; + } + }, 15); + } + } + + render() { + const { stage, current, direction } = this.props; + const content = stage?.getContent(); + + if (current) { + if (direction) { + this.additionClassName = `skeleton-stagebox-stagein-${direction}`; + } + } else if (direction) { + this.additionClassName = `skeleton-stagebox-stageout-${direction}`; + } + + const className = classNames( + 'skeleton-stagebox-stage', + { + 'skeleton-stagebox-refer': !current, + }, + this.additionClassName, + ); + + const stageBacker = stage?.hasBack() ? ( +
+ + {stage.title} + +
+ ) : null; + + return ( +
{ + this.shell = ref; + }} + className={className} + > + {stageBacker} +
{content}
+
+ ); + } +} diff --git a/packages/editor-skeleton/src/skeleton.ts b/packages/editor-skeleton/src/skeleton.ts index 6ed4ce842..01285a0f4 100644 --- a/packages/editor-skeleton/src/skeleton.ts +++ b/packages/editor-skeleton/src/skeleton.ts @@ -20,7 +20,7 @@ import PanelDock from './widget/panel-dock'; import Dock from './widget/dock'; import { Stage, StageConfig } from './widget/stage'; import { isValidElement } from 'react'; -import { isPlainObject } from '@ali/lowcode-utils'; +import { isPlainObject, uniqueId } from '@ali/lowcode-utils'; import { Divider } from '@alifd/next'; import { EditorConfig, PluginClassSet } from '@ali/lowcode-types'; @@ -248,6 +248,19 @@ export class Skeleton { return this.panels.get(name); } + getStage(name: string) { + return this.stages.container.get(name); + } + + createStage(config: any) { + const stage = this.add({ + name: uniqueId('stage'), + area: 'stages', + ...config, + }); + return stage?.getName(); + } + createContainer( name: string, handle: (item: any) => any, diff --git a/packages/editor-skeleton/src/widget/stage.ts b/packages/editor-skeleton/src/widget/stage.ts index a4bf483f2..a8b88914c 100644 --- a/packages/editor-skeleton/src/widget/stage.ts +++ b/packages/editor-skeleton/src/widget/stage.ts @@ -1,3 +1,4 @@ +import { uniqueId } from '@ali/lowcode-utils'; import Widget from './widget'; import { Skeleton } from '../skeleton'; import { WidgetConfig } from '../types';