@tmagic/editor 1.5.0-beta.9 → 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 (99) hide show
  1. package/dist/{style.css → tmagic-editor.css} +228 -22
  2. package/dist/tmagic-editor.js +1670 -1012
  3. package/dist/tmagic-editor.umd.cjs +1705 -1048
  4. package/package.json +14 -12
  5. package/src/Editor.vue +24 -6
  6. package/src/components/CodeBlockEditor.vue +14 -8
  7. package/src/components/CodeParams.vue +5 -5
  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 +6 -2
  16. package/src/fields/Code.vue +1 -2
  17. package/src/fields/CodeSelect.vue +13 -11
  18. package/src/fields/CodeSelectCol.vue +32 -5
  19. package/src/fields/CondOpSelect.vue +5 -2
  20. package/src/fields/DataSourceFields.vue +31 -12
  21. package/src/fields/DataSourceInput.vue +2 -2
  22. package/src/fields/DataSourceMethods.vue +72 -14
  23. package/src/fields/DataSourceMocks.vue +0 -1
  24. package/src/fields/DisplayConds.vue +8 -3
  25. package/src/fields/EventSelect.vue +28 -12
  26. package/src/fields/KeyValue.vue +17 -12
  27. package/src/fields/UISelect.vue +6 -3
  28. package/src/hooks/index.ts +0 -1
  29. package/src/hooks/use-code-block-edit.ts +3 -3
  30. package/src/hooks/use-data-source-edit.ts +3 -2
  31. package/src/hooks/use-filter.ts +1 -1
  32. package/src/hooks/use-getso.ts +7 -7
  33. package/src/hooks/use-node-status.ts +2 -2
  34. package/src/index.ts +2 -1
  35. package/src/initService.ts +177 -74
  36. package/src/layouts/CodeEditor.vue +2 -2
  37. package/src/layouts/Framework.vue +13 -12
  38. package/src/layouts/NavMenu.vue +2 -2
  39. package/src/layouts/page-bar/AddButton.vue +29 -9
  40. package/src/layouts/page-bar/PageBar.vue +79 -66
  41. package/src/layouts/page-bar/PageBarScrollContainer.vue +84 -98
  42. package/src/layouts/page-bar/PageList.vue +5 -3
  43. package/src/layouts/page-bar/Search.vue +67 -0
  44. package/src/layouts/props-panel/FormPanel.vue +123 -0
  45. package/src/layouts/props-panel/PropsPanel.vue +146 -0
  46. package/src/layouts/props-panel/use-style-panel.ts +29 -0
  47. package/src/layouts/sidebar/Sidebar.vue +7 -1
  48. package/src/layouts/sidebar/code-block/CodeBlockList.vue +18 -2
  49. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +35 -4
  50. package/src/layouts/sidebar/code-block/useContentMenu.ts +83 -0
  51. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +7 -5
  52. package/src/layouts/sidebar/data-source/DataSourceList.vue +13 -8
  53. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +40 -4
  54. package/src/layouts/sidebar/data-source/useContentMenu.ts +81 -0
  55. package/src/layouts/sidebar/layer/LayerMenu.vue +7 -13
  56. package/src/layouts/sidebar/layer/LayerPanel.vue +11 -4
  57. package/src/layouts/sidebar/layer/use-click.ts +2 -2
  58. package/src/layouts/sidebar/layer/use-keybinding.ts +2 -2
  59. package/src/layouts/sidebar/layer/use-node-status.ts +2 -3
  60. package/src/layouts/workspace/Workspace.vue +13 -3
  61. package/src/layouts/workspace/viewer/NodeListMenu.vue +3 -3
  62. package/src/layouts/workspace/viewer/Stage.vue +41 -11
  63. package/src/layouts/workspace/viewer/StageOverlay.vue +2 -2
  64. package/src/layouts/workspace/viewer/ViewerMenu.vue +4 -6
  65. package/src/services/dataSource.ts +12 -5
  66. package/src/services/dep.ts +61 -13
  67. package/src/services/editor.ts +46 -51
  68. package/src/services/storage.ts +2 -1
  69. package/src/services/ui.ts +1 -0
  70. package/src/theme/common/var.scss +12 -10
  71. package/src/theme/component-list-panel.scss +9 -7
  72. package/src/theme/content-menu.scss +7 -5
  73. package/src/theme/data-source.scss +3 -1
  74. package/src/theme/floating-box.scss +4 -2
  75. package/src/theme/framework.scss +7 -5
  76. package/src/theme/index.scss +4 -5
  77. package/src/theme/key-value.scss +2 -2
  78. package/src/theme/layer-panel.scss +3 -1
  79. package/src/theme/layout.scss +1 -1
  80. package/src/theme/nav-menu.scss +7 -5
  81. package/src/theme/page-bar.scss +51 -27
  82. package/src/theme/props-panel.scss +81 -1
  83. package/src/theme/resizer.scss +1 -1
  84. package/src/theme/search-input.scss +1 -0
  85. package/src/theme/sidebar.scss +4 -2
  86. package/src/theme/stage.scss +3 -1
  87. package/src/theme/theme.scss +28 -28
  88. package/src/theme/tree.scss +14 -12
  89. package/src/type.ts +10 -1
  90. package/src/utils/content-menu.ts +2 -2
  91. package/src/utils/data-source/formConfigs/http.ts +2 -0
  92. package/src/utils/data-source/index.ts +2 -2
  93. package/src/utils/editor.ts +78 -22
  94. package/src/utils/idle-task.ts +36 -4
  95. package/src/utils/props.ts +48 -6
  96. package/types/index.d.ts +2312 -2080
  97. package/src/hooks/use-data-source-method.ts +0 -100
  98. package/src/layouts/PropsPanel.vue +0 -131
  99. package/src/layouts/page-bar/SwitchTypeButton.vue +0 -45
