@tmagic/editor 1.3.6 → 1.3.8

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 (82) hide show
  1. package/LICENSE +5432 -0
  2. package/dist/style.css +34 -11
  3. package/dist/tmagic-editor.js +1360 -957
  4. package/dist/tmagic-editor.js.map +1 -1
  5. package/dist/tmagic-editor.umd.cjs +1382 -977
  6. package/dist/tmagic-editor.umd.cjs.map +1 -1
  7. package/package.json +17 -17
  8. package/src/Editor.vue +15 -7
  9. package/src/components/CodeBlockEditor.vue +2 -2
  10. package/src/components/ContentMenu.vue +5 -5
  11. package/src/components/ScrollBar.vue +2 -2
  12. package/src/components/ScrollViewer.vue +2 -2
  13. package/src/components/SplitView.vue +2 -2
  14. package/src/components/Tree.vue +1 -1
  15. package/src/components/TreeNode.vue +3 -3
  16. package/src/editorProps.ts +14 -4
  17. package/src/fields/PageFragmentSelect.vue +63 -0
  18. package/src/hooks/use-getso.ts +2 -2
  19. package/src/hooks/use-stage.ts +18 -1
  20. package/src/index.ts +3 -0
  21. package/src/initService.ts +15 -5
  22. package/src/layouts/AddPageBox.vue +16 -4
  23. package/src/layouts/CodeEditor.vue +2 -2
  24. package/src/layouts/Framework.vue +19 -3
  25. package/src/layouts/PropsPanel.vue +9 -7
  26. package/src/layouts/page-bar/AddButton.vue +48 -0
  27. package/src/layouts/page-bar/PageBar.vue +158 -0
  28. package/src/layouts/{workspace → page-bar}/PageBarScrollContainer.vue +61 -35
  29. package/src/layouts/page-bar/SwitchTypeButton.vue +45 -0
  30. package/src/layouts/sidebar/Sidebar.vue +2 -0
  31. package/src/layouts/sidebar/layer/LayerMenu.vue +39 -27
  32. package/src/layouts/sidebar/layer/LayerPanel.vue +7 -1
  33. package/src/layouts/sidebar/layer/use-click.ts +5 -3
  34. package/src/layouts/sidebar/layer/use-keybinding.ts +8 -8
  35. package/src/layouts/sidebar/layer/use-node-status.ts +3 -3
  36. package/src/layouts/workspace/Workspace.vue +6 -7
  37. package/src/layouts/workspace/viewer/NodeListMenu.vue +2 -2
  38. package/src/layouts/workspace/viewer/Stage.vue +12 -5
  39. package/src/layouts/workspace/viewer/ViewerMenu.vue +93 -80
  40. package/src/services/dataSource.ts +17 -0
  41. package/src/services/dep.ts +4 -0
  42. package/src/services/editor.ts +66 -35
  43. package/src/services/history.ts +6 -2
  44. package/src/services/keybinding.ts +48 -13
  45. package/src/theme/code-block.scss +0 -16
  46. package/src/theme/page-bar.scss +18 -1
  47. package/src/theme/page-fragment-select.scss +14 -0
  48. package/src/theme/theme.scss +1 -0
  49. package/src/theme/tree.scss +6 -0
  50. package/src/type.ts +23 -7
  51. package/src/utils/content-menu.ts +2 -2
  52. package/src/utils/editor.ts +22 -15
  53. package/src/utils/operator.ts +4 -4
  54. package/types/components/ToolButton.vue.d.ts +2 -2
  55. package/types/editorProps.d.ts +11 -2
  56. package/types/fields/PageFragmentSelect.vue.d.ts +31 -0
  57. package/types/index.d.ts +1 -0
  58. package/types/layouts/AddPageBox.vue.d.ts +14 -1
  59. package/types/layouts/Framework.vue.d.ts +14 -1
  60. package/types/layouts/PropsPanel.vue.d.ts +19 -10
  61. package/types/layouts/page-bar/AddButton.vue.d.ts +16 -0
  62. package/types/layouts/page-bar/PageBar.vue.d.ts +28 -0
  63. package/types/layouts/page-bar/PageBarScrollContainer.vue.d.ts +24 -0
  64. package/types/layouts/page-bar/SwitchTypeButton.vue.d.ts +20 -0
  65. package/types/layouts/sidebar/Sidebar.vue.d.ts +2 -0
  66. package/types/layouts/sidebar/layer/LayerMenu.vue.d.ts +22 -3
  67. package/types/layouts/sidebar/layer/LayerPanel.vue.d.ts +2 -0
  68. package/types/layouts/sidebar/layer/use-click.d.ts +25 -3
  69. package/types/layouts/sidebar/layer/use-keybinding.d.ts +1 -1
  70. package/types/layouts/workspace/Workspace.vue.d.ts +2 -0
  71. package/types/layouts/workspace/viewer/Stage.vue.d.ts +2 -0
  72. package/types/layouts/workspace/viewer/ViewerMenu.vue.d.ts +8 -0
  73. package/types/services/dataSource.d.ts +2 -0
  74. package/types/services/dep.d.ts +1 -0
  75. package/types/services/editor.d.ts +3 -2
  76. package/types/services/history.d.ts +3 -2
  77. package/types/services/keybinding.d.ts +21 -1
  78. package/types/type.d.ts +26 -12
  79. package/types/utils/editor.d.ts +9 -7
  80. package/src/layouts/workspace/PageBar.vue +0 -87
  81. package/types/layouts/workspace/PageBar.vue.d.ts +0 -15
  82. package/types/layouts/workspace/PageBarScrollContainer.vue.d.ts +0 -9
