diff --git a/packages/editor/src/initService.ts b/packages/editor/src/initService.ts index 5112ee200..fafe41035 100644 --- a/packages/editor/src/initService.ts +++ b/packages/editor/src/initService.ts @@ -1,5 +1,5 @@ import { onBeforeUnmount, reactive, toRaw, watch } from 'vue'; -import { cloneDeep, debounce } from 'lodash-es'; +import { cloneDeep } from 'lodash-es'; import type { CodeBlockContent, @@ -19,7 +19,7 @@ import { DepTargetType, Target, } from '@tmagic/core'; -import { isPage, traverseNode } from '@tmagic/utils'; +import { getNodes, isPage, traverseNode } from '@tmagic/utils'; import PropsPanel from './layouts/PropsPanel.vue'; import { EditorProps } from './editorProps'; @@ -252,63 +252,24 @@ export const initServiceEvents = ( return stage?.renderer?.runtime?.getApp?.(); }; - const updateDataSourceSchema = () => { - const root = editorService.get('root'); - - if (root?.dataSources) { - getApp()?.dataSourceManager?.updateSchema(root.dataSources); - } - }; - - const targetAddHandler = (target: Target) => { - const root = editorService.get('root'); - if (!root) return; - - if (target.type === DepTargetType.DATA_SOURCE) { - if (!root.dataSourceDeps) { - root.dataSourceDeps = {}; - } - root.dataSourceDeps[target.id] = target.deps; - } - - if (target.type === DepTargetType.DATA_SOURCE_COND) { - if (!root.dataSourceCondDeps) { - root.dataSourceCondDeps = {}; - } - root.dataSourceCondDeps[target.id] = target.deps; - } - }; - - const targetRemoveHandler = (id: string | number) => { + const updateDataSourceSchema = (nodes: MNode[], deep: boolean) => { const root = editorService.get('root'); + const app = getApp(); - if (root?.dataSourceDeps) { - delete root.dataSourceDeps[id]; + if (root && app?.dsl) { + app.dsl.dataSourceDeps = root.dataSourceDeps; + app.dsl.dataSourceCondDeps = root.dataSourceCondDeps; + app.dsl.dataSources = root.dataSources; } - if (root?.dataSourceCondDeps) { - delete root.dataSourceCondDeps[id]; + if (root?.dataSources) { + getApp()?.dataSourceManager?.updateSchema(root.dataSources); } - }; - const collectedHandler = debounce((nodes: MNode[], deep: boolean) => { - const root = editorService.get('root'); const stage = editorService.get('stage'); if (!root || !stage) return; - const app = getApp(); - - if (!app) return; - - if (app.dsl) { - app.dsl.dataSourceDeps = root.dataSourceDeps; - app.dsl.dataSourceCondDeps = root.dataSourceCondDeps; - app.dsl.dataSources = root.dataSources; - } - - updateDataSourceSchema(); - const allNodes: MNode[] = []; if (deep) { @@ -335,11 +296,60 @@ export const initServiceEvents = ( }); }); }); - }, 300); + }; + + const afterUpdateNodes = (nodes: MNode[]) => { + const root = editorService.get('root'); + if (!root) return; + const stage = editorService.get('stage'); + const app = getApp(); + if (app?.dsl) { + app.dsl.dataSourceDeps = root.dataSourceDeps; + } + for (const node of nodes) { + stage?.update({ + config: cloneDeep(node), + parentId: editorService.getParentById(node.id)?.id, + root: cloneDeep(root), + }); + } + }; + + const targetAddHandler = (target: Target) => { + const root = editorService.get('root'); + if (!root) return; + + if (target.type === DepTargetType.DATA_SOURCE) { + if (!root.dataSourceDeps) { + root.dataSourceDeps = {}; + } + root.dataSourceDeps[target.id] = target.deps; + } + + if (target.type === DepTargetType.DATA_SOURCE_COND) { + if (!root.dataSourceCondDeps) { + root.dataSourceCondDeps = {}; + } + root.dataSourceCondDeps[target.id] = target.deps; + } + }; + + const targetRemoveHandler = (id: string | number) => { + const root = editorService.get('root'); + + if (!root) return; + + if (root.dataSourceDeps) { + delete root.dataSourceDeps[id]; + } + + if (root.dataSourceCondDeps) { + delete root.dataSourceCondDeps[id]; + } + }; depService.on('add-target', targetAddHandler); depService.on('remove-target', targetRemoveHandler); - depService.on('collected', collectedHandler); const initDataSourceDepTarget = (ds: DataSourceSchema) => { depService.addTarget(createDataSourceTarget(ds, reactive({}))); @@ -347,28 +357,33 @@ export const initServiceEvents = ( depService.addTarget(createDataSourceCondTarget(ds, reactive({}))); }; - const collectIdle = (nodes: MNode[], deep: boolean) => { - nodes.forEach((node) => { - let pageId: Id | undefined; - - if (isPage(node)) { - pageId = node.id; - } else { - const info = editorService.getNodeInfo(node.id); - pageId = info.page?.id; - } - depService.collectIdle([node], { pageId }, deep); - }); - }; + const collectIdle = (nodes: MNode[], deep: boolean) => + Promise.all( + nodes.map((node) => { + let pageId: Id | undefined; + + if (isPage(node)) { + pageId = node.id; + } else { + const info = editorService.getNodeInfo(node.id); + pageId = info.page?.id; + } + return depService.collectIdle([node], { pageId }, deep); + }), + ); // 新增节点,收集依赖 const nodeAddHandler = (nodes: MNode[]) => { - collectIdle(nodes, true); + collectIdle(nodes, true).then(() => { + afterUpdateNodes(nodes); + }); }; // 节点更新,收集依赖 const nodeUpdateHandler = (nodes: MNode[]) => { - collectIdle(nodes, true); + collectIdle(nodes, true).then(() => { + afterUpdateNodes(nodes); + }); }; // 节点删除,清除对齐的依赖收集 @@ -378,7 +393,9 @@ export const initServiceEvents = ( // 由于历史记录变化是更新整个page,所以历史记录变化时,需要重新收集依赖 const historyChangeHandler = (page: MPage | MPageFragment) => { - collectIdle([page], true); + collectIdle([page], true).then(() => { + updateDataSourceSchema([page], true); + }); }; editorService.on('history-change', historyChangeHandler); @@ -413,7 +430,9 @@ export const initServiceEvents = ( removeDataSourceTarget(config.id); initDataSourceDepTarget(config); - collectIdle(root?.items || [], true); + collectIdle(root?.items || [], true).then(() => { + updateDataSourceSchema(root?.items || [], true); + }); }; const removeDataSourceTarget = (id: string) => { @@ -423,8 +442,14 @@ export const initServiceEvents = ( }; const dataSourceRemoveHandler = (id: string) => { + const root = editorService.get('root'); + const nodeIds = Object.keys(root?.dataSourceDeps?.[id] || {}); + const nodes = getNodes(nodeIds, root?.items); + collectIdle(nodes, false).then(() => { + updateDataSourceSchema(nodes, false); + }); + removeDataSourceTarget(id); - getApp()?.dataSourceManager?.removeDataSource(id); }; dataSourceService.on('add', dataSourceAddHandler); @@ -434,7 +459,6 @@ export const initServiceEvents = ( onBeforeUnmount(() => { depService.off('add-target', targetAddHandler); depService.off('remove-target', targetRemoveHandler); - depService.off('collected', collectedHandler); editorService.off('history-change', historyChangeHandler); editorService.off('root-change', rootChangeHandler); diff --git a/packages/editor/src/layouts/workspace/viewer/Stage.vue b/packages/editor/src/layouts/workspace/viewer/Stage.vue index 82e971d03..7d8a844db 100644 --- a/packages/editor/src/layouts/workspace/viewer/Stage.vue +++ b/packages/editor/src/layouts/workspace/viewer/Stage.vue @@ -132,9 +132,20 @@ watch(zoom, (zoom) => { stage.setZoom(zoom); }); +let timeoutId: NodeJS.Timeout | null = null; watch(page, (page) => { if (runtime && page) { services?.editorService.set('stageLoading', true); + + if (timeoutId) { + globalThis.clearTimeout(timeoutId); + } + + timeoutId = globalThis.setTimeout(() => { + services?.editorService.set('stageLoading', false); + timeoutId = null; + }, 3000); + runtime.updatePageId?.(page.id); nextTick(() => { stage?.select(page.id); diff --git a/packages/editor/src/services/dep.ts b/packages/editor/src/services/dep.ts index 73ac6ac3c..e27088c34 100644 --- a/packages/editor/src/services/dep.ts +++ b/packages/editor/src/services/dep.ts @@ -92,8 +92,11 @@ class Dep extends BaseService { ); }); - idleTask.once('finish', () => { - this.emit('collected', nodes, deep); + return new Promise((resolve) => { + idleTask.once('finish', () => { + this.emit('collected', nodes, deep); + resolve(); + }); }); } diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts index 985344164..d01c8a263 100644 --- a/packages/editor/src/services/editor.ts +++ b/packages/editor/src/services/editor.ts @@ -20,10 +20,19 @@ import { reactive, toRaw } from 'vue'; import { cloneDeep, get, isObject, mergeWith, uniq } from 'lodash-es'; import type { Writable } from 'type-fest'; -import type { Id, MApp, MComponent, MContainer, MNode, MPage, MPageFragment, TargetOptions } from '@tmagic/core'; +import type { Id, MApp, MContainer, MNode, MPage, MPageFragment, TargetOptions } from '@tmagic/core'; import { NodeType, Target, Watcher } from '@tmagic/core'; import { isFixed } from '@tmagic/stage'; -import { calcValueByFontsize, getElById, getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils'; +import { + calcValueByFontsize, + getElById, + getNodeInfo, + getNodePath, + isNumber, + isPage, + isPageFragment, + isPop, +} from '@tmagic/utils'; import BaseService from '@editor/services//BaseService'; import propsService from '@editor/services//props'; @@ -175,36 +184,7 @@ class Editor extends BaseService { root = toRaw(root); } - const info: EditorNodeInfo = { - node: null, - parent: null, - page: null, - }; - - if (!root) return info; - - if (id === root.id) { - info.node = root; - return info; - } - - const path = getNodePath(id, root.items); - - if (!path.length) return info; - - path.unshift(root); - - info.node = path[path.length - 1] as MComponent; - info.parent = path[path.length - 2] as MContainer; - - path.forEach((item) => { - if (isPage(item) || isPageFragment(item)) { - info.page = item as MPage | MPageFragment; - return; - } - }); - - return info; + return getNodeInfo(id, root); } /** @@ -586,11 +566,7 @@ class Editor extends BaseService { nodes.splice(targetIndex, 1, newConfig); this.set('nodes', [...nodes]); - this.get('stage')?.update({ - config: cloneDeep(newConfig), - parentId: parent.id, - root: cloneDeep(root), - }); + // update后会触发依赖收集,收集完后会掉stage.update方法 if (isPage(newConfig) || isPageFragment(newConfig)) { this.set('page', newConfig as MPage | MPageFragment); @@ -875,7 +851,11 @@ class Editor extends BaseService { await stage.select(targetId); const targetParent = this.getParentById(target.id); - await stage.update({ config: cloneDeep(target), parentId: targetParent?.id, root: cloneDeep(root) }); + await stage.update({ + config: cloneDeep(target), + parentId: targetParent?.id, + root: cloneDeep(root), + }); await this.select(newConfig); stage.select(newConfig.id); diff --git a/packages/editor/src/utils/idle-task.ts b/packages/editor/src/utils/idle-task.ts index b4f8733b0..88d9f745f 100644 --- a/packages/editor/src/utils/idle-task.ts +++ b/packages/editor/src/utils/idle-task.ts @@ -56,13 +56,14 @@ export class IdleTask extends EventEmitter { } private runTaskQueue(deadline: IdleDeadline) { - while ((deadline.timeRemaining() > 15 || deadline.didTimeout) && this.taskList.length) { + // 动画会占用空闲时间,当任务一直无法执行时,看看是否有动画正在播放 + while ((deadline.timeRemaining() > 10 || deadline.didTimeout) && this.taskList.length) { const task = this.taskList.shift(); task!.handler(task!.data); } if (this.taskList.length) { - this.taskHandle = globalThis.requestIdleCallback(this.runTaskQueue.bind(this), { timeout: 10000 }); + this.taskHandle = globalThis.requestIdleCallback(this.runTaskQueue.bind(this), { timeout: 300 }); } else { this.taskHandle = 0; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index a6ca931af..78c38af3c 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -18,9 +18,22 @@ import { cloneDeep, set as objectSet } from 'lodash-es'; -import type { DataSchema, DataSourceDeps, Id, MComponent, MNode, MNodeInstance } from '@tmagic/schema'; +import type { + DataSchema, + DataSourceDeps, + Id, + MApp, + MComponent, + MContainer, + MNode, + MNodeInstance, + MPage, + MPageFragment, +} from '@tmagic/schema'; import { NodeType } from '@tmagic/schema'; +import type { EditorNodeInfo } from '@editor/type'; + export * from './dom'; export const sleep = (ms: number): Promise => @@ -78,6 +91,39 @@ export const getNodePath = (id: Id, data: MNode[] = []): MNode[] => { return path; }; +export const getNodeInfo = (id: Id, root: MApp | null) => { + const info: EditorNodeInfo = { + node: null, + parent: null, + page: null, + }; + + if (!root) return info; + + if (id === root.id) { + info.node = root; + return info; + } + + const path = getNodePath(id, root.items); + + if (!path.length) return info; + + path.unshift(root); + + info.node = path[path.length - 1] as MComponent; + info.parent = path[path.length - 2] as MContainer; + + path.forEach((item) => { + if (isPage(item) || isPageFragment(item)) { + info.page = item as MPage | MPageFragment; + return; + } + }); + + return info; +}; + export const filterXSS = (str: string) => str.replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''');