@tmagic/editor 1.0.0-beta.9 → 1.0.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +1 -0
  2. package/dist/style.css +72 -36
  3. package/dist/tmagic-editor.es.js +1555 -2080
  4. package/dist/tmagic-editor.es.js.map +1 -1
  5. package/dist/tmagic-editor.umd.js +1555 -2082
  6. package/dist/tmagic-editor.umd.js.map +1 -1
  7. package/dist/types/src/Editor.vue.d.ts +50 -2
  8. package/dist/types/src/fields/Code.vue.d.ts +4 -4
  9. package/dist/types/src/fields/CodeLink.vue.d.ts +1 -3
  10. package/dist/types/src/index.d.ts +1 -0
  11. package/dist/types/src/layouts/CodeEditor.vue.d.ts +13 -4
  12. package/dist/types/src/layouts/Framework.vue.d.ts +15 -3
  13. package/dist/types/src/layouts/PropsPanel.vue.d.ts +4 -5
  14. package/dist/types/src/layouts/sidebar/ComponentListPanel.vue.d.ts +2 -1
  15. package/dist/types/src/layouts/sidebar/Sidebar.vue.d.ts +2 -3
  16. package/dist/types/src/layouts/sidebar/TabPane.vue.d.ts +14 -0
  17. package/dist/types/src/layouts/workspace/Workspace.vue.d.ts +5 -32
  18. package/dist/types/src/services/BaseService.d.ts +5 -2
  19. package/dist/types/src/services/editor.d.ts +11 -5
  20. package/dist/types/src/services/history.d.ts +2 -18
  21. package/dist/types/src/services/props.d.ts +8 -2
  22. package/dist/types/src/services/ui.d.ts +2 -0
  23. package/dist/types/src/type.d.ts +24 -9
  24. package/dist/types/src/utils/editor.d.ts +4 -9
  25. package/dist/types/src/utils/polyfills.d.ts +0 -0
  26. package/dist/types/src/utils/props.d.ts +10 -1
  27. package/package.json +35 -12
  28. package/src/Editor.vue +44 -9
  29. package/src/components/ContentMenu.vue +140 -0
  30. package/src/components/ScrollViewer.vue +1 -1
  31. package/src/components/ToolButton.vue +69 -30
  32. package/src/fields/UISelect.vue +14 -6
  33. package/src/index.ts +2 -0
  34. package/src/layouts/CodeEditor.vue +20 -13
  35. package/src/layouts/Framework.vue +20 -7
  36. package/src/layouts/PropsPanel.vue +5 -4
  37. package/src/layouts/sidebar/ComponentListPanel.vue +25 -3
  38. package/src/layouts/sidebar/LayerMenu.vue +93 -95
  39. package/src/layouts/sidebar/LayerPanel.vue +31 -54
  40. package/src/layouts/sidebar/Sidebar.vue +13 -52
  41. package/src/layouts/sidebar/TabPane.vue +68 -0
  42. package/src/layouts/workspace/PageBar.vue +136 -13
  43. package/src/layouts/workspace/Stage.vue +62 -104
  44. package/src/layouts/workspace/ViewerMenu.vue +119 -67
  45. package/src/layouts/workspace/Workspace.vue +124 -46
  46. package/src/services/BaseService.ts +71 -23
  47. package/src/services/editor.ts +196 -52
  48. package/src/services/history.ts +7 -0
  49. package/src/services/props.ts +64 -9
  50. package/src/services/ui.ts +35 -5
  51. package/src/theme/common/var.scss +1 -0
  52. package/src/theme/component-list-panel.scss +2 -0
  53. package/src/theme/content-menu.scss +33 -26
  54. package/src/theme/layer-panel.scss +2 -1
  55. package/src/theme/nav-menu.scss +6 -2
  56. package/src/theme/page-bar.scss +36 -3
  57. package/src/theme/props-panel.scss +19 -17
  58. package/src/theme/workspace.scss +4 -0
  59. package/src/type.ts +26 -9
  60. package/src/utils/editor.ts +35 -55
  61. package/src/utils/polyfills.ts +8 -0
  62. package/src/utils/props.ts +51 -8
  63. package/LICENSE +0 -5432
  64. package/dist/types/src/components/ToolButton.vue.d.ts +0 -35
  65. package/dist/types/src/fields/UISelect.vue.d.ts +0 -32
  66. package/dist/types/src/layouts/sidebar/LayerMenu.vue.d.ts +0 -31
  67. package/dist/types/src/layouts/sidebar/LayerPanel.vue.d.ts +0 -979
  68. package/dist/types/src/layouts/workspace/PageBar.vue.d.ts +0 -11
  69. package/dist/types/src/layouts/workspace/Stage.vue.d.ts +0 -193
  70. package/dist/types/src/layouts/workspace/ViewerMenu.vue.d.ts +0 -18
  71. package/tsconfig.json +0 -9
  72. package/vite.config.ts +0 -30
