@tmagic/editor 1.5.0 → 1.5.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 (72) hide show
  1. package/dist/{style.css → tmagic-editor.css} +153 -6
  2. package/dist/tmagic-editor.js +906 -527
  3. package/dist/tmagic-editor.umd.cjs +931 -551
  4. package/package.json +10 -10
  5. package/src/Editor.vue +18 -5
  6. package/src/components/CodeBlockEditor.vue +3 -3
  7. package/src/components/CodeParams.vue +2 -2
  8. package/src/components/ContentMenu.vue +18 -18
  9. package/src/components/FloatingBox.vue +3 -3
  10. package/src/components/Resizer.vue +4 -4
  11. package/src/components/ScrollBar.vue +3 -3
  12. package/src/components/ScrollViewer.vue +3 -3
  13. package/src/components/SplitView.vue +2 -2
  14. package/src/components/ToolButton.vue +2 -1
  15. package/src/editorProps.ts +3 -1
  16. package/src/fields/DataSourceInput.vue +2 -2
  17. package/src/fields/DataSourceMethods.vue +2 -2
  18. package/src/fields/DataSourceMocks.vue +0 -1
  19. package/src/fields/EventSelect.vue +0 -1
  20. package/src/fields/KeyValue.vue +2 -2
  21. package/src/hooks/use-code-block-edit.ts +2 -2
  22. package/src/hooks/use-getso.ts +7 -7
  23. package/src/index.ts +2 -1
  24. package/src/layouts/CodeEditor.vue +2 -2
  25. package/src/layouts/Framework.vue +5 -8
  26. package/src/layouts/NavMenu.vue +2 -2
  27. package/src/layouts/page-bar/PageBar.vue +3 -3
  28. package/src/layouts/page-bar/PageBarScrollContainer.vue +3 -3
  29. package/src/layouts/props-panel/FormPanel.vue +123 -0
  30. package/src/layouts/props-panel/PropsPanel.vue +146 -0
  31. package/src/layouts/props-panel/use-style-panel.ts +29 -0
  32. package/src/layouts/sidebar/Sidebar.vue +4 -1
  33. package/src/layouts/sidebar/code-block/CodeBlockList.vue +12 -1
  34. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +35 -4
  35. package/src/layouts/sidebar/code-block/useContentMenu.ts +83 -0
  36. package/src/layouts/sidebar/data-source/DataSourceList.vue +8 -8
  37. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +40 -4
  38. package/src/layouts/sidebar/data-source/useContentMenu.ts +81 -0
  39. package/src/layouts/sidebar/layer/LayerMenu.vue +7 -13
  40. package/src/layouts/sidebar/layer/LayerPanel.vue +11 -4
  41. package/src/layouts/sidebar/layer/use-click.ts +2 -2
  42. package/src/layouts/sidebar/layer/use-keybinding.ts +2 -2
  43. package/src/layouts/workspace/Workspace.vue +9 -2
  44. package/src/layouts/workspace/viewer/NodeListMenu.vue +3 -3
  45. package/src/layouts/workspace/viewer/Stage.vue +18 -6
  46. package/src/layouts/workspace/viewer/StageOverlay.vue +2 -2
  47. package/src/layouts/workspace/viewer/ViewerMenu.vue +4 -6
  48. package/src/services/storage.ts +2 -1
  49. package/src/services/ui.ts +1 -0
  50. package/src/theme/common/var.scss +12 -10
  51. package/src/theme/component-list-panel.scss +9 -7
  52. package/src/theme/content-menu.scss +7 -5
  53. package/src/theme/data-source.scss +3 -1
  54. package/src/theme/floating-box.scss +4 -2
  55. package/src/theme/framework.scss +7 -5
  56. package/src/theme/index.scss +4 -5
  57. package/src/theme/key-value.scss +2 -2
  58. package/src/theme/layer-panel.scss +3 -1
  59. package/src/theme/layout.scss +1 -1
  60. package/src/theme/nav-menu.scss +7 -5
  61. package/src/theme/page-bar.scss +17 -15
  62. package/src/theme/props-panel.scss +81 -1
  63. package/src/theme/resizer.scss +1 -1
  64. package/src/theme/sidebar.scss +4 -2
  65. package/src/theme/stage.scss +3 -1
  66. package/src/theme/theme.scss +28 -28
  67. package/src/theme/tree.scss +14 -12
  68. package/src/type.ts +8 -0
  69. package/src/utils/content-menu.ts +2 -2
  70. package/src/utils/props.ts +45 -3
  71. package/types/index.d.ts +1796 -1230
  72. package/src/layouts/PropsPanel.vue +0 -131
