@tmagic/editor 1.3.0-alpha.1 → 1.3.0-alpha.11

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 (124) hide show
  1. package/dist/style.css +46 -9
  2. package/dist/tmagic-editor.js +1764 -871
  3. package/dist/tmagic-editor.js.map +1 -1
  4. package/dist/tmagic-editor.umd.cjs +1780 -882
  5. package/dist/tmagic-editor.umd.cjs.map +1 -1
  6. package/package.json +19 -20
  7. package/src/Editor.vue +29 -3
  8. package/src/components/CodeDraftEditor.vue +5 -1
  9. package/src/components/ContentMenu.vue +30 -11
  10. package/src/components/FunctionEditor.vue +12 -5
  11. package/src/components/Icon.vue +5 -1
  12. package/src/components/Resizer.vue +45 -0
  13. package/src/components/ScrollBar.vue +5 -1
  14. package/src/components/ScrollViewer.vue +5 -1
  15. package/src/components/SearchInput.vue +4 -0
  16. package/src/components/{Layout.vue → SplitView.vue} +8 -4
  17. package/src/components/ToolButton.vue +5 -1
  18. package/src/editorProps.ts +5 -1
  19. package/src/fields/Code.vue +5 -1
  20. package/src/fields/CodeLink.vue +9 -3
  21. package/src/fields/CodeSelect.vue +8 -2
  22. package/src/fields/CodeSelectCol.vue +6 -2
  23. package/src/fields/DataSourceFields.vue +5 -1
  24. package/src/fields/DataSourceInput.vue +358 -0
  25. package/src/fields/DataSourceSelect.vue +75 -0
  26. package/src/fields/EventSelect.vue +9 -3
  27. package/src/fields/KeyValue.vue +4 -0
  28. package/src/fields/UISelect.vue +5 -1
  29. package/src/icons/AppManageIcon.vue +5 -1
  30. package/src/icons/CenterIcon.vue +13 -0
  31. package/src/icons/CodeIcon.vue +5 -1
  32. package/src/index.ts +12 -3
  33. package/src/initService.ts +73 -0
  34. package/src/layouts/AddPageBox.vue +5 -1
  35. package/src/layouts/CodeEditor.vue +34 -10
  36. package/src/layouts/Framework.vue +25 -25
  37. package/src/layouts/NavMenu.vue +5 -1
  38. package/src/layouts/PropsPanel.vue +14 -4
  39. package/src/layouts/sidebar/ComponentListPanel.vue +8 -5
  40. package/src/layouts/sidebar/LayerMenu.vue +13 -25
  41. package/src/layouts/sidebar/LayerNode.vue +40 -0
  42. package/src/layouts/sidebar/LayerPanel.vue +70 -27
  43. package/src/layouts/sidebar/Sidebar.vue +11 -7
  44. package/src/layouts/sidebar/code-block/CodeBlockEditor.vue +8 -4
  45. package/src/layouts/sidebar/code-block/CodeBlockList.vue +5 -1
  46. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +4 -0
  47. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +22 -2
  48. package/src/layouts/workspace/Breadcrumb.vue +5 -1
  49. package/src/layouts/workspace/PageBar.vue +5 -1
  50. package/src/layouts/workspace/PageBarScrollContainer.vue +5 -1
  51. package/src/layouts/workspace/Stage.vue +29 -7
  52. package/src/layouts/workspace/ViewerMenu.vue +15 -36
  53. package/src/layouts/workspace/Workspace.vue +9 -134
  54. package/src/services/codeBlock.ts +3 -2
  55. package/src/services/dep.ts +4 -0
  56. package/src/services/keybinding.ts +185 -0
  57. package/src/services/storage.ts +3 -2
  58. package/src/theme/content-menu.scss +1 -1
  59. package/src/theme/data-source-input.scss +18 -0
  60. package/src/theme/framework.scss +0 -3
  61. package/src/theme/layer-panel.scss +6 -0
  62. package/src/theme/layout.scss +4 -0
  63. package/src/theme/resizer.scss +25 -10
  64. package/src/theme/stage.scss +4 -0
  65. package/src/theme/theme.scss +1 -0
  66. package/src/type.ts +59 -1
  67. package/src/utils/config.ts +1 -1
  68. package/src/utils/content-menu.ts +92 -0
  69. package/src/utils/dep.ts +7 -1
  70. package/src/utils/keybinding-config.ts +120 -0
  71. package/src/utils/props.ts +34 -0
  72. package/types/Editor.vue.d.ts +7 -1
  73. package/types/components/CodeDraftEditor.vue.d.ts +6 -3
  74. package/types/components/ContentMenu.vue.d.ts +10 -5
  75. package/types/components/FunctionEditor.vue.d.ts +74 -51
  76. package/types/components/Icon.vue.d.ts +2 -2
  77. package/types/{layouts → components}/Resizer.vue.d.ts +2 -2
  78. package/types/components/ScrollBar.vue.d.ts +1 -1
  79. package/types/components/ScrollViewer.vue.d.ts +8 -5
  80. package/types/components/SearchInput.vue.d.ts +1 -1
  81. package/types/components/{Layout.vue.d.ts → SplitView.vue.d.ts} +9 -6
  82. package/types/components/ToolButton.vue.d.ts +8 -5
  83. package/types/editorProps.d.ts +4 -1
  84. package/types/fields/Code.vue.d.ts +1 -1
  85. package/types/fields/CodeLink.vue.d.ts +9 -9
  86. package/types/fields/CodeSelect.vue.d.ts +10 -7
  87. package/types/fields/CodeSelectCol.vue.d.ts +8 -5
  88. package/types/fields/DataSourceFields.vue.d.ts +8 -5
  89. package/types/fields/DataSourceInput.vue.d.ts +54 -0
  90. package/types/fields/DataSourceSelect.vue.d.ts +56 -0
  91. package/types/fields/EventSelect.vue.d.ts +3 -3
  92. package/types/fields/KeyValue.vue.d.ts +8 -5
  93. package/types/fields/UISelect.vue.d.ts +1 -1
  94. package/types/icons/AppManageIcon.vue.d.ts +1 -1
  95. package/types/icons/CenterIcon.vue.d.ts +2 -0
  96. package/types/icons/CodeIcon.vue.d.ts +1 -1
  97. package/types/index.d.ts +6 -2
  98. package/types/initService.d.ts +1 -1
  99. package/types/layouts/AddPageBox.vue.d.ts +1 -1
  100. package/types/layouts/CodeEditor.vue.d.ts +16 -10
  101. package/types/layouts/Framework.vue.d.ts +11 -32
  102. package/types/layouts/NavMenu.vue.d.ts +7 -4
  103. package/types/layouts/PropsPanel.vue.d.ts +276 -4
  104. package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +4 -4
  105. package/types/layouts/sidebar/LayerMenu.vue.d.ts +2 -2
  106. package/types/layouts/sidebar/LayerNode.vue.d.ts +16 -0
  107. package/types/layouts/sidebar/LayerPanel.vue.d.ts +5 -5
  108. package/types/layouts/sidebar/Sidebar.vue.d.ts +18 -15
  109. package/types/layouts/sidebar/code-block/CodeBlockEditor.vue.d.ts +4 -4
  110. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +7 -7
  111. package/types/layouts/sidebar/data-source/DataSourceConfigPanel.vue.d.ts +1 -1
  112. package/types/layouts/sidebar/data-source/DataSourceListPanel.vue.d.ts +3 -3
  113. package/types/layouts/workspace/Breadcrumb.vue.d.ts +1 -1
  114. package/types/layouts/workspace/PageBar.vue.d.ts +5 -5
  115. package/types/layouts/workspace/PageBarScrollContainer.vue.d.ts +2 -2
  116. package/types/layouts/workspace/Stage.vue.d.ts +2 -2
  117. package/types/layouts/workspace/ViewerMenu.vue.d.ts +7 -4
  118. package/types/layouts/workspace/Workspace.vue.d.ts +9 -9
  119. package/types/services/keybinding.d.ts +20 -0
  120. package/types/type.d.ts +56 -1
  121. package/types/utils/config.d.ts +1 -1
  122. package/types/utils/content-menu.d.ts +7 -0
  123. package/types/utils/keybinding-config.d.ts +3 -0
  124. package/src/layouts/Resizer.vue +0 -32
