@tmagic/editor 1.3.0-alpha.9 → 1.3.0-beta.1

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 (139) hide show
  1. package/dist/style.css +106 -219
  2. package/dist/tmagic-editor.js +6253 -5042
  3. package/dist/tmagic-editor.js.map +1 -1
  4. package/dist/tmagic-editor.umd.cjs +6280 -5055
  5. package/dist/tmagic-editor.umd.cjs.map +1 -1
  6. package/package.json +15 -15
  7. package/src/Editor.vue +4 -5
  8. package/src/components/CodeBlockEditor.vue +266 -0
  9. package/src/components/CodeParams.vue +60 -0
  10. package/src/components/ContentMenu.vue +97 -43
  11. package/src/components/Resizer.vue +8 -30
  12. package/src/components/SplitView.vue +3 -2
  13. package/src/editorProps.ts +30 -5
  14. package/src/fields/Code.vue +23 -19
  15. package/src/fields/CodeLink.vue +6 -7
  16. package/src/fields/CodeSelect.vue +12 -9
  17. package/src/fields/CodeSelectCol.vue +70 -81
  18. package/src/fields/DataSourceFieldSelect.vue +47 -0
  19. package/src/fields/DataSourceFields.vue +38 -22
  20. package/src/fields/DataSourceInput.vue +21 -58
  21. package/src/fields/DataSourceMethodSelect.vue +136 -0
  22. package/src/fields/DataSourceMethods.vue +104 -0
  23. package/src/fields/DataSourceSelect.vue +5 -12
  24. package/src/fields/EventSelect.vue +53 -19
  25. package/src/fields/KeyValue.vue +9 -11
  26. package/src/fields/UISelect.vue +54 -14
  27. package/src/hooks/index.ts +21 -0
  28. package/src/hooks/use-code-block-edit.ts +84 -0
  29. package/src/hooks/use-data-source-method.ts +100 -0
  30. package/src/hooks/use-getso.ts +35 -0
  31. package/src/{utils/stage.ts → hooks/use-stage.ts} +4 -7
  32. package/src/icons/CenterIcon.vue +13 -0
  33. package/src/icons/CodeIcon.vue +0 -2
  34. package/src/icons/FolderMinusIcon.vue +22 -0
  35. package/src/icons/PinIcon.vue +25 -0
  36. package/src/icons/PinnedIcon.vue +25 -0
  37. package/src/index.ts +14 -0
  38. package/src/initService.ts +69 -16
  39. package/src/layouts/CodeEditor.vue +35 -3
  40. package/src/layouts/Framework.vue +7 -3
  41. package/src/layouts/PropsPanel.vue +8 -3
  42. package/src/layouts/sidebar/ComponentListPanel.vue +4 -6
  43. package/src/layouts/sidebar/LayerMenu.vue +20 -21
  44. package/src/layouts/sidebar/LayerPanel.vue +12 -2
  45. package/src/layouts/sidebar/Sidebar.vue +22 -36
  46. package/src/layouts/sidebar/code-block/CodeBlockList.vue +81 -119
  47. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +64 -0
  48. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +27 -47
  49. package/src/layouts/sidebar/data-source/DataSourceList.vue +145 -0
  50. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +58 -85
  51. package/src/layouts/workspace/Workspace.vue +1 -1
  52. package/src/layouts/workspace/viewer/NodeListMenu.vue +159 -0
  53. package/src/layouts/workspace/viewer/NodeListMenuTitle.vue +68 -0
  54. package/src/layouts/workspace/{Stage.vue → viewer/Stage.vue} +5 -2
  55. package/src/layouts/workspace/{ViewerMenu.vue → viewer/ViewerMenu.vue} +10 -35
  56. package/src/services/codeBlock.ts +18 -71
  57. package/src/services/dataSource.ts +16 -1
  58. package/src/services/dep.ts +22 -20
  59. package/src/services/editor.ts +46 -18
  60. package/src/services/props.ts +2 -2
  61. package/src/theme/code-block.scss +5 -117
  62. package/src/theme/code-editor.scss +27 -2
  63. package/src/theme/content-menu.scss +26 -2
  64. package/src/theme/data-source-methods.scss +13 -0
  65. package/src/theme/data-source.scss +11 -0
  66. package/src/theme/event.scss +15 -12
  67. package/src/theme/framework.scss +0 -3
  68. package/src/theme/index.scss +8 -0
  69. package/src/theme/layout.scss +4 -0
  70. package/src/theme/sidebar.scss +18 -60
  71. package/src/theme/theme.scss +1 -0
  72. package/src/type.ts +49 -7
  73. package/src/utils/content-menu.ts +92 -0
  74. package/src/utils/data-source/formConfigs/base.ts +28 -30
  75. package/src/utils/data-source/formConfigs/http.ts +9 -0
  76. package/src/utils/data-source/index.ts +65 -2
  77. package/src/utils/dep.ts +33 -7
  78. package/src/utils/index.ts +1 -1
  79. package/src/utils/props.ts +308 -189
  80. package/src/utils/undo-redo.ts +1 -1
  81. package/types/Editor.vue.d.ts +30 -6
  82. package/types/components/CodeBlockEditor.vue.d.ts +25 -0
  83. package/types/components/CodeParams.vue.d.ts +26 -0
  84. package/types/components/ContentMenu.vue.d.ts +27 -8
  85. package/types/components/Resizer.vue.d.ts +1 -3
  86. package/types/components/ToolButton.vue.d.ts +3 -3
  87. package/types/editorProps.d.ts +19 -5
  88. package/types/fields/Code.vue.d.ts +24 -19
  89. package/types/fields/CodeLink.vue.d.ts +12 -21
  90. package/types/fields/CodeSelect.vue.d.ts +6 -17
  91. package/types/fields/CodeSelectCol.vue.d.ts +10 -14
  92. package/types/fields/DataSourceFieldSelect.vue.d.ts +29 -0
  93. package/types/fields/DataSourceFields.vue.d.ts +7 -18
  94. package/types/fields/DataSourceInput.vue.d.ts +10 -30
  95. package/types/fields/DataSourceMethodSelect.vue.d.ts +29 -0
  96. package/types/fields/DataSourceMethods.vue.d.ts +32 -0
  97. package/types/fields/DataSourceSelect.vue.d.ts +13 -29
  98. package/types/fields/EventSelect.vue.d.ts +2 -13
  99. package/types/fields/KeyValue.vue.d.ts +9 -24
  100. package/types/fields/UISelect.vue.d.ts +2 -11
  101. package/types/hooks/index.d.ts +3 -0
  102. package/types/{components/FunctionEditor.vue.d.ts → hooks/use-code-block-edit.d.ts} +36 -119
  103. package/types/hooks/use-data-source-method.d.ts +130 -0
  104. package/types/hooks/use-getso.d.ts +5 -0
  105. package/types/icons/CenterIcon.vue.d.ts +2 -0
  106. package/types/icons/FolderMinusIcon.vue.d.ts +2 -0
  107. package/types/icons/PinIcon.vue.d.ts +2 -0
  108. package/types/icons/PinnedIcon.vue.d.ts +2 -0
  109. package/types/index.d.ts +7 -0
  110. package/types/initService.d.ts +2 -2
  111. package/types/layouts/CodeEditor.vue.d.ts +7 -4
  112. package/types/layouts/PropsPanel.vue.d.ts +274 -2
  113. package/types/layouts/sidebar/Sidebar.vue.d.ts +0 -3
  114. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +4 -9
  115. package/types/layouts/sidebar/code-block/{CodeBlockEditor.vue.d.ts → CodeBlockListPanel.vue.d.ts} +9 -5
  116. package/types/layouts/sidebar/data-source/DataSourceConfigPanel.vue.d.ts +3 -1
  117. package/types/layouts/sidebar/data-source/DataSourceList.vue.d.ts +14 -0
  118. package/types/layouts/sidebar/data-source/DataSourceListPanel.vue.d.ts +1 -11
  119. package/types/layouts/workspace/viewer/NodeListMenu.vue.d.ts +15 -0
  120. package/types/layouts/workspace/viewer/NodeListMenuTitle.vue.d.ts +15 -0
  121. package/types/layouts/workspace/{Stage.vue.d.ts → viewer/Stage.vue.d.ts} +1 -1
  122. package/types/layouts/workspace/{ViewerMenu.vue.d.ts → viewer/ViewerMenu.vue.d.ts} +1 -1
  123. package/types/services/codeBlock.d.ts +6 -37
  124. package/types/services/dataSource.d.ts +7 -1
  125. package/types/services/dep.d.ts +12 -21
  126. package/types/services/editor.d.ts +1 -1
  127. package/types/services/props.d.ts +10 -115
  128. package/types/type.d.ts +44 -7
  129. package/types/utils/content-menu.d.ts +7 -0
  130. package/types/utils/data-source/formConfigs/base.d.ts +1 -2
  131. package/types/utils/data-source/index.d.ts +5 -0
  132. package/types/utils/dep.d.ts +4 -2
  133. package/types/utils/index.d.ts +1 -1
  134. package/types/utils/props.d.ts +13 -102
  135. package/src/components/CodeDraftEditor.vue +0 -148
  136. package/src/components/FunctionEditor.vue +0 -197
  137. package/src/layouts/sidebar/code-block/CodeBlockEditor.vue +0 -100
  138. package/types/components/CodeDraftEditor.vue.d.ts +0 -61
  139. /package/types/{utils/stage.d.ts → hooks/use-stage.d.ts} +0 -0
