@tmagic/editor 1.3.5 → 1.3.7

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 (142) hide show
  1. package/LICENSE +5432 -0
  2. package/dist/style.css +88 -62
  3. package/dist/tmagic-editor.js +2448 -2016
  4. package/dist/tmagic-editor.js.map +1 -1
  5. package/dist/tmagic-editor.umd.cjs +2453 -2019
  6. package/dist/tmagic-editor.umd.cjs.map +1 -1
  7. package/package.json +23 -23
  8. package/src/Editor.vue +15 -7
  9. package/src/components/CodeBlockEditor.vue +2 -2
  10. package/src/components/ContentMenu.vue +5 -5
  11. package/src/components/FloatingBox.vue +117 -0
  12. package/src/components/ScrollBar.vue +2 -2
  13. package/src/components/ScrollViewer.vue +2 -2
  14. package/src/components/SplitView.vue +2 -2
  15. package/src/components/Tree.vue +5 -0
  16. package/src/components/TreeNode.vue +10 -4
  17. package/src/editorProps.ts +20 -3
  18. package/src/fields/PageFragmentSelect.vue +55 -0
  19. package/src/hooks/use-filter.ts +55 -0
  20. package/src/hooks/use-getso.ts +2 -2
  21. package/src/hooks/use-node-status.ts +48 -0
  22. package/src/hooks/use-stage.ts +19 -1
  23. package/src/index.ts +3 -0
  24. package/src/initService.ts +43 -17
  25. package/src/layouts/AddPageBox.vue +16 -4
  26. package/src/layouts/CodeEditor.vue +2 -2
  27. package/src/layouts/Framework.vue +17 -3
  28. package/src/layouts/PropsPanel.vue +2 -2
  29. package/src/layouts/page-bar/AddButton.vue +48 -0
  30. package/src/layouts/page-bar/PageBar.vue +158 -0
  31. package/src/layouts/{workspace → page-bar}/PageBarScrollContainer.vue +61 -35
  32. package/src/layouts/page-bar/SwitchTypeButton.vue +45 -0
  33. package/src/layouts/sidebar/Sidebar.vue +9 -2
  34. package/src/layouts/sidebar/code-block/CodeBlockList.vue +55 -65
  35. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +2 -1
  36. package/src/layouts/sidebar/data-source/DataSourceList.vue +63 -59
  37. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +1 -1
  38. package/src/layouts/sidebar/layer/LayerMenu.vue +39 -27
  39. package/src/layouts/sidebar/layer/LayerPanel.vue +31 -8
  40. package/src/layouts/sidebar/layer/use-click.ts +5 -3
  41. package/src/layouts/sidebar/layer/use-keybinding.ts +8 -8
  42. package/src/layouts/sidebar/layer/use-node-status.ts +10 -13
  43. package/src/layouts/workspace/Workspace.vue +6 -7
  44. package/src/layouts/workspace/viewer/NodeListMenu.vue +68 -129
  45. package/src/layouts/workspace/viewer/Stage.vue +14 -6
  46. package/src/layouts/workspace/viewer/ViewerMenu.vue +94 -86
  47. package/src/services/BaseService.ts +63 -26
  48. package/src/services/codeBlock.ts +7 -1
  49. package/src/services/dep.ts +12 -8
  50. package/src/services/editor.ts +131 -64
  51. package/src/services/history.ts +6 -2
  52. package/src/services/keybinding.ts +48 -13
  53. package/src/services/props.ts +70 -12
  54. package/src/services/storage.ts +23 -13
  55. package/src/services/ui.ts +4 -1
  56. package/src/theme/code-block.scss +0 -16
  57. package/src/theme/floating-box.scss +30 -0
  58. package/src/theme/layer-panel.scss +19 -0
  59. package/src/theme/page-bar.scss +18 -1
  60. package/src/theme/stage.scss +24 -0
  61. package/src/theme/theme.scss +1 -1
  62. package/src/theme/tree.scss +12 -1
  63. package/src/type.ts +28 -10
  64. package/src/utils/compose.ts +25 -6
  65. package/src/utils/content-menu.ts +6 -7
  66. package/src/utils/editor.ts +34 -18
  67. package/src/utils/operator.ts +5 -6
  68. package/types/components/CodeBlockEditor.vue.d.ts +6 -2
  69. package/types/components/CodeParams.vue.d.ts +3 -1
  70. package/types/components/ContentMenu.vue.d.ts +13 -5
  71. package/types/components/FloatingBox.vue.d.ts +78 -0
  72. package/types/components/Resizer.vue.d.ts +6 -1
  73. package/types/components/ScrollBar.vue.d.ts +3 -1
  74. package/types/components/ScrollViewer.vue.d.ts +3 -3
  75. package/types/components/SearchInput.vue.d.ts +3 -1
  76. package/types/components/SplitView.vue.d.ts +8 -4
  77. package/types/components/ToolButton.vue.d.ts +3 -3
  78. package/types/components/Tree.vue.d.ts +27 -8
  79. package/types/components/TreeNode.vue.d.ts +24 -7
  80. package/types/editorProps.d.ts +16 -1
  81. package/types/fields/Code.vue.d.ts +6 -2
  82. package/types/fields/CodeLink.vue.d.ts +5 -3
  83. package/types/fields/CodeSelect.vue.d.ts +3 -1
  84. package/types/fields/CodeSelectCol.vue.d.ts +3 -1
  85. package/types/fields/DataSourceFieldSelect.vue.d.ts +3 -1
  86. package/types/fields/DataSourceFields.vue.d.ts +5 -3
  87. package/types/fields/DataSourceInput.vue.d.ts +8 -4
  88. package/types/fields/DataSourceMethodSelect.vue.d.ts +3 -1
  89. package/types/fields/DataSourceMethods.vue.d.ts +5 -3
  90. package/types/fields/DataSourceMocks.vue.d.ts +5 -3
  91. package/types/fields/DataSourceSelect.vue.d.ts +5 -3
  92. package/types/fields/EventSelect.vue.d.ts +3 -1
  93. package/types/fields/KeyValue.vue.d.ts +2 -2
  94. package/types/fields/PageFragmentSelect.vue.d.ts +31 -0
  95. package/types/fields/UISelect.vue.d.ts +3 -1
  96. package/types/hooks/use-code-block-edit.d.ts +14 -5
  97. package/types/hooks/use-data-source-method.d.ts +14 -5
  98. package/types/hooks/use-filter.d.ts +7 -0
  99. package/types/hooks/use-node-status.d.ts +6 -0
  100. package/types/index.d.ts +1 -0
  101. package/types/layouts/AddPageBox.vue.d.ts +14 -1
  102. package/types/layouts/CodeEditor.vue.d.ts +4 -1
  103. package/types/layouts/Framework.vue.d.ts +14 -1
  104. package/types/layouts/PropsPanel.vue.d.ts +19 -13
  105. package/types/layouts/{workspace/viewer/NodeListMenuTitle.vue.d.ts → page-bar/AddButton.vue.d.ts} +3 -2
  106. package/types/layouts/page-bar/PageBar.vue.d.ts +28 -0
  107. package/types/layouts/page-bar/PageBarScrollContainer.vue.d.ts +24 -0
  108. package/types/layouts/page-bar/SwitchTypeButton.vue.d.ts +20 -0
  109. package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +1 -1
  110. package/types/layouts/sidebar/Sidebar.vue.d.ts +7 -5
  111. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +9 -3
  112. package/types/layouts/sidebar/code-block/CodeBlockListPanel.vue.d.ts +1 -1
  113. package/types/layouts/sidebar/data-source/DataSourceConfigPanel.vue.d.ts +3 -1
  114. package/types/layouts/sidebar/data-source/DataSourceList.vue.d.ts +8 -2
  115. package/types/layouts/sidebar/data-source/DataSourceListPanel.vue.d.ts +1 -1
  116. package/types/layouts/sidebar/layer/LayerMenu.vue.d.ts +26 -3
  117. package/types/layouts/sidebar/layer/LayerPanel.vue.d.ts +3 -1
  118. package/types/layouts/sidebar/layer/use-click.d.ts +37 -6
  119. package/types/layouts/sidebar/layer/use-keybinding.d.ts +1 -1
  120. package/types/layouts/sidebar/layer/use-node-status.d.ts +3 -4
  121. package/types/layouts/workspace/Workspace.vue.d.ts +3 -1
  122. package/types/layouts/workspace/viewer/NodeListMenu.vue.d.ts +1 -14
  123. package/types/layouts/workspace/viewer/Stage.vue.d.ts +2 -0
  124. package/types/layouts/workspace/viewer/ViewerMenu.vue.d.ts +9 -1
  125. package/types/services/BaseService.d.ts +4 -1
  126. package/types/services/dataSource.d.ts +1 -0
  127. package/types/services/dep.d.ts +6 -5
  128. package/types/services/editor.d.ts +12 -5
  129. package/types/services/history.d.ts +3 -2
  130. package/types/services/keybinding.d.ts +21 -1
  131. package/types/services/props.d.ts +25 -1
  132. package/types/services/storage.d.ts +7 -7
  133. package/types/type.d.ts +32 -17
  134. package/types/utils/compose.d.ts +1 -1
  135. package/types/utils/editor.d.ts +14 -8
  136. package/src/layouts/sidebar/layer/use-filter.ts +0 -59
  137. package/src/layouts/workspace/PageBar.vue +0 -87
  138. package/src/layouts/workspace/viewer/NodeListMenuTitle.vue +0 -68
  139. package/src/theme/dep-list.scss +0 -59
  140. package/types/layouts/sidebar/layer/use-filter.d.ts +0 -7
  141. package/types/layouts/workspace/PageBar.vue.d.ts +0 -15
  142. package/types/layouts/workspace/PageBarScrollContainer.vue.d.ts +0 -9
