@tmagic/editor 1.7.9 → 1.7.10
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 +3 -1
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +17 -7
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +38 -9
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +38 -5
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +5 -0
- package/dist/es/fields/StyleSetter/pro/Transform.js +5 -0
- package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +54 -0
- package/dist/es/hooks/use-code-block-edit.js +3 -2
- package/dist/es/index.js +3 -3
- package/dist/es/initService.js +4 -4
- package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +64 -1
- package/dist/es/layouts/workspace/viewer/StageOverlay.vue_vue_type_script_setup_true_lang.js +1 -5
- package/dist/es/services/editor.js +191 -174
- package/dist/es/services/history.js +1 -9
- package/dist/es/type.js +28 -1
- package/dist/es/utils/dep/worker.js +1 -1
- package/dist/es/utils/editor-history.js +100 -0
- package/dist/es/utils/editor.js +178 -4
- package/dist/es/utils/props.js +4 -1
- package/dist/es/utils/undo-redo.js +7 -4
- package/dist/tmagic-editor.umd.cjs +744 -234
- package/package.json +8 -8
- package/src/Editor.vue +1 -0
- package/src/components/CodeBlockEditor.vue +1 -1
- package/src/components/CodeParams.vue +23 -7
- package/src/editorProps.ts +2 -0
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +39 -6
- package/src/fields/DataSourceFieldSelect/Index.vue +1 -0
- package/src/fields/DataSourceMethodSelect.vue +54 -13
- package/src/fields/DataSourceMethods.vue +9 -5
- package/src/fields/StyleSetter/Index.vue +5 -1
- package/src/fields/StyleSetter/pro/Transform.vue +54 -0
- package/src/fields/StyleSetter/pro/index.ts +1 -0
- package/src/hooks/use-code-block-edit.ts +8 -4
- package/src/initService.ts +6 -6
- package/src/layouts/workspace/viewer/Stage.vue +89 -1
- package/src/layouts/workspace/viewer/StageOverlay.vue +1 -6
- package/src/services/editor.ts +231 -270
- package/src/services/history.ts +1 -9
- package/src/type.ts +65 -5
- package/src/utils/editor-history.ts +138 -0
- package/src/utils/editor.ts +249 -4
- package/src/utils/props.ts +4 -0
- package/src/utils/undo-redo.ts +7 -6
- package/types/index.d.ts +179 -53
package/src/services/editor.ts
CHANGED
|
@@ -17,23 +17,13 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { reactive, toRaw } from 'vue';
|
|
20
|
-
import { cloneDeep,
|
|
21
|
-
import type { Writable } from 'type-fest';
|
|
20
|
+
import { cloneDeep, isObject, mergeWith, uniq } from 'lodash-es';
|
|
22
21
|
|
|
23
22
|
import type { Id, MApp, MContainer, MNode, MPage, MPageFragment, TargetOptions } from '@tmagic/core';
|
|
24
|
-
import { NodeType
|
|
23
|
+
import { NodeType } from '@tmagic/core';
|
|
25
24
|
import type { ChangeRecord } from '@tmagic/form';
|
|
26
25
|
import { isFixed } from '@tmagic/stage';
|
|
27
|
-
import {
|
|
28
|
-
calcValueByFontsize,
|
|
29
|
-
getElById,
|
|
30
|
-
getNodeInfo,
|
|
31
|
-
getNodePath,
|
|
32
|
-
isNumber,
|
|
33
|
-
isPage,
|
|
34
|
-
isPageFragment,
|
|
35
|
-
isPop,
|
|
36
|
-
} from '@tmagic/utils';
|
|
26
|
+
import { getNodeInfo, getNodePath, isPage, isPageFragment } from '@tmagic/utils';
|
|
37
27
|
|
|
38
28
|
import BaseService from '@editor/services//BaseService';
|
|
39
29
|
import propsService from '@editor/services//props';
|
|
@@ -42,69 +32,39 @@ import storageService, { Protocol } from '@editor/services/storage';
|
|
|
42
32
|
import type {
|
|
43
33
|
AddMNode,
|
|
44
34
|
AsyncHookPlugin,
|
|
35
|
+
AsyncMethodName,
|
|
36
|
+
EditorEvents,
|
|
45
37
|
EditorNodeInfo,
|
|
38
|
+
HistoryOpType,
|
|
46
39
|
PastePosition,
|
|
47
40
|
StepValue,
|
|
48
41
|
StoreState,
|
|
49
42
|
StoreStateKey,
|
|
50
43
|
} from '@editor/type';
|
|
51
|
-
import { LayerOffset, Layout } from '@editor/type';
|
|
44
|
+
import { canUsePluginMethods, LayerOffset, Layout } from '@editor/type';
|
|
52
45
|
import {
|
|
53
|
-
|
|
46
|
+
calcAlignCenterStyle,
|
|
47
|
+
calcLayerTargetIndex,
|
|
48
|
+
calcMoveStyle,
|
|
49
|
+
classifyDragSources,
|
|
50
|
+
collectRelatedNodes,
|
|
54
51
|
COPY_STORAGE_KEY,
|
|
55
|
-
|
|
52
|
+
editorNodeMergeCustomizer,
|
|
56
53
|
fixNodePosition,
|
|
57
54
|
getInitPositionStyle,
|
|
58
55
|
getNodeIndex,
|
|
59
56
|
getPageFragmentList,
|
|
60
57
|
getPageList,
|
|
61
58
|
moveItemsInContainer,
|
|
59
|
+
resolveSelectedNode,
|
|
62
60
|
setChildrenLayout,
|
|
63
61
|
setLayout,
|
|
62
|
+
toggleFixedPosition,
|
|
64
63
|
} from '@editor/utils/editor';
|
|
64
|
+
import type { HistoryOpContext } from '@editor/utils/editor-history';
|
|
65
|
+
import { applyHistoryAddOp, applyHistoryRemoveOp, applyHistoryUpdateOp } from '@editor/utils/editor-history';
|
|
65
66
|
import { beforePaste, getAddParent } from '@editor/utils/operator';
|
|
66
67
|
|
|
67
|
-
export interface EditorEvents {
|
|
68
|
-
'root-change': [value: StoreState['root'], preValue?: StoreState['root']];
|
|
69
|
-
select: [node: MNode | null];
|
|
70
|
-
add: [nodes: MNode[]];
|
|
71
|
-
remove: [nodes: MNode[]];
|
|
72
|
-
update: [nodes: { newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }[]];
|
|
73
|
-
'move-layer': [offset: number | LayerOffset];
|
|
74
|
-
'drag-to': [data: { targetIndex: number; configs: MNode | MNode[]; targetParent: MContainer }];
|
|
75
|
-
'history-change': [data: MPage | MPageFragment];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const canUsePluginMethods = {
|
|
79
|
-
async: [
|
|
80
|
-
'getLayout',
|
|
81
|
-
'highlight',
|
|
82
|
-
'select',
|
|
83
|
-
'multiSelect',
|
|
84
|
-
'doAdd',
|
|
85
|
-
'add',
|
|
86
|
-
'doRemove',
|
|
87
|
-
'remove',
|
|
88
|
-
'doUpdate',
|
|
89
|
-
'update',
|
|
90
|
-
'sort',
|
|
91
|
-
'copy',
|
|
92
|
-
'paste',
|
|
93
|
-
'doPaste',
|
|
94
|
-
'doAlignCenter',
|
|
95
|
-
'alignCenter',
|
|
96
|
-
'moveLayer',
|
|
97
|
-
'moveToContainer',
|
|
98
|
-
'dragTo',
|
|
99
|
-
'undo',
|
|
100
|
-
'redo',
|
|
101
|
-
'move',
|
|
102
|
-
] as const,
|
|
103
|
-
sync: [],
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
|
|
107
|
-
|
|
108
68
|
class Editor extends BaseService {
|
|
109
69
|
public state: StoreState = reactive({
|
|
110
70
|
root: null,
|
|
@@ -121,6 +81,7 @@ class Editor extends BaseService {
|
|
|
121
81
|
disabledMultiSelect: false,
|
|
122
82
|
});
|
|
123
83
|
private isHistoryStateChange = false;
|
|
84
|
+
private selectionBeforeOp: Id[] | null = null;
|
|
124
85
|
|
|
125
86
|
constructor() {
|
|
126
87
|
super(
|
|
@@ -390,6 +351,8 @@ class Editor extends BaseService {
|
|
|
390
351
|
* @returns 添加后的节点
|
|
391
352
|
*/
|
|
392
353
|
public async add(addNode: AddMNode | MNode[], parent?: MContainer | null): Promise<MNode | MNode[]> {
|
|
354
|
+
this.captureSelectionBeforeOp();
|
|
355
|
+
|
|
393
356
|
const stage = this.get('stage');
|
|
394
357
|
|
|
395
358
|
// 新增多个组件只存在于粘贴多个组件,粘贴的是一个完整的config,所以不再需要getPropsValue
|
|
@@ -435,7 +398,21 @@ class Editor extends BaseService {
|
|
|
435
398
|
}
|
|
436
399
|
|
|
437
400
|
if (!(isPage(newNodes[0]) || isPageFragment(newNodes[0]))) {
|
|
438
|
-
this.
|
|
401
|
+
const pageForOp = this.getNodeInfo(newNodes[0].id, false).page;
|
|
402
|
+
this.pushOpHistory(
|
|
403
|
+
'add',
|
|
404
|
+
{
|
|
405
|
+
nodes: newNodes.map((n) => cloneDeep(toRaw(n))),
|
|
406
|
+
parentId: (this.getParentById(newNodes[0].id, false) ?? this.get('root'))!.id,
|
|
407
|
+
indexMap: Object.fromEntries(
|
|
408
|
+
newNodes.map((n) => {
|
|
409
|
+
const p = this.getParentById(n.id, false) as MContainer;
|
|
410
|
+
return [n.id, p ? getNodeIndex(n.id, p) : -1];
|
|
411
|
+
}),
|
|
412
|
+
),
|
|
413
|
+
},
|
|
414
|
+
{ name: pageForOp?.name || '', id: pageForOp!.id },
|
|
415
|
+
);
|
|
439
416
|
}
|
|
440
417
|
|
|
441
418
|
this.emit('add', newNodes);
|
|
@@ -498,13 +475,33 @@ class Editor extends BaseService {
|
|
|
498
475
|
* @param {Object} node
|
|
499
476
|
*/
|
|
500
477
|
public async remove(nodeOrNodeList: MNode | MNode[]): Promise<void> {
|
|
478
|
+
this.captureSelectionBeforeOp();
|
|
479
|
+
|
|
501
480
|
const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
|
|
502
481
|
|
|
482
|
+
const removedItems: { node: MNode; parentId: Id; index: number }[] = [];
|
|
483
|
+
let pageForOp: { name: string; id: Id } | null = null;
|
|
484
|
+
if (!(isPage(nodes[0]) || isPageFragment(nodes[0]))) {
|
|
485
|
+
for (const n of nodes) {
|
|
486
|
+
const { parent, node: curNode, page } = this.getNodeInfo(n.id, false);
|
|
487
|
+
if (parent && curNode) {
|
|
488
|
+
if (!pageForOp && page) {
|
|
489
|
+
pageForOp = { name: page.name || '', id: page.id };
|
|
490
|
+
}
|
|
491
|
+
const idx = getNodeIndex(curNode.id, parent);
|
|
492
|
+
removedItems.push({
|
|
493
|
+
node: cloneDeep(toRaw(curNode)),
|
|
494
|
+
parentId: parent.id,
|
|
495
|
+
index: typeof idx === 'number' ? idx : -1,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
503
501
|
await Promise.all(nodes.map((node) => this.doRemove(node)));
|
|
504
502
|
|
|
505
|
-
if (
|
|
506
|
-
|
|
507
|
-
this.pushHistoryState();
|
|
503
|
+
if (removedItems.length > 0 && pageForOp) {
|
|
504
|
+
this.pushOpHistory('remove', { removedItems }, pageForOp);
|
|
508
505
|
}
|
|
509
506
|
|
|
510
507
|
this.emit('remove', nodes);
|
|
@@ -525,21 +522,9 @@ class Editor extends BaseService {
|
|
|
525
522
|
|
|
526
523
|
const node = toRaw(info.node);
|
|
527
524
|
|
|
528
|
-
let newConfig = await
|
|
525
|
+
let newConfig = await toggleFixedPosition(toRaw(config), node, root, this.getLayout);
|
|
529
526
|
|
|
530
|
-
newConfig = mergeWith(cloneDeep(node), newConfig,
|
|
531
|
-
if (typeof srcValue === 'undefined' && Object.hasOwn(source, key)) {
|
|
532
|
-
return '';
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (isObject(srcValue) && Array.isArray(objValue)) {
|
|
536
|
-
// 原来的配置是数组,新的配置是对象,则直接使用新的值
|
|
537
|
-
return srcValue;
|
|
538
|
-
}
|
|
539
|
-
if (Array.isArray(srcValue)) {
|
|
540
|
-
return srcValue;
|
|
541
|
-
}
|
|
542
|
-
});
|
|
527
|
+
newConfig = mergeWith(cloneDeep(node), newConfig, editorNodeMergeCustomizer);
|
|
543
528
|
|
|
544
529
|
if (!newConfig.type) throw new Error('配置缺少type值');
|
|
545
530
|
|
|
@@ -597,12 +582,28 @@ class Editor extends BaseService {
|
|
|
597
582
|
config: MNode | MNode[],
|
|
598
583
|
data: { changeRecords?: ChangeRecord[] } = {},
|
|
599
584
|
): Promise<MNode | MNode[]> {
|
|
585
|
+
this.captureSelectionBeforeOp();
|
|
586
|
+
|
|
600
587
|
const nodes = Array.isArray(config) ? config : [config];
|
|
601
588
|
|
|
602
589
|
const updateData = await Promise.all(nodes.map((node) => this.doUpdate(node, data)));
|
|
603
590
|
|
|
604
591
|
if (updateData[0].oldNode?.type !== NodeType.ROOT) {
|
|
605
|
-
this.
|
|
592
|
+
const curNodes = this.get('nodes');
|
|
593
|
+
if (!this.isHistoryStateChange && curNodes.length) {
|
|
594
|
+
const pageForOp = this.getNodeInfo(nodes[0].id, false).page;
|
|
595
|
+
this.pushOpHistory(
|
|
596
|
+
'update',
|
|
597
|
+
{
|
|
598
|
+
updatedItems: updateData.map((d) => ({
|
|
599
|
+
oldNode: cloneDeep(d.oldNode),
|
|
600
|
+
newNode: cloneDeep(toRaw(d.newNode)),
|
|
601
|
+
})),
|
|
602
|
+
},
|
|
603
|
+
{ name: pageForOp?.name || '', id: pageForOp!.id },
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
this.isHistoryStateChange = false;
|
|
606
607
|
}
|
|
607
608
|
|
|
608
609
|
this.emit('update', updateData);
|
|
@@ -616,6 +617,8 @@ class Editor extends BaseService {
|
|
|
616
617
|
* @returns void
|
|
617
618
|
*/
|
|
618
619
|
public async sort(id1: Id, id2: Id): Promise<void> {
|
|
620
|
+
this.captureSelectionBeforeOp();
|
|
621
|
+
|
|
619
622
|
const root = this.get('root');
|
|
620
623
|
if (!root) throw new Error('root为空');
|
|
621
624
|
|
|
@@ -640,9 +643,6 @@ class Editor extends BaseService {
|
|
|
640
643
|
parentId: parent.id,
|
|
641
644
|
root: cloneDeep(root),
|
|
642
645
|
});
|
|
643
|
-
|
|
644
|
-
this.addModifiedNodeId(parent.id);
|
|
645
|
-
this.pushHistoryState();
|
|
646
646
|
}
|
|
647
647
|
|
|
648
648
|
/**
|
|
@@ -664,31 +664,8 @@ class Editor extends BaseService {
|
|
|
664
664
|
public copyWithRelated(config: MNode | MNode[], collectorOptions?: TargetOptions): void {
|
|
665
665
|
const copyNodes: MNode[] = Array.isArray(config) ? config : [config];
|
|
666
666
|
|
|
667
|
-
// 初始化复制组件相关的依赖收集器
|
|
668
667
|
if (collectorOptions && typeof collectorOptions.isTarget === 'function') {
|
|
669
|
-
|
|
670
|
-
...collectorOptions,
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
const coperWatcher = new Watcher();
|
|
674
|
-
|
|
675
|
-
coperWatcher.addTarget(customTarget);
|
|
676
|
-
|
|
677
|
-
coperWatcher.collect(copyNodes, {}, true, collectorOptions.type);
|
|
678
|
-
Object.keys(customTarget.deps).forEach((nodeId: Id) => {
|
|
679
|
-
const node = this.getNodeById(nodeId);
|
|
680
|
-
if (!node) return;
|
|
681
|
-
customTarget!.deps[nodeId].keys.forEach((key) => {
|
|
682
|
-
const relateNodeId = get(node, key);
|
|
683
|
-
const isExist = copyNodes.find((node) => node.id === relateNodeId);
|
|
684
|
-
if (!isExist) {
|
|
685
|
-
const relateNode = this.getNodeById(relateNodeId);
|
|
686
|
-
if (relateNode) {
|
|
687
|
-
copyNodes.push(relateNode);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
});
|
|
691
|
-
});
|
|
668
|
+
collectRelatedNodes(copyNodes, collectorOptions, (id) => this.getNodeById(id));
|
|
692
669
|
}
|
|
693
670
|
|
|
694
671
|
storageService.setItem(COPY_STORAGE_KEY, copyNodes, {
|
|
@@ -733,32 +710,16 @@ class Editor extends BaseService {
|
|
|
733
710
|
|
|
734
711
|
public async doAlignCenter(config: MNode): Promise<MNode> {
|
|
735
712
|
const parent = this.getParentById(config.id);
|
|
736
|
-
|
|
737
713
|
if (!parent) throw new Error('找不到父节点');
|
|
738
714
|
|
|
739
715
|
const node = cloneDeep(toRaw(config));
|
|
740
716
|
const layout = await this.getLayout(parent, node);
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
if (!node.style) return config;
|
|
717
|
+
const doc = this.get('stage')?.renderer?.contentWindow?.document;
|
|
718
|
+
const newStyle = calcAlignCenterStyle(node, parent, layout, doc);
|
|
746
719
|
|
|
747
|
-
|
|
748
|
-
const doc = stage?.renderer?.contentWindow?.document;
|
|
749
|
-
|
|
750
|
-
if (doc) {
|
|
751
|
-
const el = getElById()(doc, node.id);
|
|
752
|
-
const parentEl = layout === Layout.FIXED ? doc.body : el?.offsetParent;
|
|
753
|
-
if (parentEl && el) {
|
|
754
|
-
node.style.left = calcValueByFontsize(doc, (parentEl.clientWidth - el.clientWidth) / 2);
|
|
755
|
-
node.style.right = '';
|
|
756
|
-
}
|
|
757
|
-
} else if (parent.style && isNumber(parent.style?.width) && isNumber(node.style?.width)) {
|
|
758
|
-
node.style.left = (parent.style.width - node.style.width) / 2;
|
|
759
|
-
node.style.right = '';
|
|
760
|
-
}
|
|
720
|
+
if (!newStyle) return config;
|
|
761
721
|
|
|
722
|
+
node.style = newStyle;
|
|
762
723
|
return node;
|
|
763
724
|
}
|
|
764
725
|
|
|
@@ -789,6 +750,8 @@ class Editor extends BaseService {
|
|
|
789
750
|
* @param offset 偏移量
|
|
790
751
|
*/
|
|
791
752
|
public async moveLayer(offset: number | LayerOffset): Promise<void> {
|
|
753
|
+
this.captureSelectionBeforeOp();
|
|
754
|
+
|
|
792
755
|
const root = this.get('root');
|
|
793
756
|
if (!root) throw new Error('root为空');
|
|
794
757
|
|
|
@@ -801,22 +764,16 @@ class Editor extends BaseService {
|
|
|
801
764
|
const brothers: MNode[] = parent.items || [];
|
|
802
765
|
const index = brothers.findIndex((item) => `${item.id}` === `${node?.id}`);
|
|
803
766
|
|
|
804
|
-
// 流式布局与绝对定位布局操作的相反的
|
|
805
767
|
const layout = await this.getLayout(parent, node);
|
|
806
768
|
const isRelative = layout === Layout.RELATIVE;
|
|
807
|
-
|
|
808
|
-
let offsetIndex: number;
|
|
809
|
-
if (offset === LayerOffset.TOP) {
|
|
810
|
-
offsetIndex = isRelative ? 0 : brothers.length;
|
|
811
|
-
} else if (offset === LayerOffset.BOTTOM) {
|
|
812
|
-
offsetIndex = isRelative ? brothers.length : 0;
|
|
813
|
-
} else {
|
|
814
|
-
offsetIndex = index + (isRelative ? -offset : offset);
|
|
815
|
-
}
|
|
769
|
+
const offsetIndex = calcLayerTargetIndex(index, offset, brothers.length, isRelative);
|
|
816
770
|
|
|
817
771
|
if ((offsetIndex > 0 && offsetIndex > brothers.length) || offsetIndex < 0) {
|
|
818
772
|
return;
|
|
819
773
|
}
|
|
774
|
+
|
|
775
|
+
const oldParent = cloneDeep(toRaw(parent));
|
|
776
|
+
|
|
820
777
|
brothers.splice(index, 1);
|
|
821
778
|
brothers.splice(offsetIndex, 0, node);
|
|
822
779
|
|
|
@@ -829,7 +786,14 @@ class Editor extends BaseService {
|
|
|
829
786
|
});
|
|
830
787
|
|
|
831
788
|
this.addModifiedNodeId(parent.id);
|
|
832
|
-
this.
|
|
789
|
+
const pageForOp = this.getNodeInfo(node.id, false).page;
|
|
790
|
+
this.pushOpHistory(
|
|
791
|
+
'update',
|
|
792
|
+
{
|
|
793
|
+
updatedItems: [{ oldNode: oldParent, newNode: cloneDeep(toRaw(parent)) }],
|
|
794
|
+
},
|
|
795
|
+
{ name: pageForOp?.name || '', id: pageForOp!.id },
|
|
796
|
+
);
|
|
833
797
|
|
|
834
798
|
this.emit('move-layer', offset);
|
|
835
799
|
}
|
|
@@ -840,12 +804,17 @@ class Editor extends BaseService {
|
|
|
840
804
|
* @param targetId 容器ID
|
|
841
805
|
*/
|
|
842
806
|
public async moveToContainer(config: MNode, targetId: Id): Promise<MNode | undefined> {
|
|
807
|
+
this.captureSelectionBeforeOp();
|
|
808
|
+
|
|
843
809
|
const root = this.get('root');
|
|
844
|
-
const { node, parent } = this.getNodeInfo(config.id, false);
|
|
810
|
+
const { node, parent, page: pageForOp } = this.getNodeInfo(config.id, false);
|
|
845
811
|
const target = this.getNodeById(targetId, false) as MContainer;
|
|
846
812
|
|
|
847
813
|
const stage = this.get('stage');
|
|
848
814
|
if (root && node && parent && stage) {
|
|
815
|
+
const oldSourceParent = cloneDeep(toRaw(parent));
|
|
816
|
+
const oldTarget = cloneDeep(toRaw(target));
|
|
817
|
+
|
|
849
818
|
const index = getNodeIndex(node.id, parent);
|
|
850
819
|
parent.items?.splice(index, 1);
|
|
851
820
|
|
|
@@ -876,62 +845,60 @@ class Editor extends BaseService {
|
|
|
876
845
|
|
|
877
846
|
this.addModifiedNodeId(target.id);
|
|
878
847
|
this.addModifiedNodeId(parent.id);
|
|
879
|
-
this.
|
|
848
|
+
this.pushOpHistory(
|
|
849
|
+
'update',
|
|
850
|
+
{
|
|
851
|
+
updatedItems: [
|
|
852
|
+
{ oldNode: oldSourceParent, newNode: cloneDeep(toRaw(parent)) },
|
|
853
|
+
{ oldNode: oldTarget, newNode: cloneDeep(toRaw(target)) },
|
|
854
|
+
],
|
|
855
|
+
},
|
|
856
|
+
{ name: pageForOp?.name || '', id: pageForOp!.id },
|
|
857
|
+
);
|
|
880
858
|
|
|
881
859
|
return newConfig;
|
|
882
860
|
}
|
|
883
861
|
}
|
|
884
862
|
|
|
885
863
|
public async dragTo(config: MNode | MNode[], targetParent: MContainer, targetIndex: number) {
|
|
864
|
+
this.captureSelectionBeforeOp();
|
|
865
|
+
|
|
886
866
|
if (!targetParent || !Array.isArray(targetParent.items)) return;
|
|
887
867
|
|
|
888
868
|
const configs = Array.isArray(config) ? config : [config];
|
|
889
869
|
|
|
890
|
-
const
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
896
|
-
forConfigs: for (const config of configs) {
|
|
897
|
-
const { parent, node: curNode } = this.getNodeInfo(config.id, false);
|
|
898
|
-
if (!parent || !curNode) {
|
|
899
|
-
continue;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
const path = getNodePath(curNode.id, parent.items);
|
|
903
|
-
|
|
904
|
-
for (const node of path) {
|
|
905
|
-
if (targetParent.id === node.id) {
|
|
906
|
-
continue forConfigs;
|
|
907
|
-
}
|
|
870
|
+
const beforeSnapshots = new Map<string, MNode>();
|
|
871
|
+
for (const cfg of configs) {
|
|
872
|
+
const { parent } = this.getNodeInfo(cfg.id, false);
|
|
873
|
+
if (parent && !beforeSnapshots.has(`${parent.id}`)) {
|
|
874
|
+
beforeSnapshots.set(`${parent.id}`, cloneDeep(toRaw(parent)));
|
|
908
875
|
}
|
|
876
|
+
}
|
|
877
|
+
if (!beforeSnapshots.has(`${targetParent.id}`)) {
|
|
878
|
+
beforeSnapshots.set(`${targetParent.id}`, cloneDeep(toRaw(targetParent)));
|
|
879
|
+
}
|
|
909
880
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}
|
|
916
|
-
sourceIndicesInTargetParent.push(index);
|
|
917
|
-
} else {
|
|
918
|
-
const layout = await this.getLayout(parent);
|
|
919
|
-
|
|
920
|
-
if (newLayout !== layout) {
|
|
921
|
-
setLayout(config, newLayout);
|
|
922
|
-
}
|
|
881
|
+
const newLayout = await this.getLayout(targetParent);
|
|
882
|
+
const { sameParentIndices, crossParentConfigs, aborted } = classifyDragSources(configs, targetParent, (id, raw) =>
|
|
883
|
+
this.getNodeInfo(id, raw),
|
|
884
|
+
);
|
|
885
|
+
if (aborted) return;
|
|
923
886
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
887
|
+
for (const { config: crossConfig, parent } of crossParentConfigs) {
|
|
888
|
+
const layout = await this.getLayout(parent);
|
|
889
|
+
if (newLayout !== layout) {
|
|
890
|
+
setLayout(crossConfig, newLayout);
|
|
927
891
|
}
|
|
892
|
+
const index = getNodeIndex(crossConfig.id, parent);
|
|
893
|
+
parent.items?.splice(index, 1);
|
|
894
|
+
this.addModifiedNodeId(parent.id);
|
|
928
895
|
}
|
|
929
896
|
|
|
930
|
-
moveItemsInContainer(
|
|
897
|
+
moveItemsInContainer(sameParentIndices, targetParent, targetIndex);
|
|
931
898
|
|
|
932
|
-
|
|
933
|
-
targetParent.items?.splice(targetIndex + index, 0,
|
|
934
|
-
this.addModifiedNodeId(
|
|
899
|
+
crossParentConfigs.forEach(({ config: crossConfig }, index) => {
|
|
900
|
+
targetParent.items?.splice(targetIndex + index, 0, crossConfig);
|
|
901
|
+
this.addModifiedNodeId(crossConfig.id);
|
|
935
902
|
});
|
|
936
903
|
|
|
937
904
|
const page = this.get('page');
|
|
@@ -946,28 +913,40 @@ class Editor extends BaseService {
|
|
|
946
913
|
});
|
|
947
914
|
}
|
|
948
915
|
|
|
949
|
-
|
|
916
|
+
const updatedItems: { oldNode: MNode; newNode: MNode }[] = [];
|
|
917
|
+
for (const oldNode of beforeSnapshots.values()) {
|
|
918
|
+
const newNode = this.getNodeById(oldNode.id, false);
|
|
919
|
+
if (newNode) {
|
|
920
|
+
updatedItems.push({ oldNode, newNode: cloneDeep(toRaw(newNode)) });
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const pageForOp = this.getNodeInfo(configs[0].id, false).page;
|
|
924
|
+
this.pushOpHistory('update', { updatedItems }, { name: pageForOp?.name || '', id: pageForOp!.id });
|
|
950
925
|
|
|
951
926
|
this.emit('drag-to', { targetIndex, configs, targetParent });
|
|
952
927
|
}
|
|
953
928
|
|
|
954
929
|
/**
|
|
955
930
|
* 撤销当前操作
|
|
956
|
-
* @returns
|
|
931
|
+
* @returns 被撤销的操作
|
|
957
932
|
*/
|
|
958
933
|
public async undo(): Promise<StepValue | null> {
|
|
959
934
|
const value = historyService.undo();
|
|
960
|
-
|
|
935
|
+
if (value) {
|
|
936
|
+
await this.applyHistoryOp(value, true);
|
|
937
|
+
}
|
|
961
938
|
return value;
|
|
962
939
|
}
|
|
963
940
|
|
|
964
941
|
/**
|
|
965
942
|
* 恢复到下一步
|
|
966
|
-
* @returns
|
|
943
|
+
* @returns 被恢复的操作
|
|
967
944
|
*/
|
|
968
945
|
public async redo(): Promise<StepValue | null> {
|
|
969
946
|
const value = historyService.redo();
|
|
970
|
-
|
|
947
|
+
if (value) {
|
|
948
|
+
await this.applyHistoryOp(value, false);
|
|
949
|
+
}
|
|
971
950
|
return value;
|
|
972
951
|
}
|
|
973
952
|
|
|
@@ -975,47 +954,10 @@ class Editor extends BaseService {
|
|
|
975
954
|
const node = toRaw(this.get('node'));
|
|
976
955
|
if (!node || isPage(node)) return;
|
|
977
956
|
|
|
978
|
-
const
|
|
979
|
-
if (!
|
|
980
|
-
|
|
981
|
-
const update = (style: { [key: string]: any }) =>
|
|
982
|
-
this.update({
|
|
983
|
-
id,
|
|
984
|
-
type,
|
|
985
|
-
style,
|
|
986
|
-
});
|
|
957
|
+
const newStyle = calcMoveStyle(node.style || {}, left, top);
|
|
958
|
+
if (!newStyle) return;
|
|
987
959
|
|
|
988
|
-
|
|
989
|
-
if (isNumber(style.top)) {
|
|
990
|
-
update({
|
|
991
|
-
...style,
|
|
992
|
-
top: Number(style.top) + Number(top),
|
|
993
|
-
bottom: '',
|
|
994
|
-
});
|
|
995
|
-
} else if (isNumber(style.bottom)) {
|
|
996
|
-
update({
|
|
997
|
-
...style,
|
|
998
|
-
bottom: Number(style.bottom) - Number(top),
|
|
999
|
-
top: '',
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
if (left) {
|
|
1005
|
-
if (isNumber(style.left)) {
|
|
1006
|
-
update({
|
|
1007
|
-
...style,
|
|
1008
|
-
left: Number(style.left) + Number(left),
|
|
1009
|
-
right: '',
|
|
1010
|
-
});
|
|
1011
|
-
} else if (isNumber(style.right)) {
|
|
1012
|
-
update({
|
|
1013
|
-
...style,
|
|
1014
|
-
right: Number(style.right) - Number(left),
|
|
1015
|
-
left: '',
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
960
|
+
await this.update({ id: node.id, type: node.type, style: newStyle });
|
|
1019
961
|
}
|
|
1020
962
|
|
|
1021
963
|
public resetState() {
|
|
@@ -1068,70 +1010,89 @@ class Editor extends BaseService {
|
|
|
1068
1010
|
}
|
|
1069
1011
|
}
|
|
1070
1012
|
|
|
1071
|
-
private
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1013
|
+
private captureSelectionBeforeOp() {
|
|
1014
|
+
if (this.isHistoryStateChange || this.selectionBeforeOp) return;
|
|
1015
|
+
this.selectionBeforeOp = this.get('nodes').map((n) => n.id);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
private pushOpHistory(opType: HistoryOpType, extra: Partial<StepValue>, pageData: { name: string; id: Id }) {
|
|
1019
|
+
if (this.isHistoryStateChange) {
|
|
1020
|
+
this.selectionBeforeOp = null;
|
|
1021
|
+
return;
|
|
1080
1022
|
}
|
|
1023
|
+
|
|
1024
|
+
const step: StepValue = {
|
|
1025
|
+
data: pageData,
|
|
1026
|
+
opType,
|
|
1027
|
+
selectedBefore: this.selectionBeforeOp ?? [],
|
|
1028
|
+
selectedAfter: this.get('nodes').map((n) => n.id),
|
|
1029
|
+
modifiedNodeIds: new Map(this.get('modifiedNodeIds')),
|
|
1030
|
+
...extra,
|
|
1031
|
+
};
|
|
1032
|
+
historyService.push(step);
|
|
1033
|
+
this.selectionBeforeOp = null;
|
|
1081
1034
|
this.isHistoryStateChange = false;
|
|
1082
1035
|
}
|
|
1083
1036
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1037
|
+
/**
|
|
1038
|
+
* 应用历史操作(撤销 / 重做)
|
|
1039
|
+
* @param step 操作记录
|
|
1040
|
+
* @param reverse true = 撤销,false = 重做
|
|
1041
|
+
*/
|
|
1042
|
+
private async applyHistoryOp(step: StepValue, reverse: boolean) {
|
|
1087
1043
|
this.isHistoryStateChange = true;
|
|
1088
|
-
await this.update(value.data);
|
|
1089
|
-
this.set('modifiedNodeIds', value.modifiedNodeIds);
|
|
1090
|
-
setTimeout(() => {
|
|
1091
|
-
if (!value.nodeId) return;
|
|
1092
|
-
this.select(value.nodeId).then(() => {
|
|
1093
|
-
this.get('stage')?.select(value.nodeId);
|
|
1094
|
-
});
|
|
1095
|
-
}, 0);
|
|
1096
|
-
this.emit('history-change', value.data);
|
|
1097
|
-
}
|
|
1098
1044
|
|
|
1099
|
-
|
|
1100
|
-
const
|
|
1045
|
+
const root = this.get('root');
|
|
1046
|
+
const stage = this.get('stage');
|
|
1047
|
+
if (!root) return;
|
|
1101
1048
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1049
|
+
const ctx: HistoryOpContext = {
|
|
1050
|
+
root,
|
|
1051
|
+
stage,
|
|
1052
|
+
getNodeById: (id, raw) => this.getNodeById(id, raw),
|
|
1053
|
+
getNodeInfo: (id, raw) => this.getNodeInfo(id, raw),
|
|
1054
|
+
setRoot: (r) => this.set('root', r),
|
|
1055
|
+
setPage: (p) => this.set('page', p),
|
|
1056
|
+
getPage: () => this.get('page'),
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
switch (step.opType) {
|
|
1060
|
+
case 'add':
|
|
1061
|
+
await applyHistoryAddOp(step, reverse, ctx);
|
|
1062
|
+
break;
|
|
1063
|
+
case 'remove':
|
|
1064
|
+
await applyHistoryRemoveOp(step, reverse, ctx);
|
|
1065
|
+
break;
|
|
1066
|
+
case 'update':
|
|
1067
|
+
await applyHistoryUpdateOp(step, reverse, ctx);
|
|
1068
|
+
break;
|
|
1108
1069
|
}
|
|
1109
1070
|
|
|
1110
|
-
|
|
1111
|
-
}
|
|
1071
|
+
this.set('modifiedNodeIds', step.modifiedNodeIds);
|
|
1112
1072
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1073
|
+
const page = toRaw(this.get('page'));
|
|
1074
|
+
if (page) {
|
|
1075
|
+
const selectIds = reverse ? step.selectedBefore : step.selectedAfter;
|
|
1076
|
+
setTimeout(() => {
|
|
1077
|
+
if (!selectIds.length) return;
|
|
1078
|
+
|
|
1079
|
+
if (selectIds.length > 1) {
|
|
1080
|
+
this.multiSelect(selectIds);
|
|
1081
|
+
stage?.multiSelect(selectIds);
|
|
1082
|
+
} else {
|
|
1083
|
+
this.select(selectIds[0])
|
|
1084
|
+
.then(() => stage?.select(selectIds[0]))
|
|
1085
|
+
.catch(() => {});
|
|
1086
|
+
}
|
|
1087
|
+
}, 0);
|
|
1088
|
+
this.emit('history-change', page as MPage | MPageFragment);
|
|
1122
1089
|
}
|
|
1123
1090
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1091
|
+
this.isHistoryStateChange = false;
|
|
1092
|
+
}
|
|
1126
1093
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
}
|
|
1130
|
-
return {
|
|
1131
|
-
node,
|
|
1132
|
-
parent,
|
|
1133
|
-
page,
|
|
1134
|
-
};
|
|
1094
|
+
private selectedConfigExceptionHandler(config: MNode | Id): EditorNodeInfo {
|
|
1095
|
+
return resolveSelectedNode(config, (id) => this.getNodeInfo(id), this.state.root?.id);
|
|
1135
1096
|
}
|
|
1136
1097
|
}
|
|
1137
1098
|
|