@@ -2,122 +2,110 @@
2
2
  <TMagicScrollbar class="data-source-list-panel m-editor-dep-list-panel">
3
3
  <div class="search-wrapper">
4
4
  <SearchInput @search="filterTextChangeHandler"></SearchInput>
5
- <TMagicButton type="primary" size="small" @click="addHandler">新增</TMagicButton>
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>
6
23
  </div>
7
24
 
8
25
  <!-- 数据源列表 -->
9
- <TMagicTree
10
- ref="tree"
11
- class="magic-editor-layer-tree"
12
- node-key="id"
13
- empty-text="暂无代码块"
14
- default-expand-all
15
- :expand-on-click-node="false"
16
- :data="list"
17
- :highlight-current="true"
18
- @node-click="clickHandler"
19
- >
20
- <template #default="{ data }">
21
- <div :id="data.id" class="list-container">
22
- <div class="list-item">
23
- <Icon v-if="data.type === 'code'" class="codeIcon" :icon="Coin"></Icon>
24
- <Icon v-if="data.type === 'node'" class="compIcon" :icon="Aim"></Icon>
25
- <span class="name" :class="{ code: data.type === 'code', hook: data.type === 'key' }"
26
- >{{ data.name }}({{ data.id }})</span
27
- >
28
- <!-- 右侧工具栏 -->
29
- <div class="right-tool" v-if="data.type === 'code'">
30
- <TMagicTooltip effect="dark" content="编辑" placement="bottom">
31
- <Icon class="edit-icon" :icon="Edit" @click.stop="editHandler(`${data.id}`)"></Icon>
32
- </TMagicTooltip>
33
- <TMagicTooltip effect="dark" content="删除" placement="bottom">
34
- <Icon :icon="Close" class="edit-icon" @click.stop="removeHandler(`${data.id}`)"></Icon>
35
- </TMagicTooltip>
36
- <slot name="data-source-panel-tool" :id="data.id" :data="data.codeBlockContent"></slot>
37
- </div>
38
- </div>
39
- </div>
40
- </template>
41
- </TMagicTree>
26
+ <DataSourceList @edit="editHandler" @remove="removeHandler"></DataSourceList>
42
27
 
