@tmagic/editor 1.3.0-beta.2 → 1.3.0-beta.4
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/style.css +52 -60
- package/dist/tmagic-editor.js +1634 -1065
- package/dist/tmagic-editor.js.map +1 -1
- package/dist/tmagic-editor.umd.cjs +1659 -1087
- package/dist/tmagic-editor.umd.cjs.map +1 -1
- package/package.json +10 -10
- package/src/Editor.vue +70 -65
- package/src/components/ContentMenu.vue +5 -1
- package/src/components/SearchInput.vue +5 -1
- package/src/editorProps.ts +51 -129
- package/src/fields/Code.vue +9 -3
- package/src/fields/DataSourceFieldSelect.vue +28 -16
- package/src/fields/DataSourceFields.vue +95 -7
- package/src/fields/DataSourceMethodSelect.vue +23 -14
- package/src/fields/DataSourceMocks.vue +237 -0
- package/src/fields/EventSelect.vue +18 -11
- package/src/index.ts +4 -1
- package/src/initService.ts +50 -20
- package/src/layouts/CodeEditor.vue +20 -4
- package/src/layouts/Framework.vue +3 -1
- package/src/layouts/PropsPanel.vue +3 -1
- package/src/layouts/sidebar/ComponentListPanel.vue +18 -8
- package/src/layouts/sidebar/Sidebar.vue +7 -10
- package/src/layouts/sidebar/code-block/CodeBlockList.vue +3 -1
- package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +3 -1
- package/src/layouts/sidebar/layer/LayerNode.vue +206 -0
- package/src/layouts/sidebar/layer/LayerNodeTool.vue +32 -0
- package/src/layouts/sidebar/layer/LayerPanel.vue +85 -0
- package/src/layouts/sidebar/layer/use-drag.ts +157 -0
- package/src/layouts/sidebar/layer/use-filter.ts +70 -0
- package/src/layouts/sidebar/layer/use-keybinding.ts +47 -0
- package/src/layouts/sidebar/layer/use-node-status.ts +103 -0
- package/src/layouts/workspace/Workspace.vue +3 -1
- package/src/layouts/workspace/viewer/NodeListMenu.vue +1 -1
- package/src/layouts/workspace/viewer/Stage.vue +19 -17
- package/src/services/dataSource.ts +10 -0
- package/src/services/editor.ts +45 -5
- package/src/theme/common/var.scss +2 -2
- package/src/theme/framework.scss +2 -0
- package/src/theme/layer-panel.scss +63 -63
- package/src/theme/props-panel.scss +8 -0
- package/src/theme/sidebar.scss +1 -5
- package/src/type.ts +60 -0
- package/src/utils/data-source/index.ts +11 -0
- package/src/utils/dep.ts +26 -11
- package/src/utils/editor.ts +14 -3
- package/src/utils/props.ts +3 -0
- package/types/editorProps.d.ts +61 -107
- package/types/fields/Code.vue.d.ts +10 -6
- package/types/fields/DataSourceMocks.vue.d.ts +32 -0
- package/types/index.d.ts +2 -1
- package/types/initService.d.ts +5 -4
- package/types/layouts/CodeEditor.vue.d.ts +5 -0
- package/types/layouts/Framework.vue.d.ts +2 -12
- package/types/layouts/PropsPanel.vue.d.ts +2 -3
- package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +2 -7
- package/types/layouts/sidebar/Sidebar.vue.d.ts +2 -17
- package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +2 -7
- package/types/layouts/sidebar/code-block/CodeBlockListPanel.vue.d.ts +2 -9
- package/types/layouts/sidebar/{LayerMenu.vue.d.ts → layer/LayerMenu.vue.d.ts} +1 -1
- package/types/layouts/sidebar/layer/LayerNode.vue.d.ts +50 -0
- package/types/layouts/sidebar/{LayerNode.vue.d.ts → layer/LayerNodeTool.vue.d.ts} +1 -1
- package/types/layouts/sidebar/{LayerPanel.vue.d.ts → layer/LayerPanel.vue.d.ts} +2 -8
- package/types/layouts/sidebar/layer/use-drag.d.ts +14 -0
- package/types/layouts/sidebar/layer/use-filter.d.ts +8 -0
- package/types/layouts/sidebar/layer/use-keybinding.d.ts +4 -0
- package/types/layouts/sidebar/layer/use-node-status.d.ts +7 -0
- package/types/layouts/workspace/Workspace.vue.d.ts +2 -11
- package/types/layouts/workspace/viewer/Stage.vue.d.ts +1 -1
- package/types/services/dataSource.d.ts +3 -0
- package/types/services/editor.d.ts +1 -0
- package/types/type.d.ts +60 -0
- package/types/utils/dep.d.ts +1 -1
- package/types/utils/editor.d.ts +3 -2
- package/src/layouts/sidebar/LayerNode.vue +0 -40
- package/src/layouts/sidebar/LayerPanel.vue +0 -369
- package/types/Editor.vue.d.ts +0 -233
- /package/src/layouts/sidebar/{LayerMenu.vue → layer/LayerMenu.vue} +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TMagicScrollbar class="magic-editor-layer-panel">
|
|
3
|
+
<slot name="layer-panel-header"></slot>
|
|
4
|
+
|
|
5
|
+
<SearchInput @search="filterTextChangeHandler"></SearchInput>
|
|
6
|
+
|
|
7
|
+
<div class="magic-editor-layer-tree" tabindex="-1" @dragover="handleDragOver">
|
|
8
|
+
<LayerNode
|
|
9
|
+
v-if="page && root"
|
|
10
|
+
:data="page"
|
|
11
|
+
:filter-text="filterText"
|
|
12
|
+
:is-ctrl-key-down="isCtrlKeyDown"
|
|
13
|
+
@node-contextmenu="contextmenu"
|
|
14
|
+
>
|
|
15
|
+
<template #layer-node-content="{ data: nodeData }">
|
|
16
|
+
<slot name="layer-node-content" :data="nodeData"> </slot>
|
|
17
|
+
</template>
|
|
18
|
+
</LayerNode>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<Teleport to="body">
|
|
22
|
+
<LayerMenu ref="menu" :layer-content-menu="layerContentMenu" @collapse-all="collapseAllHandler"></LayerMenu>
|
|
23
|
+
</Teleport>
|
|
24
|
+
</TMagicScrollbar>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { computed, inject, provide, ref } from 'vue';
|
|
29
|
+
|
|
30
|
+
import { TMagicScrollbar } from '@tmagic/design';
|
|
31
|
+
|
|
32
|
+
import SearchInput from '@editor/components/SearchInput.vue';
|
|
33
|
+
import type { LayerPanelSlots, MenuButton, MenuComponent, Services } from '@editor/type';
|
|
34
|
+
|
|
35
|
+
import LayerMenu from './LayerMenu.vue';
|
|
36
|
+
import LayerNode from './LayerNode.vue';
|
|
37
|
+
import { useDrag } from './use-drag';
|
|
38
|
+
import { useFilter } from './use-filter';
|
|
39
|
+
import { useKeybinding } from './use-keybinding';
|
|
40
|
+
import { useNodeStatus } from './use-node-status';
|
|
41
|
+
|
|
42
|
+
defineSlots<LayerPanelSlots>();
|
|
43
|
+
|
|
44
|
+
defineOptions({
|
|
45
|
+
name: 'MEditorLayerPanel',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
defineProps<{
|
|
49
|
+
layerContentMenu: (MenuButton | MenuComponent)[];
|
|
50
|
+
}>();
|
|
51
|
+
|
|
52
|
+
const services = inject<Services>('services');
|
|
53
|
+
const editorService = services?.editorService;
|
|
54
|
+
|
|
55
|
+
const page = computed(() => editorService?.get('page'));
|
|
56
|
+
const root = computed(() => editorService?.get('root'));
|
|
57
|
+
|
|
58
|
+
const { nodeStatusMap } = useNodeStatus(services, page);
|
|
59
|
+
const { isCtrlKeyDown } = useKeybinding(services);
|
|
60
|
+
|
|
61
|
+
provide('nodeStatusMap', nodeStatusMap);
|
|
62
|
+
|
|
63
|
+
const { filterText, filterTextChangeHandler } = useFilter(nodeStatusMap, page);
|
|
64
|
+
|
|
65
|
+
const collapseAllHandler = () => {
|
|
66
|
+
if (!page.value || !nodeStatusMap.value) return;
|
|
67
|
+
const items = nodeStatusMap.value.entries();
|
|
68
|
+
for (const [id, status] of items) {
|
|
69
|
+
if (id === page.value.id) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
status.expand = false;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// 右键菜单
|
|
77
|
+
const menu = ref<InstanceType<typeof LayerMenu>>();
|
|
78
|
+
const contextmenu = (event: MouseEvent): void => {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
|
|
81
|
+
menu.value?.show(event);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const { handleDragOver } = useDrag(services);
|
|
85
|
+
</script>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { Id, MContainer, MNode } from '@tmagic/schema';
|
|
2
|
+
import { addClassName, removeClassName } from '@tmagic/utils';
|
|
3
|
+
|
|
4
|
+
import { DragType, type Services } from '@editor/type';
|
|
5
|
+
import { getNodeIndex } from '@editor/utils';
|
|
6
|
+
|
|
7
|
+
export declare type NodeDropType = 'before' | 'after' | 'inner' | 'none';
|
|
8
|
+
|
|
9
|
+
const dragState: {
|
|
10
|
+
dragOverNodeId: Id;
|
|
11
|
+
dropType: NodeDropType | '';
|
|
12
|
+
container: HTMLElement | null;
|
|
13
|
+
} = {
|
|
14
|
+
dragOverNodeId: '',
|
|
15
|
+
dropType: '',
|
|
16
|
+
container: null,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const getNodeEl = (el: HTMLElement): HTMLElement | void => {
|
|
20
|
+
if (el.dataset.nodeId) {
|
|
21
|
+
return el;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (el.parentElement) {
|
|
25
|
+
return getNodeEl(el.parentElement);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const removeStatusClass = (el: HTMLElement | null) => {
|
|
30
|
+
if (!el) return;
|
|
31
|
+
|
|
32
|
+
['drag-before', 'drag-after', 'drag-inner'].forEach((className) => {
|
|
33
|
+
el.querySelectorAll(`.${className}`).forEach((el) => {
|
|
34
|
+
removeClassName(el, className);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* dragstart/dragleave/dragend 属于源节点
|
|
41
|
+
* dragover 属于目标节点
|
|
42
|
+
* 这些方法并不是同一个dom事件触发的
|
|
43
|
+
*/
|
|
44
|
+
export const useDrag = (services: Services | undefined) => {
|
|
45
|
+
const handleDragStart = (event: DragEvent) => {
|
|
46
|
+
if (!event.dataTransfer || !event.target || !event.currentTarget) return;
|
|
47
|
+
|
|
48
|
+
const targetEl = getNodeEl(event.target as HTMLElement);
|
|
49
|
+
|
|
50
|
+
if (!targetEl || targetEl !== event.currentTarget) return;
|
|
51
|
+
|
|
52
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
event.dataTransfer.setData(
|
|
56
|
+
'text/json',
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
dragType: DragType.LAYER_TREE,
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
} catch {}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleDragOver = (event: DragEvent) => {
|
|
65
|
+
if (!event.target) return;
|
|
66
|
+
|
|
67
|
+
const targetEl = getNodeEl(event.target as HTMLElement);
|
|
68
|
+
if (!targetEl) return;
|
|
69
|
+
|
|
70
|
+
const labelEl = targetEl.children[0];
|
|
71
|
+
if (!labelEl) return;
|
|
72
|
+
|
|
73
|
+
const { top: targetTop, height: targetHeight } = labelEl.getBoundingClientRect();
|
|
74
|
+
|
|
75
|
+
const distance = event.clientY - targetTop;
|
|
76
|
+
const isContainer = targetEl.dataset.isContainer === 'true';
|
|
77
|
+
|
|
78
|
+
if (distance < targetHeight / 3) {
|
|
79
|
+
dragState.dropType = 'before';
|
|
80
|
+
addClassName(labelEl, globalThis.document, 'drag-before');
|
|
81
|
+
removeClassName(labelEl, 'drag-after', 'drag-inner');
|
|
82
|
+
} else if (distance > (targetHeight * 2) / 3) {
|
|
83
|
+
dragState.dropType = 'after';
|
|
84
|
+
addClassName(labelEl, globalThis.document, 'drag-after');
|
|
85
|
+
removeClassName(labelEl, 'drag-before', 'drag-inner');
|
|
86
|
+
} else if (isContainer) {
|
|
87
|
+
dragState.dropType = 'inner';
|
|
88
|
+
addClassName(labelEl, globalThis.document, 'drag-inner');
|
|
89
|
+
removeClassName(labelEl, 'drag-before', 'drag-after');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!dragState.dropType) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
dragState.dragOverNodeId = targetEl.dataset.nodeId || '';
|
|
97
|
+
dragState.container = event.currentTarget as HTMLElement;
|
|
98
|
+
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleDragLeave = (event: DragEvent) => {
|
|
103
|
+
if (!event.target || !event.currentTarget) return;
|
|
104
|
+
|
|
105
|
+
const targetEl = getNodeEl(event.target as HTMLElement);
|
|
106
|
+
|
|
107
|
+
if (!targetEl || targetEl !== event.currentTarget) return;
|
|
108
|
+
|
|
109
|
+
const labelEl = targetEl.children[0];
|
|
110
|
+
|
|
111
|
+
removeClassName(labelEl, 'drag-before', 'drag-after', 'drag-inner');
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const handleDragEnd = (event: DragEvent, node: MNode) => {
|
|
115
|
+
if (!event.target || !event.currentTarget) return;
|
|
116
|
+
|
|
117
|
+
const targetEl = getNodeEl(event.target as HTMLElement);
|
|
118
|
+
|
|
119
|
+
if (!targetEl || targetEl !== event.currentTarget) return;
|
|
120
|
+
|
|
121
|
+
removeStatusClass(dragState.container);
|
|
122
|
+
|
|
123
|
+
if (node && dragState.dragOverNodeId && dragState.dropType && services) {
|
|
124
|
+
const targetInfo = services.editorService.getNodeInfo(dragState.dragOverNodeId, false);
|
|
125
|
+
const targetNode = targetInfo.node;
|
|
126
|
+
let targetParent = targetInfo.parent;
|
|
127
|
+
|
|
128
|
+
if (!targetParent || !targetNode) return;
|
|
129
|
+
|
|
130
|
+
let targetIndex = -1;
|
|
131
|
+
|
|
132
|
+
if (Array.isArray(targetNode.items) && dragState.dropType === 'inner') {
|
|
133
|
+
targetIndex = targetNode.items.length;
|
|
134
|
+
targetParent = targetNode as MContainer;
|
|
135
|
+
} else {
|
|
136
|
+
targetIndex = getNodeIndex(dragState.dragOverNodeId, targetParent);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (dragState.dropType === 'after') {
|
|
140
|
+
targetIndex += 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
services?.editorService.dragTo(node, targetParent, targetIndex);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
dragState.dragOverNodeId = '';
|
|
147
|
+
dragState.dropType = '';
|
|
148
|
+
dragState.container = null;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
handleDragStart,
|
|
153
|
+
handleDragEnd,
|
|
154
|
+
handleDragLeave,
|
|
155
|
+
handleDragOver,
|
|
156
|
+
};
|
|
157
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type ComputedRef, ref } from 'vue';
|
|
2
|
+
|
|
3
|
+
import { Id, MNode, MPage } from '@tmagic/schema';
|
|
4
|
+
import { getKeys } from '@tmagic/utils';
|
|
5
|
+
|
|
6
|
+
import { LayerNodeStatus } from '@editor/type';
|
|
7
|
+
import { traverseNode } from '@editor/utils';
|
|
8
|
+
|
|
9
|
+
export const updateStatus = (nodeStatusMap: Map<Id, LayerNodeStatus>, id: Id, status: Partial<LayerNodeStatus>) => {
|
|
10
|
+
const nodeStatus = nodeStatusMap.get(id);
|
|
11
|
+
|
|
12
|
+
if (!nodeStatus) return;
|
|
13
|
+
getKeys(status).forEach((key) => {
|
|
14
|
+
if (nodeStatus[key] !== undefined && status[key] !== undefined) {
|
|
15
|
+
nodeStatus[key] = Boolean(status[key]);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const useFilter = (
|
|
21
|
+
nodeStatusMap: ComputedRef<Map<Id, LayerNodeStatus> | undefined>,
|
|
22
|
+
page: ComputedRef<MPage | null | undefined>,
|
|
23
|
+
) => {
|
|
24
|
+
// tree方法:对树节点进行筛选时执行的方法
|
|
25
|
+
const filterIsMatch = (value: string, data: MNode): boolean => {
|
|
26
|
+
if (!value) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
let name = '';
|
|
30
|
+
if (data.name) {
|
|
31
|
+
name = data.name;
|
|
32
|
+
} else if (data.items) {
|
|
33
|
+
name = 'container';
|
|
34
|
+
}
|
|
35
|
+
return `${data.id}${name}${data.type}`.includes(value);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const filterNode = (text: string) => (node: MNode, parents: MNode[]) => {
|
|
39
|
+
if (!nodeStatusMap.value) return;
|
|
40
|
+
|
|
41
|
+
const visible = filterIsMatch(text, node);
|
|
42
|
+
if (visible && parents.length) {
|
|
43
|
+
parents.forEach((parent) => {
|
|
44
|
+
updateStatus(nodeStatusMap.value!, parent.id, {
|
|
45
|
+
visible,
|
|
46
|
+
expand: true,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
updateStatus(nodeStatusMap.value, node.id, {
|
|
52
|
+
visible,
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const filter = (text: string) => {
|
|
57
|
+
if (!page.value?.items?.length) return;
|
|
58
|
+
|
|
59
|
+
page.value.items.forEach((node) => {
|
|
60
|
+
traverseNode(node, filterNode(text));
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
filterText: ref(''),
|
|
66
|
+
filterTextChangeHandler(text: string) {
|
|
67
|
+
filter(text);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
|
2
|
+
|
|
3
|
+
import type { Services } from '@editor/type';
|
|
4
|
+
|
|
5
|
+
export const useKeybinding = (services: Services | undefined) => {
|
|
6
|
+
const keybindingService = services?.keybindingService;
|
|
7
|
+
|
|
8
|
+
// 是否多选
|
|
9
|
+
const isCtrlKeyDown = ref(false);
|
|
10
|
+
|
|
11
|
+
const windowBlurHandler = () => {
|
|
12
|
+
isCtrlKeyDown.value = false;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
keybindingService?.registeCommand('layer-panel-global-keyup', () => {
|
|
16
|
+
isCtrlKeyDown.value = false;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
keybindingService?.registeCommand('layer-panel-global-keydwon', () => {
|
|
20
|
+
isCtrlKeyDown.value = true;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
keybindingService?.registe([
|
|
24
|
+
{
|
|
25
|
+
command: 'layer-panel-global-keydwon',
|
|
26
|
+
keybinding: 'ctrl',
|
|
27
|
+
when: [['global', 'keydown']],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
command: 'layer-panel-global-keyup',
|
|
31
|
+
keybinding: 'ctrl',
|
|
32
|
+
when: [['global', 'keyup']],
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
onMounted(() => {
|
|
37
|
+
globalThis.addEventListener('blur', windowBlurHandler);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
onBeforeUnmount(() => {
|
|
41
|
+
globalThis.removeEventListener('blur', windowBlurHandler);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
isCtrlKeyDown,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { computed, type ComputedRef, ref, watch } from 'vue';
|
|
2
|
+
|
|
3
|
+
import type { Id, MNode, MPage } from '@tmagic/schema';
|
|
4
|
+
import { getNodePath } from '@tmagic/utils';
|
|
5
|
+
|
|
6
|
+
import { LayerNodeStatus, Services } from '@editor/type';
|
|
7
|
+
import { traverseNode } from '@editor/utils';
|
|
8
|
+
|
|
9
|
+
import { updateStatus } from './use-filter';
|
|
10
|
+
|
|
11
|
+
const createPageNodeStatus = (services: Services | undefined, pageId: Id) => {
|
|
12
|
+
const map = new Map<Id, LayerNodeStatus>();
|
|
13
|
+
|
|
14
|
+
map.set(pageId, {
|
|
15
|
+
visible: true,
|
|
16
|
+
expand: true,
|
|
17
|
+
selected: true,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
services?.editorService.getNodeById(pageId)?.items.forEach((node: MNode) =>
|
|
21
|
+
traverseNode(node, (node) => {
|
|
22
|
+
map.set(node.id, {
|
|
23
|
+
visible: true,
|
|
24
|
+
expand: false,
|
|
25
|
+
selected: false,
|
|
26
|
+
});
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return map;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const useNodeStatus = (services: Services | undefined, page: ComputedRef<MPage | null | undefined>) => {
|
|
34
|
+
const nodes = computed(() => services?.editorService.get('nodes') || []);
|
|
35
|
+
|
|
36
|
+
/** 所有页面的节点状态 */
|
|
37
|
+
const nodeStatusMaps = ref(new Map<Id, Map<Id, LayerNodeStatus>>());
|
|
38
|
+
|
|
39
|
+
/** 当前页面的节点状态 */
|
|
40
|
+
const nodeStatusMap = computed(() =>
|
|
41
|
+
page.value ? nodeStatusMaps.value.get(page.value.id) : new Map<Id, LayerNodeStatus>(),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// 切换页面,重新生成节点状态
|
|
45
|
+
watch(
|
|
46
|
+
() => page.value?.id,
|
|
47
|
+
(pageId) => {
|
|
48
|
+
// 已经存在,不需要重新生成
|
|
49
|
+
if (!pageId || nodeStatusMaps.value.has(pageId)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 新增页面,生成节点状态
|
|
54
|
+
nodeStatusMaps.value.set(pageId, createPageNodeStatus(services, pageId));
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
immediate: true,
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// 选中状态变化,更新节点状态
|
|
62
|
+
watch(
|
|
63
|
+
nodes,
|
|
64
|
+
(nodes) => {
|
|
65
|
+
if (!nodeStatusMap.value) return;
|
|
66
|
+
|
|
67
|
+
for (const [id, status] of nodeStatusMap.value.entries()) {
|
|
68
|
+
status.selected = nodes.some((node) => node.id === id);
|
|
69
|
+
if (status.selected) {
|
|
70
|
+
getNodePath(id, page.value?.items).forEach((node) => {
|
|
71
|
+
updateStatus(nodeStatusMap.value!, node.id, {
|
|
72
|
+
expand: true,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
immediate: true,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
services?.editorService.on('add', (newNodes: MNode[]) => {
|
|
84
|
+
newNodes.forEach((node) => {
|
|
85
|
+
nodeStatusMap.value?.set(node.id, {
|
|
86
|
+
visible: true,
|
|
87
|
+
expand: Array.isArray(node.items),
|
|
88
|
+
selected: true,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
services?.editorService.on('remove', (nodes: MNode[]) => {
|
|
94
|
+
nodes.forEach((node) => {
|
|
95
|
+
nodeStatusMap.value?.delete(node.id);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
nodeStatusMaps,
|
|
101
|
+
nodeStatusMap,
|
|
102
|
+
};
|
|
103
|
+
};
|
|
@@ -18,12 +18,14 @@
|
|
|
18
18
|
<script lang="ts" setup>
|
|
19
19
|
import { computed, inject } from 'vue';
|
|
20
20
|
|
|
21
|
-
import { MenuButton, MenuComponent, Services } from '@editor/type';
|
|
21
|
+
import type { MenuButton, MenuComponent, Services, WorkspaceSlots } from '@editor/type';
|
|
22
22
|
|
|
23
23
|
import MagicStage from './viewer/Stage.vue';
|
|
24
24
|
import Breadcrumb from './Breadcrumb.vue';
|
|
25
25
|
import PageBar from './PageBar.vue';
|
|
26
26
|
|
|
27
|
+
defineSlots<WorkspaceSlots>();
|
|
28
|
+
|
|
27
29
|
defineOptions({
|
|
28
30
|
name: 'MEditorWorkspace',
|
|
29
31
|
});
|
|
@@ -38,7 +38,7 @@ import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stag
|
|
|
38
38
|
|
|
39
39
|
import ScrollViewer from '@editor/components/ScrollViewer.vue';
|
|
40
40
|
import { useStage } from '@editor/hooks/use-stage';
|
|
41
|
-
import { Layout, MenuButton, MenuComponent, Services, StageOptions } from '@editor/type';
|
|
41
|
+
import { DragType, Layout, type MenuButton, type MenuComponent, type Services, type StageOptions } from '@editor/type';
|
|
42
42
|
import { getConfig } from '@editor/utils/config';
|
|
43
43
|
|
|
44
44
|
import NodeListMenu from './NodeListMenu.vue';
|
|
@@ -142,18 +142,30 @@ onUnmounted(() => {
|
|
|
142
142
|
services?.keybindingService.unregisteEl('stage');
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
+
const parseDSL = getConfig('parseDSL');
|
|
146
|
+
|
|
145
147
|
const contextmenuHandler = (e: MouseEvent) => {
|
|
146
148
|
e.preventDefault();
|
|
147
149
|
menu.value?.show(e);
|
|
148
150
|
};
|
|
149
151
|
|
|
150
152
|
const dragoverHandler = (e: DragEvent) => {
|
|
151
|
-
e.preventDefault();
|
|
152
153
|
if (!e.dataTransfer) return;
|
|
154
|
+
e.preventDefault();
|
|
153
155
|
e.dataTransfer.dropEffect = 'move';
|
|
154
156
|
};
|
|
155
157
|
|
|
156
158
|
const dropHandler = async (e: DragEvent) => {
|
|
159
|
+
if (!e.dataTransfer) return;
|
|
160
|
+
|
|
161
|
+
const data = e.dataTransfer.getData('text/json');
|
|
162
|
+
|
|
163
|
+
if (!data) return;
|
|
164
|
+
|
|
165
|
+
const config = parseDSL(`(${data})`);
|
|
166
|
+
|
|
167
|
+
if (!config || config.dragType !== DragType.COMPONENT_LIST) return;
|
|
168
|
+
|
|
157
169
|
e.preventDefault();
|
|
158
170
|
|
|
159
171
|
const doc = stage?.renderer.contentWindow?.document;
|
|
@@ -164,22 +176,12 @@ const dropHandler = async (e: DragEvent) => {
|
|
|
164
176
|
parent = services?.editorService.getNodeById(parentEl.id, false) as MContainer;
|
|
165
177
|
}
|
|
166
178
|
|
|
167
|
-
if (
|
|
168
|
-
const parseDSL = getConfig('parseDSL');
|
|
169
|
-
|
|
170
|
-
const data = e.dataTransfer.getData('text/json');
|
|
171
|
-
|
|
172
|
-
if (!data) return;
|
|
173
|
-
|
|
174
|
-
const config = parseDSL(`(${data})`);
|
|
175
|
-
|
|
176
|
-
if (!config) return;
|
|
177
|
-
|
|
179
|
+
if (parent && stageContainer.value && stage) {
|
|
178
180
|
const layout = await services?.editorService.getLayout(parent);
|
|
179
181
|
|
|
180
182
|
const containerRect = stageContainer.value.getBoundingClientRect();
|
|
181
183
|
const { scrollTop, scrollLeft } = stage.mask;
|
|
182
|
-
const { style = {} } = config;
|
|
184
|
+
const { style = {} } = config.data;
|
|
183
185
|
|
|
184
186
|
let top = 0;
|
|
185
187
|
let left = 0;
|
|
@@ -197,16 +199,16 @@ const dropHandler = async (e: DragEvent) => {
|
|
|
197
199
|
}
|
|
198
200
|
}
|
|
199
201
|
|
|
200
|
-
config.style = {
|
|
202
|
+
config.data.style = {
|
|
201
203
|
...style,
|
|
202
204
|
position,
|
|
203
205
|
top: top / zoom.value,
|
|
204
206
|
left: left / zoom.value,
|
|
205
207
|
};
|
|
206
208
|
|
|
207
|
-
config.inputEvent = e;
|
|
209
|
+
config.data.inputEvent = e;
|
|
208
210
|
|
|
209
|
-
services?.editorService.add(config, parent);
|
|
211
|
+
services?.editorService.add(config.data, parent);
|
|
210
212
|
}
|
|
211
213
|
};
|
|
212
214
|
</script>
|
|
@@ -18,6 +18,7 @@ interface State {
|
|
|
18
18
|
configs: Record<string, FormConfig>;
|
|
19
19
|
values: Record<string, Partial<DataSourceSchema>>;
|
|
20
20
|
events: Record<string, EventOption[]>;
|
|
21
|
+
methods: Record<string, EventOption[]>;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
type StateKey = keyof State;
|
|
@@ -29,6 +30,7 @@ class DataSource extends BaseService {
|
|
|
29
30
|
configs: {},
|
|
30
31
|
values: {},
|
|
31
32
|
events: {},
|
|
33
|
+
methods: {},
|
|
32
34
|
});
|
|
33
35
|
|
|
34
36
|
public set<K extends StateKey, T extends State[K]>(name: K, value: T) {
|
|
@@ -63,6 +65,14 @@ class DataSource extends BaseService {
|
|
|
63
65
|
this.get('events')[type] = value;
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
public getFormMethod(type = 'base') {
|
|
69
|
+
return this.get('methods')[type] || [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public setFormMethod(type: string, value: EventOption[] = []) {
|
|
73
|
+
this.get('methods')[type] = value;
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
public add(config: DataSourceSchema) {
|
|
67
77
|
const newConfig = {
|
|
68
78
|
...config,
|
package/src/services/editor.ts
CHANGED
|
@@ -80,6 +80,7 @@ class Editor extends BaseService {
|
|
|
80
80
|
'undo',
|
|
81
81
|
'redo',
|
|
82
82
|
'highlight',
|
|
83
|
+
'dragTo',
|
|
83
84
|
],
|
|
84
85
|
// 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用
|
|
85
86
|
['select', 'update', 'moveLayer'],
|
|
@@ -253,7 +254,7 @@ class Editor extends BaseService {
|
|
|
253
254
|
|
|
254
255
|
if (!parent) return node;
|
|
255
256
|
|
|
256
|
-
const index = getNodeIndex(node, parent);
|
|
257
|
+
const index = getNodeIndex(node.id, parent);
|
|
257
258
|
|
|
258
259
|
const nextNode = parent.items[index + 1] || parent.items[0];
|
|
259
260
|
|
|
@@ -270,7 +271,7 @@ class Editor extends BaseService {
|
|
|
270
271
|
if (!page) throw new Error('page不能为空');
|
|
271
272
|
if (!root) throw new Error('root不能为空');
|
|
272
273
|
|
|
273
|
-
const index = getNodeIndex(page, root);
|
|
274
|
+
const index = getNodeIndex(page.id, root);
|
|
274
275
|
|
|
275
276
|
const nextPage = root.items[index + 1] || root.items[0];
|
|
276
277
|
|
|
@@ -418,7 +419,7 @@ class Editor extends BaseService {
|
|
|
418
419
|
|
|
419
420
|
if (!parent || !curNode) throw new Error('找不要删除的节点');
|
|
420
421
|
|
|
421
|
-
const index = getNodeIndex(curNode, parent);
|
|
422
|
+
const index = getNodeIndex(curNode.id, parent);
|
|
422
423
|
|
|
423
424
|
if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
|
|
424
425
|
|
|
@@ -503,7 +504,7 @@ class Editor extends BaseService {
|
|
|
503
504
|
if (!parent) throw new Error('获取不到父级节点');
|
|
504
505
|
|
|
505
506
|
const parentNodeItems = parent.items;
|
|
506
|
-
const index = getNodeIndex(newConfig, parent);
|
|
507
|
+
const index = getNodeIndex(newConfig.id, parent);
|
|
507
508
|
|
|
508
509
|
if (!parentNodeItems || typeof index === 'undefined' || index === -1) throw new Error('更新的节点未找到');
|
|
509
510
|
|
|
@@ -737,7 +738,7 @@ class Editor extends BaseService {
|
|
|
737
738
|
|
|
738
739
|
const stage = this.get('stage');
|
|
739
740
|
if (root && node && parent && stage) {
|
|
740
|
-
const index = getNodeIndex(node, parent);
|
|
741
|
+
const index = getNodeIndex(node.id, parent);
|
|
741
742
|
parent.items?.splice(index, 1);
|
|
742
743
|
|
|
743
744
|
await stage.remove({ id: node.id, parentId: parent.id, root });
|
|
@@ -769,6 +770,45 @@ class Editor extends BaseService {
|
|
|
769
770
|
}
|
|
770
771
|
}
|
|
771
772
|
|
|
773
|
+
public async dragTo(config: MNode, targetParent: MContainer, targetIndex: number) {
|
|
774
|
+
if (!targetParent || !Array.isArray(targetParent.items)) return;
|
|
775
|
+
|
|
776
|
+
const { parent, node: curNode } = this.getNodeInfo(config.id, false);
|
|
777
|
+
if (!parent || !curNode) throw new Error('找不要删除的节点');
|
|
778
|
+
|
|
779
|
+
const index = getNodeIndex(curNode.id, parent);
|
|
780
|
+
|
|
781
|
+
if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
|
|
782
|
+
|
|
783
|
+
if (parent.id === targetParent.id) {
|
|
784
|
+
if (index === targetIndex) return;
|
|
785
|
+
|
|
786
|
+
if (index < targetIndex) {
|
|
787
|
+
targetIndex -= 1;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
parent.items?.splice(index, 1);
|
|
792
|
+
targetParent.items?.splice(targetIndex, 0, config);
|
|
793
|
+
|
|
794
|
+
const page = this.get('page');
|
|
795
|
+
const root = this.get('root');
|
|
796
|
+
const stage = this.get('stage');
|
|
797
|
+
|
|
798
|
+
if (stage && page && root) {
|
|
799
|
+
stage.update({
|
|
800
|
+
config: cloneDeep(page),
|
|
801
|
+
parentId: root.id,
|
|
802
|
+
root: cloneDeep(root),
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
this.addModifiedNodeId(config.id);
|
|
807
|
+
this.addModifiedNodeId(parent.id);
|
|
808
|
+
|
|
809
|
+
this.pushHistoryState();
|
|
810
|
+
}
|
|
811
|
+
|
|
772
812
|
/**
|
|
773
813
|
* 撤销当前操作
|
|
774
814
|
* @returns 上一次数据
|