@tmagic/editor 1.4.8 → 1.4.9

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.
Files changed (143) hide show
  1. package/package.json +11 -11
  2. package/src/Editor.vue +211 -0
  3. package/src/components/CodeBlockEditor.vue +268 -0
  4. package/src/components/CodeParams.vue +60 -0
  5. package/src/components/ContentMenu.vue +193 -0
  6. package/src/components/FloatingBox.vue +178 -0
  7. package/src/components/Icon.vue +27 -0
  8. package/src/components/Resizer.vue +23 -0
  9. package/src/components/ScrollBar.vue +151 -0
  10. package/src/components/ScrollViewer.vue +128 -0
  11. package/src/components/SearchInput.vue +37 -0
  12. package/src/components/SplitView.vue +186 -0
  13. package/src/components/ToolButton.vue +138 -0
  14. package/src/components/Tree.vue +71 -0
  15. package/src/components/TreeNode.vue +150 -0
  16. package/src/editorProps.ts +117 -0
  17. package/src/fields/Code.vue +50 -0
  18. package/src/fields/CodeLink.vue +80 -0
  19. package/src/fields/CodeSelect.vue +132 -0
  20. package/src/fields/CodeSelectCol.vue +139 -0
  21. package/src/fields/DataSourceFieldSelect.vue +149 -0
  22. package/src/fields/DataSourceFields.vue +323 -0
  23. package/src/fields/DataSourceInput.vue +339 -0
  24. package/src/fields/DataSourceMethodSelect.vue +153 -0
  25. package/src/fields/DataSourceMethods.vue +104 -0
  26. package/src/fields/DataSourceMocks.vue +255 -0
  27. package/src/fields/DataSourceSelect.vue +98 -0
  28. package/src/fields/EventSelect.vue +375 -0
  29. package/src/fields/KeyValue.vue +137 -0
  30. package/src/fields/PageFragmentSelect.vue +63 -0
  31. package/src/fields/UISelect.vue +135 -0
  32. package/src/hooks/index.ts +24 -0
  33. package/src/hooks/use-code-block-edit.ts +83 -0
  34. package/src/hooks/use-data-source-edit.ts +46 -0
  35. package/src/hooks/use-data-source-method.ts +100 -0
  36. package/src/hooks/use-editor-content-height.ts +26 -0
  37. package/src/hooks/use-filter.ts +55 -0
  38. package/src/hooks/use-float-box.ts +76 -0
  39. package/src/hooks/use-getso.ts +35 -0
  40. package/src/hooks/use-next-float-box-position.ts +29 -0
  41. package/src/hooks/use-node-status.ts +48 -0
  42. package/src/hooks/use-stage.ts +129 -0
  43. package/src/hooks/use-window-rect.ts +20 -0
  44. package/src/icons/AppManageIcon.vue +15 -0
  45. package/src/icons/CenterIcon.vue +13 -0
  46. package/src/icons/CodeIcon.vue +28 -0
  47. package/src/icons/FolderMinusIcon.vue +22 -0
  48. package/src/icons/PinIcon.vue +25 -0
  49. package/src/icons/PinnedIcon.vue +25 -0
  50. package/src/index.ts +118 -0
  51. package/src/initService.ts +448 -0
  52. package/src/layouts/AddPageBox.vue +55 -0
  53. package/src/layouts/CodeEditor.vue +226 -0
  54. package/src/layouts/Framework.vue +163 -0
  55. package/src/layouts/NavMenu.vue +200 -0
  56. package/src/layouts/PropsPanel.vue +131 -0
  57. package/src/layouts/page-bar/AddButton.vue +48 -0
  58. package/src/layouts/page-bar/PageBar.vue +159 -0
  59. package/src/layouts/page-bar/PageBarScrollContainer.vue +156 -0
  60. package/src/layouts/page-bar/SwitchTypeButton.vue +45 -0
  61. package/src/layouts/sidebar/ComponentListPanel.vue +134 -0
  62. package/src/layouts/sidebar/Sidebar.vue +278 -0
  63. package/src/layouts/sidebar/code-block/CodeBlockList.vue +160 -0
  64. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +70 -0
  65. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +84 -0
  66. package/src/layouts/sidebar/data-source/DataSourceList.vue +172 -0
  67. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +106 -0
  68. package/src/layouts/sidebar/layer/LayerMenu.vue +128 -0
  69. package/src/layouts/sidebar/layer/LayerNodeTool.vue +32 -0
  70. package/src/layouts/sidebar/layer/LayerPanel.vue +119 -0
  71. package/src/layouts/sidebar/layer/use-click.ts +113 -0
  72. package/src/layouts/sidebar/layer/use-drag.ts +167 -0
  73. package/src/layouts/sidebar/layer/use-keybinding.ts +54 -0
  74. package/src/layouts/sidebar/layer/use-node-status.ts +114 -0
  75. package/src/layouts/workspace/Breadcrumb.vue +35 -0
  76. package/src/layouts/workspace/Workspace.vue +46 -0
  77. package/src/layouts/workspace/viewer/NodeListMenu.vue +115 -0
  78. package/src/layouts/workspace/viewer/Stage.vue +246 -0
  79. package/src/layouts/workspace/viewer/StageOverlay.vue +68 -0
  80. package/src/layouts/workspace/viewer/ViewerMenu.vue +145 -0
  81. package/src/services/BaseService.ts +230 -0
  82. package/src/services/codeBlock.ts +327 -0
  83. package/src/services/componentList.ts +58 -0
  84. package/src/services/dataSource.ts +216 -0
  85. package/src/services/dep.ts +152 -0
  86. package/src/services/editor.ts +1135 -0
  87. package/src/services/events.ts +93 -0
  88. package/src/services/history.ts +126 -0
  89. package/src/services/keybinding.ts +220 -0
  90. package/src/services/props.ts +286 -0
  91. package/src/services/stageOverlay.ts +212 -0
  92. package/src/services/storage.ts +155 -0
  93. package/src/services/ui.ts +156 -0
  94. package/src/shims-vue.d.ts +6 -0
  95. package/src/theme/breadcrumb.scss +6 -0
  96. package/src/theme/code-block.scss +6 -0
  97. package/src/theme/code-editor.scss +38 -0
  98. package/src/theme/common/var.scss +14 -0
  99. package/src/theme/component-list-panel.scss +95 -0
  100. package/src/theme/content-menu.scss +76 -0
  101. package/src/theme/data-source-field.scss +12 -0
  102. package/src/theme/data-source-fields.scss +13 -0
  103. package/src/theme/data-source-input.scss +18 -0
  104. package/src/theme/data-source-methods.scss +13 -0
  105. package/src/theme/data-source.scss +28 -0
  106. package/src/theme/event.scss +37 -0
  107. package/src/theme/floating-box.scss +32 -0
  108. package/src/theme/framework.scss +69 -0
  109. package/src/theme/icon.scss +12 -0
  110. package/src/theme/index.scss +10 -0
  111. package/src/theme/key-value.scss +20 -0
  112. package/src/theme/layer-panel.scss +26 -0
  113. package/src/theme/layout.scss +9 -0
  114. package/src/theme/nav-menu.scss +78 -0
  115. package/src/theme/page-bar.scss +89 -0
  116. package/src/theme/page-fragment-select.scss +14 -0
  117. package/src/theme/props-panel.scss +55 -0
  118. package/src/theme/resizer.scss +66 -0
  119. package/src/theme/ruler.scss +38 -0
  120. package/src/theme/search-input.scss +14 -0
  121. package/src/theme/sidebar.scss +79 -0
  122. package/src/theme/stage.scss +76 -0
  123. package/src/theme/theme.scss +27 -0
  124. package/src/theme/tree.scss +89 -0
  125. package/src/theme/workspace.scss +9 -0
  126. package/src/type.ts +749 -0
  127. package/src/utils/compose.ts +52 -0
  128. package/src/utils/config.ts +29 -0
  129. package/src/utils/content-menu.ts +95 -0
  130. package/src/utils/data-source/formConfigs/base.ts +30 -0
  131. package/src/utils/data-source/formConfigs/http.ts +60 -0
  132. package/src/utils/data-source/index.ts +243 -0
  133. package/src/utils/editor.ts +313 -0
  134. package/src/utils/idle-task.ts +72 -0
  135. package/src/utils/index.ts +24 -0
  136. package/src/utils/keybinding-config.ts +125 -0
  137. package/src/utils/logger.ts +47 -0
  138. package/src/utils/monaco-editor.ts +8 -0
  139. package/src/utils/operator.ts +106 -0
  140. package/src/utils/props.ts +504 -0
  141. package/src/utils/scroll-viewer.ts +165 -0
  142. package/src/utils/tree.ts +15 -0
  143. package/src/utils/undo-redo.ts +76 -0