43
28
  <DataSourceConfigPanel
44
29
  ref="editDialog"
30
+ :disabled="!editable"
45
31
  :values="dataSourceValues"
46
- :title="typeof dataSourceValues.id !== 'undefined' ? `编辑${dataSourceValues.title}` : '新增'"
32
+ :title="dialogTitle"
47
33
  @submit="submitDataSourceHandler"
48
34
  ></DataSourceConfigPanel>
49
35
  </TMagicScrollbar>
50
36
  </template>
51
37
 
52
- <script setup lang="ts" name="MEditorDataSourceListPanel">
38
+ <script setup lang="ts">
53
39
  import { computed, inject, ref } from 'vue';
54
- import { Aim, Close, Coin, Edit } from '@element-plus/icons-vue';
40
+ import { mergeWith } from 'lodash-es';
55
41
 
56
- import { TMagicButton, tMagicMessageBox, TMagicScrollbar, TMagicTooltip, TMagicTree } from '@tmagic/design';
57
- import { DataSourceSchema, Id } from '@tmagic/schema';
42
+ import { TMagicButton, TMagicPopover, TMagicScrollbar } from '@tmagic/design';
43
+ import type { DataSourceSchema } from '@tmagic/schema';
58
44
 
59
- import Icon from '@editor/components/Icon.vue';
60
45
  import SearchInput from '@editor/components/SearchInput.vue';