@@ -0,0 +1,81 @@
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 = () => {
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?.dataSourceService?.get('editable') ?? true,
20
+ handler: () => {
21
+ if (!selectId) {
22
+ return;
23
+ }
24
+
25
+ eventBus?.emit('edit-data-source', selectId);
26
+ },
27
+ },
28
+ {
29
+ type: 'button',
30
+ text: '复制并粘贴至当前',
31
+ icon: markRaw(CopyDocument),
32
+ handler: ({ dataSourceService }: Services) => {
33
+ if (!selectId) {
34
+ return;
35
+ }
36
+
37
+ const ds = dataSourceService.getDataSourceById(selectId);
38
+ if (!ds) {
39
+ return;
40
+ }
41
+
42
+ dataSourceService.add(cloneDeep(ds));
43
+ },
44
+ },
45
+ {
46
+ type: 'button',
47
+ text: '删除',
48
+ icon: Delete,
49
+ handler: () => {
50
+ if (!selectId) {
51
+ return;
52
+ }
53
+
54
+ eventBus?.emit('remove-data-source', selectId);
55
+ },
56
+ },
57
+ ];
58
+
59
+ const nodeContentMenuHandler = (event: MouseEvent, data: TreeNodeData) => {
60
+ event.preventDefault();
61
+
62
+ if (data.type === 'ds') {
63
+ menuRef.value?.show(event);
64
+ if (data.id) {
65
+ selectId = `${data.id}`;
66
+ } else {
67
+ selectId = '';
68
+ }
69
+ }
70
+ };
71
+
72
+ const contentMenuHideHandler = () => {
73
+ selectId = '';
74
+ };
75
+
76
+ return {
77
+ menuData,
78
+ nodeContentMenuHandler,
79
+ contentMenuHideHandler,
80
+ };
81
+ };
@@ -3,37 +3,31 @@
3
3
  </template>
4
4
 
5
5
  <script lang="ts" setup>
6
- import { computed, inject, markRaw, ref } from 'vue';
6
+ import { computed, inject, markRaw, useTemplateRef } from 'vue';
7
7
  import { Files, Plus } from '@element-plus/icons-vue';
