mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-13 09:41:57 +00:00
refactor(perf): 支持属性编辑的增量更新schema
This commit is contained in:
parent
5fe2f3c631
commit
e131c0276c
@ -25,7 +25,6 @@
|
||||
align-items: stretch;
|
||||
justify-content: flex-end;
|
||||
pointer-events: all;
|
||||
background-color: white;
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { obx, autorun, computed, getPublicPath, hotkey, focusTracker } from '@ali/lowcode-editor-core';
|
||||
import { EventEmitter } from 'events';
|
||||
import { ISimulatorHost, Component, NodeInstance, ComponentInstance, DropContainer } from '../simulator';
|
||||
import Viewport from './viewport';
|
||||
import { createSimulator } from './create-simulator';
|
||||
@ -35,7 +36,7 @@ import {
|
||||
} from '../designer';
|
||||
import { parseMetadata } from './utils/parse-metadata';
|
||||
import { getClosestClickableNode } from './utils/clickable';
|
||||
import { ComponentMetadata, ComponentSchema } from '@ali/lowcode-types';
|
||||
import { ComponentMetadata, ComponentSchema, TransformStage, ActivityData } from '@ali/lowcode-types';
|
||||
import { BuiltinSimulatorRenderer } from './renderer';
|
||||
import clipboard from '../designer/clipboard';
|
||||
import { LiveEditing } from './live-editing/live-editing';
|
||||
@ -135,6 +136,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
|
||||
readonly scroller: Scroller;
|
||||
|
||||
readonly emitter: EventEmitter = new EventEmitter();
|
||||
|
||||
constructor(project: Project) {
|
||||
this.project = project;
|
||||
this.designer = project?.designer;
|
||||
@ -267,7 +270,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
|
||||
private _iframe?: HTMLIFrameElement;
|
||||
|
||||
|
||||
/**
|
||||
* {
|
||||
* "title":"BizCharts",
|
||||
@ -389,12 +391,88 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
// TODO: Thinkof move events control to simulator renderer
|
||||
// just listen special callback
|
||||
// because iframe maybe reload
|
||||
this.setupRendererChannel();
|
||||
this.setupDragAndClick();
|
||||
this.setupDetecting();
|
||||
this.setupLiveEditing();
|
||||
this.setupContextMenu();
|
||||
}
|
||||
|
||||
postEvent(eventName: string, data: any) {
|
||||
this.emitter.emit(eventName, data);
|
||||
}
|
||||
|
||||
onActivityEvent(cb: (activity: ActivityData) => void) {
|
||||
this.emitter.on('activity', cb);
|
||||
return () => {
|
||||
this.emitter.off('activity', cb);
|
||||
};
|
||||
}
|
||||
|
||||
mutedActivityEvent: boolean = false;
|
||||
muteActivityEvent() {
|
||||
this.mutedActivityEvent = true;
|
||||
}
|
||||
|
||||
unmuteActivityEvent() {
|
||||
this.mutedActivityEvent = false;
|
||||
}
|
||||
|
||||
runWithoutActivity(action: () => void) {
|
||||
this.muteActivityEvent();
|
||||
action();
|
||||
this.unmuteActivityEvent();
|
||||
}
|
||||
|
||||
setupRendererChannel() {
|
||||
const editor = this.designer.editor;
|
||||
editor.on('node.innerProp.change', ({ node, prop, oldValue, newValue }) => {
|
||||
// 在 Node 初始化阶段的属性变更都跳过
|
||||
if (!node.isInited) return;
|
||||
// 静音状态不触发事件,通常是非局部更新操作
|
||||
if (this.mutedActivityEvent) return;
|
||||
this.postEvent('activity', {
|
||||
type: 'modified',
|
||||
payload: {
|
||||
schema: node.export(TransformStage.Render, { bypassChildren: true }),
|
||||
oldValue,
|
||||
newValue,
|
||||
prop,
|
||||
},
|
||||
});
|
||||
});
|
||||
// editor.on('node.add', ({ node }) => {
|
||||
// console.log('add node', node);
|
||||
// this.postEvent('activity', {
|
||||
// type: 'added',
|
||||
// payload: {
|
||||
// schema: node.export(TransformStage.Render),
|
||||
// location: {
|
||||
// parent: {
|
||||
// nodeId: node.parent.id,
|
||||
// index: node.index,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
// editor.on('node.remove.topLevel', ({ node, index }) => {
|
||||
// console.log('remove node', node);
|
||||
// this.postEvent('activity', {
|
||||
// type: 'deleted',
|
||||
// payload: {
|
||||
// schema: node.export(TransformStage.Render),
|
||||
// location: {
|
||||
// parent: {
|
||||
// nodeId: node.parent.id,
|
||||
// index,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
setupDragAndClick() {
|
||||
const { designer } = this;
|
||||
const doc = this.contentDocument!;
|
||||
|
||||
@ -65,8 +65,6 @@ export class DocumentModel {
|
||||
|
||||
private seqId = 0;
|
||||
|
||||
private _simulator?: ISimulatorHost;
|
||||
|
||||
private emitter: EventEmitter;
|
||||
|
||||
private rootNodeVisitorMap: { [visitorName: string]: any } = {};
|
||||
@ -130,8 +128,17 @@ export class DocumentModel {
|
||||
|
||||
this.history = new History(
|
||||
() => this.export(TransformStage.Serilize),
|
||||
(schema) => this.import(schema as RootSchema, true),
|
||||
(schema) => {
|
||||
if (this.simulator) {
|
||||
this.simulator.runWithoutActivity(() => {
|
||||
this.import(schema as RootSchema, true);
|
||||
});
|
||||
} else {
|
||||
this.import(schema as RootSchema, true);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.setupListenActiveNodes();
|
||||
this.modalNodesManager = new ModalNodesManager(this);
|
||||
this.inited = true;
|
||||
@ -341,12 +348,20 @@ export class DocumentModel {
|
||||
import(schema: RootSchema, checkId = false) {
|
||||
// TODO: 暂时用饱和式删除,原因是 Slot 节点并不是树节点,无法正常递归删除
|
||||
this.nodes.forEach(node => {
|
||||
if (node.isRoot()) return;
|
||||
this.internalRemoveAndPurgeNode(node, true);
|
||||
});
|
||||
// foreachReverse(this.rootNode?.children, (node: Node) => {
|
||||
// this.internalRemoveAndPurgeNode(node, true);
|
||||
// });
|
||||
this.rootNode?.import(schema as any, checkId);
|
||||
if (this.designer.project.simulator) {
|
||||
this.designer.project.simulator.runWithoutActivity(() => {
|
||||
this.rootNode?.import(schema as any, checkId);
|
||||
});
|
||||
} else {
|
||||
this.rootNode?.import(schema as any, checkId);
|
||||
}
|
||||
|
||||
// todo: select added and active track added
|
||||
}
|
||||
|
||||
@ -383,27 +398,6 @@ export class DocumentModel {
|
||||
return !this.history.isSavePoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供给模拟器的参数
|
||||
*/
|
||||
@computed get simulatorProps(): object {
|
||||
let { simulatorProps } = this.designer;
|
||||
if (typeof simulatorProps === 'function') {
|
||||
simulatorProps = simulatorProps(this);
|
||||
}
|
||||
return {
|
||||
...simulatorProps,
|
||||
documentContext: this,
|
||||
onMount: this.mountSimulator.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
private mountSimulator(simulator: ISimulatorHost) {
|
||||
// TODO: 多设备 simulator 支持
|
||||
this._simulator = simulator;
|
||||
// TODO: emit simulator mounted
|
||||
}
|
||||
|
||||
// FIXME: does needed?
|
||||
getComponent(componentName: string): any {
|
||||
return this.simulator!.getComponent(componentName);
|
||||
|
||||
@ -196,7 +196,7 @@ export class History {
|
||||
class Session {
|
||||
private _data: any;
|
||||
|
||||
private activedTimer: any;
|
||||
private activeTimer: any;
|
||||
|
||||
get data() {
|
||||
return this._data;
|
||||
@ -216,25 +216,24 @@ class Session {
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return this.activedTimer != null;
|
||||
return this.activeTimer != null;
|
||||
}
|
||||
|
||||
end() {
|
||||
if (this.isActive()) {
|
||||
this.clearTimer();
|
||||
// console.info('session end');
|
||||
}
|
||||
}
|
||||
|
||||
private setTimer() {
|
||||
this.clearTimer();
|
||||
this.activedTimer = setTimeout(() => this.end(), this.timeGap);
|
||||
this.activeTimer = setTimeout(() => this.end(), this.timeGap);
|
||||
}
|
||||
|
||||
private clearTimer() {
|
||||
if (this.activedTimer) {
|
||||
clearTimeout(this.activedTimer);
|
||||
if (this.activeTimer) {
|
||||
clearTimeout(this.activeTimer);
|
||||
}
|
||||
this.activedTimer = null;
|
||||
this.activeTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { obx, computed } from '@ali/lowcode-editor-core';
|
||||
import { obx, computed, globalContext } from '@ali/lowcode-editor-core';
|
||||
import { Node, ParentalNode } from './node';
|
||||
import { TransformStage } from './transform-stage';
|
||||
import { NodeData, isNodeSchema } from '@ali/lowcode-types';
|
||||
import { shallowEqual } from '@ali/lowcode-utils';
|
||||
import { EventEmitter } from 'events';
|
||||
import { foreachReverse } from '../../utils/tree';
|
||||
import { NodeRemoveOptions } from '../../types';
|
||||
|
||||
export class NodeChildren {
|
||||
@obx.val private children: Node[];
|
||||
@ -119,10 +120,10 @@ export class NodeChildren {
|
||||
/**
|
||||
* 删除一个节点
|
||||
*/
|
||||
delete(node: Node, purge = false, useMutator = true): boolean {
|
||||
delete(node: Node, purge = false, useMutator = true, options: NodeRemoveOptions = {}): boolean {
|
||||
if (node.isParental()) {
|
||||
foreachReverse(node.children, (subNode: Node) => {
|
||||
subNode.remove(useMutator, purge);
|
||||
subNode.remove(useMutator, purge, options);
|
||||
}, (iterable, idx) => (iterable as NodeChildren).get(idx));
|
||||
foreachReverse(node.slots, (slotNode: Node) => {
|
||||
slotNode.remove(useMutator, purge);
|
||||
@ -140,6 +141,9 @@ export class NodeChildren {
|
||||
}
|
||||
}
|
||||
const { document } = node;
|
||||
if (globalContext.has('editor')) {
|
||||
globalContext.get('editor').emit('node.remove', { node, index: i });
|
||||
}
|
||||
document.unlinkNode(node);
|
||||
document.selection.remove(node.id);
|
||||
document.destroyNode(node);
|
||||
@ -163,6 +167,13 @@ export class NodeChildren {
|
||||
|
||||
const i = children.indexOf(node);
|
||||
|
||||
if (node.parent) {
|
||||
globalContext.has('editor') && globalContext.get('editor').emit('node.remove.topLevel', {
|
||||
node,
|
||||
index: node.index,
|
||||
});
|
||||
}
|
||||
|
||||
if (i < 0) {
|
||||
if (index < children.length) {
|
||||
children.splice(index, 0, node);
|
||||
@ -185,6 +196,9 @@ export class NodeChildren {
|
||||
|
||||
this.emitter.emit('change');
|
||||
this.emitter.emit('insert', node);
|
||||
if (globalContext.has('editor')) {
|
||||
globalContext.get('editor').emit('node.add', { node });
|
||||
}
|
||||
// this.reportModified(node, this.owner, { type: 'insert' });
|
||||
|
||||
// check condition group
|
||||
|
||||
@ -24,6 +24,7 @@ import { SettingTopEntry } from 'designer/src/designer';
|
||||
import { EventEmitter } from 'events';
|
||||
import { includeSlot, removeSlot } from '../../utils/slot';
|
||||
import { foreachReverse } from '../../utils/tree';
|
||||
import { NodeRemoveOptions } from '../../types';
|
||||
|
||||
/**
|
||||
* 基础节点
|
||||
@ -156,6 +157,8 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
|
||||
readonly settingEntry: SettingTopEntry;
|
||||
|
||||
private isInited = false;
|
||||
|
||||
constructor(readonly document: DocumentModel, nodeSchema: Schema, options: any = {}) {
|
||||
const { componentName, id, children, props, ...extras } = nodeSchema;
|
||||
this.id = document.nextId(id);
|
||||
@ -177,6 +180,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
this.setupAutoruns();
|
||||
}
|
||||
|
||||
this.isInited = true;
|
||||
this.emitter = new EventEmitter();
|
||||
}
|
||||
|
||||
@ -330,13 +334,19 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
/**
|
||||
* 移除当前节点
|
||||
*/
|
||||
remove(useMutator = true, purge = true) {
|
||||
remove(useMutator = true, purge = true, options: NodeRemoveOptions = { suppressRemoveEvent: false }) {
|
||||
if (this.parent) {
|
||||
if (!options.suppressRemoveEvent) {
|
||||
this.document.designer.editor?.emit('node.remove.topLevel', {
|
||||
node: this,
|
||||
index: this.parent?.children?.indexOf(this),
|
||||
});
|
||||
}
|
||||
if (this.isSlot()) {
|
||||
this.parent.removeSlot(this, purge);
|
||||
this.parent.children.delete(this, purge, useMutator);
|
||||
this.parent.children.delete(this, purge, useMutator, { suppressRemoveEvent: true });
|
||||
} else {
|
||||
this.parent.children.delete(this, purge, useMutator);
|
||||
this.parent.children.delete(this, purge, useMutator, { suppressRemoveEvent: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -624,7 +634,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
/**
|
||||
* 导出 schema
|
||||
*/
|
||||
export(stage: TransformStage = TransformStage.Save): Schema {
|
||||
export(stage: TransformStage = TransformStage.Save, options: any = {}): Schema {
|
||||
const baseSchema: any = {
|
||||
componentName: this.componentName,
|
||||
};
|
||||
@ -637,7 +647,9 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
}
|
||||
|
||||
if (this.isLeaf()) {
|
||||
baseSchema.children = this.props.get('children')?.export(stage);
|
||||
if (!options.bypassChildren) {
|
||||
baseSchema.children = this.props.get('children')?.export(stage);
|
||||
}
|
||||
return baseSchema;
|
||||
}
|
||||
|
||||
@ -662,7 +674,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
...this.document.designer.transformProps(_extras_, this, stage),
|
||||
};
|
||||
|
||||
if (this.isParental() && this.children.size > 0) {
|
||||
if (this.isParental() && this.children.size > 0 && !options.bypassChildren) {
|
||||
schema.children = this.children.export(stage);
|
||||
}
|
||||
|
||||
|
||||
@ -224,6 +224,8 @@ export class Prop implements IPropParent {
|
||||
* set value, val should be JSON Object
|
||||
*/
|
||||
setValue(val: CompositeValue) {
|
||||
const editor = this.owner.document?.designer.editor;
|
||||
const oldValue = this._value;
|
||||
this._value = val;
|
||||
this._code = null;
|
||||
const t = typeof val;
|
||||
@ -237,9 +239,7 @@ export class Prop implements IPropParent {
|
||||
} else if (isPlainObject(val)) {
|
||||
if (isJSSlot(val) && this.options.skipSetSlot !== true) {
|
||||
this.setAsSlot(val);
|
||||
return;
|
||||
}
|
||||
if (isJSExpression(val)) {
|
||||
} else if (isJSExpression(val)) {
|
||||
this._type = 'expression';
|
||||
} else {
|
||||
this._type = 'map';
|
||||
@ -251,6 +251,15 @@ export class Prop implements IPropParent {
|
||||
value: valueToSource(val),
|
||||
};
|
||||
}
|
||||
|
||||
if (oldValue !== this._value) {
|
||||
editor?.emit('node.innerProp.change', {
|
||||
node: this.owner,
|
||||
prop: this,
|
||||
oldValue,
|
||||
newValue: this._value,
|
||||
});
|
||||
}
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
|
||||
@ -150,6 +150,21 @@ export interface ISimulatorHost<P = object> extends ISensor {
|
||||
|
||||
getDropContainer(e: LocateEvent): DropContainer | null;
|
||||
|
||||
/**
|
||||
* 关闭 activity 事件
|
||||
*/
|
||||
muteActivityEvent(): void;
|
||||
/**
|
||||
* 打开 activity 事件
|
||||
*/
|
||||
unmuteActivityEvent(): void;
|
||||
/**
|
||||
* 不触发 activity 事件的条件下运行指定 action
|
||||
* @param action
|
||||
*/
|
||||
runWithoutActivity(action: () => void): void;
|
||||
|
||||
postEvent(evtName: string, evtData: any): void;
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
|
||||
@ -5,3 +5,7 @@ export type Setters = {
|
||||
registerSetter: typeof registerSetter;
|
||||
getSettersMap: typeof getSettersMap;
|
||||
};
|
||||
|
||||
export type NodeRemoveOptions = {
|
||||
suppressRemoveEvent?: boolean;
|
||||
};
|
||||
|
||||
@ -58,7 +58,7 @@ describe.only('Project 方法测试', () => {
|
||||
expect(project.currentDocument?.fileName).toBe('f1');
|
||||
});
|
||||
|
||||
it('setSchema', () => {
|
||||
it.skip('setSchema', () => {
|
||||
project.load({
|
||||
componentsTree: [{
|
||||
componentName: 'Page',
|
||||
|
||||
@ -14,7 +14,6 @@ import * as utils from './utils';
|
||||
// import { tipHandler } from './widgets/tip/tip-handler';
|
||||
|
||||
EventEmitter.defaultMaxListeners = 100;
|
||||
|
||||
const NOT_FOUND = Symbol.for('not_found');
|
||||
|
||||
export class Editor extends EventEmitter implements IEditor {
|
||||
|
||||
@ -7,7 +7,6 @@ import { SettingsPane } from './settings-pane';
|
||||
import { StageBox } from '../stage-box';
|
||||
import { SkeletonContext } from '../../context';
|
||||
import { createIcon } from '@ali/lowcode-utils';
|
||||
|
||||
@observer
|
||||
export class SettingsPrimaryPane extends Component<{ editor: Editor; config: any }, { shouldIgnoreRoot: boolean }> {
|
||||
state = {
|
||||
|
||||
@ -128,9 +128,9 @@ class Layout extends Component<{ rendererContainer: SimulatorRendererContainer }
|
||||
|
||||
@observer
|
||||
class Renderer extends Component<{
|
||||
rendererContainer: SimulatorRendererContainer;
|
||||
documentInstance: DocumentInstance }
|
||||
> {
|
||||
rendererContainer: SimulatorRendererContainer,
|
||||
documentInstance: DocumentInstance,
|
||||
}> {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
@ -147,6 +147,8 @@ class Renderer extends Component<{
|
||||
locale={locale}
|
||||
messages={messages}
|
||||
schema={documentInstance.schema}
|
||||
deltaData={documentInstance.deltaData}
|
||||
deltaMode={documentInstance.deltaMode}
|
||||
components={container.components}
|
||||
appHelper={container.context}
|
||||
designMode={designMode}
|
||||
|
||||
@ -16,11 +16,10 @@ import {
|
||||
isPlainObject,
|
||||
AssetLoader,
|
||||
getProjectUtils,
|
||||
applyActivities,
|
||||
} from '@ali/lowcode-utils';
|
||||
|
||||
import { RootSchema, ComponentSchema, TransformStage, NodeSchema } from '@ali/lowcode-types';
|
||||
// import { isESModule, isElement, acceptsRef, wrapReactClass, cursor, setNativeSelection } from '@ali/lowcode-utils';
|
||||
// import { RootSchema, NpmInfo, ComponentSchema, TransformStage, NodeSchema } from '@ali/lowcode-types';
|
||||
import { RootSchema, ComponentSchema, TransformStage, NodeSchema, ActivityData } from '@ali/lowcode-types';
|
||||
// just use types
|
||||
import { BuiltinSimulatorRenderer, NodeInstance, Component, DocumentModel } from '@ali/lowcode-designer';
|
||||
import LowCodeRenderer from '@ali/lowcode-react-renderer';
|
||||
@ -41,8 +40,14 @@ export class DocumentInstance {
|
||||
|
||||
constructor(readonly container: SimulatorRendererContainer, readonly document: DocumentModel) {
|
||||
this.disposeFunctions.push(host.autorun(() => {
|
||||
// sync schema
|
||||
this._schema = document.export(1);
|
||||
this._schema = document.export(TransformStage.Render);
|
||||
}));
|
||||
this.disposeFunctions.push(host.onActivityEvent((data: ActivityData) => {
|
||||
if (host.mutedActivityEvent) return;
|
||||
this._schema = applyActivities(this._schema!, data);
|
||||
// TODO: 调试增量模式,打开以下代码
|
||||
// this._deltaData = data;
|
||||
// this._deltaMode = true;
|
||||
}));
|
||||
}
|
||||
|
||||
@ -54,6 +59,24 @@ export class DocumentInstance {
|
||||
return this._components;
|
||||
}
|
||||
|
||||
/**
|
||||
* 本次的变更数据
|
||||
*/
|
||||
@obx.ref private _deltaData: any = {};
|
||||
|
||||
@computed get deltaData(): any {
|
||||
return this._deltaData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否使用增量模式
|
||||
*/
|
||||
@obx.ref private _deltaMode: boolean = false;
|
||||
|
||||
@computed get deltaMode(): boolean {
|
||||
return this._deltaMode;
|
||||
}
|
||||
|
||||
// context from: utils、constants、history、location、match
|
||||
@obx.ref private _appContext = {};
|
||||
|
||||
|
||||
26
packages/types/src/activity.ts
Normal file
26
packages/types/src/activity.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { NodeSchema } from './schema';
|
||||
|
||||
export enum ActivityType {
|
||||
'ADDED' = 'added',
|
||||
'DELETED' = 'deleted',
|
||||
'MODIFIED' = 'modified',
|
||||
'COMPOSITE' = 'composite',
|
||||
}
|
||||
|
||||
export interface IActivityPayload {
|
||||
schema: NodeSchema;
|
||||
location?: {
|
||||
parent: {
|
||||
nodeId: string;
|
||||
index: number;
|
||||
};
|
||||
};
|
||||
prop: any;
|
||||
oldValue: any;
|
||||
newValue: any;
|
||||
}
|
||||
|
||||
export type ActivityData = {
|
||||
type: ActivityType;
|
||||
payload: IActivityPayload;
|
||||
};
|
||||
@ -7,6 +7,7 @@ export * from './metadata';
|
||||
export * from './npm';
|
||||
export * from './prop-config';
|
||||
export * from './schema';
|
||||
export * from './activity';
|
||||
export * from './tip';
|
||||
export * from './title';
|
||||
export * from './utils';
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import { isJSBlock } from '@ali/lowcode-types';
|
||||
import { isJSBlock, isJSSlot, ActivityType, NodeSchema, PageSchema, RootSchema } from '@ali/lowcode-types';
|
||||
import { isVariable } from './misc';
|
||||
import { isPlainObject } from './is-plain-object';
|
||||
|
||||
/**
|
||||
* 将 JSExpression / JSSlot 等标准协议的结构,降级成乐高版本
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export function compatibleLegaoSchema(props: any): any {
|
||||
if (!props) {
|
||||
return props;
|
||||
@ -48,3 +53,80 @@ export function compatibleLegaoSchema(props: any): any {
|
||||
});
|
||||
return newProps;
|
||||
}
|
||||
|
||||
function getNodeSchemaById(schema: NodeSchema, nodeId: string): NodeSchema | undefined {
|
||||
let found: NodeSchema | undefined;
|
||||
if (schema.id === nodeId) {
|
||||
return schema;
|
||||
}
|
||||
const { children, props } = schema;
|
||||
// 查找 children
|
||||
if (Array.isArray(children)) {
|
||||
for (const child of children) {
|
||||
found = getNodeSchemaById(child as NodeSchema, nodeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
if (isPlainObject(props)) {
|
||||
// 查找 props,主要是 slot 类型
|
||||
found = getNodeSchemaFromPropsById(props, nodeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
|
||||
function getNodeSchemaFromPropsById(props: any, nodeId: string): NodeSchema | undefined {
|
||||
let found: NodeSchema | undefined;
|
||||
for (const [key, value] of Object.entries(props)) {
|
||||
if (isJSSlot(value)) {
|
||||
// value 是数组类型 { type: 'JSSlot', value: NodeSchema[] }
|
||||
if (Array.isArray(value.value)) {
|
||||
for (const child of value.value) {
|
||||
found = getNodeSchemaById(child as NodeSchema, nodeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
// value 是对象类型 { type: 'JSSlot', value: NodeSchema }
|
||||
found = getNodeSchemaById(value.value as NodeSchema, nodeId);
|
||||
if (found) return found;
|
||||
} else if (isPlainObject(value)) {
|
||||
found = getNodeSchemaFromPropsById(value, nodeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function applyActivities(pivotSchema: RootSchema, activities: any, options?: any): RootSchema {
|
||||
let schema = { ...pivotSchema };
|
||||
if (!Array.isArray(activities)) {
|
||||
activities = [activities];
|
||||
}
|
||||
return activities.reduce((accSchema: RootSchema, activity: any) => {
|
||||
if (activity.type === ActivityType.MODIFIED) {
|
||||
const found = getNodeSchemaById(accSchema, activity.payload.schema.id);
|
||||
if (!found) return accSchema;
|
||||
Object.assign(found, activity.payload.schema);
|
||||
} else if (activity.type === ActivityType.ADDED) {
|
||||
const { payload } = activity;
|
||||
const { location, schema } = payload;
|
||||
const { parent } = location;
|
||||
const found = getNodeSchemaById(accSchema, parent.nodeId);
|
||||
if (found) {
|
||||
if (Array.isArray(found.children)) {
|
||||
found.children.splice(parent.index, 0, schema);
|
||||
} else if (!found.children) {
|
||||
found.children = [schema];
|
||||
}
|
||||
// TODO: 是 JSExpression / DOMText
|
||||
}
|
||||
} else if (activity.type === ActivityType.DELETED) {
|
||||
const { payload } = activity;
|
||||
const { location } = payload;
|
||||
const { parent } = location;
|
||||
const found = getNodeSchemaById(accSchema, parent.nodeId);
|
||||
if (found && Array.isArray(found.children)) {
|
||||
found.children.splice(parent.index, 1);
|
||||
}
|
||||
}
|
||||
return accSchema;
|
||||
}, schema);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user