diff --git a/packages/designer/src/builtins/drag-ghost/index.tsx b/packages/designer/src/builtins/drag-ghost/index.tsx index 29d65c752..9aa0546a2 100644 --- a/packages/designer/src/builtins/drag-ghost/index.tsx +++ b/packages/designer/src/builtins/drag-ghost/index.tsx @@ -1,5 +1,5 @@ import { Component } from 'react'; -import { observer, obx } from '../../../../globals'; +import { observer, obx, Title } from '../../../../globals'; import Designer from '../../designer/designer'; import { isDragNodeObject, DragObject, isDragNodeDataObject } from '../../designer/helper/dragon'; import './ghost.less'; @@ -53,7 +53,7 @@ export default class Ghost extends Component<{ designer: Designer }> { return dragObject.nodes.map(node => { const ghost = (
-
{node.title}
+ </div> ); return ghost; diff --git a/packages/designer/src/designer/component-meta.ts b/packages/designer/src/designer/component-meta.ts index b2b86f1dc..abfc72f1f 100644 --- a/packages/designer/src/designer/component-meta.ts +++ b/packages/designer/src/designer/component-meta.ts @@ -108,7 +108,8 @@ export class ComponentMeta { return this._title || this.componentName; } - get icon() { + @computed get icon() { + // give Slot default icon return ( this._transformedMetadata?.icon || (this.componentName === 'Page' ? IconPage : this.isContainer ? IconContainer : IconComponent) diff --git a/packages/designer/src/designer/document/node/exclusive-group.ts b/packages/designer/src/designer/document/node/exclusive-group.ts new file mode 100644 index 000000000..92233b6f2 --- /dev/null +++ b/packages/designer/src/designer/document/node/exclusive-group.ts @@ -0,0 +1,73 @@ +import { obx, computed, TitleContent } from '../../../../../globals'; +import { uniqueId } from '../../../../../utils/unique-id'; +import Node from './node'; +import { intl } from '../../../locale'; + +export default class ExclusiveGroup { + readonly isExclusiveGroup = true; + readonly id = uniqueId('cond-grp'); + @obx.val readonly children: Node[] = []; + + @obx private visibleIndex = 0; + @computed get document() { + return this.visibleNode.document; + } + @computed get zLevel() { + return this.visibleNode.zLevel; + } + @computed get length() { + return this.children.length; + } + @computed get visibleNode(): Node { + return this.children[this.visibleIndex]; + } + @computed get firstNode(): Node { + return this.children[0]!; + } + + add(node: Node) { + if (node.nextSibling && node.nextSibling.conditionGroup === this) { + const i = this.children.indexOf(node.nextSibling); + this.children.splice(i, 0, node); + } else { + this.children.push(node); + } + } + + remove(node: Node) { + const i = this.children.indexOf(node); + if (i > -1) { + this.children.splice(i, 1); + if (this.visibleIndex > i) { + this.visibleIndex -= 1; + } else if (this.visibleIndex >= this.children.length) { + this.visibleIndex = this.children.length - 1; + } + } + } + + setVisible(node: Node) { + const i = this.children.indexOf(node); + if (i > -1) { + this.visibleIndex = i; + } + } + + isVisible(node: Node) { + const i = this.children.indexOf(node); + return i === this.visibleIndex; + } + + readonly title: TitleContent; + + constructor(readonly name: string, title?: TitleContent) { + this.title = title || { + type: 'i18n', + intl: intl('Condition Group'), + }; + } +} + +export function isExclusiveGroup(obj: any): obj is ExclusiveGroup { + return obj && obj.isExclusiveGroup; +} diff --git a/packages/designer/src/designer/document/node/node-children.ts b/packages/designer/src/designer/document/node/node-children.ts index 232ef10fe..d0eb01792 100644 --- a/packages/designer/src/designer/document/node/node-children.ts +++ b/packages/designer/src/designer/document/node/node-children.ts @@ -5,12 +5,14 @@ export default class NodeChildren { @obx.val private children: Node[]; constructor(readonly owner: NodeParent, data: NodeData | NodeData[]) { this.children = (Array.isArray(data) ? data : [data]).map(child => { - const node = this.owner.document.createNode(child); - node.internalSetParent(this.owner); - return node; + return this.owner.document.createNode(child); }); } + interalInitParent() { + this.children.forEach(child => child.internalSetParent(this.owner)); + } + /** * 导出 schema * @param serialize 序列化,加 id 标识符,用于储存为操作记录 @@ -109,11 +111,24 @@ export default class NodeChildren { } // check condition group - node.conditionGroup = null; + if (node.conditionGroup) { + if ( + !( + // just sort at condition group + ( + (node.prevSibling && node.prevSibling.conditionGroup === node.conditionGroup) || + (node.nextSibling && node.nextSibling.conditionGroup === node.conditionGroup) + ) + ) + ) { + node.setConditionGroup(null); + } + } if (node.prevSibling && node.nextSibling) { const conditionGroup = node.prevSibling.conditionGroup; + // insert at condition group if (conditionGroup && conditionGroup === node.nextSibling.conditionGroup) { - node.conditionGroup = conditionGroup; + node.setConditionGroup(conditionGroup); } } } diff --git a/packages/designer/src/designer/document/node/node.ts b/packages/designer/src/designer/document/node/node.ts index 035e68d77..0af5522de 100644 --- a/packages/designer/src/designer/document/node/node.ts +++ b/packages/designer/src/designer/document/node/node.ts @@ -14,6 +14,7 @@ import { obx, computed, } from '../../../../../globals'; +import ExclusiveGroup, { isExclusiveGroup } from './exclusive-group'; /** * 基础节点 @@ -29,10 +30,10 @@ import { * condition * ------- future support ----- * conditionGroup - * x-title - * x-ignore - * x-locked - * x-hidden + * title + * ignored + * locked + * hidden */ export default class Node { /** @@ -80,7 +81,7 @@ export default class Node { if (this._parent) { return this._parent.zLevel + 1; } - return -1; + return 0; } @computed get title(): TitleContent { @@ -110,6 +111,7 @@ export default class Node { if (isNodeParent(this)) { _props = new Props(this, props, extras); this._children = new NodeChildren(this as NodeParent, children || []); + this._children.interalInitParent(); } else { _props = new Props(this, { children: isDOMText(children) || isJSExpression(children) ? children : '', @@ -138,6 +140,13 @@ export default class Node { } this._parent = parent; + if (parent && !this.conditionGroup) { + // initial conditionGroup + const grp = this.getExtraProp('conditionGroup', false)?.getAsString(); + if (grp) { + this.setConditionGroup(grp); + } + } } private _slotFor?: Prop | null = null; @@ -214,37 +223,53 @@ export default class Node { return slots; } - private _conditionGroup: string | null = null; - /** - * 条件组 - */ - get conditionGroup(): string | null { - if (this._conditionGroup) { - return this._conditionGroup; - } - // 如果 condition 有值,且没有 group - if (this._condition) { - return this.id; - } - return null; - } - set conditionGroup(val) { - this._conditionGroup = val; + @obx.ref private _conditionGroup: ExclusiveGroup | null = null; + get conditionGroup(): ExclusiveGroup | null { + return this._conditionGroup; } - private _condition: any; - /** - * - */ - get condition() { - if (this._condition == null) { + setConditionGroup(grp: ExclusiveGroup | string | null) { + if (!grp) { + this.getExtraProp('conditionGroup', false)?.remove(); if (this._conditionGroup) { - // FIXME: should be expression - return true; + this._conditionGroup.remove(this); + this._conditionGroup = null; } - return null; + return; } - return this._condition; + if (!isExclusiveGroup(grp)) { + if (this.prevSibling?.conditionGroup?.name === grp) { + grp = this.prevSibling.conditionGroup; + } else { + grp = new ExclusiveGroup(grp); + } + } + if (this._conditionGroup !== grp) { + this.getExtraProp('conditionGroup', true)?.setValue(grp.name); + if (this._conditionGroup) { + this._conditionGroup.remove(this); + } + this._conditionGroup = grp; + grp.add(this); + } + } + + @computed isConditionalVisible(): boolean | undefined { + return this._conditionGroup?.isVisible(this); + } + + setConditionalVisible() { + this._conditionGroup?.setVisible(this); + } + + @computed hasCondition() { + const v = this.getExtraProp('condition', false)?.getValue(); + return v != null && v !== ''; + } + + @computed hasLoop() { + const v = this.getExtraProp('loop', false)?.getValue(); + return v != null && v !== ''; } wrapWith(schema: NodeSchema) { diff --git a/packages/designer/src/locale/en-US.json b/packages/designer/src/locale/en-US.json index 722b8a4c6..9f5c5f234 100644 --- a/packages/designer/src/locale/en-US.json +++ b/packages/designer/src/locale/en-US.json @@ -1,4 +1,5 @@ { "copy": "Copy", - "remove": "Remove" + "remove": "Remove", + "Condition Group": "Condition Group" } diff --git a/packages/designer/src/locale/zh-CN.json b/packages/designer/src/locale/zh-CN.json index f90c1c8a8..39042c3af 100644 --- a/packages/designer/src/locale/zh-CN.json +++ b/packages/designer/src/locale/zh-CN.json @@ -1,4 +1,5 @@ { "copy": "复制", - "remove": "删除" + "remove": "删除", + "Condition Group": "条件组" } diff --git a/packages/editor/src/config/components.js b/packages/editor/src/config/components.js index f5712c6e0..596f984c8 100644 --- a/packages/editor/src/config/components.js +++ b/packages/editor/src/config/components.js @@ -14,6 +14,7 @@ import undoRedo from '../plugins/undoRedo'; import Designer from '../plugins/designer'; import logo from '../plugins/logo'; import save from '../plugins/save'; +import OutlineTree from '../../../plugin-outline-tree'; import PluginFactory from '../framework/pluginFactory'; @@ -22,6 +23,7 @@ export default { save: PluginFactory(save), designer: PluginFactory(Designer), settings: PluginFactory(Settings), + outlineTree: PluginFactory(OutlineTree), undoRedo: PluginFactory(undoRedo), topBalloonIcon: PluginFactory(topBalloonIcon), topDialogIcon: PluginFactory(topDialogIcon), diff --git a/packages/editor/src/config/skeleton.js b/packages/editor/src/config/skeleton.js index 6fc68aa2a..b624b8a41 100644 --- a/packages/editor/src/config/skeleton.js +++ b/packages/editor/src/config/skeleton.js @@ -238,6 +238,7 @@ export default { } ], rightArea: [ + /* { pluginKey: 'settings', type: 'Panel', @@ -246,52 +247,16 @@ export default { version: '^1.0.0' }, pluginProps: {} + },*/ + { + pluginKey: 'outlineTree', + type: 'Panel', + props: {}, + config: { + version: '^1.0.0' + }, + pluginProps: {} } - // { - // pluginKey: 'rightPanel1', - // type: 'TabPanel', - // props: { - // title: '样式' - // }, - // config: { - // version: '^1.0.0' - // }, - // pluginProps: {} - // }, - // { - // pluginKey: 'rightPanel2', - // type: 'TabPanel', - // props: { - // title: '属性', - // icon: 'dengpao' - // }, - // config: { - // version: '^1.0.0' - // }, - // pluginProps: {} - // }, - // { - // pluginKey: 'rightPanel3', - // type: 'TabPanel', - // props: { - // title: '事件' - // }, - // config: { - // version: '^1.0.0' - // }, - // pluginProps: {} - // }, - // { - // pluginKey: 'rightPanel4', - // type: 'TabPanel', - // props: { - // title: '数据' - // }, - // config: { - // version: '^1.0.0' - // }, - // pluginProps: {} - // } ], centerArea: [ { diff --git a/packages/editor/src/plugins/designer/index.tsx b/packages/editor/src/plugins/designer/index.tsx index b953f4b6e..1043308e3 100644 --- a/packages/editor/src/plugins/designer/index.tsx +++ b/packages/editor/src/plugins/designer/index.tsx @@ -81,7 +81,13 @@ const SCHEMA = { { componentName: 'Form.Item', props: { - label: '职业:', + label: { + type: 'JSSlot', + value: { + componentName: 'Div', + children: '职业:', + } + }, name: 'profession' }, children: [ @@ -127,7 +133,10 @@ const SCHEMA = { }, htmlType: 'submit' }, - children: '提交' + children: '提交', + condition: true, + loop: [1,2,3], + conditionGroup: '1' }, { componentName: 'Button', @@ -138,7 +147,9 @@ const SCHEMA = { }, htmlType: 'reset' }, - children: '重置' + children: '重置', + condition: false, + conditionGroup: '1' } ] } diff --git a/packages/globals/src/icons/clone.tsx b/packages/globals/src/icons/clone.tsx index 60b6b0dfc..12b65a9c9 100644 --- a/packages/globals/src/icons/clone.tsx +++ b/packages/globals/src/icons/clone.tsx @@ -1,6 +1,6 @@ -import { IconBase, IconBaseProps } from "./icon-base"; +import { IconBase, IconProps } from "./icon-base"; -export function IconClone(props: IconBaseProps) { +export function IconClone(props: IconProps) { return ( <IconBase viewBox="0 0 1024 1024" {...props}> <path d="M192 256.16C192 220.736 220.704 192 256.16 192h639.68C931.264 192 960 220.704 960 256.16v639.68A64.16 64.16 0 0 1 895.84 960H256.16A64.16 64.16 0 0 1 192 895.84V256.16z m64 31.584v576.512a32 32 0 0 0 31.744 31.744h576.512a32 32 0 0 0 31.744-31.744V287.744A32 32 0 0 0 864.256 256H287.744A32 32 0 0 0 256 287.744zM288 192v64h64V192H288z m128 0v64h64V192h-64z m128 0v64h64V192h-64z m128 0v64h64V192h-64z m128 0v64h64V192h-64z m96 96v64h64V288h-64z m0 128v64h64v-64h-64z m0 128v64h64v-64h-64z m0 128v64h64v-64h-64z m0 128v64h64v-64h-64z m-96 96v64h64v-64h-64z m-128 0v64h64v-64h-64z m-128 0v64h64v-64h-64z m-128 0v64h64v-64h-64z m-128 0v64h64v-64H288z m-96-96v64h64v-64H192z m0-128v64h64v-64H192z m0-128v64h64v-64H192z m0-128v64h64v-64H192z m0-128v64h64V288H192z m160 416c0-17.664 14.592-32 32.064-32h319.872a31.968 31.968 0 1 1 0 64h-319.872A31.968 31.968 0 0 1 352 704z m0-128c0-17.664 14.4-32 32.224-32h383.552c17.792 0 32.224 14.208 32.224 32 0 17.664-14.4 32-32.224 32H384.224A32.032 32.032 0 0 1 352 576z m0-128c0-17.664 14.4-32 32.224-32h383.552c17.792 0 32.224 14.208 32.224 32 0 17.664-14.4 32-32.224 32H384.224A32.032 32.032 0 0 1 352 448z m512 47.936V192h-64V159.968A31.776 31.776 0 0 0 768.032 128H160A31.776 31.776 0 0 0 128 159.968V768c0 17.92 14.304 31.968 31.968 31.968H192v64h303.936H128.128A63.968 63.968 0 0 1 64 799.872V128.128C64 92.704 92.48 64 128.128 64h671.744C835.296 64 864 92.48 864 128.128v367.808z"/> diff --git a/packages/globals/src/icons/component.tsx b/packages/globals/src/icons/component.tsx index c389eac39..a612a5be3 100644 --- a/packages/globals/src/icons/component.tsx +++ b/packages/globals/src/icons/component.tsx @@ -1,6 +1,6 @@ -import { IconBase, IconBaseProps } from "./icon-base"; +import { IconBase, IconProps } from "./icon-base"; -export function IconComponent(props: IconBaseProps) { +export function IconComponent(props: IconProps) { return ( <IconBase viewBox="0 0 1024 1024" {...props}> <path d="M783.5648 437.4528h-18.0224V336.6912c0-43.8272-35.6352-79.4624-79.4624-79.4624h-110.592V241.664c0-90.9312-73.728-164.6592-164.6592-164.6592-90.9312 0-164.6592 73.728-164.6592 164.6592v15.5648H155.2384c-43.8272 0-79.4624 35.6352-79.4624 79.4624v131.4816c0 16.7936 13.9264 30.72 30.72 30.72h56.1152c56.9344 0 103.2192 46.2848 103.2192 103.2192s-46.2848 103.2192-103.2192 103.2192H106.496c-16.7936 0-30.72 13.9264-30.72 30.72v131.4816c0 43.8272 35.6352 79.4624 79.4624 79.4624h531.2512c43.8272 0 79.4624-35.6352 79.4624-79.4624v-100.7616h18.0224c90.9312 0 164.6592-73.728 164.6592-164.6592-0.4096-90.9312-74.1376-164.6592-165.0688-164.6592z m0 267.8784h-48.7424c-16.7936 0-30.72 13.9264-30.72 30.72v131.4816c0 9.8304-8.192 18.0224-18.0224 18.0224H155.2384c-9.8304 0-18.0224-8.192-18.0224-18.0224v-100.7616h25.3952c90.9312 0 164.6592-73.728 164.6592-164.6592 0-90.9312-73.728-164.6592-164.6592-164.6592h-25.3952V336.6912c0-9.8304 8.192-18.0224 18.0224-18.0224h121.6512c16.7936 0 30.72-13.9264 30.72-30.72V241.664c0-56.9344 46.2848-103.2192 103.2192-103.2192s103.2192 46.2848 103.2192 103.2192v46.2848c0 16.7936 13.9264 30.72 30.72 30.72h141.312c9.8304 0 18.0224 8.192 18.0224 18.0224v131.4816c0 16.7936 13.9264 30.72 30.72 30.72h48.7424c56.9344 0 103.2192 46.2848 103.2192 103.2192s-46.2848 103.2192-103.2192 103.2192z" /> diff --git a/packages/globals/src/icons/container.tsx b/packages/globals/src/icons/container.tsx index ceff81183..c6a928ec5 100644 --- a/packages/globals/src/icons/container.tsx +++ b/packages/globals/src/icons/container.tsx @@ -1,6 +1,6 @@ -import { IconBase, IconBaseProps } from "./icon-base"; +import { IconBase, IconProps } from "./icon-base"; -export function IconContainer(props: IconBaseProps) { +export function IconContainer(props: IconProps) { return ( <IconBase viewBox="0 0 1024 1024" {...props}> <path d="M800 800h64v64h-64v-64z m-128 0h64v64h-64v-64z m-128 0h64v64h-64v-64z m-128 0h64v64h-64v-64z m-256 0h64v64h-64v-64z m0-640h64v64h-64v-64z m128 640h64v64h-64v-64zM160 672h64v64h-64v-64z m0-128h64v64h-64v-64z m0-128h64v64h-64v-64z m0-128h64v64h-64v-64z m640 384h64v64h-64v-64z m0-128h64v64h-64v-64z m0-128h64v64h-64v-64z m0-128h64v64h-64v-64z m0-128h64v64h-64v-64z m-128 0h64v64h-64v-64z m-128 0h64v64h-64v-64z m-128 0h64v64h-64v-64z m-128 0h64v64h-64v-64z" /> diff --git a/packages/globals/src/icons/hidden.tsx b/packages/globals/src/icons/hidden.tsx index 5df9004cb..85c38d177 100644 --- a/packages/globals/src/icons/hidden.tsx +++ b/packages/globals/src/icons/hidden.tsx @@ -1,6 +1,6 @@ -import { IconBase, IconBaseProps } from "./icon-base"; +import { IconBase, IconProps } from "./icon-base"; -export function IconHidden(props: IconBaseProps) { +export function IconHidden(props: IconProps) { return ( <IconBase viewBox="0 0 1024 1024" {...props}> <path d="M183.423543 657.078213l163.499771-98.484012c-4.233418-14.908548-6.646374-30.585599-6.646374-46.852074 0-94.665033 76.739778-171.404812 171.404812-171.404812 45.983287 0 87.641059 18.20871 118.42518 47.679929l129.791042-78.17957c-73.254398-41.73145-157.866471-65.812915-248.216221-65.812915-192.742792 0-360.068705 108.505249-444.453604 267.715321C96.636944 567.228859 136.301316 616.355743 183.423543 657.078213zM841.253886 367.552144l-164.382884 99.015108c3.934612 14.415314 6.215562 29.513174 6.215562 45.174875 0 94.665033-76.739778 171.404812-171.404812 171.404812-45.361117 0-86.484723-17.747199-117.142977-46.515407l-129.419582 77.955466c72.874751 41.149189 156.893306 64.871473 246.563582 64.871473 192.742792 0 360.068705-108.505249 444.453604-267.717368C927.000805 456.773188 887.794875 408.054603 841.253886 367.552144zM420.280042 511.741104c0 0.550539 0.152473 1.060145 0.161682 1.608637l135.080511-81.366146c-13.065574-7.198959-27.854395-11.658528-43.826158-11.658528C461.20922 420.325068 420.280042 461.254246 420.280042 511.741104zM447.739441 576.947198l69.02098-41.574884L948.364369 275.395234c10.812253-6.512321 14.297634-20.558222 7.785314-31.369452-6.512321-10.812253-20.556175-14.296611-31.368428-7.785314L575.654762 446.537056l0 0-151.20577 91.078345 0 0L75.027787 748.090043c-10.812253 6.512321-14.297634 20.556175-7.785314 31.368428 6.512321 10.812253 20.556175 14.297634 31.369452 7.785314L447.739441 576.947198 447.739441 576.947198zM511.696078 603.157139c50.487881 0 91.416036-40.928155 91.416036-91.416036 0-0.549515-0.152473-1.057075-0.161682-1.605567l-135.079488 81.364099C480.935494 598.699618 495.724315 603.157139 511.696078 603.157139z" /> diff --git a/packages/globals/src/icons/icon-base.tsx b/packages/globals/src/icons/icon-base.tsx index 843c3d700..778761ef0 100644 --- a/packages/globals/src/icons/icon-base.tsx +++ b/packages/globals/src/icons/icon-base.tsx @@ -8,16 +8,24 @@ const SizePresets: any = { xlarge: 30, }; -export interface IconBaseProps { +export interface IconProps { className?: string; fill?: string; size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | number; - viewBox: string; children?: ReactNode; style?: object; -}; +} -export function IconBase({ fill, size = 'medium', viewBox, style, children, ...props }: IconBaseProps) { +export function IconBase({ + fill, + size = 'medium', + viewBox, + style, + children, + ...props +}: IconProps & { + viewBox: string; +}) { if (SizePresets.hasOwnProperty(size)) { size = SizePresets[size]; } @@ -31,10 +39,11 @@ export function IconBase({ fill, size = 'medium', viewBox, style, children, ...p viewBox={viewBox} {...props} style={{ - verticalAlign: 'middle', color: fill, ...style, }} - >{children}</svg> + > + {children} + </svg> ); } diff --git a/packages/globals/src/icons/page.tsx b/packages/globals/src/icons/page.tsx index 6fd182bf4..8f5a770f6 100644 --- a/packages/globals/src/icons/page.tsx +++ b/packages/globals/src/icons/page.tsx @@ -1,6 +1,6 @@ -import { IconBase, IconBaseProps } from "./icon-base"; +import { IconBase, IconProps } from "./icon-base"; -export function IconPage(props: IconBaseProps) { +export function IconPage(props: IconProps) { return ( <IconBase viewBox="0 0 1024 1024" {...props}> <path d="M381.6 864H162.4c-6.9 0-12.4 4.6-12.4 10.3v19.3c0 5.7 5.6 10.3 12.4 10.3h219.1c6.8 0 12.4-4.6 12.4-10.3v-19.3c0.1-5.7-5.5-10.3-12.3-10.3zM382 780.6H162c-6.9 0-12.5 4.6-12.5 10.3v19.3c0 5.7 5.6 10.3 12.5 10.3h220c6.9 0 12.5-4.6 12.5-10.3v-19.3c0-5.7-5.6-10.3-12.5-10.3zM162.4 737.2h219.1c6.8 0 12.4-4.6 12.4-10.3v-19.3c0-5.7-5.6-10.3-12.4-10.3H162.4c-6.9 0-12.4 4.6-12.4 10.3v19.3c0 5.7 5.6 10.3 12.4 10.3z" /> diff --git a/packages/globals/src/icons/remove.tsx b/packages/globals/src/icons/remove.tsx index 5caf18f9e..4e454ac65 100644 --- a/packages/globals/src/icons/remove.tsx +++ b/packages/globals/src/icons/remove.tsx @@ -1,6 +1,6 @@ -import { IconBase, IconBaseProps } from './icon-base'; +import { IconBase, IconProps } from './icon-base'; -export function IconRemove(props: IconBaseProps) { +export function IconRemove(props: IconProps) { return ( <IconBase viewBox="0 0 1024 1024" {...props}> <path d="M224 256v639.84A64 64 0 0 0 287.84 960h448.32A64 64 0 0 0 800 895.84V256h64a32 32 0 1 0 0-64H160a32 32 0 1 0 0 64h64zM384 96c0-17.664 14.496-32 31.904-32h192.192C625.696 64 640 78.208 640 96c0 17.664-14.496 32-31.904 32H415.904A31.872 31.872 0 0 1 384 96z m-96 191.744C288 270.208 302.4 256 320.224 256h383.552C721.6 256 736 270.56 736 287.744v576.512C736 881.792 721.6 896 703.776 896H320.224A32.224 32.224 0 0 1 288 864.256V287.744zM352 352c0-17.696 14.208-32.032 32-32.032 17.664 0 32 14.24 32 32v448c0 17.664-14.208 32-32 32-17.664 0-32-14.24-32-32V352z m128 0c0-17.696 14.208-32.032 32-32.032 17.664 0 32 14.24 32 32v448c0 17.664-14.208 32-32 32-17.664 0-32-14.24-32-32V352z m128 0c0-17.696 14.208-32.032 32-32.032 17.664 0 32 14.24 32 32v448c0 17.664-14.208 32-32 32-17.664 0-32-14.24-32-32V352z" /> diff --git a/packages/globals/src/icons/setting.tsx b/packages/globals/src/icons/setting.tsx index e555cdf43..8b1da4410 100644 --- a/packages/globals/src/icons/setting.tsx +++ b/packages/globals/src/icons/setting.tsx @@ -1,6 +1,6 @@ -import { IconBase, IconBaseProps } from './icon-base'; +import { IconBase, IconProps } from './icon-base'; -export function IconSetting(props: IconBaseProps) { +export function IconSetting(props: IconProps) { return ( <IconBase viewBox="0 0 1024 1024" {...props}> <path d="M965.824 405.952a180.48 180.48 0 0 1-117.12-85.376 174.464 174.464 0 0 1-16-142.08 22.208 22.208 0 0 0-7.04-23.552 480.576 480.576 0 0 0-153.6-89.216 23.104 23.104 0 0 0-24.32 5.76 182.208 182.208 0 0 1-135.68 57.92 182.208 182.208 0 0 1-133.12-56.64 23.104 23.104 0 0 0-26.88-7.04 478.656 478.656 0 0 0-153.6 89.856 22.208 22.208 0 0 0-7.04 23.552 174.464 174.464 0 0 1-16 141.44A180.48 180.48 0 0 1 58.24 405.952a22.4 22.4 0 0 0-17.28 17.792 455.08 455.08 0 0 0 0 176.512 22.4 22.4 0 0 0 17.28 17.792 180.48 180.48 0 0 1 117.12 84.736c25.408 42.944 31.232 94.592 16 142.08a22.208 22.208 0 0 0 7.04 23.552A480.576 480.576 0 0 0 352 957.632h7.68a23.04 23.04 0 0 0 16.64-7.04 184.128 184.128 0 0 1 266.944 0c6.592 8.96 18.752 11.968 28.8 7.04a479.36 479.36 0 0 0 156.16-88.576 22.208 22.208 0 0 0 7.04-23.552 174.464 174.464 0 0 1 13.44-142.72 180.48 180.48 0 0 1 117.12-84.736 22.4 22.4 0 0 0 17.28-17.792 452.613 452.613 0 0 0 0-176.512 23.04 23.04 0 0 0-17.28-17.792z m-42.88 169.408a218.752 218.752 0 0 0-128 98.112 211.904 211.904 0 0 0-21.76 156.736 415.936 415.936 0 0 1-112 63.68 217.472 217.472 0 0 0-149.12-63.68 221.312 221.312 0 0 0-149.12 63.68 414.592 414.592 0 0 1-112-63.68c12.8-53.12 4.288-109.12-23.68-156.096A218.752 218.752 0 0 0 101.12 575.36a386.176 386.176 0 0 1 0-127.36 218.752 218.752 0 0 0 128-98.112c27.2-47.552 34.944-103.68 21.76-156.8a415.296 415.296 0 0 1 112-63.68A221.44 221.44 0 0 0 512 187.392a218.24 218.24 0 0 0 149.12-57.984 413.952 413.952 0 0 1 112 63.744 211.904 211.904 0 0 0 23.04 156.096 218.752 218.752 0 0 0 128 98.112 386.65 386.65 0 0 1 0 127.36l-1.28 0.64z" /> diff --git a/packages/globals/src/intl/index.tsx b/packages/globals/src/intl/index.tsx index 47064b08e..2b7619d5c 100644 --- a/packages/globals/src/intl/index.tsx +++ b/packages/globals/src/intl/index.tsx @@ -62,6 +62,9 @@ class Intl extends PureComponent<{ data: any; params?: object }> { export function intl(data: any, params?: object): ReactNode { if (isI18nData(data)) { + if (data.intl) { + return data.intl; + } return <Intl data={data} params={params} />; } return data; diff --git a/packages/globals/src/types/i18n.ts b/packages/globals/src/types/i18n.ts index 8a3f6e558..44f8ef855 100644 --- a/packages/globals/src/types/i18n.ts +++ b/packages/globals/src/types/i18n.ts @@ -1,6 +1,9 @@ +import { ReactNode } from 'react'; + export interface I18nData { type: 'i18n'; - [key: string]: string; + intl?: ReactNode; + [key: string]: any; } // type checks diff --git a/packages/plugin-outline-pane/src/helper/is-container.ts b/packages/plugin-outline-pane/src/helper/is-container.ts deleted file mode 100644 index e8474ad6a..000000000 --- a/packages/plugin-outline-pane/src/helper/is-container.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { INode, isElementNode, isRootNode } from '../../../../document/node'; - -export function isContainer(node: INode): boolean { - if (isRootNode(node)) { - return true; - } - if (isElementNode(node)) { - // TODO: check from prototype - // block Fragment - return true; - } - return false; -} diff --git a/packages/plugin-outline-pane/src/icons/caret-down.svg b/packages/plugin-outline-pane/src/icons/caret-down.svg deleted file mode 100644 index 68139c5eb..000000000 --- a/packages/plugin-outline-pane/src/icons/caret-down.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1562677537258" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="907" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M965.46812628 219.26174551H58.53187372c-27.20256421 0-42.39181327 28.72148912-25.54555523 48.3294288l453.46812628 525.82418542c12.97990373 15.05116498 37.97312263 15.05116498 51.09111046 0L991.01368151 267.59117431c16.84625804-19.6079397 1.65700898-48.3294288-25.54555523-48.3294288z" fill="#ffffff" p-id="908"></path></svg> \ No newline at end of file diff --git a/packages/plugin-outline-pane/src/icons/caret-right.svg b/packages/plugin-outline-pane/src/icons/caret-right.svg deleted file mode 100644 index 150293223..000000000 --- a/packages/plugin-outline-pane/src/icons/caret-right.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1562677542938" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1019" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M793.41535973 486.45444477L267.59117431 32.98631849c-19.6079397-16.84625804-48.3294288-1.65700898-48.3294288 25.54555523v906.93625256c0 27.20256421 28.72148912 42.39181327 48.3294288 25.54555523l525.82418542-453.46812628c15.05116498-12.97990373 15.05116498-38.11120672 0-51.09111046z" fill="#ffffff" p-id="1020"></path></svg> \ No newline at end of file diff --git a/packages/plugin-outline-pane/src/locale/en-US.json b/packages/plugin-outline-pane/src/locale/en-US.json deleted file mode 100644 index 666810c46..000000000 --- a/packages/plugin-outline-pane/src/locale/en-US.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Designer not found": "Designer not found", - "No opened document": "No opened document", -} diff --git a/packages/plugin-outline-pane/src/locale/zh-CN.json b/packages/plugin-outline-pane/src/locale/zh-CN.json deleted file mode 100644 index d75772bdf..000000000 --- a/packages/plugin-outline-pane/src/locale/zh-CN.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Designer not found": "未发现设计器模块", - "No opened document": "没有打开的文档" -} diff --git a/packages/plugin-outline-pane/src/views/pane.less b/packages/plugin-outline-pane/src/views/pane.less deleted file mode 100644 index 001de23f1..000000000 --- a/packages/plugin-outline-pane/src/views/pane.less +++ /dev/null @@ -1,251 +0,0 @@ -/* 面板背景的颜色 */ -@pane-bgcolor: #333131; // #1a1c23; -/* 标题背景色 */ -//@title-bgcolor: var(--pane-bg-color; // backup rgba(0, 0, 0, 0.2); -/* 标题边框色 */ -@title-bdcolor: transparent; -@title-selectedcolor: #111; -@section-bgcolor: transparent; -@section-bdcolor: rgba(0, 0, 0, 0.1); -/* 文字颜色 */ -@text-color: #ffffff; - -.hidden { - display: none; -} - -.my-outline-pane { - top: 0; - left: 0; - height: 100%; - width: 100%; - position: absolute; - - > .tree-scroll-container { - top: 0; - left: 0; - bottom: 0; - right: 0; - position: absolute; - overflow: auto; - } -} - -.my-outline-tree { - overflow: hidden; - margin-bottom: 20px; - padding-left: 5px; - - // 禁用 Text Select - user-select: none; - - .insertion { - pointer-events: none !important; - border: 1px dashed var(--color-brand-light); - height: 25px; - transform: translateZ(0); - } - - .condition-flow-container { - @bd-setting: 1px solid #7b605b; - border-top: @bd-setting; - border-bottom: @bd-setting; - position: relative; - - &:before { - position: absolute; - display: block; - width: 0; - border-left: @bd-setting; - height: 100%; - top: 0; - left: 0; - content: ' '; - z-index: 2; - } - } - - .tree-node { - color: rgb(217, 217, 217); - - .c-control-flow-title { - text-align: center; - background-color: #7b605b; - height: 14px; - > b { - transform: scale(0.75); - transform-origin: top; - text-shadow: 0px 0px 2px black; - display: block; - } - } - - .tree-node-collapsed-icon { - transition: transform 0.01s; - margin-left: 4px; - filter: opacity(0.5); - - & > svg { - width: 8px; - height: 8px; - } - - &:hover { - filter: opacity(1); - } - } - - .tree-node-icon { - transform: translateZ(0); - display: flex; - align-items: center; - margin-right: 5px; - margin-left: 5px; - - & > svg { - width: 16px; - height: 16px; - } - } - - .tree-node-ignored-icon { - display: none; - position: absolute; - right: 8px; - top: 6px; - } - - .tree-node-title { - font-size: var(--font-size-text); - padding: 0; - cursor: pointer; - background: var(--pane-bg-color); - display: flex; - align-items: center; - position: relative; - transform: translateZ(0); - - .tree-node-title-inner { - flex: 1; - height: 26px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - display: flex; - align-items: center; - } - - .tree-node-title-input { - flex: 1; - border: none; - background-color: var(--pane-bg-color); - color: var(--color-pane-label); - height: 24px; - line-height: 24px; - } - - .tree-node-title-text { - flex: 1; - color: rgb(217, 217, 217); - // problem - //width: 100%; - //height: 100%; - display: flex; - align-items: center; - - &.x-for-text { - color: #9370db; - } - - &.x-if-text { - color: #ff6308; - } - - .info { - margin-left: 10px; - } - - .editable { - display: flex; - justify-content: space-between; - } - } - } - - &.expanded { - > .tree-node-title > .tree-node-collapsed-icon { - transform-origin: center; - transform: rotate(90deg); - } - //> .branches { - // > .tree-node > .tree-node-title > .tree-node-icon { - // margin-left: 9px; - // } - //} - } - - &.hover { - & > .tree-node-title { - background: @title-selectedcolor * 1.6; - - .tree-node-ignored-icon { - display: block; - } - } - } - - // 忽略节点处理 - &.ignored { - .tree-node-title-text { - color: #9b9b9b; - } - - .tree-node-collapsed-icon, - .tree-node-ignored-icon { - display: block; - filter: opacity(0.5); - } - } - - // 选中节点处理 - &.selected { - & > .tree-node-title { - background: @title-selectedcolor; - } - } - - // 处理拖入节点 - &.dropping { - & > .tree-node-title { - background: @title-selectedcolor * 0.75; - } - - & > .branches:before { - border-left: 1px solid var(--color-brand-light); - } - } - - .branches { - padding-left: 12px; - position: relative; - - &:before { - position: absolute; - display: block; - width: 0; - border-left: 0.5px solid rgba(149, 216, 160, 0.25); - height: 100%; - top: 0; - left: 10px; - content: ' '; - z-index: 2; - } - - &.x-flow { - &:before { - border-left: 1px solid #ff6308; - } - } - } - } -} diff --git a/packages/plugin-outline-pane/src/views/tree-branches.tsx b/packages/plugin-outline-pane/src/views/tree-branches.tsx deleted file mode 100644 index c60df167a..000000000 --- a/packages/plugin-outline-pane/src/views/tree-branches.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { observer } from '../../../globals/src'; - -@observer -export default class TreeBranches extends Component<TreeNodeProps> { - shouldComponentUpdate() { - return false; - } - - render() { - const treeNode = this.props.treeNode; - const { expanded } = treeNode; - - if (!expanded) { - return null; - } - - const branchClassName = classNames({ - branches: !isRootNode(treeNode.node), - // 'x-branch': treeNode.hasXIf() && treeNode.branchIndex !== treeNode.branchNode!.children.length - 1, - }); - - let children: any = []; - - if (treeNode.hasChildren() /* || node.hasSlots() */) { - children = treeNode.children.map((child: TreeNode) => { - if (child.hasXIf()) { - if (child.flowIndex === 0) { - const conditionFlowContainer = classNames('condition-group-container', { - hidden: child.hidden, - }); - return ( - <div key={child.id} className={conditionFlowContainer} data-id={child.id}> - <div className="c-control-flow-title"><b>Condition Flow</b></div> - {child.conditionGroup!.children.map(c => { - return <TreeNodeView key={c.id} treeNode={tree.getTreeNode(c)} />; - })} - </div> - ); - } else { - return null; - } - } - return <TreeNodeView key={child.id} treeNode={child} />; - }); - } - if (treeNode.dropIndex != null) { - children.splice( - treeNode.dropIndex, - 0, - <div key="insertion" ref={ref => tree.mountInsertion(ref)} className="insertion" />, - ); - } - - return children.length > 0 && <div className={branchClassName}>{children}</div>; - } -} diff --git a/packages/plugin-outline-pane/src/views/tree-node-icon-view.tsx b/packages/plugin-outline-pane/src/views/tree-node-icon-view.tsx deleted file mode 100644 index 6b87e60dc..000000000 --- a/packages/plugin-outline-pane/src/views/tree-node-icon-view.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import DIVIcon from 'my-icons/container.svg'; -import IMGIcon from 'my-icons/image.svg'; -import { observer } from '@ali/recore'; - -@observer -export default class TreeNodeIconView extends React.Component<{ tagName: string }> { - shouldComponentUpdate(): boolean { - return false; - } - - render() { - const { tagName } = this.props; - switch (tagName) { - case 'img': { - console.log('>>> tag:', tagName); - return <IMGIcon />; - } - default: - return <DIVIcon />; - } - } -} diff --git a/packages/plugin-outline-pane/src/views/tree-node.tsx b/packages/plugin-outline-pane/src/views/tree-node.tsx deleted file mode 100644 index 12300b4d8..000000000 --- a/packages/plugin-outline-pane/src/views/tree-node.tsx +++ /dev/null @@ -1,58 +0,0 @@ - - -export interface TreeNodeProps { - treeNode: TreeNode; -} - -@observer -export default class TreeNodeView extends Component<TreeNodeProps> { - shouldComponentUpdate() { - return false; - } - - render() { - const { treeNode } = this.props; - const className = classNames('tree-node', { - // 是否展开 - expanded: treeNode.expanded, - // 是否悬停 - hover: treeNode.hover, - // 是否选中 - selected: treeNode.selected, - // 是否隐藏 - hidden: treeNode.hidden, - // 是否忽略的 - ignored: treeNode.ignored, - // 是否锁定的 - locked: treeNode.locked, - // 是否投放响应 - dropping: treeNode.dropIndex != null, - // 是否? - highlight: treeNode.isDropContainer() && treeNode.dropIndex == null, - }); - - return ( - <div className={className} data-id={treeNode.id}> - <TreeTitle treeNode={treeNode} /> - <TreeBranches treeNode={treeNode} /> - </div> - ); - } -} - -export function findTargetByEvent(e: MouseEvent): HTMLElement | null { - return (e.target as HTMLElement).closest('.tree-node') as HTMLElement; -} - -export function getNodeIDFromTarget(target: HTMLElement): string | null { - return target.getAttribute('data-id'); -} - -export function getNodeIDByEvent(e: MouseEvent): string | null { - const target = findTargetByEvent(e); - if (target) { - return getNodeIDFromTarget(target); - } - - return null; -} diff --git a/packages/plugin-outline-pane/src/views/tree-title.tsx b/packages/plugin-outline-pane/src/views/tree-title.tsx deleted file mode 100644 index 19125d7a6..000000000 --- a/packages/plugin-outline-pane/src/views/tree-title.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { observer } from '@ali/recore'; -import React, { Component, KeyboardEvent } from 'react'; -import classNames from 'classnames'; -import ElementNode from '../../../../document/node/element-node'; -import { isElementNode } from '../../../../document/node'; -import { TreeNodeProps } from './tree-node'; -import TreeNodeIconView from './tree-node-icon-view'; -import CollapsedIcon from '../icons/caret-right.svg'; -import EyeCloseIcon from 'my-icons/eye-close.svg'; - -interface IState { - editing: boolean; -} - -@observer -export default class TreeNodeTitle extends Component<TreeNodeProps, IState> { - - private inputRef = React.createRef<HTMLInputElement>(); - - constructor(props: TreeNodeProps) { - super(props); - this.state = { - editing: false, - }; - } - - toggleIgnored() { - const treeNode = this.props.treeNode; - const node = treeNode.node as ElementNode; - if (treeNode.ignored) { - node.getDirective('x-ignore').remove(); - } else { - node.getDirective('x-ignore').value = true; - } - } - - toggleExpanded() { - const treeNode = this.props.treeNode; - const { expanded } = treeNode; - treeNode.expanded = !expanded; - } - - renderExpandIcon() { - const node = this.props.treeNode; - - if (!node.expandable) { - return null; - } - - return ( - <div - className="tree-node-collapsed-icon" - onClick={e => { - e.stopPropagation(); - this.toggleExpanded(); - }} - > - <CollapsedIcon /> - </div> - ); - } - - setTitle(xtitle: string = '') { - const { treeNode } = this.props; - const node = treeNode.node as ElementNode; - const title = node.getProp('x-title'); - if (xtitle && xtitle !== node.tagName) { - title.code = `"${xtitle}"`; - } else { - title.remove(); - } - } - - enableEdit = () => { - this.setState({ - editing: true, - }); - } - - cancelEdit() { - this.setState({ - editing: false, - }); - } - - saveEdit = () => { - const { current } = this.inputRef; - - if (current) { - this.setTitle(current.value); - } - - this.cancelEdit(); - } - - handleKeyUp(e: KeyboardEvent<HTMLInputElement>) { - if (e.keyCode === 13) { - this.saveEdit(); - } - if (e.keyCode === 27) { - this.cancelEdit(); - } - } - - componentDidUpdate() { - const { current } = this.inputRef; - if (current) { - current.select(); - } - } - - render() { - const { treeNode } = this.props; - const { editing } = this.state; - const { title } = treeNode; - const depth = treeNode.depth; - const indent = depth * 12; - - const titleClassName = classNames('tree-node-title'); - const titleTextClassName = classNames('tree-node-title-text', { - 'x-if-text': treeNode.hasXIf(), - 'x-for-text': treeNode.hasXFor(), - }); - const xForValue = treeNode.xForValue; - - return ( - <div - className={titleClassName} - ref={ref => treeNode.mount(ref)} - style={{ paddingLeft: indent, marginLeft: -indent }} - > - {this.renderExpandIcon()} - <div className="tree-node-icon"> - <TreeNodeIconView tagName={treeNode.node.tagName} /> - </div> - <div className="tree-node-title-inner" onDoubleClick={this.enableEdit}> - { - editing ? - <input - className="tree-node-title-input" - defaultValue={title.label} - onBlur={this.saveEdit} - onKeyUp={e => {this.handleKeyUp(e)}} - ref={this.inputRef} - /> - : - <div className={titleTextClassName}> - {title.label} - {xForValue && ( - <span className="info"> - (x <b>{xForValue.length}</b>) - </span> - )} - {treeNode.hasXIf() && ( - <span className="info"> - <b>{treeNode.flowHidden ? '' : '(visible)'}</b> - </span> - )} - </div> - } - - </div> - <div className="tree-node-ignored-icon"> - {isElementNode(treeNode.node) && !editing && ( - <EyeCloseIcon - onClick={(e: MouseEvent) => { - e.stopPropagation(); - this.toggleIgnored(); - }} - /> - )} - </div> - </div> - ); - } -} diff --git a/packages/plugin-outline-pane/.eslintignore b/packages/plugin-outline-tree/.eslintignore similarity index 100% rename from packages/plugin-outline-pane/.eslintignore rename to packages/plugin-outline-tree/.eslintignore diff --git a/packages/plugin-outline-pane/.eslintrc b/packages/plugin-outline-tree/.eslintrc similarity index 100% rename from packages/plugin-outline-pane/.eslintrc rename to packages/plugin-outline-tree/.eslintrc diff --git a/packages/plugin-outline-pane/.prettierrc b/packages/plugin-outline-tree/.prettierrc similarity index 100% rename from packages/plugin-outline-pane/.prettierrc rename to packages/plugin-outline-tree/.prettierrc diff --git a/packages/plugin-outline-pane/README.md b/packages/plugin-outline-tree/README.md similarity index 100% rename from packages/plugin-outline-pane/README.md rename to packages/plugin-outline-tree/README.md diff --git a/packages/plugin-outline-pane/package.json b/packages/plugin-outline-tree/package.json similarity index 95% rename from packages/plugin-outline-pane/package.json rename to packages/plugin-outline-tree/package.json index c34c3f4da..5301bced4 100644 --- a/packages/plugin-outline-pane/package.json +++ b/packages/plugin-outline-tree/package.json @@ -1,5 +1,5 @@ { - "name": "@ali/lowcode-plugin-outline-pane", + "name": "@ali/lowcode-plugin-outline-tree", "version": "0.0.0", "description": "xxx for Ali lowCode engine", "main": "src/index.ts", diff --git a/packages/plugin-outline-pane/src/README.md b/packages/plugin-outline-tree/src/README.md similarity index 100% rename from packages/plugin-outline-pane/src/README.md rename to packages/plugin-outline-tree/src/README.md diff --git a/packages/plugin-outline-pane/src/helper/dwell-timer.ts b/packages/plugin-outline-tree/src/helper/dwell-timer.ts similarity index 100% rename from packages/plugin-outline-pane/src/helper/dwell-timer.ts rename to packages/plugin-outline-tree/src/helper/dwell-timer.ts diff --git a/packages/plugin-outline-pane/src/helper/x-axis-tracker.ts b/packages/plugin-outline-tree/src/helper/x-axis-tracker.ts similarity index 100% rename from packages/plugin-outline-pane/src/helper/x-axis-tracker.ts rename to packages/plugin-outline-tree/src/helper/x-axis-tracker.ts diff --git a/packages/plugin-outline-tree/src/icons/arrow-right.tsx b/packages/plugin-outline-tree/src/icons/arrow-right.tsx new file mode 100644 index 000000000..f28ef7f00 --- /dev/null +++ b/packages/plugin-outline-tree/src/icons/arrow-right.tsx @@ -0,0 +1,11 @@ +import { IconBase, IconProps } from '../../../globals'; + +export function IconArrowRight(props: IconProps) { + return ( + <IconBase viewBox="0 0 1024 1024" {...props}> + <path d="M512.002047 771.904425c-10.152221 0.518816-20.442588-2.800789-28.202319-10.598382L77.902254 315.937602c-14.548344-14.618952-14.548344-38.318724 0-52.933583 14.544251-14.614859 38.118156-14.614859 52.662407 0l381.437385 418.531212L893.432269 263.004019c14.544251-14.614859 38.125319-14.614859 52.662407 0 14.552437 14.614859 14.552437 38.314631 0 52.933583L540.205389 761.307066C532.451798 769.103636 522.158361 772.424264 512.002047 771.904425z"/> + </IconBase> + ); +} + +IconArrowRight.displayName = 'IconArrowRight'; diff --git a/packages/plugin-outline-pane/src/icons/border-outer.svg b/packages/plugin-outline-tree/src/icons/border-outer.svg similarity index 100% rename from packages/plugin-outline-pane/src/icons/border-outer.svg rename to packages/plugin-outline-tree/src/icons/border-outer.svg diff --git a/packages/plugin-outline-tree/src/icons/cond.tsx b/packages/plugin-outline-tree/src/icons/cond.tsx new file mode 100644 index 000000000..1f6f1156b --- /dev/null +++ b/packages/plugin-outline-tree/src/icons/cond.tsx @@ -0,0 +1,11 @@ +import { IconBase, IconProps } from '../../../globals'; + +export function IconCond(props: IconProps) { + return ( + <IconBase viewBox="0 0 1024 1024" {...props}> + <path d="M479.552 276.544l296.896 2.752v75.712L960 249.024l-183.552-106.048v92.48h-271.36l-46.656-2.752-190.784 203.648 30.976 30.976 180.928-190.784z m296.896 484.928l-253.056-2.816-262.976-263.04H64v43.904h175.296l262.912 262.976 274.176 2.816v75.712L960 774.976l-183.616-105.984 0.064 92.48z" /> + </IconBase> + ); +} + +IconCond.displayName = 'IconCond'; diff --git a/packages/plugin-outline-tree/src/icons/eye-close.tsx b/packages/plugin-outline-tree/src/icons/eye-close.tsx new file mode 100644 index 000000000..b463e556b --- /dev/null +++ b/packages/plugin-outline-tree/src/icons/eye-close.tsx @@ -0,0 +1,12 @@ +import { IconBase, IconProps } from '../../../globals'; + +export function IconEyeClose(props: IconProps) { + return ( + <IconBase viewBox="0 0 1024 1024" {...props}> + <path d="M512.7 700.9c-102.1 0-184.9-82.8-184.9-184.9 0-28.6 6.5-55.6 18-79.7l-93.7-93.7C138.9 418.1 65.2 514 65.2 514s200.4 260.7 447.6 260.7c50.2 0 98.6-10.8 143.6-27.9l-63.9-63.9c-24.2 11.5-51.2 18-79.8 18z" /> + <path d="M960.3 514S759.9 253.3 512.7 253.3c-49.5 0-97.2 10.5-141.7 27.2L243.5 153.1l-45.3 45.3 262.3 262.2c-13.1 13.3-21.2 31.5-21.2 51.6 0 40.6 32.9 73.4 73.4 73.4 20.1 0 38.4-8.1 51.6-21.2l260.9 260.8 45.3-45.3-95.6-95.6C887.2 609.1 960.3 514 960.3 514z m-376.7-20.9c-6.8-25.2-26.6-45.1-51.9-51.9L437.5 347c23-10.3 48.5-16 75.3-16 102.1 0 184.9 82.8 184.9 184.9 0 26.8-5.7 52.2-15.9 75.2l-98.2-98z" /> + </IconBase> + ); +} + +IconEyeClose.displayName = 'IconEyeClose'; diff --git a/packages/plugin-outline-tree/src/icons/eye.tsx b/packages/plugin-outline-tree/src/icons/eye.tsx new file mode 100644 index 000000000..faa0ef8bd --- /dev/null +++ b/packages/plugin-outline-tree/src/icons/eye.tsx @@ -0,0 +1,12 @@ +import { IconBase, IconProps } from '../../../globals'; + +export function IconEye(props: IconProps) { + return ( + <IconBase viewBox="0 0 1024 1024" {...props}> + <path d="M512 256c-163.8 0-291.4 97.6-448 256 134.8 135.4 248 256 448 256 199.8 0 346.8-152.8 448-253.2C856.4 397.2 709.6 256 512 256z m0 438.6c-98.8 0-179.2-82-179.2-182.6 0-100.8 80.4-182.6 179.2-182.6s179.2 82 179.2 182.6c0 100.8-80.4 182.6-179.2 182.6z" /> + <path d="M512 448c0-15.8 5.8-30.2 15.2-41.4-5-0.8-10-1.2-15.2-1.2-57.6 0-104.6 47.8-104.6 106.6s47 106.6 104.6 106.6 104.6-47.8 104.6-106.6c0-4.6-0.4-9.2-0.8-13.8-11 8.6-24.6 13.8-39.6 13.8-35.6 0-64.2-28.6-64.2-64z" /> + </IconBase> + ); +} + +IconEye.displayName = 'IconEye'; diff --git a/packages/plugin-outline-tree/src/icons/lock.tsx b/packages/plugin-outline-tree/src/icons/lock.tsx new file mode 100644 index 000000000..645d24551 --- /dev/null +++ b/packages/plugin-outline-tree/src/icons/lock.tsx @@ -0,0 +1,11 @@ +import { IconBase, IconProps } from '../../../globals'; + +export function IconLock(props: IconProps) { + return ( + <IconBase viewBox="0 0 1024 1024" {...props}> + <path d="M832 464h-68V240c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zM540 701v53c0 4.4-3.6 8-8 8h-40c-4.4 0-8-3.6-8-8v-53c-12.1-8.7-20-22.9-20-39 0-26.5 21.5-48 48-48s48 21.5 48 48c0 16.1-7.9 30.3-20 39z m152-237H332V240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v224z" /> + </IconBase> + ); +} + +IconLock.displayName = 'IconLock'; diff --git a/packages/plugin-outline-tree/src/icons/loop.tsx b/packages/plugin-outline-tree/src/icons/loop.tsx new file mode 100644 index 000000000..9d08538f4 --- /dev/null +++ b/packages/plugin-outline-tree/src/icons/loop.tsx @@ -0,0 +1,11 @@ +import { IconBase, IconProps } from '../../../globals'; + +export function IconLoop(props: IconProps) { + return ( + <IconBase viewBox="0 0 1024 1024" {...props}> + <path d="M60.235294 542.117647c0 132.879059 103.062588 240.941176 229.677176 240.941176l0 60.235294c-159.864471 0-289.912471-135.107765-289.912471-301.176471s130.048-301.176471 289.912471-301.176471l254.735059 0-99.147294-99.147294 42.586353-42.586353 171.911529 171.851294-171.851294 171.911529-42.646588-42.646588 99.207529-99.147294-254.795294 0c-126.614588 0-229.677176 108.062118-229.677176 240.941176zM734.087529 240.941176l0 60.235294c126.614588 0 229.677176 108.062118 229.677176 240.941176s-103.062588 240.941176-229.677176 240.941176l-254.795294 0 99.147294-99.147294-42.586353-42.586353-171.851294 171.851294 171.911529 171.911529 42.586353-42.586353-99.207529-99.207529 254.735059 0c159.924706 0 289.972706-135.107765 289.972706-301.176471s-130.048-301.176471-289.912471-301.176471z" /> + </IconBase> + ); +} + +IconLoop.displayName = 'IconLoop'; diff --git a/packages/plugin-outline-tree/src/icons/slot.tsx b/packages/plugin-outline-tree/src/icons/slot.tsx new file mode 100644 index 000000000..23d02f744 --- /dev/null +++ b/packages/plugin-outline-tree/src/icons/slot.tsx @@ -0,0 +1,12 @@ +import { IconBase, IconProps } from '../../../globals/src'; + +export function IconSlot(props: IconProps) { + return ( + <IconBase viewBox="0 0 1024 1024" {...props}> + <path d="M682.325333 135.509333V204.8H819.2v613.376h-614.741333V204.8h136.874666v-69.290667h-206.165333v752.298667h754.346667V135.509333z" /> + <path d="M512 512m-170.325333 0a170.325333 170.325333 0 1 0 340.650666 0 170.325333 170.325333 0 1 0-340.650666 0Z" /> + </IconBase> + ); +} + +IconSlot.displayName = 'IconSlot'; diff --git a/packages/plugin-outline-tree/src/icons/unlock.tsx b/packages/plugin-outline-tree/src/icons/unlock.tsx new file mode 100644 index 000000000..1a010a4ce --- /dev/null +++ b/packages/plugin-outline-tree/src/icons/unlock.tsx @@ -0,0 +1,12 @@ +import { IconBase, IconProps } from '../../../globals/src'; + +export function IconUnlock(props: IconProps) { + return ( + <IconBase viewBox="0 0 1024 1024" {...props}> + <path d="M832 464H332V240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v68c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-68c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32z m-40 376H232V536h560v304z" /> + <path d="M484 701v53c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-53c12.1-8.7 20-22.9 20-39 0-26.5-21.5-48-48-48s-48 21.5-48 48c0 16.1 7.9 30.3 20 39z" /> + </IconBase> + ); +} + +IconUnlock.displayName = 'IconUnlock'; diff --git a/packages/plugin-outline-pane/src/index.ts b/packages/plugin-outline-tree/src/index.ts similarity index 86% rename from packages/plugin-outline-pane/src/index.ts rename to packages/plugin-outline-tree/src/index.ts index 34f4e1230..bc52dcf36 100644 --- a/packages/plugin-outline-pane/src/index.ts +++ b/packages/plugin-outline-tree/src/index.ts @@ -1,5 +1,6 @@ import Pane from './views/pane'; +/* export default { name: 'outline-tree', title: { @@ -8,3 +9,6 @@ export default { }, content: Pane, }; +*/ + +export default Pane; diff --git a/packages/plugin-outline-tree/src/locale/en-US.json b/packages/plugin-outline-tree/src/locale/en-US.json new file mode 100644 index 000000000..3040148b8 --- /dev/null +++ b/packages/plugin-outline-tree/src/locale/en-US.json @@ -0,0 +1,14 @@ +{ + "Designer not found": "Designer not found", + "No opened document": "No opened document", + "Hide": "Hide", + "Show": "Show", + "Lock": "Lock", + "Unlock": "Unlock", + "Expand": "Expand", + "Collapse": "Collapse", + "Conditional": "Condition", + "Loop": "Loop", + "Slots": "Slots", + "Slot for {prop}": "Slot for {prop}" +} diff --git a/packages/plugin-outline-pane/src/locale/index.ts b/packages/plugin-outline-tree/src/locale/index.ts similarity index 100% rename from packages/plugin-outline-pane/src/locale/index.ts rename to packages/plugin-outline-tree/src/locale/index.ts diff --git a/packages/plugin-outline-tree/src/locale/zh-CN.json b/packages/plugin-outline-tree/src/locale/zh-CN.json new file mode 100644 index 000000000..a4478ba8b --- /dev/null +++ b/packages/plugin-outline-tree/src/locale/zh-CN.json @@ -0,0 +1,14 @@ +{ + "Designer not found": "未发现设计器模块", + "No opened document": "没有打开的文档", + "Hide": "隐藏", + "Show": "显示", + "Lock": "锁定", + "Unlock": "解锁", + "Expand": "展开", + "Collapse": "收起", + "Conditional": "条件式", + "Loop": "循环", + "Slots": "插槽", + "Slot for {prop}": "属性 {prop} 的插槽" +} diff --git a/packages/plugin-outline-pane/src/main.ts b/packages/plugin-outline-tree/src/main.ts similarity index 79% rename from packages/plugin-outline-pane/src/main.ts rename to packages/plugin-outline-tree/src/main.ts index aa8afd27c..91cdb5620 100644 --- a/packages/plugin-outline-pane/src/main.ts +++ b/packages/plugin-outline-tree/src/main.ts @@ -5,7 +5,22 @@ import { Tree } from './tree'; import Location from '../../designer/src/designer/helper/location'; class TreeMaster { - constructor(readonly designer: Designer) {} + constructor(readonly designer: Designer) { + designer.dragon.onDragstart((e) => { + const tree = this.currentTree; + if (tree) { + tree.document.selection.getTopNodes().forEach(node => { + tree.getTreeNode(node).setExpanded(false); + }); + }; + }); + designer.activeTracker.onChange((target) => { + const tree = this.currentTree; + if (tree && target.node.document === tree.document) { + tree.getTreeNode(target.node).expandParents(); + } + }); + } private treeMap = new Map<string, Tree>(); @computed get currentTree(): Tree | null { @@ -70,7 +85,7 @@ export class OutlineMain implements ISensor { private setupDesigner(designer: Designer) { this._designer = designer; this._master = getTreeMaster(designer); - designer.dragon.addSensor(this); + // designer.dragon.addSensor(this); } purge() { @@ -93,7 +108,7 @@ export class OutlineMain implements ISensor { } this._shell = shell; if (shell) { - this._sensorAvailable = true; + // this._sensorAvailable = true; } } } diff --git a/packages/plugin-outline-pane/src/sensor.ts b/packages/plugin-outline-tree/src/sensor.ts similarity index 100% rename from packages/plugin-outline-pane/src/sensor.ts rename to packages/plugin-outline-tree/src/sensor.ts diff --git a/packages/plugin-outline-pane/src/tree-node.ts b/packages/plugin-outline-tree/src/tree-node.ts similarity index 72% rename from packages/plugin-outline-pane/src/tree-node.ts rename to packages/plugin-outline-tree/src/tree-node.ts index d1e3a502f..300ffebf3 100644 --- a/packages/plugin-outline-pane/src/tree-node.ts +++ b/packages/plugin-outline-tree/src/tree-node.ts @@ -1,16 +1,10 @@ -import { computed, obx, TitleContent } from '../../globals'; +import { computed, obx, TitleContent, isI18nData, localeFormat } from '../../globals'; import Node from '../../designer/src/designer/document/node/node'; import DocumentModel from '../../designer/src/designer/document/document-model'; import { isLocationChildrenDetail } from '../../designer/src/designer/helper/location'; import Designer from '../../designer/src/designer/designer'; import { Tree } from './tree'; -export interface Title { - label: string; - icon?: string; - actions?: any; -} - export default class TreeNode { get id(): string { return this.node.id; @@ -35,6 +29,10 @@ export default class TreeNode { return this.node.zLevel; } + isRoot() { + return this.tree.root === this; + } + /** * 是否是响应投放区 */ @@ -52,10 +50,10 @@ export default class TreeNode { */ @obx.ref private _expanded = false; get expanded(): boolean { - return this.expandable && this._expanded; + return this.isRoot() || (this.expandable && this._expanded); } - set expanded(value: boolean) { + setExpanded(value: boolean) { this._expanded = value; } @@ -64,17 +62,36 @@ export default class TreeNode { } @computed get hidden(): boolean { - return this.node.getExtraProp('hidden', false)?.getValue() === true; + const cv = this.node.isConditionalVisible(); + if (cv == null) { + return this.node.getExtraProp('hidden', false)?.getValue() === true; + } + return !cv; } - @computed get ignored(): boolean { - return this.node.getExtraProp('ignored', false)?.getValue() === true; + setHidden(flag: boolean) { + if (this.node.conditionGroup) { + return; + } + if (flag) { + this.node.getExtraProp('hidden', true)?.setValue(true); + } else { + this.node.getExtraProp('hidden', false)?.remove(); + } } @computed get locked(): boolean { return this.node.getExtraProp('locked', false)?.getValue() === true; } + setLocked(flag: boolean) { + if (flag) { + this.node.getExtraProp('locked', true)?.setValue(true); + } else { + this.node.getExtraProp('locked', false)?.remove(); + } + } + @computed get selected(): boolean { // TODO: check is dragging const selection = this.document.selection; @@ -85,6 +102,39 @@ export default class TreeNode { return this.node.title; } + @computed get titleLabel() { + let title = this.title; + if (!title) { + return ''; + } + if ((title as any).label) { + title = (title as any).label; + } + if (typeof title === 'string') { + return title; + } + if (isI18nData(title)) { + return localeFormat(title); + } + return this.node.componentName; + } + + setTitleLabel(label: string) { + const origLabel = this.titleLabel; + if (label === origLabel) { + return; + } + if (label === '') { + this.node.getExtraProp('title', false)?.remove(); + } else { + this.node.getExtraProp('title', true)?.setValue(label); + } + } + + get icon() { + return this.node.componentMeta.icon; + } + @computed get parent() { const parent = this.node.parent; if (parent) { @@ -155,7 +205,7 @@ export default class TreeNode { // 这边不能直接使用 expanded,需要额外判断是否可以展开 // 如果只使用 expanded,会漏掉不可以展开的情况,即在不可以展开的情况下,会触发展开 if (this.expandable && !this._expanded) { - this.expanded = true; + this.setExpanded(true); } if (tryExpandParents) { this.expandParents(); @@ -188,7 +238,7 @@ export default class TreeNode { expandParents() { let p = this.node.parent; while (p) { - this.tree.getTreeNode(p).expanded = true; + this.tree.getTreeNode(p).setExpanded(true); p = p.parent; } } @@ -226,8 +276,19 @@ export default class TreeNode { readonly designer: Designer; readonly document: DocumentModel; - constructor(readonly tree: Tree, readonly node: Node) { + @obx.ref private _node: Node; + get node() { + return this._node; + } + constructor(readonly tree: Tree, node: Node) { this.document = node.document; this.designer = this.document.designer; + this._node = node; + } + + setNode(node: Node) { + if (this._node !== node) { + this._node = node; + } } } diff --git a/packages/plugin-outline-pane/src/tree.ts b/packages/plugin-outline-tree/src/tree.ts similarity index 79% rename from packages/plugin-outline-pane/src/tree.ts rename to packages/plugin-outline-tree/src/tree.ts index cefe9493c..e445ea2fc 100644 --- a/packages/plugin-outline-pane/src/tree.ts +++ b/packages/plugin-outline-tree/src/tree.ts @@ -7,13 +7,18 @@ export class Tree { readonly root: TreeNode; + readonly id: string; + constructor(readonly document: DocumentModel) { this.root = this.getTreeNode(document.rootNode); + this.id = document.id; } getTreeNode(node: Node): TreeNode { if (this.treeNodesMap.has(node.id)) { - return this.treeNodesMap.get(node.id)!; + const tnode = this.treeNodesMap.get(node.id)!; + tnode.setNode(node); + return tnode; } const treeNode = new TreeNode(this, node); diff --git a/packages/plugin-outline-pane/src/views/pane.tsx b/packages/plugin-outline-tree/src/views/pane.tsx similarity index 75% rename from packages/plugin-outline-pane/src/views/pane.tsx rename to packages/plugin-outline-tree/src/views/pane.tsx index f698611be..66dc10424 100644 --- a/packages/plugin-outline-pane/src/views/pane.tsx +++ b/packages/plugin-outline-tree/src/views/pane.tsx @@ -1,7 +1,8 @@ import React, { Component } from 'react'; -import { OutlineMain } from '../main'; +import { observer } from '../../../globals'; import { intl } from '../locale'; -import { observer } from '../../../globals/src'; +import { OutlineMain } from '../main'; +import TreeView from './tree'; import './style.less'; @observer @@ -25,7 +26,9 @@ export default class OutlinePane extends Component<{ editor: any }> { ); } - if (!this.main.master.currentTree) { + const tree = this.main.master.currentTree; + + if (!tree) { return ( <div className="lc-outline-pane"> <p className="lc-outline-notice">{intl('No opened document')}</p> @@ -35,11 +38,8 @@ export default class OutlinePane extends Component<{ editor: any }> { return ( <div className="lc-outline-pane"> - <div - ref={shell => this.main.mount(shell)} - className="lc-outline-tree-container" - > - <TreeView tree={this.main.master.currentTree} /> + <div ref={shell => this.main.mount(shell)} className="lc-outline-tree-container"> + <TreeView key={tree.id} tree={tree} /> </div> </div> ); diff --git a/packages/plugin-outline-tree/src/views/style.less b/packages/plugin-outline-tree/src/views/style.less new file mode 100644 index 000000000..a13fa5167 --- /dev/null +++ b/packages/plugin-outline-tree/src/views/style.less @@ -0,0 +1,295 @@ +.lc-outline-pane { + height: 100%; + width: 100%; + position: relative; + + > .lc-outline-tree-container { + top: 0; + left: 0; + bottom: 0; + right: 0; + position: absolute; + overflow: auto; + } +} + +.lc-outline-tree { + overflow: hidden; + margin-bottom: 20px; + user-select: none; + + .tree-node-branches::before { + position: absolute; + display: block; + width: 0; + border-left: 1px solid transparent; + height: 100%; + top: 0; + left: 6px; + content: ' '; + z-index: 2; + } + + &:hover { + .tree-node-branches::before { + border-left-color: #ddd; + } + } + + .insertion { + pointer-events: none !important; + border: 1px dashed var(--color-brand-light); + height: 18px; + transform: translateZ(0); + } + + .condition-group-container { + border-bottom: 1px solid #7b605b; + position: relative; + + &:before { + position: absolute; + display: block; + width: 0; + border-left: 0.5px solid #7b605b; + height: 100%; + top: 0; + left: 0; + content: ' '; + z-index: 2; + } + >.condition-group-title { + text-align: center; + background-color: #7b605b; + height: 14px; + > .lc-title { + font-size: 12px; + transform: scale(0.8); + transform-origin: top; + color: white; + text-shadow: 0px 0px 2px black; + display: block; + } + } + } + .tree-node-slots { + border-bottom: 1px solid rgb(144, 94, 190); + position: relative; + &:before { + position: absolute; + display: block; + width: 0; + border-left: 0.5px solid rgb(144, 94, 190); + height: 100%; + top: 0; + left: 0; + content: ' '; + z-index: 2; + } + >.tree-node-slots-title { + text-align: center; + background-color: rgb(144, 94, 190); + height: 14px; + > .lc-title { + font-size: 12px; + transform: scale(0.8); + transform-origin: top; + color: white; + text-shadow: 0px 0px 2px black; + display: block; + } + } + } + + .tree-node { + .tree-node-expand-btn { + width: 12px; + line-height: 0; + align-self: stretch; + display: flex; + align-items: center; + transition: color 200ms ease; + color: var(--color-icon-normal); + &:hover { + color: var(--color-icon-hover); + } + > svg { + transform-origin: center; + transform: rotate(-90deg); + transition: transform 100ms ease; + } + margin-right: 4px; + } + .tree-node-expand-placeholder { + width: 12px; + height: 12px; + margin-right: 4px; + } + + .tree-node-icon { + transform: translateZ(0); + display: flex; + align-items: center; + margin-right: 4px; + color: var(--color-text); + + & > svg { + width: 16px; + height: 16px; + } + } + + .tree-node-title { + font-size: var(--font-size-text); + cursor: pointer; + background: var(--color-pane-background); + border-bottom: 1px solid var(--color-line-normal, rgba(31, 56, 88, 0.1)); + display: flex; + align-items: center; + height: 30px; + position: relative; + transform: translateZ(0); + padding-right: 5px; + & > :first-child { + margin-left: 2px; + } + + .tree-node-title-label { + flex: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: flex; + align-items: center; + align-self: stretch; + overflow: visible; + margin-right: 5px; + + .tree-node-title-input { + flex: 1; + border: 1px solid var(--color-brand-light); + background-color: var(--color-pane-background); + color: var(--color-text); + line-height: 18px; + padding: 2px; + outline: none; + margin-left: -3px; + border-radius: 2px; + } + } + + .tree-node-hide-btn, .tree-node-lock-btn { + opacity: 0; + color: var(--color-text); + line-height: 0; + align-self: stretch; + display: flex; + align-items: center; + justify-content: center; + width: 22px; + &:hover { + opacity: 1 !important; + } + } + &:hover { + .tree-node-hide-btn, .tree-node-lock-btn { + opacity: 0.5; + } + } + &.editing { + & > .tree-node-hide-btn, & >.tree-node-lock-btn { + display: none; + } + } + + .tree-node-tag { + margin-left: 5px; + display: flex; + align-items: center; + line-height: 0; + &.cond { + color: rgb(179, 52, 6); + } + &.loop { + color: rgb(103, 187, 187); + } + &.slot { + color: rgb(211, 90, 211); + } + } + } + + &.is-root { + > .tree-node-title { + padding-left: 5px; + } + } + + &.expanded { + & > .tree-node-title > .tree-node-expand-btn > svg { + transform: rotate(0); + } + } + + &.hovering > .tree-node-title { + background: var(--color-block-background-light); + } + + // 选中节点处理 + &.selected { + & > .tree-node-title { + background: var(--color-block-background-shallow); + } + + & > .tree-node-branches::before { + border-left-color: var(--color-brand-light); + } + } + + &.hidden { + .tree-node-title-label { + color: #9b9b9b; + } + & > .tree-node-title > .tree-node-hide-btn { + opacity: 0.8; + } + .tree-node-branches { + .tree-node-hide-btn { + opacity: 0; + } + } + } + + &.condition-flow { + & > .tree-node-title > .tree-node-hide-btn { + opacity: 1; + } + &.hidden > .tree-node-title > .tree-node-hide-btn { + opacity: 0; + } + } + + &.locked { + & > .tree-node-title > .tree-node-lock-btn { + opacity: 0.8; + } + .tree-node-branches { + .tree-node-lock-btn, .tree-node-hide-btn { + opacity: 0; + } + } + } + + // 处理拖入节点 + &.dropping { + & > .tree-node-branches::before { + border-left: 1px solid var(--color-brand); + } + } + + .tree-node-branches { + padding-left: 12px; + position: relative; + } + } +} diff --git a/packages/plugin-outline-tree/src/views/tree-branches.tsx b/packages/plugin-outline-tree/src/views/tree-branches.tsx new file mode 100644 index 000000000..25d904367 --- /dev/null +++ b/packages/plugin-outline-tree/src/views/tree-branches.tsx @@ -0,0 +1,114 @@ +import { observer, Title } from '../../../globals'; +import { Component } from 'react'; +import TreeNode from '../tree-node'; +import TreeNodeView from './tree-node'; +import ExclusiveGroup from '../../../designer/src/designer/document/node/exclusive-group'; +import { intl } from '../locale'; + +@observer +export default class TreeBranches extends Component<{ + treeNode: TreeNode; +}> { + shouldComponentUpdate() { + return false; + } + + render() { + const treeNode = this.props.treeNode; + const { expanded } = treeNode; + + if (!expanded) { + return null; + } + + return ( + <div className="tree-node-branches"> + <TreeNodeSlots treeNode={treeNode} /> + <TreeNodeChildren treeNode={treeNode} /> + </div> + ); + } +} + +@observer +class TreeNodeChildren extends Component<{ + treeNode: TreeNode; +}> { + shouldComponentUpdate() { + return false; + } + render() { + const { treeNode } = this.props; + let children: any = []; + let groupContents: any[] = []; + let currentGrp: ExclusiveGroup; + const endGroup = () => { + if (groupContents.length > 0) { + children.push( + <div key={currentGrp.id} className="condition-group-container" data-id={currentGrp.firstNode.id}> + <div className="condition-group-title"> + <Title title={currentGrp.title} /> + </div> + {groupContents} + </div>, + ); + groupContents = []; + } + }; + const { dropIndex } = treeNode; + treeNode.children?.forEach((child, index) => { + const { conditionGroup } = child.node; + if (conditionGroup !== currentGrp) { + endGroup(); + } + + if (conditionGroup) { + currentGrp = conditionGroup; + if (index === dropIndex) { + if (groupContents.length > 0) { + groupContents.push(<div key="insertion" className="insertion" />); + } else { + children.push(<div key="insertion" className="insertion" />); + } + } + groupContents.push(<TreeNodeView key={child.id} treeNode={child} />); + } else { + if (index === dropIndex) { + children.push(<div key="insertion" className="insertion" />); + } + children.push(<TreeNodeView key={child.id} treeNode={child} />); + } + }); + endGroup(); + if (dropIndex != null && dropIndex === treeNode.children?.length) { + children.push(<div key="insertion" className="insertion" />); + } + + return <div className="tree-node-children">{children}</div>; + } +} + +@observer +class TreeNodeSlots extends Component<{ + treeNode: TreeNode; +}> { + shouldComponentUpdate() { + return false; + } + render() { + const { treeNode } = this.props; + if (!treeNode.isSlotContainer()) { + return null; + } + return ( + <div className="tree-node-slots"> + <div className="tree-node-slots-title"> + <Title title={{ type: 'i18n', intl: intl('Slots')}} /> + </div> + {treeNode.slots.map(tnode => ( + <TreeNodeView key={tnode.id} treeNode={tnode} /> + ))} + </div> + ); + } +} diff --git a/packages/plugin-outline-tree/src/views/tree-node.tsx b/packages/plugin-outline-tree/src/views/tree-node.tsx new file mode 100644 index 000000000..b18a9688a --- /dev/null +++ b/packages/plugin-outline-tree/src/views/tree-node.tsx @@ -0,0 +1,43 @@ +import { Component } from 'react'; +import classNames from 'classnames'; +import { observer } from '../../../globals'; +import TreeNode from '../tree-node'; +import TreeTitle from './tree-title'; +import TreeBranches from './tree-branches'; + +@observer +export default class TreeNodeView extends Component<{ treeNode: TreeNode }> { + shouldComponentUpdate() { + return false; + } + + render() { + const { treeNode } = this.props; + const className = classNames('tree-node', { + // 是否展开 + expanded: treeNode.expanded, + // 是否悬停中 + hovering: treeNode.hovering, + // 是否选中的 + selected: treeNode.selected, + // 是否隐藏的 + hidden: treeNode.hidden, + // 是否忽略的 + // ignored: treeNode.ignored, + // 是否锁定的 + locked: treeNode.locked, + // 是否投放响应 + dropping: treeNode.dropIndex != null, + 'is-root': treeNode.isRoot(), + 'condition-flow': treeNode.node.conditionGroup != null, + // highlight: treeNode.isResponseDropping() && treeNode.dropIndex == null, + }); + + return ( + <div className={className} data-id={treeNode.id}> + <TreeTitle treeNode={treeNode} /> + <TreeBranches treeNode={treeNode} /> + </div> + ); + } +} diff --git a/packages/plugin-outline-tree/src/views/tree-title.tsx b/packages/plugin-outline-tree/src/views/tree-title.tsx new file mode 100644 index 000000000..2e89551fc --- /dev/null +++ b/packages/plugin-outline-tree/src/views/tree-title.tsx @@ -0,0 +1,199 @@ +import { Component, KeyboardEvent, FocusEvent, Fragment } from 'react'; +import classNames from 'classnames'; +import { observer, createIcon, Title, EmbedTip } from '../../../globals'; +import { IconArrowRight } from '../icons/arrow-right'; +import { IconEyeClose } from '../icons/eye-close'; +import { IconLock } from '../icons/lock'; +import { IconUnlock } from '../icons/unlock'; +import { intl } from '../locale'; +import TreeNode from '../tree-node'; +import { IconEye } from '../icons/eye'; +import { IconCond } from '../icons/cond'; +import { IconLoop } from '../icons/loop'; +import { IconSlot } from '../icons/slot'; + +@observer +export default class TreeTitle extends Component<{ + treeNode: TreeNode; +}> { + state = { + editing: false, + }; + + private enableEdit = () => { + this.setState({ + editing: true, + }); + }; + + private cancelEdit() { + this.setState({ + editing: false, + }); + } + + private saveEdit = (e: FocusEvent<HTMLInputElement> | KeyboardEvent<HTMLInputElement>) => { + const { treeNode } = this.props; + treeNode.setTitleLabel((e.target as HTMLInputElement).value || ''); + this.cancelEdit(); + }; + + private handleKeyUp = (e: KeyboardEvent<HTMLInputElement>) => { + if (e.keyCode === 13) { + this.saveEdit(e); + } + if (e.keyCode === 27) { + this.cancelEdit(); + } + }; + + componentDidUpdate() { + // TODO: + /* + const { current } = this.inputRef; + if (current) { + current.select(); + } + */ + } + + render() { + const { treeNode } = this.props; + const { editing } = this.state; + const isCNode = !treeNode.isRoot(); + const isNodeParent = treeNode.node.isNodeParent; + let style: any; + if (isCNode) { + const depth = treeNode.depth; + const indent = depth * 12; + style = { + paddingLeft: indent, + marginLeft: -indent, + }; + } + + return ( + <div + className={classNames('tree-node-title', { + editing, + })} + ref={ref => treeNode.mount(ref)} + style={style} + onClick={treeNode.node.conditionGroup ? () => treeNode.node.setConditionalVisible() : undefined} + > + {isCNode && <ExpandBtn treeNode={treeNode} />} + <div className="tree-node-icon">{createIcon(treeNode.icon)}</div> + <div className="tree-node-title-label" onDoubleClick={isNodeParent ? this.enableEdit : undefined}> + {editing ? ( + <input + className="tree-node-title-input" + defaultValue={treeNode.titleLabel} + onBlur={this.saveEdit} + onKeyUp={this.handleKeyUp} + /> + ) : ( + <Fragment> + <Title title={treeNode.title} /> + {treeNode.node.slotFor && (<a className="tree-node-tag slot"> + {/* todo: click redirect to prop */} + <IconSlot /> + <EmbedTip>{intl('Slot for {prop}', { prop: treeNode.node.slotFor.key })}</EmbedTip> + </a>)} + {treeNode.node.hasLoop() && ( + <a className="tree-node-tag loop"> + {/* todo: click todo something */} + <IconLoop /> + <EmbedTip>{intl('Loop')}</EmbedTip> + </a> + )} + {treeNode.node.hasCondition() && !treeNode.node.conditionGroup && ( + <a className="tree-node-tag cond"> + {/* todo: click todo something */} + <IconCond /> + <EmbedTip>{intl('Conditional')}</EmbedTip> + </a> + )} + </Fragment> + )} + </div> + {isCNode && isNodeParent && <HideBtn treeNode={treeNode} />} + {isCNode && isNodeParent && <LockBtn treeNode={treeNode} />} + </div> + ); + } +} + +@observer +class LockBtn extends Component<{ + treeNode: TreeNode; +}> { + shouldComponentUpdate() { + return false; + } + render() { + const { treeNode } = this.props; + return ( + <div + className="tree-node-lock-btn" + onClick={e => { + e.stopPropagation(); + treeNode.setLocked(!treeNode.locked); + }} + > + {treeNode.locked ? <IconLock /> : <IconUnlock />} + <EmbedTip>{treeNode.locked ? intl('Unlock') : intl('Lock')}</EmbedTip> + </div> + ); + } +} + +@observer +class HideBtn extends Component<{ + treeNode: TreeNode; +}> { + shouldComponentUpdate() { + return false; + } + render() { + const { treeNode } = this.props; + return ( + <div + className="tree-node-hide-btn" + onClick={e => { + e.stopPropagation(); + treeNode.setHidden(!treeNode.hidden); + }} + > + {treeNode.hidden ? <IconEyeClose /> : <IconEye />} + <EmbedTip>{treeNode.hidden ? intl('Show') : intl('Hide')}</EmbedTip> + </div> + ); + } +} + +@observer +class ExpandBtn extends Component<{ + treeNode: TreeNode; +}> { + shouldComponentUpdate() { + return false; + } + render() { + const { treeNode } = this.props; + if (!treeNode.expandable) { + return <i className="tree-node-expand-placeholder" />; + } + return ( + <div + className="tree-node-expand-btn" + onClick={e => { + e.stopPropagation(); + treeNode.setExpanded(!treeNode.expanded); + }} + > + <IconArrowRight size="small" /> + <EmbedTip>{treeNode.expanded ? intl('Collapse') : intl('Expand')}</EmbedTip> + </div> + ); + } +} diff --git a/packages/plugin-outline-pane/src/views/tree.tsx b/packages/plugin-outline-tree/src/views/tree.tsx similarity index 82% rename from packages/plugin-outline-pane/src/views/tree.tsx rename to packages/plugin-outline-tree/src/views/tree.tsx index 551147ea9..bc219cf41 100644 --- a/packages/plugin-outline-pane/src/views/tree.tsx +++ b/packages/plugin-outline-tree/src/views/tree.tsx @@ -1,8 +1,11 @@ -@observer -export default class TreeView extends React.Component { - private ref = React.createRef<HTMLDivElement>(); - private dispose?: () => void; +import { Component } from 'react'; +import { observer } from '../../../globals'; +import { Tree } from '../tree'; +import TreeNodeView from './tree-node'; +@observer +export default class TreeView extends Component<{ tree: Tree }> { + /* hover(e: any) { const treeNode = tree.getTreeNodeByEvent(e); @@ -68,12 +71,13 @@ export default class TreeView extends React.Component { }); } } + */ render() { const { tree } = this.props; const root = tree.root; return ( - <div className="my-outline-tree"> + <div className="lc-outline-tree"> <TreeNodeView key={root.id} treeNode={root} /> </div> ); diff --git a/packages/plugin-outline-pane/tsconfig.json b/packages/plugin-outline-tree/tsconfig.json similarity index 100% rename from packages/plugin-outline-pane/tsconfig.json rename to packages/plugin-outline-tree/tsconfig.json