@tmagic/editor 1.8.0-beta.0 → 1.8.0-beta.2
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/Editor.vue_vue_type_script_setup_true_lang.js +11 -2
- package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +18 -110
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +6 -0
- package/dist/es/components/CompareForm.js +5 -0
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +167 -0
- package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +19 -3
- package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +18 -2
- package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +37 -6
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +12 -3
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +14 -6
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +3 -1
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +15 -7
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +14 -6
- package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +58 -11
- package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +11 -6
- package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/es/fields/StyleSetter/components/Box.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +16 -3
- package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +14 -2
- package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +8 -5
- package/dist/es/hooks/use-code-block-edit.js +2 -2
- package/dist/es/hooks/use-stage.js +15 -7
- package/dist/es/index.js +3 -1
- package/dist/es/initService.js +6 -6
- package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +12 -7
- package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +8 -0
- package/dist/es/layouts/history-list/Bucket.js +5 -0
- package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +84 -0
- package/dist/es/layouts/history-list/CodeBlockTab.js +5 -0
- package/dist/es/layouts/history-list/CodeBlockTab.vue_vue_type_script_setup_true_lang.js +62 -0
- package/dist/es/layouts/history-list/DataSourceTab.js +5 -0
- package/dist/es/layouts/history-list/DataSourceTab.vue_vue_type_script_setup_true_lang.js +62 -0
- package/dist/es/layouts/history-list/GroupRow.js +5 -0
- package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +180 -0
- package/dist/es/layouts/history-list/HistoryDiffDialog.js +5 -0
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +195 -0
- package/dist/es/layouts/history-list/HistoryListPanel.js +5 -0
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +270 -0
- package/dist/es/layouts/history-list/InitialRow.js +5 -0
- package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +48 -0
- package/dist/es/layouts/history-list/PageTab.js +5 -0
- package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +99 -0
- package/dist/es/layouts/history-list/composables.js +154 -0
- package/dist/es/layouts/sidebar/layer/use-node-status.js +38 -4
- package/dist/es/services/BaseService.js +31 -68
- package/dist/es/services/codeBlock.js +205 -8
- package/dist/es/services/componentList.js +2 -2
- package/dist/es/services/dataSource.js +217 -8
- package/dist/es/services/dep.js +21 -9
- package/dist/es/services/editor.js +350 -160
- package/dist/es/services/events.js +2 -2
- package/dist/es/services/history.js +407 -12
- package/dist/es/services/keybinding.js +2 -2
- package/dist/es/services/props.js +8 -8
- package/dist/es/services/stageOverlay.js +2 -2
- package/dist/es/services/storage.js +2 -2
- package/dist/es/services/ui.js +2 -2
- package/dist/es/style.css +347 -1
- package/dist/es/utils/code-block.js +122 -0
- package/dist/es/utils/content-menu.js +4 -5
- package/dist/es/utils/dep/worker.js +1 -1
- package/dist/es/utils/editor.js +3 -3
- package/dist/es/utils/undo-redo.js +20 -0
- package/dist/style.css +347 -1
- package/dist/tmagic-editor.umd.cjs +3273 -766
- package/package.json +7 -7
- package/src/Editor.vue +7 -0
- package/src/components/CodeBlockEditor.vue +20 -109
- package/src/components/CodeParams.vue +6 -0
- package/src/components/CompareForm.vue +225 -0
- package/src/editorProps.ts +2 -0
- package/src/fields/Code.vue +19 -3
- package/src/fields/CodeSelect.vue +17 -1
- package/src/fields/CodeSelectCol.vue +30 -2
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +7 -1
- package/src/fields/DataSourceFieldSelect/Index.vue +9 -2
- package/src/fields/DataSourceFields.vue +12 -3
- package/src/fields/DataSourceMethodSelect.vue +4 -1
- package/src/fields/DataSourceMethods.vue +14 -4
- package/src/fields/DataSourceMocks.vue +13 -3
- package/src/fields/DataSourceSelect.vue +4 -1
- package/src/fields/EventSelect.vue +57 -8
- package/src/fields/KeyValue.vue +17 -4
- package/src/fields/StyleSetter/Index.vue +6 -0
- package/src/fields/StyleSetter/components/Border.vue +15 -1
- package/src/fields/StyleSetter/components/Box.vue +2 -0
- package/src/fields/StyleSetter/pro/Background.vue +15 -1
- package/src/fields/StyleSetter/pro/Border.vue +24 -2
- package/src/fields/StyleSetter/pro/Font.vue +15 -1
- package/src/fields/StyleSetter/pro/Layout.vue +17 -1
- package/src/fields/StyleSetter/pro/Position.vue +15 -1
- package/src/fields/StyleSetter/pro/Transform.vue +15 -1
- package/src/fields/UISelect.vue +5 -2
- package/src/hooks/use-code-block-edit.ts +5 -2
- package/src/hooks/use-stage.ts +23 -7
- package/src/index.ts +1 -0
- package/src/layouts/CodeEditor.vue +15 -0
- package/src/layouts/Framework.vue +10 -5
- package/src/layouts/NavMenu.vue +9 -0
- package/src/layouts/history-list/Bucket.vue +99 -0
- package/src/layouts/history-list/CodeBlockTab.vue +61 -0
- package/src/layouts/history-list/DataSourceTab.vue +61 -0
- package/src/layouts/history-list/GroupRow.vue +207 -0
- package/src/layouts/history-list/HistoryDiffDialog.vue +212 -0
- package/src/layouts/history-list/HistoryListPanel.vue +271 -0
- package/src/layouts/history-list/InitialRow.vue +41 -0
- package/src/layouts/history-list/PageTab.vue +94 -0
- package/src/layouts/history-list/composables.ts +195 -0
- package/src/layouts/sidebar/layer/use-node-status.ts +64 -12
- package/src/services/BaseService.ts +35 -86
- package/src/services/codeBlock.ts +256 -5
- package/src/services/dataSource.ts +257 -6
- package/src/services/dep.ts +32 -9
- package/src/services/editor.ts +454 -171
- package/src/services/history.ts +452 -8
- package/src/services/props.ts +2 -2
- package/src/theme/history-list-panel.scss +423 -0
- package/src/theme/theme.scss +1 -0
- package/src/type.ts +179 -6
- package/src/utils/code-block.ts +150 -0
- package/src/utils/content-menu.ts +8 -5
- package/src/utils/dep/worker.ts +5 -3
- package/src/utils/editor.ts +4 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/undo-redo.ts +23 -0
- package/types/index.d.ts +2107 -810
- package/dist/es/utils/compose.js +0 -44
- package/dist/es/utils/editor-history.js +0 -100
- package/src/utils/compose.ts +0 -52
- package/src/utils/editor-history.ts +0 -138
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
import { Layout, canUsePluginMethods } from "../type.js";
|
|
2
|
-
import
|
|
2
|
+
import BaseService from "./BaseService.js";
|
|
3
3
|
import props_default from "./props.js";
|
|
4
4
|
import history_default from "./history.js";
|
|
5
5
|
import storage_default, { Protocol } from "./storage.js";
|
|
6
6
|
import { COPY_STORAGE_KEY, calcAlignCenterStyle, calcLayerTargetIndex, calcMoveStyle, classifyDragSources, collectRelatedNodes, editorNodeMergeCustomizer, fixNodePosition, getInitPositionStyle, getNodeIndex, getPageFragmentList, getPageList, moveItemsInContainer, resolveSelectedNode, setChildrenLayout, setLayout, toggleFixedPosition } from "../utils/editor.js";
|
|
7
|
-
import { applyHistoryAddOp, applyHistoryRemoveOp, applyHistoryUpdateOp } from "../utils/editor-history.js";
|
|
8
7
|
import { beforePaste, getAddParent } from "../utils/operator.js";
|
|
9
8
|
import { isFixed } from "@tmagic/stage";
|
|
10
|
-
import { getNodeInfo, getNodePath, isPage, isPageFragment } from "@tmagic/utils";
|
|
9
|
+
import { getNodeInfo, getNodePath, getValueByKeyPath, isPage, isPageFragment, setValueByKeyPath } from "@tmagic/utils";
|
|
11
10
|
import { NodeType } from "@tmagic/core";
|
|
12
11
|
import { reactive, toRaw } from "vue";
|
|
13
|
-
import { cloneDeep, isObject, mergeWith, uniq } from "lodash-es";
|
|
12
|
+
import { cloneDeep as cloneDeep$1, isObject, mergeWith, uniq } from "lodash-es";
|
|
14
13
|
//#region packages/editor/src/services/editor.ts
|
|
15
14
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* 给「回滚」生成的新 step 用的简短描述生成器。
|
|
16
|
+
* 与 UI 层 `describePageStep` 同义,但避免 service 反向依赖 layouts/,故在此本地实现。
|
|
18
17
|
*/
|
|
19
|
-
var
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
var describeStepForRevert = (step) => {
|
|
19
|
+
switch (step.opType) {
|
|
20
|
+
case "add": {
|
|
21
|
+
const count = step.nodes?.length ?? 0;
|
|
22
|
+
const node = step.nodes?.[0];
|
|
23
|
+
const label = node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : "");
|
|
24
|
+
return `撤回新增 ${count} 个节点${count === 1 && label ? `(${label})` : ""}`;
|
|
25
|
+
}
|
|
26
|
+
case "remove": {
|
|
27
|
+
const count = step.removedItems?.length ?? 0;
|
|
28
|
+
const node = step.removedItems?.[0]?.node;
|
|
29
|
+
const label = node?.name || node?.type || (node?.id !== void 0 ? `${node.id}` : "");
|
|
30
|
+
return `还原已删除的 ${count} 个节点${count === 1 && label ? `(${label})` : ""}`;
|
|
31
|
+
}
|
|
32
|
+
default: {
|
|
33
|
+
const items = step.updatedItems ?? [];
|
|
34
|
+
if (items.length === 1) {
|
|
35
|
+
const { newNode, oldNode, changeRecords } = items[0];
|
|
36
|
+
const target = newNode?.name || newNode?.type || oldNode?.name || oldNode?.type || `${newNode?.id ?? ""}`;
|
|
37
|
+
const propPath = changeRecords?.[0]?.propPath;
|
|
38
|
+
return propPath ? `还原 ${target} · ${propPath}` : `还原 ${target}`;
|
|
39
|
+
}
|
|
40
|
+
return `还原 ${items.length} 个节点的修改`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
31
43
|
};
|
|
32
|
-
var Editor = class extends
|
|
44
|
+
var Editor = class extends BaseService {
|
|
33
45
|
state = reactive({
|
|
34
46
|
root: null,
|
|
35
47
|
page: null,
|
|
@@ -45,7 +57,6 @@ var Editor = class extends BaseService_default {
|
|
|
45
57
|
disabledMultiSelect: false,
|
|
46
58
|
alwaysMultiSelect: false
|
|
47
59
|
});
|
|
48
|
-
isHistoryStateChange = false;
|
|
49
60
|
selectionBeforeOp = null;
|
|
50
61
|
constructor() {
|
|
51
62
|
super(canUsePluginMethods.async.map((methodName) => ({
|
|
@@ -137,7 +148,7 @@ var Editor = class extends BaseService_default {
|
|
|
137
148
|
* 只有容器拥有布局
|
|
138
149
|
*/
|
|
139
150
|
async getLayout(parent, node) {
|
|
140
|
-
if (node &&
|
|
151
|
+
if (node && isFixed(node.style || {})) return Layout.FIXED;
|
|
141
152
|
if (parent.layout) return parent.layout;
|
|
142
153
|
if (!parent.style?.position) return Layout.RELATIVE;
|
|
143
154
|
return Layout.ABSOLUTE;
|
|
@@ -231,18 +242,18 @@ var Editor = class extends BaseService_default {
|
|
|
231
242
|
const layout = await this.getLayout(toRaw(parent), node);
|
|
232
243
|
node.style = getInitPositionStyle(node.style, layout);
|
|
233
244
|
await stage?.add({
|
|
234
|
-
config: cloneDeep(node),
|
|
235
|
-
parent: cloneDeep(parent),
|
|
245
|
+
config: cloneDeep$1(node),
|
|
246
|
+
parent: cloneDeep$1(parent),
|
|
236
247
|
parentId: parent.id,
|
|
237
|
-
root: cloneDeep(root)
|
|
248
|
+
root: cloneDeep$1(root)
|
|
238
249
|
});
|
|
239
250
|
const newStyle = fixNodePosition(node, parent, stage);
|
|
240
251
|
if (newStyle && (newStyle.top !== node.style.top || newStyle.left !== node.style.left)) {
|
|
241
252
|
node.style = newStyle;
|
|
242
253
|
await stage?.update({
|
|
243
|
-
config: cloneDeep(node),
|
|
254
|
+
config: cloneDeep$1(node),
|
|
244
255
|
parentId: parent.id,
|
|
245
|
-
root: cloneDeep(root)
|
|
256
|
+
root: cloneDeep$1(root)
|
|
246
257
|
});
|
|
247
258
|
}
|
|
248
259
|
this.addModifiedNodeId(node.id);
|
|
@@ -255,11 +266,10 @@ var Editor = class extends BaseService_default {
|
|
|
255
266
|
* @param options 可选配置
|
|
256
267
|
* @param options.doNotSelect 添加后是否不更新当前选中节点(默认 false,添加后会选中新增的节点)
|
|
257
268
|
* @param options.doNotSwitchPage 添加后是否不切换当前页面(默认 false;新增页面 / 跨页新增时为 true 会跳过会引发页面切换的选中操作)
|
|
269
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
258
270
|
* @returns 添加后的节点
|
|
259
271
|
*/
|
|
260
|
-
async add(addNode, parent,
|
|
261
|
-
const safeParentNode = safeParent(parent);
|
|
262
|
-
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions(options);
|
|
272
|
+
async add(addNode, parent, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription } = {}) {
|
|
263
273
|
this.captureSelectionBeforeOp();
|
|
264
274
|
const stage = this.get("stage");
|
|
265
275
|
const addNodes = [];
|
|
@@ -271,7 +281,7 @@ var Editor = class extends BaseService_default {
|
|
|
271
281
|
const newNodes = await Promise.all(addNodes.map((node) => {
|
|
272
282
|
const root = this.get("root");
|
|
273
283
|
if ((isPage(node) || isPageFragment(node)) && root) return this.doAdd(node, root);
|
|
274
|
-
const parentNode =
|
|
284
|
+
const parentNode = parent ?? getAddParent(node);
|
|
275
285
|
if (!parentNode) throw new Error("未找到父元素");
|
|
276
286
|
return this.doAdd(node, parentNode);
|
|
277
287
|
}));
|
|
@@ -292,8 +302,8 @@ var Editor = class extends BaseService_default {
|
|
|
292
302
|
}
|
|
293
303
|
if (!(isPage(newNodes[0]) || isPageFragment(newNodes[0]))) {
|
|
294
304
|
const pageForOp = this.getNodeInfo(newNodes[0].id, false).page;
|
|
295
|
-
this.pushOpHistory("add", {
|
|
296
|
-
nodes: newNodes.map((n) => cloneDeep(toRaw(n))),
|
|
305
|
+
if (!doNotPushHistory) this.pushOpHistory("add", {
|
|
306
|
+
nodes: newNodes.map((n) => cloneDeep$1(toRaw(n))),
|
|
297
307
|
parentId: (this.getParentById(newNodes[0].id, false) ?? this.get("root")).id,
|
|
298
308
|
indexMap: Object.fromEntries(newNodes.map((n) => {
|
|
299
309
|
const p = this.getParentById(n.id, false);
|
|
@@ -302,13 +312,13 @@ var Editor = class extends BaseService_default {
|
|
|
302
312
|
}, {
|
|
303
313
|
name: pageForOp?.name || "",
|
|
304
314
|
id: pageForOp.id
|
|
305
|
-
});
|
|
315
|
+
}, historyDescription);
|
|
316
|
+
else this.selectionBeforeOp = null;
|
|
306
317
|
}
|
|
307
318
|
this.emit("add", newNodes);
|
|
308
319
|
return Array.isArray(addNode) ? newNodes : newNodes[0];
|
|
309
320
|
}
|
|
310
|
-
async doRemove(node,
|
|
311
|
-
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions(options);
|
|
321
|
+
async doRemove(node, { doNotSelect = false, doNotSwitchPage = false } = {}) {
|
|
312
322
|
const root = this.get("root");
|
|
313
323
|
if (!root) throw new Error("root不能为空");
|
|
314
324
|
const { parent, node: curNode } = this.getNodeInfo(node.id, false);
|
|
@@ -320,7 +330,7 @@ var Editor = class extends BaseService_default {
|
|
|
320
330
|
stage?.remove({
|
|
321
331
|
id: node.id,
|
|
322
332
|
parentId: parent.id,
|
|
323
|
-
root: cloneDeep(root)
|
|
333
|
+
root: cloneDeep$1(root)
|
|
324
334
|
});
|
|
325
335
|
const selectedNodes = this.get("nodes");
|
|
326
336
|
const removedSelectedIndex = selectedNodes.findIndex((n) => `${n.id}` === `${node.id}`);
|
|
@@ -367,9 +377,9 @@ var Editor = class extends BaseService_default {
|
|
|
367
377
|
* @param options 可选配置
|
|
368
378
|
* @param options.doNotSelect 删除后是否不更新当前选中节点(默认 false,删除后会选中父节点或首个页面)
|
|
369
379
|
* @param options.doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
|
|
380
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
370
381
|
*/
|
|
371
|
-
async remove(nodeOrNodeList,
|
|
372
|
-
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions(options);
|
|
382
|
+
async remove(nodeOrNodeList, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false, historyDescription } = {}) {
|
|
373
383
|
this.captureSelectionBeforeOp();
|
|
374
384
|
const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
|
|
375
385
|
const removedItems = [];
|
|
@@ -383,7 +393,7 @@ var Editor = class extends BaseService_default {
|
|
|
383
393
|
};
|
|
384
394
|
const idx = getNodeIndex(curNode.id, parent);
|
|
385
395
|
removedItems.push({
|
|
386
|
-
node: cloneDeep(toRaw(curNode)),
|
|
396
|
+
node: cloneDeep$1(toRaw(curNode)),
|
|
387
397
|
parentId: parent.id,
|
|
388
398
|
index: typeof idx === "number" ? idx : -1
|
|
389
399
|
});
|
|
@@ -393,7 +403,8 @@ var Editor = class extends BaseService_default {
|
|
|
393
403
|
doNotSelect,
|
|
394
404
|
doNotSwitchPage
|
|
395
405
|
})));
|
|
396
|
-
if (removedItems.length > 0 && pageForOp) this.pushOpHistory("remove", { removedItems }, pageForOp);
|
|
406
|
+
if (removedItems.length > 0 && pageForOp) if (!doNotPushHistory) this.pushOpHistory("remove", { removedItems }, pageForOp, historyDescription);
|
|
407
|
+
else this.selectionBeforeOp = null;
|
|
397
408
|
this.emit("remove", nodes);
|
|
398
409
|
}
|
|
399
410
|
async doUpdate(config, { changeRecords = [] } = {}) {
|
|
@@ -404,7 +415,7 @@ var Editor = class extends BaseService_default {
|
|
|
404
415
|
if (!info.node) throw new Error(`获取不到id为${config.id}的节点`);
|
|
405
416
|
const node = toRaw(info.node);
|
|
406
417
|
let newConfig = await toggleFixedPosition(toRaw(config), node, root, this.getLayout);
|
|
407
|
-
newConfig = mergeWith(cloneDeep(node), newConfig, editorNodeMergeCustomizer);
|
|
418
|
+
newConfig = mergeWith(cloneDeep$1(node), newConfig, editorNodeMergeCustomizer);
|
|
408
419
|
if (!newConfig.type) throw new Error("配置缺少type值");
|
|
409
420
|
if (newConfig.type === NodeType.ROOT) {
|
|
410
421
|
this.set("root", newConfig);
|
|
@@ -444,25 +455,33 @@ var Editor = class extends BaseService_default {
|
|
|
444
455
|
* 更新节点
|
|
445
456
|
* update后会触发依赖收集,收集完后会掉stage.update方法
|
|
446
457
|
* @param config 新的节点配置,配置中需要有id信息
|
|
458
|
+
* @param data 额外数据
|
|
459
|
+
* @param data.changeRecords 单节点 form 端变更记录(多节点场景下被忽略,使用 changeRecordList)
|
|
460
|
+
* @param data.changeRecordList 多节点 form 端变更记录列表,按 config 数组同序对应每个节点;优先级高于 changeRecords
|
|
461
|
+
* @param data.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
462
|
+
* @param data.historyDescription 入栈时附带的人类可读描述,用于历史面板展示(不影响 undo/redo 行为)
|
|
447
463
|
* @returns 更新后的节点配置
|
|
448
464
|
*/
|
|
449
465
|
async update(config, data = {}) {
|
|
450
466
|
this.captureSelectionBeforeOp();
|
|
467
|
+
const { doNotPushHistory = false, changeRecordList, changeRecords, historyDescription } = data;
|
|
451
468
|
const nodes = Array.isArray(config) ? config : [config];
|
|
452
|
-
const updateData = await Promise.all(nodes.map((node) =>
|
|
469
|
+
const updateData = await Promise.all(nodes.map((node, index) => {
|
|
470
|
+
const recordsForNode = changeRecordList ? changeRecordList[index] ?? [] : changeRecords ?? [];
|
|
471
|
+
return this.doUpdate(node, { changeRecords: recordsForNode });
|
|
472
|
+
}));
|
|
453
473
|
if (updateData[0].oldNode?.type !== NodeType.ROOT) {
|
|
454
|
-
|
|
455
|
-
if (!this.isHistoryStateChange && curNodes.length) {
|
|
474
|
+
if (this.get("nodes").length) if (!doNotPushHistory) {
|
|
456
475
|
const pageForOp = this.getNodeInfo(nodes[0].id, false).page;
|
|
457
476
|
this.pushOpHistory("update", { updatedItems: updateData.map((d) => ({
|
|
458
|
-
oldNode: cloneDeep(d.oldNode),
|
|
459
|
-
newNode: cloneDeep(toRaw(d.newNode))
|
|
477
|
+
oldNode: cloneDeep$1(d.oldNode),
|
|
478
|
+
newNode: cloneDeep$1(toRaw(d.newNode)),
|
|
479
|
+
changeRecords: d.changeRecords?.length ? cloneDeep$1(d.changeRecords) : void 0
|
|
460
480
|
})) }, {
|
|
461
481
|
name: pageForOp?.name || "",
|
|
462
482
|
id: pageForOp.id
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
this.isHistoryStateChange = false;
|
|
483
|
+
}, historyDescription);
|
|
484
|
+
} else this.selectionBeforeOp = null;
|
|
466
485
|
}
|
|
467
486
|
this.emit("update", updateData);
|
|
468
487
|
return Array.isArray(config) ? updateData.map((item) => item.newNode) : updateData[0].newNode;
|
|
@@ -474,27 +493,27 @@ var Editor = class extends BaseService_default {
|
|
|
474
493
|
* @param options 可选配置
|
|
475
494
|
* @param options.doNotSelect 排序后是否不更新当前选中节点(默认 false)
|
|
476
495
|
* @param options.doNotSwitchPage 排序后是否不切换当前页面(排序只发生在同一父节点内,方法内为空操作;保留以与其它 DSL 操作 API 一致)
|
|
496
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
477
497
|
* @returns void
|
|
478
498
|
*/
|
|
479
|
-
async sort(id1, id2,
|
|
480
|
-
const { doNotSelect = false } = safeOptions(options);
|
|
499
|
+
async sort(id1, id2, { doNotSelect = false, doNotPushHistory = false } = {}) {
|
|
481
500
|
this.captureSelectionBeforeOp();
|
|
482
501
|
const root = this.get("root");
|
|
483
502
|
if (!root) throw new Error("root为空");
|
|
484
503
|
const node = this.get("node");
|
|
485
504
|
if (!node) throw new Error("当前节点为空");
|
|
486
|
-
const parent = cloneDeep(toRaw(this.get("parent")));
|
|
505
|
+
const parent = cloneDeep$1(toRaw(this.get("parent")));
|
|
487
506
|
if (!parent) throw new Error("父节点为空");
|
|
488
507
|
const index2 = parent.items.findIndex((node) => `${node.id}` === `${id2}`);
|
|
489
508
|
if (index2 < 0) return;
|
|
490
509
|
const index1 = parent.items.findIndex((node) => `${node.id}` === `${id1}`);
|
|
491
510
|
parent.items.splice(index2, 0, ...parent.items.splice(index1, 1));
|
|
492
|
-
await this.update(parent);
|
|
511
|
+
await this.update(parent, { doNotPushHistory });
|
|
493
512
|
if (!doNotSelect) await this.select(node);
|
|
494
513
|
this.get("stage")?.update({
|
|
495
|
-
config: cloneDeep(node),
|
|
514
|
+
config: cloneDeep$1(node),
|
|
496
515
|
parentId: parent.id,
|
|
497
|
-
root: cloneDeep(root)
|
|
516
|
+
root: cloneDeep$1(root)
|
|
498
517
|
});
|
|
499
518
|
}
|
|
500
519
|
/**
|
|
@@ -522,10 +541,10 @@ var Editor = class extends BaseService_default {
|
|
|
522
541
|
* @param options 可选配置
|
|
523
542
|
* @param options.doNotSelect 粘贴后是否不更新当前选中节点(默认 false)
|
|
524
543
|
* @param options.doNotSwitchPage 粘贴后是否不切换当前页面(默认 false;跨页粘贴时为 true 会跳过页面切换)
|
|
544
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
525
545
|
* @returns 添加后的组件节点配置
|
|
526
546
|
*/
|
|
527
|
-
async paste(position = {}, collectorOptions,
|
|
528
|
-
const { doNotSelect = false, doNotSwitchPage = false } = safeOptions(options);
|
|
547
|
+
async paste(position = {}, collectorOptions, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false } = {}) {
|
|
529
548
|
const config = storage_default.getItem(COPY_STORAGE_KEY);
|
|
530
549
|
if (!Array.isArray(config)) return;
|
|
531
550
|
const node = this.get("node");
|
|
@@ -538,18 +557,19 @@ var Editor = class extends BaseService_default {
|
|
|
538
557
|
if (collectorOptions && typeof collectorOptions.isTarget === "function") props_default.replaceRelateId(config, pasteConfigs, collectorOptions);
|
|
539
558
|
return this.add(pasteConfigs, parent, {
|
|
540
559
|
doNotSelect,
|
|
541
|
-
doNotSwitchPage
|
|
560
|
+
doNotSwitchPage,
|
|
561
|
+
doNotPushHistory
|
|
542
562
|
});
|
|
543
563
|
}
|
|
544
564
|
async doPaste(config, position = {}) {
|
|
545
565
|
props_default.clearRelateId();
|
|
546
566
|
const doc = this.get("stage")?.renderer?.contentWindow?.document;
|
|
547
|
-
return beforePaste(position, cloneDeep(config), doc);
|
|
567
|
+
return beforePaste(position, cloneDeep$1(config), doc);
|
|
548
568
|
}
|
|
549
569
|
async doAlignCenter(config) {
|
|
550
570
|
const parent = this.getParentById(config.id);
|
|
551
571
|
if (!parent) throw new Error("找不到父节点");
|
|
552
|
-
const node = cloneDeep(toRaw(config));
|
|
572
|
+
const node = cloneDeep$1(toRaw(config));
|
|
553
573
|
const layout = await this.getLayout(parent, node);
|
|
554
574
|
const doc = this.get("stage")?.renderer?.contentWindow?.document;
|
|
555
575
|
const newStyle = calcAlignCenterStyle(node, parent, layout, doc);
|
|
@@ -563,14 +583,14 @@ var Editor = class extends BaseService_default {
|
|
|
563
583
|
* @param options 可选配置
|
|
564
584
|
* @param options.doNotSelect 居中后是否不更新当前选中节点(默认 false)
|
|
565
585
|
* @param options.doNotSwitchPage 居中后是否不切换当前页面(居中只更新节点 style,方法内为空操作;保留以与其它 DSL 操作 API 一致)
|
|
586
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
566
587
|
* @returns 当前组件节点配置
|
|
567
588
|
*/
|
|
568
|
-
async alignCenter(config,
|
|
569
|
-
const { doNotSelect = false } = safeOptions(options);
|
|
589
|
+
async alignCenter(config, { doNotSelect = false, doNotPushHistory = false } = {}) {
|
|
570
590
|
const nodes = Array.isArray(config) ? config : [config];
|
|
571
591
|
const stage = this.get("stage");
|
|
572
592
|
const newNodes = await Promise.all(nodes.map((node) => this.doAlignCenter(node)));
|
|
573
|
-
const newNode = await this.update(newNodes);
|
|
593
|
+
const newNode = await this.update(newNodes, { doNotPushHistory });
|
|
574
594
|
if (!doNotSelect) if (newNodes.length > 1) await stage?.multiSelect(newNodes.map((node) => node.id));
|
|
575
595
|
else await stage?.select(newNodes[0].id);
|
|
576
596
|
return newNode;
|
|
@@ -578,8 +598,10 @@ var Editor = class extends BaseService_default {
|
|
|
578
598
|
/**
|
|
579
599
|
* 移动当前选中节点位置
|
|
580
600
|
* @param offset 偏移量
|
|
601
|
+
* @param options 可选配置
|
|
602
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
581
603
|
*/
|
|
582
|
-
async moveLayer(offset) {
|
|
604
|
+
async moveLayer(offset, { doNotPushHistory = false } = {}) {
|
|
583
605
|
this.captureSelectionBeforeOp();
|
|
584
606
|
const root = this.get("root");
|
|
585
607
|
if (!root) throw new Error("root为空");
|
|
@@ -592,95 +614,104 @@ var Editor = class extends BaseService_default {
|
|
|
592
614
|
const isRelative = await this.getLayout(parent, node) === Layout.RELATIVE;
|
|
593
615
|
const offsetIndex = calcLayerTargetIndex(index, offset, brothers.length, isRelative);
|
|
594
616
|
if (offsetIndex > 0 && offsetIndex > brothers.length || offsetIndex < 0) return;
|
|
595
|
-
const oldParent = cloneDeep(toRaw(parent));
|
|
617
|
+
const oldParent = cloneDeep$1(toRaw(parent));
|
|
596
618
|
brothers.splice(index, 1);
|
|
597
619
|
brothers.splice(offsetIndex, 0, node);
|
|
598
620
|
const grandparent = this.getParentById(parent.id);
|
|
599
621
|
this.get("stage")?.update({
|
|
600
|
-
config: cloneDeep(toRaw(parent)),
|
|
622
|
+
config: cloneDeep$1(toRaw(parent)),
|
|
601
623
|
parentId: grandparent?.id,
|
|
602
|
-
root: cloneDeep(root)
|
|
624
|
+
root: cloneDeep$1(root)
|
|
603
625
|
});
|
|
604
626
|
this.addModifiedNodeId(parent.id);
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
627
|
+
if (!doNotPushHistory) {
|
|
628
|
+
const pageForOp = this.getNodeInfo(node.id, false).page;
|
|
629
|
+
this.pushOpHistory("update", { updatedItems: [{
|
|
630
|
+
oldNode: oldParent,
|
|
631
|
+
newNode: cloneDeep$1(toRaw(parent))
|
|
632
|
+
}] }, {
|
|
633
|
+
name: pageForOp?.name || "",
|
|
634
|
+
id: pageForOp.id
|
|
635
|
+
});
|
|
636
|
+
} else this.selectionBeforeOp = null;
|
|
613
637
|
this.emit("move-layer", offset);
|
|
614
638
|
}
|
|
615
639
|
/**
|
|
616
|
-
*
|
|
617
|
-
*
|
|
640
|
+
* 移动一个或多个节点到指定容器中。
|
|
641
|
+
*
|
|
642
|
+
* 多选场景(config 是数组)只会产生一条历史记录,
|
|
643
|
+
* `updatedItems` 涵盖所有源父容器 + 目标容器的前后快照。
|
|
644
|
+
* 这避免了"多选移动到某容器"在历史栈里被切成 N 条记录。
|
|
645
|
+
*
|
|
646
|
+
* @param config 需要移动的节点(或节点数组,各项需带 id;style 等字段会与原节点合并)
|
|
618
647
|
* @param targetId 容器ID
|
|
619
648
|
* @param options 可选配置
|
|
620
649
|
* @param options.doNotSelect 移动后是否不更新当前选中节点(默认 false)
|
|
621
650
|
* @param options.doNotSwitchPage 移动后是否不切换当前页面(默认 false;目标容器位于其它页面时为 true 会跳过自动选中以避免页面切换)
|
|
651
|
+
* @param options.doNotPushHistory 是否不写入历史记录(默认 false)
|
|
622
652
|
*/
|
|
623
|
-
async moveToContainer(config, targetId,
|
|
624
|
-
const
|
|
653
|
+
async moveToContainer(config, targetId, { doNotSelect = false, doNotSwitchPage = false, doNotPushHistory = false } = {}) {
|
|
654
|
+
const isBatch = Array.isArray(config);
|
|
655
|
+
const configs = (isBatch ? config : [config]).filter((item) => !(isPage(item) || isPageFragment(item)));
|
|
656
|
+
if (configs.length === 0) throw new Error("没有可移动的节点");
|
|
625
657
|
this.captureSelectionBeforeOp();
|
|
626
|
-
const root = this.get("root");
|
|
627
|
-
const { node, parent, page: pageForOp } = this.getNodeInfo(config.id, false);
|
|
628
658
|
const target = this.getNodeById(targetId, false);
|
|
659
|
+
if (!target) throw new Error("目标容器不存在");
|
|
660
|
+
const root = this.get("root");
|
|
629
661
|
const stage = this.get("stage");
|
|
630
|
-
if (root
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (Array.isArray(srcValue)) return srcValue;
|
|
643
|
-
});
|
|
644
|
-
newConfig.style = getInitPositionStyle(newConfig.style, layout);
|
|
645
|
-
target.items.push(newConfig);
|
|
646
|
-
const targetWouldSwitchPage = this.isOnDifferentPage(target);
|
|
647
|
-
const skipSelect = doNotSelect || doNotSwitchPage && targetWouldSwitchPage;
|
|
648
|
-
if (!skipSelect) await stage.select(targetId);
|
|
649
|
-
const targetParent = this.getParentById(target.id);
|
|
650
|
-
await stage.update({
|
|
651
|
-
config: cloneDeep(target),
|
|
652
|
-
parentId: targetParent?.id,
|
|
653
|
-
root: cloneDeep(root)
|
|
654
|
-
});
|
|
655
|
-
if (!skipSelect) {
|
|
656
|
-
await this.select(newConfig);
|
|
657
|
-
stage.select(newConfig.id);
|
|
658
|
-
}
|
|
659
|
-
this.addModifiedNodeId(target.id);
|
|
660
|
-
this.addModifiedNodeId(parent.id);
|
|
661
|
-
this.pushOpHistory("update", { updatedItems: [{
|
|
662
|
-
oldNode: oldSourceParent,
|
|
663
|
-
newNode: cloneDeep(toRaw(parent))
|
|
664
|
-
}, {
|
|
665
|
-
oldNode: oldTarget,
|
|
666
|
-
newNode: cloneDeep(toRaw(target))
|
|
667
|
-
}] }, {
|
|
668
|
-
name: pageForOp?.name || "",
|
|
669
|
-
id: pageForOp.id
|
|
662
|
+
if (!root || !stage) throw new Error("root 或 stage为空");
|
|
663
|
+
const moves = [];
|
|
664
|
+
for (const { id } of configs) {
|
|
665
|
+
const { node, parent, page } = this.getNodeInfo(id, false);
|
|
666
|
+
if (!node || !parent) continue;
|
|
667
|
+
moves.push({
|
|
668
|
+
node,
|
|
669
|
+
parent,
|
|
670
|
+
pageForOp: page ? {
|
|
671
|
+
name: page.name || "",
|
|
672
|
+
id: page.id
|
|
673
|
+
} : null
|
|
670
674
|
});
|
|
671
|
-
return newConfig;
|
|
672
675
|
}
|
|
676
|
+
if (moves.length === 0) throw new Error("没有可移动的节点");
|
|
677
|
+
const beforeSnapshots = /* @__PURE__ */ new Map();
|
|
678
|
+
beforeSnapshots.set(target.id, cloneDeep$1(toRaw(target)));
|
|
679
|
+
for (const { parent } of moves) if (!beforeSnapshots.has(parent.id)) beforeSnapshots.set(parent.id, cloneDeep$1(toRaw(parent)));
|
|
680
|
+
let newConfigs = [];
|
|
681
|
+
const moveNodes = moves.map(({ node }) => node);
|
|
682
|
+
await this.remove(moveNodes, {
|
|
683
|
+
doNotPushHistory: true,
|
|
684
|
+
doNotSelect,
|
|
685
|
+
doNotSwitchPage: true
|
|
686
|
+
});
|
|
687
|
+
newConfigs = await this.add(moveNodes, target, {
|
|
688
|
+
doNotPushHistory: true,
|
|
689
|
+
doNotSelect,
|
|
690
|
+
doNotSwitchPage
|
|
691
|
+
});
|
|
692
|
+
if (!doNotPushHistory) {
|
|
693
|
+
const updatedItems = Array.from(beforeSnapshots.entries()).map(([id, oldNode]) => ({
|
|
694
|
+
oldNode,
|
|
695
|
+
newNode: cloneDeep$1(toRaw(this.getNodeById(id, false)))
|
|
696
|
+
}));
|
|
697
|
+
const historyPage = moves[0].pageForOp ?? {
|
|
698
|
+
name: "",
|
|
699
|
+
id: target.id
|
|
700
|
+
};
|
|
701
|
+
this.pushOpHistory("update", { updatedItems }, historyPage);
|
|
702
|
+
} else this.selectionBeforeOp = null;
|
|
703
|
+
return isBatch ? newConfigs : newConfigs[0];
|
|
673
704
|
}
|
|
674
|
-
async dragTo(config, targetParent, targetIndex) {
|
|
705
|
+
async dragTo(config, targetParent, targetIndex, { doNotPushHistory = false } = {}) {
|
|
675
706
|
this.captureSelectionBeforeOp();
|
|
676
707
|
if (!targetParent || !Array.isArray(targetParent.items)) return;
|
|
677
708
|
const configs = Array.isArray(config) ? config : [config];
|
|
678
709
|
const beforeSnapshots = /* @__PURE__ */ new Map();
|
|
679
710
|
for (const cfg of configs) {
|
|
680
711
|
const { parent } = this.getNodeInfo(cfg.id, false);
|
|
681
|
-
if (parent && !beforeSnapshots.has(`${parent.id}`)) beforeSnapshots.set(`${parent.id}`, cloneDeep(toRaw(parent)));
|
|
712
|
+
if (parent && !beforeSnapshots.has(`${parent.id}`)) beforeSnapshots.set(`${parent.id}`, cloneDeep$1(toRaw(parent)));
|
|
682
713
|
}
|
|
683
|
-
if (!beforeSnapshots.has(`${targetParent.id}`)) beforeSnapshots.set(`${targetParent.id}`, cloneDeep(toRaw(targetParent)));
|
|
714
|
+
if (!beforeSnapshots.has(`${targetParent.id}`)) beforeSnapshots.set(`${targetParent.id}`, cloneDeep$1(toRaw(targetParent)));
|
|
684
715
|
const newLayout = await this.getLayout(targetParent);
|
|
685
716
|
const { sameParentIndices, crossParentConfigs, aborted } = classifyDragSources(configs, targetParent, (id, raw) => this.getNodeInfo(id, raw));
|
|
686
717
|
if (aborted) return;
|
|
@@ -699,23 +730,25 @@ var Editor = class extends BaseService_default {
|
|
|
699
730
|
const root = this.get("root");
|
|
700
731
|
const stage = this.get("stage");
|
|
701
732
|
if (stage && page && root) stage.update({
|
|
702
|
-
config: cloneDeep(page),
|
|
733
|
+
config: cloneDeep$1(page),
|
|
703
734
|
parentId: root.id,
|
|
704
|
-
root: cloneDeep(root)
|
|
735
|
+
root: cloneDeep$1(root)
|
|
705
736
|
});
|
|
706
737
|
const updatedItems = [];
|
|
707
738
|
for (const oldNode of beforeSnapshots.values()) {
|
|
708
739
|
const newNode = this.getNodeById(oldNode.id, false);
|
|
709
740
|
if (newNode) updatedItems.push({
|
|
710
741
|
oldNode,
|
|
711
|
-
newNode: cloneDeep(toRaw(newNode))
|
|
742
|
+
newNode: cloneDeep$1(toRaw(newNode))
|
|
712
743
|
});
|
|
713
744
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
745
|
+
if (!doNotPushHistory) {
|
|
746
|
+
const pageForOp = this.getNodeInfo(configs[0].id, false).page;
|
|
747
|
+
this.pushOpHistory("update", { updatedItems }, {
|
|
748
|
+
name: pageForOp?.name || "",
|
|
749
|
+
id: pageForOp.id
|
|
750
|
+
});
|
|
751
|
+
} else this.selectionBeforeOp = null;
|
|
719
752
|
this.emit("drag-to", {
|
|
720
753
|
targetIndex,
|
|
721
754
|
configs,
|
|
@@ -740,7 +773,107 @@ var Editor = class extends BaseService_default {
|
|
|
740
773
|
if (value) await this.applyHistoryOp(value, false);
|
|
741
774
|
return value;
|
|
742
775
|
}
|
|
743
|
-
|
|
776
|
+
/**
|
|
777
|
+
* 「回滚」指定页面历史步骤(类 git revert 语义):
|
|
778
|
+
* - 不动原始历史栈结构(不移动 cursor、不丢弃任何步骤);
|
|
779
|
+
* - 取出 `index` 对应的 step,**反向应用**一次(add→remove / remove→add / update→旧值);
|
|
780
|
+
* - 把这次反向应用作为一条**新步骤**追加到栈顶,可被普通 undo / redo。
|
|
781
|
+
*
|
|
782
|
+
* 与 `gotoPageStep`(类 git reset)的区别在于此操作**不丢弃**目标之后的历史。
|
|
783
|
+
* 与 `applyHistoryOp(reverse=true)` 的区别在于:本方法**不带** `doNotPushHistory`,
|
|
784
|
+
* 反向应用会以一条新 step 入栈;并且不实施 step 中保存的选区与 modifiedNodeIds 状态,
|
|
785
|
+
* 选区由用户当前位置决定,符合"新提交"语义。
|
|
786
|
+
*
|
|
787
|
+
* 仅对处于「已应用」状态的步骤生效——未应用的步骤本身就不存在于当前 DSL 中,反向无意义。
|
|
788
|
+
*
|
|
789
|
+
* @param index 目标 step 在所属页面栈中的索引(0 为最早),通常由历史面板传入
|
|
790
|
+
* @returns 反向后产生的新 step;目标不存在 / 未应用 / 反向失败时返回 null
|
|
791
|
+
*/
|
|
792
|
+
async revertPageStep(index) {
|
|
793
|
+
const entry = history_default.getPageStepList()[index];
|
|
794
|
+
if (!entry?.applied) return null;
|
|
795
|
+
const { step } = entry;
|
|
796
|
+
if (!this.get("root")) return null;
|
|
797
|
+
let revertedStep = null;
|
|
798
|
+
const captureRevert = (s) => {
|
|
799
|
+
revertedStep = s;
|
|
800
|
+
};
|
|
801
|
+
history_default.once("change", captureRevert);
|
|
802
|
+
const historyDescription = `回滚 #${index + 1}: ${describeStepForRevert(step)}`;
|
|
803
|
+
const opts = {
|
|
804
|
+
doNotSelect: true,
|
|
805
|
+
doNotSwitchPage: true,
|
|
806
|
+
historyDescription
|
|
807
|
+
};
|
|
808
|
+
try {
|
|
809
|
+
switch (step.opType) {
|
|
810
|
+
case "add": {
|
|
811
|
+
const nodes = step.nodes ?? [];
|
|
812
|
+
for (const n of nodes) {
|
|
813
|
+
const existing = this.getNodeById(n.id, false);
|
|
814
|
+
if (existing) await this.remove(existing, opts);
|
|
815
|
+
}
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
case "remove": {
|
|
819
|
+
const sorted = [...step.removedItems ?? []].sort((a, b) => a.index - b.index);
|
|
820
|
+
for (const { node, parentId } of sorted) {
|
|
821
|
+
const parent = this.getNodeById(parentId, false);
|
|
822
|
+
if (parent) await this.add([cloneDeep$1(node)], parent, opts);
|
|
823
|
+
}
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
case "update": {
|
|
827
|
+
const configs = (step.updatedItems ?? []).map(({ oldNode, newNode, changeRecords }) => {
|
|
828
|
+
if (changeRecords?.length) {
|
|
829
|
+
const patch = {
|
|
830
|
+
id: newNode.id,
|
|
831
|
+
type: newNode.type
|
|
832
|
+
};
|
|
833
|
+
for (const record of changeRecords) {
|
|
834
|
+
if (!record.propPath) return cloneDeep$1(oldNode);
|
|
835
|
+
const value = cloneDeep$1(getValueByKeyPath(record.propPath, oldNode));
|
|
836
|
+
setValueByKeyPath(record.propPath, value, patch);
|
|
837
|
+
}
|
|
838
|
+
return patch;
|
|
839
|
+
}
|
|
840
|
+
return cloneDeep$1(oldNode);
|
|
841
|
+
});
|
|
842
|
+
if (configs.length) await this.update(configs, { historyDescription });
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
} finally {
|
|
847
|
+
history_default.off("change", captureRevert);
|
|
848
|
+
}
|
|
849
|
+
const page = toRaw(this.get("page"));
|
|
850
|
+
if (page) this.emit("history-change", page);
|
|
851
|
+
return revertedStep;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* 跳转当前页面历史栈到指定游标位置。
|
|
855
|
+
*
|
|
856
|
+
* `targetCursor` 与 `UndoRedo.getCursor()` 同义:表示"已应用步骤数量",
|
|
857
|
+
* 取值范围 `[0, length]`。当目标 < 当前游标时循环 undo,否则循环 redo。
|
|
858
|
+
* 通常由历史面板传入「点击的 step.index + 1」作为目标。
|
|
859
|
+
*
|
|
860
|
+
* @returns 实际移动到的最终游标位置
|
|
861
|
+
*/
|
|
862
|
+
async gotoPageStep(targetCursor) {
|
|
863
|
+
let cursor = history_default.getPageCursor();
|
|
864
|
+
const { length } = history_default.getPageStepList();
|
|
865
|
+
const target = Math.max(0, Math.min(targetCursor, length));
|
|
866
|
+
while (cursor > target) {
|
|
867
|
+
if (!await this.undo()) break;
|
|
868
|
+
cursor -= 1;
|
|
869
|
+
}
|
|
870
|
+
while (cursor < target) {
|
|
871
|
+
if (!await this.redo()) break;
|
|
872
|
+
cursor += 1;
|
|
873
|
+
}
|
|
874
|
+
return cursor;
|
|
875
|
+
}
|
|
876
|
+
async move(left, top, { doNotPushHistory = false } = {}) {
|
|
744
877
|
const node = toRaw(this.get("node"));
|
|
745
878
|
if (!node || isPage(node)) return;
|
|
746
879
|
const newStyle = calcMoveStyle(node.style || {}, left, top);
|
|
@@ -749,7 +882,7 @@ var Editor = class extends BaseService_default {
|
|
|
749
882
|
id: node.id,
|
|
750
883
|
type: node.type,
|
|
751
884
|
style: newStyle
|
|
752
|
-
});
|
|
885
|
+
}, { doNotPushHistory });
|
|
753
886
|
}
|
|
754
887
|
resetState() {
|
|
755
888
|
this.set("root", null);
|
|
@@ -783,17 +916,13 @@ var Editor = class extends BaseService_default {
|
|
|
783
916
|
return super.emit(eventName, ...args);
|
|
784
917
|
}
|
|
785
918
|
addModifiedNodeId(id) {
|
|
786
|
-
|
|
919
|
+
this.get("modifiedNodeIds").set(id, id);
|
|
787
920
|
}
|
|
788
921
|
captureSelectionBeforeOp() {
|
|
789
|
-
if (this.
|
|
922
|
+
if (this.selectionBeforeOp) return;
|
|
790
923
|
this.selectionBeforeOp = this.get("nodes").map((n) => n.id);
|
|
791
924
|
}
|
|
792
|
-
pushOpHistory(opType, extra, pageData) {
|
|
793
|
-
if (this.isHistoryStateChange) {
|
|
794
|
-
this.selectionBeforeOp = null;
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
925
|
+
pushOpHistory(opType, extra, pageData, historyDescription) {
|
|
797
926
|
const step = {
|
|
798
927
|
data: pageData,
|
|
799
928
|
opType,
|
|
@@ -802,39 +931,101 @@ var Editor = class extends BaseService_default {
|
|
|
802
931
|
modifiedNodeIds: new Map(this.get("modifiedNodeIds")),
|
|
803
932
|
...extra
|
|
804
933
|
};
|
|
805
|
-
|
|
934
|
+
if (historyDescription) step.historyDescription = historyDescription;
|
|
935
|
+
history_default.push(step, pageData.id);
|
|
806
936
|
this.selectionBeforeOp = null;
|
|
807
|
-
this.isHistoryStateChange = false;
|
|
808
937
|
}
|
|
809
938
|
/**
|
|
810
939
|
* 应用历史操作(撤销 / 重做)
|
|
940
|
+
*
|
|
941
|
+
* 所有 DSL 修改都走 `editor.add / remove / update`,并通过 `doNotPushHistory` 阻止再次入栈、
|
|
942
|
+
* `doNotSelect / doNotSwitchPage` 让选区由方法末尾的统一逻辑兜底。
|
|
943
|
+
*
|
|
944
|
+
* 注意:这些公开方法会发出 add / remove / update 事件,业务侧若需要区分"用户操作"与"撤销重做触发",
|
|
945
|
+
* 请监听 `history-change` 事件配合判断。
|
|
946
|
+
*
|
|
811
947
|
* @param step 操作记录
|
|
812
948
|
* @param reverse true = 撤销,false = 重做
|
|
813
949
|
*/
|
|
814
950
|
async applyHistoryOp(step, reverse) {
|
|
815
|
-
this.isHistoryStateChange = true;
|
|
816
951
|
const root = this.get("root");
|
|
817
952
|
const stage = this.get("stage");
|
|
818
953
|
if (!root) return;
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
getNodeInfo: (id, raw) => this.getNodeInfo(id, raw),
|
|
824
|
-
setRoot: (r) => this.set("root", r),
|
|
825
|
-
setPage: (p) => this.set("page", p),
|
|
826
|
-
getPage: () => this.get("page")
|
|
954
|
+
const commonOpts = {
|
|
955
|
+
doNotSelect: true,
|
|
956
|
+
doNotSwitchPage: true,
|
|
957
|
+
doNotPushHistory: true
|
|
827
958
|
};
|
|
828
959
|
switch (step.opType) {
|
|
829
|
-
case "add":
|
|
830
|
-
|
|
960
|
+
case "add": {
|
|
961
|
+
const nodes = step.nodes ?? [];
|
|
962
|
+
if (reverse) for (const n of nodes) {
|
|
963
|
+
const existing = this.getNodeById(n.id, false);
|
|
964
|
+
if (existing) await this.remove(existing, commonOpts);
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
const parent = this.getNodeById(step.parentId, false);
|
|
968
|
+
if (parent) {
|
|
969
|
+
const sorted = [...nodes].sort((a, b) => (step.indexMap?.[a.id] ?? 0) - (step.indexMap?.[b.id] ?? 0));
|
|
970
|
+
for (const n of sorted) {
|
|
971
|
+
const idx = step.indexMap?.[n.id];
|
|
972
|
+
if (parent.items) {
|
|
973
|
+
if (typeof idx === "number" && idx >= 0 && idx < parent.items.length) parent.items.splice(idx, 0, cloneDeep$1(n));
|
|
974
|
+
else parent.items.push(cloneDeep$1(n));
|
|
975
|
+
await stage?.add({
|
|
976
|
+
config: cloneDeep$1(n),
|
|
977
|
+
parent: cloneDeep$1(parent),
|
|
978
|
+
parentId: parent.id,
|
|
979
|
+
root: cloneDeep$1(root)
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
831
985
|
break;
|
|
832
|
-
|
|
833
|
-
|
|
986
|
+
}
|
|
987
|
+
case "remove": {
|
|
988
|
+
const items = step.removedItems ?? [];
|
|
989
|
+
if (reverse) {
|
|
990
|
+
const sorted = [...items].sort((a, b) => a.index - b.index);
|
|
991
|
+
for (const { node, parentId, index } of sorted) {
|
|
992
|
+
const parent = this.getNodeById(parentId, false);
|
|
993
|
+
if (parent?.items) {
|
|
994
|
+
parent.items.splice(index, 0, cloneDeep$1(node));
|
|
995
|
+
await stage?.add({
|
|
996
|
+
config: cloneDeep$1(node),
|
|
997
|
+
parent: cloneDeep$1(parent),
|
|
998
|
+
parentId,
|
|
999
|
+
root: cloneDeep$1(root)
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
} else for (const { node } of items) {
|
|
1004
|
+
const existing = this.getNodeById(node.id, false);
|
|
1005
|
+
if (existing) await this.remove(existing, commonOpts);
|
|
1006
|
+
}
|
|
834
1007
|
break;
|
|
835
|
-
|
|
836
|
-
|
|
1008
|
+
}
|
|
1009
|
+
case "update": {
|
|
1010
|
+
const configs = (step.updatedItems ?? []).map(({ oldNode, newNode, changeRecords }) => {
|
|
1011
|
+
if (changeRecords?.length) {
|
|
1012
|
+
const sourceForValues = reverse ? oldNode : newNode;
|
|
1013
|
+
const patch = {
|
|
1014
|
+
id: newNode.id,
|
|
1015
|
+
type: newNode.type
|
|
1016
|
+
};
|
|
1017
|
+
for (const record of changeRecords) {
|
|
1018
|
+
if (!record.propPath) return cloneDeep$1(sourceForValues);
|
|
1019
|
+
const value = cloneDeep$1(getValueByKeyPath(record.propPath, sourceForValues));
|
|
1020
|
+
setValueByKeyPath(record.propPath, value, patch);
|
|
1021
|
+
}
|
|
1022
|
+
return patch;
|
|
1023
|
+
}
|
|
1024
|
+
return cloneDeep$1(reverse ? oldNode : newNode);
|
|
1025
|
+
});
|
|
1026
|
+
if (configs.length) await this.update(configs, { doNotPushHistory: true });
|
|
837
1027
|
break;
|
|
1028
|
+
}
|
|
838
1029
|
}
|
|
839
1030
|
this.set("modifiedNodeIds", step.modifiedNodeIds);
|
|
840
1031
|
const page = toRaw(this.get("page"));
|
|
@@ -849,7 +1040,6 @@ var Editor = class extends BaseService_default {
|
|
|
849
1040
|
}, 0);
|
|
850
1041
|
this.emit("history-change", page);
|
|
851
1042
|
}
|
|
852
|
-
this.isHistoryStateChange = false;
|
|
853
1043
|
}
|
|
854
1044
|
selectedConfigExceptionHandler(config) {
|
|
855
1045
|
return resolveSelectedNode(config, (id) => this.getNodeInfo(id), this.state.root?.id);
|