@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,172 @@
1
+ <template>
2
+ <Tree :data="list" :node-status-map="nodeStatusMap" @node-click="clickHandler">
3
+ <template #tree-node-label="{ data }">
4
+ <div
5
+ :class="{
6
+ ds: data.type === 'ds',
7
+ hook: data.type === 'key',
8
+ disabled: data.type === 'key' || data.type === 'ds',
9
+ }"
10
+ >
11
+ {{ data.name }} {{ data.key ? `(${data.key})` : '' }}
12
+ </div>
13
+ </template>
14
+ <template #tree-node-tool="{ data }">
15
+ <TMagicTooltip v-if="data.type === 'ds'" effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
16
+ <Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editHandler(`${data.key}`)"></Icon>
17
+ </TMagicTooltip>
18
+ <TMagicTooltip v-if="data.type === 'ds' && editable" effect="dark" content="删除" placement="bottom">
19
+ <Icon :icon="Close" class="edit-icon" @click.stop="removeHandler(`${data.key}`)"></Icon>
20
+ </TMagicTooltip>
21
+ <slot name="data-source-panel-tool" :data="data"></slot>
22
+ </template>
23
+ </Tree>
24
+ </template>
25
+
26
+ <script lang="ts" setup>
27
+ import { computed, inject } from 'vue';
28
+ import { Close, Edit, View } from '@element-plus/icons-vue';
29
+
30
+ import { DepTargetType } from '@tmagic/dep';
31
+ import { tMagicMessageBox, TMagicTooltip } from '@tmagic/design';
32
+ import { DepData, Id, MNode } from '@tmagic/schema';
33
+
34
+ import Icon from '@editor/components/Icon.vue';
35
+ import Tree from '@editor/components/Tree.vue';
36
+ import { useFilter } from '@editor/hooks/use-filter';
37
+ import { useNodeStatus } from '@editor/hooks/use-node-status';
38
+ import type { DataSourceListSlots, Services } from '@editor/type';
39
+
40
+ defineSlots<DataSourceListSlots>();
41
+
42
+ defineOptions({
43
+ name: 'MEditorDataSourceList',
44
+ });
45
+
46
+ const emit = defineEmits<{
47
+ edit: [id: string];
48
+ remove: [id: string];
49
+ }>();
50
+
51
+ const { depService, editorService, dataSourceService } = inject<Services>('services') || {};
52
+
53
+ const editable = computed(() => dataSourceService?.get('editable') ?? true);
54
+
55
+ const dataSources = computed(() => dataSourceService?.get('dataSources') || []);
56
+
57
+ const dsDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE) || {});
58
+ const dsMethodDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE_METHOD) || {});
59
+ const dsCondDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE_COND) || {});
60
+
61
+ const getKeyTreeConfig = (dep: DepData[string], type?: string, parentKey?: Id) =>
62
+ dep.keys.map((key) => ({
63
+ name: key,
64
+ id: `${parentKey}_${key}`,
65
+ type: 'key',
66
+ isMethod: type === 'method',
67
+ isCond: type === 'cond',
68
+ }));
69
+
70
+ const getNodeTreeConfig = (id: string, dep: DepData[string], type?: string, parentKey?: Id) => ({
71
+ name: dep.name,
72
+ type: 'node',
73
+ id: `${parentKey}_${id}`,
74
+ key: id,
75
+ items: getKeyTreeConfig(dep, type, `${parentKey}_${id}`),
76
+ });
77
+
78
+ /**
79
+ * 生成tree中依赖节点的数据
80
+ * @param items 节点
81
+ * @param deps 依赖
82
+ * @param type 依赖类型
83
+ */
84
+ const mergeChildren = (dsId: Id, pageItems: any[], deps: DepData, type?: string) => {
85
+ Object.entries(deps).forEach(([id, dep]) => {
86
+ // 按页面分类显示
87
+ const page = pageItems.find((page) => page.key === dep.data?.pageId);
88
+
89
+ // 已经生成过的节点
90
+ const nodeItem = page?.items.find((item: any) => item.key === id);
91
+ // 节点存在,则追加依赖的key
92
+ if (nodeItem) {
93
+ nodeItem.items = nodeItem.items.concat(getKeyTreeConfig(dep, type, nodeItem.key));
94
+ } else {
95
+ // 节点不存在,则生成
96
+ page?.items.push(getNodeTreeConfig(id, dep, type, page.id));
97
+ }
98
+ });
99
+ };
100
+
101
+ const list = computed(() =>
102
+ dataSources.value.map((ds) => {
103
+ const dsDeps = dsDep.value[ds.id]?.deps || {};
104
+ const dsMethodDeps = dsMethodDep.value[ds.id]?.deps || {};
105
+ const dsCondDeps = dsCondDep.value[ds.id]?.deps || {};
106
+
107
+ const items =
108
+ editorService?.get('root')?.items.map((page) => ({
109
+ name: page.devconfig?.tabName || page.name,
110
+ type: 'node',
111
+ id: `${ds.id}_${page.id}`,
112
+ key: page.id,
113
+ items: [],
114
+ })) || [];
115
+
116
+ // 数据源依赖分为三种类型:key/node、method、cond,是分开存储,这里将其合并展示
117
+ mergeChildren(ds.id, items, dsDeps);
118
+ mergeChildren(ds.id, items, dsMethodDeps, 'method');
119
+ mergeChildren(ds.id, items, dsCondDeps, 'cond');
120
+
121
+ return {
122
+ id: ds.id,
123
+ key: ds.id,
124
+ name: ds.title,
125
+ type: 'ds',
126
+ // 只有一个页面不显示页面分类
127
+ items: items.length > 1 ? items.filter((page) => page.items.length) : items[0]?.items || [],
128
+ };
129
+ }),
130
+ );
131
+
132
+ const filterNode = (value: string, data: MNode): boolean => {
133
+ if (!value) {
134
+ return true;
135
+ }
136
+ return `${data.name}${data.id}`.toLocaleLowerCase().includes(value.toLocaleLowerCase());
137
+ };
138
+
139
+ const { nodeStatusMap } = useNodeStatus(list);
140
+ const { filterTextChangeHandler } = useFilter(list, nodeStatusMap, filterNode);
141
+
142
+ const editHandler = (id: string) => {
143
+ emit('edit', id);
144
+ };
145
+
146
+ const removeHandler = async (id: string) => {
147
+ await tMagicMessageBox.confirm('确定删除?', '提示', {
148
+ confirmButtonText: '确定',
149
+ cancelButtonText: '取消',
150
+ type: 'warning',
151
+ });
152
+
153
+ emit('remove', id);
154
+ };
155
+
156
+ // 选中组件
157
+ const selectComp = (compId: Id) => {
158
+ const stage = editorService?.get('stage');
159
+ editorService?.select(compId);
160
+ stage?.select(compId);
161
+ };
162
+
163
+ const clickHandler = (event: MouseEvent, data: any) => {
164
+ if (data.type === 'node') {
165
+ selectComp(data.key);
166
+ }
167
+ };
168
+
169
+ defineExpose({
170
+ filter: filterTextChangeHandler,
171
+ });
172
+ </script>
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <TMagicScrollbar class="data-source-list-panel m-editor-layer-panel">
3
+ <div class="search-wrapper">
4
+ <SearchInput @search="filterTextChangeHandler"></SearchInput>
5
+ <TMagicPopover v-if="editable" placement="right">
6
+ <template #reference>
7
+ <TMagicButton type="primary" size="small">新增</TMagicButton>
8
+ </template>
9
+ <div class="data-source-list-panel-add-menu">
10
+ <ToolButton
11
+ v-for="(item, index) in datasourceTypeList"
12
+ :data="{
13
+ type: 'button',
14
+ text: item.text,
15
+ handler: () => {
16
+ addHandler(item.type);
17
+ },
18
+ }"
19
+ :key="index"
20
+ ></ToolButton>
21
+ </div>
22
+ </TMagicPopover>
23
+
24
+ <slot name="data-source-panel-search"></slot>
25
+ </div>
26
+
27
+ <!-- 数据源列表 -->
28
+ <DataSourceList ref="dataSourceList" @edit="editHandler" @remove="removeHandler"></DataSourceList>
29
+ </TMagicScrollbar>
30
+
31
+ <DataSourceConfigPanel
32
+ ref="editDialog"
33
+ :disabled="!editable"
34
+ :values="dataSourceValues"
35
+ :title="dialogTitle"
36
+ @submit="submitDataSourceHandler"
37
+ ></DataSourceConfigPanel>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { computed, inject, ref } from 'vue';
42
+ import { mergeWith } from 'lodash-es';
43
+
44
+ import { TMagicButton, TMagicPopover, TMagicScrollbar } from '@tmagic/design';
45
+
46
+ import SearchInput from '@editor/components/SearchInput.vue';
47
+ import ToolButton from '@editor/components/ToolButton.vue';
48
+ import { useDataSourceEdit } from '@editor/hooks/use-data-source-edit';
49
+ import type { DataSourceListSlots, EventBus, Services } from '@editor/type';
50
+
51
+ import DataSourceConfigPanel from './DataSourceConfigPanel.vue';
52
+ import DataSourceList from './DataSourceList.vue';
53
+
54
+ defineSlots<DataSourceListSlots>();
55
+
56
+ defineOptions({
57
+ name: 'MEditorDataSourceListPanel',
58
+ });
59
+
60
+ const eventBus = inject<EventBus>('eventBus');
61
+ const { dataSourceService } = inject<Services>('services') || {};
62
+
63
+ const { editDialog, dataSourceValues, dialogTitle, editable, editHandler, submitDataSourceHandler } =
64
+ useDataSourceEdit(dataSourceService);
65
+
66
+ const datasourceTypeList = computed(() =>
67
+ [
68
+ { text: '基础', type: 'base' },
69
+ { text: 'HTTP', type: 'http' },
70
+ ].concat(dataSourceService?.get('datasourceTypeList') ?? []),
71
+ );
72
+
73
+ const addHandler = (type: string) => {
74
+ if (!editDialog.value) return;
75
+
76
+ const datasourceType = datasourceTypeList.value.find((item) => item.type === type);
77
+
78
+ dataSourceValues.value = mergeWith(
79
+ { type, title: datasourceType?.text },
80
+ dataSourceService?.getFormValue(type) || {},
81
+ (objValue, srcValue) => {
82
+ if (Array.isArray(srcValue)) {
83
+ return srcValue;
84
+ }
85
+ },
86
+ );
87
+
88
+ dialogTitle.value = `新增${datasourceType?.text || ''}`;
89
+
90
+ editDialog.value.show();
91
+ };
92
+
93
+ const removeHandler = (id: string) => {
94
+ dataSourceService?.remove(id);
95
+ };
96
+
97
+ const dataSourceList = ref<InstanceType<typeof DataSourceList>>();
98
+
99
+ const filterTextChangeHandler = (val: string) => {
100
+ dataSourceList.value?.filter(val);
101
+ };
102
+
103
+ eventBus?.on('edit-data-source', (id: string) => {
104
+ editHandler(id);
105
+ });
106
+ </script>
@@ -0,0 +1,128 @@
1
+ <template>
2
+ <ContentMenu :menu-data="menuData" ref="menu" style="overflow: initial"></ContentMenu>
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ import { computed, inject, markRaw, ref } from 'vue';
7
+ import { Files, Plus } from '@element-plus/icons-vue';
8
+
9
+ import { isPage, isPageFragment } from '@tmagic/utils';
10
+
11
+ import ContentMenu from '@editor/components/ContentMenu.vue';
12
+ import FolderMinusIcon from '@editor/icons/FolderMinusIcon.vue';
13
+ import type { ComponentGroup, MenuButton, MenuComponent, Services } from '@editor/type';
14
+ import { useCopyMenu, useDeleteMenu, useMoveToMenu, usePasteMenu } from '@editor/utils/content-menu';
15
+
16
+ defineOptions({
17
+ name: 'MEditorLayerMenu',
18
+ });
19
+
20
+ const props = withDefaults(
21
+ defineProps<{
22
+ layerContentMenu: (MenuButton | MenuComponent)[];
23
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
24
+ }>(),
25
+ {
26
+ layerContentMenu: () => [],
27
+ customContentMenu: (menus: (MenuButton | MenuComponent)[]) => menus,
28
+ },
29
+ );
30
+
31
+ const emit = defineEmits<{
32
+ 'collapse-all': [];
33
+ }>();
34
+
35
+ const services = inject<Services>('services');
36
+ const menu = ref<InstanceType<typeof ContentMenu>>();
37
+ const node = computed(() => services?.editorService.get('node'));
38
+ const nodes = computed(() => services?.editorService.get('nodes'));
39
+ const componentList = computed(() => services?.componentListService.getList() || []);
40
+
41
+ const createMenuItems = (group: ComponentGroup): MenuButton[] =>
42
+ group.items.map((component) => ({
43
+ text: component.text,
44
+ type: 'button',
45
+ icon: component.icon,
46
+ handler: () => {
47
+ services?.editorService.add({
48
+ name: component.text,
49
+ type: component.type,
50
+ ...(component.data || {}),
51
+ });
52
+ },
53
+ }));
54
+
55
+ const getSubMenuData = computed<MenuButton[]>(() => {
56
+ if (node.value?.type === 'tabs') {
57
+ return [
58
+ {
59
+ text: '标签页',
60
+ type: 'button',
61
+ icon: Files,
62
+ handler: () => {
63
+ services?.editorService.add({
64
+ type: 'tab-pane',
65
+ });
66
+ },
67
+ },
68
+ ];
69
+ }
70
+ if (node.value?.items) {
71
+ return (
72
+ componentList.value.reduce(
73
+ (subMenuData: MenuButton[], group: ComponentGroup, index) =>
74
+ subMenuData.concat(
75
+ createMenuItems(group),
76
+ index < componentList.value.length - 1
77
+ ? [
78
+ {
79
+ type: 'divider',
80
+ direction: 'horizontal',
81
+ },
82
+ ]
83
+ : [],
84
+ ),
85
+ [],
86
+ ) || []
87
+ );
88
+ }
89
+ return [];
90
+ });
91
+
92
+ const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
93
+ props.customContentMenu(
94
+ [
95
+ {
96
+ type: 'button',
97
+ text: '全部折叠',
98
+ icon: FolderMinusIcon,
99
+ display: () => isPage(node.value) || isPageFragment(node.value),
100
+ handler: () => {
101
+ emit('collapse-all');
102
+ },
103
+ },
104
+ {
105
+ type: 'button',
106
+ text: '新增',
107
+ icon: markRaw(Plus),
108
+ display: () => node.value?.items && nodes.value?.length === 1,
109
+ items: getSubMenuData.value,
110
+ },
111
+ useCopyMenu(),
112
+ usePasteMenu(),
113
+ useDeleteMenu(),
114
+ useMoveToMenu(services),
115
+ ...props.layerContentMenu,
116
+ ],
117
+ 'layer',
118
+ ),
119
+ );
120
+
121
+ const show = (e: MouseEvent) => {
122
+ menu.value?.show(e);
123
+ };
124
+
125
+ defineExpose({
126
+ show,
127
+ });
128
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <template v-if="data.type !== 'page'">
3
+ <MIcon v-if="data.visible === false" :icon="Hide" @click.stop="setNodeVisible(true)" title="点击显示"></MIcon>
4
+ <MIcon v-else :icon="View" @click.stop="setNodeVisible(false)" class="node-lock" title="点击隐藏"></MIcon>
5
+ </template>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import { inject } from 'vue';
10
+ import { Hide, View } from '@element-plus/icons-vue';
11
+
12
+ import type { MNode } from '@tmagic/schema';
13
+
14
+ import MIcon from '@editor/components/Icon.vue';
15
+ import { Services } from '@editor/type';
16
+
17
+ const props = defineProps<{
18
+ data: MNode;
19
+ }>();
20
+
21
+ const services = inject<Services>('services');
22
+ const editorService = services?.editorService;
23
+
24
+ const setNodeVisible = (visible: boolean) => {
25
+ if (!editorService) return;
26
+
27
+ editorService.update({
28
+ id: props.data.id,
29
+ visible,
30
+ });
31
+ };
32
+ </script>
@@ -0,0 +1,119 @@
1
+ <template>
2
+ <TMagicScrollbar class="m-editor-layer-panel">
3
+ <slot name="layer-panel-header"></slot>
4
+
5
+ <SearchInput @search="filterTextChangeHandler"></SearchInput>
6
+
7
+ <Tree
8
+ v-if="page && nodeStatusMap"
9
+ tabindex="-1"
10
+ ref="tree"
11
+ :data="nodeData"
12
+ :node-status-map="nodeStatusMap"
13
+ @node-dragover="handleDragOver"
14
+ @node-dragstart="handleDragStart"
15
+ @node-dragleave="handleDragLeave"
16
+ @node-dragend="handleDragEnd"
17
+ @node-contextmenu="nodeContentMenuHandler"
18
+ @node-mouseenter="mouseenterHandler"
19
+ @node-click="nodeClickHandler"
20
+ >
21
+ <template #tree-node-content="{ data: nodeData }">
22
+ <slot name="layer-node-content" :data="nodeData"> </slot>
23
+ </template>
24
+
25
+ <template #tree-node-tool="{ data: nodeData }">
26
+ <slot name="layer-node-tool" :data="nodeData">
27
+ <LayerNodeTool :data="nodeData"></LayerNodeTool>
28
+ </slot>
29
+ </template>
30
+
31
+ <template #tree-node-label="{ data: nodeData }">
32
+ <slot name="layer-node-label" :data="nodeData"></slot>
33
+ </template>
34
+ </Tree>
35
+
36
+ <Teleport to="body">
37
+ <LayerMenu
38
+ ref="menu"
39
+ :layer-content-menu="layerContentMenu"
40
+ :custom-content-menu="customContentMenu"
41
+ @collapse-all="collapseAllHandler"
42
+ ></LayerMenu>
43
+ </Teleport>
44
+ </TMagicScrollbar>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ import { computed, inject, ref } from 'vue';
49
+
50
+ import { TMagicScrollbar } from '@tmagic/design';
51
+ import type { MNode } from '@tmagic/schema';
52
+
53
+ import SearchInput from '@editor/components/SearchInput.vue';
54
+ import Tree from '@editor/components/Tree.vue';
55
+ import { useFilter } from '@editor/hooks/use-filter';
56
+ import { LayerPanelSlots, MenuButton, MenuComponent, Services, TreeNodeData } from '@editor/type';
57
+
58
+ import LayerMenu from './LayerMenu.vue';
59
+ import LayerNodeTool from './LayerNodeTool.vue';
60
+ import { useClick } from './use-click';
61
+ import { useDrag } from './use-drag';
62
+ import { useKeybinding } from './use-keybinding';
63
+ import { useNodeStatus } from './use-node-status';
64
+
65
+ defineSlots<LayerPanelSlots>();
66
+
67
+ defineOptions({
68
+ name: 'MEditorLayerPanel',
69
+ });
70
+
71
+ defineProps<{
72
+ layerContentMenu: (MenuButton | MenuComponent)[];
73
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
74
+ }>();
75
+
76
+ const services = inject<Services>('services');
77
+ const editorService = services?.editorService;
78
+
79
+ const tree = ref<InstanceType<typeof Tree>>();
80
+
81
+ const page = computed(() => editorService?.get('page'));
82
+ const nodeData = computed<TreeNodeData[]>(() => (!page.value ? [] : [page.value]));
83
+
84
+ const { nodeStatusMap } = useNodeStatus(services);
85
+ const { isCtrlKeyDown } = useKeybinding(services, tree);
86
+
87
+ const filterNodeMethod = (v: string, data: MNode): boolean => {
88
+ let name = '';
89
+ if (data.name) {
90
+ name = data.name;
91
+ } else if (data.items) {
92
+ name = 'container';
93
+ }
94
+
95
+ return `${data.id}${name}${data.type}`.includes(v);
96
+ };
97
+
98
+ const { filterTextChangeHandler } = useFilter(nodeData, nodeStatusMap, filterNodeMethod);
99
+
100
+ const collapseAllHandler = () => {
101
+ if (!page.value || !nodeStatusMap.value) return;
102
+ const items = nodeStatusMap.value.entries();
103
+ for (const [id, status] of items) {
104
+ if (id === page.value.id) {
105
+ continue;
106
+ }
107
+ status.expand = false;
108
+ }
109
+ };
110
+
111
+ const { handleDragStart, handleDragEnd, handleDragLeave, handleDragOver } = useDrag(services);
112
+
113
+ const {
114
+ menu,
115
+ nodeClickHandler,
116
+ nodeContentMenuHandler,
117
+ highlightHandler: mouseenterHandler,
118
+ } = useClick(services, isCtrlKeyDown, nodeStatusMap);
119
+ </script>
@@ -0,0 +1,113 @@
1
+ import { computed, type ComputedRef, nextTick, type Ref, ref } from 'vue';
2
+ import { throttle } from 'lodash-es';
3
+
4
+ import { Id, MNode } from '@tmagic/schema';
5
+
6
+ import { LayerNodeStatus, Services, TreeNodeData, UI_SELECT_MODE_EVENT_NAME } from '@editor/type';
7
+ import { updateStatus } from '@editor/utils/tree';
8
+
9
+ import LayerMenu from './LayerMenu.vue';
10
+
11
+ export const useClick = (
12
+ services: Services | undefined,
13
+ isCtrlKeyDown: Ref<boolean>,
14
+ nodeStatusMap: ComputedRef<Map<Id, LayerNodeStatus> | undefined>,
15
+ ) => {
16
+ const isMultiSelect = computed(() => isCtrlKeyDown.value && !services?.editorService.get('disabledMultiSelect'));
17
+
18
+ // 触发画布选中
19
+ const select = async (data: MNode) => {
20
+ if (!data.id) {
21
+ throw new Error('没有id');
22
+ }
23
+
24
+ if (isMultiSelect.value) {
25
+ multiSelect(data);
26
+ } else {
27
+ await services?.editorService.select(data);
28
+ services?.editorService.get('stage')?.select(data.id);
29
+ services?.stageOverlayService.get('stage')?.select(data.id);
30
+ }
31
+ };
32
+
33
+ const multiSelect = async (data: MNode) => {
34
+ const nodes = services?.editorService.get('nodes') || [];
35
+
36
+ const newNodes: Id[] = [];
37
+ let isCancel = false;
38
+ nodes.forEach((node) => {
39
+ if (node.id === data.id) {
40
+ isCancel = true;
41
+ return;
42
+ }
43
+
44
+ newNodes.push(node.id);
45
+ });
46
+
47
+ // 只剩一个不能取消选中
48
+ if (!isCancel || newNodes.length === 0) {
49
+ newNodes.push(data.id);
50
+ }
51
+
52
+ await services?.editorService.multiSelect(newNodes);
53
+ services?.editorService.get('stage')?.multiSelect(newNodes);
54
+ services?.stageOverlayService.get('stage')?.multiSelect(newNodes);
55
+ };
56
+
57
+ const throttleTime = 300;
58
+ // 鼠标在组件树移动触发高亮
59
+ const highlightHandler: (event: MouseEvent, data: TreeNodeData) => void = throttle(
60
+ (event: MouseEvent, data: TreeNodeData) => {
61
+ highlight(data);
62
+ },
63
+ throttleTime,
64
+ );
65
+
66
+ // 触发画布高亮
67
+ const highlight = (data: TreeNodeData) => {
68
+ services?.editorService?.highlight(data);
69
+ services?.editorService?.get('stage')?.highlight(data.id);
70
+ services?.stageOverlayService?.get('stage')?.highlight(data.id);
71
+ };
72
+
73
+ const nodeClickHandler = (event: MouseEvent, data: TreeNodeData): void => {
74
+ if (!nodeStatusMap?.value) return;
75
+
76
+ if (services?.uiService.get('uiSelectMode')) {
77
+ document.dispatchEvent(new CustomEvent(UI_SELECT_MODE_EVENT_NAME, { detail: data }));
78
+ return;
79
+ }
80
+
81
+ if (data.items && data.items.length > 0 && !isMultiSelect.value) {
82
+ updateStatus(nodeStatusMap.value, data.id, {
83
+ expand: true,
84
+ });
85
+ }
86
+
87
+ nextTick(() => {
88
+ select(data);
89
+ });
90
+ };
91
+
92
+ // 右键菜单
93
+ const menu = ref<InstanceType<typeof LayerMenu>>();
94
+
95
+ return {
96
+ menu,
97
+
98
+ nodeClickHandler,
99
+
100
+ nodeContentMenuHandler(event: MouseEvent, data: TreeNodeData): void {
101
+ event.preventDefault();
102
+
103
+ const nodes = services?.editorService.get('nodes') || [];
104
+ if (nodes.length < 2 || !nodes.includes(data)) {
105
+ nodeClickHandler(event, data);
106
+ }
107
+
108
+ menu.value?.show(event);
109
+ },
110
+
111
+ highlightHandler,
112
+ };
113
+ };