From 3dba0f4796c7d46b3019dfb786bc9de54c09f78e Mon Sep 17 00:00:00 2001 From: liujuping Date: Wed, 6 Mar 2024 16:38:19 +0800 Subject: [PATCH] fix: remove console.log statements, update test, and purge children in NodeChildren class --- packages/designer/jest.config.js | 1 + .../src/designer/setting/setting-field.ts | 54 +----------------- .../src/designer/setting/setting-top-entry.ts | 56 ++++++++----------- .../src/document/node/node-children.ts | 5 ++ packages/designer/src/document/node/node.ts | 2 +- .../setting/setting-top-entry.test.ts | 23 +++++++- packages/editor-core/src/hotkey.ts | 2 - .../src/components/settings/main.ts | 20 ++++--- .../settings/settings-primary-pane.tsx | 19 +++---- 9 files changed, 77 insertions(+), 105 deletions(-) diff --git a/packages/designer/jest.config.js b/packages/designer/jest.config.js index 3684a48ac..0d0f7f8f8 100644 --- a/packages/designer/jest.config.js +++ b/packages/designer/jest.config.js @@ -22,6 +22,7 @@ const jestConfig = { // testMatch: ['**/selection.test.ts'], // testMatch: ['**/plugin/sequencify.test.ts'], // testMatch: ['**/builtin-simulator/utils/parse-metadata.test.ts'], + // testMatch: ['**/setting/setting-top-entry.test.ts'], transformIgnorePatterns: [ `/node_modules/(?!${esModules})/`, ], diff --git a/packages/designer/src/designer/setting/setting-field.ts b/packages/designer/src/designer/setting/setting-field.ts index 1a63fb7a4..39b0b1e30 100644 --- a/packages/designer/src/designer/setting/setting-field.ts +++ b/packages/designer/src/designer/setting/setting-field.ts @@ -1,4 +1,3 @@ -import { ReactNode } from 'react'; import { IPublicTypeTitleContent, IPublicTypeSetterType, @@ -7,18 +6,15 @@ import { IPublicTypeFieldConfig, IPublicTypeCustomView, IPublicTypeDisposable, - IPublicModelSettingField, - IBaseModelSettingField, } from '@alilc/lowcode-types'; import type { IPublicTypeSetValueOptions, } from '@alilc/lowcode-types'; import { Transducer } from './utils'; -import { ISettingPropEntry, SettingPropEntry } from './setting-prop-entry'; +import { SettingPropEntry } from './setting-prop-entry'; import { computed, obx, makeObservable, action, untracked, intl } from '@alilc/lowcode-editor-core'; import { cloneDeep, isCustomView, isDynamicSetter, isJSExpression } from '@alilc/lowcode-utils'; import { ISettingTopEntry } from './setting-top-entry'; -import { IComponentMeta, INode } from '@alilc/lowcode-designer'; function getSettingFieldCollectorKey(parent: ISettingTopEntry | ISettingField, config: IPublicTypeFieldConfig) { let cur = parent; @@ -32,53 +28,9 @@ function getSettingFieldCollectorKey(parent: ISettingTopEntry | ISettingField, c return path.join('.'); } -export interface ISettingField extends ISettingPropEntry, Omit, 'setValue' | 'key' | 'node'> { - readonly isSettingField: true; +export interface ISettingField extends SettingField {} - readonly isRequired: boolean; - - readonly isGroup: boolean; - - extraProps: IPublicTypeFieldExtraProps; - - get items(): Array; - - get title(): string | ReactNode | undefined; - - get setter(): IPublicTypeSetterType | null; - - get expanded(): boolean; - - get valueState(): number; - - setExpanded(value: boolean): void; - - purge(): void; - - setValue( - val: any, - isHotValue?: boolean, - force?: boolean, - extraOptions?: IPublicTypeSetValueOptions, - ): void; - - clearValue(): void; - - valueChange(options: IPublicTypeSetValueOptions): void; - - createField(config: IPublicTypeFieldConfig): ISettingField; - - onEffect(action: () => void): IPublicTypeDisposable; - - internalToShellField(): IPublicModelSettingField; -} - -export class SettingField extends SettingPropEntry implements ISettingField { +export class SettingField extends SettingPropEntry { readonly isSettingField = true; readonly isRequired: boolean; diff --git a/packages/designer/src/designer/setting/setting-top-entry.ts b/packages/designer/src/designer/setting/setting-top-entry.ts index 85be74b7f..850cd165e 100644 --- a/packages/designer/src/designer/setting/setting-top-entry.ts +++ b/packages/designer/src/designer/setting/setting-top-entry.ts @@ -1,6 +1,6 @@ import { IPublicTypeCustomView, IPublicModelEditor, IPublicModelSettingTopEntry, IPublicApiSetters } from '@alilc/lowcode-types'; import { isCustomView } from '@alilc/lowcode-utils'; -import { computed, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core'; +import { computed, IEventBus, createModuleEventBus, obx, makeObservable } from '@alilc/lowcode-editor-core'; import { ISettingEntry } from './setting-entry-type'; import { ISettingField, SettingField } from './setting-field'; import { INode } from '../../document'; @@ -14,33 +14,17 @@ function generateSessionId(nodes: INode[]) { .join(','); } -export interface ISettingTopEntry extends ISettingEntry, IPublicModelSettingTopEntry< +export interface ISettingTopEntry extends SettingTopEntry {} + +export class SettingTopEntry implements ISettingEntry, IPublicModelSettingTopEntry< INode, ISettingField > { - readonly top: ISettingTopEntry; - - readonly parent: ISettingTopEntry; - - readonly path: never[]; - - items: Array; - - componentMeta: IComponentMeta | null; - - purge(): void; - - getExtraPropValue(propName: string): void; - - setExtraPropValue(propName: string, value: any): void; -} - -export class SettingTopEntry implements ISettingTopEntry { private emitter: IEventBus = createModuleEventBus('SettingTopEntry'); - private _items: Array = []; + private _items: Array = []; - private _componentMeta: IComponentMeta | null = null; + private _componentMeta: IComponentMeta | null | undefined = null; private _isSame = true; @@ -75,7 +59,7 @@ export class SettingTopEntry implements ISettingTopEntry { } get isLocked(): boolean { - return this.first.isLocked; + return this.first?.isLocked ?? false; } /** @@ -87,7 +71,11 @@ export class SettingTopEntry implements ISettingTopEntry { readonly id: string; - readonly first: INode; + @computed get first(): INode | null { + return this._first; + } + + @obx.ref _first: INode | null; readonly designer: IDesigner | undefined; @@ -96,12 +84,14 @@ export class SettingTopEntry implements ISettingTopEntry { disposeFunctions: any[] = []; constructor(readonly editor: IPublicModelEditor, readonly nodes: INode[]) { + makeObservable(this); + if (!Array.isArray(nodes) || nodes.length < 1) { throw new ReferenceError('nodes should not be empty'); } this.id = generateSessionId(nodes); - this.first = nodes[0]; - this.designer = this.first.document?.designer; + this._first = nodes[0]; + this.designer = this._first.document?.designer; this.setters = editor.get('setters') as IPublicApiSetters; // setups @@ -116,7 +106,7 @@ export class SettingTopEntry implements ISettingTopEntry { private setupComponentMeta() { // todo: enhance compile a temp configure.compiled const { first } = this; - const meta = first.componentMeta; + const meta = first?.componentMeta; const l = this.nodes.length; let theSame = true; for (let i = 1; i < l; i++) { @@ -160,7 +150,7 @@ export class SettingTopEntry implements ISettingTopEntry { /** * 获取当前属性值 */ - @computed getValue(): any { + getValue(): any { return this.first?.propsData; } @@ -202,14 +192,14 @@ export class SettingTopEntry implements ISettingTopEntry { * 获取子级属性值 */ getPropValue(propName: string | number): any { - return this.first.getProp(propName.toString(), true)?.getValue(); + return this.first?.getProp(propName.toString(), true)?.getValue(); } /** * 获取顶层附属属性值 */ getExtraPropValue(propName: string) { - return this.first.getExtraProp(propName, false)?.getValue(); + return this.first?.getExtraProp(propName, false)?.getValue(); } /** @@ -244,8 +234,9 @@ export class SettingTopEntry implements ISettingTopEntry { this.disposeItems(); this._settingFieldMap = {}; this.emitter.removeAllListeners(); - this.disposeFunctions.forEach(f => f()); + this.disposeFunctions.forEach(f => f?.()); this.disposeFunctions = []; + this._first = null; } getProp(propName: string | number) { @@ -274,7 +265,7 @@ export class SettingTopEntry implements ISettingTopEntry { } getPage() { - return this.first.document; + return this.first?.document; } /** @@ -292,6 +283,7 @@ export class SettingTopEntry implements ISettingTopEntry { interface Purgeable { purge(): void; } + function isPurgeable(obj: any): obj is Purgeable { return obj && obj.purge; } diff --git a/packages/designer/src/document/node/node-children.ts b/packages/designer/src/document/node/node-children.ts index b7f03d9fe..d374daa8e 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -91,6 +91,7 @@ export class NodeChildren implements Omit, node.import(item); } else { node = this.owner.document?.createNode(item); + child?.purge(); } if (node) { @@ -98,6 +99,10 @@ export class NodeChildren implements Omit, } } + for (let i = data.length; i < originChildren.length; i++) { + originChildren[i].purge(); + } + this.children = children; this.internalInitParent(); if (!shallowEqual(children, originChildren)) { diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index 0b698831e..04c72a8fd 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -987,7 +987,7 @@ export class Node this.autoruns?.forEach((dispose) => dispose()); this.props.purge(); this.settingEntry?.purge(); - // this.document.destroyNode(this); + this.children?.purge(); } internalPurgeStart() { diff --git a/packages/designer/tests/designer/setting/setting-top-entry.test.ts b/packages/designer/tests/designer/setting/setting-top-entry.test.ts index 23a42c2af..e5234690c 100644 --- a/packages/designer/tests/designer/setting/setting-top-entry.test.ts +++ b/packages/designer/tests/designer/setting/setting-top-entry.test.ts @@ -1,8 +1,9 @@ import '../../fixtures/window'; -import { Editor, Setters } from '@alilc/lowcode-editor-core'; +import { Editor, Setters, reaction } from '@alilc/lowcode-editor-core'; import { Node } from '../../../src/document/node/node'; import { Designer } from '../../../src/designer/designer'; import settingSchema from '../../fixtures/schema/setting'; +import { SettingTopEntry } from '../../../src/designer/setting/setting-top-entry'; import divMeta from '../../fixtures/component-metadata/div'; import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory'; @@ -109,6 +110,26 @@ describe('setting-top-entry 测试', () => { expect(settingEntry.items).toHaveLength(0); }); + it('should notify when _first is set to null', (done) => { + // 创建一个简单的INode数组用于初始化SettingTopEntry实例 + const nodes = [{ id: '1', propsData: {} }, { id: '2', propsData: {} }]; + const entry = new SettingTopEntry(editor as any, nodes as any); + + // 使用MobX的reaction来观察_first属性的变化 + const dispose = reaction( + () => entry.first, + (first) => { + if (first === null) { + dispose(); // 清理reaction监听 + done(); // 结束测试 + } + } + ); + + // 执行purge方法,期望_first被设置为null,触发reaction回调 + entry.purge(); + }); + it('vision 兼容测试', () => { designer.createComponentMeta(divMeta); designer.project.open(settingSchema); diff --git a/packages/editor-core/src/hotkey.ts b/packages/editor-core/src/hotkey.ts index 496cec251..e3482ab4d 100644 --- a/packages/editor-core/src/hotkey.ts +++ b/packages/editor-core/src/hotkey.ts @@ -541,8 +541,6 @@ export class Hotkey implements Omit { } private handleKeyEvent(e: KeyboardEvent): void { - console.log(e); - // debugger; if (!this.isActivate) { return; } diff --git a/packages/editor-skeleton/src/components/settings/main.ts b/packages/editor-skeleton/src/components/settings/main.ts index 6cc672c90..3cca2c861 100644 --- a/packages/editor-skeleton/src/components/settings/main.ts +++ b/packages/editor-skeleton/src/components/settings/main.ts @@ -1,7 +1,7 @@ -import { Node, Designer, Selection, SettingTopEntry } from '@alilc/lowcode-designer'; +import { INode, IDesigner, Selection, SettingTopEntry } from '@alilc/lowcode-designer'; import { Editor, obx, computed, makeObservable, action, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core'; -function generateSessionId(nodes: Node[]) { +function generateSessionId(nodes: INode[]) { return nodes .map((node) => node.id) .sort() @@ -29,7 +29,11 @@ export class SettingsMain { private disposeListener: () => void; - private designer?: Designer; + private _designer?: IDesigner; + + get designer(): IDesigner | undefined { + return this._designer; + } constructor(readonly editor: Editor) { makeObservable(this); @@ -49,12 +53,12 @@ export class SettingsMain { this.editor.removeListener('designer.selection.change', setupSelection); }; const designer = await this.editor.onceGot('designer'); - this.designer = designer; + this._designer = designer; setupSelection(designer.currentSelection); } @action - private setup(nodes: Node[]) { + private setup(nodes: INode[]) { // check nodes change const sessionId = generateSessionId(nodes); if (sessionId === this._sessionId) { @@ -66,15 +70,15 @@ export class SettingsMain { return; } - if (!this.designer) { - this.designer = nodes[0].document.designer; + if (!this._designer) { + this._designer = nodes[0].document.designer; } // 当节点只有一个时,复用 node 上挂载的 settingEntry,不会产生平行的两个实例,这样在整个系统中对 // 某个节点操作的 SettingTopEntry 只有一个实例,后续的 getProp() 也会拿到相同的 SettingField 实例 if (nodes.length === 1) { this._settings = nodes[0].settingEntry; } else { - this._settings = this.designer.createSettingEntry(nodes); + this._settings = this._designer.createSettingEntry(nodes); } } diff --git a/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx b/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx index 747a2ea1d..b2eb8c7a8 100644 --- a/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx +++ b/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx @@ -1,14 +1,14 @@ import React, { Component } from 'react'; import { Tab, Breadcrumb } from '@alifd/next'; import { Title, observer, Editor, obx, globalContext, engineConfig, makeObservable } from '@alilc/lowcode-editor-core'; -import { Node, SettingField, isSettingField, INode } from '@alilc/lowcode-designer'; +import { ISettingField, INode } from '@alilc/lowcode-designer'; import classNames from 'classnames'; import { SettingsMain } from './main'; import { SettingsPane } from './settings-pane'; import { StageBox } from '../stage-box'; import { SkeletonContext } from '../../context'; import { intl } from '../../locale'; -import { createIcon } from '@alilc/lowcode-utils'; +import { createIcon, isSettingField } from '@alilc/lowcode-utils'; interface ISettingsPrimaryPaneProps { engineEditor: Editor; @@ -53,8 +53,7 @@ export class SettingsPrimaryPane extends Component { + const tabs = (items as ISettingField[]).map((field) => { if (this._activeKey === field.name) { matched = true; } @@ -235,7 +233,7 @@ export class SettingsPrimaryPane extends Component ); }); - const activeKey = matched ? this._activeKey : (items[0] as SettingField).name; + const activeKey = matched ? this._activeKey : (items[0] as ISettingField).name; const className = classNames('lc-settings-main', { 'lc-settings-hide-tabs': @@ -261,9 +259,10 @@ export class SettingsPrimaryPane extends Component