8
8
 
9
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';
13
- import type { ComponentGroup, MenuButton, MenuComponent, Services } from '@editor/type';
13
+ import type { ComponentGroup, CustomContentMenuFunction, MenuButton, MenuComponent, Services } from '@editor/type';
14
14
  import { useCopyMenu, useDeleteMenu, useMoveToMenu, usePasteMenu } from '@editor/utils/content-menu';
15
15
 
16
16
  defineOptions({
17
17
  name: 'MEditorLayerMenu',
18
18
  });
19
19
 
20
- const props = withDefaults(
21
- defineProps<{
22
- layerContentMenu: (MenuButton | MenuComponent)[];
23
- customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
24
- }>(),
25
- {
26
- layerContentMenu: () => [],
27
- customContentMenu: (menus: (MenuButton | MenuComponent)[]) => menus,
28
- },
29
- );
20
+ const props = defineProps<{
21
+ layerContentMenu: (MenuButton | MenuComponent)[];
22
+ customContentMenu: CustomContentMenuFunction;
23
+ }>();
30
24
 
31
25
  const emit = defineEmits<{
32
26
  'collapse-all': [];
33
27
  }>();
34
28
 
35
29
  const services = inject<Services>('services');
36
- const menu = ref<InstanceType<typeof ContentMenu>>();
30
+ const menu = useTemplateRef<InstanceType<typeof ContentMenu>>('menu');
37
31
  const node = computed(() => services?.editorService.get('node'));
38
32
  const nodes = computed(() => services?.editorService.get('nodes'));
39
33
  const componentList = computed(() => services?.componentListService.getList() || []);
@@ -47,7 +47,7 @@
47
47
  </template>
48
48
 
49
49
  <script setup lang="ts">
50
- import { computed, inject, ref } from 'vue';
50
+ import { computed, inject, useTemplateRef } from 'vue';
51
51
 
52
52
  import type { MNode } from '@tmagic/core';
53
53
  import { TMagicScrollbar } from '@tmagic/design';
@@ -55,7 +55,14 @@ import { TMagicScrollbar } from '@tmagic/design';
55
55
  import SearchInput from '@editor/components/SearchInput.vue';
56
56
  import Tree from '@editor/components/Tree.vue';
57
57
  import { useFilter } from '@editor/hooks/use-filter';
58
- import type { LayerPanelSlots, MenuButton, MenuComponent, Services, TreeNodeData } from '@editor/type';
58
+ import type {
59
+ CustomContentMenuFunction,
60
+ LayerPanelSlots,
61
+ MenuButton,
62
+ MenuComponent,
63
+ Services,
64
+ TreeNodeData,
65
+ } from '@editor/type';
59
66
 
60
67
  import LayerMenu from './LayerMenu.vue';
61
68
  import LayerNodeTool from './LayerNodeTool.vue';
@@ -74,13 +81,13 @@ defineProps<{
74
81
  layerContentMenu: (MenuButton | MenuComponent)[];
75
82
  indent?: number;
76
83
  nextLevelIndentIncrement?: number;
77
- customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
84
+ customContentMenu: CustomContentMenuFunction;
78
85
  }>();
79
86
 
80
87
  const services = inject<Services>('services');
81
88
  const editorService = services?.editorService;
82
89
 
83
- const tree = ref<InstanceType<typeof Tree>>();
90
+ const tree = useTemplateRef<InstanceType<typeof Tree>>('tree');
84
91
 
85
92
  const page = computed(() => editorService?.get('page'));
86
93
  const nodeData = computed<TreeNodeData[]>(() => (!page.value ? [] : [page.value]));
@@ -1,4 +1,4 @@
1
- import { computed, type ComputedRef, nextTick, type Ref, ref } from 'vue';
1
+ import { computed, type ComputedRef, nextTick, type Ref, useTemplateRef } from 'vue';
2
2
  import { throttle } from 'lodash-es';