@@ -1,36 +1,26 @@
1
1
  <template>
2
- <el-tabs v-if="data.type === 'tabs'" class="m-editor-sidebar" v-model="activeTabName" type="card" tab-position="left">
3
- <el-tab-pane v-for="item in items" :key="item.text" :name="item.text">
4
- <template #label>
5
- <span>
6
- <m-icon v-if="item.icon" :icon="item.icon"></m-icon>
7
- <div v-if="item.text" class="magic-editor-tab-panel-title">{{ item.text }}</div>
8
- </span>
9
- </template>
10
-
11
- <component :is="item.component" v-bind="item.props || {}" v-on="item.listeners || {}">
12
- <template #layer-node-content="{ data, node }" v-if="item.slots?.layerNodeContent">
13
- <component :is="item.slots.layerNodeContent" :data="data" :node="node" />
14
- </template>
15
- </component>
16
- </el-tab-pane>
2
+ <el-tabs
3
+ v-if="data.type === 'tabs' && data.items.length"
4
+ class="m-editor-sidebar"
5
+ v-model="activeTabName"
6
+ type="card"
7
+ tab-position="left"
8
+ >
9
+ <tab-pane v-for="(item, index) in data.items" :key="index" :data="item"></tab-pane>
17
10
  </el-tabs>
18
11
  </template>
19
12
 
20
13
  <script lang="ts">
21
- import { computed, defineComponent, PropType, ref, watch } from 'vue';
22
- import { Coin, Files } from '@element-plus/icons';
14
+ import { defineComponent, PropType, ref, watch } from 'vue';
23
15
 
24
- import MIcon from '@editor/components/Icon.vue';
25
- import { SideBarData, SideComponent } from '@editor/type';
16
+ import { SideBarData } from '@editor/type';
26
17
 
27
- import ComponentListPanel from './ComponentListPanel.vue';
28
- import LayerPanel from './LayerPanel.vue';
18
+ import TabPane from './TabPane.vue';
29
19
 
