@tmagic/editor 1.8.0-beta.19 → 1.8.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/layouts/sidebar/layer/LayerMenu.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/layer/use-click.js +3 -3
- package/dist/es/layouts/sidebar/layer/use-node-status.js +2 -2
- package/dist/es/layouts/workspace/viewer/ViewerMenu.vue_vue_type_script_setup_true_lang.js +7 -7
- package/dist/es/services/dep.js +82 -26
- package/dist/es/services/editor.js +36 -34
- package/dist/es/services/history.js +1 -1
- package/dist/es/services/keybinding.js +3 -3
- package/dist/es/utils/content-menu.js +2 -2
- package/dist/es/utils/dep/collect-worker-client.js +114 -0
- package/dist/es/utils/dep/worker.js +1 -1
- package/dist/es/utils/operator.js +2 -2
- package/dist/tmagic-editor.umd.cjs +241 -72
- package/package.json +7 -7
- package/src/layouts/sidebar/layer/LayerMenu.vue +2 -2
- package/src/layouts/sidebar/layer/use-click.ts +3 -3
- package/src/layouts/sidebar/layer/use-node-status.ts +2 -2
- package/src/layouts/workspace/viewer/ViewerMenu.vue +7 -7
- package/src/services/dep.ts +160 -31
- package/src/services/editor.ts +52 -43
- package/src/services/history.ts +1 -1
- package/src/services/keybinding.ts +3 -3
- package/src/utils/content-menu.ts +2 -2
- package/src/utils/dep/collect-worker-client.ts +149 -0
- package/src/utils/dep/worker.ts +113 -9
- package/src/utils/operator.ts +2 -2
- package/types/index.d.ts +24 -2
|
@@ -2,7 +2,7 @@ import { useServices } from "../../../hooks/use-services.js";
|
|
|
2
2
|
import ContentMenu_default from "../../../components/ContentMenu.js";
|
|
3
3
|
import FolderMinusIcon_default from "../../../icons/FolderMinusIcon.js";
|
|
4
4
|
import { useCopyMenu, useDeleteMenu, useMoveToMenu, usePasteMenu } from "../../../utils/content-menu.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isPageOrFragment } from "@tmagic/utils";
|
|
6
6
|
import { computed, createBlock, defineComponent, markRaw, openBlock, useTemplateRef } from "vue";
|
|
7
7
|
import { Files, Plus } from "@element-plus/icons-vue";
|
|
8
8
|
//#region packages/editor/src/layouts/sidebar/layer/LayerMenu.vue?vue&type=script&setup=true&lang.ts
|
|
@@ -55,7 +55,7 @@ var LayerMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
55
55
|
type: "button",
|
|
56
56
|
text: "全部折叠",
|
|
57
57
|
icon: FolderMinusIcon_default,
|
|
58
|
-
display: () =>
|
|
58
|
+
display: () => isPageOrFragment(node.value),
|
|
59
59
|
handler: () => {
|
|
60
60
|
emit("collapse-all");
|
|
61
61
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UI_SELECT_MODE_EVENT_NAME } from "../../../utils/const.js";
|
|
2
2
|
import { updateStatus } from "../../../utils/tree.js";
|
|
3
|
-
import { getElById,
|
|
3
|
+
import { getElById, isPageOrFragment } from "@tmagic/utils";
|
|
4
4
|
import { computed, nextTick } from "vue";
|
|
5
5
|
import { throttle } from "lodash-es";
|
|
6
6
|
//#region packages/editor/src/layouts/sidebar/layer/use-click.ts
|
|
@@ -16,7 +16,7 @@ var useClick = ({ editorService, stageOverlayService, uiService }, isCtrlKeyDown
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
const multiSelect = async (data) => {
|
|
19
|
-
if (
|
|
19
|
+
if (isPageOrFragment(data)) return;
|
|
20
20
|
const nodes = editorService.get("nodes") || [];
|
|
21
21
|
const newNodes = [];
|
|
22
22
|
let isCancel = false;
|
|
@@ -25,7 +25,7 @@ var useClick = ({ editorService, stageOverlayService, uiService }, isCtrlKeyDown
|
|
|
25
25
|
isCancel = true;
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
if (
|
|
28
|
+
if (isPageOrFragment(node)) return;
|
|
29
29
|
newNodes.push(node.id);
|
|
30
30
|
});
|
|
31
31
|
if (!isCancel || newNodes.length === 0) newNodes.push(data.id);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { updateStatus } from "../../../utils/tree.js";
|
|
2
|
-
import { getNodePath,
|
|
2
|
+
import { getNodePath, isPageOrFragment, traverseNode } from "@tmagic/utils";
|
|
3
3
|
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
|
4
4
|
//#region packages/editor/src/layouts/sidebar/layer/use-node-status.ts
|
|
5
5
|
var createPageNodeStatus = (page, initialLayerNodeStatus) => {
|
|
@@ -75,7 +75,7 @@ var useNodeStatus = ({ editorService }) => {
|
|
|
75
75
|
}, { immediate: true });
|
|
76
76
|
const addHandler = (newNodes) => {
|
|
77
77
|
newNodes.forEach((node) => {
|
|
78
|
-
if (
|
|
78
|
+
if (isPageOrFragment(node)) return;
|
|
79
79
|
traverseNode(node, (node) => {
|
|
80
80
|
nodeStatusMap.value?.set(node.id, {
|
|
81
81
|
visible: true,
|
|
@@ -3,7 +3,7 @@ import { useServices } from "../../../hooks/use-services.js";
|
|
|
3
3
|
import ContentMenu_default from "../../../components/ContentMenu.js";
|
|
4
4
|
import { useCopyMenu, useDeleteMenu, useMoveToMenu, usePasteMenu } from "../../../utils/content-menu.js";
|
|
5
5
|
import CenterIcon_default from "../../../icons/CenterIcon.js";
|
|
6
|
-
import {
|
|
6
|
+
import { isPageOrFragment } from "@tmagic/utils";
|
|
7
7
|
import { NodeType } from "@tmagic/core";
|
|
8
8
|
import { computed, createBlock, defineComponent, markRaw, openBlock, ref, useTemplateRef, watch } from "vue";
|
|
9
9
|
import { Bottom, Top } from "@element-plus/icons-vue";
|
|
@@ -46,14 +46,14 @@ var ViewerMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
46
46
|
direction: "horizontal",
|
|
47
47
|
display: () => {
|
|
48
48
|
if (!node.value) return false;
|
|
49
|
-
return !
|
|
49
|
+
return !isPageOrFragment(node.value);
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
type: "button",
|
|
54
54
|
text: "上移一层",
|
|
55
55
|
icon: markRaw(Top),
|
|
56
|
-
display: () => !
|
|
56
|
+
display: () => !isPageOrFragment(node.value) && !props.isMultiSelect,
|
|
57
57
|
handler: () => {
|
|
58
58
|
editorService.moveLayer(1, { historySource: "stage-contextmenu" });
|
|
59
59
|
}
|
|
@@ -62,7 +62,7 @@ var ViewerMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
62
62
|
type: "button",
|
|
63
63
|
text: "下移一层",
|
|
64
64
|
icon: markRaw(Bottom),
|
|
65
|
-
display: () => !
|
|
65
|
+
display: () => !isPageOrFragment(node.value) && !props.isMultiSelect,
|
|
66
66
|
handler: () => {
|
|
67
67
|
editorService.moveLayer(-1, { historySource: "stage-contextmenu" });
|
|
68
68
|
}
|
|
@@ -71,7 +71,7 @@ var ViewerMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
71
71
|
type: "button",
|
|
72
72
|
text: "置顶",
|
|
73
73
|
icon: markRaw(Top),
|
|
74
|
-
display: () => !
|
|
74
|
+
display: () => !isPageOrFragment(node.value) && !props.isMultiSelect,
|
|
75
75
|
handler: () => {
|
|
76
76
|
editorService.moveLayer(LayerOffset.TOP, { historySource: "stage-contextmenu" });
|
|
77
77
|
}
|
|
@@ -80,7 +80,7 @@ var ViewerMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
80
80
|
type: "button",
|
|
81
81
|
text: "置底",
|
|
82
82
|
icon: markRaw(Bottom),
|
|
83
|
-
display: () => !
|
|
83
|
+
display: () => !isPageOrFragment(node.value) && !props.isMultiSelect,
|
|
84
84
|
handler: () => {
|
|
85
85
|
editorService.moveLayer(LayerOffset.BOTTOM, { historySource: "stage-contextmenu" });
|
|
86
86
|
}
|
|
@@ -89,7 +89,7 @@ var ViewerMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
89
89
|
{
|
|
90
90
|
type: "divider",
|
|
91
91
|
direction: "horizontal",
|
|
92
|
-
display: () => !
|
|
92
|
+
display: () => !isPageOrFragment(node.value) && !props.isMultiSelect
|
|
93
93
|
},
|
|
94
94
|
useDeleteMenu("stage-contextmenu"),
|
|
95
95
|
{
|
package/dist/es/services/dep.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import BaseService from "./BaseService.js";
|
|
2
2
|
import { IdleTask } from "../utils/dep/idle-task.js";
|
|
3
|
-
import
|
|
3
|
+
import { CollectWorkerClient } from "../utils/dep/collect-worker-client.js";
|
|
4
4
|
import { isPage } from "@tmagic/utils";
|
|
5
5
|
import { DepTargetType, Watcher, traverseTarget } from "@tmagic/core";
|
|
6
6
|
import { reactive, shallowReactive } from "vue";
|
|
7
7
|
import { throttle } from "lodash-es";
|
|
8
|
-
import serialize from "serialize-javascript";
|
|
9
8
|
//#region packages/editor/src/services/dep.ts
|
|
10
9
|
var Dep = class extends BaseService {
|
|
11
10
|
state = shallowReactive({
|
|
@@ -13,6 +12,7 @@ var Dep = class extends BaseService {
|
|
|
13
12
|
taskLength: 0
|
|
14
13
|
});
|
|
15
14
|
idleTask = new IdleTask();
|
|
15
|
+
collectWorker = new CollectWorkerClient();
|
|
16
16
|
watcher = new Watcher({ initialTargets: reactive({}) });
|
|
17
17
|
waitingWorker;
|
|
18
18
|
resolveWaitingWorker;
|
|
@@ -65,6 +65,14 @@ var Dep = class extends BaseService {
|
|
|
65
65
|
this.emit("collected", nodes, deep);
|
|
66
66
|
this.emit("ds-collected", nodes, deep);
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* 空闲收集依赖
|
|
70
|
+
*
|
|
71
|
+
* 遍历匹配是整个收集过程中最耗时的部分(要深度遍历节点树并对每个属性做 target 匹配),
|
|
72
|
+
* 节点/数据源多时在主线程执行会长时间卡死页面,因此优先交给 worker 执行,主线程只负责把结果写回 target。
|
|
73
|
+
* 自定义 target 的 isTarget 是无法跨线程传递的闭包函数(没有 descriptor),仍走主线程空闲队列;
|
|
74
|
+
* worker 不可用(如 SSR)或执行失败时也会回退到主线程空闲队列。
|
|
75
|
+
*/
|
|
68
76
|
async collectIdle(nodes, depExtendedData = {}, deep = false, type) {
|
|
69
77
|
if (this.waitingWorker) await this.waitingWorker;
|
|
70
78
|
const batch = {
|
|
@@ -74,11 +82,13 @@ var Dep = class extends BaseService {
|
|
|
74
82
|
dsPending: 0,
|
|
75
83
|
collectedEmitted: false,
|
|
76
84
|
dsSettled: false,
|
|
85
|
+
aborted: false,
|
|
77
86
|
resolve: () => {}
|
|
78
87
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
const workerTargets = [];
|
|
89
|
+
for (const target of this.watcher.getCollectableTargets(type)) if (target.descriptor && this.collectWorker.isSupported) workerTargets.push(target);
|
|
90
|
+
else this.enqueueTasks(nodes, target, depExtendedData, deep, batch);
|
|
91
|
+
if (workerTargets.length && nodes.length) this.collectByCollectWorker(nodes, workerTargets, depExtendedData, deep, batch);
|
|
82
92
|
if (batch.pending === 0) {
|
|
83
93
|
this.emit("collected", nodes, deep);
|
|
84
94
|
this.updateCollectingState();
|
|
@@ -97,16 +107,8 @@ var Dep = class extends BaseService {
|
|
|
97
107
|
const { promise, resolve: waitingResolve } = Promise.withResolvers();
|
|
98
108
|
this.waitingWorker = promise;
|
|
99
109
|
this.resolveWaitingWorker = waitingResolve;
|
|
100
|
-
return
|
|
101
|
-
const
|
|
102
|
-
worker.postMessage({ dsl: serialize(dsl) });
|
|
103
|
-
worker.onmessage = (e) => {
|
|
104
|
-
resolve(e.data);
|
|
105
|
-
};
|
|
106
|
-
worker.onerror = () => {
|
|
107
|
-
resolve({});
|
|
108
|
-
};
|
|
109
|
-
}).then((depsData) => {
|
|
110
|
+
return this.collectWorker.collectDsl(dsl).then((deps) => {
|
|
111
|
+
const depsData = deps || {};
|
|
110
112
|
if (generation !== this.workerGeneration) {
|
|
111
113
|
waitingResolve();
|
|
112
114
|
return depsData;
|
|
@@ -132,7 +134,8 @@ var Dep = class extends BaseService {
|
|
|
132
134
|
}
|
|
133
135
|
collectNode(node, target, depExtendedData = {}, deep = false) {
|
|
134
136
|
if (isPage(node)) this.removePageDep(target, depExtendedData);
|
|
135
|
-
else this.watcher.removeTargetDep(target, node);
|
|
137
|
+
else if (deep) this.watcher.removeTargetDep(target, node);
|
|
138
|
+
else target.removeDep(node.id);
|
|
136
139
|
this.watcher.collectItem(node, target, depExtendedData, deep);
|
|
137
140
|
}
|
|
138
141
|
clear(nodes) {
|
|
@@ -150,6 +153,7 @@ var Dep = class extends BaseService {
|
|
|
150
153
|
clearIdleTasks() {
|
|
151
154
|
this.abortActiveBatches();
|
|
152
155
|
this.idleTask.clearTasks();
|
|
156
|
+
this.collectWorker.abort();
|
|
153
157
|
}
|
|
154
158
|
on(eventName, listener) {
|
|
155
159
|
return super.on(eventName, listener);
|
|
@@ -164,6 +168,7 @@ var Dep = class extends BaseService {
|
|
|
164
168
|
this.resolveWaitingWorker = void 0;
|
|
165
169
|
this.waitingWorker = void 0;
|
|
166
170
|
this.idleTask.clearTasks();
|
|
171
|
+
this.collectWorker.abort();
|
|
167
172
|
for (const type of Object.keys(this.watcher.getTargetsList())) this.removeTargets(type);
|
|
168
173
|
this.set("collecting", false);
|
|
169
174
|
}
|
|
@@ -173,6 +178,7 @@ var Dep = class extends BaseService {
|
|
|
173
178
|
this.reset();
|
|
174
179
|
this.removeAllPlugins();
|
|
175
180
|
this.idleTask.removeAllListeners();
|
|
181
|
+
this.collectWorker.terminate();
|
|
176
182
|
}
|
|
177
183
|
emit(eventName, ...args) {
|
|
178
184
|
return super.emit(eventName, ...args);
|
|
@@ -187,26 +193,73 @@ var Dep = class extends BaseService {
|
|
|
187
193
|
if (dep.data?.pageId && dep.data.pageId === depExtendedData.pageId) delete target.deps[depKey];
|
|
188
194
|
}
|
|
189
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* 把遍历匹配交给 worker,结果回来后按 target 分片写回主线程
|
|
198
|
+
*/
|
|
199
|
+
collectByCollectWorker(nodes, targets, depExtendedData, deep, batch) {
|
|
200
|
+
const hasDataSource = targets.some((target) => target.type === DepTargetType.DATA_SOURCE);
|
|
201
|
+
this.beginBatchTask(batch, hasDataSource);
|
|
202
|
+
this.collectWorker.collect({
|
|
203
|
+
nodes,
|
|
204
|
+
targets: targets.map((target) => target.descriptor),
|
|
205
|
+
depExtendedData,
|
|
206
|
+
deep
|
|
207
|
+
}).then((result) => {
|
|
208
|
+
if (batch.aborted) return;
|
|
209
|
+
for (const target of targets) if (result) this.enqueueApplyTask(target, result.deps[target.type]?.[target.id] || {}, result.nodeIds, batch, {
|
|
210
|
+
nodes,
|
|
211
|
+
depExtendedData,
|
|
212
|
+
deep
|
|
213
|
+
});
|
|
214
|
+
else this.enqueueTasks(nodes, target, depExtendedData, deep, batch);
|
|
215
|
+
}).finally(() => {
|
|
216
|
+
this.onBatchTaskDone(batch, hasDataSource);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 用 worker 的收集结果替换 target 上这批节点的依赖
|
|
221
|
+
*/
|
|
222
|
+
enqueueApplyTask(target, deps, nodeIds, batch, { nodes, depExtendedData, deep }) {
|
|
223
|
+
const isDataSource = target.type === DepTargetType.DATA_SOURCE;
|
|
224
|
+
this.beginBatchTask(batch, isDataSource);
|
|
225
|
+
this.idleTask.enqueueTask(() => {
|
|
226
|
+
try {
|
|
227
|
+
const current = this.watcher.getTarget(target.id, target.type);
|
|
228
|
+
if (current !== target) {
|
|
229
|
+
if (current) this.enqueueTasks(nodes, current, depExtendedData, deep, batch);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (nodes.some((node) => isPage(node))) this.removePageDep(target, depExtendedData);
|
|
233
|
+
for (const id of nodeIds) target.removeDep(id);
|
|
234
|
+
for (const [id, dep] of Object.entries(deps)) target.deps[id] = dep;
|
|
235
|
+
} finally {
|
|
236
|
+
this.onBatchTaskDone(batch, isDataSource);
|
|
237
|
+
}
|
|
238
|
+
}, void 0, isDataSource);
|
|
239
|
+
}
|
|
240
|
+
enqueueTasks(nodes, target, depExtendedData, deep, batch) {
|
|
241
|
+
for (const node of nodes) this.enqueueTask(node, target, depExtendedData, deep, batch);
|
|
242
|
+
}
|
|
190
243
|
enqueueTask(node, target, depExtendedData, deep, batch) {
|
|
191
244
|
const isDataSource = target.type === DepTargetType.DATA_SOURCE;
|
|
192
|
-
batch
|
|
193
|
-
|
|
194
|
-
this.idleTask.enqueueTask(({ node, deep, target }) => {
|
|
245
|
+
this.beginBatchTask(batch, isDataSource);
|
|
246
|
+
this.idleTask.enqueueTask(() => {
|
|
195
247
|
try {
|
|
196
|
-
this.collectNode(node, target, depExtendedData
|
|
248
|
+
this.collectNode(node, target, depExtendedData);
|
|
197
249
|
} finally {
|
|
198
250
|
this.onBatchTaskDone(batch, isDataSource);
|
|
199
251
|
}
|
|
200
|
-
},
|
|
201
|
-
node,
|
|
202
|
-
deep: false,
|
|
203
|
-
target
|
|
204
|
-
}, isDataSource);
|
|
252
|
+
}, void 0, isDataSource);
|
|
205
253
|
if (deep && Array.isArray(node.items) && node.items.length) node.items.forEach((item) => {
|
|
206
254
|
this.enqueueTask(item, target, depExtendedData, deep, batch);
|
|
207
255
|
});
|
|
208
256
|
}
|
|
257
|
+
beginBatchTask(batch, isDataSource) {
|
|
258
|
+
batch.pending += 1;
|
|
259
|
+
if (isDataSource) batch.dsPending += 1;
|
|
260
|
+
}
|
|
209
261
|
onBatchTaskDone(batch, isDataSource) {
|
|
262
|
+
if (batch.aborted) return;
|
|
210
263
|
if (isDataSource) {
|
|
211
264
|
batch.dsPending -= 1;
|
|
212
265
|
if (batch.dsPending === 0) this.settleBatchDs(batch);
|
|
@@ -238,7 +291,10 @@ var Dep = class extends BaseService {
|
|
|
238
291
|
if (!this.activeBatches.size) return;
|
|
239
292
|
const batches = [...this.activeBatches];
|
|
240
293
|
this.activeBatches.clear();
|
|
241
|
-
for (const batch of batches)
|
|
294
|
+
for (const batch of batches) {
|
|
295
|
+
batch.aborted = true;
|
|
296
|
+
batch.resolve(false);
|
|
297
|
+
}
|
|
242
298
|
this.updateCollectingState();
|
|
243
299
|
}
|
|
244
300
|
updateCollectingState() {
|
|
@@ -7,9 +7,9 @@ import storage_default, { Protocol } from "./storage.js";
|
|
|
7
7
|
import { COPY_STORAGE_KEY, calcAlignCenterStyle, calcLayerTargetIndex, calcMoveStyle, classifyDragSources, collectRelatedNodes, describeStepForRevert, editorNodeMergeCustomizer, fixNodePosition, getInitPositionStyle, getNodeIndex, getPageFragmentList, getPageList, moveItemsInContainer, resolveSelectedNode, setChildrenLayout, setLayout, toggleFixedPosition } from "../utils/editor.js";
|
|
8
8
|
import { beforePaste, getAddParent } from "../utils/operator.js";
|
|
9
9
|
import { isFixed } from "@tmagic/stage";
|
|
10
|
-
import { getNodeInfo, getNodePath, getValueByKeyPath, guid, isPage, isPageFragment, setValueByKeyPath, traverseNode } from "@tmagic/utils";
|
|
10
|
+
import { getNodeInfo, getNodePath, getValueByKeyPath, guid, isPage, isPageFragment, isPageOrFragment, setValueByKeyPath, traverseNode } from "@tmagic/utils";
|
|
11
11
|
import { NodeType } from "@tmagic/core";
|
|
12
|
-
import { reactive, toRaw } from "vue";
|
|
12
|
+
import { nextTick, reactive, toRaw } from "vue";
|
|
13
13
|
import { cloneDeep as cloneDeep$1, isEmpty, isEqual, isObject, mergeWith, uniq } from "lodash-es";
|
|
14
14
|
//#region packages/editor/src/services/editor.ts
|
|
15
15
|
/**
|
|
@@ -159,7 +159,7 @@ var Editor = class extends BaseService {
|
|
|
159
159
|
isOnDifferentPage(node) {
|
|
160
160
|
const currentPageId = this.get("page")?.id;
|
|
161
161
|
if (currentPageId === void 0 || currentPageId === null) return false;
|
|
162
|
-
if (
|
|
162
|
+
if (isPageOrFragment(node)) return `${node.id}` !== `${currentPageId}`;
|
|
163
163
|
const nodePage = this.getNodeInfo(node.id, false).page;
|
|
164
164
|
if (!nodePage) return false;
|
|
165
165
|
return `${nodePage.id}` !== `${currentPageId}`;
|
|
@@ -252,8 +252,8 @@ var Editor = class extends BaseService {
|
|
|
252
252
|
const curNode = this.get("node");
|
|
253
253
|
const stage = this.get("stage");
|
|
254
254
|
if (!curNode) throw new Error("当前选中节点为空");
|
|
255
|
-
if ((parent.type === NodeType.ROOT || curNode?.type === NodeType.ROOT) && !(
|
|
256
|
-
if (parent.id !== curNode.id && !(
|
|
255
|
+
if ((parent.type === NodeType.ROOT || curNode?.type === NodeType.ROOT) && !isPageOrFragment(node)) throw new Error("app下不能添加组件");
|
|
256
|
+
if (parent.id !== curNode.id && !isPageOrFragment(node)) {
|
|
257
257
|
const index = parent.items.indexOf(curNode);
|
|
258
258
|
parent.items?.splice(index + 1, 0, node);
|
|
259
259
|
} else parent.items?.push(node);
|
|
@@ -298,7 +298,7 @@ var Editor = class extends BaseService {
|
|
|
298
298
|
} else addNodes.push(...addNode);
|
|
299
299
|
const newNodes = await Promise.all(addNodes.map((node) => {
|
|
300
300
|
const root = this.get("root");
|
|
301
|
-
if ((
|
|
301
|
+
if (isPageOrFragment(node) && root) return this.doAdd(node, root);
|
|
302
302
|
const parentNode = parent ?? getAddParent(node);
|
|
303
303
|
if (!parentNode) throw new Error("未找到父元素");
|
|
304
304
|
return this.doAdd(node, parentNode);
|
|
@@ -318,7 +318,7 @@ var Editor = class extends BaseService {
|
|
|
318
318
|
else if (isPageFragment(newNodes[0])) this.state.pageFragmentLength += 1;
|
|
319
319
|
else if (!skipSelect) stage?.select(newNodes[0].id);
|
|
320
320
|
}
|
|
321
|
-
if (!(
|
|
321
|
+
if (!isPageOrFragment(newNodes[0])) {
|
|
322
322
|
const pageForOp = this.getNodeInfo(newNodes[0].id, false).page;
|
|
323
323
|
if (!doNotPushHistory) {
|
|
324
324
|
const parentId = (this.getParentById(newNodes[0].id, false) ?? this.get("root")).id;
|
|
@@ -351,7 +351,7 @@ var Editor = class extends BaseService {
|
|
|
351
351
|
historySource,
|
|
352
352
|
doNotPushHistory
|
|
353
353
|
});
|
|
354
|
-
const addedPages = newNodes.filter((node) =>
|
|
354
|
+
const addedPages = newNodes.filter((node) => isPageOrFragment(node));
|
|
355
355
|
if (addedPages.length) history_default.notifyPageStructureChange({
|
|
356
356
|
add: addedPages,
|
|
357
357
|
remove: []
|
|
@@ -365,13 +365,10 @@ var Editor = class extends BaseService {
|
|
|
365
365
|
if (!parent || !curNode) throw new Error("找不要删除的节点");
|
|
366
366
|
const index = getNodeIndex(curNode.id, parent);
|
|
367
367
|
if (typeof index !== "number" || index === -1) throw new Error("找不要删除的节点");
|
|
368
|
-
parent.items?.splice(index, 1);
|
|
369
368
|
const stage = this.get("stage");
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
root: cloneDeep$1(root)
|
|
374
|
-
});
|
|
369
|
+
const currentPage = this.get("page");
|
|
370
|
+
const isDeletingCurrentPage = isPageOrFragment(node) && !!currentPage && `${currentPage.id}` === `${node.id}`;
|
|
371
|
+
parent.items?.splice(index, 1);
|
|
375
372
|
const selectedNodes = this.get("nodes");
|
|
376
373
|
const removedSelectedIndex = selectedNodes.findIndex((n) => `${n.id}` === `${node.id}`);
|
|
377
374
|
if (removedSelectedIndex !== -1) {
|
|
@@ -379,24 +376,29 @@ var Editor = class extends BaseService {
|
|
|
379
376
|
nextSelected.splice(removedSelectedIndex, 1);
|
|
380
377
|
this.set("nodes", nextSelected);
|
|
381
378
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const selectDefault = async (pages) => {
|
|
387
|
-
if (pages[0]) {
|
|
388
|
-
await this.select(pages[0]);
|
|
389
|
-
stage?.select(pages[0].id);
|
|
390
|
-
} else this.selectRoot();
|
|
379
|
+
const removeData = {
|
|
380
|
+
id: node.id,
|
|
381
|
+
parentId: parent.id,
|
|
382
|
+
root: cloneDeep$1(root)
|
|
391
383
|
};
|
|
392
384
|
const rootItems = root.items || [];
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
this.state.pageFragmentLength -= 1;
|
|
398
|
-
if (
|
|
385
|
+
const shouldReselect = isDeletingCurrentPage && !doNotSelect && !doNotSwitchPage;
|
|
386
|
+
const shouldSwitchPage = shouldReselect && rootItems.length > 0;
|
|
387
|
+
if (isPageOrFragment(node)) {
|
|
388
|
+
if (isPage(node)) this.state.pageLength -= 1;
|
|
389
|
+
else this.state.pageFragmentLength -= 1;
|
|
390
|
+
if (shouldSwitchPage) {
|
|
391
|
+
await this.select(rootItems[0]);
|
|
392
|
+
stage?.select(rootItems[0].id);
|
|
393
|
+
await nextTick();
|
|
394
|
+
stage?.remove(removeData);
|
|
395
|
+
} else {
|
|
396
|
+
stage?.remove(removeData);
|
|
397
|
+
if (shouldReselect) this.selectRoot();
|
|
398
|
+
else if (isDeletingCurrentPage) this.set("page", null);
|
|
399
|
+
}
|
|
399
400
|
} else {
|
|
401
|
+
stage?.remove(removeData);
|
|
400
402
|
if (!doNotSelect) {
|
|
401
403
|
await this.select(parent);
|
|
402
404
|
stage?.select(parent.id);
|
|
@@ -425,7 +427,7 @@ var Editor = class extends BaseService {
|
|
|
425
427
|
}));
|
|
426
428
|
const removedItems = [];
|
|
427
429
|
let pageForOp = null;
|
|
428
|
-
if (!(
|
|
430
|
+
if (!isPageOrFragment(nodes[0])) for (const n of nodes) {
|
|
429
431
|
const { parent, node: curNode, page } = this.getNodeInfo(n.id, false);
|
|
430
432
|
if (parent && curNode) {
|
|
431
433
|
if (!pageForOp && page) pageForOp = {
|
|
@@ -459,7 +461,7 @@ var Editor = class extends BaseService {
|
|
|
459
461
|
historySource,
|
|
460
462
|
doNotPushHistory
|
|
461
463
|
});
|
|
462
|
-
const removedPages = nodes.filter((node) =>
|
|
464
|
+
const removedPages = nodes.filter((node) => isPageOrFragment(node));
|
|
463
465
|
if (removedPages.length) history_default.notifyPageStructureChange({
|
|
464
466
|
add: [],
|
|
465
467
|
remove: removedPages
|
|
@@ -497,7 +499,7 @@ var Editor = class extends BaseService {
|
|
|
497
499
|
selectedNodes.splice(targetIndex, 1, newConfig);
|
|
498
500
|
this.set("nodes", [...selectedNodes]);
|
|
499
501
|
}
|
|
500
|
-
if (
|
|
502
|
+
if (isPageOrFragment(newConfig)) {
|
|
501
503
|
const currentPage = this.get("page");
|
|
502
504
|
if (currentPage && `${currentPage.id}` === `${newConfig.id}`) this.set("page", newConfig);
|
|
503
505
|
}
|
|
@@ -751,7 +753,7 @@ var Editor = class extends BaseService {
|
|
|
751
753
|
*/
|
|
752
754
|
async moveToContainer(config, targetId, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription, historySource } = {}) {
|
|
753
755
|
const isBatch = Array.isArray(config);
|
|
754
|
-
const configs = (isBatch ? config : [config]).filter((item) => !(
|
|
756
|
+
const configs = (isBatch ? config : [config]).filter((item) => !isPageOrFragment(item));
|
|
755
757
|
if (configs.length === 0) throw new Error("没有可移动的节点");
|
|
756
758
|
this.captureSelectionBeforeOp();
|
|
757
759
|
const target = this.getNodeById(targetId, false);
|
|
@@ -1225,7 +1227,7 @@ var Editor = class extends BaseService {
|
|
|
1225
1227
|
getPageOfNode(id) {
|
|
1226
1228
|
const { node, page } = this.getNodeInfo(id, false);
|
|
1227
1229
|
if (page) return page;
|
|
1228
|
-
if (node && (
|
|
1230
|
+
if (node && isPageOrFragment(node)) return node;
|
|
1229
1231
|
return null;
|
|
1230
1232
|
}
|
|
1231
1233
|
captureSelectionBeforeOp() {
|
|
@@ -247,7 +247,7 @@ var History = class extends BaseService {
|
|
|
247
247
|
* 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
|
|
248
248
|
*
|
|
249
249
|
* 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
|
|
250
|
-
* 对
|
|
250
|
+
* 对 isPageOrFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
|
|
251
251
|
* 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
|
|
252
252
|
*
|
|
253
253
|
* 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
|
|
@@ -2,7 +2,7 @@ import { KeyBindingCommand } from "../type.js";
|
|
|
2
2
|
import BaseService from "./BaseService.js";
|
|
3
3
|
import editor_default from "./editor.js";
|
|
4
4
|
import ui_default from "./ui.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isPageOrFragment } from "@tmagic/utils";
|
|
6
6
|
import KeyController from "keycon";
|
|
7
7
|
//#region packages/editor/src/services/keybinding.ts
|
|
8
8
|
var Keybinding = class extends BaseService {
|
|
@@ -12,7 +12,7 @@ var Keybinding = class extends BaseService {
|
|
|
12
12
|
commands = {
|
|
13
13
|
[KeyBindingCommand.DELETE_NODE]: () => {
|
|
14
14
|
const nodes = editor_default.get("nodes");
|
|
15
|
-
if (!nodes ||
|
|
15
|
+
if (!nodes || isPageOrFragment(nodes[0])) return;
|
|
16
16
|
editor_default.remove(nodes, { historySource: "shortcut" });
|
|
17
17
|
},
|
|
18
18
|
[KeyBindingCommand.COPY_NODE]: () => {
|
|
@@ -21,7 +21,7 @@ var Keybinding = class extends BaseService {
|
|
|
21
21
|
},
|
|
22
22
|
[KeyBindingCommand.CUT_NODE]: () => {
|
|
23
23
|
const nodes = editor_default.get("nodes");
|
|
24
|
-
if (!nodes ||
|
|
24
|
+
if (!nodes || isPageOrFragment(nodes[0])) return;
|
|
25
25
|
editor_default.copy(nodes);
|
|
26
26
|
editor_default.remove(nodes, { historySource: "shortcut" });
|
|
27
27
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { COPY_STORAGE_KEY } from "./editor.js";
|
|
2
|
-
import { calcValueByFontsize, isPage,
|
|
2
|
+
import { calcValueByFontsize, isPage, isPageOrFragment } from "@tmagic/utils";
|
|
3
3
|
import { NodeType, cloneDeep } from "@tmagic/core";
|
|
4
4
|
import { computed, markRaw } from "vue";
|
|
5
5
|
import { CopyDocument, Delete, DocumentCopy } from "@element-plus/icons-vue";
|
|
@@ -15,7 +15,7 @@ var useDeleteMenu = (historySource) => ({
|
|
|
15
15
|
icon: Delete,
|
|
16
16
|
display: ({ editorService }) => {
|
|
17
17
|
const node = editorService.get("node");
|
|
18
|
-
return node?.type !== NodeType.ROOT && !
|
|
18
|
+
return node?.type !== NodeType.ROOT && !isPageOrFragment(node);
|
|
19
19
|
},
|
|
20
20
|
handler: ({ editorService }) => {
|
|
21
21
|
const nodes = editorService.get("nodes");
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { error } from "../logger.js";
|
|
2
|
+
import WorkerWrapper from "./worker.js";
|
|
3
|
+
import serialize from "serialize-javascript";
|
|
4
|
+
//#region packages/editor/src/utils/dep/collect-worker-client.ts
|
|
5
|
+
/**
|
|
6
|
+
* 依赖收集 worker 的主线程客户端
|
|
7
|
+
*
|
|
8
|
+
* 依赖收集需要深度遍历整棵节点树并对每个属性做匹配,节点/数据源多时会长时间占用主线程导致页面卡死,
|
|
9
|
+
* 这里把遍历匹配放到 worker 中执行,主线程只负责把结果写回 target。
|
|
10
|
+
*
|
|
11
|
+
* 全量收集与增量收集共用一个常驻 worker:增量收集调用频繁(每次节点更新都会触发),
|
|
12
|
+
* 按次创建 worker 的启动开销无法接受,也容易漏掉销毁导致线程泄漏。
|
|
13
|
+
*/
|
|
14
|
+
var CollectWorkerClient = class {
|
|
15
|
+
worker = null;
|
|
16
|
+
seed = 0;
|
|
17
|
+
pending = /* @__PURE__ */ new Map();
|
|
18
|
+
get isSupported() {
|
|
19
|
+
return typeof Worker !== "undefined";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 在 worker 中全量收集整个 DSL 的依赖,target 由 worker 根据 DSL 重建
|
|
23
|
+
* @returns 收集结果,返回 null 表示 worker 不可用或执行失败
|
|
24
|
+
*/
|
|
25
|
+
collectDsl(dsl) {
|
|
26
|
+
return this.request((id) => ({
|
|
27
|
+
id,
|
|
28
|
+
dsl: serialize(dsl)
|
|
29
|
+
})).then((response) => response?.deps || null);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 在 worker 中增量收集指定节点的依赖,target 以可序列化描述的形式传入
|
|
33
|
+
* @returns 收集结果,返回 null 表示 worker 不可用或执行失败,调用方需要回退到主线程收集
|
|
34
|
+
*/
|
|
35
|
+
collect(payload) {
|
|
36
|
+
return this.request((id) => ({
|
|
37
|
+
id,
|
|
38
|
+
payload: serialize(payload)
|
|
39
|
+
})).then((response) => response ? {
|
|
40
|
+
deps: response.deps || {},
|
|
41
|
+
nodeIds: response.nodeIds || []
|
|
42
|
+
} : null);
|
|
43
|
+
}
|
|
44
|
+
terminate() {
|
|
45
|
+
this.worker?.terminate();
|
|
46
|
+
this.worker = null;
|
|
47
|
+
this.settleAllPending();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 丢弃在途请求并重建 worker
|
|
51
|
+
* clearIdleTasks / reset 中断收集时调用,避免已 abort 的长任务继续占着常驻 worker 拖慢后续收集
|
|
52
|
+
*/
|
|
53
|
+
abort() {
|
|
54
|
+
this.terminate();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* worker 按收到的顺序逐个处理请求,因此请求间不会互相打断,用 id 把结果分发回各自的调用方
|
|
58
|
+
*/
|
|
59
|
+
request(createRequest) {
|
|
60
|
+
const worker = this.getWorker();
|
|
61
|
+
if (!worker) return Promise.resolve(null);
|
|
62
|
+
this.seed += 1;
|
|
63
|
+
const id = this.seed;
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
this.pending.set(id, resolve);
|
|
66
|
+
try {
|
|
67
|
+
worker.postMessage(createRequest(id));
|
|
68
|
+
} catch (e) {
|
|
69
|
+
error("magic editor: 依赖收集 worker 通信失败", e);
|
|
70
|
+
this.pending.delete(id);
|
|
71
|
+
resolve(null);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
getWorker() {
|
|
76
|
+
if (!this.isSupported) return null;
|
|
77
|
+
if (this.worker) return this.worker;
|
|
78
|
+
try {
|
|
79
|
+
const worker = new WorkerWrapper();
|
|
80
|
+
worker.onmessage = (e) => {
|
|
81
|
+
this.handleResponse(e.data);
|
|
82
|
+
};
|
|
83
|
+
worker.onerror = () => {
|
|
84
|
+
this.handleFatalError();
|
|
85
|
+
};
|
|
86
|
+
worker.onmessageerror = () => {
|
|
87
|
+
this.handleFatalError();
|
|
88
|
+
};
|
|
89
|
+
this.worker = worker;
|
|
90
|
+
} catch (e) {
|
|
91
|
+
error("magic editor: 依赖收集 worker 创建失败", e);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return this.worker;
|
|
95
|
+
}
|
|
96
|
+
handleResponse(response) {
|
|
97
|
+
const resolve = this.pending.get(response?.id);
|
|
98
|
+
if (!resolve) return;
|
|
99
|
+
this.pending.delete(response.id);
|
|
100
|
+
resolve(response.failed ? null : response);
|
|
101
|
+
}
|
|
102
|
+
handleFatalError() {
|
|
103
|
+
this.worker?.terminate();
|
|
104
|
+
this.worker = null;
|
|
105
|
+
this.settleAllPending();
|
|
106
|
+
}
|
|
107
|
+
settleAllPending() {
|
|
108
|
+
const resolvers = [...this.pending.values()];
|
|
109
|
+
this.pending.clear();
|
|
110
|
+
for (const resolve of resolvers) resolve(null);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
export { CollectWorkerClient };
|