diff --git a/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx b/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx index 9fa3b6798..f0c546b99 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx @@ -10,7 +10,7 @@ import { isVertical } from '../../designer'; import { ISimulatorHost, } from '../../simulator'; -import {NodeParent } from '../../document'; +import {ParentalNode } from '../../document'; import './insertion.less'; interface InsertionData { @@ -24,7 +24,7 @@ interface InsertionData { /** * 处理拖拽子节点(INode)情况 */ -function processChildrenDetail(sim: ISimulatorHost, container: NodeParent, detail: LocationChildrenDetail): InsertionData { +function processChildrenDetail(sim: ISimulatorHost, container: ParentalNode, detail: LocationChildrenDetail): InsertionData { let edge = detail.edge || null; if (!edge) { diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index 18fc15a83..0dbddf5f1 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -2,7 +2,7 @@ import { obx, autorun, computed } from '@ali/lowcode-globals'; import { ISimulatorHost, Component, NodeInstance, ComponentInstance } from '../simulator'; import Viewport from './viewport'; import { createSimulator } from './create-simulator'; -import { Node, NodeParent, DocumentModel, isNodeParent, isNode, contains, isRootNode } from '../document'; +import { Node, ParentalNode, DocumentModel, isNode, contains, isRootNode } from '../document'; import ResourceConsumer from './resource-consumer'; import { AssetLevel, Asset, AssetList, assetBundle, assetItem, AssetType, getPublicPath } from '@ali/lowcode-globals'; import { @@ -21,7 +21,7 @@ import { Rect, CanvasPoint, } from '../designer'; -import { parseProps } from './utils/parse-props'; +import { parseProps, parseMetadata } from './utils/parse-metadata'; import { isElement, hotkey } from '@ali/lowcode-globals'; import { ComponentMetadata } from '@ali/lowcode-globals'; import { BuiltinSimulatorRenderer } from './renderer'; @@ -387,16 +387,19 @@ export class BuiltinSimulatorHost implements ISimulatorHost, selector: string): Element } interface DropContainer { - container: NodeParent; + container: ParentalNode; instance: ComponentInstance; } diff --git a/packages/designer/src/builtin-simulator/utils/parse-props.ts b/packages/designer/src/builtin-simulator/utils/parse-metadata.ts similarity index 97% rename from packages/designer/src/builtin-simulator/utils/parse-props.ts rename to packages/designer/src/builtin-simulator/utils/parse-metadata.ts index a427ff3c1..257f09609 100644 --- a/packages/designer/src/builtin-simulator/utils/parse-props.ts +++ b/packages/designer/src/builtin-simulator/utils/parse-metadata.ts @@ -198,3 +198,10 @@ export function parseProps(component: any): PropConfig[] { return Object.keys(result).map(key => result[key]); } + +export function parseMetadata(component: any): any { + return { + props: parseProps(component), + ...component.componentMetadata, + }; +} diff --git a/packages/designer/src/component-meta.ts b/packages/designer/src/component-meta.ts index 3c531acb7..d4ff4a6b6 100644 --- a/packages/designer/src/component-meta.ts +++ b/packages/designer/src/component-meta.ts @@ -11,7 +11,7 @@ import { computed, NestingFilter, } from '@ali/lowcode-globals'; -import { Node, NodeParent } from './document'; +import { Node, ParentalNode } from './document'; import { Designer } from './designer'; import { intl } from './locale'; import { IconContainer } from './icons/container'; @@ -194,7 +194,7 @@ export class ComponentMeta { return this._transformedMetadata!; } - checkNestingUp(my: Node | NodeData, parent: NodeParent) { + checkNestingUp(my: Node | NodeData, parent: ParentalNode) { // 检查父子关系,直接约束型,在画布中拖拽直接掠过目标容器 if (this.parentWhitelist) { return this.parentWhitelist(parent, my); diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index a53002477..39ddd99c9 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -10,7 +10,7 @@ import { autorun, } from '@ali/lowcode-globals'; import { Project } from '../project'; -import { Node, DocumentModel, insertChildren, isRootNode, NodeParent } from '../document'; +import { Node, DocumentModel, insertChildren, isRootNode, ParentalNode } from '../document'; import { ComponentMeta } from '../component-meta'; import { INodeSelector, Component } from '../simulator'; import { Scroller, IScrollable } from './scroller'; @@ -201,7 +201,7 @@ export class Designer { /** * 获得合适的插入位置 */ - getSuitableInsertion(): { target: NodeParent; index?: number } | null { + getSuitableInsertion(): { target: ParentalNode; index?: number } | null { const activedDoc = this.project.currentDocument; if (!activedDoc) { return null; diff --git a/packages/designer/src/designer/dragon.ts b/packages/designer/src/designer/dragon.ts index e009a08a2..58c14acea 100644 --- a/packages/designer/src/designer/dragon.ts +++ b/packages/designer/src/designer/dragon.ts @@ -88,7 +88,6 @@ export interface DragNodeObject { export interface DragNodeDataObject { type: DragObjectType.NodeData; data: NodeSchema | NodeSchema[]; - maps?: { [componentName: string]: string }; thumbnail?: string; description?: string; [extra: string]: any; @@ -233,7 +232,7 @@ export class Dragon { const masterSensors = this.getMasterSensors(); const handleEvents = makeEventsHandler(boostEvent, masterSensors); const newBie = !isDragNodeObject(dragObject); - const forceCopyState = isDragNodeObject(dragObject) && dragObject.nodes.some((node) => node.isSlotRoot); + const forceCopyState = isDragNodeObject(dragObject) && dragObject.nodes.some((node) => node.isSlot()); const isBoostFromDragAPI = boostEvent.type.substr(0, 4) === 'drag'; let lastSensor: ISensor | undefined; diff --git a/packages/designer/src/designer/location.ts b/packages/designer/src/designer/location.ts index 2c82891ca..5de3df7e0 100644 --- a/packages/designer/src/designer/location.ts +++ b/packages/designer/src/designer/location.ts @@ -1,8 +1,8 @@ -import { DocumentModel, Node as ComponentNode, NodeParent } from '../document'; +import { DocumentModel, Node as ComponentNode, ParentalNode } from '../document'; import { LocateEvent } from './dragon'; export interface LocationData { - target: NodeParent; // shadowNode | ConditionFlow | ElementNode | RootNode + target: ParentalNode; // shadowNode | ConditionFlow | ElementNode | RootNode detail: LocationDetail; source: string; event: LocateEvent; @@ -27,7 +27,7 @@ export interface LocationChildrenDetail { rect?: Rect; align?: 'V' | 'H'; }; - focus?: { type: 'slots' } | { type: 'node'; node: NodeParent }; + focus?: { type: 'slots' } | { type: 'node'; node: ParentalNode }; } export interface LocationPropDetail { @@ -126,7 +126,7 @@ export function getWindow(elem: Element | Document): Window { } export class DropLocation { - readonly target: NodeParent; + readonly target: ParentalNode; readonly detail: LocationDetail; readonly event: LocateEvent; readonly source: string; diff --git a/packages/designer/src/document/document-model.ts b/packages/designer/src/document/document-model.ts index 13b2e19d3..b0129f51c 100644 --- a/packages/designer/src/document/document-model.ts +++ b/packages/designer/src/document/document-model.ts @@ -1,5 +1,4 @@ import { - RootSchema, NodeData, isJSExpression, isDOMText, @@ -9,16 +8,26 @@ import { autorun, isNodeSchema, uniqueId, + PageSchema, + ComponentSchema, + RootSchema, } from '@ali/lowcode-globals'; import { Project } from '../project'; import { ISimulatorHost } from '../simulator'; import { ComponentMeta } from '../component-meta'; import { isDragNodeDataObject, DragNodeObject, DragNodeDataObject, DropLocation } from '../designer'; -import { Node, isNodeParent, insertChildren, insertChild, NodeParent, isNode } from './node/node'; +import { Node, insertChildren, insertChild, isNode, RootNode, ParentalNode } from './node/node'; import { Selection } from './selection'; -import { RootNode } from './node/root-node'; import { History } from './history'; -import { Prop } from './node/props/prop'; +import { ExportType } from './node'; + +export type GetDataType = T extends undefined + ? NodeType extends { + schema: infer R; + } + ? R + : any + : T; export class DocumentModel { /** @@ -58,7 +67,7 @@ export class DocumentModel { this.rootNode.getExtraProp('fileName', true)?.setValue(fileName); } - private _modalNode?: NodeParent; + private _modalNode?: ParentalNode; private _blank?: boolean; get modalNode() { return this._modalNode; @@ -81,8 +90,9 @@ export class DocumentModel { this._blank = true; } - this.rootNode = this.createRootNode(schema || { + this.rootNode = this.createNode(schema || { componentName: 'Page', + id: 'root', fileName: '' }); @@ -130,7 +140,7 @@ export class DocumentModel { /** * 根据 schema 创建一个节点 */ - createNode(data: NodeData, slotFor?: Prop): Node { + createNode(data: GetDataType): T { let schema: any; if (isDOMText(data) || isJSExpression(data)) { schema = { @@ -150,14 +160,13 @@ export class DocumentModel { // will move to another position // todo: this.activeNodes?.push(node); } - node.internalSetSlotFor(slotFor); node.import(schema, true); } else if (node) { node = null; } } if (!node) { - node = new Node(this, schema, slotFor); + node = new Node(this, schema); // will add // todo: this.activeNodes?.push(node); } @@ -169,27 +178,20 @@ export class DocumentModel { this.nodesMap.set(node.id, node); this.nodes.add(node); - return node; - } - - private createRootNode(schema: RootSchema) { - const node = new RootNode(this, schema); - this.nodesMap.set(node.id, node); - this.nodes.add(node); - return node; + return node as any; } /** * 插入一个节点 */ - insertNode(parent: NodeParent, thing: Node | NodeData, at?: number | null, copy?: boolean): Node { + insertNode(parent: ParentalNode, thing: Node | NodeData, at?: number | null, copy?: boolean): Node { return insertChild(parent, thing, at, copy); } /** * 插入多个节点 */ - insertNodes(parent: NodeParent, thing: Node[] | NodeData[], at?: number | null, copy?: boolean) { + insertNodes(parent: ParentalNode, thing: Node[] | NodeData[], at?: number | null, copy?: boolean) { return insertChildren(parent, thing, at, copy); } @@ -249,7 +251,7 @@ export class DocumentModel { return null; } const wrapper = this.createNode(schema); - if (isNodeParent(wrapper)) { + if (wrapper.isParental()) { const first = nodes[0]; // TODO: check nesting rules x 2 insertChild(first.parent!, wrapper, first.index); @@ -270,11 +272,15 @@ export class DocumentModel { } import(schema: RootSchema, checkId = false) { - this.rootNode.import(schema, checkId); + this.rootNode.import(schema as any, checkId); // todo: purge something // todo: select added and active track added } + export(exportType: ExportType = ExportType.ForSerilize) { + return this.rootNode.export(exportType); + } + /** * 导出节点数据 */ @@ -409,7 +415,7 @@ export class DocumentModel { // todo: } - checkNesting(dropTarget: NodeParent, dragObject: DragNodeObject | DragNodeDataObject): boolean { + checkNesting(dropTarget: ParentalNode, dragObject: DragNodeObject | DragNodeDataObject): boolean { let items: Array; if (isDragNodeDataObject(dragObject)) { items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data]; @@ -419,7 +425,7 @@ export class DocumentModel { return items.every((item) => this.checkNestingDown(dropTarget, item)); } - checkDropTarget(dropTarget: NodeParent, dragObject: DragNodeObject | DragNodeDataObject): boolean { + checkDropTarget(dropTarget: ParentalNode, dragObject: DragNodeObject | DragNodeDataObject): boolean { let items: Array; if (isDragNodeDataObject(dragObject)) { items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data]; @@ -432,7 +438,7 @@ export class DocumentModel { /** * 检查对象对父级的要求,涉及配置 parentWhitelist */ - checkNestingUp(parent: NodeParent, obj: NodeSchema | Node): boolean { + checkNestingUp(parent: ParentalNode, obj: NodeSchema | Node): boolean { if (isNode(obj) || isNodeSchema(obj)) { const config = isNode(obj) ? obj.componentMeta : this.getComponentMeta(obj.componentName); if (config) { @@ -446,7 +452,7 @@ export class DocumentModel { /** * 检查投放位置对子级的要求,涉及配置 childWhitelist */ - checkNestingDown(parent: NodeParent, obj: NodeSchema | Node): boolean { + checkNestingDown(parent: ParentalNode, obj: NodeSchema | Node): boolean { const config = parent.componentMeta; return config.checkNestingDown(parent, obj) && this.checkNestingUp(parent, obj); } diff --git a/packages/designer/src/document/node/export-type.ts b/packages/designer/src/document/node/export-type.ts new file mode 100644 index 000000000..b57e52fff --- /dev/null +++ b/packages/designer/src/document/node/export-type.ts @@ -0,0 +1,5 @@ +export enum ExportType { + ForRender = 1, + ForSerilize = 2, + ForSave = 3, +} diff --git a/packages/designer/src/document/node/index.ts b/packages/designer/src/document/node/index.ts index 4a387ce79..7251d43c8 100644 --- a/packages/designer/src/document/node/index.ts +++ b/packages/designer/src/document/node/index.ts @@ -1,7 +1,7 @@ export * from './exclusive-group'; export * from './node'; export * from './node-children'; -export * from './root-node'; export * from './props/prop'; export * from './props/prop-stash'; export * from './props/props'; +export * from './export-type'; diff --git a/packages/designer/src/document/node/node-children.ts b/packages/designer/src/document/node/node-children.ts index c17702dcc..40d448420 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -1,9 +1,10 @@ import { NodeData, isNodeSchema, obx, computed } from '@ali/lowcode-globals'; -import { Node, NodeParent } from './node'; +import { Node, ParentalNode } from './node'; +import { ExportType } from './export-type'; export class NodeChildren { @obx.val private children: Node[]; - constructor(readonly owner: NodeParent, data: NodeData | NodeData[]) { + constructor(readonly owner: ParentalNode, data: NodeData | NodeData[]) { this.children = (Array.isArray(data) ? data : [data]).map(child => { return this.owner.document.createNode(child); }); @@ -15,10 +16,16 @@ export class NodeChildren { /** * 导出 schema - * @param serialize 序列化,加 id 标识符,用于储存为操作记录 */ - export(serialize = false): NodeData[] { - return this.children.map(node => node.export(serialize)); + export(exportType: ExportType = ExportType.ForSave): NodeData[] { + return this.children.map(node => { + const data = node.export(exportType); + if (node.isLeaf() && ExportType.ForSave === exportType) { + // FIXME: filter empty + return data.children as NodeData; + } + return data; + }); } import(data?: NodeData | NodeData[], checkId = false) { diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index f3990e98d..ad02a56fe 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -8,6 +8,9 @@ import { TitleContent, obx, computed, + SlotSchema, + PageSchema, + ComponentSchema, } from '@ali/lowcode-globals'; import { Props, EXTRA_KEY_PREFIX } from './props/props'; import { DocumentModel } from '../document-model'; @@ -15,6 +18,7 @@ import { NodeChildren } from './node-children'; import { Prop } from './props/prop'; import { ComponentMeta } from '../../component-meta'; import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group'; +import { ExportType } from './export-type'; /** * 基础节点 @@ -35,8 +39,36 @@ import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group'; * locked can not select/hover/ item on canvas but can control on outline * hidden not visible on canvas * slotArgs like loopArgs, for slot node + * + * 根容器节点 + * + * [Node Properties] + * componentName: Page/Block/Component + * props + * children + * + * [Root Container Extra Properties] + * fileName + * meta + * state + * defaultProps + * dataSource + * lifeCycles + * methods + * css + * + * [Directives **not used**] + * loop + * loopArgs + * condition + * ------- future support ----- + * conditionGroup + * title + * ignore + * locked + * hidden */ -export class Node { +export class Node { /** * 是节点实例 */ @@ -63,11 +95,11 @@ export class Node { */ readonly props: Props; protected _children?: NodeChildren; - @obx.ref private _parent: NodeParent | null = null; + @obx.ref private _parent: ParentalNode | null = null; /** * 父级节点 */ - get parent(): NodeParent | null { + get parent(): ParentalNode | null { return this._parent; } /** @@ -100,48 +132,71 @@ export class Node { return this.componentMeta.title; } - get isSlotRoot(): boolean { - return this._slotFor != null; - } - - isRoot() { - return (this.document.rootNode as any) == this; - } - - constructor(readonly document: DocumentModel, nodeSchema: NodeSchema, slotFor?: Prop) { + constructor(readonly document: DocumentModel, nodeSchema: Schema) { const { componentName, id, children, props, ...extras } = nodeSchema; this.id = id || `node$${document.nextId()}`; this.componentName = componentName; - this._slotFor = slotFor; let _props: Props; - if (isNodeParent(this)) { - _props = new Props(this, props, extras); - this._children = new NodeChildren(this as NodeParent, children || []); - this._children.interalInitParent(); - } else { + if (this.componentName === 'Leaf') { _props = new Props(this, { children: isDOMText(children) || isJSExpression(children) ? children : '', }); + } else { + _props = new Props(this, this.buildProps(props), extras); + this._children = new NodeChildren(this as ParentalNode, children || []); + this._children.interalInitParent(); } this.props = _props; } + private buildProps(props: any): any { + // TODO: run componentMeta(initials|initialValue|accessor) + return props; + } + + isContainer(): boolean { + return this.isParental() && this.componentMeta.isContainer; + } + + isRoot(): this is RootNode { + return this.document.rootNode == this as any; + } + + isPage(): this is PageNode { + return this.isRoot() && this.componentName === 'Page'; + } + + isComponent(): this is ComponentNode { + return this.isRoot() && this.componentName === 'Component'; + } + + isSlot(): this is SlotNode { + return this._slotFor != null && this.componentName === 'Slot'; + } + /** * 是否一个父亲类节点 */ - get isNodeParent(): boolean { - return this.componentName !== 'Leaf'; + isParental(): this is ParentalNode { + return !this.isLeaf(); + } + + /** + * 终端节点,内容一般为 文字 或者 表达式 + */ + isLeaf(): this is LeafNode { + return this.componentName === 'Leaf'; } /** * 内部方法,请勿使用 */ - internalSetParent(parent: NodeParent | null) { + internalSetParent(parent: ParentalNode | null) { if (this._parent === parent) { return; } - if (this._parent && !this.isSlotRoot) { + if (!this.isSlot() && this._parent) { this._parent.children.delete(this); } @@ -160,6 +215,9 @@ export class Node { this._slotFor = slotFor; } + /** + * 关联属性 + */ get slotFor() { return this._slotFor; } @@ -168,7 +226,7 @@ export class Node { * 移除当前节点 */ remove() { - if (this.parent && !this.isSlotRoot) { + if (!this.isSlot() && this.parent) { this.parent.children.delete(this, true); } } @@ -199,17 +257,13 @@ export class Node { } @computed get propsData(): PropsMap | PropsList | null { - if (!this.isNodeParent || this.componentName === 'Fragment') { + if (!this.isParental() || this.componentName === 'Fragment') { return null; } - return this.props.export(true).props || null; + return this.props.export(ExportType.ForSerilize).props || null; } - isContainer() { - return this.isNodeParent && this.componentMeta.isContainer; - } - - @computed isSlotContainer() { + @computed hasSlots() { for (const item of this.props) { if (item.type === 'slot') { return true; @@ -280,11 +334,11 @@ export class Node { return v != null && v !== ''; } - wrapWith(schema: NodeSchema) { + wrapWith(schema: Schema) { // todo } - replaceWith(schema: NodeSchema, migrate = true) { + replaceWith(schema: Schema, migrate = true) { // reuse the same id? or replaceSelection // } @@ -366,22 +420,18 @@ export class Node { /** * 获取符合搭建协议-节点 schema 结构 */ - get schema(): NodeSchema { - // FIXME! serilize? - // for design - pass to Renderer - // for save production data - // for serilize mutation record - return this.export(true); + get schema(): Schema { + return this.export(ExportType.ForSave); } - set schema(data: NodeSchema) { + set schema(data: Schema) { this.import(data); } - import(data: NodeSchema, checkId = false) { + import(data: Schema, checkId = false) { const { componentName, id, children, props, ...extras } = data; - if (isNodeParent(this)) { + if (this.isParental()) { this.props.import(props, extras); (this._children as NodeChildren).import(children, checkId); } else { @@ -391,32 +441,32 @@ export class Node { /** * 导出 schema - * @param serialize 序列化,加 id 标识符,用于储存为操作记录 */ - export(serialize = false): NodeSchema { + export(exportType: ExportType = ExportType.ForSave): Schema { + // run transducers + // run const baseSchema: any = { - componentName: this.componentName === 'Leaf' ? 'Fragment' : this.componentName, + componentName: this.componentName, }; - if (serialize) { + if (exportType !== ExportType.ForSave) { baseSchema.id = this.id; } - if (!isNodeParent(this)) { - baseSchema.children = this.props.get('children')?.export(serialize); - // FIXME! - return baseSchema.children; + if (this.isLeaf()) { + baseSchema.children = this.props.get('children')?.export(exportType); + return baseSchema; } - const { props = {}, extras } = this.props.export(serialize) || {}; + const { props = {}, extras } = this.props.export(exportType) || {}; const schema: any = { ...baseSchema, props, ...extras, }; - if (this.children.size > 0) { - schema.children = this.children.export(serialize); + if (this.isParental() && this.children.size > 0) { + schema.children = this.children.export(exportType); } return schema; @@ -468,7 +518,7 @@ export class Node { return; } this.purged = true; - if (isNodeParent(this)) { + if (this.isParental()) { this.children.purge(); } this.props.purge(); @@ -510,7 +560,7 @@ export class Node { /** * @deprecated */ - getDOMNode() { + getDOMNode(): any { const instance = this.document.simulator?.getComponentInstances(this)?.[0]; if (!instance) { return; @@ -535,17 +585,24 @@ export class Node { } } -export interface NodeParent extends Node { +export interface ParentalNode extends Node { readonly children: NodeChildren; - readonly props: Props; } +export interface LeafNode extends Node { + readonly children: null; +} + +export interface SlotNode extends ParentalNode {} +export interface PageNode extends ParentalNode {} +export interface ComponentNode extends ParentalNode {} +export type RootNode = PageNode | ComponentNode; export function isNode(node: any): node is Node { return node && node.isNode; } -export function isNodeParent(node: Node): node is NodeParent { - return node.isNodeParent; +export function isRootNode(node: Node): node is RootNode { + return node && node.isRoot(); } export function getZLevelTop(child: Node, zLevel: number): Node | null { @@ -568,7 +625,7 @@ export function contains(node1: Node, node2: Node): boolean { return true; } - if (!node1.isNodeParent || !node2.parent) { + if (!node1.isParental || !node2.parent) { return false; } @@ -617,10 +674,10 @@ export function comparePosition(node1: Node, node2: Node): PositionNO { return PositionNO.BeforeOrAfter; } -export function insertChild(container: NodeParent, thing: Node | NodeData, at?: number | null, copy?: boolean): Node { +export function insertChild(container: ParentalNode, thing: Node | NodeData, at?: number | null, copy?: boolean): Node { let node: Node; - if (isNode(thing) && (copy || thing.isSlotRoot)) { - thing = thing.export(false); + if (isNode(thing) && (copy || thing.isSlot())) { + thing = thing.export(ExportType.ForSave); } if (isNode(thing)) { node = thing; @@ -634,7 +691,7 @@ export function insertChild(container: NodeParent, thing: Node | NodeData, at?: } export function insertChildren( - container: NodeParent, + container: ParentalNode, nodes: Node[] | NodeData[], at?: number | null, copy?: boolean, diff --git a/packages/designer/src/document/node/props/prop.ts b/packages/designer/src/document/node/props/prop.ts index 196e66ef9..4ec68c44c 100644 --- a/packages/designer/src/document/node/props/prop.ts +++ b/packages/designer/src/document/node/props/prop.ts @@ -2,11 +2,11 @@ import { CompositeValue, isJSExpression, isJSSlot, - NodeData, - isNodeSchema, untracked, computed, - obx + obx, + JSSlot, + SlotSchema } from '@ali/lowcode-globals'; import { uniqueId } from '@ali/lowcode-globals'; import { isPlainObject } from '@ali/lowcode-globals'; @@ -14,7 +14,8 @@ import { hasOwnProperty } from '@ali/lowcode-globals'; import { PropStash } from './prop-stash'; import { valueToSource } from './value-to-source'; import { Props } from './props'; -import { Node } from '../node'; +import { SlotNode } from '../node'; +import { ExportType } from '../export-type'; export const UNSET = Symbol.for('unset'); export type UNSET = typeof UNSET; @@ -45,10 +46,10 @@ export class Prop implements IPropParent { * 属性值 */ @computed get value(): CompositeValue | UNSET { - return this.export(true); + return this.export(ExportType.ForSerilize); } - export(serialize = false): CompositeValue | UNSET { + export(exporType: ExportType = ExportType.ForSave): CompositeValue | UNSET { const type = this._type; if (type === 'unset') { @@ -60,9 +61,18 @@ export class Prop implements IPropParent { } if (type === 'slot') { + const schema = this._slotNode!.export(exporType); + if (exporType === ExportType.ForSave) { + return { + type: 'JSSlot', + params: schema.params, + value: schema.children, + }; + } return { type: 'JSSlot', - value: this._slotNode!.export(serialize), + params: schema.params, + value: schema, }; } @@ -72,7 +82,7 @@ export class Prop implements IPropParent { } const maps: any = {}; this.items!.forEach((prop, key) => { - const v = prop.export(serialize); + const v = prop.export(exporType); if (v !== UNSET) { maps[key] = v; } @@ -85,7 +95,7 @@ export class Prop implements IPropParent { return this._value; } return this.items!.map((prop) => { - const v = prop.export(serialize); + const v = prop.export(exporType); return v === UNSET ? null : v; }); } @@ -103,7 +113,7 @@ export class Prop implements IPropParent { } // todo: JSFunction ... if (this.type === 'slot') { - return JSON.stringify(this._slotNode!.export(false)); + return JSON.stringify(this._slotNode!.export(ExportType.ForSave)); } return this._code != null ? this._code : JSON.stringify(this.value); } @@ -161,7 +171,7 @@ export class Prop implements IPropParent { this._type = 'list'; } else if (isPlainObject(val)) { if (isJSSlot(val)) { - this.setAsSlot(val.value); + this.setAsSlot(val); return; } if (isJSExpression(val)) { @@ -181,7 +191,7 @@ export class Prop implements IPropParent { } @computed getValue(): CompositeValue { - const v = this.export(true); + const v = this.export(ExportType.ForSerilize); if (v === UNSET) { return null; } @@ -204,24 +214,25 @@ export class Prop implements IPropParent { } } - private _slotNode?: Node; + private _slotNode?: SlotNode; get slotNode() { return this._slotNode; } - setAsSlot(data: NodeData) { + setAsSlot(data: JSSlot) { this._type = 'slot'; - if ( - this._slotNode && - isNodeSchema(data) && - (!data.id || this._slotNode.id === data.id) && - this._slotNode.componentName === data.componentName - ) { - this._slotNode.import(data); + const slotSchema: SlotSchema = { + componentName: 'Slot', + title: data.title, + params: data.params, + children: data.value, + }; + if (this._slotNode) { + this._slotNode.import(slotSchema); } else { - this._slotNode?.internalSetParent(null); const owner = this.props.owner; - this._slotNode = owner.document.createNode(data, this); + this._slotNode = owner.document.createNode(slotSchema); this._slotNode.internalSetParent(owner as any); + this._slotNode.internalSetSlotFor(this); } this.dispose(); } diff --git a/packages/designer/src/document/node/props/props.ts b/packages/designer/src/document/node/props/props.ts index 5a97a1e18..57200134b 100644 --- a/packages/designer/src/document/node/props/props.ts +++ b/packages/designer/src/document/node/props/props.ts @@ -2,6 +2,7 @@ import { PropsMap, PropsList, CompositeValue, computed, obx, uniqueId } from '@a import { PropStash } from './prop-stash'; import { Prop, IPropParent, UNSET } from './prop'; import { Node } from '../node'; +import { ExportType } from '../export-type'; export const EXTRA_KEY_PREFIX = '__'; @@ -79,7 +80,7 @@ export class Props implements IPropParent { }); } - export(serialize = false): { props?: PropsMap | PropsList; extras?: object } { + export(exportType: ExportType = ExportType.ForSave): { props?: PropsMap | PropsList; extras?: object } { if (this.items.length < 1) { return {}; } @@ -88,7 +89,7 @@ export class Props implements IPropParent { if (this.type === 'list') { props = []; this.items.forEach(item => { - let value = item.export(serialize); + let value = item.export(exportType); if (value === UNSET) { value = null; } @@ -111,7 +112,7 @@ export class Props implements IPropParent { // todo ...spread return; } - let value = item.export(serialize); + let value = item.export(exportType); if (value === UNSET) { value = null; } diff --git a/packages/designer/src/document/node/root-node.ts b/packages/designer/src/document/node/root-node.ts deleted file mode 100644 index d60e6c029..000000000 --- a/packages/designer/src/document/node/root-node.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { RootSchema } from '@ali/lowcode-globals'; -import { Node, NodeParent } from './node'; -import { DocumentModel } from '../document-model'; -import { NodeChildren } from './node-children'; - -/** - * 根容器节点 - * - * [Node Properties] - * componentName: Page/Block/Component - * props - * children - * - * [Root Container Extra Properties] - * fileName - * meta - * state - * defaultProps - * dataSource - * lifeCycles - * methods - * css - * - * [Directives **not used**] - * loop - * loopArgs - * condition - * ------- future support ----- - * conditionGroup - * title - * ignore - * locked - * hidden - */ -export class RootNode extends Node implements NodeParent { - readonly isRootNode = true; - get isNodeParent() { - return true; - } - get index() { - return 0; - } - get nextSibling() { - return null; - } - get prevSibling() { - return null; - } - get zLevel() { - return 0; - } - get parent() { - return null; - } - get children(): NodeChildren { - return this._children as NodeChildren; - } - internalSetParent(parent: null) { - // empty - } - - constructor(readonly document: DocumentModel, rootSchema: RootSchema) { - super(document, rootSchema); - } - - isPage() { - return this.componentName === 'Page'; - } - - isComponent() { - return this.componentName === 'Component'; - } - - isBlock() { - return this.componentName === 'Block'; - } -} - -export function isRootNode(node: any): node is RootNode { - return node && node.isRootNode; -} diff --git a/packages/globals/src/types/schema.ts b/packages/globals/src/types/schema.ts index 74f8d9e74..1a8c3f091 100644 --- a/packages/globals/src/types/schema.ts +++ b/packages/globals/src/types/schema.ts @@ -13,6 +13,13 @@ export interface NodeSchema { loop?: CompositeValue; loopArgs?: [string, string]; children?: NodeData | NodeData[]; + + // ------- future support ----- + conditionGroup?: string; + title?: string; + ignore?: boolean; + locked?: boolean; + hidden?: boolean; } export type PropsMap = CompositeObject; @@ -30,7 +37,7 @@ export function isDOMText(data: any): data is DOMText { export type DOMText = string; -export interface RootSchema extends NodeSchema { +export interface ContainerSchema extends NodeSchema { componentName: string; // 'Block' | 'Page' | 'Component'; fileName: string; meta?: object; @@ -48,18 +55,24 @@ export interface RootSchema extends NodeSchema { defaultProps?: CompositeObject; } -export interface BlockSchema extends RootSchema { - componentName: 'Block'; -} - -export interface PageSchema extends RootSchema { +export interface PageSchema extends ContainerSchema { componentName: 'Page'; } -export interface ComponentSchema extends RootSchema { +export interface ComponentSchema extends ContainerSchema { componentName: 'Component'; } +export type RootSchema = PageSchema | ComponentSchema; + +export interface BlockSchema extends NodeSchema { + componentName: 'Block'; +} +export interface SlotSchema extends NodeSchema { + componentName: 'Slot'; + params?: string[]; +} + export interface ProjectSchema { version: string; componentsMap: ComponentsMap; diff --git a/packages/globals/src/types/value-type.ts b/packages/globals/src/types/value-type.ts index f12f52266..145722eff 100644 --- a/packages/globals/src/types/value-type.ts +++ b/packages/globals/src/types/value-type.ts @@ -1,4 +1,4 @@ -import { NodeSchema } from './schema'; +import { NodeSchema, NodeData } from './schema'; // 表达式 export interface JSExpression { @@ -15,9 +15,10 @@ export interface JSExpression { export interface JSSlot { type: 'JSSlot'; + title?: string; // 函数的入参 params?: string[]; - value: NodeSchema[]; + value?: NodeData[] | NodeData; } // JSON 基本类型 diff --git a/packages/plugin-outline-pane/src/helper/dwell-timer.ts b/packages/plugin-outline-pane/src/helper/dwell-timer.ts index 64e5e0965..cd29f4c6b 100644 --- a/packages/plugin-outline-pane/src/helper/dwell-timer.ts +++ b/packages/plugin-outline-pane/src/helper/dwell-timer.ts @@ -1,16 +1,16 @@ -import { NodeParent, DropLocation, isLocationChildrenDetail, LocateEvent } from '@ali/lowcode-designer'; +import { ParentalNode, DropLocation, isLocationChildrenDetail, LocateEvent } from '@ali/lowcode-designer'; /** * 停留检查计时器 */ export default class DwellTimer { private timer: number | undefined; - private previous?: NodeParent; + private previous?: ParentalNode; private event?: LocateEvent; - constructor(private decide: (node: NodeParent, event: LocateEvent) => void, private timeout: number = 500) {} + constructor(private decide: (node: ParentalNode, event: LocateEvent) => void, private timeout: number = 500) {} - focus(node: NodeParent, event: LocateEvent) { + focus(node: ParentalNode, event: LocateEvent) { this.event = event; if (this.previous === node) { return; diff --git a/packages/plugin-outline-pane/src/helper/indent-track.ts b/packages/plugin-outline-pane/src/helper/indent-track.ts index f8d0eb3ad..373b59931 100644 --- a/packages/plugin-outline-pane/src/helper/indent-track.ts +++ b/packages/plugin-outline-pane/src/helper/indent-track.ts @@ -1,4 +1,4 @@ -import { DropLocation, NodeParent, isLocationChildrenDetail } from '@ali/lowcode-designer'; +import { DropLocation, ParentalNode, isLocationChildrenDetail } from '@ali/lowcode-designer'; const IndentSensitive = 15; export class IndentTrack { @@ -6,7 +6,7 @@ export class IndentTrack { reset() { this.indentStart = null; } - getIndentParent(lastLoc: DropLocation, loc: DropLocation): [NodeParent, number] | null { + getIndentParent(lastLoc: DropLocation, loc: DropLocation): [ParentalNode, number] | null { if ( lastLoc.target !== loc.target || !isLocationChildrenDetail(lastLoc.detail) || @@ -33,7 +33,7 @@ export class IndentTrack { const index = loc.detail.index; if (direction === 'left') { - if (!parent.parent || parent.isSlotRoot || index < parent.children.size) { + if (parent.isSlot() || !parent.parent || index < parent.children.size) { return null; } return [parent.parent, parent.index + 1]; diff --git a/packages/plugin-outline-pane/src/main.ts b/packages/plugin-outline-pane/src/main.ts index d603acff7..c3c94ce0f 100644 --- a/packages/plugin-outline-pane/src/main.ts +++ b/packages/plugin-outline-pane/src/main.ts @@ -13,7 +13,7 @@ import { isLocationChildrenDetail, LocationChildrenDetail, LocationDetailType, - NodeParent, + ParentalNode, contains, Node, } from '@ali/lowcode-designer'; @@ -199,7 +199,7 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable { let index: any; let focus: any; let valid = true; - if (target.isSlotContainer()) { + if (target.hasSlots()) { index = null; focus = { type: 'slots' }; } else { @@ -301,7 +301,7 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable { if (focusSlots) { this.dwell.reset(); return designer.createLocation({ - target: node as NodeParent, + target: node as ParentalNode, source: this.id, event: e, detail: { @@ -342,9 +342,9 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable { index = node.index; } - if (node.isSlotRoot) { + if (node.isSlot()) { // 是个插槽根节点 - if (!treeNode.isContainer() && !treeNode.isSlotContainer()) { + if (!treeNode.isContainer() && !treeNode.hasSlots()) { return designer.createLocation({ target: node.parent!, source: this.id, @@ -377,7 +377,7 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable { let focusNode: Node | undefined; // focus - if (!expanded && (treeNode.isContainer() || treeNode.isSlotContainer())) { + if (!expanded && (treeNode.isContainer() || treeNode.hasSlots())) { focusNode = node; } @@ -439,7 +439,7 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable { return null; } - const container = treeNode.node as NodeParent; + const container = treeNode.node as ParentalNode; const detail: LocationChildrenDetail = { type: LocationDetailType.Children, }; @@ -449,10 +449,10 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable { source: this.id, event: e, }; - const isSlotContainer = treeNode.isSlotContainer(); + const isSlotContainer = treeNode.hasSlots(); const isContainer = treeNode.isContainer(); - if (container.isSlotRoot && !treeNode.expanded) { + if (container.isSlot() && !treeNode.expanded) { // 未展开,直接定位到内部第一个节点 if (isSlotContainer) { detail.index = null; @@ -693,7 +693,7 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable { } } -function checkRecursion(parent: Node | undefined | null, dragObject: DragObject): parent is NodeParent { +function checkRecursion(parent: Node | undefined | null, dragObject: DragObject): parent is ParentalNode { if (!parent) { return false; } diff --git a/packages/plugin-outline-pane/src/tree-node.ts b/packages/plugin-outline-pane/src/tree-node.ts index e3636f73f..c7d5f24d5 100644 --- a/packages/plugin-outline-pane/src/tree-node.ts +++ b/packages/plugin-outline-pane/src/tree-node.ts @@ -11,7 +11,7 @@ export default class TreeNode { * 是否可以展开 */ @computed get expandable(): boolean { - return this.hasChildren() || this.isSlotContainer() || this.dropDetail?.index != null; + return this.hasChildren() || this.hasSlots() || this.dropDetail?.index != null; } /** @@ -169,51 +169,17 @@ export default class TreeNode { /** * 判断是否有"插槽" */ - isSlotContainer(): boolean { - return this.node.isSlotContainer(); + hasSlots(): boolean { + return this.node.hasSlots(); } hasChildren(): boolean { return this.isContainer() && this.node.children?.notEmpty() ? true : false; } - /* - get xForValue() { - const node = this.node; - return isElementNode(node) && node.xforValue ? node.xforValue : null; - } - - get flowHidden() { - return (this.node as ElementNode).flowHidden; - } - - get flowIndex() { - return (this.node as ElementNode).flowIndex; - } - - get conditionFlow() { - return (this.node as ElementNode).conditionFlow; - } - - hasXIf() { - return hasConditionFlow(this.node); - } - - hasXFor() { - const node = this.node; - return isElementNode(node) && node.xforFn; - } - */ - select(isMulti: boolean) { const node = this.node; - /* - if (this.hasXIf()) { - (node as ElementNode).setFlowVisible(); - } - */ - const selection = node.document.selection; if (isMulti) { selection.add(node.id); diff --git a/packages/plugin-outline-pane/src/views/tree-branches.tsx b/packages/plugin-outline-pane/src/views/tree-branches.tsx index 15feda9dd..6193652d0 100644 --- a/packages/plugin-outline-pane/src/views/tree-branches.tsx +++ b/packages/plugin-outline-pane/src/views/tree-branches.tsx @@ -108,7 +108,7 @@ class TreeNodeSlots extends Component<{ } render() { const { treeNode } = this.props; - if (!treeNode.isSlotContainer()) { + if (!treeNode.hasSlots()) { return null; } return ( diff --git a/packages/plugin-outline-pane/src/views/tree-title.tsx b/packages/plugin-outline-pane/src/views/tree-title.tsx index 1669ddd71..516314f3c 100644 --- a/packages/plugin-outline-pane/src/views/tree-title.tsx +++ b/packages/plugin-outline-pane/src/views/tree-title.tsx @@ -66,7 +66,7 @@ export default class TreeTitle extends Component<{ const { editing } = this.state; const isCNode = !treeNode.isRoot(); const { node } = treeNode; - const isNodeParent = node.isNodeParent; + const isNodeParent = node.isParental(); let style: any; if (isCNode) { const depth = treeNode.depth; diff --git a/packages/plugin-settings-pane/src/setters/mixed-setter/style.less b/packages/plugin-settings-pane/src/setters/mixed-setter/style.less index b64e6ef86..6efebb28c 100644 --- a/packages/plugin-settings-pane/src/setters/mixed-setter/style.less +++ b/packages/plugin-settings-pane/src/setters/mixed-setter/style.less @@ -17,7 +17,7 @@ } } } - >.next-input { + .next-input,.next-date-picker { width: 100%; } &.lc-block-setter { diff --git a/packages/react-renderer/src/engine/base.jsx b/packages/react-renderer/src/engine/base.jsx index 60787073d..6bf45fe3f 100644 --- a/packages/react-renderer/src/engine/base.jsx +++ b/packages/react-renderer/src/engine/base.jsx @@ -386,6 +386,7 @@ export default class BaseEngine extends PureComponent { __parseProps = (props, self, path, info) => { const { schema, Comp, componentInfo = {} } = info; const propInfo = getValue(componentInfo.props, path); + // FIXME! 将这行逻辑外置,解耦,线上环境不要验证参数,调试环境可以有,通过传参自定义 const propType = propInfo && propInfo.extra && propInfo.extra.propType; const ignoreParse = schema.__ignoreParse || []; const checkProps = (value) => { diff --git a/packages/react-simulator-renderer/src/builtin-components.ts b/packages/react-simulator-renderer/src/builtin-components/builtin-components.ts similarity index 100% rename from packages/react-simulator-renderer/src/builtin-components.ts rename to packages/react-simulator-renderer/src/builtin-components/builtin-components.ts diff --git a/packages/react-simulator-renderer/src/builtin-components/leaf.tsx b/packages/react-simulator-renderer/src/builtin-components/leaf.tsx new file mode 100644 index 000000000..a199ddfe4 --- /dev/null +++ b/packages/react-simulator-renderer/src/builtin-components/leaf.tsx @@ -0,0 +1,23 @@ +import { Component } from 'react'; + +class Leaf extends Component { + static displayName = 'Leaf'; + static componentMetadata = { + componentName: 'Leaf', + configure: { + props: [{ + name: 'children', + setter: 'StringSetter', + }], + // events/className/style/general/directives + supports: false, + } + }; + + render() { + const { children } = this.props; + return children; + } +} + +export default Leaf; diff --git a/packages/react-simulator-renderer/src/builtin-components/slot.tsx b/packages/react-simulator-renderer/src/builtin-components/slot.tsx new file mode 100644 index 000000000..107864d52 --- /dev/null +++ b/packages/react-simulator-renderer/src/builtin-components/slot.tsx @@ -0,0 +1,53 @@ +import { Component } from 'react'; + +class Slot extends Component { + static displayName = 'Slot'; + static componentMetadata = { + componentName: 'Slot', + configure: { + props: [{ + name: '___title___', + title: { + type: 'i18n', + 'en-US': 'Slot Title', + 'zh-CN': '插槽标题' + }, + setter: 'StringSetter', + defaultValue: '插槽容器' + }, { + name: '___params___', + title: { + type: 'i18n', + 'en-US': 'Slot Params', + 'zh-CN': '插槽入参' + }, + setter: { + componentName: 'ArraySetter', + props: { + itemSetter: { + componentName: 'StringSetter', + props: { + placeholder: { + type: 'i18n', + 'zh-CN': '参数名称', + 'en-US': 'Argument Name' + } + } + } + } + } + }], + // events/className/style/general/directives + supports: false, + } + }; + + render() { + const { children } = this.props; + return ( +
{children}
+ ); + } +} + +export default Slot; diff --git a/packages/react-simulator-renderer/src/renderer-view.tsx b/packages/react-simulator-renderer/src/renderer-view.tsx index cf0617d28..230e3e302 100644 --- a/packages/react-simulator-renderer/src/renderer-view.tsx +++ b/packages/react-simulator-renderer/src/renderer-view.tsx @@ -41,6 +41,7 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> { } render() { const { renderer } = this.props; + console.info(renderer.schema) return ( }) { - const components: any = {}; + const components: any = { + ...builtinComponents + }; Object.keys(componentsMap).forEach((componentName) => { let component = componentsMap[componentName]; if (isReactComponent(component)) { diff --git a/packages/vision-polyfill/src/bundle/upgrade-metadata.ts b/packages/vision-polyfill/src/bundle/upgrade-metadata.ts index 0c4ac65cf..b9da6eb35 100644 --- a/packages/vision-polyfill/src/bundle/upgrade-metadata.ts +++ b/packages/vision-polyfill/src/bundle/upgrade-metadata.ts @@ -304,13 +304,24 @@ export function upgradePropConfig(config: OldPropConfig) { extraProps.defaultValue = initialValue; } - const initialFn = initial || initialValue; + let initialFn = initial || initialValue; + + if (accessor) { + extraProps.getValue = (field: Field, fieldValue: any) => { + return accessor.call(field, fieldValue); + }; + if (!initialFn) { + // FIXME! + initialFn + } + } extraProps.initialValue = (field: Field, defaultValue?: any) => { if (defaultValue === undefined) { defaultValue = extraProps.defaultValue; } if (typeof initialFn === 'function') { + // ? return initialFn(null, defaultValue); } @@ -325,11 +336,6 @@ export function upgradePropConfig(config: OldPropConfig) { } } } - if (accessor) { - extraProps.getValue = (field: Field, fieldValue: any) => { - return accessor.call(field, fieldValue); - }; - } if (mutator) { extraProps.setValue = (field: Field, value: any) => { mutator.call(field, value); @@ -535,9 +541,17 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) { experimental.context = context; } if (snippets) { - experimental.snippets = snippets; - } - if (defaultProps || initialChildren) { + experimental.snippets = snippets.map(data => { + const { schema = {} } = data; + if (initialChildren && !schema.children) { + schema.children = initialChildren; + } + return { + ...data, + schema, + }; + }); + } else if (defaultProps || initialChildren) { const snippet = { screenshot: icon, label: title, diff --git a/packages/vision-polyfill/src/bus.ts b/packages/vision-polyfill/src/bus.ts index 278a1b82e..c8554d99f 100644 --- a/packages/vision-polyfill/src/bus.ts +++ b/packages/vision-polyfill/src/bus.ts @@ -19,6 +19,7 @@ export class Bus { // alias to unsub off(event: string, func: (...args: any[]) => any) { this.unsub(event, func); + } // alias to pub diff --git a/packages/vision-polyfill/src/drag-engine.ts b/packages/vision-polyfill/src/drag-engine.ts index e905e86f4..6c3bf752b 100644 --- a/packages/vision-polyfill/src/drag-engine.ts +++ b/packages/vision-polyfill/src/drag-engine.ts @@ -1,5 +1,5 @@ import { designer } from './editor'; -import { DragObjectType, isNode } from '@ali/lowcode-designer'; +import { DragObjectType, isNode, ExportType } from '@ali/lowcode-designer'; const dragon = designer.dragon; const DragEngine = { @@ -12,14 +12,16 @@ const DragEngine = { if (isNode(r)) { return { type: DragObjectType.NodeData, - data: r.export(false), + data: r.export(ExportType.ForSave), }; + // FIXME! designer has bug /* return { type: DragObjectType.Node, nodes: [r], - };*/ + }; + */ } else { return { type: DragObjectType.NodeData,