@@ -0,0 +1,167 @@
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?.draggable) 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
+ if (dragState.dragOverNodeId === node.id) {
125
+ return;
126
+ }
127
+
128
+ const targetInfo = services.editorService.getNodeInfo(dragState.dragOverNodeId, false);
129
+ const targetNode = targetInfo.node;
130
+ let targetParent = targetInfo.parent;
131
+
132
+ if (!targetParent || !targetNode) return;
133
+
134
+ let targetIndex = -1;
135
+
136
+ if (Array.isArray(targetNode.items) && dragState.dropType === 'inner') {
137
+ targetIndex = targetNode.items.length;
138
+ targetParent = targetNode as MContainer;
139
+ } else {
140
+ targetIndex = getNodeIndex(dragState.dragOverNodeId, targetParent);
141
+ }
142
+
143
+ if (dragState.dropType === 'after') {
144
+ targetIndex += 1;
145
+ }
146
+
147
+ const selectedNodes = services.editorService.get('nodes');
148
+
149
+ if (selectedNodes.find((n) => `${n.id}` === `${node.id}`)) {
150
+ services.editorService.dragTo(selectedNodes, targetParent, targetIndex);
151
+ } else {
152
+ services.editorService.dragTo([node], targetParent, targetIndex);
153
+ }
154
+ }
155
+
156
+ dragState.dragOverNodeId = '';
157
+ dragState.dropType = '';
158
+ dragState.container = null;
159
+ };
160
+
161
+ return {
162
+ handleDragStart,
163
+ handleDragEnd,
164
+ handleDragLeave,
165
+ handleDragOver,
166
+ };
167
+ };
@@ -0,0 +1,54 @@
1
+ import { type Ref, ref, watchEffect } from 'vue';
2
+
3
+ import Tree from '@editor/components/Tree.vue';
4
+ import type { Services } from '@editor/type';
5
+ import { KeyBindingContainerKey } from '@editor/utils/keybinding-config';
6
+
7
+ export const useKeybinding = (
8
+ services: Services | undefined,
9
+ container: Ref<InstanceType<typeof Tree> | undefined>,
10
+ ) => {
11
+ const keybindingService = services?.keybindingService;
12
+
13
+ // 是否多选
14
+ const isCtrlKeyDown = ref(false);
15
+
16
+ const windowBlurHandler = () => {
17
+ isCtrlKeyDown.value = false;
18
+ };
19
+
20
+ keybindingService?.registerCommand('layer-panel-global-keyup', () => {
21
+ isCtrlKeyDown.value = false;
22
+ });
23
+
24
+ keybindingService?.registerCommand('layer-panel-global-keydown', () => {
25
+ isCtrlKeyDown.value = true;
26
+ });
27
+
28
+ keybindingService?.register([
29
+ {
30
+ command: 'layer-panel-global-keydown',
31
+ keybinding: 'ctrl',
32
+ when: [['global', 'keydown']],
33
+ },
34
+ {
35
+ command: 'layer-panel-global-keyup',
36
+ keybinding: 'ctrl',
37
+ when: [['global', 'keyup']],
38
+ },
39
+ ]);
40
+
41
+ watchEffect(() => {
42
+ if (container.value) {
43
+ globalThis.addEventListener('blur', windowBlurHandler);
44
+ keybindingService?.registerEl(KeyBindingContainerKey.LAYER_PANEL, container.value.$el);
45
+ } else {
46
+ globalThis.removeEventListener('blur', windowBlurHandler);
47
+ keybindingService?.unregisterEl(KeyBindingContainerKey.LAYER_PANEL);
48
+ }
49
+ });
50
+
51
+ return {
52
+ isCtrlKeyDown,
53
+ };
54
+ };
@@ -0,0 +1,114 @@
1
+ import { computed, ref, watch } from 'vue';
2
+
3
+ import type { Id, MNode, MPage, MPageFragment } from '@tmagic/schema';
4
+ import { getNodePath, isPage, isPageFragment } from '@tmagic/utils';
5
+
6
+ import { LayerNodeStatus, Services } from '@editor/type';
7
+ import { traverseNode } from '@editor/utils';
8
+ import { updateStatus } from '@editor/utils/tree';
9
+
10
+ const createPageNodeStatus = (page: MPage | MPageFragment, initialLayerNodeStatus?: Map<Id, LayerNodeStatus>) => {
11
+ const map = new Map<Id, LayerNodeStatus>();
12
+
13
+ map.set(page.id, {
14
+ visible: true,
15
+ expand: true,
16
+ selected: true,
17
+ draggable: false,
18
+ });
19
+
20
+ page.items.forEach((node: MNode) =>
21
+ traverseNode<MNode>(node, (node) => {
22
+ map.set(
23
+ node.id,
24
+ initialLayerNodeStatus?.get(node.id) || {
25
+ visible: true,
26
+ expand: false,
27
+ selected: false,
28
+ draggable: true,
29
+ },
30
+ );
31
+ }),
32
+ );
33
+
34
+ return map;
35
+ };
36
+
37
+ export const useNodeStatus = (services: Services | undefined) => {
38
+ const page = computed(() => services?.editorService.get('page'));
39
+ const nodes = computed(() => services?.editorService.get('nodes') || []);
40
+
41
+ /** 所有页面的节点状态 */
42
+ const nodeStatusMaps = ref(new Map<Id, Map<Id, LayerNodeStatus>>());
43
+
44
+ /** 当前页面的节点状态 */
45
+ const nodeStatusMap = computed(() =>
46
+ page.value ? nodeStatusMaps.value.get(page.value.id) : new Map<Id, LayerNodeStatus>(),
47
+ );
48
+
49
+ // 切换页面或者新增页面,重新生成节点状态
50
+ watch(
51
+ () => page.value?.id,
52
+ (pageId) => {
53
+ if (!pageId) {
54
+ return;
55
+ }
56
+
57
+ // 生成节点状态
58
+ nodeStatusMaps.value.set(pageId, createPageNodeStatus(page.value!, nodeStatusMaps.value.get(pageId)));
59
+ },
60
+ {
61
+ immediate: true,
62
+ },
63
+ );
64
+
65
+ // 选中状态变化,更新节点状态
66
+ watch(
67
+ nodes,
68
+ (nodes) => {
69
+ if (!nodeStatusMap.value) return;
70
+
71
+ for (const [id, status] of nodeStatusMap.value.entries()) {
72
+ status.selected = nodes.some((node) => node.id === id);
73
+ if (status.selected) {
74
+ getNodePath(id, page.value?.items).forEach((node) => {
75
+ updateStatus(nodeStatusMap.value!, node.id, {
76
+ expand: true,
77
+ });
78
+ });
79
+ }
80
+ }
81
+ },
82
+ {
83
+ immediate: true,
84
+ },
85
+ );
86
+
87
+ services?.editorService.on('add', (newNodes: MNode[]) => {
88
+ newNodes.forEach((node) => {
89
+ if (isPage(node) || isPageFragment(node)) return;
90
+
91
+ traverseNode(node, (node: MNode) => {
92
+ nodeStatusMap.value?.set(node.id, {
93
+ visible: true,
94
+ expand: Array.isArray(node.items),
95
+ selected: true,
96
+ draggable: true,
97
+ });
98
+ });
99
+ });
100
+ });
101
+
102
+ services?.editorService.on('remove', (nodes: MNode[]) => {
103
+ nodes.forEach((node) => {
104
+ traverseNode(node, (node: MNode) => {
105
+ nodeStatusMap.value?.delete(node.id);
106
+ });
107
+ });
108
+ });
109
+
110
+ return {
111
+ nodeStatusMaps,
112
+ nodeStatusMap,
113
+ };
114
+ };
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <div v-if="nodes.length === 1" class="m-editor-breadcrumb">
3
+ <template v-for="(item, index) in path" :key="item.id">
4
+ <TMagicButton link :disabled="item.id === node?.id" @click="select(item)">{{ item.name }}</TMagicButton
5
+ ><span v-if="index < path.length - 1">/</span>
6
+ </template>
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { computed, inject } from 'vue';
12
+
13
+ import { TMagicButton } from '@tmagic/design';
14
+ import type { MNode } from '@tmagic/schema';
15
+ import { getNodePath } from '@tmagic/utils';
16
+
17
+ import type { Services } from '@editor/type';
18
+
19
+ defineOptions({
20
+ name: 'MEditorBreadcrumb',
21
+ });
22
+
23
+ const services = inject<Services>('services');
24
+ const editorService = services?.editorService;
25
+
26
+ const node = computed(() => editorService?.get('node'));
27
+ const nodes = computed(() => editorService?.get('nodes') || []);
28
+ const root = computed(() => editorService?.get('root'));
29
+ const path = computed(() => getNodePath(node.value?.id || '', root.value?.items || []));
30
+
31
+ const select = async (node: MNode) => {
32
+ await editorService?.select(node);
33
+ editorService?.get('stage')?.select(node.id);
34
+ };
35
+ </script>
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <div class="m-editor-workspace">
3
+ <Breadcrumb></Breadcrumb>
4
+
5
+ <slot name="stage">
6
+ <MagicStage
7
+ v-if="page"
8
+ :disabled-stage-overlay="disabledStageOverlay"
9
+ :stage-content-menu="stageContentMenu"
10
+ :custom-content-menu="customContentMenu"
11
+ ></MagicStage>
12
+ </slot>
13
+
14
+ <slot name="workspace-content"></slot>
15
+ </div>
16
+ </template>
17
+
18
+ <script lang="ts" setup>
19
+ import { computed, inject } from 'vue';
20
+
21
+ import type { MenuButton, MenuComponent, Services, WorkspaceSlots } from '@editor/type';
22
+
23
+ import MagicStage from './viewer/Stage.vue';
24
+ import Breadcrumb from './Breadcrumb.vue';
25
+
26
+ defineSlots<WorkspaceSlots>();
27
+
28
+ defineOptions({
29
+ name: 'MEditorWorkspace',
30
+ });
31
+
32
+ withDefaults(
33
+ defineProps<{
34
+ stageContentMenu: (MenuButton | MenuComponent)[];
35
+ disabledStageOverlay?: boolean;
36
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
37
+ }>(),
38
+ {
39
+ disabledStageOverlay: false,
40
+ },
41
+ );
42
+
43
+ const services = inject<Services>('services');
44
+
45
+ const page = computed(() => services?.editorService.get('page'));
46
+ </script>
@@ -0,0 +1,115 @@
1
+ <template>
2
+ <TMagicTooltip v-if="page && buttonVisible" content="点击查看当前位置下的组件">
3
+ <div ref="button" class="m-editor-stage-float-button" @click="visible = true">可选组件</div>
4
+ </TMagicTooltip>
5
+
6
+ <FloatingBox
7
+ v-if="page && nodeStatusMap && buttonVisible"
8
+ ref="box"
9
+ v-model:visible="visible"
10
+ title="当前位置下的组件"
11
+ :position="menuPosition"
12
+ >
13
+ <template #body>
14
+ <Tree
15
+ class="m-editor-node-list-menu magic-editor-layer-tree"
16
+ :data="nodeData"
17
+ :node-status-map="nodeStatusMap"
18
+ @node-click="clickHandler"
19
+ ></Tree>
20
+ </template>
21
+ </FloatingBox>
22
+ </template>
23
+
24
+ <script lang="ts" setup>
25
+ import { computed, inject, nextTick, ref, watch } from 'vue';
26
+
27
+ import { TMagicTooltip } from '@tmagic/design';
28
+ import type { MNode } from '@tmagic/schema';
29
+
30
+ import FloatingBox from '@editor/components/FloatingBox.vue';
31
+ import Tree from '@editor/components/Tree.vue';
32
+ import { useFilter } from '@editor/hooks/use-filter';
33
+ import { useNodeStatus } from '@editor/layouts/sidebar/layer/use-node-status';
34
+ import type { Services, TreeNodeData } from '@editor/type';
35
+
36
+ const services = inject<Services>('services');
37
+ const editorService = services?.editorService;
38
+
39
+ const visible = ref(false);
40
+ const buttonVisible = ref(false);
41
+ const button = ref<HTMLDivElement>();
42
+ const box = ref<InstanceType<typeof FloatingBox>>();
43
+
44
+ const stage = computed(() => editorService?.get('stage'));
45
+ const page = computed(() => editorService?.get('page'));
46
+ const nodes = computed(() => editorService?.get('nodes') || []);
47
+ const nodeData = computed<TreeNodeData[]>(() => (!page.value ? [] : [page.value]));
48
+
49
+ const { nodeStatusMap } = useNodeStatus(services);
50
+
51
+ const filterNodeMethod = (value: string, data: MNode): boolean => data.id === value;
52
+
53
+ const { filterTextChangeHandler } = useFilter(nodeData, nodeStatusMap, filterNodeMethod);
54
+
55
+ const unWatch = watch(
56
+ stage,
57
+ (stage) => {
58
+ if (!stage) return;
59
+
60
+ nextTick(() => unWatch());
61
+
62
+ stage.on('select', (el: HTMLElement, event: MouseEvent) => {
63
+ const els = stage.renderer.getElementsFromPoint(event) || [];
64
+ const ids = els.map((el) => el.id).filter((id) => Boolean(id));
65
+
66
+ buttonVisible.value = ids.length > 3;
67
+
68
+ filterTextChangeHandler(ids);
69
+ });
70
+ },
71
+ {
72
+ immediate: true,
73
+ },
74
+ );
75
+
76
+ watch(
77
+ nodes,
78
+ (nodes) => {
79
+ if (!nodeStatusMap.value) return;
80
+
81
+ for (const [id, status] of nodeStatusMap.value.entries()) {
82
+ status.selected = nodes.some((node) => node.id === id);
83
+ }
84
+ },
85
+ {
86
+ immediate: true,
87
+ },
88
+ );
89
+
90
+ const clickHandler = async (event: MouseEvent, data: TreeNodeData) => {
91
+ await editorService?.select(data.id);
92
+ stage.value?.select(data.id);
93
+ };
94
+
95
+ const menuPosition = ref({
96
+ left: 0,
97
+ top: 0,
98
+ });
99
+
100
+ watch(visible, async (visible) => {
101
+ if (!button.value || !visible) {
102
+ return;
103
+ }
104
+
105
+ await nextTick();
106
+
107
+ const rect = button.value.getBoundingClientRect();
108
+ const height = box.value?.target?.clientHeight || 0;
109
+
110
+ menuPosition.value = {
111
+ left: rect.left + rect.width + 5,
112
+ top: rect.top - height / 2 + rect.height / 2,
113
+ };
114
+ });
115
+ </script>