@@ -10,14 +10,14 @@
10
10
  :config="curFormConfig"
11
11
  :extend-state="extendState"
12
12
  @change="submit"
13
+ @error="errorHandler"
13
14
  ></MForm>
14
15
  </div>
15
16
  </template>
16
17
 
17
18
  <script lang="ts" setup>
18
- import { computed, getCurrentInstance, inject, onMounted, onUnmounted, ref, watchEffect } from 'vue';
19
+ import { computed, getCurrentInstance, inject, onBeforeUnmount, onMounted, ref, watchEffect } from 'vue';
19
20
 
20
- import { tMagicMessage } from '@tmagic/design';
21
21
  import type { FormState, FormValue } from '@tmagic/form';
22
22
  import { MForm } from '@tmagic/form';
23
23
 
@@ -33,7 +33,7 @@ defineProps<{
33
33
  extendState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
34
34
  }>();
35
35
 
36
- const emit = defineEmits(['mounted']);
36
+ const emit = defineEmits(['mounted', 'submit-error', 'form-error']);
37
37
 
38
38
  const internalInstance = getCurrentInstance();
39
39
  const values = ref<FormValue>({});
@@ -64,7 +64,7 @@ onMounted(() => {
64
64
  emit('mounted', internalInstance);
65
65
  });
66
66
 
