@tmagic/editor 1.3.0-beta.1 → 1.3.0-beta.3
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 +67 -75
- package/dist/tmagic-editor.js +1667 -1062
- package/dist/tmagic-editor.js.map +1 -1
- package/dist/tmagic-editor.umd.cjs +1692 -1084
- 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/components/ToolButton.vue +1 -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 +30 -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 +16 -9
- package/src/layouts/workspace/viewer/Stage.vue +19 -17
- package/src/layouts/workspace/viewer/ViewerMenu.vue +1 -1
- package/src/services/dataSource.ts +21 -0
- package/src/services/editor.ts +45 -5
- package/src/theme/common/var.scss +2 -2
- package/src/theme/content-menu.scss +22 -20
- 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 +61 -0
- package/src/utils/data-source/index.ts +23 -0
- package/src/utils/dep.ts +26 -11
- package/src/utils/editor.ts +14 -3
- package/src/utils/props.ts +4 -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 +7 -0
- package/types/services/editor.d.ts +1 -0
- package/types/type.d.ts +61 -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
|
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<ContentMenu
|
|
3
3
|
ref="menu"
|
|
4
|
+
class="magic-editor-node-list-menu"
|
|
5
|
+
style="max-width: 280px"
|
|
4
6
|
:menu-data="menuData"
|
|
5
7
|
:active="node?.id"
|
|
6
8
|
:auto-hide="!pinned"
|
|
@@ -9,7 +11,7 @@
|
|
|
9
11
|
<template #title>
|
|
10
12
|
<NodeListMenuTitle v-model:pinned="pinned" @change="dragMenuHandler" @close="closeHandler"></NodeListMenuTitle>
|
|
11
13
|
</template>
|
|
12
|
-
</
|
|
14
|
+
</ContentMenu>
|
|
13
15
|
</template>
|
|
14
16
|
|
|
15
17
|
<script lang="ts" setup>
|
|
@@ -53,6 +55,14 @@ const cancel = () => {
|
|
|
53
55
|
menu.value?.hide();
|
|
54
56
|
};
|
|
55
57
|
|
|
58
|
+
const clearTimeoutLazy = () => {
|
|
59
|
+
globalThis.setTimeout(() => {
|
|
60
|
+
if (timeout) {
|
|
61
|
+
globalThis.clearTimeout(timeout);
|
|
62
|
+
}
|
|
63
|
+
}, 300);
|
|
64
|
+
};
|
|
65
|
+
|
|
56
66
|
const unWatch = watch(
|
|
57
67
|
stage,
|
|
58
68
|
(stage) => {
|
|
@@ -70,7 +80,7 @@ const unWatch = watch(
|
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
timeout = globalThis.setTimeout(() => {
|
|
73
|
-
const els = stage
|
|
83
|
+
const els = stage.renderer.getElementsFromPoint(event) || [];
|
|
74
84
|
|
|
75
85
|
const nodes = getNodes(
|
|
76
86
|
els.map((el) => el.id),
|
|
@@ -90,7 +100,8 @@ const unWatch = watch(
|
|
|
90
100
|
});
|
|
91
101
|
|
|
92
102
|
stage.on('mouseleave', () => {
|
|
93
|
-
|
|
103
|
+
// mouseleave后,大概率还有最后一个mousemove事件,这里延迟清除
|
|
104
|
+
clearTimeoutLazy();
|
|
94
105
|
});
|
|
95
106
|
|
|
96
107
|
unWatch();
|
|
@@ -135,11 +146,7 @@ const menuData = computed<MenuButton[]>(() =>
|
|
|
135
146
|
|
|
136
147
|
const mouseenterHandler = () => {
|
|
137
148
|
// menu的mouseenter后,大概率还有最后一个mousemove事件,这里延迟清除
|
|
138
|
-
|
|
139
|
-
if (timeout) {
|
|
140
|
-
globalThis.clearTimeout(timeout);
|
|
141
|
-
}
|
|
142
|
-
}, 300);
|
|
149
|
+
clearTimeoutLazy();
|
|
143
150
|
};
|
|
144
151
|
|
|
145
152
|
const dragMenuHandler = ({ deltaY, deltaX }: OnDrag) => {
|
|
@@ -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>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { reactive } from 'vue';
|
|
2
2
|
import { cloneDeep } from 'lodash-es';
|
|
3
3
|
|
|
4
|
+
import type { EventOption } from '@tmagic/core';
|
|
4
5
|
import type { FormConfig } from '@tmagic/form';
|
|
5
6
|
import type { DataSourceSchema } from '@tmagic/schema';
|
|
6
7
|
import { guid } from '@tmagic/utils';
|
|
@@ -16,6 +17,8 @@ interface State {
|
|
|
16
17
|
editable: boolean;
|
|
17
18
|
configs: Record<string, FormConfig>;
|
|
18
19
|
values: Record<string, Partial<DataSourceSchema>>;
|
|
20
|
+
events: Record<string, EventOption[]>;
|
|
21
|
+
methods: Record<string, EventOption[]>;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
type StateKey = keyof State;
|
|
@@ -26,6 +29,8 @@ class DataSource extends BaseService {
|
|
|
26
29
|
editable: true,
|
|
27
30
|
configs: {},
|
|
28
31
|
values: {},
|
|
32
|
+
events: {},
|
|
33
|
+
methods: {},
|
|
29
34
|
});
|
|
30
35
|
|
|
31
36
|
public set<K extends StateKey, T extends State[K]>(name: K, value: T) {
|
|
@@ -52,6 +57,22 @@ class DataSource extends BaseService {
|
|
|
52
57
|
this.get('values')[type] = value;
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
public getFormEvent(type = 'base') {
|
|
61
|
+
return this.get('events')[type] || [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public setFormEvent(type: string, value: EventOption[] = []) {
|
|
65
|
+
this.get('events')[type] = value;
|
|
66
|
+
}
|
|
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
|
+
|
|
55
76
|
public add(config: DataSourceSchema) {
|
|
56
77
|
const newConfig = {
|
|
57
78
|
...config,
|