From 5f569cc1ca98b988372b749ef853f0824bb593d0 Mon Sep 17 00:00:00 2001 From: kangwei Date: Sun, 15 Mar 2020 01:50:28 +0800 Subject: [PATCH] complete metadata transducer --- .../src/builtins/simulator/host/host.ts | 18 +- .../builtins/simulator/renderer/renderer.ts | 15 +- .../designer/src/designer/component-meta.ts | 224 +++++++++ .../designer/src/designer/component-type.ts | 343 ------------- packages/designer/src/designer/designer.ts | 61 +-- .../src/designer/document/document-model.ts | 20 +- .../src/designer/document/node/node.ts | 31 +- packages/designer/src/designer/prop-config.ts | 46 ++ packages/designer/src/designer/simulator.ts | 7 +- .../builtin-setters/array-setter/index.tsx | 63 +-- .../builtin-setters/array-setter/style.less | 12 + .../builtin-setters/object-setter/index.tsx | 3 +- packages/plugin-settings/src/index.tsx | 7 +- packages/plugin-settings/src/main.ts | 36 +- .../src/register-transducer.ts | 459 ++++++++++++++++++ 15 files changed, 878 insertions(+), 467 deletions(-) create mode 100644 packages/designer/src/designer/component-meta.ts delete mode 100644 packages/designer/src/designer/component-type.ts create mode 100644 packages/designer/src/designer/prop-config.ts create mode 100644 packages/plugin-settings/src/register-transducer.ts diff --git a/packages/designer/src/builtins/simulator/host/host.ts b/packages/designer/src/builtins/simulator/host/host.ts index e8c87c19b..313fec373 100644 --- a/packages/designer/src/builtins/simulator/host/host.ts +++ b/packages/designer/src/builtins/simulator/host/host.ts @@ -29,7 +29,7 @@ import { CanvasPoint, } from '../../../designer/helper/location'; import { isNodeSchema, NodeSchema } from '../../../designer/schema'; -import { ComponentDescription } from '../../../designer/component-type'; +import { ComponentMetadata } from '../../../designer/component-meta'; import { ReactInstance } from 'react'; import { setNativeSelection } from '../../../designer/helper/navtive-selection'; import cursor from '../../../designer/helper/cursor'; @@ -332,8 +332,14 @@ export class SimulatorHost implements ISimulator { /** * @see ISimulator */ - describeComponent(component: Component): ComponentDescription { - throw new Error('Method not implemented.'); + generateComponentMetadata(componentName: string): ComponentMetadata { + const component = this.getComponent(componentName); + // TODO: + // 1. generate builtin div/p/h1/h2 + // 2. read propTypes + return { + componentName, + }; } /** @@ -826,7 +832,7 @@ export class SimulatorHost implements ISimulator { return this.checkDropTarget(container, dragObject as any); } - const config = container.componentType; + const config = container.componentMeta; if (!config.isContainer) { return false; @@ -911,7 +917,7 @@ export class SimulatorHost implements ISimulator { checkNestingUp(parent: NodeParent, target: NodeSchema | Node): boolean { if (isNode(target) || isNodeSchema(target)) { - const config = isNode(target) ? target.componentType : this.designer.getComponentType(target.componentName); + const config = isNode(target) ? target.componentMeta : this.document.getComponentMeta(target.componentName); if (config) { return config.checkNestingUp(target, parent); } @@ -921,7 +927,7 @@ export class SimulatorHost implements ISimulator { } checkNestingDown(parent: NodeParent, target: NodeSchema | Node): boolean { - const config = parent.componentType; + const config = parent.componentMeta; return config.checkNestingDown(parent, target) && this.checkNestingUp(parent, target); } // #endregion diff --git a/packages/designer/src/builtins/simulator/renderer/renderer.ts b/packages/designer/src/builtins/simulator/renderer/renderer.ts index b456cc69a..7573c7adb 100644 --- a/packages/designer/src/builtins/simulator/renderer/renderer.ts +++ b/packages/designer/src/builtins/simulator/renderer/renderer.ts @@ -7,7 +7,7 @@ import { RootSchema, NpmInfo } from '../../../designer/schema'; import { getClientRects } from '../../../utils/get-client-rects'; import { Asset } from '../utils/asset'; import loader from '../utils/loader'; -import { ComponentDescription } from '../../../designer/component-type'; +import { ComponentMetadata } from '../../../designer/component-meta'; import { reactFindDOMNodes, FIBER_KEY } from '../utils/react-find-dom-nodes'; import { isESModule } from '../../../../../utils/is-es-module'; import { NodeInstance } from '../../../designer/simulator'; @@ -39,13 +39,13 @@ export class SimulatorRenderer { // sync device }); - host.componentsConsumer.consume(async (componentsAsset) => { + host.componentsConsumer.consume(async componentsAsset => { if (componentsAsset) { await this.load(componentsAsset); this.buildComponents(); } }); - host.injectionConsumer.consume((data) => { + host.injectionConsumer.consume(data => { // sync utils, i18n, contants,... config this._appContext = { utils: {}, @@ -142,7 +142,7 @@ export class SimulatorRenderer { origUnmount = origUnmount.origUnmount; } // hack! delete instance from map - const newUnmount = function (this: any) { + const newUnmount = function(this: any) { unmountIntance(id, instance); origUnmount && origUnmount.call(this); }; @@ -204,7 +204,7 @@ export class SimulatorRenderer { cursor.release(); } - private _running: boolean = false; + private _running = false; run() { if (this._running) { return; @@ -281,15 +281,14 @@ function findComponent(componentName: string, npm?: NpmInfo) { return getSubComponent(library, paths); } -function buildComponents(componentsMap: { [componentName: string]: ComponentDescription }) { +function buildComponents(componentsMap: { [componentName: string]: NpmInfo }) { const components: any = {}; Object.keys(componentsMap).forEach(componentName => { - components[componentName] = findComponent(componentName, componentsMap[componentName].npm); + components[componentName] = findComponent(componentName, componentsMap[componentName]); }); return components; } - let REACT_KEY = ''; function cacheReactKey(el: Element): Element { if (REACT_KEY !== '') { diff --git a/packages/designer/src/designer/component-meta.ts b/packages/designer/src/designer/component-meta.ts new file mode 100644 index 000000000..71562673e --- /dev/null +++ b/packages/designer/src/designer/component-meta.ts @@ -0,0 +1,224 @@ +import { ReactNode } from 'react'; +import Node, { NodeParent } from './document/node/node'; +import { NodeData, NodeSchema } from './schema'; +import { PropConfig } from './prop-config'; + +export interface NestingRule { + childWhitelist?: string[]; + parentWhitelist?: string[]; +} + +export interface Configure { + props?: any[]; + styles?: object; + events?: object; + component?: { + isContainer?: boolean; + isModal?: boolean; + descriptor?: string; + nestingRule?: NestingRule; + }; +} + +export interface ComponentMetadata { + componentName: string; + /** + * unique id + */ + uri?: string; + /** + * title or description + */ + title?: string; + /** + * svg icon for component + */ + icon?: string | ReactNode; + tags?: string[]; + description?: string; + docUrl?: string; + screenshot?: string; + devMode?: 'procode' | 'lowcode'; + npm?: { + package: string; + exportName: string; + subName: string; + main: string; + destructuring: boolean; + version: string; + }; + props?: PropConfig[]; + configure?: any[] | Configure; +} + +interface TransformedComponentMetadata extends ComponentMetadata { + configure?: Configure & { + combined?: any[]; + }; +} + +function ensureAList(list?: string | string[]): string[] | null { + if (!list) { + return null; + } + if (!Array.isArray(list)) { + list = list.split(/ *[ ,|] */).filter(Boolean); + } + if (list.length < 1) { + return null; + } + return list; +} + +function npmToURI(npm: { + package: string; + exportName?: string; + subName?: string; + destructuring?: boolean; + main?: string; + version: string; +}): string { + const pkg = []; + if (npm.package) { + pkg.push(npm.package); + } + if (npm.main) { + if (npm.main[0] === '/') { + pkg.push(npm.main.slice(1)); + } else if (npm.main.slice(0, 2) === './') { + pkg.push(npm.main.slice(2)); + } else { + pkg.push(npm.main); + } + } + + let uri = pkg.join('/'); + uri += `:${npm.destructuring && npm.exportName ? npm.exportName : 'default'}`; + + if (npm.subName) { + uri += `.${npm.subName}`; + } + + return uri; +} + +export type MetadataTransducer = (prev: ComponentMetadata) => TransformedComponentMetadata; +const metadataTransducers: MetadataTransducer[] = []; + +export function registerMetadataTransducer(transducer: MetadataTransducer) { + metadataTransducers.push(transducer); +} + +export class ComponentMeta { + readonly isComponentMeta = true; + private _uri?: string; + get uri(): string { + return this._uri!; + } + private _componentName?: string; + get componentName(): string { + return this._componentName!; + } + private _isContainer?: boolean; + get isContainer(): boolean { + return this._isContainer! || this.isRootComponent(); + } + private _isModal?: boolean; + get isModal(): boolean { + return this._isModal!; + } + private _descriptor?: string; + get descriptor(): string { + return this._descriptor!; + } + private _acceptable?: boolean; + get acceptable(): boolean { + return this._acceptable!; + } + private _transformedMetadata?: TransformedComponentMetadata; + get configure() { + const config = this._transformedMetadata?.configure; + return config?.combined || config?.props || []; + } + + private parentWhitelist?: string[] | null; + private childWhitelist?: string[] | null; + + get title() { + return this._metadata.title || this.componentName; + } + + get icon() { + return this._metadata.icon; + } + + constructor(private _metadata: ComponentMetadata) { + this.parseMetadata(_metadata); + } + + private parseMetadata(metadta: ComponentMetadata) { + const { componentName, uri, npm, props } = metadta; + this._uri = uri || (npm ? npmToURI(npm) : componentName); + this._componentName = componentName; + + metadta.uri = this._uri; + // 额外转换逻辑 + this._transformedMetadata = this.transformMetadata(metadta); + + const { configure = {} } = this._transformedMetadata; + this._acceptable = false; + + const { component } = configure; + if (component) { + this._isContainer = component.isContainer ? true : false; + this._isModal = component.isModal ? true : false; + this._descriptor = component.descriptor; + if (component.nestingRule) { + const { parentWhitelist, childWhitelist } = component.nestingRule; + this.parentWhitelist = ensureAList(parentWhitelist); + this.childWhitelist = ensureAList(childWhitelist); + } + } else { + this._isContainer = false; + this._isModal = false; + } + } + + private transformMetadata(metadta: ComponentMetadata): TransformedComponentMetadata { + const result = metadataTransducers.reduce((prevMetadata, current) => { + return current(prevMetadata); + }, metadta); + + if (!result.configure) { + result.configure = {}; + } + return result as any; + } + + isRootComponent() { + return this.componentName === 'Page' || this.componentName === 'Block' || this.componentName === 'Component'; + } + + set metadata(metadata: ComponentMetadata) { + this._metadata = metadata; + this.parseMetadata(metadata); + } + + get metadata(): ComponentMetadata { + return this._metadata; + } + + checkNestingUp(my: Node | NodeData, parent: NodeParent) { + if (this.parentWhitelist) { + return this.parentWhitelist.includes(parent.componentName); + } + return true; + } + + checkNestingDown(my: Node, target: Node | NodeSchema) { + if (this.childWhitelist) { + return this.childWhitelist.includes(target.componentName); + } + return true; + } +} diff --git a/packages/designer/src/designer/component-type.ts b/packages/designer/src/designer/component-type.ts deleted file mode 100644 index c3a8bf77d..000000000 --- a/packages/designer/src/designer/component-type.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { ReactNode } from 'react'; -import Node, { NodeParent } from './document/node/node'; -import { NodeData, NodeSchema } from './schema'; - -export type BasicTypes = 'array' | 'bool' | 'func' | 'number' | 'object' | 'string' | 'node' | 'element' | 'any'; -export interface CompositeType { - type: BasicTypes; - isRequired: boolean; -} - -// TODO: add complex types - -export interface PropConfig { - name: string; - propType: BasicTypes | CompositeType; - description?: string; - defaultValue?: any; -} - -export interface NestingRule { - childWhitelist?: string[]; - parentWhitelist?: string[]; -} - -export interface Configure { - props?: any[]; - styles?: object; - events?: object; - component?: { - isContainer?: boolean; - isModal?: boolean; - descriptor?: string; - nestingRule?: NestingRule; - }; -} - -export interface ComponentDescription { - componentName: string; - /** - * unique id - */ - uri?: string; - /** - * title or description - */ - title?: string; - /** - * svg icon for component - */ - icon?: string | ReactNode; - tags?: string[]; - description?: string; - docUrl?: string; - screenshot?: string; - devMode?: 'procode' | 'lowcode'; - npm?: { - package: string; - exportName: string; - subName: string; - main: string; - destructuring: boolean; - version: string; - }; - props?: PropConfig[]; - configure?: any[] | Configure; -} - -function ensureAList(list?: string | string[]): string[] | null { - if (!list) { - return null; - } - if (!Array.isArray(list)) { - list = list.split(/ *[ ,|] */).filter(Boolean); - } - if (list.length < 1) { - return null; - } - return list; -} - -function npmToURI(npm: { - package: string; - exportName?: string; - subName?: string; - destructuring?: boolean; - main?: string; - version: string; -}): string { - const pkg = []; - if (npm.package) { - pkg.push(npm.package); - } - if (npm.main) { - if (npm.main[0] === '/') { - pkg.push(npm.main.slice(1)); - } else if (npm.main.slice(0, 2) === './') { - pkg.push(npm.main.slice(2)); - } else { - pkg.push(npm.main); - } - } - - let uri = pkg.join('/'); - uri += `:${npm.destructuring && npm.exportName ? npm.exportName : 'default'}`; - - if (npm.subName) { - uri += `.${npm.subName}`; - } - - return uri; -} - -function generatePropsConfigure(props: PropConfig[]) { - // todo: - return []; -} - -export class ComponentType { - readonly isComponentType = true; - private _uri?: string; - get uri(): string { - return this._uri!; - } - private _componentName?: string; - get componentName(): string { - return this._componentName!; - } - private _isContainer?: boolean; - get isContainer(): boolean { - return true; // this._isContainer! || this.isRootComponent(); - } - private _isModal?: boolean; - get isModal(): boolean { - return this._isModal!; - } - private _descriptor?: string; - get descriptor(): string { - return this._descriptor!; - } - private _acceptable?: boolean; - get acceptable(): boolean { - return this._acceptable!; - } - private _configure?: Configure; - get configure() { - return [ - { - name: '#props', - title: '属性', - items: [ - { - name: 'label', - title: '标签', - setter: 'StringSetter', - }, - { - name: 'data', - title: '数据', - setter: { - componentName: 'ArraySetter', - props: { - itemConfig: { - setter: { - componentName: 'ObjectSetter', - props: { - config: { - items: [ - { - name: 'title', - title: '名称', - setter: 'StringSetter', - important: true, - }, - { - name: 'records', - title: '记录集', - setter: { - componentName: 'ArraySetter', - props: { - itemConfig: { - setter: { - componentName: 'ArraySetter', - props: { - itemConfig: { - setter: 'StringSetter', - defaultValue: '', - }, - }, - }, - defaultValue: [], - }, - }, - }, - important: true, - }, - ], - extraConfig: {}, - }, - // mode: 'popup' - }, - }, - defaultValue: {}, - }, - }, - }, - }, - { - name: 'age', - title: '年龄', - setter: 'NumberSetter', - }, - ], - }, - { - name: '#styles', - title: '样式', - items: [ - { - name: 'className', - title: '类名绑定', - setter: 'ClassNameSetter', - }, - { - name: 'className2', - title: '类名绑定', - setter: 'StringSetter', - }, - { - name: '#inlineStyles', - title: '行内样式', - items: [], - }, - ], - }, - { - name: '#events', - title: '事件', - items: [ - { - name: '!events', - title: '事件绑定', - setter: { - componentName: 'EventsSetter', - }, - extraProps: { - getValue(field: any) { - console.info('lifeCycles', field.getExtraPropValue('lifeCycles')); - return field.getPropValue('xxx'); - }, - setValue(field: any, val: any) { - field.setExtraPropValue('lifeCycles', val); - field.setPropValue('xxx', val); - }, - }, - }, - ], - }, - { - name: '#data', - title: '数据', - items: [], - }, - ]; - } - - private parentWhitelist?: string[] | null; - private childWhitelist?: string[] | null; - - get title() { - return this._spec.title || this.componentName; - } - - get icon() { - return this._spec.icon; - } - - constructor(private _spec: ComponentDescription) { - this.parseSpec(_spec); - } - - private parseSpec(spec: ComponentDescription) { - const { componentName, uri, configure, npm, props } = spec; - this._uri = uri || (npm ? npmToURI(npm) : componentName); - this._componentName = componentName; - this._acceptable = false; - - if (!configure || Array.isArray(configure)) { - this._configure = { - props: !configure ? [] : configure, - styles: { - supportClassName: true, - supportInlineStyle: true, - }, - }; - } else { - this._configure = configure; - } - if (!this._configure.props) { - this._configure.props = props ? generatePropsConfigure(props) : []; - } - const { component } = this._configure; - if (component) { - this._isContainer = component.isContainer ? true : false; - this._isModal = component.isModal ? true : false; - this._descriptor = component.descriptor; - if (component.nestingRule) { - const { parentWhitelist, childWhitelist } = component.nestingRule; - this.parentWhitelist = ensureAList(parentWhitelist); - this.childWhitelist = ensureAList(childWhitelist); - } - } else { - this._isContainer = false; - this._isModal = false; - } - } - - isRootComponent() { - return this.componentName === 'Page' || this.componentName === 'Block' || this.componentName === 'Component'; - } - - set spec(spec: ComponentDescription) { - this._spec = spec; - this.parseSpec(spec); - } - - get spec(): ComponentDescription { - return this._spec; - } - - checkNestingUp(my: Node | NodeData, parent: NodeParent) { - if (this.parentWhitelist) { - return this.parentWhitelist.includes(parent.componentName); - } - return true; - } - - checkNestingDown(my: Node, target: Node | NodeSchema) { - if (this.childWhitelist) { - return this.childWhitelist.includes(target.componentName); - } - return true; - } -} diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index 3d0da8b8f..ddf6abd61 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -2,7 +2,7 @@ import { ComponentType as ReactComponentType } from 'react'; import { obx, computed, autorun } from '@recore/obx'; import BuiltinSimulatorView from '../builtins/simulator'; import Project from './project'; -import { ProjectSchema } from './schema'; +import { ProjectSchema, NpmInfo } from './schema'; import Dragon, { isDragNodeObject, isDragNodeDataObject, LocateEvent, DragObject } from './helper/dragon'; import ActiveTracker from './helper/active-tracker'; import Hovering from './helper/hovering'; @@ -10,7 +10,7 @@ import Location, { LocationData, isLocationChildrenDetail } from './helper/locat import DocumentModel from './document/document-model'; import Node, { insertChildren } from './document/node/node'; import { isRootNode } from './document/node/root-node'; -import { ComponentDescription, ComponentType } from './component-type'; +import { ComponentMetadata, ComponentMeta } from './component-meta'; import Scroller, { IScrollable } from './helper/scroller'; import { INodeSelector } from './simulator'; import OffsetObserver, { createOffsetObserver } from './helper/offset-observer'; @@ -25,7 +25,7 @@ export interface DesignerProps { simulatorComponent?: ReactComponentType; dragGhostComponent?: ReactComponentType; suspensed?: boolean; - componentsDescription?: ComponentDescription[]; + componentsDescription?: ComponentMetadata[]; eventPipe?: EventEmitter; onMount?: (designer: Designer) => void; onDragstart?: (e: LocateEvent) => void; @@ -222,7 +222,7 @@ export default class Designer { this.suspensed = props.suspensed; } if (props.componentsDescription !== this.props.componentsDescription && props.componentsDescription != null) { - this.buildComponentTypesMap(props.componentsDescription); + this.buildComponentMetasMap(props.componentsDescription); } } else { // init hotkeys @@ -239,7 +239,7 @@ export default class Designer { this.suspensed = props.suspensed; } if (props.componentsDescription != null) { - this.buildComponentTypesMap(props.componentsDescription); + this.buildComponentMetasMap(props.componentsDescription); } } this.props = props; @@ -283,52 +283,53 @@ export default class Designer { // todo: } - @obx.val private _componentTypesMap = new Map(); - private _lostComponentTypesMap = new Map(); + @obx.val private _componentMetasMap = new Map(); + private _lostComponentMetasMap = new Map(); - private buildComponentTypesMap(specs: ComponentDescription[]) { - specs.forEach(spec => { - const key = spec.componentName; - let cType = this._componentTypesMap.get(key); - if (cType) { - cType.spec = spec; + private buildComponentMetasMap(metas: ComponentMetadata[]) { + metas.forEach(data => { + const key = data.componentName; + let meta = this._componentMetasMap.get(key); + if (meta) { + meta.metadata = data; } else { - cType = this._lostComponentTypesMap.get(key); + meta = this._lostComponentMetasMap.get(key); - if (cType) { - cType.spec = spec; - this._lostComponentTypesMap.delete(key); + if (meta) { + meta.metadata = data; + this._lostComponentMetasMap.delete(key); } else { - cType = new ComponentType(spec); + meta = new ComponentMeta(data); } - this._componentTypesMap.set(key, cType); + this._componentMetasMap.set(key, meta); } }); } - getComponentType(componentName: string): ComponentType { - if (this._componentTypesMap.has(componentName)) { - return this._componentTypesMap.get(componentName)!; + getComponentMeta(componentName: string, generateMetadata?: () => ComponentMetadata | null): ComponentMeta { + if (this._componentMetasMap.has(componentName)) { + return this._componentMetasMap.get(componentName)!; } - if (this._lostComponentTypesMap.has(componentName)) { - return this._lostComponentTypesMap.get(componentName)!; + if (this._lostComponentMetasMap.has(componentName)) { + return this._lostComponentMetasMap.get(componentName)!; } - const cType = new ComponentType({ + const meta = new ComponentMeta({ componentName, + ...(generateMetadata ? generateMetadata() : null), }); - this._lostComponentTypesMap.set(componentName, cType); + this._lostComponentMetasMap.set(componentName, meta); - return cType; + return meta; } - get componentsMap(): { [key: string]: ComponentDescription } { + get componentsMap(): { [key: string]: NpmInfo } { const maps: any = {}; - this._componentTypesMap.forEach((config, key) => { - maps[key] = config.spec; + this._componentMetasMap.forEach((config, key) => { + maps[key] = config.metadata.npm; }); return maps; } diff --git a/packages/designer/src/designer/document/document-model.ts b/packages/designer/src/designer/document/document-model.ts index d33cc0f17..f20256db0 100644 --- a/packages/designer/src/designer/document/document-model.ts +++ b/packages/designer/src/designer/document/document-model.ts @@ -6,7 +6,7 @@ import RootNode from './node/root-node'; import { ISimulator, Component } from '../simulator'; import { computed, obx, autorun } from '@recore/obx'; import Location from '../helper/location'; -import { ComponentType } from '../component-type'; +import { ComponentMeta } from '../component-meta'; import History from '../helper/history'; import Prop from './node/props/prop'; @@ -41,7 +41,7 @@ export default class DocumentModel { } get fileName(): string { - return (this.rootNode.getExtraProp('fileName')?.getAsString()) || this.id; + return this.rootNode.getExtraProp('fileName')?.getAsString() || this.id; } set fileName(fileName: string) { @@ -60,7 +60,7 @@ export default class DocumentModel { this.id = this.rootNode.id; this.history = new History( () => this.schema, - (schema) => this.import(schema as RootSchema, true), + schema => this.import(schema as RootSchema, true), ); this.setupListenActiveNodes(); } @@ -237,7 +237,7 @@ export default class DocumentModel { return this.rootNode.schema as any; } - import(schema: RootSchema, checkId: boolean = false) { + import(schema: RootSchema, checkId = false) { this.rootNode.import(schema, checkId); // todo: purge something // todo: select added and active track added @@ -285,13 +285,15 @@ export default class DocumentModel { return this.simulator!.getComponent(componentName); } - getComponentType(componentName: string, component?: Component | null): ComponentType { - // TODO: guess componentConfig from component by simulator - return this.designer.getComponentType(componentName); + getComponentMeta(componentName: string): ComponentMeta { + return this.designer.getComponentMeta( + componentName, + () => this.simulator?.generateComponentMetadata(componentName) || null, + ); } - @obx.ref private _opened: boolean = false; - @obx.ref private _suspensed: boolean = false; + @obx.ref private _opened = false; + @obx.ref private _suspensed = false; /** * 是否不是激活的 diff --git a/packages/designer/src/designer/document/node/node.ts b/packages/designer/src/designer/document/node/node.ts index 09799c8e4..c4dcbdf45 100644 --- a/packages/designer/src/designer/document/node/node.ts +++ b/packages/designer/src/designer/document/node/node.ts @@ -6,7 +6,7 @@ import NodeChildren from './node-children'; import Prop from './props/prop'; import NodeContent from './node-content'; import { Component } from '../../simulator'; -import { ComponentType } from '../../component-type'; +import { ComponentMeta } from '../../component-meta'; /** * 基础节点 @@ -78,8 +78,8 @@ export default class Node { @computed get title(): string { let t = this.getExtraProp('title'); - if (!t && this.componentType.descriptor) { - t = this.getProp(this.componentType.descriptor, false); + if (!t && this.componentMeta.descriptor) { + t = this.getProp(this.componentMeta.descriptor, false); } if (t) { const v = t.getAsString(); @@ -87,7 +87,7 @@ export default class Node { return v; } } - return this.componentType.title; + return this.componentMeta.title; } get isSlotRoot(): boolean { @@ -157,7 +157,7 @@ export default class Node { /** * 悬停高亮 */ - hover(flag: boolean = true) { + hover(flag = true) { if (flag) { this.document.designer.hovering.hover(this); } else { @@ -168,18 +168,18 @@ export default class Node { /** * 节点组件类 */ - @obx.ref get component(): Component | null { + @obx.ref get component(): Component { if (this.isNodeParent) { - return this.document.getComponent(this.componentName); + return this.document.getComponent(this.componentName) || this.componentName; } - return null; + return this.componentName; } /** * 节点组件描述 */ - @computed get componentType(): ComponentType { - return this.document.getComponentType(this.componentName, this.component); + @computed get componentMeta(): ComponentMeta { + return this.document.getComponentMeta(this.componentName); } @computed get propsData(): PropsMap | PropsList | null { @@ -223,19 +223,19 @@ export default class Node { } wrapWith(schema: NodeSchema) { - + // todo } - replaceWith(schema: NodeSchema, migrate: boolean = true) { + replaceWith(schema: NodeSchema, migrate = true) { // reuse the same id? or replaceSelection // } - getProp(path: string, useStash: boolean = true): Prop | null { + getProp(path: string, useStash = true): Prop | null { return this.props?.query(path, useStash as any) || null; } - getExtraProp(key: string, useStash: boolean = true): Prop | null { + getExtraProp(key: string, useStash = true): Prop | null { return this.props?.get(EXTRA_KEY_PREFIX + key, useStash) || null; } @@ -316,7 +316,7 @@ export default class Node { this.import(data); } - import(data: NodeSchema, checkId: boolean = false) { + import(data: NodeSchema, checkId = false) { const { componentName, id, children, props, ...extras } = data; if (isNodeParent(this)) { @@ -514,4 +514,3 @@ export function insertChildren( } return results; } - diff --git a/packages/designer/src/designer/prop-config.ts b/packages/designer/src/designer/prop-config.ts new file mode 100644 index 000000000..a8bc3620d --- /dev/null +++ b/packages/designer/src/designer/prop-config.ts @@ -0,0 +1,46 @@ +export type PropType = BasicType | RequiredType | ComplexType; +export type BasicType = 'array' | 'bool' | 'func' | 'number' | 'object' | 'string' | 'node' | 'element' | 'any'; +export type ComplexType = OneOf | OneOfType | ArrayOf | ObjectOf | Shape | Exact; + +export interface RequiredType { + type: BasicType; + isRequired?: boolean; +} + +export interface OneOf { + type: 'oneOf'; + value: string[]; + isRequired?: boolean; +} +export interface OneOfType { + type: 'oneOfType'; + value: PropType[]; + isRequired?: boolean; +} +export interface ArrayOf { + type: 'arrayOf'; + value: PropType; + isRequired?: boolean; +} +export interface ObjectOf { + type: 'objectOf'; + value: PropType; + isRequired?: boolean; +} +export interface Shape { + type: 'shape'; + value: PropConfig[]; + isRequired?: boolean; +} +export interface Exact { + type: 'exact'; + value: PropConfig[]; + isRequired?: boolean; +} + +export interface PropConfig { + name: string; + propType: PropType; + description?: string; + defaultValue?: any; +} diff --git a/packages/designer/src/designer/simulator.ts b/packages/designer/src/designer/simulator.ts index 07735d86e..8212354fc 100644 --- a/packages/designer/src/designer/simulator.ts +++ b/packages/designer/src/designer/simulator.ts @@ -3,7 +3,7 @@ import { LocateEvent, ISensor } from './helper/dragon'; import { Point } from './helper/location'; import Node from './document/node/node'; import { ScrollTarget, IScrollable } from './helper/scroller'; -import { ComponentDescription } from './component-type'; +import { ComponentMetadata } from './component-meta'; export type AutoFit = '100%'; export const AutoFit = '100%'; @@ -85,7 +85,6 @@ export interface ISimulator

extends ISensor { // 获取区块代码, 通过 components 传递,可异步获取 setProps(props: P): void; - setSuspense(suspensed: boolean): void; // #region ========= drag and drop helpers ============= @@ -117,7 +116,7 @@ export interface ISimulator

extends ISensor { /** * 描述组件 */ - describeComponent(component: Component): ComponentDescription; + generateComponentMetadata(componentName: string): ComponentMetadata; /** * 根据组件信息获取组件类 */ @@ -158,7 +157,7 @@ export interface NodeInstance { /** * 组件类定义 */ -export type Component = ComponentType | object; +export type Component = ComponentType | object | string; /** * 组件实例定义 diff --git a/packages/plugin-settings/src/builtin-setters/array-setter/index.tsx b/packages/plugin-settings/src/builtin-setters/array-setter/index.tsx index 8c802ceea..71c521bfc 100644 --- a/packages/plugin-settings/src/builtin-setters/array-setter/index.tsx +++ b/packages/plugin-settings/src/builtin-setters/array-setter/index.tsx @@ -16,11 +16,8 @@ interface ArraySetterState { interface ArraySetterProps { value: any[]; field: SettingField; - itemConfig?: { - setter?: SetterType; - defaultValue?: any | ((field: SettingField) => any); - required?: boolean; - }; + itemSetter?: SetterType; + columns?: FieldConfig[]; multiValue?: boolean; } @@ -45,8 +42,8 @@ export class ListSetter extends Component { if (newLength > originLength) { for (let i = originLength; i < newLength; i++) { const item = field.createField({ - ...props.itemConfig, name: i, + setter: props.itemSetter, forceInline: 2, }); items[i] = item; @@ -86,16 +83,16 @@ export class ListSetter extends Component { private scrollToLast: boolean = false; onAdd() { const { items, itemsMap } = this.state; - const { itemConfig } = this.props; - const defaultValue = itemConfig ? itemConfig.defaultValue : null; + const { itemSetter } = this.props; + const initialValue = typeof itemSetter === 'object' ? (itemSetter as any).initialValue : null; const item = this.props.field.createField({ - ...itemConfig, name: items.length, - forceInline: 1, + setter: itemSetter, + forceInline: 2, }); items.push(item); itemsMap.set(item.id, item); - item.setValue(typeof defaultValue === 'function' ? defaultValue(item) : defaultValue); + item.setValue(typeof initialValue === 'function' ? initialValue(item) : initialValue); this.scrollToLast = true; this.setState({ items: items.slice(), @@ -132,13 +129,11 @@ export class ListSetter extends Component { } render() { - // mini Button: depends popup - if (this.props.itemConfig) { - // check is ObjectSetter then check if show columns + let columns: any = null; + if (this.props.columns) { + columns = this.props.columns.map(column => ); } - console.info(this.state.items); - const { items } = this.state; const scrollToLast = this.scrollToLast; this.scrollToLast = false; @@ -172,6 +167,7 @@ export class ListSetter extends Component<ArraySetterProps, ArraySetterState> { <span>添加</span> </Button> </div>*/} + {columns && <div className="lc-setter-list-columns">{columns}</div>} {content} <Button className="lc-setter-list-add" type="primary" onClick={this.onAdd.bind(this)}> <Icon type="add" /> @@ -226,7 +222,6 @@ export default class ArraySetter extends Component<{ itemConfig?: { setter?: SetterType; defaultValue?: any | ((field: SettingField) => any); - required?: boolean; }; mode?: 'popup' | 'list'; forceInline?: boolean; @@ -237,6 +232,20 @@ export default class ArraySetter extends Component<{ render() { const { mode, forceInline, ...props } = this.props; const { field, itemConfig } = props; + let columns: FieldConfig[] | undefined; + const setter: any = itemConfig?.setter; + if (setter?.componentName === 'ObjectSetter') { + const items: FieldConfig[] = setter.props?.config?.items; + if (items && Array.isArray(items)) { + columns = items.filter(item => item.isRequired || item.important); + if (columns.length === 3) { + columns = columns.slice(0, 3); + } else if (columns.length > 3) { + columns = columns.slice(0, 4); + } + } + } + if (mode === 'popup' || forceInline) { const title = ( <Fragment> @@ -246,26 +255,22 @@ export default class ArraySetter extends Component<{ ); if (!this.pipe) { let width = 360; - const setter: any = itemConfig?.setter; - if (setter?.componentName === 'ObjectSetter') { - const items: FieldConfig[] = setter.props?.config?.items; - if (items && Array.isArray(items)) { - const length = items.filter(item => item.required || item.important).length; - if (length === 3) { - width = 480; - } else if (length > 3) { - width = 600; - } + if (columns) { + if (columns.length === 3) { + width = 480; + } else if (columns.length > 3) { + width = 600; } } this.pipe = (this.context as PopupPipe).create({ width }); } this.pipe.send( - <TableSetter key={field.id} {...props} />, + <TableSetter key={field.id} {...props} columns={columns} />, title, ); return ( <Button + type={forceInline ? 'normal' : 'primary'} onClick={e => { this.pipe.show((e as any).target, field.id); }} @@ -275,7 +280,7 @@ export default class ArraySetter extends Component<{ </Button> ); } else { - return <ListSetter {...props} />; + return <ListSetter {...props} columns={columns?.slice(0, 2)} />; } } } diff --git a/packages/plugin-settings/src/builtin-setters/array-setter/style.less b/packages/plugin-settings/src/builtin-setters/array-setter/style.less index 86cf52f0e..407ed8041 100644 --- a/packages/plugin-settings/src/builtin-setters/array-setter/style.less +++ b/packages/plugin-settings/src/builtin-setters/array-setter/style.less @@ -18,6 +18,18 @@ margin-top: 8px;; } + + .lc-setter-list-columns { + display: flex; + > .lc-title { + flex: 1; + justify-content: center; + } + margin-left: 47px; + margin-right: 28px; + margin-bottom: 5px; + } + .lc-setter-list-scroll-body { margin: -8px -5px; padding: 8px 10px; diff --git a/packages/plugin-settings/src/builtin-setters/object-setter/index.tsx b/packages/plugin-settings/src/builtin-setters/object-setter/index.tsx index 5d7f8f412..2c5be9433 100644 --- a/packages/plugin-settings/src/builtin-setters/object-setter/index.tsx +++ b/packages/plugin-settings/src/builtin-setters/object-setter/index.tsx @@ -34,7 +34,6 @@ interface ObjectSetterConfig { items?: FieldConfig[]; extraConfig?: { setter?: SetterType; - defaultValue?: any | ((field: SettingField, editor: any) => any); }; } @@ -62,7 +61,7 @@ class RowSetter extends Component<RowSetterProps> { const l = Math.min(config.items.length, columns); for (let i = 0; i < l; i++) { const conf = config.items[i]; - if (conf.required || conf.important) { + if (conf.isRequired || conf.important) { const item = field.createField({ ...conf, // in column-cell diff --git a/packages/plugin-settings/src/index.tsx b/packages/plugin-settings/src/index.tsx index 1508f6cb6..c7ebbf33a 100644 --- a/packages/plugin-settings/src/index.tsx +++ b/packages/plugin-settings/src/index.tsx @@ -7,6 +7,7 @@ import SettingsPane, { registerSetter, createSetterContent, getSetter, createSet import Node from '../../designer/src/designer/document/node/node'; import ArraySetter from './builtin-setters/array-setter'; import ObjectSetter from './builtin-setters/object-setter'; +import './register-transducer'; export default class SettingsMainView extends Component { private main: SettingsMain; @@ -31,9 +32,9 @@ export default class SettingsMainView extends Component { if (this.main.isMulti) { return ( <div className="lc-settings-navigator"> - {this.main.componentType!.icon || <Icon type="ellipsis" size="small" />} + {this.main.componentMeta!.icon || <Icon type="ellipsis" size="small" />} <span> - {this.main.componentType!.title} x {this.main.nodes.length} + {this.main.componentMeta!.title} x {this.main.nodes.length} </span> </div> ); @@ -57,7 +58,7 @@ export default class SettingsMainView extends Component { return ( <div className="lc-settings-navigator"> - {this.main.componentType!.icon || <Icon type="ellipsis" size="small" />} + {this.main.componentMeta!.icon || <Icon type="ellipsis" size="small" />} <Breadcrumb className="lc-settings-node-breadcrumb">{items}</Breadcrumb> </div> ); diff --git a/packages/plugin-settings/src/main.ts b/packages/plugin-settings/src/main.ts index 099c8f673..872ef5e05 100644 --- a/packages/plugin-settings/src/main.ts +++ b/packages/plugin-settings/src/main.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; import { uniqueId } from '../../utils/unique-id'; -import { ComponentType } from '../../designer/src/designer/component-type'; +import { ComponentMeta } from '../../designer/src/designer/component-meta'; import Node from '../../designer/src/designer/document/node/node'; import { TitleContent } from './title'; import { ReactElement, ComponentType as ReactComponentType, isValidElement } from 'react'; @@ -12,7 +12,7 @@ export interface SettingTarget { // 所设置的节点集,至少一个 readonly nodes: Node[]; - readonly componentType: ComponentType | null; + readonly componentMeta: ComponentMeta | null; readonly items: Array<SettingField | CustomView>; @@ -92,6 +92,8 @@ export interface SetterConfig { */ props?: object | DynamicProps; children?: any; + isRequired?: boolean; + initialValue?: any | ((field: SettingField) => any); } /** @@ -103,7 +105,7 @@ export interface FieldExtraProps { /** * 是否必填参数 */ - required?: boolean; + isRequired?: boolean; /** * default value of target prop for setter use */ @@ -172,7 +174,7 @@ export class SettingField implements SettingTarget { readonly isOne: boolean; readonly isNone: boolean; readonly nodes: Node[]; - readonly componentType: ComponentType | null; + readonly componentMeta: ComponentMeta | null; readonly designer: Designer; readonly top: SettingTarget; get path() { @@ -212,7 +214,7 @@ export class SettingField implements SettingTarget { // copy parent properties this.editor = parent.editor; this.nodes = parent.nodes; - this.componentType = parent.componentType; + this.componentMeta = parent.componentMeta; this.isSame = parent.isSame; this.isMulti = parent.isMulti; this.isOne = parent.isOne; @@ -369,7 +371,7 @@ export class SettingsMain implements SettingTarget { private _nodes: Node[] = []; private _items: Array<SettingField | CustomView> = []; private _sessionId = ''; - private _componentType: ComponentType | null = null; + private _componentMeta: ComponentMeta | null = null; private _isSame: boolean = true; readonly path = []; readonly top: SettingTarget = this; @@ -378,8 +380,8 @@ export class SettingsMain implements SettingTarget { return this._nodes; } - get componentType() { - return this._componentType; + get componentMeta() { + return this._componentMeta; } get items() { @@ -506,7 +508,7 @@ export class SettingsMain implements SettingTarget { this._sessionId = sessionId; // setups - this.setupComponentType(); + this.setupComponentMeta(); // todo: enhance when componentType not changed do merge // clear fields @@ -521,36 +523,36 @@ export class SettingsMain implements SettingTarget { this._items = []; } - private setupComponentType() { + private setupComponentMeta() { if (this.nodes.length < 1) { this._isSame = false; - this._componentType = null; + this._componentMeta = null; return; } const first = this.nodes[0]; - const type = first.componentType; + 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 as any).componentType !== type) { + if ((other as any).componentType !== meta) { theSame = false; break; } } if (theSame) { this._isSame = true; - this._componentType = type; + this._componentMeta = meta; } else { this._isSame = false; - this._componentType = null; + this._componentMeta = null; } } private setupItems() { this.disposeItems(); - if (this.componentType) { - this._items = this.componentType.configure.map(item => { + if (this.componentMeta) { + this._items = this.componentMeta.configure.map(item => { if (isCustomView(item)) { return item; } diff --git a/packages/plugin-settings/src/register-transducer.ts b/packages/plugin-settings/src/register-transducer.ts new file mode 100644 index 000000000..1af3a066f --- /dev/null +++ b/packages/plugin-settings/src/register-transducer.ts @@ -0,0 +1,459 @@ +import { + PropConfig, + PropType, + Shape, + OneOf, + ObjectOf, + ArrayOf, + OneOfType, +} from '../../designer/src/designer/prop-config'; +import { SetterType, FieldConfig, SettingField } from './main'; +import { registerMetadataTransducer } from '../../designer/src/designer/component-meta'; + +export function propConfigToFieldConfig(propConfig: PropConfig): FieldConfig { + return { + ...propConfig, + setter: propTypeToSetter(propConfig.propType), + }; +} + +export function propTypeToSetter(propType: PropType): SetterType { + let typeName: string; + let isRequired: boolean | undefined = false; + if (typeof propType === 'string') { + typeName = propType; + } else { + typeName = propType.type; + isRequired = propType.isRequired; + } + // TODO: use mixinSetter wrapper + switch (typeName) { + case 'string': + return { + componentName: 'StringSetter', + isRequired, + initialValue: '', + }; + + case 'number': + return { + componentName: 'NumberSetter', + isRequired, + initialValue: 0, + }; + case 'bool': + return { + componentName: 'NumberSetter', + isRequired, + initialValue: false, + }; + case 'oneOf': + const dataSource = ((propType as OneOf).value || []).map((value, index) => { + const t = typeof value; + return { + label: t === 'string' || t === 'number' || t === 'boolean' ? String(value) : `value ${index}`, + value, + }; + }); + const componentName = dataSource.length > 4 ? 'SelectSetter' : 'RadioSetter'; + return { + componentName, + props: { dataSource }, + isRequired, + initialValue: dataSource[0] ? dataSource[0].value : null, + }; + + case 'element': + case 'node': + return { + // slotSetter + componentName: 'NodeSetter', + props: { + mode: typeName, + }, + isRequired, + initialValue: { + type: 'JSSlot', + value: '', + }, + }; + case 'shape': + case 'exact': + const items = (propType as Shape).value.map(item => propConfigToFieldConfig(item)); + return { + componentName: 'ObjectSetter', + props: { + config: { + items, + extraSetter: typeName === 'shape' ? propTypeToSetter('any') : null, + }, + }, + isRequired, + initialValue: (field: any) => { + const data: any = {}; + items.forEach(item => { + let initial = item.defaultValue; + if (initial == null && item.setter && typeof item.setter === 'object') { + initial = (item.setter as any).initialValue; + } + data[item.name] = initial ? (typeof initial === 'function' ? initial(field) : initial) : null; + }); + return data; + }, + }; + case 'object': + case 'objectOf': + return { + componentName: 'ObjectSetter', + props: { + config: { + extraSetter: propTypeToSetter(typeName === 'objectOf' ? (propType as ObjectOf).value : 'any'), + }, + }, + isRequired, + }; + case 'array': + case 'arrayOf': + return { + componentName: 'ArraySetter', + props: { + itemSetter: propTypeToSetter(typeName === 'arrayOf' ? (propType as ArrayOf).value : 'any'), + }, + isRequired, + initialValue: [], + }; + case 'func': + return { + componentName: 'FunctionSetter', + isRequired, + initialValue: { + type: 'JSFunction', + value: 'function(){}', + }, + }; + case 'oneOfType': + return { + componentName: 'MixinSetter', + props: { + setters: (propType as OneOfType).value.map(item => propTypeToSetter(item)), + }, + isRequired, + }; + } + + return { + componentName: 'MixinSetter', + isRequired, + }; +} + +const EVENT_RE = /^on[A-Z][\w]*$/; + +registerMetadataTransducer(metadata => { + if (metadata.configure) { + if (Array.isArray(metadata.configure)) { + return { + ...metadata, + configure: { + props: metadata.configure, + }, + }; + } + if (metadata.configure.props) { + return metadata as any; + } + } + if (!metadata.props) { + return { + ...metadata, + configure: { + props: metadata.configure && Array.isArray(metadata.configure) ? metadata.configure : [], + }, + }; + } + + const { configure = {} } = metadata; + const { props = [], component = {}, events = {}, styles = {} } = configure; + const supportEvents: string[] | null = (events as any).supportEvents ? null : []; + + metadata.props.forEach(prop => { + const { name, propType } = prop; + if (name === 'children' && (component.isContainer || (propType === 'node' || propType === 'element' || propType === 'any'))) { + if (component.isContainer !== false) { + component.isContainer = true; + return; + } + } + + if (EVENT_RE.test(name) && (propType === 'func' || propType === 'any')) { + if (supportEvents) { + supportEvents.push(name); + } + return; + } + + if (name === 'className' && (propType === 'string' || propType === 'any')) { + if ((styles as any).supportClassName == null) { + (styles as any).supportClassName = true; + } + return; + } + + if (name === 'style' && (propType === 'object' || propType === 'any')) { + if ((styles as any).supportInlineStyle == null) { + (styles as any).supportInlineStyle = true; + } + return; + } + + props.push(propConfigToFieldConfig(prop)); + }); + + + return { + ...metadata, + configure: { + ...configure, + props, + events, + styles, + component, + }, + }; +}); + +registerMetadataTransducer((metadata) => { + const { configure = {}, componentName } = metadata; + const { component = {} } = configure as any; + if (!component.nestingRule) { + let m; + // uri match xx.Group set subcontrolling: true, childWhiteList + if ((m = /^(.+)\.Group$/.exec(componentName))) { + // component.subControlling = true; + if (!component.nestingRule) { + component.nestingRule = { + childWhitelist: [`${m[1]}`], + }; + } + } + // uri match xx.Node set selfControlled: false, parentWhiteList + else if ((m = /^(.+)\.Node$/.exec(componentName))) { + // component.selfControlled = false; + component.nestingRule = { + parentWhitelist: [`${m[1]}`, componentName], + }; + } + // uri match .Item .Node .Option set parentWhiteList + else if ((m = /^(.+)\.(Item|Node|Option)$/.exec(componentName))) { + component.nestingRule = { + parentWhitelist: [`${m[1]}`], + }; + } + } + if (component.isModal == null && /Dialog/.test(componentName)) { + component.isModal = true; + } + return { + ...metadata, + configure: { + ...configure, + component, + }, + }; +}); + +registerMetadataTransducer((metadata) => { + const { componentName, configure = {} } = metadata; + if (componentName === '#frag') { + return { + ...metadata, + configure: { + ...configure, + combined: [{ + name: 'children', + title: '内容设置', + setter: { + componentName: 'MixinSetter', + props: { + setters: [{ + componentName: 'StringSetter', + initialValue: '', + }, { + componentName: 'ExpressionSetter', + initialValue: { + type: 'JSExpression', + value: '' + } + }] + } + } + }] + } + } + } + + const { props, events, styles } = configure as any; + let supportEvents: any; + let isRoot: boolean = false; + if (componentName === 'Page' || componentName === 'Component') { + isRoot = true; + // todo + /* + supportEvents = [{ + description: '初始化时', + name: 'constructor' + }, { + description: '装载后', + name: 'componentDidMount' + }, { + description: '更新时', + name: 'componentDidMount' + }, { + description: '卸载时', + name: 'componentWillUnmount' + }] + */ + } else { + supportEvents = (events?.supportEvents || []).map((event: any) => { + return typeof event === 'string' ? { + name: event, + } : event; + }); + } + // 通用设置 + const propsGroup = props || []; + propsGroup.push({ + name: '#generals', + title: '通用', + items: [{ + name: 'id', + title: 'ID', + setter: 'StringSetter', + }, { + name: 'key', + title: 'Key', + setter: 'StringSetter', + }, { + name: 'ref', + setter: 'StringSetter', + }, { + name: '!more', + title: '更多', + setter: 'PropertiesSetter' + }] + }); + const combined = [{ + title: '属性', + name: '#props', + items: propsGroup, + }]; + const stylesGroup = []; + if (styles?.supportClassName) { + stylesGroup.push({ + name: 'className', + title: '类名绑定', + setter: 'ClassNameSetter' + }); + } + if (styles?.supportInlineStyle) { + stylesGroup.push({ + name: 'style', + title: '行内样式', + setter: 'StyleSetter' + }); + } + if (stylesGroup.length > 0) { + combined.push({ + name: '#styles', + title: '样式', + items: stylesGroup, + }); + } + + if (supportEvents) { + combined.push({ + name: '#events', + title: '事件', + items: [{ + name: '!events', + title: '事件设置', + setter: { + componentName: 'EventsSetter', + props: { + definition: [] + } + }, + getValue(field: SettingField) { + return []; + }, + setValue(field: SettingField) { + + } + }] + }); + } + + if (isRoot) { + // todo... + } else { + combined.push({ + name: '#advanced', + title: '高级', + items: [{ + name: '__condition', + title: '条件显示', + setter: 'ExpressionSetter' + }, { + name: '#loop', + title: '循环', + items: [{ + name: '__loop', + title: '循环数据', + setter: { + componentName: 'MixinSetter', + props: { + setters: [{ + componentName: 'JSONSetter', + props: { + mode: 'popup', + placeholder: '编辑数据' + } + }, { + componentName: 'ExpressionSetter', + props: { + placeholder: '绑定数据' + } + }] + } + } + }, { + name: '__loopArgs.0', + title: '迭代变量名', + setter: { + componentName: 'StringSetter', + placeholder: '默认为 item' + } + }, { + name: '__loopArgs.1', + title: '索引变量名', + setter: { + componentName: 'StringSetter', + placeholder: '默认为 index' + } + }, { + name: 'key', + title: 'Key', + setter: 'ExpressionSetter', + }] + }] + }) + } + + return { + ...metadata, + configure: { + ...configure, + combined, + }, + }; +});