67
- onUnmounted(() => {
67
+ onBeforeUnmount(() => {
68
68
  services?.propsService.off('props-configs-change', init);
69
69
  });
70
70
 
@@ -79,11 +79,13 @@ const submit = async () => {
79
79
  const values = await configForm.value?.submitForm();
80
80
  services?.editorService.update(values);
81
81
  } catch (e: any) {
82
- console.error(e);
83
- tMagicMessage.closeAll();
84
- tMagicMessage.error(e.message);
82
+ emit('submit-error', e);
85
83
  }
86
84
  };
87
85
 
86
+ const errorHandler = (e: any) => {
87
+ emit('form-error', e);
88
+ };
89
+
88
90
  defineExpose({ configForm, submit });
89
91
  </script>
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <div
3
+ v-if="showAddPageButton"
4
+ id="m-editor-page-bar-add-icon"
5
+ class="m-editor-page-bar-item m-editor-page-bar-item-icon"
6
+ @click="addPage"
7
+ >
8
+ <Icon :icon="Plus"></Icon>
9
+ </div>
10
+ <div v-else style="width: 21px"></div>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { computed, inject, toRaw } from 'vue';
15
+ import { Plus } from '@element-plus/icons-vue';
16
+
17
+ import { NodeType } from '@tmagic/schema';
18
+
19
+ import Icon from '@editor/components/Icon.vue';
20
+ import type { Services } from '@editor/type';
21
+ import { generatePageNameByApp } from '@editor/utils/editor';
22
+
23
+ defineOptions({
24
+ name: 'MEditorPageBarAddButton',
25
+ });
26
+
27
+ const props = defineProps<{
28
+ type: NodeType.PAGE | NodeType.PAGE_FRAGMENT;
29
+ }>();
30
+
31
+ const services = inject<Services>('services');
32
+ const uiService = services?.uiService;
33
+ const editorService = services?.editorService;
34
+
35
+ const showAddPageButton = computed(() => uiService?.get('showAddPageButton'));
36
+
37
+ const addPage = () => {
38
+ if (!editorService) return;
39
+ const root = toRaw(editorService.get('root'));
40
+ if (!root) throw new Error('root 不能为空');
41
+ const pageConfig = {
42
+ type: props.type,
43
+ name: generatePageNameByApp(root, props.type),
44
+ items: [],
45
+ };
46
+ editorService.add(pageConfig);
47
+ };
48
+ </script>
@@ -0,0 +1,158 @@
1
+ <template>
2
+ <div class="m-editor-page-bar-tabs">
3
+ <SwitchTypeButton v-if="!disabledPageFragment" v-model="active" />
4
+
5
+ <PageBarScrollContainer :type="active">
6
+ <template #prepend>
7
+ <AddButton :type="active"></AddButton>
8
+ </template>
9
+
10
+ <div
11
+ v-for="item in list"
12
+ class="m-editor-page-bar-item"
13
+ :key="item.id"
14
+ :class="{ active: page?.id === item.id }"
15
+ @click="switchPage(item.id)"
16
+ >
17
+ <div class="m-editor-page-bar-title">
18
+ <slot name="page-bar-title" :page="item">
19
+ <span :title="item.name">{{ item.name || item.id }}</span>
20
+ </slot>
21
+ </div>
22
+
23
+ <TMagicPopover popper-class="page-bar-popover" placement="top" :width="160" trigger="hover">
24
+ <div>
25
+ <slot name="page-bar-popover" :page="item">
26
+ <ToolButton
27
+ :data="{
28
+ type: 'button',
29
+ text: '复制',
30
+ icon: DocumentCopy,
31
+ handler: () => copy(item),
32
+ }"
33
+ ></ToolButton>
34
+ <ToolButton
35
+ :data="{
36
+ type: 'button',
37
+ text: '删除',
38
+ icon: Delete,
39
+ handler: () => remove(item),
40
+ }"
41
+ ></ToolButton>
42
+ </slot>
43
+ </div>
44
+ <template #reference>
45
+ <TMagicIcon class="m-editor-page-bar-menu-icon">
46
+ <CaretBottom></CaretBottom>
47
+ </TMagicIcon>
48
+ </template>
49
+ </TMagicPopover>
50
+ </div>
51
+ </PageBarScrollContainer>
52
+ </div>
53
+ </template>
54
+
55
+ <script lang="ts" setup>
56
+ import { computed, inject, ref, watch } from 'vue';
57
+ import { CaretBottom, Delete, DocumentCopy } from '@element-plus/icons-vue';
58
+
59
+ import { TMagicIcon, TMagicPopover } from '@tmagic/design';
60
+ import { Id, type MPage, type MPageFragment, NodeType } from '@tmagic/schema';
61
+ import { isPage, isPageFragment } from '@tmagic/utils';
62
+
63
+ import ToolButton from '@editor/components/ToolButton.vue';
64
+ import type { Services } from '@editor/type';
65
+ import { getPageFragmentList, getPageList } from '@editor/utils';
66
+
67
+ import AddButton from './AddButton.vue';
68
+ import PageBarScrollContainer from './PageBarScrollContainer.vue';
69
+ import SwitchTypeButton from './SwitchTypeButton.vue';
70
+
71
+ defineOptions({
72
+ name: 'MEditorPageBar',
73
+ });
74
+
75
+ defineProps<{
76
+ disabledPageFragment: boolean;
77
+ }>();
78
+
79
+ const active = ref<NodeType.PAGE | NodeType.PAGE_FRAGMENT>(NodeType.PAGE);
80
+
81
+ const services = inject<Services>('services');
82
+ const editorService = services?.editorService;
83
+
84
+ const root = computed(() => editorService?.get('root'));
85
+ const page = computed(() => editorService?.get('page'));
86
+ const pageList = computed(() => getPageList(root.value));
87
+ const pageFragmentList = computed(() => getPageFragmentList(root.value));
88
+
89
+ const list = computed(() => (active.value === NodeType.PAGE ? pageList.value : pageFragmentList.value));
90
+
91
+ const activePage = ref<Id>('');
92
+ const activePageFragment = ref<Id>('');
93
+
94
+ watch(
95
+ page,
96
+ (page) => {
97
+ if (!page) {
98
+ if (active.value === NodeType.PAGE) {
99
+ activePage.value = '';
100
+ }
101
+ if (active.value === NodeType.PAGE_FRAGMENT) {
102
+ activePageFragment.value = '';
103
+ }
104
+ return;
105
+ }
106
+
107
+ if (isPage(page)) {
108
+ activePage.value = page?.id;
109
+ if (active.value !== NodeType.PAGE) {
110
+ active.value = NodeType.PAGE;
111
+ }
112
+ } else if (isPageFragment(page)) {
113
+ activePageFragment.value = page?.id;
114
+ if (active.value !== NodeType.PAGE_FRAGMENT) {
115
+ active.value = NodeType.PAGE_FRAGMENT;
116
+ }
117
+ }
118
+ },
119
+ {
120
+ immediate: true,
121
+ },
122
+ );
123
+
124
+ watch(active, (active) => {
125
+ if (active === NodeType.PAGE) {
126
+ if (!activePage.value) {
127
+ editorService?.selectRoot();
128
+ return;
129
+ }
130
+ switchPage(activePage.value);
131
+ return;
132
+ }
133
+
134
+ if (active === NodeType.PAGE_FRAGMENT) {
135
+ if (!activePageFragment.value) {
136
+ editorService?.selectRoot();
137
+ return;
138
+ }
139
+ switchPage(activePageFragment.value);
140
+ }
141
+ });
142
+
143
+ const switchPage = (id: Id) => {
144
+ editorService?.select(id);
145
+ };
146
+
147
+ const copy = (node: MPage | MPageFragment) => {
148
+ node && editorService?.copy(node);
149
+ editorService?.paste({
150
+ left: 0,
151
+ top: 0,
152
+ });
153
+ };
154
+
155
+ const remove = (node: MPage | MPageFragment) => {
156
+ editorService?.remove(node);
157
+ };
158
+ </script>
@@ -1,25 +1,20 @@
1
1
  <template>