@@ -0,0 +1,123 @@
1
+ <template>
2
+ <div class="m-editor-props-form-panel">
3
+ <slot name="props-form-panel-header"></slot>
4
+
5
+ <TMagicScrollbar>
6
+ <MForm
7
+ ref="configForm"
8
+ :class="propsPanelSize"
9
+ :popper-class="`m-editor-props-panel-popper ${propsPanelSize}`"
10
+ :label-width="labelWidth"
11
+ :label-position="labelPosition"
12
+ :size="propsPanelSize"
13
+ :init-values="values"
14
+ :config="config"
15
+ :extend-state="extendState"
16
+ @change="submit"
17
+ @error="errorHandler"
18
+ ></MForm>
19
+ </TMagicScrollbar>
20
+
21
+ <TMagicButton
22
+ v-if="!disabledShowSrc"
23
+ class="m-editor-props-panel-src-icon"
24
+ circle
25
+ title="源码"
26
+ :type="showSrc ? 'primary' : ''"
27
+ @click="showSrc = !showSrc"
28
+ >
29
+ <MIcon :icon="DocumentIcon"></MIcon>
30
+ </TMagicButton>
31
+
32
+ <CodeEditor
33
+ v-if="showSrc"
34
+ class="m-editor-props-panel-src-code"
35
+ :height="`${editorContentHeight}px`"
36
+ :init-values="codeValueKey ? values[codeValueKey] : values"
37
+ :options="codeOptions"
38
+ :parse="true"
39
+ @save="saveCode"
40
+ ></CodeEditor>
41
+ </div>
42
+ </template>
43
+
44
+ <script setup lang="ts">
45
+ import { computed, getCurrentInstance, inject, onMounted, ref, useTemplateRef, watchEffect } from 'vue';
46
+ import { Document as DocumentIcon } from '@element-plus/icons-vue';
47
+
48
+ import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
49
+ import type { ContainerChangeEventData, FormConfig, FormState, FormValue } from '@tmagic/form';
50
+ import { MForm } from '@tmagic/form';
51
+
52
+ import MIcon from '@editor/components/Icon.vue';
53
+ import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
54
+ import type { Services } from '@editor/type';
55
+
56
+ import CodeEditor from '../CodeEditor.vue';
57
+
58
+ defineSlots<{
59
+ 'props-form-panel-header'(props: {}): any;
60
+ }>();
61
+
62
+ defineOptions({
63
+ name: 'MEditorFormPanel',
64
+ });
65
+
66
+ const props = defineProps<{
67
+ config: FormConfig;
68
+ values: FormValue;
69
+ disabledShowSrc?: boolean;
70
+ labelWidth?: string;
71
+ codeValueKey?: string;
72
+ labelPosition?: string;
73
+ extendState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
74
+ }>();
75
+
76
+ const emit = defineEmits<{
77
+ submit: [values: any, eventData?: ContainerChangeEventData];
78
+ 'submit-error': [e: any];
79
+ 'form-error': [e: any];
80
+ mounted: [internalInstance: any];
81
+ }>();
82
+
83
+ const services = inject<Services>('services');
84
+ const codeOptions = inject('codeOptions', {});
85
+
86
+ const showSrc = ref(false);
87
+ const propsPanelSize = computed(() => services?.uiService.get('propsPanelSize') || 'small');
88
+ const { height: editorContentHeight } = useEditorContentHeight();
89
+ const stage = computed(() => services?.editorService.get('stage'));
90
+
91
+ const configForm = useTemplateRef<InstanceType<typeof MForm>>('configForm');
92
+
93
+ watchEffect(() => {
94
+ if (configForm.value) {
95
+ configForm.value.formState.stage = stage.value;
96
+ configForm.value.formState.services = services;
97
+ }
98
+ });
99
+
100
+ const internalInstance = getCurrentInstance();
101
+ onMounted(() => {
102
+ emit('mounted', internalInstance);
103
+ });
104
+
105
+ const submit = async (v: FormValue, eventData: ContainerChangeEventData) => {
106
+ try {
107
+ const values = await configForm.value?.submitForm();
108
+ emit('submit', values, eventData);
109
+ } catch (e: any) {
110
+ emit('submit-error', e);
111
+ }
112
+ };
113
+
114
+ const errorHandler = (e: any) => {
115
+ emit('form-error', e);
116
+ };
117
+
118
+ const saveCode = (values: any) => {
119
+ emit('submit', props.codeValueKey ? { [props.codeValueKey]: values } : values);
120
+ };
121
+
122
+ defineExpose({ configForm, submit });
123
+ </script>
@@ -0,0 +1,146 @@
1
+ <template>
2
+ <div class="m-editor-props-panel" v-show="nodes.length === 1">
3
+ <slot name="props-panel-header"></slot>
4
+ <FormPanel
5
+ ref="propertyFormPanel"
6
+ class="m-editor-props-property-panel"
7
+ :class="{ 'show-style-panel': showStylePanel }"
8
+ :config="curFormConfig"
9
+ :values="values"
10
+ :disabledShowSrc="disabledShowSrc"
11
+ :extendState="extendState"
12
+ @submit="submit"
13
+ @submit-error="errorHandler"
14
+ @form-error="errorHandler"
15
+ @mounted="mountedHandler"
16
+ ></FormPanel>
17
+
18
+ <FormPanel
19
+ v-if="showStylePanel"
20
+ class="m-editor-props-style-panel"
21
+ label-position="top"
22
+ code-value-key="style"
23
+ :config="styleFormConfig"
24
+ :values="values"
25
+ :disabledShowSrc="disabledShowSrc"
26
+ :extendState="extendState"
27
+ @submit="submit"
28
+ @submit-error="errorHandler"
29
+ @form-error="errorHandler"
30
+ >
31
+ <template #props-form-panel-header>
32
+ <div class="m-editor-props-style-panel-title">
33
+ <span>样式</span>
34
+ <div>
35
+ <TMagicButton link size="small" @click="closeStylePanelHandler"><MIcon :icon="Close"></MIcon></TMagicButton>
36
+ </div>
37
+ </div>
38
+ </template>
39
+ </FormPanel>
40
+
41
+ <TMagicButton
42
+ v-if="!showStylePanel"
43
+ class="m-editor-props-panel-style-icon"
44
+ circle
45
+ :type="showStylePanel ? 'primary' : ''"
46
+ @click="showStylePanelHandler"
47
+ >
48
+ <MIcon :icon="Sugar"></MIcon>
49
+ </TMagicButton>
50
+ </div>
51
+ </template>
52
+
53
+ <script lang="ts" setup>
54
+ import { computed, inject, onBeforeUnmount, ref, useTemplateRef, watchEffect } from 'vue';
55
+ import { Close, Sugar } from '@element-plus/icons-vue';
56
+
57
+ import type { MNode } from '@tmagic/core';
58
+ import { TMagicButton } from '@tmagic/design';
59
+ import type { ContainerChangeEventData, FormState, FormValue } from '@tmagic/form';
60
+
61
+ import MIcon from '@editor/components/Icon.vue';
62
+ import type { PropsPanelSlots, Services } from '@editor/type';
63
+ import { styleTabConfig } from '@editor/utils';
64
+
65
+ import FormPanel from './FormPanel.vue';
66
+ import { useStylePanel } from './use-style-panel';
67
+
68
+ defineSlots<PropsPanelSlots>();
69
+
70
+ defineOptions({
71
+ name: 'MEditorPropsPanel',
72
+ });
73
+
74
+ defineProps<{
75
+ disabledShowSrc?: boolean;
76
+ extendState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
77
+ }>();
78
+
79
+ const emit = defineEmits<{
80
+ 'submit-error': [e: any];
81
+ 'form-error': [e: any];
82
+ mounted: [internalInstance: InstanceType<typeof FormPanel>];
83
+ }>();
84
+
85
+ const services = inject<Services>('services');
86
+
87
+ const values = ref<FormValue>({});
88
+ // ts类型应该是FormConfig, 但是打包时会出错,所以暂时用any
89
+ const curFormConfig = ref<any>([]);
90
+ const node = computed(() => services?.editorService.get('node'));
91
+ const nodes = computed(() => services?.editorService.get('nodes') || []);
92
+
93
+ const styleFormConfig = [
94
+ {
95
+ tabPosition: 'right',
96
+ items: styleTabConfig.items,
97
+ },
98
+ ];
99
+
100
+ const init = async () => {
101
+ if (!node.value) {
102
+ curFormConfig.value = [];
103
+ return;
104
+ }
105
+
106
+ const type = node.value.type || (node.value.items ? 'container' : 'text');
107
+ curFormConfig.value = (await services?.propsService.getPropsConfig(type)) || [];
108
+ values.value = node.value;
109
+ };
110
+
111
+ watchEffect(init);
112
+ services?.propsService.on('props-configs-change', init);
113
+
114
+ onBeforeUnmount(() => {
115
+ services?.propsService.off('props-configs-change', init);
116
+ });
117
+
118
+ const submit = async (v: MNode, eventData?: ContainerChangeEventData) => {
119
+ try {
120
+ if (!v.id) {
121
+ v.id = values.value.id;
122
+ }
123
+ services?.editorService.update(v, { changeRecords: eventData?.changeRecords });
124
+ } catch (e: any) {
125
+ emit('submit-error', e);
126
+ }
127
+ };
128
+
129
+ const errorHandler = (e: any) => {
130
+ emit('form-error', e);
131
+ };
132
+
133
+ const mountedHandler = (e: InstanceType<typeof FormPanel>) => {
134
+ emit('mounted', e);
135
+ };
136
+
137
+ const { showStylePanel, showStylePanelHandler, closeStylePanelHandler } = useStylePanel(services);
138
+
139
+ const propertyFormPanelRef = useTemplateRef<InstanceType<typeof FormPanel>>('propertyFormPanel');
140
+ defineExpose({
141
+ getFormState() {
142
+ return propertyFormPanelRef.value?.configForm?.formState;
143
+ },
144
+ submit,
145
+ });
146
+ </script>
@@ -0,0 +1,29 @@
1
+ import { computed } from 'vue';
2
+
3
+ import { Protocol } from '@editor/services/storage';
4
+ import { Services } from '@editor/type';
5
+
6
+ export const useStylePanel = (services?: Services) => {
7
+ const showStylePanelStorageKey = 'props-panel-show-style-panel';
8
+ const showStylePanelStorageValue = services?.storageService.getItem(showStylePanelStorageKey, {
9
+ protocol: Protocol.BOOLEAN,
10
+ });
11
+ if (typeof showStylePanelStorageValue === 'boolean') {
12
+ services?.uiService.set('showStylePanel', showStylePanelStorageValue);
13
+ }
14
+ const showStylePanel = computed(() => services?.uiService.get('showStylePanel') ?? true);
15
+ const showStylePanelHandler = () => {
16
+ services?.uiService.set('showStylePanel', true);
17
+ services?.storageService.setItem(showStylePanelStorageKey, true, { protocol: Protocol.BOOLEAN });
18
+ };
19
+ const closeStylePanelHandler = () => {
20
+ services?.uiService.set('showStylePanel', false);
21
+ services?.storageService.setItem(showStylePanelStorageKey, false, { protocol: Protocol.BOOLEAN });
22
+ };
23
+
24
+ return {
25
+ showStylePanel,
26
+ showStylePanelHandler,
27
+ closeStylePanelHandler,
28
+ };
29
+ };
@@ -158,6 +158,7 @@ import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height'
158
158
  import { useFloatBox } from '@editor/hooks/use-float-box';