@@ -10,6 +10,7 @@
10
10
  ref="tree"
11
11
  node-key="id"
12
12
  empty-text="页面空荡荡的"
13
+ tabindex="-1"
13
14
  draggable
14
15
  :default-expanded-keys="expandedKeys"
15
16
  :default-checked-keys="checkedKeys"
@@ -34,9 +35,7 @@
34
35
  <template #default="{ node, data }">
35
36
  <div class="cus-tree-node" :id="data.id" @mouseenter="highlightHandler(data)">
36
37
  <slot name="layer-node-content" :node="node" :data="data">
37
- <span>
38
- {{ `${data.name} (${data.id})` }}
39
- </span>
38
+ <LayerNode :data="data"></LayerNode>
40
39
  </slot>
41
40
  </div>
42
41
  </template>
@@ -48,9 +47,8 @@
48
47
  </TMagicScrollbar>
49
48
  </template>
50
49
 
51
- <script lang="ts" setup name="MEditorLayerPanel">
52
- import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue';
53
- import KeyController from 'keycon';
50
+ <script lang="ts" setup>
51
+ import { computed, inject, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue';
54
52
  import { difference, throttle, union } from 'lodash-es';
55
53
 
56
54
  import { TMagicScrollbar, TMagicTree } from '@tmagic/design';
@@ -63,6 +61,11 @@ import type { MenuButton, MenuComponent, Services } from '@editor/type';
63
61
  import { Layout } from '@editor/type';
64
62
 
65
63
  import LayerMenu from './LayerMenu.vue';
64
+ import LayerNode from './LayerNode.vue';
65
+
66
+ defineOptions({
67
+ name: 'MEditorLayerPanel',
68
+ });
66
69
 
67
70
  defineProps<{
68
71
  layerContentMenu: (MenuButton | MenuComponent)[];
@@ -71,6 +74,7 @@ defineProps<{
71
74
  const throttleTime = 150;
72
75
  const services = inject<Services>('services');
73
76
  const editorService = services?.editorService;
77
+ const keybindingService = services?.keybindingService;
74
78
 
75
79
  const tree = ref<InstanceType<typeof TMagicTree>>();
76
80
  const menu = ref<InstanceType<typeof LayerMenu>>();
@@ -242,34 +246,69 @@ const windowBlurHandler = () => {
242
246
  isCtrlKeyDown.value = false;
243
247
  };
244
248
 
245
- let keycon: KeyController;
249
+ keybindingService?.registeCommand('layer-panel-not-ctrl-keydown', (e) => {
250
+ if (e.key !== keybindingService.ctrlKey) {
251
+ isCtrlKeyDown.value = false;
252
+ }
253
+ });
254
+
255
+ keybindingService?.registeCommand('layer-panel-ctrl-keydown', () => {
256
+ isCtrlKeyDown.value = true;
257
+ });
258
+
259
+ keybindingService?.registeCommand('layer-panel-ctrl-keyup', () => {
260
+ isCtrlKeyDown.value = false;
261
+ });
262
+
263
+ keybindingService?.registeCommand('layer-panel-global-keydwon', () => {
264
+ if (!tree.value?.$el.contains(document.activeElement)) {
265
+ isCtrlKeyDown.value = false;
266
+ }
267
+ });
268
+
269
+ keybindingService?.registe([
270
+ {
271
+ command: 'layer-panel-not-ctrl-keydown',
272
+ when: [['layer-panel', 'keydown']],
273
+ },
274
+ {
275
+ command: 'layer-panel-ctrl-keydown',
276
+ keybinding: 'ctrl',
277
+ when: [['layer-panel', 'keydown']],
278
+ },
279
+ {
280
+ command: 'layer-panel-ctrl-keyup',
281
+ keybinding: 'ctrl',
282
+ when: [['layer-panel', 'keyup']],
283
+ },
284
+ {
285
+ command: 'layer-panel-global-keydwon',
286
+ keybinding: 'ctrl',
287
+ when: [['global', 'keydown']],
288
+ },
289
+ ]);
290
+
291
+ watch(tree, () => {
292
+ if (tree.value?.$el) {
293
+ keybindingService?.registeEl('layer-panel', tree.value.$el);
294
+
295
+ tree.value.$el.addEventListener('blur', windowBlurHandler);
296
+ } else {
297
+ keybindingService?.unregisteEl('layer-panel');
298
+ }
299
+ });
246
300
 
247
301
  onMounted(() => {
248
302
  editorService?.on('remove', editorServiceRemoveHandler);
249
303
 
250
- keycon = new KeyController();
251
- const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
252
- const ctrl = isMac ? 'meta' : 'ctrl';
253
-
254
- keycon
255
- .keydown((e) => {
256
- if (e.key !== ctrl) {
257
- isCtrlKeyDown.value = false;
258
- }
259
- })
260
- .keydown(ctrl, () => {
261
- isCtrlKeyDown.value = true;
262
- })
263
- .keyup(ctrl, () => {
264
- isCtrlKeyDown.value = false;
265
- });
266
-
267
304
  globalThis.addEventListener('blur', windowBlurHandler);
268
305
  });
269
306
 
270
- onUnmounted(() => {
271
- keycon.destroy();
307
+ onBeforeUnmount(() => {
308
+ tree.value?.$el.removeEventListener('blur', windowBlurHandler);
309
+ });
272
310
 
311
+ onUnmounted(() => {
273
312
  editorService?.off('remove', editorServiceRemoveHandler);
274
313
  globalThis.removeEventListener('blur', windowBlurHandler);
275
314
  });
@@ -310,7 +349,11 @@ const clickHandler = (data: MNode): void => {
310
349
  // 右键菜单
311
350
  const contextmenu = async (event: MouseEvent, data: MNode): Promise<void> => {
312
351
  event.preventDefault();
313
- await select(data);
352
+
353
+ if (nodes.value.length < 2) {
354
+ await select(data);
355
+ }
356
+
314
357
  menu.value?.show(event);
315
358
  };
316
359
  </script>
@@ -3,13 +3,13 @@
3
3
  v-if="data.type === 'tabs' && data.items.length"
4
4
  v-model="activeTabName"
5
5
  class="m-editor-sidebar tmagic-design-tabs"
6
- v-bind="tabsComponent.props({ type: 'card', tabPosition: 'left' })"
7
- :is="tabsComponent.component"
6
+ v-bind="tabsComponent?.props({ type: 'card', tabPosition: 'left' }) || {}"
7
+ :is="tabsComponent?.component || 'el-tabs'"
8
8
  >
9
9
  <component
10
10
  v-for="(config, index) in sideBarItems"
11
- v-bind="tabPaneComponent.props({ name: config.text })"
12
- :is="tabPaneComponent.component"
11
+ v-bind="tabPaneComponent?.props({ name: config.text, lazy: config.lazy }) || {}"
12
+ :is="tabPaneComponent?.component || 'el-tab-pane'"
13
13
  :key="config.$key || index"
14
14
  >
15
15
  <template #label>
@@ -83,7 +83,7 @@
83
83
  </component>
84
84
  </template>
85
85
 
86
- <script lang="ts" setup name="MEditorSidebar">
86
+ <script lang="ts" setup>
87
87
  import { computed, ref, watch } from 'vue';
88
88
  import { Coin, EditPen, Goods, List } from '@element-plus/icons-vue';
89
89
 
@@ -98,6 +98,10 @@ import DataSourceListPanel from './data-source/DataSourceListPanel.vue';
98
98
  import ComponentListPanel from './ComponentListPanel.vue';
99
99
  import LayerPanel from './LayerPanel.vue';
100
100
 
101
+ defineOptions({
102
+ name: 'MEditorSidebar',
103
+ });
104
+
101
105
  const props = withDefaults(
102
106
  defineProps<{
103
107
  data: SideBarData;
@@ -108,8 +112,8 @@ const props = withDefaults(
108
112
  },
109
113
  );
110
114
 
111
- const tabPaneComponent = getConfig('components').tabPane;
112
- const tabsComponent = getConfig('components').tabs;
115
+ const tabPaneComponent = getConfig('components')?.tabPane;
116
+ const tabsComponent = getConfig('components')?.tabs;
113
117
 
114
118
  const activeTabName = ref(props.data?.status);
115
119
 
@@ -10,7 +10,7 @@
10
10
  :size="size"
11
11
  :before-close="handleClose"
12
12
  >
13
- <Layout v-model:left="left" :min-left="45" class="code-editor-layout">
13
+ <SplitView v-model:left="left" :min-left="45" class="code-editor-layout">
14
14
  <!-- 右侧区域 -->
15
15
  <template #center>
16
16
  <div v-if="!isEmpty(codeConfig)" class="m-editor-code-block-editor-panel">
@@ -28,11 +28,11 @@
28
28
  ></FunctionEditor>
29
29
  </div>
30
30
  </template>
31
- </Layout>
31
+ </SplitView>
32
32
  </TMagicDrawer>
33
33
  </template>
34
34
 
35
- <script lang="ts" setup name="MEditorCodeBlockEditor">
35
+ <script lang="ts" setup>
36
36
  import { computed, inject, reactive, ref, watchEffect } from 'vue';
37
37
  import { cloneDeep, forIn, isEmpty } from 'lodash-es';
38
38
 
@@ -41,10 +41,14 @@ import { ColumnConfig } from '@tmagic/form';
41
41
  import { CodeBlockContent } from '@tmagic/schema';
42
42
 
43
43
  import FunctionEditor from '@editor/components/FunctionEditor.vue';
44
- import Layout from '@editor/components/Layout.vue';
44
+ import SplitView from '@editor/components/SplitView.vue';
45
45
  import type { ListState, Services } from '@editor/type';
46
46
  import { serializeConfig } from '@editor/utils/editor';
47
47
 
48
+ defineOptions({
49
+ name: 'MEditorCodeBlockEditor',
50
+ });
51
+
48
52
  const services = inject<Services>('services');
49
53
  const codeOptions = inject('codeOptions', {});
50
54
 
@@ -55,7 +55,7 @@
55
55
  </TMagicScrollbar>
56
56
  </template>
57
57
 
58
- <script setup lang="ts" name="MEditorCodeBlockList">
58
+ <script setup lang="ts">
59
59
  import { computed, inject, ref } from 'vue';
60
60
  import { Close, Edit, View } from '@element-plus/icons-vue';
61
61
 
@@ -71,6 +71,10 @@ import { CodeDeleteErrorType, CodeDslItem, Services } from '@editor/type';
71
71
 
72
72
  import CodeBlockEditor from './CodeBlockEditor.vue';
73
73
 
74
+ defineOptions({
75
+ name: 'MEditorCodeBlockList',
76
+ });
77
+
74
78
  const props = defineProps<{
75
79
  customError?: (id: Id, errorType: CodeDeleteErrorType) => any;
76
80
  paramsColConfig?: ColumnConfig;
@@ -27,6 +27,10 @@ import { MForm } from '@tmagic/form';
27
27
 
28
28
  import type { Services } from '@editor/type';
29
29
 
30
+ defineOptions({
31
+ name: 'MEditorDataSourceConfigPanel',
32
+ });
33
+
30
34
  const props = defineProps<{
31
35
  title?: string;
32
36
  values: any;
@@ -15,6 +15,7 @@
15
15
  :expand-on-click-node="false"
16
16
  :data="list"
17
17
  :highlight-current="true"
18
+ @node-click="clickHandler"
18
19
  >
19
20
  <template #default="{ data }">
20
21
  <div :id="data.id" class="list-container">
@@ -53,7 +54,7 @@ import { computed, inject, ref } from 'vue';
53
54
  import { Aim, Close, Coin, Edit } from '@element-plus/icons-vue';
54
55
 
55
56
  import { TMagicButton, tMagicMessageBox, TMagicScrollbar, TMagicTooltip, TMagicTree } from '@tmagic/design';
56
- import { DataSourceSchema } from '@tmagic/schema';
57
+ import { DataSourceSchema, Id } from '@tmagic/schema';
57
58
 
58
59
  import Icon from '@editor/components/Icon.vue';
59
60
  import SearchInput from '@editor/components/SearchInput.vue';
@@ -61,8 +62,12 @@ import type { Services } from '@editor/type';
61
62
 
62
63
  import DataSourceConfigPanel from './DataSourceConfigPanel.vue';
63
64
 
65
+ defineOptions({
66
+ name: 'MEditorDataSourceListPanel',
67
+ });
68
+
64
69
  const services = inject<Partial<Services>>('services', {});
65
- const { dataSourceService, depService } = inject<Services>('services') || {};
70
+ const { dataSourceService, depService, editorService } = inject<Services>('services') || {};
66
71
 
67
72
  const list = computed(() =>
68
73
  Object.values(depService?.targets['data-source'] || {}).map((target) => ({
@@ -127,4 +132,19 @@ const tree = ref<InstanceType<typeof TMagicTree>>();
127
132
  const filterTextChangeHandler = (val: string) => {
128
133
  tree.value?.filter(val);
129
134
  };
135
+
136
+ // 选中组件
137
+ const selectComp = (compId: Id) => {
138
+ const stage = editorService?.get('stage');
139
+ editorService?.select(compId);
140
+ stage?.select(compId);
141
+ };
142
+
143
+ const clickHandler = (data: any, node: any) => {
144
+ if (data.type === 'node') {
145
+ selectComp(data.id);
146
+ } else if (data.type === 'key') {
147
+ selectComp(node.parent.data.id);
148
+ }
149
+ };
130
150
  </script>
@@ -7,7 +7,7 @@
7
7
  </div>
8
8
  </template>
9
9
 
10
- <script setup lang="ts" name="MEditorBreadcrumb">
10
+ <script setup lang="ts">
11
11
  import { computed, inject } from 'vue';
12
12
 
13
13
  import { TMagicButton } from '@tmagic/design';
@@ -16,6 +16,10 @@ import { getNodePath } from '@tmagic/utils';
16
16
 
17
17
  import type { Services } from '@editor/type';
18
18
 
19
+ defineOptions({
20
+ name: 'MEditorBreadcrumb',
21
+ });
22
+
19
23
  const services = inject<Services>('services');
20
24
  const editorService = services?.editorService;
21
25
 
@@ -46,7 +46,7 @@
46
46
  </PageBarScrollContainer>
47
47
  </template>
48
48
 
49
- <script lang="ts" setup name="MEditorPageBar">
49
+ <script lang="ts" setup>
50
50
  import { computed, inject } from 'vue';
51
51
  import { CaretBottom, Delete, DocumentCopy } from '@element-plus/icons-vue';
52
52
 
@@ -58,6 +58,10 @@ import type { Services } from '@editor/type';
58
58
 
59
59
  import PageBarScrollContainer from './PageBarScrollContainer.vue';
60
60
 
61
+ defineOptions({
62
+ name: 'MEditorPageBar',
63
+ });
64
+
61
65
  const services = inject<Services>('services');
62
66
  const editorService = services?.editorService;
63
67
 
@@ -26,7 +26,7 @@
26
26
  </div>
27
27
  </template>
28
28
 
29
- <script setup lang="ts" name="MEditorPageBarScrollContainer">
29
+ <script setup lang="ts">
30
30
  import { computed, inject, nextTick, onMounted, onUnmounted, ref, toRaw, watch } from 'vue';
31
31
  import { ArrowLeftBold, ArrowRightBold, Plus } from '@element-plus/icons-vue';
32
32
 
@@ -36,6 +36,10 @@ import Icon from '@editor/components/Icon.vue';
36
36
  import type { Services } from '@editor/type';
37
37
  import { generatePageNameByApp } from '@editor/utils/editor';
38
38
 
39
+ defineOptions({
40
+ name: 'MEditorPageBarScrollContainer',
41
+ });
42
+
39
43
  const services = inject<Services>('services');
40
44
  const editorService = services?.editorService;
41
45
  const uiService = services?.uiService;
@@ -2,6 +2,7 @@
2
2
  <ScrollViewer
3
3
  class="m-editor-stage"
4
4
  ref="stageWrap"
5
+ tabindex="-1"
5
6
  :width="stageRect?.width"
6
7
  :height="stageRect?.height"
7
8
  :wrap-width="stageContainerRect?.width"
@@ -11,6 +12,7 @@
11
12
  width: 60,
12
13
  height: 50,
13
14
  }"
15
+ @click="stageWrap?.container?.focus()"
14
16
  >
15
17
  <div
16
18
  class="m-editor-stage-container"
@@ -26,7 +28,7 @@
26
28
  </ScrollViewer>
27
29
  </template>
28
30
 
29
- <script lang="ts" setup name="MEditorStage">
31
+ <script lang="ts" setup>
30
32
  import { computed, inject, markRaw, nextTick, onMounted, onUnmounted, ref, toRaw, watch, watchEffect } from 'vue';
31
33
  import { cloneDeep } from 'lodash-es';
32
34
 
@@ -35,10 +37,15 @@ import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stag
35
37
 
36
38
  import ScrollViewer from '@editor/components/ScrollViewer.vue';
37
39
  import { Layout, MenuButton, MenuComponent, Services, StageOptions } from '@editor/type';
40
+ import { getConfig } from '@editor/utils/config';
38
41
  import { useStage } from '@editor/utils/stage';
39
42
 
40
43
  import ViewerMenu from './ViewerMenu.vue';
41
44
 
45
+ defineOptions({
46
+ name: 'MEditorStage',
47
+ });
48
+
42
49
  defineProps<{
43
50
  stageContentMenu: (MenuButton | MenuComponent)[];
44
51
  }>();
@@ -70,6 +77,10 @@ watchEffect(() => {
70
77
 
71
78
  stage = useStage(stageOptions);
72
79
 
80
+ stage.on('select', () => {
81
+ stageWrap.value?.container?.focus();
82
+ });
83
+
73
84
  services?.editorService.set('stage', markRaw(stage));
74
85
 
75
86
  stage?.mount(stageContainer.value);
@@ -116,13 +127,17 @@ const resizeObserver = new ResizeObserver((entries) => {
116
127
  });
117
128
 
118
129
  onMounted(() => {
119
- stageWrap.value?.container && resizeObserver.observe(stageWrap.value.container);
130
+ if (stageWrap.value?.container) {
131
+ resizeObserver.observe(stageWrap.value.container);
132
+ services?.keybindingService.registeEl('stage', stageWrap.value.container);
133
+ }
120
134
  });
121
135
 
122
136
  onUnmounted(() => {
123
137
  stage?.destroy();
124
138
  resizeObserver.disconnect();
125
139
  services?.editorService.set('stage', null);
140
+ services?.keybindingService.unregisteEl('stage');
126
141
  });
127
142
 
128
143
  const contextmenuHandler = (e: MouseEvent) => {
@@ -132,9 +147,8 @@ const contextmenuHandler = (e: MouseEvent) => {
132
147
 
133
148
  const dragoverHandler = (e: DragEvent) => {
134
149
  e.preventDefault();
135
- if (e.dataTransfer) {
136
- e.dataTransfer.dropEffect = 'move';
137
- }
150
+ if (!e.dataTransfer) return;
151
+ e.dataTransfer.dropEffect = 'move';
138
152
  };
139
153
 
140
154
  const dropHandler = async (e: DragEvent) => {
@@ -149,8 +163,16 @@ const dropHandler = async (e: DragEvent) => {
149
163
  }
150
164
 
151
165
  if (e.dataTransfer && parent && stageContainer.value && stage) {
152
- // eslint-disable-next-line no-eval
153
- const config = eval(`(${e.dataTransfer.getData('data')})`);
166
+ const parseDSL = getConfig('parseDSL');
167
+
168
+ const data = e.dataTransfer.getData('text/html');
169
+
170
+ if (!data) return;
171
+
172
+ const config = parseDSL(`(${data})`);
173
+
174
+ if (!config) return;
175
+
154
176
  const layout = await services?.editorService.getLayout(parent);
155
177
 
156
178
  const containerRect = stageContainer.value.getBoundingClientRect();
@@ -2,18 +2,24 @@
2
2
  <content-menu :menu-data="menuData" ref="menu"></content-menu>
3
3
  </template>
4
4
 
5
- <script lang="ts" setup name="MEditorViewerMenu">
5
+ <script lang="ts" setup>
6
6
  import { computed, inject, markRaw, ref, watch } from 'vue';
7
- import { Bottom, CopyDocument, Delete, DocumentCopy, Top } from '@element-plus/icons-vue';
7
+ import { Bottom, Top } from '@element-plus/icons-vue';
8
8
 
9
9
  import { NodeType } from '@tmagic/schema';
10
10
  import { isPage } from '@tmagic/utils';
11
11
 
12
12
  import ContentMenu from '@editor/components/ContentMenu.vue';
13
+ import CenterIcon from '@editor/icons/CenterIcon.vue';
13
14
  import storageService from '@editor/services/storage';
14
15
  import { LayerOffset, Layout, MenuButton, MenuComponent, Services } from '@editor/type';
16
+ import { useCopyMenu, useDeleteMenu, useMoveToMenu, usePasteMenu } from '@editor/utils/content-menu';
15
17
  import { COPY_STORAGE_KEY } from '@editor/utils/editor';
16
18
 
19
+ defineOptions({
20
+ name: 'MEditorViewerMenu',
21
+ });
22
+
17
23
  const props = withDefaults(
18
24
  defineProps<{ isMultiSelect?: boolean; stageContentMenu: (MenuButton | MenuComponent)[] }>(),
19
25
  { isMultiSelect: false },
@@ -28,42 +34,20 @@ const canCenter = ref(false);
28
34
  const node = computed(() => editorService?.get('node'));
29
35
  const nodes = computed(() => editorService?.get('nodes'));
30
36
  const parent = computed(() => editorService?.get('parent'));
31
- const stage = computed(() => editorService?.get('stage'));
32
37
 
33
38
  const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
34
39
  {
35
40
  type: 'button',
36
41
  text: '水平居中',
42
+ icon: markRaw(CenterIcon),
37
43
  display: () => canCenter.value,
38
44
  handler: () => {
39
45
  if (!nodes.value) return;
40
46
  editorService?.alignCenter(nodes.value);
41
47
  },
42
48
  },
43
- {
44
- type: 'button',
45
- text: '复制',
46
- icon: markRaw(CopyDocument),
47
- handler: () => {
48
- nodes.value && editorService?.copy(nodes.value);
49
- canPaste.value = true;
50
- },
51
- },
52
- {
53
- type: 'button',
54
- text: '粘贴',
55
- icon: markRaw(DocumentCopy),
56
- display: () => canPaste.value,
57
- handler: () => {
58
- const rect = menu.value?.$el.getBoundingClientRect();
59
- const parentRect = stage.value?.container?.getBoundingClientRect();
60
- const initialLeft = (rect?.left || 0) - (parentRect?.left || 0);
61
- const initialTop = (rect?.top || 0) - (parentRect?.top || 0);
62
-
63
- if (!nodes.value || nodes.value.length === 0) return;
64
- editorService?.paste({ left: initialLeft, top: initialTop });
65
- },
66
- },
49
+ useCopyMenu(),
50
+ usePasteMenu(menu),
67
51
  {
68
52
  type: 'divider',
69
53
  direction: 'horizontal',
@@ -93,6 +77,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
93
77
  {
94
78
  type: 'button',
95
79
  text: '置顶',
80
+ icon: markRaw(Top),
96
81
  display: () => !isPage(node.value) && !props.isMultiSelect,
97
82
  handler: () => {
98
83
  editorService?.moveLayer(LayerOffset.TOP);
@@ -101,25 +86,19 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
101
86
  {
102
87
  type: 'button',
103
88
  text: '置底',
89
+ icon: markRaw(Bottom),
104
90
  display: () => !isPage(node.value) && !props.isMultiSelect,
105
91
  handler: () => {
106
92
  editorService?.moveLayer(LayerOffset.BOTTOM);
107
93
  },
108
94
  },
95
+ useMoveToMenu(services),
109
96
  {
110
97
  type: 'divider',
111
98
  direction: 'horizontal',
112
99
  display: () => !isPage(node.value) && !props.isMultiSelect,
113
100
  },
114
- {
115
- type: 'button',
116
- text: '删除',
117
- icon: Delete,
118
- display: () => !isPage(node.value),
119
- handler: () => {
120
- nodes.value && editorService?.remove(nodes.value);
121
- },
122
- },
101
+ useDeleteMenu(),
123
102
  {
124
103
  type: 'divider',
125
104
  direction: 'horizontal',