@tmagic/editor 1.4.8 → 1.4.10

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 (166) hide show
  1. package/dist/style.css +26 -0
  2. package/dist/tmagic-editor.js +3213 -2820
  3. package/dist/tmagic-editor.umd.cjs +3237 -2839
  4. package/package.json +14 -12
  5. package/src/Editor.vue +212 -0
  6. package/src/components/CodeBlockEditor.vue +268 -0
  7. package/src/components/CodeParams.vue +60 -0
  8. package/src/components/ContentMenu.vue +193 -0
  9. package/src/components/FloatingBox.vue +178 -0
  10. package/src/components/Icon.vue +27 -0
  11. package/src/components/Resizer.vue +23 -0
  12. package/src/components/ScrollBar.vue +151 -0
  13. package/src/components/ScrollViewer.vue +128 -0
  14. package/src/components/SearchInput.vue +37 -0
  15. package/src/components/SplitView.vue +186 -0
  16. package/src/components/ToolButton.vue +138 -0
  17. package/src/components/Tree.vue +71 -0
  18. package/src/components/TreeNode.vue +150 -0
  19. package/src/editorProps.ts +120 -0
  20. package/src/fields/Code.vue +50 -0
  21. package/src/fields/CodeLink.vue +80 -0
  22. package/src/fields/CodeSelect.vue +132 -0
  23. package/src/fields/CodeSelectCol.vue +139 -0
  24. package/src/fields/CondOpSelect.vue +87 -0
  25. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +152 -0
  26. package/src/fields/DataSourceFieldSelect/Index.vue +168 -0
  27. package/src/fields/DataSourceFields.vue +329 -0
  28. package/src/fields/DataSourceInput.vue +339 -0
  29. package/src/fields/DataSourceMethodSelect.vue +153 -0
  30. package/src/fields/DataSourceMethods.vue +104 -0
  31. package/src/fields/DataSourceMocks.vue +255 -0
  32. package/src/fields/DataSourceSelect.vue +98 -0
  33. package/src/fields/DisplayConds.vue +145 -0
  34. package/src/fields/EventSelect.vue +375 -0
  35. package/src/fields/KeyValue.vue +137 -0
  36. package/src/fields/PageFragmentSelect.vue +63 -0
  37. package/src/fields/UISelect.vue +135 -0
  38. package/src/hooks/index.ts +24 -0
  39. package/src/hooks/use-code-block-edit.ts +83 -0
  40. package/src/hooks/use-data-source-edit.ts +46 -0
  41. package/src/hooks/use-data-source-method.ts +100 -0
  42. package/src/hooks/use-editor-content-height.ts +26 -0
  43. package/src/hooks/use-filter.ts +55 -0
  44. package/src/hooks/use-float-box.ts +76 -0
  45. package/src/hooks/use-getso.ts +35 -0
  46. package/src/hooks/use-next-float-box-position.ts +29 -0
  47. package/src/hooks/use-node-status.ts +48 -0
  48. package/src/hooks/use-stage.ts +129 -0
  49. package/src/hooks/use-window-rect.ts +20 -0
  50. package/src/icons/AppManageIcon.vue +15 -0
  51. package/src/icons/CenterIcon.vue +13 -0
  52. package/src/icons/CodeIcon.vue +28 -0
  53. package/src/icons/FolderMinusIcon.vue +22 -0
  54. package/src/icons/PinIcon.vue +25 -0
  55. package/src/icons/PinnedIcon.vue +25 -0
  56. package/src/index.ts +124 -0
  57. package/src/initService.ts +448 -0
  58. package/src/layouts/AddPageBox.vue +55 -0
  59. package/src/layouts/CodeEditor.vue +226 -0
  60. package/src/layouts/Framework.vue +165 -0
  61. package/src/layouts/NavMenu.vue +200 -0
  62. package/src/layouts/PropsPanel.vue +131 -0
  63. package/src/layouts/page-bar/AddButton.vue +48 -0
  64. package/src/layouts/page-bar/PageBar.vue +165 -0
  65. package/src/layouts/page-bar/PageBarScrollContainer.vue +192 -0
  66. package/src/layouts/page-bar/PageList.vue +55 -0
  67. package/src/layouts/page-bar/SwitchTypeButton.vue +45 -0
  68. package/src/layouts/sidebar/ComponentListPanel.vue +134 -0
  69. package/src/layouts/sidebar/Sidebar.vue +278 -0
  70. package/src/layouts/sidebar/code-block/CodeBlockList.vue +160 -0
  71. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +70 -0
  72. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +84 -0
  73. package/src/layouts/sidebar/data-source/DataSourceList.vue +172 -0
  74. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +106 -0
  75. package/src/layouts/sidebar/layer/LayerMenu.vue +128 -0
  76. package/src/layouts/sidebar/layer/LayerNodeTool.vue +32 -0
  77. package/src/layouts/sidebar/layer/LayerPanel.vue +119 -0
  78. package/src/layouts/sidebar/layer/use-click.ts +113 -0
  79. package/src/layouts/sidebar/layer/use-drag.ts +167 -0
  80. package/src/layouts/sidebar/layer/use-keybinding.ts +54 -0
  81. package/src/layouts/sidebar/layer/use-node-status.ts +114 -0
  82. package/src/layouts/workspace/Breadcrumb.vue +35 -0
  83. package/src/layouts/workspace/Workspace.vue +46 -0
  84. package/src/layouts/workspace/viewer/NodeListMenu.vue +115 -0
  85. package/src/layouts/workspace/viewer/Stage.vue +246 -0
  86. package/src/layouts/workspace/viewer/StageOverlay.vue +68 -0
  87. package/src/layouts/workspace/viewer/ViewerMenu.vue +145 -0
  88. package/src/services/BaseService.ts +230 -0
  89. package/src/services/codeBlock.ts +327 -0
  90. package/src/services/componentList.ts +58 -0
  91. package/src/services/dataSource.ts +216 -0
  92. package/src/services/dep.ts +152 -0
  93. package/src/services/editor.ts +1135 -0
  94. package/src/services/events.ts +93 -0
  95. package/src/services/history.ts +126 -0
  96. package/src/services/keybinding.ts +220 -0
  97. package/src/services/props.ts +286 -0
  98. package/src/services/stageOverlay.ts +212 -0
  99. package/src/services/storage.ts +155 -0
  100. package/src/services/ui.ts +157 -0
  101. package/src/shims-vue.d.ts +6 -0
  102. package/src/theme/breadcrumb.scss +6 -0
  103. package/src/theme/code-block.scss +6 -0
  104. package/src/theme/code-editor.scss +38 -0
  105. package/src/theme/common/var.scss +14 -0
  106. package/src/theme/component-list-panel.scss +95 -0
  107. package/src/theme/content-menu.scss +76 -0
  108. package/src/theme/data-source-field-select.scss +16 -0
  109. package/src/theme/data-source-field.scss +12 -0
  110. package/src/theme/data-source-fields.scss +13 -0
  111. package/src/theme/data-source-input.scss +18 -0
  112. package/src/theme/data-source-methods.scss +13 -0
  113. package/src/theme/data-source.scss +28 -0
  114. package/src/theme/event.scss +37 -0
  115. package/src/theme/floating-box.scss +32 -0
  116. package/src/theme/framework.scss +69 -0
  117. package/src/theme/icon.scss +12 -0
  118. package/src/theme/index.scss +10 -0
  119. package/src/theme/key-value.scss +20 -0
  120. package/src/theme/layer-panel.scss +26 -0
  121. package/src/theme/layout.scss +9 -0
  122. package/src/theme/nav-menu.scss +78 -0
  123. package/src/theme/page-bar.scss +102 -0
  124. package/src/theme/page-fragment-select.scss +14 -0
  125. package/src/theme/props-panel.scss +55 -0
  126. package/src/theme/resizer.scss +66 -0
  127. package/src/theme/ruler.scss +38 -0
  128. package/src/theme/search-input.scss +14 -0
  129. package/src/theme/sidebar.scss +79 -0
  130. package/src/theme/stage.scss +76 -0
  131. package/src/theme/theme.scss +28 -0
  132. package/src/theme/tree.scss +89 -0
  133. package/src/theme/workspace.scss +9 -0
  134. package/src/type.ts +780 -0
  135. package/src/utils/compose.ts +52 -0
  136. package/src/utils/config.ts +29 -0
  137. package/src/utils/content-menu.ts +95 -0
  138. package/src/utils/data-source/formConfigs/base.ts +30 -0
  139. package/src/utils/data-source/formConfigs/http.ts +60 -0
  140. package/src/utils/data-source/index.ts +249 -0
  141. package/src/utils/editor.ts +313 -0
  142. package/src/utils/idle-task.ts +72 -0
  143. package/src/utils/index.ts +24 -0
  144. package/src/utils/keybinding-config.ts +125 -0
  145. package/src/utils/logger.ts +47 -0
  146. package/src/utils/monaco-editor.ts +8 -0
  147. package/src/utils/operator.ts +106 -0
  148. package/src/utils/props.ts +407 -0
  149. package/src/utils/scroll-viewer.ts +165 -0
  150. package/src/utils/tree.ts +15 -0
  151. package/src/utils/undo-redo.ts +76 -0
  152. package/types/editorProps.d.ts +3 -1
  153. package/types/fields/CondOpSelect.vue.d.ts +17 -0
  154. package/types/fields/DataSourceFieldSelect/FieldSelect.vue.d.ts +36 -0
  155. package/types/fields/{DataSourceFieldSelect.vue.d.ts → DataSourceFieldSelect/Index.vue.d.ts} +1 -1
  156. package/types/fields/DisplayConds.vue.d.ts +32 -0
  157. package/types/index.d.ts +3 -1
  158. package/types/layouts/Framework.vue.d.ts +3 -1
  159. package/types/layouts/PropsPanel.vue.d.ts +21 -21
  160. package/types/layouts/page-bar/PageBar.vue.d.ts +6 -0
  161. package/types/layouts/page-bar/PageBarScrollContainer.vue.d.ts +3 -0
  162. package/types/layouts/page-bar/PageList.vue.d.ts +25 -0
  163. package/types/services/ui.d.ts +1 -0
  164. package/types/type.d.ts +30 -4
  165. package/types/utils/data-source/index.d.ts +1 -0
  166. package/types/utils/props.d.ts +12 -0
