@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,37 @@
1
+ <template>
2
+ <TMagicInput
3
+ v-model="filterText"
4
+ class="search-input"
5
+ size="small"
6
+ placeholder="输入关键字进行过滤"
7
+ clearable
8
+ @input="filterTextChangeHandler"
9
+ >
10
+ <template #prefix>
11
+ <TMagicIcon><Search /></TMagicIcon>
12
+ </template>
13
+ </TMagicInput>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { ref } from 'vue';
18
+ import { Search } from '@element-plus/icons-vue';
19
+
20
+ import { TMagicIcon, TMagicInput } from '@tmagic/design';
21
+
22
+ defineOptions({
23
+ name: 'MEditorSearchInput',
24
+ });
25
+
26
+ const emit = defineEmits(['search']);
27
+
28
+ const filterText = ref('');
29
+
30
+ let timer: NodeJS.Timeout | null = null;
31
+ const filterTextChangeHandler = () => {
32
+ timer && clearTimeout(timer);
33
+ timer = setTimeout(() => {
34
+ emit('search', filterText.value);
35
+ }, 300);
36
+ };
37
+ </script>
@@ -0,0 +1,186 @@
1
+ <template>
2
+ <div ref="el" class="m-editor-layout" :style="`min-width: ${props.minCenter + props.minLeft + props.minRight}px`">
3
+ <template v-if="hasLeft && $slots.left">
4
+ <div class="m-editor-layout-left" :class="leftClass" :style="`width: ${left}px`">
5
+ <slot name="left"></slot>
6
+ </div>
7
+ <Resizer @change="changeLeft"></Resizer>
8
+ </template>
9
+
10
+ <div class="m-editor-layout-center" :class="centerClass" :style="`width: ${center}px`">
11
+ <slot name="center"></slot>
12
+ </div>
13
+
14
+ <template v-if="hasRight && $slots.right">
15
+ <Resizer @change="changeRight"></Resizer>
16
+ <div class="m-editor-layout-right" :class="rightClass" :style="`width: ${right}px`">
17
+ <slot name="right"></slot>
18
+ </div>
19
+ </template>
20
+ </div>
21
+ </template>
22
+
23
+ <script lang="ts" setup>
24
+ import { computed, onBeforeUnmount, onMounted, ref, watchEffect } from 'vue';
25
+ import { OnDrag } from 'gesto';
26
+
27
+ import Resizer from './Resizer.vue';
28
+
29
+ defineOptions({
30
+ name: 'MEditorSplitView',
31
+ });
32
+
33
+ const emit = defineEmits(['update:left', 'change', 'update:right']);
34
+
35
+ const props = withDefaults(
36
+ defineProps<{
37
+ width?: number;
38
+ left?: number;
39
+ right?: number;
40
+ minLeft?: number;
41
+ minRight?: number;
42
+ minCenter?: number;
43
+ leftClass?: string;
44
+ rightClass?: string;
45
+ centerClass?: string;
46
+ }>(),
47
+ {
48
+ minLeft: 46,
49
+ minRight: 1,
50
+ minCenter: 5,
51
+ },
52
+ );
53
+
54
+ const el = ref<HTMLElement>();
55
+
56
+ const hasLeft = computed(() => typeof props.left !== 'undefined');
57
+ const hasRight = computed(() => typeof props.right !== 'undefined');
58
+ const center = ref(0);
59
+
60
+ let clientWidth = 0;
61
+
62
+ const getCenterWidth = (l = 0, r = 0) => {
63
+ let right = r > 0 ? r : 0;
64
+ let left = l > 0 ? l : 0;
65
+ let center = clientWidth - left - right;
66
+
67
+ if (center < props.minCenter) {
68
+ center = props.minCenter;
69
+ if (right > center + props.minRight) {
70
+ right = clientWidth - left - center;
71
+ } else {
72
+ right = props.minRight;
73
+ left = clientWidth - right - center;
74
+ }
75
+ }
76
+ return {
77
+ center,
78
+ left,
79
+ right,
80
+ };
81
+ };
82
+
83
+ const widthChange = (width: number) => {
84
+ if (width <= 0) {
85
+ return;
86
+ }
87
+
88
+ clientWidth = width;
89
+ let left = props.left || 0;
90
+ let right = props.right || 0;
91
+
92
+ if (left > clientWidth) {
93
+ left = clientWidth / 3;
94
+ }
95
+
96
+ if (right > clientWidth) {
97
+ right = clientWidth / 3;
98
+ }
99
+
100
+ const columnWidth = getCenterWidth(left, right);
101
+
102
+ center.value = columnWidth.center;
103
+
104
+ emit('change', columnWidth);
105
+ };
106
+
107
+ if (typeof props.width !== 'number') {
108
+ const resizerObserver = new ResizeObserver((entries) => {
109
+ for (const { contentRect } of entries) {
110
+ widthChange(contentRect.width);
111
+ }
112
+ });
113
+
114
+ onMounted(() => {
115
+ if (el.value) {
116
+ resizerObserver.observe(el.value);
117
+ }
118
+ });
119
+
120
+ onBeforeUnmount(() => {
121
+ resizerObserver.disconnect();
122
+ });
123
+ } else {
124
+ watchEffect(() => {
125
+ if (typeof props.width === 'number') widthChange(props.width);
126
+ });
127
+ }
128
+
129
+ const changeLeft = ({ deltaX }: OnDrag) => {
130
+ if (typeof props.left === 'undefined') return;
131
+ let left = Math.max(props.left + deltaX, props.minLeft) || 0;
132
+ emit('update:left', left);
133
+
134
+ if (clientWidth - left - (props.right || 0) <= 0) {
135
+ left = props.left;
136
+ }
137
+
138
+ const columnWidth = getCenterWidth(left, props.right || 0);
139
+
140
+ center.value = columnWidth.center;
141
+
142
+ emit('change', {
143
+ left: columnWidth.left,
144
+ center: center.value,
145
+ right: columnWidth.right,
146
+ });
147
+ };
148
+
149
+ const changeRight = ({ deltaX }: OnDrag) => {
150
+ if (typeof props.right === 'undefined') return;
151
+ let right = Math.max(props.right - deltaX, props.minRight) || 0;
152
+ emit('update:right', right);
153
+
154
+ if (clientWidth - (props.left || 0) - right <= 0) {
155
+ right = props.right;
156
+ }
157
+
158
+ const columnWidth = getCenterWidth(props.left, right);
159
+
160
+ center.value = columnWidth.center;
161
+
162
+ emit('change', {
163
+ left: columnWidth.left,
164
+ center: center.value,
165
+ right: columnWidth.right,
166
+ });
167
+ };
168
+
169
+ defineExpose({
170
+ updateWidth() {
171
+ clientWidth = props.width ?? el.value?.clientWidth ?? clientWidth;
172
+
173
+ if (clientWidth <= 0) {
174
+ return;
175
+ }
176
+
177
+ const columnWidth = getCenterWidth(props.left, props.right);
178
+
179
+ emit('change', {
180
+ left: columnWidth.left,
181
+ center: center.value,
182
+ right: columnWidth.right,
183
+ });
184
+ },
185
+ });
186
+ </script>
@@ -0,0 +1,138 @@
1
+ <template>
2
+ <div
3
+ v-if="display"
4
+ class="menu-item"
5
+ :class="`${data.type} ${data.className || ''}`"
6
+ @click="clickHandler(data, $event)"
7
+ @mousedown="mousedownHandler(data, $event)"
8
+ @mouseup="mouseupHandler(data, $event)"
9
+ >
10
+ <TMagicDivider v-if="data.type === 'divider'" :direction="data.direction || 'vertical'"></TMagicDivider>
11
+ <div v-else-if="data.type === 'text'" class="menu-item-text">{{ data.text }}</div>
12
+
13
+ <template v-else-if="data.type === 'button'">
14
+ <TMagicTooltip v-if="data.tooltip" effect="dark" placement="bottom-start" :content="data.tooltip">
15
+ <TMagicButton size="small" link :disabled="disabled"
16
+ ><MIcon v-if="data.icon" :icon="data.icon"></MIcon><span>{{ data.text }}</span></TMagicButton
17
+ >
18
+ </TMagicTooltip>
19
+ <TMagicButton v-else size="small" link :disabled="disabled" :title="data.text"
20
+ ><MIcon v-if="data.icon" :icon="data.icon"></MIcon><span>{{ data.text }}</span></TMagicButton
21
+ >
22
+ </template>
23
+
24
+ <TMagicDropdown
25
+ v-else-if="data.type === 'dropdown'"
26
+ trigger="click"
27
+ :disabled="disabled"
28
+ @command="dropdownHandler"
29
+ >
30
+ <span class="el-dropdown-link menubar-menu-button">
31
+ {{ data.text }}<TMagicIcon class="el-icon--right"><ArrowDown></ArrowDown></TMagicIcon>
32
+ </span>
33
+ <template #dropdown>
34
+ <TMagicDropdownMenu v-if="data.items && data.items.length">
35
+ <TMagicDropdownItem v-for="(subItem, index) in data.items" :key="index" :command="{ data, subItem }">{{
36
+ subItem.text
37
+ }}</TMagicDropdownItem>
38
+ </TMagicDropdownMenu>
39
+ </template>
40
+ </TMagicDropdown>
41
+
42
+ <component
43
+ v-else-if="data.type === 'component'"
44
+ v-bind="data.props || {}"
45
+ v-on="(data as MenuComponent).listeners || {}"
46
+ :is="data.component"
47
+ ></component>
48
+ </div>
49
+ </template>
50
+
51
+ <script lang="ts" setup>
52
+ import { computed, inject } from 'vue';
53
+ import { ArrowDown } from '@element-plus/icons-vue';
54
+
55
+ import {
56
+ TMagicButton,
57
+ TMagicDivider,
58
+ TMagicDropdown,
59
+ TMagicDropdownItem,
60
+ TMagicDropdownMenu,
61
+ TMagicIcon,
62
+ TMagicTooltip,
63
+ } from '@tmagic/design';
64
+
65
+ import MIcon from '../components/Icon.vue';
66
+ import type { MenuButton, MenuComponent, Services } from '../type';
67
+
68
+ defineOptions({
69
+ name: 'MEditorToolButton',
70
+ });
71
+
72
+ const props = withDefaults(
73
+ defineProps<{
74
+ data?: MenuButton | MenuComponent;
75
+ eventType?: 'mousedown' | 'mouseup' | 'click';
76
+ }>(),
77
+ {
78
+ data: () => ({
79
+ type: 'text',
80
+ display: false,
81
+ }),
82
+ eventType: 'click',
83
+ },
84
+ );
85
+ const services = inject<Services>('services');
86
+
87
+ const disabled = computed(() => {
88
+ if (typeof props.data === 'string') return false;
89
+ if (props.data.type === 'component') return false;
90
+ if (typeof props.data.disabled === 'function') {
91
+ return props.data.disabled(services);
92
+ }
93
+ return props.data.disabled;
94
+ });
95
+
96
+ const display = computed(() => {
97
+ if (!props.data) return false;
98
+ if (typeof props.data === 'string') return true;
99
+ if (typeof props.data.display === 'function') {
100
+ return props.data.display(services);
101
+ }
102
+ return props.data.display ?? true;
103
+ });
104
+
105
+ const buttonHandler = (item: MenuButton | MenuComponent, event: MouseEvent) => {
106
+ if (disabled.value) return;
107
+ if (typeof (item as MenuButton).handler === 'function' && services) {
108
+ (item as MenuButton).handler?.(services, event);
109
+ }
110
+ };
111
+
112
+ const dropdownHandler = (command: any) => {
113
+ if (command.item.handler) {
114
+ command.item.handler(services);
115
+ }
116
+ };
117
+
118
+ const clickHandler = (item: MenuButton | MenuComponent, event: MouseEvent) => {
119
+ if (props.eventType !== 'click') return;
120
+ if (item.type === 'button') {
121
+ buttonHandler(item, event);
122
+ }
123
+ };
124
+
125
+ const mousedownHandler = (item: MenuButton | MenuComponent, event: MouseEvent) => {
126
+ if (props.eventType !== 'mousedown') return;
127
+ if (item.type === 'button') {
128
+ buttonHandler(item, event);
129
+ }
130
+ };
131
+
132
+ const mouseupHandler = (item: MenuButton | MenuComponent, event: MouseEvent) => {
133
+ if (props.eventType !== 'mouseup') return;
134
+ if (item.type === 'button') {
135
+ buttonHandler(item, event);
136
+ }
137
+ };
138
+ </script>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div class="m-editor-tree" @dragover="handleDragOver">
3
+ <template v-if="data?.length">
4
+ <TreeNode v-for="item in data" :key="item.id" :data="item" :indent="indent" :node-status-map="nodeStatusMap">
5
+ <template #tree-node-content="{ data: nodeData }">
6
+ <slot name="tree-node-content" :data="nodeData"> </slot>
7
+ </template>
8
+
9
+ <template #tree-node-label="{ data: nodeData }">
10
+ <slot name="tree-node-label" :data="nodeData"> </slot>
11
+ </template>
12
+
13
+ <template #tree-node-tool="{ data: nodeData }">
14
+ <slot name="tree-node-tool" :data="nodeData"> </slot>
15
+ </template>
16
+ </TreeNode>
17
+ </template>
18
+ <div v-else class="m-editor-tree-empty">
19
+ <p>{{ emptyText }}</p>
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { provide } from 'vue';
26
+
27
+ import type { Id } from '@tmagic/schema';
28
+
29
+ import type { LayerNodeStatus, TreeNodeData } from '@editor/type';
30
+
31
+ import TreeNode from './TreeNode.vue';
32
+
33
+ defineSlots<{
34
+ 'tree-node-content'(props: { data: TreeNodeData }): any;
35
+ 'tree-node-label'(props: { data: TreeNodeData }): any;
36
+ 'tree-node-tool'(props: { data: TreeNodeData }): any;
37
+ }>();
38
+
39
+ defineOptions({
40
+ name: 'MEditorTree',
41
+ });
42
+
43
+ const emit = defineEmits<{
44
+ 'node-dragover': [event: DragEvent];
45
+ 'node-dragstart': [event: DragEvent, data: TreeNodeData];
46
+ 'node-dragleave': [event: DragEvent, data: TreeNodeData];
47
+ 'node-dragend': [event: DragEvent, data: TreeNodeData];
48
+ 'node-contextmenu': [event: MouseEvent, data: TreeNodeData];
49
+ 'node-mouseenter': [event: MouseEvent, data: TreeNodeData];
50
+ 'node-click': [event: MouseEvent, data: TreeNodeData];
51
+ }>();
52
+
53
+ provide('treeEmit', emit);
54
+
55
+ withDefaults(
56
+ defineProps<{
57
+ data: TreeNodeData[];
58
+ nodeStatusMap: Map<Id, LayerNodeStatus>;
59
+ indent?: number;
60
+ emptyText?: string;
61
+ }>(),
62
+ {
63
+ indent: 0,
64
+ emptyText: '暂无数据',
65
+ },
66
+ );
67
+
68
+ const handleDragOver = (event: DragEvent) => {
69
+ emit('node-dragover', event);
70
+ };
71
+ </script>
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <div
3
+ v-show="visible"
4
+ class="m-editor-tree-node"
5
+ :draggable="draggable"
6
+ :data-node-id="data.id"
7
+ :data-is-container="Array.isArray(data.items)"
8
+ @dragstart="handleDragStart"
9
+ @dragleave="handleDragLeave"
10
+ @dragend="handleDragEnd"
11
+ >
12
+ <div
13
+ class="tree-node"
14
+ :class="{ selected, expanded }"
15
+ :style="`padding-left: ${indent}px`"
16
+ @contextmenu="nodeContentmenuHandler"
17
+ @mouseenter="mouseenterHandler"
18
+ >
19
+ <MIcon
20
+ class="expand-icon"
21
+ :style="hasChildren ? '' : 'color: transparent; cursor: default'"
22
+ :icon="expanded ? ArrowDown : ArrowRight"
23
+ @click="expandHandler"
24
+ ></MIcon>
25
+
26
+ <div class="tree-node-content" @click="nodeClickHandler">
27
+ <slot name="tree-node-content" :data="data">
28
+ <div class="tree-node-label">
29
+ <slot name="tree-node-label" :data="data">{{ `${data.name} (${data.id})` }}</slot>
30
+ </div>
31
+ <div class="tree-node-tool">
32
+ <slot name="tree-node-tool" :data="data"></slot>
33
+ </div>
34
+ </slot>
35
+ </div>
36
+ </div>
37
+
38
+ <div v-if="hasChildren && expanded" class="m-editor-tree-node-children">
39
+ <TreeNode
40
+ v-for="item in data.items"
41
+ :key="item.id"
42
+ :data="item"
43
+ :node-status-map="nodeStatusMap"
44
+ :indent="indent + 11"
45
+ >
46
+ <template #tree-node-content="{ data: nodeData }">
47
+ <slot name="tree-node-content" :data="nodeData"> </slot>
48
+ </template>
49
+ <template #tree-node-label="{ data: nodeData }">
50
+ <slot name="tree-node-label" :data="nodeData"> </slot>
51
+ </template>
52
+ <template #tree-node-tool="{ data: nodeData }">
53
+ <slot name="tree-node-tool" :data="nodeData"> </slot>
54
+ </template>
55
+ </TreeNode>
56
+ </div>
57
+ </div>
58
+ </template>
59
+
60
+ <script setup lang="ts">
61
+ import { computed, inject } from 'vue';
62
+ import { ArrowDown, ArrowRight } from '@element-plus/icons-vue';
63
+
64
+ import type { Id } from '@tmagic/schema';
65
+
66
+ import MIcon from '@editor/components/Icon.vue';
67
+ import type { LayerNodeStatus, TreeNodeData } from '@editor/type';
68
+ import { updateStatus } from '@editor/utils/tree';
69
+
70
+ defineSlots<{
71
+ 'tree-node-label'(props: { data: TreeNodeData }): any;
72
+ 'tree-node-tool'(props: { data: TreeNodeData }): any;
73
+ 'tree-node-content'(props: { data: TreeNodeData }): any;
74
+ }>();
75
+
76
+ defineOptions({
77
+ name: 'MEditorTreeNode',
78
+ });
79
+
80
+ const emit = defineEmits<{
81
+ 'node-dragstart': [event: DragEvent, data: TreeNodeData];
82
+ 'node-dragleave': [event: DragEvent, data: TreeNodeData];
83
+ 'node-dragend': [event: DragEvent, data: TreeNodeData];
84
+ 'node-contextmenu': [event: MouseEvent, data: TreeNodeData];
85
+ 'node-mouseenter': [event: MouseEvent, data: TreeNodeData];
86
+ 'node-click': [event: MouseEvent, data: TreeNodeData];
87
+ }>();
88
+
89
+ const treeEmit = inject<typeof emit>('treeEmit');
90
+
91
+ const props = withDefaults(
92
+ defineProps<{
93
+ data: TreeNodeData;
94
+ nodeStatusMap: Map<Id, LayerNodeStatus>;
95
+ indent?: number;
96
+ }>(),
97
+ {
98
+ indent: 0,
99
+ },
100
+ );
101
+
102
+ const nodeStatus = computed(
103
+ () =>
104
+ props.nodeStatusMap?.get(props.data.id) || {
105
+ selected: false,
106
+ expand: false,
107
+ visible: false,
108
+ draggable: false,
109
+ },
110
+ );
111
+
112
+ const expanded = computed(() => nodeStatus.value.expand);
113
+ const selected = computed(() => nodeStatus.value.selected);
114
+ const visible = computed(() => nodeStatus.value.visible);
115
+ const draggable = computed(() => nodeStatus.value.draggable);
116
+
117
+ const hasChildren = computed(
118
+ () => Array.isArray(props.data.items) && props.data.items.some((item) => props.nodeStatusMap.get(item.id)?.visible),
119
+ );
120
+
121
+ const handleDragStart = (event: DragEvent) => {
122
+ treeEmit?.('node-dragstart', event, props.data);
123
+ };
124
+
125
+ const handleDragLeave = (event: DragEvent) => {
126
+ treeEmit?.('node-dragleave', event, props.data);
127
+ };
128
+
129
+ const handleDragEnd = (event: DragEvent) => {
130
+ treeEmit?.('node-dragend', event, props.data);
131
+ };
132
+
133
+ const nodeContentmenuHandler = (event: MouseEvent) => {
134
+ treeEmit?.('node-contextmenu', event, props.data);
135
+ };
136
+
137
+ const mouseenterHandler = (event: MouseEvent) => {
138
+ treeEmit?.('node-mouseenter', event, props.data);
139
+ };
140
+
141
+ const expandHandler = () => {
142
+ updateStatus(props.nodeStatusMap, props.data.id, {
143
+ expand: !expanded.value,
144
+ });
145
+ };
146
+
147
+ const nodeClickHandler = (event: MouseEvent) => {
148
+ treeEmit?.('node-click', event, props.data);
149
+ };
150
+ </script>
@@ -0,0 +1,120 @@
1
+ import type { EventOption } from '@tmagic/core';
2
+ import type { FormConfig, FormState } from '@tmagic/form';
3
+ import type { DataSourceSchema, Id, MApp, MNode } from '@tmagic/schema';
4
+ import StageCore, {
5
+ CONTAINER_HIGHLIGHT_CLASS_NAME,
6
+ ContainerHighlightType,
7
+ type CustomizeMoveableOptionsCallbackConfig,
8
+ type GuidesOptions,
9
+ type MoveableOptions,
10
+ RenderType,
11
+ type UpdateDragEl,
12
+ } from '@tmagic/stage';
13
+
14
+ import type {
15
+ ComponentGroup,
16
+ DatasourceTypeOption,
17
+ MenuBarData,
18
+ MenuButton,
19
+ MenuComponent,
20
+ PageBarSortOptions,
21
+ SideBarData,
22
+ StageRect,
23
+ } from './type';
24
+
25
+ export interface EditorProps {
26
+ /** 页面初始值 */
27
+ modelValue?: MApp;
28
+ /** 左侧面板中的组件类型列表 */
29
+ componentGroupList?: ComponentGroup[];
30
+ /** 左侧面板中的数据源类型列表 */
31
+ datasourceList?: DatasourceTypeOption[];
32
+ /** 左侧面板配置 */
33
+ sidebar?: SideBarData;
34
+ /** 顶部工具栏配置 */
35
+ menu?: MenuBarData;
36
+ /** 组件树右键菜单 */
37
+ layerContentMenu?: (MenuButton | MenuComponent)[];
38
+ /** 画布右键菜单 */
39
+ stageContentMenu?: (MenuButton | MenuComponent)[];
40
+ /** 中间工作区域中画布通过iframe渲染时的页面url */
41
+ runtimeUrl?: string;
42
+ /** 是用iframe渲染还是直接渲染 */
43
+ renderType?: RenderType;
44
+ /** 选中时是否自动滚动到可视区域 */
45
+ autoScrollIntoView?: boolean;
46
+ /** 组件的属性配置表单的dsl */
47
+ propsConfigs?: Record<string, FormConfig>;
48
+ /** 添加组件时的默认值 */
49
+ propsValues?: Record<string, Partial<MNode>>;
50
+ /** 组件联动事件选项列表 */
51
+ eventMethodList?: Record<string, { events: EventOption[]; methods: EventOption[] }>;
52
+ /** 添加数据源时的默认值 */
53
+ datasourceValues?: Record<string, Partial<DataSourceSchema>>;
54
+ /** 数据源的属性配置表单的dsl */
55
+ datasourceConfigs?: Record<string, FormConfig>;
56
+ datasourceEventMethodList?: Record<string, { events: EventOption[]; methods: EventOption[] }>;
57
+ /** 画布中组件选中框的移动范围 */
58
+ moveableOptions?: MoveableOptions | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions);
59
+ /** 编辑器初始化时默认选中的组件ID */
60
+ defaultSelected?: Id;
61
+ /** 拖入画布中容器时,识别到容器后给容器根dom加上的class */
62
+ containerHighlightClassName?: string;
63
+ /** 拖入画布中容器时,悬停识别容器的时间 */
64
+ containerHighlightDuration?: number;
65
+ /** 拖入画布中容器时,识别容器的操作类型 */
66
+ containerHighlightType?: ContainerHighlightType;
67
+ /** 画布大小 */
68
+ stageRect?: StageRect;
69
+ /** monaco editor 的配置 */
70
+ codeOptions?: { [key: string]: any };
71
+ /** 禁用鼠标左键按下时就开始拖拽,需要先选中再可以拖拽 */
72
+ disabledDragStart?: boolean;
73
+ /** 标尺配置 */
74
+ guidesOptions?: Partial<GuidesOptions>;
75
+ /** 禁止多选 */
76
+ disabledMultiSelect?: boolean;
77
+ /** 禁用页面片 */
78
+ disabledPageFragment?: boolean;
79
+ /** 禁用双击在浮层中单独编辑选中组件 */
80
+ disabledStageOverlay?: boolean;
81
+ /** 禁用属性配置面板右下角显示源码的按钮 */
82
+ disabledShowSrc?: boolean;
83
+ /** 中间工作区域中画布渲染的内容 */
84
+ render?: (stage: StageCore) => HTMLDivElement | Promise<HTMLDivElement>;
85
+ /** 选中时会在画布上复制出一个大小相同的dom,实际拖拽的是这个dom,此方法用于干预这个dom的生成方式 */
86
+ updateDragEl?: UpdateDragEl;
87
+ /** 用于设置画布上的dom是否可以被选中 */
88
+ canSelect?: (el: HTMLElement) => boolean | Promise<boolean>;
89
+ /** 用于设置画布上的dom是否可以被拖入其中 */
90
+ isContainer?: (el: HTMLElement) => boolean | Promise<boolean>;
91
+ /** 用于自定义组件树与画布的右键菜单 */
92
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
93
+ extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
94
+ /** 页面顺序拖拽配置参数 */
95
+ pageBarSortOptions?: PageBarSortOptions;
96
+ }
97
+
98
+ export const defaultEditorProps = {
99
+ renderType: RenderType.IFRAME,
100
+ disabledMultiSelect: false,
101
+ disabledPageFragment: false,
102
+ disabledStageOverlay: false,
103
+ containerHighlightClassName: CONTAINER_HIGHLIGHT_CLASS_NAME,
104
+ containerHighlightDuration: 800,
105
+ containerHighlightType: ContainerHighlightType.DEFAULT,
106
+ disabledShowSrc: false,
107
+ componentGroupList: () => [],
108
+ datasourceList: () => [],
109
+ menu: () => ({ left: [], right: [] }),
110
+ layerContentMenu: () => [],
111
+ stageContentMenu: () => [],
112
+ propsConfigs: () => ({}),
113
+ propsValues: () => ({}),
114
+ eventMethodList: () => ({}),
115
+ datasourceValues: () => ({}),
116
+ datasourceConfigs: () => ({}),
117
+ canSelect: (el: HTMLElement) => Boolean(el.id),
118
+ isContainer: (el: HTMLElement) => el.classList.contains('magic-ui-container'),
119
+ codeOptions: () => ({}),
120
+ };