46
+ import ToolButton from '@editor/components/ToolButton.vue';
61
47
  import type { Services } from '@editor/type';
62
48
 
63
49
  import DataSourceConfigPanel from './DataSourceConfigPanel.vue';
50
+ import DataSourceList from './DataSourceList.vue';
64
51
 
65
52
  defineOptions({
66
53
  name: 'MEditorDataSourceListPanel',
67
54
  });
68
55
 
69
- const services = inject<Partial<Services>>('services', {});
70
- const { dataSourceService, depService, editorService } = inject<Services>('services') || {};
71
-
72
- const list = computed(() =>
73
- Object.values(depService?.targets['data-source'] || {}).map((target) => ({
74
- id: target.id,
75
- name: target.name,
76
- type: 'code',
77
- children: Object.entries(target.deps).map(([id, dep]) => ({
78
- name: dep.name,
79
- type: 'node',
80
- id,
81
- children: dep.keys.map((key) => ({ name: key, id: key, type: 'key' })),
82
- })),
83
- })),
84
- );
56
+ const { dataSourceService } = inject<Services>('services') || {};
85
57
 
86
58
  const editDialog = ref<InstanceType<typeof DataSourceConfigPanel>>();
87
59
 
88
60
  const dataSourceValues = ref<Record<string, any>>({});
89
61
 