@@ -6,7 +6,7 @@
6
6
  import { computed, inject, markRaw, ref } from 'vue';
7
7
  import { Files, Plus } from '@element-plus/icons-vue';
8
8
 
9
- import { isPage } from '@tmagic/utils';
9
+ import { isPage, isPageFragment } from '@tmagic/utils';
10
10
 
11
11
  import ContentMenu from '@editor/components/ContentMenu.vue';
12
12
  import FolderMinusIcon from '@editor/icons/FolderMinusIcon.vue';
@@ -17,9 +17,16 @@ defineOptions({
17
17
  name: 'MEditorLayerMenu',
18
18
  });
19
19
 
20
- const props = defineProps<{
21
- layerContentMenu: (MenuButton | MenuComponent)[];
22
- }>();
20
+ const props = withDefaults(
21
+ defineProps<{
22
+ layerContentMenu: (MenuButton | MenuComponent)[];
23
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
24
+ }>(),
25
+ {
26
+ layerContentMenu: () => [],
27
+ customContentMenu: (menus: (MenuButton | MenuComponent)[]) => menus,
28
+ },
29
+ );
23
30
 
24
31
  const emit = defineEmits<{
25
32
  'collapse-all': [];
@@ -82,29 +89,34 @@ const getSubMenuData = computed<MenuButton[]>(() => {
82
89
  return [];
83
90
  });
84
91
 
85
- const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
86
- {
87
- type: 'button',
88
- text: '全部折叠',
89
- icon: FolderMinusIcon,
90
- display: () => isPage(node.value),
91
- handler: () => {
92
- emit('collapse-all');
93
- },
94
- },
95
- {
96
- type: 'button',
97
- text: '新增',
98
- icon: markRaw(Plus),
99
- display: () => node.value?.items && nodes.value?.length === 1,
100
- items: getSubMenuData.value,
101
- },
102
- useCopyMenu(),
103
- usePasteMenu(),
104
- useDeleteMenu(),
105
- useMoveToMenu(services),
106
- ...props.layerContentMenu,
107
- ]);
92
+ const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
93
+ props.customContentMenu(
94
+ [
95
+ {
96
+ type: 'button',
97
+ text: '全部折叠',
98
+ icon: FolderMinusIcon,
99
+ display: () => isPage(node.value) || isPageFragment(node.value),
100
+ handler: () => {
101
+ emit('collapse-all');
102
+ },
103
+ },
104
+ {
105
+ type: 'button',
106
+ text: '新增',
107
+ icon: markRaw(Plus),
108
+ display: () => node.value?.items && nodes.value?.length === 1,
109
+ items: getSubMenuData.value,
110
+ },
111
+ useCopyMenu(),
112
+ usePasteMenu(),
113
+ useDeleteMenu(),
114
+ useMoveToMenu(services),
115
+ ...props.layerContentMenu,
116
+ ],
117
+ 'layer',
118
+ ),
119
+ );
108
120
 