30
20
  export default defineComponent({
31
- name: 'm-sidebar',
21
+ components: { TabPane },
32
22
 
33
- components: { MIcon },
23
+ name: 'm-sidebar',
34
24
 
35
25
  props: {
36
26
  data: {
@@ -51,35 +41,6 @@ export default defineComponent({
51
41
 
52
42
  return {
53
43
  activeTabName,
54
-
55
- items: computed<SideComponent[] | Record<string, any>[]>(() =>
56
- props.data?.items.map((item) => {
57
- if (typeof item !== 'string') {
58
- return item;
59
- }
60
-
61
- switch (item) {
62
- case 'component-list':
63
- return {
64
- type: 'component',
65
- icon: Coin,
66
- text: '组件',
67
- component: ComponentListPanel,
68
- slots: {},
69
- };
70
- case 'layer':
71
- return {
72
- type: 'component',
73
- icon: Files,
74
- text: '已选组件',
75
- component: LayerPanel,
76
- slots: {},
77
- };
78
- default:
79
- return {};
80
- }
81
- }),
82
- ),
83
44
  };
84
45
  },
85
46
  });
@@ -0,0 +1,68 @@
1
+ <template>
2
+ <el-tab-pane v-if="config" :name="config.text">
3
+ <template #label>
4
+ <div :key="config.text">
5
+ <m-icon v-if="config.icon" :icon="config.icon"></m-icon>
6
+ <div v-if="config.text" class="magic-editor-tab-panel-title">{{ config.text }}</div>
7
+ </div>
8
+ </template>
9
+
10
+ <component :is="config.component" v-bind="config.props || {}" v-on="config?.listeners || {}">
11
+ <template #layer-node-content="{ data, node }" v-if="config.slots?.layerNodeContent">
12
+ <component :is="config.slots?.layerNodeContent" :data="data" :node="node" />
13
+ </template>
14
+ </component>
15
+ </el-tab-pane>
16
+ </template>
17
+
18
+ <script lang="ts">
19
+ import { computed, defineComponent, PropType } from 'vue';
20
+ import { Coin, Files } from '@element-plus/icons';
21
+
22
+ import MIcon from '@editor/components/Icon.vue';
23
+ import { SideComponent, SideItem } from '@editor/type';
24
+
25
+ import ComponentListPanel from './ComponentListPanel.vue';
26
+ import LayerPanel from './LayerPanel.vue';
27
+
28
+ export default defineComponent({
29
+ components: { MIcon },
30
+
31
+ props: {
32
+ data: {
33
+ type: [Object, String] as PropType<SideItem>,
34
+ },
35
+ },
36
+
37
+ setup(props) {
38
+ return {
39
+ config: computed<SideComponent | undefined>(() => {
40
+ if (typeof props.data !== 'string') {
41
+ return props.data;
42
+ }
43
+
44
+ switch (props.data) {
45
+ case 'component-list':
46
+ return {
47
+ type: 'component',
48
+ icon: Coin,
49
+ text: '组件',
50
+ component: ComponentListPanel,
51
+ slots: {},
52
+ };
53
+ case 'layer':
54
+ return {
55
+ type: 'component',
56
+ icon: Files,
57
+ text: '已选组件',
58
+ component: LayerPanel,
59
+ slots: {},
60
+ };
61
+ default:
62
+ return undefined;
63
+ }
64
+ }),
65
+ };
66
+ },
67
+ });
68
+ </script>
@@ -1,9 +1,12 @@
1
1
  <template>
2
- <div class="m-editor-page-bar">
3
- <div class="m-editor-page-bar-item" @click="addPage">
4
- <el-icon class="m-editor-page-bar-menu-add-icon"><plus></plus></el-icon>
2
+ <div class="m-editor-page-bar" ref="pageBar">
3
+ <div id="m-editor-page-bar-add-icon" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="addPage">
4
+ <el-icon><plus></plus></el-icon>
5
5
  </div>
6
- <template v-if="root">
6
+ <div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('left')">
7
+ <el-icon><arrow-left-bold></arrow-left-bold></el-icon>
8
+ </div>
9
+ <div v-if="root" class="m-editor-page-bar-items" ref="itemsContainer" :style="`width: ${itemsContainerWidth}px`">
7
10
  <div
8
11
  v-for="item in root.items"
9
12
  :key="item.key"
@@ -19,11 +22,25 @@
19
22
  </slot>
20
23
  </div>
21
24
 
22
- <el-popover placement="top" :width="160" trigger="hover">
25
+ <el-popover popper-class="page-bar-popover" placement="top" :width="160" trigger="hover">
23
26
  <div>
24
27
  <slot name="page-bar-popover" :page="item">
25
- <div class="magic-editor-content-menu-item" @click="() => copy(item)">复制</div>
26
- <div class="magic-editor-content-menu-item" @click="() => remove(item)">删除</div>
28
+ <tool-button
29
+ :data="{
30
+ type: 'button',
31
+ text: '复制',
32
+ icon: DocumentCopy,
33
+ handler: () => copy(item),
34
+ }"
35
+ ></tool-button>
36
+ <tool-button
37
+ :data="{
38
+ type: 'button',
39
+ text: '删除',
40
+ icon: Delete,
41
+ handler: () => remove(item),
42
+ }"
43
+ ></tool-button>
27
44
  </slot>
28
45
  </div>
29
46
  <template #reference>
@@ -33,29 +50,135 @@
33
50
  </template>
34
51
  </el-popover>
35
52
  </div>
36
- </template>
53
+ </div>
54
+ <div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('right')">
55
+ <el-icon><arrow-right-bold></arrow-right-bold></el-icon>
56
+ </div>
37
57
  </div>
38
58
  </template>
39
59
 
40
60
  <script lang="ts">
41
- import { computed, defineComponent, inject, toRaw } from 'vue';
42
- import { CaretBottom, Plus } from '@element-plus/icons';
61
+ import {
62
+ computed,
63
+ ComputedRef,
64
+ defineComponent,
65
+ inject,
66
+ markRaw,
67
+ onMounted,
68
+ onUnmounted,
69
+ ref,
70
+ toRaw,
71
+ watch,
72
+ } from 'vue';
73
+ import { ArrowLeftBold, ArrowRightBold, CaretBottom, Delete, DocumentCopy, Plus } from '@element-plus/icons';
43
74
 
44
- import type { MPage } from '@tmagic/schema';
75
+ import type { MApp, MPage } from '@tmagic/schema';
45
76
  import { NodeType } from '@tmagic/schema';
46
77
 
78
+ import ToolButton from '@editor/components/ToolButton.vue';
47
79
  import type { Services } from '@editor/type';
48
80
  import { generatePageNameByApp } from '@editor/utils/editor';
49
81
 
82
+ const useScroll = (root: ComputedRef<MApp | undefined>) => {
83
+ const pageBar = ref<HTMLDivElement>();
84
+ const itemsContainer = ref<HTMLDivElement>();
85
+
86
+ const pageBarWidth = ref(0);
87
+ const canScroll = ref(false);
88
+
89
+ const itemsContainerWidth = computed(() => pageBarWidth.value - 105);
90
+
91
+ let translateLeft = 0;
92
+ const resizeObserver = new ResizeObserver((entries) => {
93
+ for (const { contentRect } of entries) {
94
+ const { width } = contentRect;
95
+ pageBarWidth.value = width || 0;
96
+
97
+ setCanScroll();
98
+ }
99
+ });
100
+
101
+ const setCanScroll = () => {
102
+ if (itemsContainer.value) {
103
+ canScroll.value = itemsContainer.value.scrollWidth > pageBarWidth.value - 105;
104
+ }
105
+ };
106
+
107
+ const scroll = (type: 'left' | 'right' | 'start' | 'end') => {
108
+ if (!itemsContainer.value) return;
109
+
110
+ const maxScrollLeft = itemsContainer.value.scrollWidth - itemsContainerWidth.value;
111
+
112
+ if (type === 'left') {
113
+ translateLeft += 100;
114
+
115
+ if (translateLeft > 0) {
116
+ translateLeft = 0;
117
+ }
118
+ } else if (type === 'right') {
119
+ translateLeft -= 100;
120
+
121
+ if (-translateLeft > maxScrollLeft) {
122
+ translateLeft = -maxScrollLeft;
123
+ }
124
+ } else if (type === 'start') {
125
+ translateLeft = 0;
126
+ } else if (type === 'end') {
127
+ translateLeft = -maxScrollLeft;
128
+ }
129
+
130
+ itemsContainer.value.style.transform = `translate(${translateLeft}px, 0px)`;
131
+ };
132
+
133
+ onMounted(() => {
134
+ pageBar.value && resizeObserver.observe(pageBar.value);
135
+ });
136
+
137
+ onUnmounted(() => {
138
+ resizeObserver.disconnect();
139
+ });
140
+
141
+ watch(
142
+ () => root.value?.items.length,
143
+ (length = 0, preLength = 0) => {
144
+ setTimeout(() => {
145
+ setCanScroll();
146
+ if (length < preLength) {
147
+ scroll('start');
148
+ } else {
149
+ scroll('end');
150
+ }
151
+ });
152
+ },
153
+ );
154
+
155
+ return {
156
+ pageBar,
157
+ itemsContainer,
158
+ canScroll,
159
+
160
+ itemsContainerWidth,
161
+
162
+ scroll,
163
+ };
164
+ };
165
+
50
166
  export default defineComponent({
51
- components: { CaretBottom, Plus },
167
+ components: { ArrowLeftBold, ArrowRightBold, CaretBottom, Plus, ToolButton },
52
168
 
53
169
  setup() {
54
170
  const services = inject<Services>('services');
55
171
  const editorService = services?.editorService;
56
172
 
173
+ const root = computed(() => editorService?.get<MApp>('root'));
174
+
57
175
  return {
58
- root: computed(() => editorService?.get('root')),
176
+ Delete: markRaw(Delete),
177
+ DocumentCopy: markRaw(DocumentCopy),
178
+
179
+ ...useScroll(root),
180
+
181
+ root,
59
182
  page: computed(() => editorService?.get('page')),
60
183
 
61
184
  switchPage(page: MPage) {
@@ -9,11 +9,13 @@
9
9
  <div
10
10
  class="m-editor-stage-container"
11
11
  ref="stageContainer"
12
- @contextmenu="contextmenuHandler"
13
12
  :style="`transform: scale(${zoom})`"
13
+ @contextmenu="contextmenuHandler"
14
+ @drop="dropHandler"
15
+ @dragover="dragoverHandler"
14
16
  ></div>
15
17
  <teleport to="body">
16
- <viewer-menu ref="menu" :style="menuStyle"></viewer-menu>
18
+ <viewer-menu ref="menu"></viewer-menu>
17
19
  </teleport>
18
20
  </scroll-viewer>
19
21
  </template>
@@ -23,10 +25,9 @@ import {
23
25
  computed,
24
26
  defineComponent,
25
27
  inject,
26
- nextTick,
28
+ markRaw,
27
29
  onMounted,
28
30
  onUnmounted,
29
- PropType,
30
31
  ref,
31
32
  toRaw,
32
33
  watch,
@@ -35,53 +36,14 @@ import {
35
36
  import { cloneDeep } from 'lodash-es';
36
37
 
37
38
  import type { MApp, MNode, MPage } from '@tmagic/schema';
38
- import type { MoveableOptions, Runtime, SortEventData, UpdateEventData } from '@tmagic/stage';
39
+ import type { Runtime, SortEventData, UpdateEventData } from '@tmagic/stage';
39
40
  import StageCore from '@tmagic/stage';
40
41
 
41
42
  import ScrollViewer from '@editor/components/ScrollViewer.vue';
42
- import type { Services, StageRect } from '@editor/type';
43
+ import { Layout, Services, StageOptions, StageRect } from '@editor/type';
43
44
 
44
45
  import ViewerMenu from './ViewerMenu.vue';
45
46
 
46
- const useMenu = () => {
47
- const menu = ref<InstanceType<typeof ViewerMenu>>();
48
- const menuStyle = ref({
49
- display: 'none',
50
- left: '0',
51
- top: '0',
52
- });
53
-
54
- onMounted(() => {
55
- document.addEventListener(
56
- 'click',
57
- () => {
58
- menuStyle.value.display = 'none';
59
- },
60
- true,
61
- );
62
- });
63
-
64
- return {
65
- menu,
66
- menuStyle,
67
-
68
- contextmenuHandler(e: MouseEvent) {
69
- e.preventDefault();
70
-
71
- const menuHeight = menu.value?.$el.clientHeight;
72
- let top = e.clientY;
73
- if (menuHeight + e.clientY > document.body.clientHeight) {
74
- top = document.body.clientHeight - menuHeight;
75
- }
76
- menuStyle.value = {
77
- display: 'block',
78
- top: `${top}px`,
79
- left: `${e.clientX}px`,
80
- };
81
- },
82
- };
83
- };
84
-
85
47
  export default defineComponent({
86
48
  name: 'magic-stage',
87
49
 
@@ -90,41 +52,20 @@ export default defineComponent({
90
52
  ScrollViewer,
91
53
  },
92
54
 
93
- props: {
94
- render: {
95
- type: Function as PropType<() => HTMLDivElement>,
96
- },
97
-
98
- runtimeUrl: String,
99
-
100
- canSelect: {
101
- type: Function as PropType<(el: HTMLElement) => boolean | Promise<boolean>>,
102
- default: (el: HTMLElement) => Boolean(el.id),
103
- },
104
-
105
- moveableOptions: {
106
- type: [Object, Function] as PropType<MoveableOptions | ((core?: StageCore) => MoveableOptions)>,
107
- default: () => (core?: StageCore) => ({
108
- container: core?.renderer?.contentWindow?.document.getElementById('app'),
109
- }),
110
- },
111
- },
112
-
113
- emits: ['select', 'update', 'sort', 'highlight'],
114
-
115
- setup(props, { emit }) {
55
+ setup() {
116
56
  const services = inject<Services>('services');
57
+ const stageOptions = inject<StageOptions>('stageOptions');
117
58
 
118
59
  const stageWrap = ref<InstanceType<typeof ScrollViewer>>();
119
60
  const stageContainer = ref<HTMLDivElement>();
61
+ const menu = ref<InstanceType<typeof ViewerMenu>>();
120
62
 
121
63
  const stageRect = computed(() => services?.uiService.get<StageRect>('stageRect'));
122
64
  const uiSelectMode = computed(() => services?.uiService.get<boolean>('uiSelectMode'));
123
65
  const root = computed(() => services?.editorService.get<MApp>('root'));
124
66
  const page = computed(() => services?.editorService.get<MPage>('page'));
125
- const zoom = computed(() => services?.uiService.get<number>('zoom'));
67
+ const zoom = computed(() => services?.uiService.get<number>('zoom') || 1);
126
68
  const node = computed(() => services?.editorService.get<MNode>('node'));
127
- const highlightNode = computed(() => services?.editorService.get<MNode>('highlightNode'));
128
69
 
129
70
  let stage: StageCore | null = null;
130
71
  let runtime: Runtime | null = null;
@@ -133,43 +74,45 @@ export default defineComponent({
133
74
  if (stage) return;
134
75
 
135
76
  if (!stageContainer.value) return;
136
- if (!(props.runtimeUrl || props.render) || !root.value) return;
77
+ if (!(stageOptions?.runtimeUrl || stageOptions?.render) || !root.value) return;
137
78
 
138
79
  stage = new StageCore({
139
- render: props.render,
140
- runtimeUrl: props.runtimeUrl,
80
+ render: stageOptions.render,
81
+ runtimeUrl: stageOptions.runtimeUrl,
141
82
  zoom: zoom.value,
142
- canSelect: (el, stop) => {
143
- const elCanSelect = props.canSelect(el);
83
+ autoScrollIntoView: stageOptions.autoScrollIntoView,
84
+ canSelect: (el, event, stop) => {
85
+ const elCanSelect = stageOptions.canSelect(el);
144
86
  // 在组件联动过程中不能再往下选择,返回并触发 ui-select
145
- if (uiSelectMode.value && elCanSelect) {
87
+ if (uiSelectMode.value && elCanSelect && event.type === 'mousedown') {
146
88
  document.dispatchEvent(new CustomEvent('ui-select', { detail: el }));
147
89
  return stop();
148
90
  }
149
91
 
150
92
  return elCanSelect;
151
93
  },
152
- moveableOptions: props.moveableOptions,
94
+ moveableOptions: stageOptions.moveableOptions,
95
+ updateDragEl: stageOptions.updateDragEl,
153
96
  });
154
97
 
155
- services?.editorService.set('stage', stage);
98
+ services?.editorService.set('stage', markRaw(stage));
156
99
 
157
100
  stage?.mount(stageContainer.value);
158
101
 
159
102
  stage?.on('select', (el: HTMLElement) => {
160
- emit('select', el);
103
+ services?.editorService.select(el.id);
161
104
  });
162
105
 
163
106
  stage?.on('highlight', (el: HTMLElement) => {
164
- emit('highlight', el);
107
+ services?.editorService.highlight(el.id);
165
108
  });
166
109
 
167
110
  stage?.on('update', (ev: UpdateEventData) => {
168
- emit('update', { id: ev.el.id, style: ev.style });
111
+ services?.editorService.update({ id: ev.el.id, style: ev.style });
169
112
  });
170
113
 
171
114
  stage?.on('sort', (ev: SortEventData) => {
172
- emit('sort', ev);
115
+ services?.editorService.sort(ev.src, ev.dist);
173
116
  });
174
117
 
175
118
  stage?.on('changeGuides', () => {
@@ -180,7 +123,7 @@ export default defineComponent({
180
123
  stage?.on('runtime-ready', (rt) => {
181
124
  runtime = rt;
182
125
  // toRaw返回的值是一个引用而非快照,需要cloneDeep
183
- root.value && runtime?.updateRootConfig(cloneDeep(toRaw(root.value)));
126
+ root.value && runtime?.updateRootConfig?.(cloneDeep(toRaw(root.value)));
184
127
  page.value?.id && runtime?.updatePageId?.(page.value.id);
185
128
  setTimeout(() => {
186
129
  node.value && stage?.select(toRaw(node.value.id));
@@ -195,29 +138,10 @@ export default defineComponent({
195
138
 
196
139
  watch(root, (root) => {
197
140
  if (runtime && root) {
198
- runtime.updateRootConfig(cloneDeep(toRaw(root)));
141
+ runtime.updateRootConfig?.(cloneDeep(toRaw(root)));
199
142
  }
200
143
  });
201
144
 
202
- watch(
203
- () => node.value?.id,
204
- (id) => {
205
- nextTick(() => {
206
- // 等待相关dom变更完成后,再select,适用大多数场景
207
- id && stage?.select(id);
208
- });
209
- },
210
- );
211
-
212
- watch(
213
- () => highlightNode.value?.id,
214
- (id) => {
215
- nextTick(() => {
216
- id && stage?.highlight(id);
217
- });
218
- },
219
- );
220
-
221
145
  const resizeObserver = new ResizeObserver((entries) => {
222
146
  for (const { contentRect } of entries) {
223
147
  services?.uiService.set('stageContainerRect', {
@@ -240,9 +164,43 @@ export default defineComponent({
240
164
  return {
241
165
  stageWrap,
242
166
  stageContainer,
167
+ menu,
243
168
  stageRect,
244
169
  zoom,
245
- ...useMenu(),
170
+
171
+ contextmenuHandler(e: MouseEvent) {
172
+ e.preventDefault();
173
+ menu.value?.show(e);
174
+ },
175
+
176
+ dragoverHandler(e: DragEvent) {
177
+ e.preventDefault();
178
+ if (e.dataTransfer) {
179
+ e.dataTransfer.dropEffect = 'move';
180
+ }
181
+ },
182
+
183
+ async dropHandler(e: DragEvent) {
184
+ e.preventDefault();
185
+ if (e.dataTransfer && page.value && stageContainer.value && stage) {
186
+ // eslint-disable-next-line no-eval
187
+ const config = eval(`(${e.dataTransfer.getData('data')})`);
188
+ const layout = await services?.editorService.getLayout(page.value);
189
+
190
+ const containerRect = stageContainer.value.getBoundingClientRect();
191
+ const { scrollTop, scrollLeft } = stage.mask;
192
+ if (layout === Layout.ABSOLUTE) {
193
+ config.style = {
194
+ ...(config.style || {}),
195
+ position: 'absolute',
196
+ top: e.clientY - containerRect.top + scrollTop,
197
+ left: e.clientX - containerRect.left + scrollLeft,
198
+ };
199
+ }
200
+
201
+ services?.editorService.add(config, page.value);
202
+ }
203
+ },
246
204
  };
247
205
  },
248
206
  });