2
2
  <div class="m-editor-page-bar" ref="pageBar">
3
- <div
4
- v-if="showAddPageButton"
5
- id="m-editor-page-bar-add-icon"
6
- class="m-editor-page-bar-item m-editor-page-bar-item-icon"
7
- @click="addPage"
8
- >
9
- <Icon :icon="Plus"></Icon>
10
- </div>
11
- <div v-else style="width: 21px"></div>
3
+ <slot name="prepend"></slot>
4
+
12
5
  <div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('left')">
13
6
  <Icon :icon="ArrowLeftBold"></Icon>
14
7
  </div>
8
+
15
9
  <div
16
- v-if="pageLength"
10
+ v-if="(type === NodeType.PAGE && pageLength) || (type === NodeType.PAGE_FRAGMENT && pageFragmentLength)"
17
11
  class="m-editor-page-bar-items"
18
12
  ref="itemsContainer"
19
13
  :style="`width: ${itemsContainerWidth}px`"
20
14
  >
21
15
  <slot></slot>
22
16
  </div>
17
+
23
18
  <div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('right')">
24
19
  <Icon :icon="ArrowRightBold"></Icon>
25
20
  </div>
@@ -27,19 +22,32 @@
27
22
  </template>
28
23
 
29
24
  <script setup lang="ts">
30
- import { computed, inject, nextTick, onMounted, onUnmounted, ref, toRaw, watch } from 'vue';
31
- import { ArrowLeftBold, ArrowRightBold, Plus } from '@element-plus/icons-vue';
25
+ import {
26
+ computed,
27
+ type ComputedRef,
28
+ inject,
29
+ nextTick,
30
+ onBeforeUnmount,
31
+ onMounted,
32
+ ref,
33
+ watch,
34
+ type WatchStopHandle,
35
+ } from 'vue';
36
+ import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
32
37
 
33
38
  import { NodeType } from '@tmagic/schema';
34
39
 
35
40
  import Icon from '@editor/components/Icon.vue';
36
41
  import type { Services } from '@editor/type';
37
- import { generatePageNameByApp } from '@editor/utils/editor';
38
42
 
39
43
  defineOptions({
40
44
  name: 'MEditorPageBarScrollContainer',
41
45
  });
42
46
 
