diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index 39ddd99c9..0c75d8d81 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -8,6 +8,7 @@ import { obx, computed, autorun, + IEditor, } from '@ali/lowcode-globals'; import { Project } from '../project'; import { Node, DocumentModel, insertChildren, isRootNode, ParentalNode } from '../document'; @@ -20,6 +21,7 @@ import { Hovering } from './hovering'; import { DropLocation, LocationData, isLocationChildrenDetail } from './location'; import { OffsetObserver, createOffsetObserver } from './offset-observer'; import { focusing } from './focusing'; +import { SettingTopEntry } from './setting'; export interface DesignerProps { className?: string; @@ -198,6 +200,10 @@ export class Designer { return createOffsetObserver(nodeInstance); } + createSettingEntry(editor: IEditor, nodes: Node[]) { + return new SettingTopEntry(editor, nodes); + } + /** * 获得合适的插入位置 */ diff --git a/packages/designer/src/designer/index.ts b/packages/designer/src/designer/index.ts index a928b3d49..1de0fb2ee 100644 --- a/packages/designer/src/designer/index.ts +++ b/packages/designer/src/designer/index.ts @@ -6,3 +6,4 @@ export * from './hovering'; export * from './location'; export * from './offset-observer'; export * from './scroller'; +export * from './setting'; diff --git a/packages/designer/src/designer/setting/index.ts b/packages/designer/src/designer/setting/index.ts new file mode 100644 index 000000000..a8319e5b2 --- /dev/null +++ b/packages/designer/src/designer/setting/index.ts @@ -0,0 +1,3 @@ +export * from './setting-field'; +export * from './setting-top-entry'; +export * from './setting-entry'; diff --git a/packages/designer/src/designer/setting/setting-entry.ts b/packages/designer/src/designer/setting/setting-entry.ts new file mode 100644 index 000000000..9ab5831db --- /dev/null +++ b/packages/designer/src/designer/setting/setting-entry.ts @@ -0,0 +1,17 @@ +import { SettingTarget } from '@ali/lowcode-globals'; +import { ComponentMeta } from '../../component-meta'; +import { Designer } from '../designer'; +import { Node } from '../../document'; + +export interface SettingEntry extends SettingTarget { + readonly nodes: Node[]; + readonly componentMeta: ComponentMeta | null; + readonly designer: Designer; + + // 顶端 + readonly top: SettingEntry; + // 父级 + readonly parent: SettingEntry; + + get(propName: string | number): SettingEntry; +} diff --git a/packages/plugin-settings-pane/src/settings/setting-field.ts b/packages/designer/src/designer/setting/setting-field.ts similarity index 92% rename from packages/plugin-settings-pane/src/settings/setting-field.ts rename to packages/designer/src/designer/setting/setting-field.ts index 229d575fc..2560cfaca 100644 --- a/packages/plugin-settings-pane/src/settings/setting-field.ts +++ b/packages/designer/src/designer/setting/setting-field.ts @@ -1,9 +1,9 @@ import { TitleContent, computed, isDynamicSetter, SetterType, DynamicSetter, FieldExtraProps, FieldConfig, CustomView, isCustomView, obx } from '@ali/lowcode-globals'; -import { Transducer } from '../utils'; -import { SettingPropEntry } from './setting-entry'; -import { SettingTarget } from './setting-target'; +import { Transducer } from './utils'; +import { SettingPropEntry } from './setting-prop-entry'; +import { SettingEntry } from './setting-entry'; -export class SettingField extends SettingPropEntry implements SettingTarget { +export class SettingField extends SettingPropEntry implements SettingEntry { readonly isSettingField = true; readonly isRequired: boolean; readonly transducer: Transducer; @@ -35,7 +35,7 @@ export class SettingField extends SettingPropEntry implements SettingTarget { this._expanded = value; } - constructor(readonly parent: SettingTarget, config: FieldConfig) { + constructor(readonly parent: SettingEntry, config: FieldConfig) { super(parent, config.name, config.type); const { title, items, setter, extraProps, ...rest } = config; @@ -53,6 +53,7 @@ export class SettingField extends SettingPropEntry implements SettingTarget { this.initItems(items); } + // compatiable old config this.transducer = new Transducer(this, { setter }); } diff --git a/packages/designer/src/designer/setting/setting-prop-entry.ts b/packages/designer/src/designer/setting/setting-prop-entry.ts new file mode 100644 index 000000000..d8fd7daf4 --- /dev/null +++ b/packages/designer/src/designer/setting/setting-prop-entry.ts @@ -0,0 +1,192 @@ +import { obx, uniqueId, computed, IEditor } from '@ali/lowcode-globals'; +import { SettingEntry } from './setting-entry'; +import { Node } from '../../document'; +import { ComponentMeta } from '../../component-meta'; +import { Designer } from '../designer'; + +export class SettingPropEntry implements SettingEntry { + // === static properties === + readonly editor: IEditor; + readonly isSameComponent: boolean; + readonly isMultiple: boolean; + readonly isSingle: boolean; + readonly nodes: Node[]; + readonly componentMeta: ComponentMeta | null; + readonly designer: Designer; + readonly top: SettingEntry; + readonly isGroup: boolean; + readonly type: 'field' | 'group'; + readonly id = uniqueId('entry'); + + // ==== dynamic properties ==== + @obx.ref private _name: string | number; + get name() { + return this._name; + } + @computed get path() { + const path = this.parent.path.slice(); + if (this.type === 'field') { + path.push(this.name); + } + return path; + } + + extraProps: any = {}; + + constructor(readonly parent: SettingEntry, name: string | number, type?: 'field' | 'group') { + if (type == null) { + const c = typeof name === 'string' ? name.substr(0, 1) : ''; + if (c === '#') { + this.type = 'group'; + } else { + this.type = 'field'; + } + } else { + this.type = type; + } + // initial self properties + this._name = name; + this.isGroup = this.type === 'group'; + + // copy parent static properties + this.editor = parent.editor; + this.nodes = parent.nodes; + this.componentMeta = parent.componentMeta; + this.isSameComponent = parent.isSameComponent; + this.isMultiple = parent.isMultiple; + this.isSingle = parent.isSingle; + this.designer = parent.designer; + this.top = parent.top; + } + + getId() { + return this.id; + } + + setKey(key: string | number) { + if (this.type !== 'field') { + return; + } + const propName = this.path.join('.'); + let l = this.nodes.length; + while (l-- > 1) { + this.nodes[l].getProp(propName, true)!.key = key; + } + this._name = key; + } + + getKey() { + return this._name; + } + + remove() { + if (this.type !== 'field') { + return; + } + const propName = this.path.join('.'); + let l = this.nodes.length; + while (l-- > 1) { + this.nodes[l].getProp(propName)?.remove() + } + } + + // ====== 当前属性读写 ===== + + /** + * 获取当前属性值 + */ + @computed getValue(): any { + let val: any = null; + if (this.type === 'field') { + val = this.parent.getPropValue(this.name); + } + const { getValue } = this.extraProps; + return getValue ? getValue(this, val) : val; + } + + /** + * 设置当前属性值 + */ + setValue(val: any) { + if (this.type === 'field') { + this.parent.setPropValue(this.name, val); + } + const { setValue } = this.extraProps; + if (setValue) { + setValue(this, val); + } + // TODO: emit value change + } + + /** + * 获取子项 + */ + get(propName: string | number) { + const path = this.path.concat(propName).join('.'); + return this.top.get(path); + } + + /** + * 设置子级属性值 + */ + setPropValue(propName: string | number, value: any) { + const path = this.path.concat(propName).join('.'); + this.top.setPropValue(path, value); + } + + /** + * 获取子级属性值 + */ + getPropValue(propName: string | number): any { + return this.top.getPropValue(this.path.concat(propName).join('.')); + } + + /** + * 获取顶层附属属性值 + */ + getExtraPropValue(propName: string) { + return this.top.getExtraPropValue(propName); + } + + /** + * 设置顶层附属属性值 + */ + setExtraPropValue(propName: string, value: any) { + this.top.setExtraPropValue(propName, value); + } + + // ======= compatibles for vision ====== + getNode() { + return this.top; + } + + getName(): string { + return this.path.join('.'); + } + + getProps() { + return this.top; + } + + onValueChange() { + // TODO: + return () => {}; + } + + getDefaultValue() { + return this.extraProps.defaultValue; + } + + isIgnore() { + return false; + } + /* + getConfig(configName?: K): IPropConfig[K] | IPropConfig { + if (configName) { + return this.config[configName]; + } + + return this.config; + } + */ +} diff --git a/packages/designer/src/designer/setting/setting-top-entry.ts b/packages/designer/src/designer/setting/setting-top-entry.ts new file mode 100644 index 000000000..759ece7fe --- /dev/null +++ b/packages/designer/src/designer/setting/setting-top-entry.ts @@ -0,0 +1,218 @@ +import { EventEmitter } from 'events'; +import { CustomView, computed, isCustomView, IEditor } from '@ali/lowcode-globals'; +import { SettingEntry } from './setting-entry'; +import { SettingField } from './setting-field'; +import { SettingPropEntry } from './setting-prop-entry'; +import { Node } from '../../document'; +import { ComponentMeta } from '../../component-meta'; +import { Designer } from '../designer'; + +function generateSessionId(nodes: Node[]) { + return nodes + .map((node) => node.id) + .sort() + .join(','); +} + +export class SettingTopEntry implements SettingEntry { + private emitter = new EventEmitter(); + private _items: Array = []; + private _componentMeta: ComponentMeta | null = null; + private _isSame: boolean = true; + readonly path = []; + readonly top = this; + readonly parent = this; + + get componentMeta() { + return this._componentMeta; + } + + get items() { + return this._items; + } + + /** + * 同样的 + */ + get isSameComponent(): boolean { + return this._isSame; + } + + /** + * 一个 + */ + get isSingle(): boolean { + return this.nodes.length === 1; + } + + /** + * 多个 + */ + get isMultiple(): boolean { + return this.nodes.length > 1; + } + + readonly id: string; + readonly first: Node; + readonly designer: Designer; + + constructor(readonly editor: IEditor, readonly nodes: Node[]) { + if (nodes.length < 1) { + throw new ReferenceError('nodes should not be empty'); + } + this.id = generateSessionId(nodes); + this.first = nodes[0]; + this.designer = this.first.document.designer; + + // setups + this.setupComponentMeta(); + + // clear fields + this.setupItems(); + } + + private setupComponentMeta() { + // todo: enhance compile a temp configure.compiled + const first = this.first; + const meta = first.componentMeta; + const l = this.nodes.length; + let theSame = true; + for (let i = 1; i < l; i++) { + const other = this.nodes[i]; + if (other.componentMeta !== meta) { + theSame = false; + break; + } + } + if (theSame) { + this._isSame = true; + this._componentMeta = meta; + } else { + this._isSame = false; + this._componentMeta = null; + } + } + + private setupItems() { + if (this.componentMeta) { + this._items = this.componentMeta.configure.map((item) => { + if (isCustomView(item)) { + return item; + } + return new SettingField(this, item as any); + }); + } + } + + /** + * 获取当前属性值 + */ + @computed getValue(): any { + this.first.propsData; + } + + /** + * 设置当前属性值 + */ + setValue(val: any) { + this.setProps(val); + // TODO: emit value change + } + + /** + * 获取子项 + */ + get(propName: string | number): SettingPropEntry { + return new SettingPropEntry(this, propName); + } + + /** + * 设置子级属性值 + */ + setPropValue(propName: string, value: any) { + this.nodes.forEach((node) => { + node.setPropValue(propName, value); + }); + } + + /** + * 获取子级属性值 + */ + getPropValue(propName: string): any { + return this.first.getProp(propName, true)?.getValue(); + } + + /** + * 获取顶层附属属性值 + */ + getExtraPropValue(propName: string) { + return this.first.getExtraProp(propName, false)?.getValue(); + } + + /** + * 设置顶层附属属性值 + */ + setExtraPropValue(propName: string, value: any) { + this.nodes.forEach((node) => { + node.getExtraProp(propName, true)?.setValue(value); + }); + } + + // 设置多个属性值,替换原有值 + setProps(data: object) { + this.nodes.forEach((node) => { + node.setProps(data as any); + }); + } + + // 设置多个属性值,和原有值合并 + mergeProps(data: object) { + this.nodes.forEach((node) => { + node.mergeProps(data as any); + }); + } + + private disposeItems() { + this._items.forEach((item) => isPurgeable(item) && item.purge()); + this._items = []; + } + + purge() { + this.disposeItems(); + this.emitter.removeAllListeners(); + } + + + // ==== compatibles for vision ===== + getProp(propName: string | number) { + return this.get(propName); + } + + // ==== copy some Node api ===== + // `VE.Node.getProps` + getStatus() { + + } + setStatus() { + + } + getChildren() { + // this.nodes.map() + } + getDOMNode() { + + } + getId() { + return this.id; + } + getPage() { + return this.first.document; + } +} + +interface Purgeable { + purge(): void; +} +function isPurgeable(obj: any): obj is Purgeable { + return obj && obj.purge; +} diff --git a/packages/designer/src/designer/setting/utils.js b/packages/designer/src/designer/setting/utils.js new file mode 100644 index 000000000..b8f5bbdcc --- /dev/null +++ b/packages/designer/src/designer/setting/utils.js @@ -0,0 +1,41 @@ +function getHotterFromSetter(setter) { + return setter && (setter.Hotter || (setter.type && setter.type.Hotter)) || []; // eslint-disable-line +} + +function getTransducerFromSetter(setter) { + return setter && ( + setter.transducer || setter.Transducer + || (setter.type && (setter.type.transducer || setter.type.Transducer)) + ) || null; // eslint-disable-line +} + +function combineTransducer(transducer, arr, context) { + if (!transducer && Array.isArray(arr)) { + const [toHot, toNative] = arr; + transducer = { toHot, toNative }; + } + + return { + toHot: (transducer && transducer.toHot || (x => x)).bind(context), // eslint-disable-line + toNative: (transducer && transducer.toNative || (x => x)).bind(context), // eslint-disable-line + }; +} + +export class Transducer { + constructor(context, config) { + this.setterTransducer = combineTransducer( + getTransducerFromSetter(config.setter), + getHotterFromSetter(config.setter), + context, + ); + this.context = context; + } + + toHot(data) { + return this.setterTransducer.toHot(data); + } + + toNative(data) { + return this.setterTransducer.toNative(data); + } +} diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index ad02a56fe..ab206662f 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -142,18 +142,24 @@ export class Node { children: isDOMText(children) || isJSExpression(children) ? children : '', }); } else { - _props = new Props(this, this.buildProps(props), extras); + // run initialChildren this._children = new NodeChildren(this as ParentalNode, children || []); this._children.interalInitParent(); + _props = new Props(this, this.upgradeProps(props), extras); } this.props = _props; } - private buildProps(props: any): any { + private upgradeProps(props: any): any { // TODO: run componentMeta(initials|initialValue|accessor) + // run transform return props; } + private transformOut() { + + } + isContainer(): boolean { return this.isParental() && this.componentMeta.isContainer; } @@ -525,7 +531,7 @@ export class Node { this.document.internalRemoveAndPurgeNode(this); } - // ======= compatibles ==== + // ======= compatible apis ==== isEmpty(): boolean { return this.children ? this.children.isEmpty() : true; } @@ -544,6 +550,12 @@ export class Node { getParent() { return this.parent; } + getId() { + return this.id; + } + getNode() { + return this; + } /** * @deprecated diff --git a/packages/globals/src/di/editor.ts b/packages/globals/src/di/editor.ts new file mode 100644 index 000000000..f3f7f2214 --- /dev/null +++ b/packages/globals/src/di/editor.ts @@ -0,0 +1,33 @@ +import { EventEmitter } from 'events'; +import { RegisterOptions } from 'power-di'; + +export type KeyType = Function | symbol | string; +export type ClassType = Function | (new (...args: any[]) => any); +export interface GetOptions { + forceNew?: boolean; + sourceCls?: ClassType; +} +export type GetReturnType = T extends undefined + ? ClsType extends { + prototype: infer R; + } + ? R + : any + : T; + +export interface IEditor extends EventEmitter { + get(keyOrType: KeyOrType, opt?: GetOptions): GetReturnType | undefined; + + has(keyOrType: KeyType): boolean; + + set(key: KeyType, data: any): void; + + onceGot(keyOrType: KeyOrType): Promise>; + + onGot( + keyOrType: KeyOrType, + fn: (data: GetReturnType) => void, + ): () => void; + + register(data: any, key?: KeyType, options?: RegisterOptions): void; +} diff --git a/packages/globals/src/di/index.ts b/packages/globals/src/di/index.ts index 2c7c76973..fafbd578c 100644 --- a/packages/globals/src/di/index.ts +++ b/packages/globals/src/di/index.ts @@ -1,3 +1,4 @@ export * from './setter'; export * from './transducer'; export * from './ioc-context'; +export * from './editor'; diff --git a/packages/globals/src/types/field-config.ts b/packages/globals/src/types/field-config.ts index 21ace1a20..d003ec1b6 100644 --- a/packages/globals/src/types/field-config.ts +++ b/packages/globals/src/types/field-config.ts @@ -1,5 +1,6 @@ import { TitleContent } from './title'; import { SetterType, DynamicSetter } from './setter-config'; +import { SettingTarget } from './setting-target'; export interface FieldExtraProps { @@ -11,21 +12,21 @@ export interface FieldExtraProps { * default value of target prop for setter use */ defaultValue?: any; - getValue?: (field: any, fieldValue: any) => any; - setValue?: (field: any, value: any) => void; + getValue?: (target: SettingTarget, fieldValue: any) => any; + setValue?: (target: SettingTarget, value: any) => void; /** * the field conditional show, is not set always true * @default undefined */ - condition?: (field: any) => boolean; + condition?: (target: SettingTarget) => boolean; /** * autorun when something change */ - autorun?: (field: any) => void; + autorun?: (target: SettingTarget) => void; /** * is this field is a virtual field that not save to schema */ - virtual?: (field: any) => boolean; + virtual?: (target: SettingTarget) => boolean; /** * default collapsed when display accordion */ diff --git a/packages/globals/src/types/index.ts b/packages/globals/src/types/index.ts index 705f7b28a..9ebb731ff 100644 --- a/packages/globals/src/types/index.ts +++ b/packages/globals/src/types/index.ts @@ -11,3 +11,4 @@ export * from './title'; export * from './utils'; export * from './value-type'; export * from './setter-config'; +export * from './setting-target'; diff --git a/packages/globals/src/types/setter-config.ts b/packages/globals/src/types/setter-config.ts index 956e5af81..add5b59ca 100644 --- a/packages/globals/src/types/setter-config.ts +++ b/packages/globals/src/types/setter-config.ts @@ -1,11 +1,12 @@ import { isReactComponent } from '../utils'; import { ComponentType, ReactElement, isValidElement } from 'react'; import { TitleContent } from './title'; +import { SettingTarget } from './setting-target'; export type CustomView = ReactElement | ComponentType; -export type DynamicProps = (field: any) => object; -export type DynamicSetter = (field: any) => string | SetterConfig | CustomView; +export type DynamicProps = (target: SettingTarget) => object; +export type DynamicSetter = (target: SettingTarget) => string | SetterConfig | CustomView; export interface SetterConfig { /** @@ -18,11 +19,11 @@ export interface SetterConfig { props?: object | DynamicProps; children?: any; isRequired?: boolean; - initialValue?: any | ((field: any) => any); + initialValue?: any | ((target: SettingTarget) => any); /* for MixedSetter */ title?: TitleContent; // for MixedSetter check this is available - condition?: (field: any) => boolean; + condition?: (target: SettingTarget) => boolean; } /** diff --git a/packages/plugin-settings-pane/src/settings/setting-target.ts b/packages/globals/src/types/setting-target.ts similarity index 57% rename from packages/plugin-settings-pane/src/settings/setting-target.ts rename to packages/globals/src/types/setting-target.ts index 0fbf7ccc4..94a589561 100644 --- a/packages/plugin-settings-pane/src/settings/setting-target.ts +++ b/packages/globals/src/types/setting-target.ts @@ -1,12 +1,6 @@ -import { ComponentMeta, Designer, Node } from '@ali/lowcode-designer'; -import Editor from '@ali/lowcode-editor-core'; +import { IEditor } from '../di'; export interface SettingTarget { - - readonly nodes: Node[]; - - readonly componentMeta: ComponentMeta | null; - /** * 同样类型的节点 */ @@ -15,23 +9,26 @@ export interface SettingTarget { /** * 一个 */ - readonly isOneNode: boolean; + readonly isSingle: boolean; /** * 多个 */ - readonly isMultiNodes: boolean; + readonly isMultiple: boolean; /** * 编辑器引用 */ - readonly editor: Editor; - - readonly designer: Designer; + readonly editor: IEditor; + /** + * 访问路径 + */ readonly path: Array; - // 顶端对应 Props + /** + * 顶端 + */ readonly top: SettingTarget; // 父级 @@ -53,15 +50,6 @@ export interface SettingTarget { // 设置子项属性值 setPropValue(propName: string | number, value: any): void; - // 取得兄弟项 - getSibling(propName: string | number): SettingTarget | null; - - // 取得兄弟属性值 - getSiblingValue(propName: string | number): any; - - // 设置兄弟属性值 - setSiblingValue(propName: string | number, value: any): void; - // 获取顶层附属属性值 getExtraPropValue(propName: string): any; diff --git a/packages/plugin-settings-pane/src/settings/main-view.tsx b/packages/plugin-settings-pane/src/settings/main-view.tsx index 9acfce688..e77fb9e2e 100644 --- a/packages/plugin-settings-pane/src/settings/main-view.tsx +++ b/packages/plugin-settings-pane/src/settings/main-view.tsx @@ -1,12 +1,11 @@ import React, { Component, PureComponent } from 'react'; import { Tab, Breadcrumb } from '@alifd/next'; import { Title, createIcon, observer } from '@ali/lowcode-globals'; -import { Node } from '@ali/lowcode-designer'; +import { Node, isSettingField, SettingField } from '@ali/lowcode-designer'; import { Pane as OutlinePane } from '@ali/lowcode-plugin-outline-pane'; import Editor from '@ali/lowcode-editor-core'; import { SettingsMain } from './main'; import SettingsPane from './settings-pane'; -import { isSettingField, SettingField } from './setting-field'; @observer export default class SettingsMainView extends Component<{ editor: Editor }> { @@ -25,7 +24,7 @@ export default class SettingsMainView extends Component<{ editor: Editor }> { if (!settings) { return null; } - if (settings.isMultiNodes) { + if (settings.isMultiple) { return (
{createIcon(settings.componentMeta?.icon, { className: 'lc-settings-navigator-icon'})} diff --git a/packages/plugin-settings-pane/src/settings/main.ts b/packages/plugin-settings-pane/src/settings/main.ts index acf3858a3..f752add28 100644 --- a/packages/plugin-settings-pane/src/settings/main.ts +++ b/packages/plugin-settings-pane/src/settings/main.ts @@ -1,9 +1,15 @@ import { EventEmitter } from 'events'; import { obx, computed } from '@ali/lowcode-globals'; -import { Node, Designer, Selection } from '@ali/lowcode-designer'; +import { Node, Designer, Selection, SettingTopEntry } from '@ali/lowcode-designer'; import { getTreeMaster } from '@ali/lowcode-plugin-outline-pane'; import Editor from '@ali/lowcode-editor-core'; -import { SettingTopEntry, generateSessionId } from './setting-entry'; + +function generateSessionId(nodes: Node[]) { + return nodes + .map((node) => node.id) + .sort() + .join(','); +} export class SettingsMain { private emitter = new EventEmitter(); @@ -24,7 +30,13 @@ export class SettingsMain { private disposeListener: () => void; + private designer?: Designer; + constructor(readonly editor: Editor) { + this.init(); + } + + private async init() { const setupSelection = (selection?: Selection) => { if (selection) { this.setup(selection.getNodes()); @@ -32,17 +44,16 @@ export class SettingsMain { this.setup([]); } }; - editor.on('designer.selection.change', setupSelection); + this.editor.on('designer.selection.change', setupSelection); this.disposeListener = () => { - editor.removeListener('designer.selection.change', setupSelection); + this.editor.removeListener('designer.selection.change', setupSelection); }; - (async () => { - const designer = await editor.onceGot(Designer); - getTreeMaster(designer).onceEnableBuiltin(() => { - this.emitter.emit('outline-visible'); - }); - setupSelection(designer.currentSelection); - })(); + const designer = await this.editor.onceGot(Designer); + this.designer = designer; + getTreeMaster(designer).onceEnableBuiltin(() => { + this.emitter.emit('outline-visible'); + }); + setupSelection(designer.currentSelection); } private setup(nodes: Node[]) { @@ -57,7 +68,11 @@ export class SettingsMain { return; } - this._settings = new SettingTopEntry(this.editor, nodes); + if (!this.designer) { + this.designer = nodes[0].document.designer; + } + + this._settings = this.designer.createSettingEntry(this.editor, nodes); } onceOutlineVisible(fn: () => void): () => void { diff --git a/packages/plugin-settings-pane/src/settings/setting-entry.ts b/packages/plugin-settings-pane/src/settings/setting-entry.ts deleted file mode 100644 index 8d56ce56c..000000000 --- a/packages/plugin-settings-pane/src/settings/setting-entry.ts +++ /dev/null @@ -1,436 +0,0 @@ -import { EventEmitter } from 'events'; -import { Node, ComponentMeta, Designer } from '@ali/lowcode-designer'; -import { CustomView, obx, uniqueId, computed, isCustomView } from '@ali/lowcode-globals'; -import Editor from '@ali/lowcode-editor-core'; -import { SettingTarget } from './setting-target'; -import { SettingField } from './setting-field'; - -export function generateSessionId(nodes: Node[]) { - return nodes - .map((node) => node.id) - .sort() - .join(','); -} - -export class SettingTopEntry implements SettingTarget { - private emitter = new EventEmitter(); - private _items: Array = []; - private _componentMeta: ComponentMeta | null = null; - private _isSame: boolean = true; - readonly path = []; - readonly top = this; - readonly parent = this; - - get componentMeta() { - return this._componentMeta; - } - - get items() { - return this._items; - } - - /** - * 同样的 - */ - get isSameComponent(): boolean { - return this._isSame; - } - - /** - * 一个 - */ - get isOneNode(): boolean { - return this.nodes.length === 1; - } - - /** - * 多个 - */ - get isMultiNodes(): boolean { - return this.nodes.length > 1; - } - - readonly id: string; - readonly first: Node; - readonly designer: Designer; - - constructor(readonly editor: Editor, readonly nodes: Node[], ) { - if (nodes.length < 1) { - throw new ReferenceError('nodes should not be empty'); - } - this.id = generateSessionId(nodes); - this.first = nodes[0]; - this.designer = this.first.document.designer; - - // setups - this.setupComponentMeta(); - - // clear fields - this.setupItems(); - } - - private setupComponentMeta() { - // todo: enhance compile a temp configure.compiled - const first = this.first; - const meta = first.componentMeta; - const l = this.nodes.length; - let theSame = true; - for (let i = 1; i < l; i++) { - const other = this.nodes[i]; - if (other.componentMeta !== meta) { - theSame = false; - break; - } - } - if (theSame) { - this._isSame = true; - this._componentMeta = meta; - } else { - this._isSame = false; - this._componentMeta = null; - } - } - - private setupItems() { - if (this.componentMeta) { - this._items = this.componentMeta.configure.map((item) => { - if (isCustomView(item)) { - return item; - } - return new SettingField(this, item as any); - }); - } - } - - /** - * 获取当前属性值 - */ - @computed getValue(): any { - this.first.propsData; - } - - /** - * 设置当前属性值 - */ - setValue(val: any) { - this.setProps(val); - // TODO: emit value change - } - - /** - * 获取子项 - */ - get(propName: string | number): SettingPropEntry { - return new SettingPropEntry(this, propName); - } - - /** - * 设置子级属性值 - */ - setPropValue(propName: string, value: any) { - this.nodes.forEach((node) => { - node.setPropValue(propName, value); - }); - } - - /** - * 获取子级属性值 - */ - getPropValue(propName: string): any { - return this.first.getProp(propName, true)?.getValue(); - } - - /** - * 获取兄弟项 - */ - getSibling(propName: string | number) { - return null; - } - - /** - * 取得兄弟属性值 - */ - getSiblingValue(propName: string | number): any { - return null; - } - - /** - * 设置兄弟属性值 - */ - setSiblingValue(propName: string | number, value: any): void { - // noop - } - - /** - * 获取顶层附属属性值 - */ - getExtraPropValue(propName: string) { - return this.first.getExtraProp(propName, false)?.getValue(); - } - - /** - * 设置顶层附属属性值 - */ - setExtraPropValue(propName: string, value: any) { - this.nodes.forEach((node) => { - node.getExtraProp(propName, true)?.setValue(value); - }); - } - - // 设置多个属性值,替换原有值 - setProps(data: object) { - this.nodes.forEach((node) => { - node.setProps(data as any); - }); - } - - // 设置多个属性值,和原有值合并 - mergeProps(data: object) { - this.nodes.forEach((node) => { - node.mergeProps(data as any); - }); - } - - private disposeItems() { - this._items.forEach((item) => isPurgeable(item) && item.purge()); - this._items = []; - } - - purge() { - this.disposeItems(); - this.emitter.removeAllListeners(); - } - - // ==== compatibles for vision ===== - getProp(propName: string | number) { - return this.get(propName); - } -} - -export interface Purgeable { - purge(): void; -} -export function isPurgeable(obj: any): obj is Purgeable { - return obj && obj.purge; -} - - -export class SettingPropEntry implements SettingTarget { - // === static properties === - readonly editor: Editor; - readonly isSameComponent: boolean; - readonly isMultiNodes: boolean; - readonly isOneNode: boolean; - readonly nodes: Node[]; - readonly componentMeta: ComponentMeta | null; - readonly designer: Designer; - readonly top: SettingTarget; - readonly isGroup: boolean; - readonly type: 'field' | 'group'; - readonly id = uniqueId('entry'); - - // ==== dynamic properties ==== - @obx.ref private _name: string | number; - get name() { - return this._name; - } - @computed get path() { - const path = this.parent.path.slice(); - if (this.type === 'field') { - path.push(this.name); - } - return path; - } - - extraProps: any = {}; - - constructor(readonly parent: SettingTarget, name: string | number, type?: 'field' | 'group') { - if (type == null) { - const c = typeof name === 'string' ? name.substr(0, 1) : ''; - if (c === '#') { - this.type = 'group'; - } else { - this.type = 'field'; - } - } else { - this.type = type; - } - // initial self properties - this._name = name; - this.isGroup = this.type === 'group'; - - // copy parent static properties - this.editor = parent.editor; - this.nodes = parent.nodes; - this.componentMeta = parent.componentMeta; - this.isSameComponent = parent.isSameComponent; - this.isMultiNodes = parent.isMultiNodes; - this.isOneNode = parent.isOneNode; - this.designer = parent.designer; - this.top = parent.top; - } - - setKey(key: string | number) { - if (this.type !== 'field') { - return; - } - const propName = this.path.join('.'); - let l = this.nodes.length; - while (l-- > 1) { - this.nodes[l].getProp(propName, true)!.key = key; - } - this._name = key; - } - - remove() { - if (this.type !== 'field') { - return; - } - const propName = this.path.join('.'); - let l = this.nodes.length; - while (l-- > 1) { - this.nodes[l].getProp(propName)?.remove() - } - } - - // ====== 当前属性读写 ===== - - /** - * 获取当前属性值 - */ - @computed getValue(): any { - let val: any = null; - if (this.type === 'field') { - val = this.parent.getPropValue(this.name); - } - const { getValue } = this.extraProps; - return getValue ? getValue(this, val) : val; - } - - /** - * 设置当前属性值 - */ - setValue(val: any) { - if (this.type === 'field') { - this.parent.setPropValue(this.name, val); - } - const { setValue } = this.extraProps; - if (setValue) { - setValue(this, val); - } - // TODO: emit value change - } - - /** - * 获取子项 - */ - get(propName: string | number) { - const path = this.path.concat(propName).join('.'); - return this.top.get(path); - } - - /** - * 设置子级属性值 - */ - setPropValue(propName: string | number, value: any) { - const path = this.path.concat(propName).join('.'); - this.top.setPropValue(path, value); - } - - /** - * 获取子级属性值 - */ - getPropValue(propName: string | number): any { - return this.top.getPropValue(this.path.concat(propName).join('.')); - } - - /** - * 获取兄弟项 - */ - getSibling(propName: string | number) { - return this.parent.get(propName); - } - - /** - * 取得兄弟属性值 - */ - getSiblingValue(propName: string | number): any { - return this.parent.getPropValue(propName); - } - - /** - * 设置兄弟属性值 - */ - setSiblingValue(propName: string | number, value: any): void { - this.parent.setPropValue(propName, value); - } - - /** - * 获取顶层附属属性值 - */ - getExtraPropValue(propName: string) { - return this.top.getExtraPropValue(propName); - } - - /** - * 设置顶层附属属性值 - */ - setExtraPropValue(propName: string, value: any) { - this.top.setExtraPropValue(propName, value); - } - - // ======= compatibles for vision ====== - getNode() { - return this.nodes[0]; - } - - getProps() { - return this.top; - } - - onValueChange() { - return () => {}; - } - - getId() { - return this.id; - } - - getName(): string { - return this.path.join('.'); - } - - getKey() { - return this.name; - } - - getDefaultValue() { - return this.extraProps.defaultValue; - } - /* - getConfig(configName?: K): IPropConfig[K] | IPropConfig { - if (configName) { - return this.config[configName]; - } - - return this.config; - } - */ - /* - isHidden() { - return false; - } - - isDisabled() { - return false; - } - - getSetter() { - - } - */ - - isIgnore() { - return false; - } -} diff --git a/packages/plugin-settings-pane/src/settings/settings-pane.tsx b/packages/plugin-settings-pane/src/settings/settings-pane.tsx index 0501a9710..11edc8a4c 100644 --- a/packages/plugin-settings-pane/src/settings/settings-pane.tsx +++ b/packages/plugin-settings-pane/src/settings/settings-pane.tsx @@ -10,9 +10,7 @@ import { } from '@ali/lowcode-globals'; import { Field, createField } from '../field'; import PopupService from '../popup'; -import { SettingField, isSettingField } from './setting-field'; -import { SettingTarget } from './setting-target'; -import { SettingTopEntry } from './setting-entry'; +import { SettingField, isSettingField, SettingTopEntry, SettingEntry } from '@ali/lowcode-designer'; @observer class SettingFieldView extends Component<{ field: SettingField }> { @@ -20,7 +18,7 @@ class SettingFieldView extends Component<{ field: SettingField }> { const { field } = this.props; const { extraProps } = field; const { condition, defaultValue } = extraProps; - const visible = field.isOneNode && typeof condition === 'function' ? condition(field) !== false : true; + const visible = field.isSingle && typeof condition === 'function' ? condition(field) !== false : true; if (!visible) { return null; } @@ -99,7 +97,7 @@ class SettingGroupView extends Component<{ field: SettingField }> { const { field } = this.props; const { extraProps } = field; const { condition } = extraProps; - const visible = field.isOneNode && typeof condition === 'function' ? condition(field) !== false : true; + const visible = field.isSingle && typeof condition === 'function' ? condition(field) !== false : true; if (!visible) { return null; @@ -116,7 +114,7 @@ class SettingGroupView extends Component<{ field: SettingField }> { } } -export function createSettingFieldView(item: SettingField | CustomView, field: SettingTarget, index?: number) { +export function createSettingFieldView(item: SettingField | CustomView, field: SettingEntry, index?: number) { if (isSettingField(item)) { if (item.isGroup) { return ; diff --git a/packages/vision-polyfill/src/bundle/upgrade-metadata.ts b/packages/vision-polyfill/src/bundle/upgrade-metadata.ts index b9da6eb35..aa0bcaace 100644 --- a/packages/vision-polyfill/src/bundle/upgrade-metadata.ts +++ b/packages/vision-polyfill/src/bundle/upgrade-metadata.ts @@ -1,7 +1,7 @@ import { ComponentType, ReactElement, isValidElement, ComponentClass } from 'react'; -import { isI18nData } from '@ali/lowcode-globals'; +import { isI18nData, SettingTarget } from '@ali/lowcode-globals'; -type Field = any; +type Field = SettingTarget; export enum DISPLAY_TYPE { NONE = 'none', // => condition'plain' @@ -282,7 +282,6 @@ export function upgradePropConfig(config: OldPropConfig) { componentName: 'SlotSetter', initialValue: () => ({ type: 'JSSlot', - // params: value: initialChildren }), } @@ -315,14 +314,14 @@ export function upgradePropConfig(config: OldPropConfig) { initialFn } } - extraProps.initialValue = (field: Field, defaultValue?: any) => { + extraProps.initialValue = (field: Field, currentValue: any, defaultValue?: any) => { if (defaultValue === undefined) { defaultValue = extraProps.defaultValue; } if (typeof initialFn === 'function') { // ? - return initialFn(null, defaultValue); + return initialFn.call(field, currentValue, defaultValue); } return defaultValue;