90
- const addHandler = () => {
62
+ const dialogTitle = ref('');
63
+
64
+ const editable = computed(() => dataSourceService?.get('editable') ?? true);
65
+ const datasourceTypeList = computed(() =>
66
+ [
67
+ { text: '基础', type: 'base' },
68
+ { text: 'HTTP', type: 'http' },
69
+ ].concat(dataSourceService?.get('datasourceTypeList') ?? []),
70
+ );
71
+
72
+ const addHandler = (type: string) => {
91
73
  if (!editDialog.value) return;
92
74
 
93
- dataSourceValues.value = {};
75
+ const datasourceType = datasourceTypeList.value.find((item) => item.type === type);
76
+
77
+ dataSourceValues.value = mergeWith(
78
+ { type, title: datasourceType?.text },
79
+ dataSourceService?.getFormValue(type) || {},
80
+ (objValue, srcValue) => {
81
+ if (Array.isArray(srcValue)) {
82
+ return srcValue;
83
+ }
84
+ },
85
+ );
86
+
87
+ dialogTitle.value = `新增${datasourceType?.text || ''}`;
94
88
 
95
89
  editDialog.value.show();
96
90
  };
97
91
 
98
92
  const editHandler = (id: string) => {
99
- if (!editDialog.value || !services) return;
93
+ if (!editDialog.value) return;
100
94
 
101
95
  dataSourceValues.value = {
102
96
  ...dataSourceService?.getDataSourceById(id),
103
97
  };
104
98
 
99
+ dialogTitle.value = `新增${dataSourceValues.value.title || ''}`;
100
+
105
101
  editDialog.value.show();
106
102
  };
107
103
 
108
- const removeHandler = async (id: string) => {
109
- await tMagicMessageBox.confirm('确定删除?', '提示', {
110
- confirmButtonText: '确定',
111
- cancelButtonText: '取消',
112
- type: 'warning',
113
- });
114
-
104
+ const removeHandler = (id: string) => {
115
105
  dataSourceService?.remove(id);
116
106
  };
117
107
 
118
108
  const submitDataSourceHandler = (value: DataSourceSchema) => {
119
- if (!services) return;
120
-
121
109
  if (value.id) {
122
110
  dataSourceService?.update(value);
123
111
  } else {
@@ -127,24 +115,9 @@ const submitDataSourceHandler = (value: DataSourceSchema) => {
127
115
  editDialog.value?.hide();
128
116
  };
129
117
 
130
- const tree = ref<InstanceType<typeof TMagicTree>>();
118
+ const dataSourceList = ref<InstanceType<typeof DataSourceList>>();
131
119
 
132
120
  const filterTextChangeHandler = (val: string) => {
133
- tree.value?.filter(val);
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
- }
121
+ dataSourceList.value?.filter(val);
149
122
  };
150
123
  </script>
@@ -20,9 +20,9 @@ import { computed, inject } from 'vue';
20
20
 
21
21
  import { MenuButton, MenuComponent, Services } from '@editor/type';
22
22
 
23
+ import MagicStage from './viewer/Stage.vue';
23
24
  import Breadcrumb from './Breadcrumb.vue';
24
25
  import PageBar from './PageBar.vue';
25
- import MagicStage from './Stage.vue';
26
26
 
27
27
  defineOptions({
28
28
  name: 'MEditorWorkspace',
@@ -0,0 +1,159 @@
1
+ <template>
2
+ <content-menu
3
+ ref="menu"
4
+ :menu-data="menuData"
5
+ :active="node?.id"
6
+ :auto-hide="!pinned"
7
+ @mouseenter="mouseenterHandler()"
8
+ >
9
+ <template #title>
10
+ <NodeListMenuTitle v-model:pinned="pinned" @change="dragMenuHandler" @close="closeHandler"></NodeListMenuTitle>
11
+ </template>
12
+ </content-menu>
13
+ </template>
14
+
15
+ <script lang="ts" setup>
16
+ import { Component, computed, inject, ref, watch } from 'vue';
17
+ import type { OnDrag } from 'gesto';
18
+
19
+ import type { MNode } from '@tmagic/schema';
20
+ import { StageDragStatus } from '@tmagic/stage';
21
+ import { getNodes } from '@tmagic/utils';
22
+
23
+ import ContentMenu from '@editor/components/ContentMenu.vue';
24
+ import type { ComponentItem, MenuButton, Services } from '@editor/type';
25
+
26
+ import NodeListMenuTitle from './NodeListMenuTitle.vue';
27
+ const props = defineProps<{ isMultiSelect?: boolean }>();
28
+
29
+ const menu = ref<InstanceType<typeof ContentMenu>>();
30
+ const nodeList = ref<MNode[]>([]);
31
+ const pinned = ref(false);
32
+
33
+ const services = inject<Services>('services');
34
+ const editorService = services?.editorService;
35
+ const componentListService = services?.componentListService;
36
+
37
+ const stage = computed(() => editorService?.get('stage'));
38
+ const page = computed(() => editorService?.get('page'));
39
+ const node = computed(() => editorService?.get('node'));
40
+
41
+ let timeout: NodeJS.Timeout | null = null;
42
+
43
+ const cancel = () => {
44
+ if (timeout) {
45
+ globalThis.clearTimeout(timeout);
46
+ }
47
+
48
+ if (pinned.value) {
49
+ return;
50
+ }
51
+
52
+ nodeList.value = [];
53
+ menu.value?.hide();
54
+ };
55
+
56
+ const unWatch = watch(
57
+ stage,
58
+ (stage) => {
59
+ if (!stage) return;
60
+
61
+ stage.on('drag-start', () => {
62
+ cancel();
63
+ });
64
+
65
+ stage.on('mousemove', (event: MouseEvent) => {
66
+ cancel();
67
+
68
+ if (props.isMultiSelect || stage.getDragStatus() !== StageDragStatus.END) {
69
+ return;
70
+ }
71
+
72
+ timeout = globalThis.setTimeout(() => {
73
+ const els = stage?.renderer.getElementsFromPoint(event);
74
+
75
+ const nodes = getNodes(
76
+ els.map((el) => el.id),
77
+ page.value?.items,
78
+ );
79
+
80
+ if (pinned.value && nodes.length === 0) {
81
+ return;
82
+ }
83
+
84
+ nodeList.value = nodes;
85
+
86
+ if (nodeList.value.length > 1) {
87
+ menu.value?.show(pinned.value ? undefined : event);
88
+ }
89
+ }, 1500);
90
+ });
91
+
92
+ stage.on('mouseleave', () => {
93
+ cancel();
94
+ });
95
+
96
+ unWatch();
97
+ },
98
+ {
99
+ immediate: true,
100
+ },
101
+ );
102
+
103
+ const componentMap = computed(() => {
104
+ const map: Record<string, ComponentItem> = {};
105
+ componentListService?.getList().forEach((group) => {
106
+ group.items.forEach((item) => {
107
+ map[item.type] = item;
108
+ });
109
+ });
110
+ return map;
111
+ });
112
+
113
+ const menuData = computed<MenuButton[]>(() =>
114
+ nodeList.value.map((node: MNode) => {
115
+ let text = node.name;
116
+ let icon: string | Component<{}, {}, any> | undefined;
117
+ if (node.type) {
118
+ const item = componentMap.value[node.type];
119
+ text += ` (${item?.text})`;
120
+ icon = item?.icon;
121
+ }
122
+
123
+ return {
124
+ type: 'button',
125
+ text,
126
+ id: node.id,
127
+ icon,
128
+ handler: async () => {
129
+ await editorService?.select(node);
130
+ stage.value?.select(node.id);
131
+ },
132
+ };
133
+ }),
134
+ );
135
+
136
+ const mouseenterHandler = () => {
137
+ // menu的mouseenter后,大概率还有最后一个mousemove事件,这里延迟清除
138
+ globalThis.setTimeout(() => {
139
+ if (timeout) {
140
+ globalThis.clearTimeout(timeout);
141
+ }
142
+ }, 300);
143
+ };
144
+
145
+ const dragMenuHandler = ({ deltaY, deltaX }: OnDrag) => {
146
+ if (!menu.value) return;
147
+
148
+ const { menuPosition } = menu.value;
149
+
150
+ menu.value?.setPosition({
151
+ clientY: menuPosition.top + deltaY,
152
+ clientX: menuPosition.left + deltaX,
153
+ });
154
+ };
155
+
156
+ const closeHandler = () => {
157
+ menu.value?.hide();
158
+ };
159
+ </script>
@@ -0,0 +1,68 @@
1
+ <template>
2
+ <div
3
+ style="
4
+ text-align: center;
5
+ padding: 5px 0;
6
+ font-size: 14px;
7
+ cursor: move;
8
+ display: flex;
9
+ justify-content: space-between;
10
+ align-items: center;
11
+ "
12
+ ref="target"
13
+ >
14
+ <TMagicTooltip placement="top" :content="pinned ? '取消置于顶层自动隐藏' : '置于顶层不消失'">
15
+ <MIcon
16
+ style="margin-left: 10px; cursor: pointer"
17
+ :icon="pinned ? PinnedIcon : PinIcon"
18
+ @click="pinHandler"
19
+ ></MIcon>
20
+ </TMagicTooltip>
21
+
22
+ <span>可选组件</span>
23
+
24
+ <div style="margin-right: 10px">
25
+ <TMagicButton text size="small" @click="closeHandler">
26
+ <MIcon :icon="Close"></MIcon>
27
+ </TMagicButton>
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script lang="ts" setup>
33
+ import { ref } from 'vue';
34
+ import { Close } from '@element-plus/icons-vue';
35
+ import type { OnDrag } from 'gesto';
36
+
37
+ import { TMagicButton, TMagicTooltip } from '@tmagic/design';
38
+
39
+ import MIcon from '@editor/components/Icon.vue';
40
+ import { useGetSo } from '@editor/hooks/use-getso';
41
+ import PinIcon from '@editor/icons/PinIcon.vue';
42
+ import PinnedIcon from '@editor/icons/PinnedIcon.vue';
43
+
44
+ defineOptions({
45
+ name: 'MEditorNodeListMenuTitle',
46
+ });
47
+
48
+ const props = defineProps<{
49
+ pinned: boolean;
50
+ }>();
51
+
52
+ const emit = defineEmits<{
53
+ close: [];
54
+ 'update:pinned': [pinned: boolean];
55
+ change: [e: OnDrag];
56
+ }>();
57
+
58
+ const pinHandler = () => {
59
+ emit('update:pinned', !props.pinned);
60
+ };
61
+
62
+ const closeHandler = () => {
63
+ emit('close');
64
+ };
65
+
66
+ const target = ref<HTMLDivElement>();
67
+ useGetSo(target, emit);
68
+ </script>
@@ -12,6 +12,7 @@
12
12
  width: 60,
13
13
  height: 50,
14
14
  }"
15
+ @click="stageWrap?.container?.focus()"
15
16
  >
16
17
  <div
17
18
  class="m-editor-stage-container"
@@ -23,6 +24,7 @@
23
24
  ></div>
24
25
  <Teleport to="body">
25
26
  <ViewerMenu ref="menu" :is-multi-select="isMultiSelect" :stage-content-menu="stageContentMenu"></ViewerMenu>
27
+ <NodeListMenu ref="nodeList" :is-multi-select="isMultiSelect"></NodeListMenu>
26
28
  </Teleport>
27
29
  </ScrollViewer>
28
30
  </template>
@@ -35,10 +37,11 @@ import type { MContainer } from '@tmagic/schema';
35
37
  import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stage';
36
38
 
37
39
  import ScrollViewer from '@editor/components/ScrollViewer.vue';
40
+ import { useStage } from '@editor/hooks/use-stage';
38
41
  import { Layout, MenuButton, MenuComponent, Services, StageOptions } from '@editor/type';
39
42
  import { getConfig } from '@editor/utils/config';
40
- import { useStage } from '@editor/utils/stage';
41
43
 
44
+ import NodeListMenu from './NodeListMenu.vue';
42
45
  import ViewerMenu from './ViewerMenu.vue';
43
46
 
44
47
  defineOptions({
@@ -164,7 +167,7 @@ const dropHandler = async (e: DragEvent) => {
164
167
  if (e.dataTransfer && parent && stageContainer.value && stage) {
165
168
  const parseDSL = getConfig('parseDSL');
166
169
 
167
- const data = e.dataTransfer.getData('text/html');
170
+ const data = e.dataTransfer.getData('text/json');
168
171
 
169
172
  if (!data) return;
170
173
 
@@ -4,14 +4,16 @@
4
4
 
5
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
 
17
19
  defineOptions({
@@ -32,42 +34,20 @@ const canCenter = ref(false);
32
34
  const node = computed(() => editorService?.get('node'));
33
35
  const nodes = computed(() => editorService?.get('nodes'));
34
36
  const parent = computed(() => editorService?.get('parent'));
35
- const stage = computed(() => editorService?.get('stage'));
36
37
 
37
38
  const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
38
39
  {
39
40
  type: 'button',
40
41
  text: '水平居中',
42
+ icon: markRaw(CenterIcon),
41
43
  display: () => canCenter.value,
42
44
  handler: () => {
43
45
  if (!nodes.value) return;
44
46
  editorService?.alignCenter(nodes.value);
45
47
  },
46
48
  },
47
- {
48
- type: 'button',
49
- text: '复制',
50
- icon: markRaw(CopyDocument),
51
- handler: () => {
52
- nodes.value && editorService?.copy(nodes.value);
53
- canPaste.value = true;
54
- },
55
- },
56
- {
57
- type: 'button',
58
- text: '粘贴',
59
- icon: markRaw(DocumentCopy),
60
- display: () => canPaste.value,
61
- handler: () => {
62
- const rect = menu.value?.$el.getBoundingClientRect();
63
- const parentRect = stage.value?.container?.getBoundingClientRect();
64
- const initialLeft = (rect?.left || 0) - (parentRect?.left || 0);
65
- const initialTop = (rect?.top || 0) - (parentRect?.top || 0);
66
-
67
- if (!nodes.value || nodes.value.length === 0) return;
68
- editorService?.paste({ left: initialLeft, top: initialTop });
69
- },
70
- },
49
+ useCopyMenu(),
50
+ usePasteMenu(menu),
71
51
  {
72
52
  type: 'divider',
73
53
  direction: 'horizontal',
@@ -97,6 +77,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
97
77
  {
98
78
  type: 'button',
99
79
  text: '置顶',
80
+ icon: markRaw(Top),
100
81
  display: () => !isPage(node.value) && !props.isMultiSelect,
101
82
  handler: () => {
102
83
  editorService?.moveLayer(LayerOffset.TOP);
@@ -105,25 +86,19 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
105
86
  {
106
87
  type: 'button',
107
88
  text: '置底',
89
+ icon: markRaw(Bottom),
108
90
  display: () => !isPage(node.value) && !props.isMultiSelect,
109
91
  handler: () => {
110
92
  editorService?.moveLayer(LayerOffset.BOTTOM);
111
93
  },
112
94
  },
95
+ useMoveToMenu(services),
113
96
  {
114
97
  type: 'divider',
115
98
  direction: 'horizontal',
116
99
  display: () => !isPage(node.value) && !props.isMultiSelect,
117
100
  },
118
- {
119
- type: 'button',
120
- text: '删除',
121
- icon: Delete,
122
- display: () => !isPage(node.value),
123
- handler: () => {
124
- nodes.value && editorService?.remove(nodes.value);
125
- },
126
- },
101
+ useDeleteMenu(),
127
102
  {
128
103
  type: 'divider',
129
104
  direction: 'horizontal',