159
159
  import {
160
160
  ColumnLayout,
161
+ CustomContentMenuFunction,
161
162
  type MenuButton,
162
163
  type MenuComponent,
163
164
  type Services,
@@ -185,7 +186,7 @@ const props = withDefaults(
185
186
  layerContentMenu: (MenuButton | MenuComponent)[];
186
187
  indent?: number;
187
188
  nextLevelIndentIncrement?: number;
188
- customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
189
+ customContentMenu: CustomContentMenuFunction;
189
190
  }>(),
190
191
  {
191
192
  data: () => ({
@@ -254,6 +255,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
254
255
  props: {
255
256
  indent: props.indent,
256
257
  nextLevelIndentIncrement: props.nextLevelIndentIncrement,
258
+ customContentMenu: props.customContentMenu,
257
259
  },
258
260
  slots: {},
259
261
  },
@@ -266,6 +268,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
266
268
  props: {
267
269
  indent: props.indent,
268
270
  nextLevelIndentIncrement: props.nextLevelIndentIncrement,
271
+ customContentMenu: props.customContentMenu,
269
272
  },
270
273
  slots: {},
271
274
  },
@@ -5,6 +5,7 @@
5
5
  :indent="indent"
6
6
  :next-level-indent-increment="nextLevelIndentIncrement"
7
7
  @node-click="clickHandler"
8
+ @node-contextmenu="nodeContentMenuHandler"
8
9
  >
9
10
  <template #tree-node-label="{ data }">
10
11
  <div
@@ -62,6 +63,7 @@ const props = defineProps<{
62
63
  const emit = defineEmits<{
63
64
  edit: [id: string];
64
65
  remove: [id: string];
66
+ 'node-contextmenu': [event: MouseEvent, data: TreeNodeData];
65
67
  }>();
66
68
 
67
69
  const services = inject<Services>('services');
@@ -162,12 +164,21 @@ const deleteCode = async (id: string) => {
162
164
  if (typeof props.customError === 'function') {
163
165
  props.customError(id, existBinds ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
164
166
  } else {
165
- tMagicMessage.error('代码块删除失败');
167
+ if (existBinds) {
168
+ tMagicMessage.error('代码块存在绑定关系,不可删除');
169
+ } else {
170
+ tMagicMessage.error('代码块不可删除');
171
+ }
166
172
  }
167
173
  }
168
174
  };
169
175
 
176
+ const nodeContentMenuHandler = (event: MouseEvent, data: TreeNodeData) => {
177
+ emit('node-contextmenu', event, data);
178
+ };
179
+
170
180
  defineExpose({
171
181
  filter: filterTextChangeHandler,
182
+ deleteCode,
172
183
  });
173
184
  </script>
@@ -18,6 +18,7 @@
18
18
  :next-level-indent-increment="nextLevelIndentIncrement"
19
19
  @edit="editCode"
20
20
  @remove="deleteCode"
21
+ @node-contextmenu="nodeContentMenuHandler"
21
22
  >
22
23
  <template #code-block-panel-tool="{ id, data }">
23
24
  <slot name="code-block-panel-tool" :id="id" :data="data"></slot>
@@ -32,20 +33,40 @@
32
33
  :content="codeConfig"
33
34
  @submit="submitCodeBlockHandler"
34
35
  ></CodeBlockEditor>
36
+
37
+ <Teleport to="body">
38
+ <ContentMenu
39
+ v-if="menuData.length"
40
+ :menu-data="menuData"
41
+ ref="menu"
42
+ style="overflow: initial"
43
+ @hide="contentMenuHideHandler"
44
+ ></ContentMenu>
45
+ </Teleport>
35
46
  </template>
36
47
 
37
48
  <script setup lang="ts">
38
- import { computed, inject, ref } from 'vue';
49
+ import { computed, inject, useTemplateRef } from 'vue';
39
50
 
40
51
  import type { Id } from '@tmagic/core';
41
52
  import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
42
53
 
43
54
  import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue';
55
+ import ContentMenu from '@editor/components/ContentMenu.vue';
44
56
  import SearchInput from '@editor/components/SearchInput.vue';
45
57
  import { useCodeBlockEdit } from '@editor/hooks/use-code-block-edit';
46
- import type { CodeBlockListPanelSlots, CodeDeleteErrorType, EventBus, Services } from '@editor/type';
58
+ import type {
59
+ CodeBlockListPanelSlots,
60
+ CodeDeleteErrorType,
61
+ CustomContentMenuFunction,
62
+ EventBus,
63
+ MenuButton,
64
+ MenuComponent,
65
+ Services,
66
+ } from '@editor/type';
47
67
 
48
68
  import CodeBlockList from './CodeBlockList.vue';
69
+ import { useContentMenu } from './useContentMenu';
49
70
 
50
71
  defineSlots<CodeBlockListPanelSlots>();
51
72
 
@@ -53,10 +74,11 @@ defineOptions({
53
74
  name: 'MEditorCodeBlockListPanel',
54
75
  });
55
76
 
56
- defineProps<{
77
+ const props = defineProps<{
57
78
  indent?: number;
58
79
  nextLevelIndentIncrement?: number;
59
80
  customError?: (id: Id, errorType: CodeDeleteErrorType) => any;
81
+ customContentMenu: CustomContentMenuFunction;
60
82
  }>();
61
83
 
62
84
  const eventBus = inject<EventBus>('eventBus');
@@ -67,7 +89,7 @@ const editable = computed(() => codeBlockService?.getEditStatus());
67
89
  const { codeBlockEditor, codeConfig, editCode, deleteCode, createCodeBlock, submitCodeBlockHandler } =
68
90
  useCodeBlockEdit(codeBlockService);
69
91
 
70
- const codeBlockList = ref<InstanceType<typeof CodeBlockList>>();
92
+ const codeBlockList = useTemplateRef<InstanceType<typeof CodeBlockList>>('codeBlockList');
71
93
 
72
94
  const filterTextChangeHandler = (val: string) => {
73
95
  codeBlockList.value?.filter(val);
@@ -76,4 +98,13 @@ const filterTextChangeHandler = (val: string) => {
76
98
  eventBus?.on('edit-code', (id: string) => {
77
99
  editCode(id);
78
100
  });
101
+
102
+ const {
103
+ nodeContentMenuHandler,
104
+ menuData: contentMenuData,
105
+ contentMenuHideHandler,
106
+ } = useContentMenu((id: string) => {
107
+ codeBlockList.value?.deleteCode(id);
108
+ });
109
+ const menuData = computed<(MenuButton | MenuComponent)[]>(() => props.customContentMenu(contentMenuData, 'code-block'));
79
110
  </script>
@@ -0,0 +1,83 @@
1
+ import { inject, markRaw, useTemplateRef } from 'vue';
2
+ import { CopyDocument, Delete, Edit } from '@element-plus/icons-vue';
3
+ import { cloneDeep } from 'lodash-es';
4
+
5
+ import ContentMenu from '@editor/components/ContentMenu.vue';
6
+ import type { EventBus, MenuButton, MenuComponent, Services, TreeNodeData } from '@editor/type';
7
+
8
+ export const useContentMenu = (deleteCode: (id: string) => void) => {
9
+ const eventBus = inject<EventBus>('eventBus');
10
+ const menuRef = useTemplateRef<InstanceType<typeof ContentMenu>>('menu');
11
+
12
+ let selectId = '';
13
+
14
+ const menuData: (MenuButton | MenuComponent)[] = [
15
+ {
16
+ type: 'button',
17
+ text: '编辑',
18
+ icon: Edit,
19
+ display: (services) => services?.codeBlockService?.getEditStatus() ?? true,
20
+ handler: () => {
21
+ if (!selectId) {
22
+ return;
23
+ }
24
+
25
+ eventBus?.emit('edit-code', selectId);
26
+ },
27
+ },
28
+ {
29
+ type: 'button',
30
+ text: '复制并粘贴至当前',
31
+ icon: markRaw(CopyDocument),
32
+ handler: async ({ codeBlockService }: Services) => {
33
+ if (!selectId) {
34
+ return;
35
+ }
36
+
37
+ const codeBlock = codeBlockService.getCodeContentById(selectId);
38
+ if (!codeBlock) {
39
+ return;
40
+ }
41
+
42
+ const newCodeId = await codeBlockService.getUniqueId();
43
+
44
+ codeBlockService.setCodeDslById(newCodeId, cloneDeep(codeBlock));
45
+ },
46
+ },
47
+ {
48
+ type: 'button',
49
+ text: '删除',
50
+ icon: Delete,
51
+ handler: () => {
52
+ if (!selectId) {
53
+ return;
54
+ }
55
+
56
+ deleteCode(selectId);
57
+ },
58
+ },
59
+ ];
60
+
61
+ const nodeContentMenuHandler = (event: MouseEvent, data: TreeNodeData) => {
62
+ event.preventDefault();
63
+
64
+ if (data.type === 'code') {
65
+ menuRef.value?.show(event);
66
+ if (data.id) {
67
+ selectId = `${data.id}`;
68
+ } else {
69
+ selectId = '';
70
+ }
71
+ }
72
+ };
73
+
74
+ const contentMenuHideHandler = () => {
75
+ selectId = '';
76
+ };
77
+
78
+ return {
79
+ menuData,
80
+ nodeContentMenuHandler,
81
+ contentMenuHideHandler,
82
+ };
83
+ };
@@ -5,6 +5,7 @@
5
5
  :indent="indent"
6
6
  :next-level-indent-increment="nextLevelIndentIncrement"
7
7
  @node-click="clickHandler"
8
+ @node-contextmenu="nodeContentMenuHandler"
8
9
  >
9
10
  <template #tree-node-label="{ data }">
10
11
  <div
@@ -37,13 +38,13 @@ import { computed, inject } from 'vue';
37
38
  import { Close, Edit, View } from '@element-plus/icons-vue';
38
39
 
39
40
  import { DepData, DepTargetType, Id, MNode } from '@tmagic/core';
40
- import { tMagicMessageBox, TMagicTag, TMagicTooltip } from '@tmagic/design';
41
+ import { TMagicTag, TMagicTooltip } from '@tmagic/design';
41
42
 
42
43
  import Icon from '@editor/components/Icon.vue';
43
44
  import Tree from '@editor/components/Tree.vue';
44
45
  import { useFilter } from '@editor/hooks/use-filter';
45
46
  import { useNodeStatus } from '@editor/hooks/use-node-status';
46
- import type { DataSourceListSlots, Services } from '@editor/type';
47
+ import type { DataSourceListSlots, Services, TreeNodeData } from '@editor/type';
47
48
 
48
49
  defineSlots<DataSourceListSlots>();
49
50
 
@@ -59,6 +60,7 @@ defineProps<{
59
60
  const emit = defineEmits<{
60
61
  edit: [id: string];
61
62
  remove: [id: string];
63
+ 'node-contextmenu': [event: MouseEvent, data: TreeNodeData];
62
64
  }>();
63
65
 
64
66
  const { depService, editorService, dataSourceService } = inject<Services>('services') || {};
@@ -159,12 +161,6 @@ const editHandler = (id: string) => {
159
161
  };
160
162
 
161
163
  const removeHandler = async (id: string) => {
162
- await tMagicMessageBox.confirm('确定删除?', '提示', {
163
- confirmButtonText: '确定',
164
- cancelButtonText: '取消',
165
- type: 'warning',
166
- });
167
-
168
164
  emit('remove', id);
169
165
  };
170
166
 
@@ -181,6 +177,10 @@ const clickHandler = (event: MouseEvent, data: any) => {
181
177
  }
182
178
  };
183
179
 
180
+ const nodeContentMenuHandler = (event: MouseEvent, data: TreeNodeData) => {
181
+ emit('node-contextmenu', event, data);
182
+ };
183
+
184
184
  defineExpose({
185
185
  filter: filterTextChangeHandler,
186
186
  });
@@ -35,6 +35,7 @@
35
35
  :next-level-indent-increment="nextLevelIndentIncrement"
36
36
  @edit="editHandler"
37
37
  @remove="removeHandler"
38
+ @node-contextmenu="nodeContentMenuHandler"
38
39
  ></DataSourceList>
39
40
  </TMagicScrollbar>
40
41
 
@@ -45,21 +46,40 @@
45
46
  :title="dialogTitle"
46
47
  @submit="submitDataSourceHandler"
47
48
  ></DataSourceConfigPanel>
49
+
50
+ <Teleport to="body">
51
+ <ContentMenu
52
+ v-if="menuData.length"
53
+ :menu-data="menuData"
54
+ ref="menu"
55
+ style="overflow: initial"
56
+ @hide="contentMenuHideHandler"
57
+ ></ContentMenu>
58
+ </Teleport>
48
59
  </template>
49
60
 
50
61
  <script setup lang="ts">
51
62
  import { computed, inject, ref } from 'vue';
52
63
  import { mergeWith } from 'lodash-es';
53
64
 
54
- import { TMagicButton, TMagicPopover, TMagicScrollbar } from '@tmagic/design';
65
+ import { TMagicButton, tMagicMessageBox, TMagicPopover, TMagicScrollbar } from '@tmagic/design';
55
66
 
67
+ import ContentMenu from '@editor/components/ContentMenu.vue';
56
68
  import SearchInput from '@editor/components/SearchInput.vue';
57
69
  import ToolButton from '@editor/components/ToolButton.vue';
58
70
  import { useDataSourceEdit } from '@editor/hooks/use-data-source-edit';
59
- import type { DataSourceListSlots, EventBus, Services } from '@editor/type';
71
+ import type {
72
+ CustomContentMenuFunction,
73
+ DataSourceListSlots,
74
+ EventBus,
75
+ MenuButton,
76
+ MenuComponent,
77
+ Services,
78
+ } from '@editor/type';
60
79
 
61
80
  import DataSourceConfigPanel from './DataSourceConfigPanel.vue';
62
81
  import DataSourceList from './DataSourceList.vue';
82
+ import { useContentMenu } from './useContentMenu';
63
83
 
64
84
  defineSlots<DataSourceListSlots>();
65
85
 
@@ -67,9 +87,10 @@ defineOptions({
67
87
  name: 'MEditorDataSourceListPanel',
68
88
  });
69
89
 
70
- defineProps<{
90
+ const props = defineProps<{
71
91
  indent?: number;
72
92
  nextLevelIndentIncrement?: number;
93
+ customContentMenu: CustomContentMenuFunction;
73
94
  }>();
74
95
 
75
96
  const eventBus = inject<EventBus>('eventBus');
@@ -105,7 +126,13 @@ const addHandler = (type: string) => {
105
126
  editDialog.value.show();
106
127
  };
107
128
 
108
- const removeHandler = (id: string) => {
129
+ const removeHandler = async (id: string) => {
130
+ await tMagicMessageBox.confirm('确定删除?', '提示', {
131
+ confirmButtonText: '确定',
132
+ cancelButtonText: '取消',
133
+ type: 'warning',
134
+ });
135
+
109
136
  dataSourceService?.remove(id);
110
137
  };
111
138
 
@@ -118,4 +145,13 @@ const filterTextChangeHandler = (val: string) => {
118
145
  eventBus?.on('edit-data-source', (id: string) => {
119
146
  editHandler(id);
120
147
  });
148
+
149
+ eventBus?.on('remove-data-source', (id: string) => {
150
+ removeHandler(id);
151
+ });
152
+
153
+ const { nodeContentMenuHandler, menuData: contentMenuData, contentMenuHideHandler } = useContentMenu();
154
+ const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
155
+ props.customContentMenu(contentMenuData, 'data-source'),
156
+ );
121
157
  </script>