47
+ const props = defineProps<{
48
+ type: NodeType.PAGE | NodeType.PAGE_FRAGMENT;
49
+ }>();
50
+
43
51
  const services = inject<Services>('services');
44
52
  const editorService = services?.editorService;
45
53
  const uiService = services?.uiService;
@@ -72,7 +80,7 @@ onMounted(() => {
72
80
  pageBar.value && resizeObserver.observe(pageBar.value);
73
81
  });
74
82
 
75
- onUnmounted(() => {
83
+ onBeforeUnmount(() => {
76
84
  resizeObserver.disconnect();
77
85
  });
78
86
 
@@ -104,27 +112,45 @@ const scroll = (type: 'left' | 'right' | 'start' | 'end') => {
104
112
  itemsContainer.value.style.transform = `translate(${translateLeft}px, 0px)`;
105
113
  };
106
114
 
107
- const pageLength = computed(() => editorService?.get('pageLength'));
108
-
109
- watch(pageLength, (length = 0, preLength = 0) => {
110
- setTimeout(() => {
111
- setCanScroll();
112
- if (length < preLength) {
113
- scroll('start');
115
+ const pageLength = computed(() => editorService?.get('pageLength') || 0);
116
+ const pageFragmentLength = computed(() => editorService?.get('pageFragmentLength') || 0);
117
+
118
+ const crateWatchLength = (length: ComputedRef<number>) =>
119
+ watch(
120
+ length,
121
+ (length = 0, preLength = 0) => {
122
+ setTimeout(() => {
123
+ setCanScroll();
124
+ if (length < preLength) {
125
+ scroll('start');
126
+ } else {
127
+ scroll('end');
128
+ }
129
+ });
130
+ },
131
+ {
132
+ immediate: true,
133
+ },
134
+ );
135
+
136
+ let unWatchPageLength: WatchStopHandle | null;
137
+ let unWatchPageFragmentLength: WatchStopHandle | null;
138
+
139
+ watch(
140
+ () => props.type,
141
+ (type) => {
142
+ if (type === NodeType.PAGE) {
143
+ unWatchPageFragmentLength?.();
144
+ unWatchPageFragmentLength = null;
145
+ unWatchPageLength = crateWatchLength(pageLength);
114
146
  } else {
115
- scroll('end');
147
+ unWatchPageLength?.();
148
+ unWatchPageLength = null;
149
+ unWatchPageFragmentLength = crateWatchLength(pageFragmentLength);
116
150
  }
117
- });
118
- });
119
-
120
- const addPage = () => {
121
- if (!editorService) return;
122
- const root = toRaw(editorService.get('root'));
123
- if (!root) throw new Error('root 不能为空');
124
- const pageConfig = {
125
- type: NodeType.PAGE,
126
- name: generatePageNameByApp(root),
127
- };
128
- editorService.add(pageConfig);
129
- };
151
+ },
152
+ {
153
+ immediate: true,
154
+ },
155
+ );
130
156
  </script>
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <TMagicButton
3
+ v-for="item in data"
4
+ class="m-editor-page-bar-switch-type-button"
5
+ size="small"
6
+ :key="item.type"
7
+ text
8
+ :class="{ active: modelValue === item.type }"
9
+ :type="modelValue === item.type ? 'primary' : ''"
10
+ @click="clickHandler(item.type)"
11
+ >{{ item.text }}</TMagicButton
12
+ >
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { TMagicButton } from '@tmagic/design';
17
+ import { NodeType } from '@tmagic/schema';
18
+
19
+ defineOptions({
20
+ name: 'MEditorPageBarSwitchTypeButton',
21
+ });
22
+
23
+ defineProps<{
24
+ modelValue: NodeType.PAGE | NodeType.PAGE_FRAGMENT;
25
+ }>();
26
+
27
+ const data: { type: NodeType.PAGE | NodeType.PAGE_FRAGMENT; text: string }[] = [
28
+ {
29
+ type: NodeType.PAGE,
30
+ text: '页面',
31
+ },
32
+ {
33
+ type: NodeType.PAGE_FRAGMENT,
34
+ text: '页面片',
35
+ },
36
+ ];
37
+
38
+ const emit = defineEmits<{
39
+ 'update:modelValue': [value: NodeType.PAGE | NodeType.PAGE_FRAGMENT];
40
+ }>();
41
+
42
+ const clickHandler = (value: NodeType.PAGE | NodeType.PAGE_FRAGMENT) => {
43
+ emit('update:modelValue', value);
44
+ };
45
+ </script>
@@ -160,6 +160,7 @@ const props = withDefaults(
160
160
  defineProps<{
161
161
  data: SideBarData;
162
162
  layerContentMenu: (MenuButton | MenuComponent)[];
163
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
163
164
  }>(),
164
165
  {
165
166
  data: () => ({ type: 'tabs', status: '组件', items: ['component-list', 'layer', 'code-block', 'data-source'] }),
@@ -187,6 +188,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
187
188
  text: '已选组件',
188
189
  props: {
189
190
  layerContentMenu: props.layerContentMenu,
191
+ customContentMenu: props.customContentMenu,
190
192
  },
191
193
  component: LayerPanel,
192
194
  slots: {},
@@ -6,7 +6,7 @@
6
6
  import { computed, inject, markRaw, ref } from 'vue';
7
7
  import { Files, Plus } from '@element-plus/icons-vue';
8
8
 
9
- import { isPage } from '@tmagic/utils';
9
+ import { isPage, isPageFragment } from '@tmagic/utils';
10
10
 
11
11
  import ContentMenu from '@editor/components/ContentMenu.vue';
12
12
  import FolderMinusIcon from '@editor/icons/FolderMinusIcon.vue';
@@ -17,9 +17,16 @@ defineOptions({
17
17
  name: 'MEditorLayerMenu',
18
18
  });
19
19
 
20
- const props = defineProps<{
21
- layerContentMenu: (MenuButton | MenuComponent)[];
22
- }>();
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
+ );
23
30
 
24
31
  const emit = defineEmits<{
25
32
  'collapse-all': [];
@@ -82,29 +89,34 @@ const getSubMenuData = computed<MenuButton[]>(() => {
82
89
  return [];
83
90
  });
84
91
 
85
- const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
86
- {
87
- type: 'button',
88
- text: '全部折叠',
89
- icon: FolderMinusIcon,
90
- display: () => isPage(node.value),
91
- handler: () => {
92
- emit('collapse-all');
93
- },
94
- },
95
- {
96
- type: 'button',
97
- text: '新增',
98
- icon: markRaw(Plus),
99
- display: () => node.value?.items && nodes.value?.length === 1,
100
- items: getSubMenuData.value,
101
- },
102
- useCopyMenu(),
103
- usePasteMenu(),
104
- useDeleteMenu(),
105
- useMoveToMenu(services),
106
- ...props.layerContentMenu,
107
- ]);
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
+ );
108
120
 
109
121
  const show = (e: MouseEvent) => {
110
122
  menu.value?.show(e);
@@ -34,7 +34,12 @@
34
34
  </Tree>
35
35
 
36
36
  <Teleport to="body">
37
- <LayerMenu ref="menu" :layer-content-menu="layerContentMenu" @collapse-all="collapseAllHandler"></LayerMenu>
37
+ <LayerMenu
38
+ ref="menu"
39
+ :layer-content-menu="layerContentMenu"
40
+ :custom-content-menu="customContentMenu"
41
+ @collapse-all="collapseAllHandler"
42
+ ></LayerMenu>
38
43
  </Teleport>
39
44
  </TMagicScrollbar>
40
45
  </template>
@@ -65,6 +70,7 @@ defineOptions({
65
70
 
66
71
  defineProps<{
67
72
  layerContentMenu: (MenuButton | MenuComponent)[];
73
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
68
74
  }>();
69
75
 
70
76
  const services = inject<Services>('services');
@@ -1,4 +1,4 @@
1
- import { type ComputedRef, nextTick, type Ref, ref } from 'vue';
1
+ import { computed, type ComputedRef, nextTick, type Ref, ref } from 'vue';
2
2
  import { throttle } from 'lodash-es';
3
3
 
4
4
  import { Id, MNode } from '@tmagic/schema';
@@ -13,13 +13,15 @@ export const useClick = (
13
13
  isCtrlKeyDown: Ref<boolean>,
14
14
  nodeStatusMap: ComputedRef<Map<Id, LayerNodeStatus> | undefined>,
15
15
  ) => {
16
+ const isMultiSelect = computed(() => isCtrlKeyDown.value && !services?.editorService.get('disabledMultiSelect'));
17
+
16
18
  // 触发画布选中
17
19
  const select = async (data: MNode) => {
18
20
  if (!data.id) {
19
21
  throw new Error('没有id');
20
22
  }
21
23
 
22
- if (isCtrlKeyDown.value) {
24
+ if (isMultiSelect.value) {
23
25
  multiSelect(data);
24
26
  } else {
25
27
  await services?.editorService.select(data);
@@ -70,7 +72,7 @@ export const useClick = (
70
72
  return;
71
73
  }
72
74
 
73
- if (data.items && data.items.length > 0 && !isCtrlKeyDown.value) {
75
+ if (data.items && data.items.length > 0 && !isMultiSelect.value) {
74
76
  updateStatus(nodeStatusMap.value, data.id, {
75
77
  expand: true,
76
78
  });
@@ -6,7 +6,7 @@ import { KeyBindingContainerKey } from '@editor/utils/keybinding-config';
6
6
 
7
7
  export const useKeybinding = (
8
8
  services: Services | undefined,
9
- contianer: Ref<InstanceType<typeof Tree> | undefined>,
9
+ container: Ref<InstanceType<typeof Tree> | undefined>,
10
10
  ) => {
11
11
  const keybindingService = services?.keybindingService;
12
12
 
@@ -17,17 +17,17 @@ export const useKeybinding = (
17
17
  isCtrlKeyDown.value = false;
18
18
  };
19
19
 
20
- keybindingService?.registeCommand('layer-panel-global-keyup', () => {
20
+ keybindingService?.registerCommand('layer-panel-global-keyup', () => {
21
21
  isCtrlKeyDown.value = false;
22
22
  });
23
23
 
24
- keybindingService?.registeCommand('layer-panel-global-keydwon', () => {
24
+ keybindingService?.registerCommand('layer-panel-global-keydown', () => {
25
25
  isCtrlKeyDown.value = true;
26
26
  });
27
27
 
28
- keybindingService?.registe([
28
+ keybindingService?.register([
29
29
  {
30
- command: 'layer-panel-global-keydwon',
30
+ command: 'layer-panel-global-keydown',
31
31
  keybinding: 'ctrl',
32
32
  when: [['global', 'keydown']],
33
33
  },
@@ -39,12 +39,12 @@ export const useKeybinding = (
39
39
  ]);
40
40
 
41
41
  watchEffect(() => {
42
- if (contianer.value) {
42
+ if (container.value) {
43
43
  globalThis.addEventListener('blur', windowBlurHandler);
44
- keybindingService?.registeEl(KeyBindingContainerKey.LAYER_PANEL, contianer.value.$el);
44
+ keybindingService?.registerEl(KeyBindingContainerKey.LAYER_PANEL, container.value.$el);
45
45
  } else {
46
46
  globalThis.removeEventListener('blur', windowBlurHandler);
47
- keybindingService?.unregisteEl(KeyBindingContainerKey.LAYER_PANEL);
47
+ keybindingService?.unregisterEl(KeyBindingContainerKey.LAYER_PANEL);
48
48
  }
49
49
  });
50
50
 
@@ -1,13 +1,13 @@
1
1
  import { computed, ref, watch } from 'vue';
2
2
 
3
- import type { Id, MNode, MPage } from '@tmagic/schema';
3
+ import type { Id, MNode, MPage, MPageFragment } from '@tmagic/schema';
4
4
  import { getNodePath } from '@tmagic/utils';
5
5
 
6
6
  import { LayerNodeStatus, Services } from '@editor/type';
7
7
  import { traverseNode } from '@editor/utils';
8
8
  import { updateStatus } from '@editor/utils/tree';
9
9
 
10
- const createPageNodeStatus = (page: MPage, initalLayerNodeStatus?: Map<Id, LayerNodeStatus>) => {
10
+ const createPageNodeStatus = (page: MPage | MPageFragment, initialLayerNodeStatus?: Map<Id, LayerNodeStatus>) => {
11
11
  const map = new Map<Id, LayerNodeStatus>();
12
12
 
13
13
  map.set(page.id, {
@@ -21,7 +21,7 @@ const createPageNodeStatus = (page: MPage, initalLayerNodeStatus?: Map<Id, Layer
21
21
  traverseNode<MNode>(node, (node) => {
22
22
  map.set(
23
23
  node.id,
24
- initalLayerNodeStatus?.get(node.id) || {
24
+ initialLayerNodeStatus?.get(node.id) || {
25
25
  visible: true,
26
26
  expand: false,
27
27
  selected: false,