@@ -0,0 +1,134 @@
1
+ <template>
2
+ <TMagicScrollbar>
3
+ <slot name="component-list-panel-header"></slot>
4
+
5
+ <TMagicCollapse class="ui-component-panel" :model-value="collapseValue">
6
+ <SearchInput @search="filterTextChangeHandler"></SearchInput>
7
+ <template v-for="(group, index) in list">
8
+ <TMagicCollapseItem v-if="group.items && group.items.length" :key="index" :name="`${index}`">
9
+ <template #title><MIcon :icon="Grid"></MIcon>{{ group.title }}</template>
10
+ <div
11
+ v-for="item in group.items"
12
+ class="component-item"
13
+ draggable="true"
14
+ :key="item.type"
15
+ @click="appendComponent(item)"
16
+ @dragstart="dragstartHandler(item, $event)"
17
+ @dragend="dragendHandler"
18
+ @drag="dragHandler"
19
+ >
20
+ <slot name="component-list-item" :component="item">
21
+ <TMagicTooltip placement="right" :disabled="!Boolean(item.desc)" :content="item.desc">
22
+ <MIcon :icon="item.icon"></MIcon>
23
+ </TMagicTooltip>
24
+ <span :title="item.text">{{ item.text }}</span>
25
+ </slot>
26
+ </div>
27
+ </TMagicCollapseItem>
28
+ </template>
29
+ </TMagicCollapse>
30
+ </TMagicScrollbar>
31
+ </template>
32
+
33
+ <script lang="ts" setup>
34
+ import { computed, inject, ref } from 'vue';
35
+ import { Grid } from '@element-plus/icons-vue';
36
+ import serialize from 'serialize-javascript';
37
+
38
+ import { TMagicCollapse, TMagicCollapseItem, TMagicScrollbar, TMagicTooltip } from '@tmagic/design';
39
+ import { removeClassNameByClassName } from '@tmagic/utils';
40
+
41
+ import MIcon from '@editor/components/Icon.vue';
42
+ import SearchInput from '@editor/components/SearchInput.vue';
43
+ import {
44
+ type ComponentGroup,
45
+ type ComponentItem,
46
+ ComponentListPanelSlots,
47
+ DragType,
48
+ type Services,
49
+ type StageOptions,
50
+ } from '@editor/type';
51
+
52
+ defineSlots<ComponentListPanelSlots>();
53
+
54
+ defineOptions({
55
+ name: 'MEditorComponentListPanel',
56
+ });
57
+
58
+ const searchText = ref('');
59
+
60
+ const filterTextChangeHandler = (v: string) => {
61
+ searchText.value = v;
62
+ };
63
+
64
+ const services = inject<Services>('services');
65
+ const stageOptions = inject<StageOptions>('stageOptions');
66
+
67
+ const stage = computed(() => services?.editorService.get('stage'));
68
+ const list = computed(() =>
69
+ services?.componentListService.getList().map((group: ComponentGroup) => ({
70
+ ...group,
71
+ items: group.items.filter((item: ComponentItem) => item.text.includes(searchText.value)),
72
+ })),
73
+ );
74
+ const collapseValue = computed(() =>
75
+ Array(list.value?.length)
76
+ .fill(1)
77
+ .map((x, i) => `${i}`),
78
+ );
79
+
80
+ let timeout: NodeJS.Timeout | undefined;
81
+ let clientX: number;
82
+ let clientY: number;
83
+
84
+ const appendComponent = ({ text, type, data = {} }: ComponentItem): void => {
85
+ services?.editorService.add({
86
+ name: text,
87
+ type,
88
+ ...data,
89
+ });
90
+ };
91
+
92
+ const dragstartHandler = ({ text, type, data = {} }: ComponentItem, e: DragEvent) => {
93
+ e.dataTransfer?.setData(
94
+ 'text/json',
95
+ serialize({
96
+ dragType: DragType.COMPONENT_LIST,
97
+ data: {
98
+ name: text,
99
+ type,
100
+ ...data,
101
+ },
102
+ }),
103
+ );
104
+ };
105
+
106
+ const dragendHandler = () => {
107
+ if (timeout) {
108
+ globalThis.clearTimeout(timeout);
109
+ timeout = undefined;
110
+ }
111
+ const doc = stage.value?.renderer.getDocument();
112
+ if (doc && stageOptions?.containerHighlightClassName) {
113
+ removeClassNameByClassName(doc, stageOptions.containerHighlightClassName);
114
+ }
115
+ clientX = 0;
116
+ clientY = 0;
117
+ };
118
+
119
+ const dragHandler = (e: DragEvent) => {
120
+ if (e.clientX !== clientX || e.clientY !== clientY) {
121
+ clientX = e.clientX;
122
+ clientY = e.clientY;
123
+ if (timeout) {
124
+ globalThis.clearTimeout(timeout);
125
+ timeout = undefined;
126
+ }
127
+ return;
128
+ }
129
+
130
+ if (timeout || !stage.value) return;
131
+
132
+ timeout = stage.value.delayedMarkContainer(e);
133
+ };
134
+ </script>
@@ -0,0 +1,278 @@
1
+ <template>
2
+ <div class="m-editor-sidebar" v-if="data.type === 'tabs' && data.items.length">
3
+ <div class="m-editor-sidebar-header">
4
+ <div
5
+ class="m-editor-sidebar-header-item"
6
+ v-for="(config, index) in sideBarItems"
7
+ v-show="!floatBoxStates[config.$key]?.status"
8
+ draggable="true"
9
+ :key="config.$key ?? index"
10
+ :class="{ 'is-active': activeTabName === config.text }"
11
+ :style="config.tabStyle || {}"
12
+ @click="activeTabName = config.text || config.$key || `${index}`"
13
+ @dragstart="dragstartHandler"
14
+ @dragend="dragendHandler(config.$key, $event)"
15
+ >
16
+ <MIcon v-if="config.icon" :icon="config.icon"></MIcon>
17
+ <div v-if="config.text" class="magic-editor-tab-panel-title">{{ config.text }}</div>
18
+ </div>
19
+ </div>
20
+ <div
21
+ class="m-editor-sidebar-content"
22
+ v-for="(config, index) in sideBarItems"
23
+ :key="config.$key ?? index"
24
+ v-show="[config.text, config.$key, `${index}`].includes(activeTabName)"
25
+ >
26
+ <component
27
+ v-if="config && !floatBoxStates[config.$key]?.status"
28
+ :is="config.component"
29
+ v-bind="config.props || {}"
30
+ v-on="config?.listeners || {}"
31
+ >
32
+ <template
33
+ #component-list-panel-header
34
+ v-if="config.$key === 'component-list' || config.slots?.componentListPanelHeader"
35
+ >
36
+ <slot v-if="config.$key === 'component-list'" name="component-list-panel-header"></slot>
37
+ <component v-else-if="config.slots?.componentListPanelHeader" :is="config.slots.componentListPanelHeader" />
38
+ </template>
39
+
40
+ <template
41
+ #component-list-item="{ component }"
42
+ v-if="config.$key === 'component-list' || config.slots?.componentListItem"
43
+ >
44
+ <slot v-if="config.$key === 'component-list'" name="component-list-item" :component="component"></slot>
45
+ <component
46
+ v-else-if="config.slots?.componentListItem"
47
+ :is="config.slots.componentListItem"
48
+ :component="component"
49
+ />
50
+ </template>
51
+
52
+ <template #layer-panel-header v-if="config.$key === 'layer' || config.slots?.layerPanelHeader">
53
+ <slot v-if="config.$key === 'layer'" name="layer-panel-header"></slot>
54
+ <component v-else-if="config.slots?.layerPanelHeader" :is="config.slots.layerPanelHeader" />
55
+ </template>
56
+
57
+ <template #code-block-panel-header v-if="config.$key === 'code-block' || config.slots?.codeBlockPanelHeader">
58
+ <slot v-if="config.$key === 'code-block'" name="code-block-panel-header"></slot>
59
+ <component v-else-if="config.slots?.codeBlockPanelHeader" :is="config.slots.codeBlockPanelHeader" />
60
+ </template>
61
+
62
+ <template
63
+ #code-block-panel-tool="{ id, data }"
64
+ v-if="config.$key === 'code-block' || config.slots?.codeBlockPanelTool"
65
+ >
66
+ <slot v-if="config.$key === 'code-block'" name="code-block-panel-tool" :id="id" :data="data"></slot>
67
+ <component v-else-if="config.slots?.codeBlockPanelTool" :is="config.slots.codeBlockPanelTool" />
68
+ </template>
69
+
70
+ <template #code-block-panel-search v-if="config.$key === 'code-block' || config.slots?.codeBlockPanelSearch">
71
+ <slot v-if="config.$key === 'code-block'" name="code-block-panel-search"></slot>
72
+ <component v-else-if="config.slots?.codeBlockPanelSearch" :is="config.slots.codeBlockPanelSearch" />
73
+ </template>
74
+
75
+ <template
76
+ #layer-node-content="{ data: nodeData }"
77
+ v-if="config.$key === 'layer' || config.slots?.layerNodeContent"
78
+ >
79
+ <slot v-if="config.$key === 'layer'" name="layer-node-content" :data="nodeData"></slot>
80
+ <component v-else-if="config.slots?.layerNodeContent" :is="config.slots.layerNodeContent" :data="nodeData" />
81
+ </template>
82
+
83
+ <template #layer-node-label="{ data: nodeData }" v-if="config.$key === 'layer' || config.slots?.layerNodeLabel">
84
+ <slot v-if="config.$key === 'layer'" name="layer-node-label" :data="nodeData"></slot>
85
+ <component v-else-if="config.slots?.layerNodeLabel" :is="config.slots.layerNodeTool" :data="nodeData" />
86
+ </template>
87
+
88
+ <template #layer-node-tool="{ data: nodeData }" v-if="config.$key === 'layer' || config.slots?.layerNodeTool">
89
+ <slot v-if="config.$key === 'layer'" name="layer-node-tool" :data="nodeData"></slot>
90
+ <component v-else-if="config.slots?.layerNodeTool" :is="config.slots.layerNodeTool" :data="nodeData" />
91
+ </template>
92
+
93
+ <template
94
+ #data-source-panel-tool="{ data }"
95
+ v-if="config.$key === 'data-source' || config.slots?.dataSourcePanelTool"
96
+ >
97
+ <slot v-if="config.$key === 'data-source'" name="data-source-panel-tool" :data="data"></slot>
98
+ <component v-else-if="config.slots?.DataSourcePanelTool" :is="config.slots.DataSourcePanelTool" />
99
+ </template>
100
+
101
+ <template #data-source-panel-search v-if="config.$key === 'data-source' || config.slots?.dataSourcePanelSearch">
102
+ <slot v-if="config.$key === 'data-source'" name="data-source-panel-search"></slot>
103
+ <component v-else-if="config.slots?.dataSourcePanelSearch" :is="config.slots.dataSourcePanelSearch" />
104
+ </template>
105
+ </component>
106
+ </div>
107
+ </div>
108
+
109
+ <Teleport to="body">
110
+ <template v-for="(config, index) in sideBarItems">
111
+ <FloatingBox
112
+ :key="config.$key ?? index"
113
+ v-if="floatBoxStates[config.$key]?.status"
114
+ v-model:visible="floatBoxStates[config.$key].status"
115
+ v-model:height="columnLeftHeight"
116
+ :width="columnLeftWidth"
117
+ :title="config.text"
118
+ :position="{
119
+ left: floatBoxStates[config.$key].left,
120
+ top: floatBoxStates[config.$key].top,
121
+ }"
122
+ >
123
+ <template #body>
124
+ <div class="m-editor-slide-list-box">
125
+ <component
126
+ v-if="config && floatBoxStates[config.$key].status"
127
+ :is="config.component"
128
+ v-bind="config.props || {}"
129
+ v-on="config?.listeners || {}"
130
+ />
131
+ </div>
132
+ </template>
133
+ </FloatingBox>
134
+ </template>
135
+ </Teleport>
136
+ </template>
137
+
138
+ <script setup lang="ts">
139
+ import { computed, inject, ref, watch } from 'vue';
140
+ import { Coin, EditPen, Goods, List } from '@element-plus/icons-vue';
141
+
142
+ import FloatingBox from '@editor/components/FloatingBox.vue';
143
+ import MIcon from '@editor/components/Icon.vue';
144
+ import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
145
+ import { useFloatBox } from '@editor/hooks/use-float-box';
146
+ import {
147
+ ColumnLayout,
148
+ type MenuButton,
149
+ type MenuComponent,
150
+ type Services,
151
+ type SideBarData,
152
+ type SidebarSlots,
153
+ type SideComponent,
154
+ type SideItem,
155
+ SideItemKey,
156
+ } from '@editor/type';
157
+
158
+ import CodeBlockListPanel from './code-block/CodeBlockListPanel.vue';
159
+ import DataSourceListPanel from './data-source/DataSourceListPanel.vue';
160
+ import LayerPanel from './layer/LayerPanel.vue';
161
+ import ComponentListPanel from './ComponentListPanel.vue';
162
+
163
+ defineSlots<SidebarSlots>();
164
+
165
+ defineOptions({
166
+ name: 'MEditorSidebar',
167
+ });
168
+
169
+ const props = withDefaults(
170
+ defineProps<{
171
+ data: SideBarData;
172
+ layerContentMenu: (MenuButton | MenuComponent)[];
173
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
174
+ }>(),
175
+ {
176
+ data: () => ({
177
+ type: 'tabs',
178
+ status: '组件',
179
+ items: [SideItemKey.COMPONENT_LIST, SideItemKey.LAYER, SideItemKey.CODE_BLOCK, SideItemKey.DATA_SOURCE],
180
+ }),
181
+ },
182
+ );
183
+
184
+ const services = inject<Services>('services');
185
+
186
+ const columnLeftWidth = computed(() => services?.uiService.get('columnWidth')[ColumnLayout.LEFT] || 0);
187
+ const { height: columnLeftHeight } = useEditorContentHeight();
188
+
189
+ const activeTabName = ref(props.data?.status);
190
+
191
+ const getItemConfig = (data: SideItem): SideComponent => {
192
+ const map: Record<string, SideComponent> = {
193
+ [SideItemKey.COMPONENT_LIST]: {
194
+ $key: SideItemKey.COMPONENT_LIST,
195
+ type: 'component',
196
+ icon: Goods,
197
+ text: '组件',
198
+ component: ComponentListPanel,
199
+ slots: {},
200
+ },
201
+ layer: {
202
+ $key: 'layer',
203
+ type: 'component',
204
+ icon: List,
205
+ text: '已选组件',
206
+ props: {
207
+ layerContentMenu: props.layerContentMenu,
208
+ customContentMenu: props.customContentMenu,
209
+ },
210
+ component: LayerPanel,
211
+ slots: {},
212
+ },
213
+ [SideItemKey.CODE_BLOCK]: {
214
+ $key: 'code-block',
215
+ type: 'component',
216
+ icon: EditPen,
217
+ text: '代码编辑',
218
+ component: CodeBlockListPanel,
219
+ slots: {},
220
+ },
221
+ [SideItemKey.DATA_SOURCE]: {
222
+ $key: SideItemKey.DATA_SOURCE,
223
+ type: 'component',
224
+ icon: Coin,
225
+ text: '数据源',
226
+ component: DataSourceListPanel,
227
+ slots: {},
228
+ },
229
+ };
230
+
231
+ return typeof data === 'string' ? map[data] : data;
232
+ };
233
+
234
+ const sideBarItems = computed(() => props.data.items.map((item) => getItemConfig(item)));
235
+
236
+ watch(
237
+ sideBarItems,
238
+ (items) => {
239
+ services?.uiService.set('sideBarItems', items);
240
+ },
241
+ {
242
+ immediate: true,
243
+ },
244
+ );
245
+
246
+ watch(
247
+ () => props.data.status,
248
+ (status) => {
249
+ activeTabName.value = status || '0';
250
+ },
251
+ );
252
+
253
+ const slideKeys = computed(() => sideBarItems.value.map((sideBarItem) => sideBarItem.$key));
254
+
255
+ const { dragstartHandler, dragendHandler, floatBoxStates, showingBoxKeys } = useFloatBox(slideKeys);
256
+
257
+ watch(
258
+ () => showingBoxKeys.value.length,
259
+ () => {
260
+ const isActiveTabShow = showingBoxKeys.value.some(
261
+ (key) => activeTabName.value === sideBarItems.value.find((v) => v.$key === key)?.text,
262
+ );
263
+ if (!isActiveTabShow && activeTabName.value) return;
264
+ const nextSlideBarItem = sideBarItems.value.find((sideBarItem) => !showingBoxKeys.value.includes(sideBarItem.$key));
265
+ if (!nextSlideBarItem) {
266
+ activeTabName.value = '';
267
+ services?.uiService.set('hideSlideBar', true);
268
+ return;
269
+ }
270
+ services?.uiService.set('hideSlideBar', false);
271
+ activeTabName.value = nextSlideBarItem?.text;
272
+ },
273
+ );
274
+
275
+ defineExpose({
276
+ activeTabName,
277
+ });
278
+ </script>
@@ -0,0 +1,160 @@
1
+ <template>
2
+ <Tree :data="codeList" :node-status-map="nodeStatusMap" @node-click="clickHandler">
3
+ <template #tree-node-label="{ data }">
4
+ <div
5
+ :class="{
6
+ code: data.type === 'code',
7
+ hook: data.type === 'key',
8
+ disabled: data.type === 'key' || data.type === 'code',
9
+ }"
10
+ >
11
+ {{ data.name }} {{ data.key ? `(${data.key})` : '' }}
12
+ </div>
13
+ </template>
14
+
15
+ <template #tree-node-tool="{ data }">
16
+ <TMagicTooltip v-if="data.type === 'code'" effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
17
+ <Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editCode(`${data.key}`)"></Icon>
18
+ </TMagicTooltip>
19
+ <TMagicTooltip v-if="data.type === 'code' && editable" effect="dark" content="删除" placement="bottom">
20
+ <Icon :icon="Close" class="edit-icon" @click.stop="deleteCode(`${data.key}`)"></Icon>
21
+ </TMagicTooltip>
22
+ <slot name="code-block-panel-tool" :id="data.key" :data="data"></slot>
23
+ </template>
24
+ </Tree>
25
+ </template>
26
+
27
+ <script lang="ts" setup>
28
+ import { computed, inject } from 'vue';
29
+ import { Close, Edit, View } from '@element-plus/icons-vue';
30
+
31
+ import { DepTargetType } from '@tmagic/dep';
32
+ import { tMagicMessage, tMagicMessageBox, TMagicTooltip } from '@tmagic/design';
33
+ import type { Id, MNode } from '@tmagic/schema';
34
+
35
+ import Icon from '@editor/components/Icon.vue';
36
+ import Tree from '@editor/components/Tree.vue';
37
+ import { useFilter } from '@editor/hooks/use-filter';
38
+ import { useNodeStatus } from '@editor/hooks/use-node-status';
39
+ import { type CodeBlockListSlots, CodeDeleteErrorType, type Services, type TreeNodeData } from '@editor/type';
40
+
41
+ defineSlots<CodeBlockListSlots>();
42
+
43
+ defineOptions({
44
+ name: 'MEditorCodeBlockList',
45
+ });
46
+
47
+ const props = defineProps<{
48
+ customError?: (id: Id, errorType: CodeDeleteErrorType) => any;
49
+ }>();
50
+
51
+ const emit = defineEmits<{
52
+ edit: [id: string];
53
+ remove: [id: string];
54
+ }>();
55
+
56
+ const services = inject<Services>('services');
57
+ const { codeBlockService, depService, editorService } = services || {};
58
+
59
+ // 代码块列表
60
+ const codeList = computed<TreeNodeData[]>(() =>
61
+ Object.entries(codeBlockService?.getCodeDsl() || {}).map(([codeId, code]) => {
62
+ const target = depService?.getTarget(codeId, DepTargetType.CODE_BLOCK);
63
+
64
+ // 按页面分类显示
65
+ const pageList: TreeNodeData[] =
66
+ editorService?.get('root')?.items.map((page) => ({
67
+ name: page.devconfig?.tabName || page.name,
68
+ type: 'node',
69
+ id: `${codeId}_${page.id}`,
70
+ key: page.id,
71
+ items: [],
72
+ })) || [];
73
+
74
+ // 组件节点
75
+ if (target) {
76
+ Object.entries(target.deps).forEach(([id, dep]) => {
77
+ const page = pageList.find((page) => page.key === dep.data?.pageId);
78
+ page?.items?.push({
79
+ name: dep.name,
80
+ type: 'node',
81
+ id: `${page.id}_${id}`,
82
+ key: id,
83
+ items: dep.keys.map((key) => {
84
+ const data: TreeNodeData = { name: `${key}`, id: `${target.id}_${id}_${key}`, type: 'key' };
85
+ return data;
86
+ }),
87
+ });
88
+ });
89
+ }
90
+
91
+ const data: TreeNodeData = {
92
+ id: codeId,
93
+ key: codeId,
94
+ name: code.name,
95
+ type: 'code',
96
+ codeBlockContent: codeBlockService?.getCodeContentById(codeId),
97
+ // 只有一个页面不显示页面分类
98
+ items: pageList.length > 1 ? pageList.filter((page) => page.items?.length) : pageList[0]?.items || [],
99
+ };
100
+
101
+ return data;
102
+ }),
103
+ );
104
+
105
+ const filterNode = (value: string, data: MNode): boolean => {
106
+ if (!value) {
107
+ return true;
108
+ }
109
+ return `${data.name}${data.id}`.toLocaleLowerCase().includes(value.toLocaleLowerCase());
110
+ };
111
+
112
+ const { nodeStatusMap } = useNodeStatus(codeList);
113
+ const { filterTextChangeHandler } = useFilter(codeList, nodeStatusMap, filterNode);
114
+
115
+ const editable = computed(() => codeBlockService?.getEditStatus());
116
+
117
+ // 选中组件
118
+ const selectComp = (compId: Id) => {
119
+ const stage = editorService?.get('stage');
120
+ editorService?.select(compId);
121
+ stage?.select(compId);
122
+ };
123
+
124
+ const clickHandler = (event: MouseEvent, data: any) => {
125
+ if (data.type === 'node') {
126
+ selectComp(data.key);
127
+ }
128
+ };
129
+
130
+ // 编辑代码块
131
+ const editCode = (id: string) => {
132
+ emit('edit', id);
133
+ };
134
+
135
+ const deleteCode = async (id: string) => {
136
+ const currentCode = codeList.value.find((codeItem) => codeItem.id === id);
137
+ const existBinds = Boolean(currentCode?.items?.length);
138
+ const undeleteableList = codeBlockService?.getUndeletableList() || [];
139
+ if (!existBinds && !undeleteableList.includes(id)) {
140
+ await tMagicMessageBox.confirm('确定删除该代码块吗?', '提示', {
141
+ confirmButtonText: '确定',
142
+ cancelButtonText: '取消',
143
+ type: 'warning',
144
+ });
145
+
146
+ // 无绑定关系,且不在不可删除列表中
147
+ emit('remove', id);
148
+ } else {
149
+ if (typeof props.customError === 'function') {
150
+ props.customError(id, existBinds ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
151
+ } else {
152
+ tMagicMessage.error('代码块删除失败');
153
+ }
154
+ }
155
+ };
156
+
157
+ defineExpose({
158
+ filter: filterTextChangeHandler,
159
+ });
160
+ </script>
@@ -0,0 +1,70 @@
1
+ <template>
2
+ <TMagicScrollbar class="m-editor-code-block-list m-editor-layer-panel">
3
+ <slot name="code-block-panel-header">
4
+ <div class="search-wrapper">
5
+ <SearchInput @search="filterTextChangeHandler"></SearchInput>
6
+ <TMagicButton v-if="editable" class="create-code-button" type="primary" size="small" @click="createCodeBlock"
7
+ >新增</TMagicButton
8
+ >
9
+ <slot name="code-block-panel-search"></slot>
10
+ </div>
11
+ </slot>
12
+
13
+ <!-- 代码块列表 -->
14
+ <CodeBlockList ref="codeBlockList" :custom-error="customError" @edit="editCode" @remove="deleteCode">
15
+ <template #code-block-panel-tool="{ id, data }">
16
+ <slot name="code-block-panel-tool" :id="id" :data="data"></slot>
17
+ </template>
18
+ </CodeBlockList>
19
+ </TMagicScrollbar>
20
+
21
+ <CodeBlockEditor
22
+ v-if="codeConfig"
23
+ ref="codeBlockEditor"
24
+ :disabled="!editable"
25
+ :content="codeConfig"
26
+ @submit="submitCodeBlockHandler"
27
+ ></CodeBlockEditor>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { computed, inject, ref } from 'vue';
32
+
33
+ import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
34
+ import type { Id } from '@tmagic/schema';
35
+
36
+ import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue';
37
+ import SearchInput from '@editor/components/SearchInput.vue';
38
+ import { useCodeBlockEdit } from '@editor/hooks/use-code-block-edit';
39
+ import type { CodeBlockListPanelSlots, CodeDeleteErrorType, EventBus, Services } from '@editor/type';
40
+
41
+ import CodeBlockList from './CodeBlockList.vue';
42
+
43
+ defineSlots<CodeBlockListPanelSlots>();
44
+
45
+ defineOptions({
46
+ name: 'MEditorCodeBlockListPanel',
47
+ });
48
+
49
+ defineProps<{
50
+ customError?: (id: Id, errorType: CodeDeleteErrorType) => any;
51
+ }>();
52
+
53
+ const eventBus = inject<EventBus>('eventBus');
54
+ const { codeBlockService } = inject<Services>('services') || {};
55
+
56
+ const editable = computed(() => codeBlockService?.getEditStatus());
57
+
58
+ const { codeBlockEditor, codeConfig, editCode, deleteCode, createCodeBlock, submitCodeBlockHandler } =
59
+ useCodeBlockEdit(codeBlockService);
60
+
61
+ const codeBlockList = ref<InstanceType<typeof CodeBlockList>>();
62
+
63
+ const filterTextChangeHandler = (val: string) => {
64
+ codeBlockList.value?.filter(val);
65
+ };
66
+
67
+ eventBus?.on('edit-code', (id: string) => {
68
+ editCode(id);
69
+ });
70
+ </script>