3
3
 
4
4
  import { Id, MNode } from '@tmagic/core';
@@ -99,7 +99,7 @@ export const useClick = (
99
99
  };
100
100
 
101
101
  // 右键菜单
102
- const menu = ref<InstanceType<typeof LayerMenu>>();
102
+ const menu = useTemplateRef<InstanceType<typeof LayerMenu>>('menu');
103
103
 
104
104
  return {
105
105
  menu,
@@ -1,4 +1,4 @@
1
- import { type Ref, ref, watchEffect } from 'vue';
1
+ import { ref, type ShallowRef, watchEffect } from 'vue';
2
2
 
3
3
  import Tree from '@editor/components/Tree.vue';
4
4
  import type { Services } from '@editor/type';
@@ -6,7 +6,7 @@ import { KeyBindingContainerKey } from '@editor/utils/keybinding-config';
6
6
 
7
7
  export const useKeybinding = (
8
8
  services: Services | undefined,
9
- container: Ref<InstanceType<typeof Tree> | undefined>,
9
+ container: ShallowRef<InstanceType<typeof Tree> | null>,
10
10
  ) => {
11
11
  const keybindingService = services?.keybindingService;
12
12
 
@@ -1,10 +1,9 @@
1
1
  import { computed, ref, watch } from 'vue';
2
2
 
3
3
  import type { Id, MNode, MPage, MPageFragment } from '@tmagic/core';
4
- import { getNodePath, isPage, isPageFragment } from '@tmagic/utils';
4
+ import { getNodePath, isPage, isPageFragment, traverseNode } from '@tmagic/utils';
5
5
 
6
- import { LayerNodeStatus, Services } from '@editor/type';
7
- import { traverseNode } from '@editor/utils';
6
+ import type { LayerNodeStatus, Services } from '@editor/type';
8
7
  import { updateStatus } from '@editor/utils/tree';
9
8
 
10
9
  const createPageNodeStatus = (page: MPage | MPageFragment, initialLayerNodeStatus?: Map<Id, LayerNodeStatus>) => {
@@ -4,7 +4,8 @@
4
4
 
5
5
  <slot name="stage">
6
6
  <MagicStage
7
- v-if="page"
7
+ v-if="page && (stageOptions?.render || stageOptions?.runtimeUrl)"
8
+ :stage-options="stageOptions"
8
9
  :disabled-stage-overlay="disabledStageOverlay"
9
10
  :stage-content-menu="stageContentMenu"
10
11
  :custom-content-menu="customContentMenu"
@@ -18,7 +19,14 @@
18
19
  <script lang="ts" setup>
19
20
  import { computed, inject } from 'vue';
20
21
 
21
- import type { MenuButton, MenuComponent, Services, WorkspaceSlots } from '@editor/type';
22
+ import type {
23
+ CustomContentMenuFunction,
24
+ MenuButton,
25
+ MenuComponent,
26
+ Services,
27
+ StageOptions,
28
+ WorkspaceSlots,
29
+ } from '@editor/type';
22
30
 
23
31
  import MagicStage from './viewer/Stage.vue';
24
32
  import Breadcrumb from './Breadcrumb.vue';
@@ -33,13 +41,15 @@ withDefaults(
33
41
  defineProps<{
34
42
  stageContentMenu: (MenuButton | MenuComponent)[];
35
43
  disabledStageOverlay?: boolean;
36
- customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
44
+ customContentMenu: CustomContentMenuFunction;
37
45
  }>(),
38
46
  {
39
47
  disabledStageOverlay: false,
40
48
  },
41
49
  );
42
50
 
51
+ const stageOptions = inject<StageOptions>('stageOptions');
52
+
43
53
  const services = inject<Services>('services');
44
54
 
45
55
  const page = computed(() => services?.editorService.get('page'));
@@ -22,7 +22,7 @@
22
22
  </template>
23
23
 
24
24
  <script lang="ts" setup>
25
- import { computed, inject, nextTick, ref, watch } from 'vue';
25
+ import { computed, inject, nextTick, ref, useTemplateRef, watch } from 'vue';
26
26
 
27
27
  import type { MNode } from '@tmagic/core';
28
28
  import { TMagicTooltip } from '@tmagic/design';
@@ -39,8 +39,8 @@ const editorService = services?.editorService;
39
39
 
40
40
  const visible = ref(false);
41
41
  const buttonVisible = ref(false);
42
- const button = ref<HTMLDivElement>();
43
- const box = ref<InstanceType<typeof FloatingBox>>();
42
+ const button = useTemplateRef<HTMLDivElement>('button');
43
+ const box = useTemplateRef<InstanceType<typeof FloatingBox>>('box');
44
44
 
45
45
  const stage = computed(() => editorService?.get('stage'));
46
46
  const page = computed(() => editorService?.get('page'));
@@ -3,6 +3,8 @@
3
3
  class="m-editor-stage"
4
4
  ref="stageWrap"
5
5
  tabindex="-1"
6
+ v-loading="stageLoading"
7
+ element-loading-text="Runtime 加载中..."
6
8
  :width="stageRect?.width"
7
9
  :height="stageRect?.height"
8
10
  :wrap-width="stageContainerRect?.width"
@@ -41,7 +43,18 @@
41
43
  </template>
42
44
 
43
45
  <script lang="ts" setup>
44
- import { computed, inject, markRaw, nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch, watchEffect } from 'vue';
46
+ import {
47
+ computed,
48
+ inject,
49
+ markRaw,
50
+ nextTick,
51
+ onBeforeUnmount,
52
+ onMounted,
53
+ toRaw,
54
+ useTemplateRef,
55
+ watch,
56
+ watchEffect,
57
+ } from 'vue';
45
58
  import { cloneDeep } from 'lodash-es';
46
59
 
47
60
  import type { MApp, MContainer } from '@tmagic/core';
@@ -50,7 +63,8 @@ import { calcValueByFontsize, getIdFromEl } from '@tmagic/utils';
50
63
 
51
64
  import ScrollViewer from '@editor/components/ScrollViewer.vue';
52
65
  import { useStage } from '@editor/hooks/use-stage';
53
- import { DragType, Layout, type MenuButton, type MenuComponent, type Services, type StageOptions } from '@editor/type';
66
+ import type { CustomContentMenuFunction, MenuButton, MenuComponent, Services, StageOptions } from '@editor/type';
67
+ import { DragType, Layout } from '@editor/type';
54
68
  import { getEditorConfig } from '@editor/utils/config';
55
69
  import { KeyBindingContainerKey } from '@editor/utils/keybinding-config';
56
70
 
@@ -62,11 +76,12 @@ defineOptions({
62
76
  name: 'MEditorStage',
63
77
  });
64
78
 
65
- withDefaults(
79
+ const props = withDefaults(
66
80
  defineProps<{
81
+ stageOptions: StageOptions;
67
82
  stageContentMenu: (MenuButton | MenuComponent)[];
68
83
  disabledStageOverlay?: boolean;
69
- customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
84
+ customContentMenu: CustomContentMenuFunction;
70
85
  }>(),
71
86
  {
72
87
  disabledStageOverlay: false,
@@ -77,11 +92,12 @@ let stage: StageCore | null = null;
77
92
  let runtime: Runtime | null = null;
78
93
 
79
94
  const services = inject<Services>('services');
80
- const stageOptions = inject<StageOptions>('stageOptions');
81
95
 
82
- const stageWrap = ref<InstanceType<typeof ScrollViewer>>();
83
- const stageContainer = ref<HTMLDivElement>();
84
- const menu = ref<InstanceType<typeof ViewerMenu>>();
96
+ const stageLoading = computed(() => services?.editorService.get('stageLoading') || false);
97
+
98
+ const stageWrap = useTemplateRef<InstanceType<typeof ScrollViewer>>('stageWrap');
99
+ const stageContainer = useTemplateRef<HTMLDivElement>('stageContainer');
100
+ const menu = useTemplateRef<InstanceType<typeof ViewerMenu>>('menu');
85
101
 
86
102
  const nodes = computed(() => services?.editorService.get('nodes') || []);
87
103
  const isMultiSelect = computed(() => nodes.value.length > 1);
@@ -96,9 +112,9 @@ watchEffect(() => {
96
112
  if (stage || !page.value) return;
97
113
 
98
114
  if (!stageContainer.value) return;
99
- if (!(stageOptions?.runtimeUrl || stageOptions?.render) || !root.value) return;
115
+ if (!(props.stageOptions?.runtimeUrl || props.stageOptions?.render) || !root.value) return;
100
116
 
101
- stage = useStage(stageOptions);
117
+ stage = useStage(props.stageOptions);
102
118
 
103
119
  stage.on('select', () => {
104
120
  stageWrap.value?.container?.focus();
@@ -125,6 +141,7 @@ watchEffect(() => {
125
141
 
126
142
  onBeforeUnmount(() => {
127
143
  stage?.destroy();
144
+ services?.editorService.set('stage', null);
128
145
  });
129
146
 
130
147
  watch(zoom, (zoom) => {
@@ -132,9 +149,20 @@ watch(zoom, (zoom) => {
132
149
  stage.setZoom(zoom);
133
150
  });
134
151
 
152
+ let timeoutId: NodeJS.Timeout | null = null;
135
153
  watch(page, (page) => {
136
154
  if (runtime && page) {
137
155
  services?.editorService.set('stageLoading', true);
156
+
157
+ if (timeoutId) {
158
+ globalThis.clearTimeout(timeoutId);
159
+ }
160
+
161
+ timeoutId = globalThis.setTimeout(() => {
162
+ services?.editorService.set('stageLoading', false);
163
+ timeoutId = null;
164
+ }, 3000);
165
+
138
166
  runtime.updatePageId?.(page.id);
139
167
  nextTick(() => {
140
168
  stage?.select(page.id);
@@ -201,7 +229,9 @@ const dropHandler = async (e: DragEvent) => {
201
229
  e.preventDefault();
202
230
 
203
231
  const doc = stage?.renderer?.contentWindow?.document;
204
- const parentEl: HTMLElement | null | undefined = doc?.querySelector(`.${stageOptions?.containerHighlightClassName}`);
232
+ const parentEl: HTMLElement | null | undefined = doc?.querySelector(
233
+ `.${props.stageOptions?.containerHighlightClassName}`,
234
+ );
205
235
 
206
236
  let parent: MContainer | undefined | null = page.value;
207
237
  const parentId = getIdFromEl()(parentEl);
@@ -8,7 +8,7 @@
8
8
  </template>
9
9
 
10
10
  <script setup lang="ts">
11
- import { computed, inject, onBeforeUnmount, ref, watch } from 'vue';
11
+ import { computed, inject, onBeforeUnmount, useTemplateRef, watch } from 'vue';
12
12
  import { CloseBold } from '@element-plus/icons-vue';
13
13
 
14
14
  import { TMagicIcon } from '@tmagic/design';
@@ -19,7 +19,7 @@ const services = inject<Services>('services');
19
19
 
20
20
  const stageOptions = inject<StageOptions>('stageOptions');
21
21
 
22
- const stageOverlay = ref<HTMLDivElement>();
22
+ const stageOverlay = useTemplateRef<HTMLDivElement>('stageOverlay');
23
23
 
24
24
  const stageOverlayVisible = computed(() => services?.stageOverlayService.get('stageOverlayVisible'));
25
25
  const wrapWidth = computed(() => services?.stageOverlayService.get('wrapWidth') || 0);
@@ -3,7 +3,7 @@
3
3
  </template>
4
4
 
5
5
  <script lang="ts" setup>
6
- import { computed, inject, markRaw, ref, watch } from 'vue';
6
+ import { computed, inject, markRaw, ref, useTemplateRef, watch } from 'vue';
7
7
  import { Bottom, Top } from '@element-plus/icons-vue';
8
8
 
9
9
  import { NodeType } from '@tmagic/core';
@@ -11,7 +11,7 @@ import { isPage, isPageFragment } from '@tmagic/utils';
11
11
 
12
12
  import ContentMenu from '@editor/components/ContentMenu.vue';
13
13
  import CenterIcon from '@editor/icons/CenterIcon.vue';
14
- import { LayerOffset, Layout, MenuButton, MenuComponent, Services } from '@editor/type';
14
+ import { CustomContentMenuFunction, LayerOffset, Layout, MenuButton, MenuComponent, Services } from '@editor/type';
15
15
  import { useCopyMenu, useDeleteMenu, useMoveToMenu, usePasteMenu } from '@editor/utils/content-menu';
16
16
 
17
17
  defineOptions({
@@ -22,18 +22,16 @@ const props = withDefaults(
22
22
  defineProps<{
23
23
  isMultiSelect?: boolean;
24
24
  stageContentMenu: (MenuButton | MenuComponent)[];
25
- customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
25
+ customContentMenu: CustomContentMenuFunction;
26
26
  }>(),
27
27
  {
28
28
  isMultiSelect: false,
29
- stageContentMenu: () => [],
30
- customContentMenu: (menus: (MenuButton | MenuComponent)[]) => menus,
31
29
  },
32
30
  );
33
31
 
34
32
  const services = inject<Services>('services');
35
33
  const editorService = services?.editorService;
36
- const menu = ref<InstanceType<typeof ContentMenu>>();
34
+ const menu = useTemplateRef<InstanceType<typeof ContentMenu>>('menu');
37
35
  const canCenter = ref(false);
38
36
 
39
37
  const node = computed(() => editorService?.get('node'));
@@ -4,7 +4,7 @@ import type { Writable } from 'type-fest';
4
4
 
5
5
  import type { DataSourceSchema, EventOption, Id, MNode, TargetOptions } from '@tmagic/core';
6
6
  import { Target, Watcher } from '@tmagic/core';
7
- import type { FormConfig } from '@tmagic/form';
7
+ import type { ChangeRecord, FormConfig } from '@tmagic/form';
8
8
  import { guid, toLine } from '@tmagic/utils';
9
9
 
10
10
  import editorService from '@editor/services/editor';
@@ -115,15 +115,22 @@ class DataSource extends BaseService {
115
115
  return newConfig;
116
116
  }
117
117
 
118
- public update(config: DataSourceSchema) {
118
+ public update(config: DataSourceSchema, { changeRecords = [] }: { changeRecords?: ChangeRecord[] } = {}) {
119
119
  const dataSources = this.get('dataSources');
120
120
 
121
121
  const index = dataSources.findIndex((ds) => ds.id === config.id);
122
- dataSources[index] = cloneDeep(config);
123
122
 
124
- this.emit('update', config);
123
+ const oldConfig = dataSources[index];
124
+ const newConfig = cloneDeep(config);
125
125
 
126
- return config;
126
+ dataSources[index] = newConfig;
127
+
128
+ this.emit('update', newConfig, {
129
+ oldConfig,
130
+ changeRecords,
131
+ });
132
+
133
+ return newConfig;
127
134
  }
128
135
 
129
136
  public remove(id: string) {
@@ -31,11 +31,29 @@ export interface DepEvents {
31
31
  collected: [nodes: MNode[], deep: boolean];
32
32
  }
33
33
 
34
+ interface State {
35
+ collecting: boolean;
36
+ }
37
+
38
+ type StateKey = keyof State;
39
+
34
40
  const idleTask = new IdleTask<{ node: TargetNode; deep: boolean; target: Target }>();
35
41
 
36
42
  class Dep extends BaseService {
43
+ private state = reactive<State>({
44
+ collecting: false,
45
+ });
46
+
37
47
  private watcher = new Watcher({ initialTargets: reactive({}) });
38
48
 
49
+ public set<K extends StateKey, T extends State[K]>(name: K, value: T) {
50
+ this.state[name] = value;
51
+ }
52
+
53
+ public get<K extends StateKey>(name: K): State[K] {
54
+ return this.state[name];
55
+ }
56
+
39
57
  public removeTargets(type: string = DepTargetType.DEFAULT) {
40
58
  this.watcher.removeTargets(type);
41
59
 
@@ -71,33 +89,40 @@ class Dep extends BaseService {
71
89
  }
72
90
 
73
91
  public collect(nodes: MNode[], depExtendedData: DepExtendedData = {}, deep = false, type?: DepTargetType) {
92
+ this.set('collecting', true);
74
93
  this.watcher.collectByCallback(nodes, type, ({ node, target }) => {
75
94
  this.collectNode(node, target, depExtendedData, deep);
76
95
  });
96
+ this.set('collecting', false);
77
97
 
78
98
  this.emit('collected', nodes, deep);
79
99
  }
80
100
 
81
101
  public collectIdle(nodes: MNode[], depExtendedData: DepExtendedData = {}, deep = false, type?: DepTargetType) {
102
+ this.set('collecting', true);
103
+ let startTask = false;
82
104
  this.watcher.collectByCallback(nodes, type, ({ node, target }) => {
83
- idleTask.enqueueTask(
84
- ({ node, deep, target }) => {
85
- this.collectNode(node, target, depExtendedData, deep);
86
- },
87
- {
88
- node,
89
- deep,
90
- target,
91
- },
92
- );
105
+ startTask = true;
106
+
107
+ this.enqueueTask(node, target, depExtendedData, deep);
93
108
  });
94
109
 
95
- idleTask.once('finish', () => {
96
- this.emit('collected', nodes, deep);
110
+ return new Promise<void>((resolve) => {
111
+ if (!startTask) {
112
+ this.emit('collected', nodes, deep);
113
+ this.set('collecting', false);
114
+ resolve();
115
+ return;
116
+ }
117
+ idleTask.once('finish', () => {
118
+ this.emit('collected', nodes, deep);
119
+ this.set('collecting', false);
120
+ resolve();
121
+ });
97
122
  });
98
123
  }
99
124
 
100
- collectNode(node: MNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) {
125
+ public collectNode(node: MNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) {
101
126
  // 先删除原有依赖,重新收集
102
127
  if (isPage(node)) {
103
128
  Object.entries(target.deps).forEach(([depKey, dep]) => {
@@ -128,6 +153,10 @@ class Dep extends BaseService {
128
153
  return this.watcher.hasSpecifiedTypeTarget(type);
129
154
  }
130
155
 
156
+ public clearIdleTasks() {
157
+ idleTask.clearTasks();
158
+ }
159
+
131
160
  public on<Name extends keyof DepEvents, Param extends DepEvents[Name]>(
132
161
  eventName: Name,
133
162
  listener: (...args: Param) => void | Promise<void>,
@@ -145,6 +174,25 @@ class Dep extends BaseService {
145
174
  public emit<Name extends keyof DepEvents, Param extends DepEvents[Name]>(eventName: Name, ...args: Param) {
146
175
  return super.emit(eventName, ...args);
147
176
  }
177
+
178
+ private enqueueTask(node: MNode, target: Target, depExtendedData: DepExtendedData, deep: boolean) {
179
+ idleTask.enqueueTask(
180
+ ({ node, deep, target }) => {
181
+ this.collectNode(node, target, depExtendedData, deep);
182
+ },
183
+ {
184
+ node,
185
+ deep: false,
186
+ target,
187
+ },
188
+ );
189
+
190
+ if (deep && Array.isArray(node.items) && node.items.length) {
191
+ node.items.forEach((item) => {
192
+ this.enqueueTask(item, target, depExtendedData, deep);
193
+ });
194
+ }
195
+ }
148
196
  }
149
197
 
150
198
  export type DepService = Dep;