109
121
  const show = (e: MouseEvent) => {
110
122
  menu.value?.show(e);
@@ -8,7 +8,7 @@
8
8
  v-if="page && nodeStatusMap"
9
9
  tabindex="-1"
10
10
  ref="tree"
11
- :data="[page]"
11
+ :data="nodeData"
12
12
  :node-status-map="nodeStatusMap"
13
13
  @node-dragover="handleDragOver"
14
14
  @node-dragstart="handleDragStart"
@@ -18,19 +18,28 @@
18
18
  @node-mouseenter="mouseenterHandler"
19
19
  @node-click="nodeClickHandler"
20
20
  >
21
+ <template #tree-node-content="{ data: nodeData }">
22
+ <slot name="layer-node-content" :data="nodeData"> </slot>
23
+ </template>
24
+
21
25
  <template #tree-node-tool="{ data: nodeData }">
22
26
  <slot name="layer-node-tool" :data="nodeData">
23
27
  <LayerNodeTool :data="nodeData"></LayerNodeTool>
24
28
  </slot>
25
29
  </template>
26
30
 
27
- <template #tree-node-content="{ data: nodeData }">
28
- <slot name="layer-node-content" :data="nodeData"> </slot>
31
+ <template #tree-node-label="{ data: nodeData }">
32
+ <slot name="layer-node-label" :data="nodeData"></slot>
29
33
  </template>
30
34
  </Tree>
31
35
 
32
36
  <Teleport to="body">
33
- <LayerMenu ref="menu" :layer-content-menu="layerContentMenu" @collapse-all="collapseAllHandler"></LayerMenu>
37
+ <LayerMenu
38
+ ref="menu"
39
+ :layer-content-menu="layerContentMenu"
40
+ :custom-content-menu="customContentMenu"
41
+ @collapse-all="collapseAllHandler"
42
+ ></LayerMenu>
34
43
  </Teleport>
35
44
  </TMagicScrollbar>
36
45
  </template>
@@ -39,16 +48,17 @@
39
48
  import { computed, inject, ref } from 'vue';
40
49
 
41
50
  import { TMagicScrollbar } from '@tmagic/design';
51
+ import type { MNode } from '@tmagic/schema';
42
52
 
43
53
  import SearchInput from '@editor/components/SearchInput.vue';
44
54
  import Tree from '@editor/components/Tree.vue';
45
- import { LayerPanelSlots, MenuButton, MenuComponent, Services } from '@editor/type';
55
+ import { useFilter } from '@editor/hooks/use-filter';
56
+ import { LayerPanelSlots, MenuButton, MenuComponent, Services, TreeNodeData } from '@editor/type';
46
57
 
47
58
  import LayerMenu from './LayerMenu.vue';
48
59
  import LayerNodeTool from './LayerNodeTool.vue';
49
60
  import { useClick } from './use-click';
50
61
  import { useDrag } from './use-drag';
51
- import { useFilter } from './use-filter';
52
62
  import { useKeybinding } from './use-keybinding';
53
63
  import { useNodeStatus } from './use-node-status';
54
64
 
@@ -60,6 +70,7 @@ defineOptions({
60
70
 
61
71
  defineProps<{
62
72
  layerContentMenu: (MenuButton | MenuComponent)[];
73
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
63
74
  }>();
64
75
 
65
76
  const services = inject<Services>('services');
@@ -68,11 +79,23 @@ const editorService = services?.editorService;
68
79
  const tree = ref<InstanceType<typeof Tree>>();
69
80
 
70
81
  const page = computed(() => editorService?.get('page'));
82
+ const nodeData = computed<TreeNodeData[]>(() => (!page.value ? [] : [page.value]));
71
83
 
72
- const { nodeStatusMap } = useNodeStatus(services, page);
84
+ const { nodeStatusMap } = useNodeStatus(services);
73
85
  const { isCtrlKeyDown } = useKeybinding(services, tree);
74
86
 
75
- const { filterTextChangeHandler } = useFilter(nodeStatusMap, page);
87
+ const filterNodeMethod = (v: string, data: MNode): boolean => {
88
+ let name = '';
89
+ if (data.name) {
90
+ name = data.name;
91
+ } else if (data.items) {
92
+ name = 'container';
93
+ }
94
+
95
+ return `${data.id}${name}${data.type}`.includes(v);
96
+ };
97
+
98
+ const { filterTextChangeHandler } = useFilter(nodeData, nodeStatusMap, filterNodeMethod);
76
99
 
77
100
  const collapseAllHandler = () => {
78
101
  if (!page.value || !nodeStatusMap.value) return;
@@ -1,4 +1,4 @@
1
- import { type ComputedRef, nextTick, type Ref, ref } from 'vue';
1
+ import { computed, type ComputedRef, nextTick, type Ref, ref } from 'vue';
2
2
  import { throttle } from 'lodash-es';
3
3
 
4
4
  import { Id, MNode } from '@tmagic/schema';
@@ -13,13 +13,15 @@ export const useClick = (
13
13
  isCtrlKeyDown: Ref<boolean>,
14
14
  nodeStatusMap: ComputedRef<Map<Id, LayerNodeStatus> | undefined>,
15
15
  ) => {
16
+ const isMultiSelect = computed(() => isCtrlKeyDown.value && !services?.editorService.get('disabledMultiSelect'));
17
+
16
18
  // 触发画布选中
17
19
  const select = async (data: MNode) => {
18
20
  if (!data.id) {
19
21
  throw new Error('没有id');
20
22
  }
21
23
 
22
- if (isCtrlKeyDown.value) {
24
+ if (isMultiSelect.value) {
23
25
  multiSelect(data);
24
26
  } else {
25
27
  await services?.editorService.select(data);
@@ -70,7 +72,7 @@ export const useClick = (
70
72
  return;
71
73
  }
72
74
 
73
- if (data.items && data.items.length > 0 && !isCtrlKeyDown.value) {
75
+ if (data.items && data.items.length > 0 && !isMultiSelect.value) {
74
76
  updateStatus(nodeStatusMap.value, data.id, {
75
77
  expand: true,
76
78
  });
@@ -6,7 +6,7 @@ import { KeyBindingContainerKey } from '@editor/utils/keybinding-config';
6
6
 
7
7
  export const useKeybinding = (
8
8
  services: Services | undefined,
9
- contianer: Ref<InstanceType<typeof Tree> | undefined>,
9
+ container: Ref<InstanceType<typeof Tree> | undefined>,
10
10
  ) => {
11
11
  const keybindingService = services?.keybindingService;
12
12
 
@@ -17,17 +17,17 @@ export const useKeybinding = (
17
17
  isCtrlKeyDown.value = false;
18
18
  };
19
19
 
20
- keybindingService?.registeCommand('layer-panel-global-keyup', () => {
20
+ keybindingService?.registerCommand('layer-panel-global-keyup', () => {
21
21
  isCtrlKeyDown.value = false;
22
22
  });
23
23
 
24
- keybindingService?.registeCommand('layer-panel-global-keydwon', () => {
24
+ keybindingService?.registerCommand('layer-panel-global-keydown', () => {
25
25
  isCtrlKeyDown.value = true;
26
26
  });
27
27
 
28
- keybindingService?.registe([
28
+ keybindingService?.register([
29
29
  {
30
- command: 'layer-panel-global-keydwon',
30
+ command: 'layer-panel-global-keydown',
31
31
  keybinding: 'ctrl',
32
32
  when: [['global', 'keydown']],
33
33
  },
@@ -39,12 +39,12 @@ export const useKeybinding = (
39
39
  ]);
40
40
 
41
41
  watchEffect(() => {
42
- if (contianer.value) {
42
+ if (container.value) {
43
43
  globalThis.addEventListener('blur', windowBlurHandler);
44
- keybindingService?.registeEl(KeyBindingContainerKey.LAYER_PANEL, contianer.value.$el);
44
+ keybindingService?.registerEl(KeyBindingContainerKey.LAYER_PANEL, container.value.$el);
45
45
  } else {
46
46
  globalThis.removeEventListener('blur', windowBlurHandler);
47
- keybindingService?.unregisteEl(KeyBindingContainerKey.LAYER_PANEL);
47
+ keybindingService?.unregisterEl(KeyBindingContainerKey.LAYER_PANEL);
48
48
  }
49
49
  });
50
50
 
@@ -1,31 +1,27 @@
1
- import { computed, type ComputedRef, ref, watch } from 'vue';
1
+ import { computed, ref, watch } from 'vue';
2
2
 
3
- import type { Id, MNode, MPage } from '@tmagic/schema';
3
+ import type { Id, MNode, MPage, MPageFragment } from '@tmagic/schema';
4
4
  import { getNodePath } from '@tmagic/utils';
5
5
 
6
6
  import { LayerNodeStatus, Services } from '@editor/type';
7
7
  import { traverseNode } from '@editor/utils';
8
8
  import { updateStatus } from '@editor/utils/tree';
9
9
 
10
- const createPageNodeStatus = (
11
- services: Services | undefined,
12
- pageId: Id,
13
- initalLayerNodeStatus?: Map<Id, LayerNodeStatus>,
14
- ) => {
10
+ const createPageNodeStatus = (page: MPage | MPageFragment, initialLayerNodeStatus?: Map<Id, LayerNodeStatus>) => {
15
11
  const map = new Map<Id, LayerNodeStatus>();
16
12
 
17
- map.set(pageId, {
13
+ map.set(page.id, {
18
14
  visible: true,
19
15
  expand: true,
20
16
  selected: true,
21
17
  draggable: false,
22
18
  });
23
19
 
24
- services?.editorService.getNodeById(pageId)?.items.forEach((node: MNode) =>
25
- traverseNode(node, (node) => {
20
+ page.items.forEach((node: MNode) =>
21
+ traverseNode<MNode>(node, (node) => {
26
22
  map.set(
27
23
  node.id,
28
- initalLayerNodeStatus?.get(node.id) || {
24
+ initialLayerNodeStatus?.get(node.id) || {
29
25
  visible: true,
30
26
  expand: false,
31
27
  selected: false,
@@ -38,7 +34,8 @@ const createPageNodeStatus = (
38
34
  return map;
39
35
  };
40
36
 
41
- export const useNodeStatus = (services: Services | undefined, page: ComputedRef<MPage | null | undefined>) => {
37
+ export const useNodeStatus = (services: Services | undefined) => {
38
+ const page = computed(() => services?.editorService.get('page'));
42
39
  const nodes = computed(() => services?.editorService.get('nodes') || []);
43
40
 
44
41
  /** 所有页面的节点状态 */
@@ -57,7 +54,7 @@ export const useNodeStatus = (services: Services | undefined, page: ComputedRef<
57
54
  return;
58
55
  }
59
56
  // 生成节点状态
60
- nodeStatusMaps.value.set(page.id, createPageNodeStatus(services, page.id, nodeStatusMaps.value.get(page.id)));
57
+ nodeStatusMaps.value.set(page.id, createPageNodeStatus(page, nodeStatusMaps.value.get(page.id)));
61
58
  },
62
59
  {
63
60
  immediate: true,
@@ -3,15 +3,14 @@
3
3
  <Breadcrumb></Breadcrumb>
4
4
 
5
5
  <slot name="stage">
6
- <MagicStage v-if="page" :stage-content-menu="stageContentMenu"></MagicStage>
6
+ <MagicStage
7
+ v-if="page"
8
+ :stage-content-menu="stageContentMenu"
9
+ :custom-content-menu="customContentMenu"
10
+ ></MagicStage>
7
11
  </slot>
8
12
 
9
13
  <slot name="workspace-content"></slot>
10
-
11
- <PageBar>
12
- <template #page-bar-title="{ page }"><slot name="page-bar-title" :page="page"></slot></template>
13
- <template #page-bar-popover="{ page }"><slot name="page-bar-popover" :page="page"></slot></template>
14
- </PageBar>
15
14
  </div>
16
15
  </template>
17
16
 
@@ -22,7 +21,6 @@ import type { MenuButton, MenuComponent, Services, WorkspaceSlots } from '@edito
22
21
 
23
22
  import MagicStage from './viewer/Stage.vue';
24
23
  import Breadcrumb from './Breadcrumb.vue';
25
- import PageBar from './PageBar.vue';
26
24
 
27
25
  defineSlots<WorkspaceSlots>();
28
26
 
@@ -32,6 +30,7 @@ defineOptions({
32
30
 
33
31
  defineProps<{
34
32
  stageContentMenu: (MenuButton | MenuComponent)[];
33
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
35
34
  }>();
36
35
 
37
36
  const services = inject<Services>('services');
@@ -1,175 +1,114 @@
1
1
  <template>
2
- <ContentMenu
3
- ref="menu"
4
- class="magic-editor-node-list-menu"
5
- style="max-width: 280px"
6
- :menu-data="menuData"
7
- :active="node?.id"
8
- :auto-hide="!pinned"
9
- @mouseenter="mouseenterHandler()"
2
+ <TMagicTooltip v-if="page && buttonVisible" content="点击查看当前位置下的组件">
3
+ <div ref="button" class="m-editor-stage-float-button" @click="visible = true">可选组件</div>
4
+ </TMagicTooltip>
5
+ <FloatingBox
6
+ v-if="page && nodeStatusMap && buttonVisible"
7
+ ref="box"
8
+ v-model:visible="visible"
9
+ title="当前位置下的组件"
10
+ :position="menuPosition"
10
11
  >
11
- <template #title>
12
- <NodeListMenuTitle v-model:pinned="pinned" @change="dragMenuHandler" @close="closeHandler"></NodeListMenuTitle>
12
+ <template #body>
13
+ <Tree
14
+ class="m-editor-node-list-menu magic-editor-layer-tree"
15
+ :data="nodeData"
16
+ :node-status-map="nodeStatusMap"
17
+ @node-click="clickHandler"
18
+ ></Tree>
13
19
  </template>
14
- </ContentMenu>
20
+ </FloatingBox>
15
21
  </template>
16
22
 
17
23
  <script lang="ts" setup>
18
- import { Component, computed, inject, ref, watch } from 'vue';
19
- import type { OnDrag } from 'gesto';
24
+ import { computed, inject, nextTick, ref, watch } from 'vue';
20
25
 
26
+ import { TMagicTooltip } from '@tmagic/design';
21
27
  import type { MNode } from '@tmagic/schema';
22
- import { StageDragStatus } from '@tmagic/stage';
23
- import { getNodes } from '@tmagic/utils';
24
28
 
25
- import ContentMenu from '@editor/components/ContentMenu.vue';
26
- import type { ComponentItem, MenuButton, Services } from '@editor/type';
27
-
28
- import NodeListMenuTitle from './NodeListMenuTitle.vue';
29
-
30
- const PINNED_STATUE_CACHE_KEY = 'tmagic-pinned-node-list-pinned-status';
31
-
32
- const props = defineProps<{ isMultiSelect?: boolean }>();
33
-
34
- const menu = ref<InstanceType<typeof ContentMenu>>();
35
- const nodeList = ref<MNode[]>([]);
36
- const pinned = ref(Boolean(globalThis.localStorage.getItem(PINNED_STATUE_CACHE_KEY)));
37
- const firstShow = ref(true);
29
+ import FloatingBox from '@editor/components/FloatingBox.vue';
30
+ import Tree from '@editor/components/Tree.vue';
31
+ import { useFilter } from '@editor/hooks/use-filter';
32
+ import { useNodeStatus } from '@editor/layouts/sidebar/layer/use-node-status';
33
+ import type { Services, TreeNodeData } from '@editor/type';
38
34
 
39
35
  const services = inject<Services>('services');
40
36
  const editorService = services?.editorService;
41
- const componentListService = services?.componentListService;
37
+
38
+ const visible = ref(false);
39
+ const buttonVisible = ref(false);
40
+ const button = ref<HTMLDivElement>();
41
+ const box = ref<InstanceType<typeof FloatingBox>>();
42
42
 
43
43
  const stage = computed(() => editorService?.get('stage'));
44
44
  const page = computed(() => editorService?.get('page'));
45
- const node = computed(() => editorService?.get('node'));
45
+ const nodes = computed(() => editorService?.get('nodes') || []);
46
+ const nodeData = computed<TreeNodeData[]>(() => (!page.value ? [] : [page.value]));
46
47
 
47
- let timeout: NodeJS.Timeout | null = null;
48
+ const { nodeStatusMap } = useNodeStatus(services);
48
49
 
49
- const cancel = () => {
50
- if (timeout) {
51
- globalThis.clearTimeout(timeout);
52
- }
50
+ const filterNodeMethod = (value: string, data: MNode): boolean => data.id === value;
53
51
 
54
- if (pinned.value) {
55
- return;
56
- }
57
-
58
- nodeList.value = [];
59
- menu.value?.hide();
60
- };
61
-
62
- const clearTimeoutLazy = () => {
63
- globalThis.setTimeout(() => {
64
- if (timeout) {
65
- globalThis.clearTimeout(timeout);
66
- }
67
- }, 300);
68
- };
52
+ const { filterTextChangeHandler } = useFilter(nodeData, nodeStatusMap, filterNodeMethod);
69
53
 
70
54
  const unWatch = watch(
71
55
  stage,
72
56
  (stage) => {
73
57
  if (!stage) return;
74
58
 
75
- stage.on('drag-start', () => {
76
- cancel();
77
- });
78
-
79
- stage.on('mousemove', (event: MouseEvent) => {
80
- cancel();
81
-
82
- if (props.isMultiSelect || stage.getDragStatus() !== StageDragStatus.END) {
83
- return;
84
- }
85
-
86
- timeout = globalThis.setTimeout(() => {
87
- const els = stage.renderer.getElementsFromPoint(event) || [];
59
+ nextTick(() => unWatch());
88
60
 
89
- const nodes = getNodes(
90
- els.map((el) => el.id),
91
- page.value?.items,
92
- );
61
+ stage.on('select', (el: HTMLElement, event: MouseEvent) => {
62
+ const els = stage.renderer.getElementsFromPoint(event) || [];
63
+ const ids = els.map((el) => el.id).filter((id) => Boolean(id));
93
64
 
94
- if (pinned.value && nodes.length === 0) {
95
- return;
96
- }
65
+ buttonVisible.value = ids.length > 3;
97
66
 
98
- nodeList.value = nodes;
99
-
100
- if (nodeList.value.length > 1) {
101
- menu.value?.show(pinned.value && !firstShow.value ? undefined : event);
102
- firstShow.value = false;
103
- }
104
- }, 1500);
105
- });
106
-
107
- stage.on('mouseleave', () => {
108
- // mouseleave后,大概率还有最后一个mousemove事件,这里延迟清除
109
- clearTimeoutLazy();
67
+ filterTextChangeHandler(ids);
110
68
  });
111
-
112
- unWatch();
113
69
  },
114
70
  {
115
71
  immediate: true,
116
72
  },
117
73
  );
118
74
 
119
- const componentMap = computed(() => {
120
- const map: Record<string, ComponentItem> = {};
121
- componentListService?.getList().forEach((group) => {
122
- group.items.forEach((item) => {
123
- map[item.type] = item;
124
- });
125
- });
126
- return map;
127
- });
75
+ watch(
76
+ nodes,
77
+ (nodes) => {
78
+ if (!nodeStatusMap.value) return;
128
79
 
129
- const menuData = computed<MenuButton[]>(() =>
130
- nodeList.value.map((node: MNode) => {
131
- let text = node.name;
132
- let icon: string | Component<{}, {}, any> | undefined;
133
- if (node.type) {
134
- const item = componentMap.value[node.type];
135
- text += ` (${item?.text})`;
136
- icon = item?.icon;
80
+ for (const [id, status] of nodeStatusMap.value.entries()) {
81
+ status.selected = nodes.some((node) => node.id === id);
137
82
  }
138
-
139
- return {
140
- type: 'button',
141
- text,
142
- id: node.id,
143
- icon,
144
- handler: async () => {
145
- await editorService?.select(node);
146
- stage.value?.select(node.id);
147
- },
148
- };
149
- }),
83
+ },
84
+ {
85
+ immediate: true,
86
+ },
150
87
  );
151
88
 
152
- const mouseenterHandler = () => {
153
- // menu的mouseenter后,大概率还有最后一个mousemove事件,这里延迟清除
154
- clearTimeoutLazy();
89
+ const clickHandler = async (event: MouseEvent, data: TreeNodeData) => {
90
+ await editorService?.select(data.id);
91
+ stage.value?.select(data.id);
155
92
  };
156
93
 
157
- const dragMenuHandler = ({ deltaY, deltaX }: OnDrag) => {
158
- if (!menu.value) return;
94
+ const menuPosition = ref({
95
+ left: 0,
96
+ top: 0,
97
+ });
159
98
 
160
- const { menuPosition } = menu.value;
99
+ watch(visible, async (visible) => {
100
+ if (!button.value || !visible) {
101
+ return;
102
+ }
161
103
 
162
- menu.value?.setPosition({
163
- clientY: menuPosition.top + deltaY,
164
- clientX: menuPosition.left + deltaX,
165
- });
166
- };
104
+ await nextTick();
167
105
 
168
- const closeHandler = () => {
169
- menu.value?.hide();
170
- };
106
+ const rect = button.value.getBoundingClientRect();
107
+ const height = box.value?.target?.clientHeight || 0;
171
108
 
172
- watch(pinned, () => {
173
- globalThis.localStorage.setItem(PINNED_STATUE_CACHE_KEY, pinned.value.toString());
109
+ menuPosition.value = {
110
+ left: rect.left + rect.width + 5,
111
+ top: rect.top - height / 2 + rect.height / 2,
112
+ };
174
113
  });
175
114
  </script>
@@ -22,15 +22,21 @@
22
22
  @drop="dropHandler"
23
23
  @dragover="dragoverHandler"
24
24
  ></div>
25
+ <NodeListMenu></NodeListMenu>
26
+
25
27
  <Teleport to="body">
26
- <ViewerMenu ref="menu" :is-multi-select="isMultiSelect" :stage-content-menu="stageContentMenu"></ViewerMenu>
27
- <NodeListMenu ref="nodeList" :is-multi-select="isMultiSelect"></NodeListMenu>
28
+ <ViewerMenu
29
+ ref="menu"
30
+ :is-multi-select="isMultiSelect"
31
+ :stage-content-menu="stageContentMenu"
32
+ :custom-content-menu="customContentMenu"
33
+ ></ViewerMenu>
28
34
  </Teleport>
29
35
  </ScrollViewer>
30
36
  </template>
31
37
 
32
38
  <script lang="ts" setup>
33
- import { computed, inject, markRaw, nextTick, onMounted, onUnmounted, ref, toRaw, watch, watchEffect } from 'vue';
39
+ import { computed, inject, markRaw, nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch, watchEffect } from 'vue';
34
40
  import { cloneDeep } from 'lodash-es';
35
41
 
36
42
  import type { MApp, MContainer } from '@tmagic/schema';
@@ -51,6 +57,7 @@ defineOptions({
51
57
 
52
58
  defineProps<{
53
59
  stageContentMenu: (MenuButton | MenuComponent)[];
60
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
54
61
  }>();
55
62
 
56
63
  let stage: StageCore | null = null;
@@ -107,6 +114,7 @@ watch(zoom, (zoom) => {
107
114
 
108
115
  watch(page, (page) => {
109
116
  if (runtime && page) {
117
+ services?.editorService.set('stageLoading', true);
110
118
  runtime.updatePageId?.(page.id);
111
119
  nextTick(() => {
112
120
  stage?.select(page.id);
@@ -134,15 +142,15 @@ const resizeObserver = new ResizeObserver((entries) => {
134
142
  onMounted(() => {
135
143
  if (stageWrap.value?.container) {
136
144
  resizeObserver.observe(stageWrap.value.container);
137
- services?.keybindingService.registeEl(KeyBindingContainerKey.STAGE, stageWrap.value.container);
145
+ services?.keybindingService.registerEl(KeyBindingContainerKey.STAGE, stageWrap.value.container);
138
146
  }
139
147
  });
140
148
 
141
- onUnmounted(() => {
149
+ onBeforeUnmount(() => {
142
150
  stage?.destroy();
143
151
  resizeObserver.disconnect();
144
152
  services?.editorService.set('stage', null);
145
- services?.keybindingService.unregisteEl('stage');
153
+ services?.keybindingService.unregisterEl('stage');
146
154
  services?.editorService.off('root-change', rootChangeHandler);
147
155
  });
148
156