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

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 (120) hide show
  1. package/README.md +1 -0
  2. package/coverage/clover.xml +1556 -0
  3. package/coverage/coverage-final.json +36 -0
  4. package/coverage/lcov-report/base.css +224 -0
  5. package/coverage/lcov-report/block-navigation.js +87 -0
  6. package/coverage/lcov-report/favicon.png +0 -0
  7. package/coverage/lcov-report/index.html +206 -0
  8. package/coverage/lcov-report/prettify.css +1 -0
  9. package/coverage/lcov-report/prettify.js +2 -0
  10. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  11. package/coverage/lcov-report/sorter.js +196 -0
  12. package/coverage/lcov-report/src/Editor.vue.html +814 -0
  13. package/coverage/lcov-report/src/components/ContentMenu.vue.html +406 -0
  14. package/coverage/lcov-report/src/components/Icon.vue.html +172 -0
  15. package/coverage/lcov-report/src/components/ScrollViewer.vue.html +283 -0
  16. package/coverage/lcov-report/src/components/ToolButton.vue.html +772 -0
  17. package/coverage/lcov-report/src/components/index.html +161 -0
  18. package/coverage/lcov-report/src/index.html +131 -0
  19. package/coverage/lcov-report/src/layouts/AddPageBox.vue.html +214 -0
  20. package/coverage/lcov-report/src/layouts/Framework.vue.html +343 -0
  21. package/coverage/lcov-report/src/layouts/NavMenu.vue.html +208 -0
  22. package/coverage/lcov-report/src/layouts/PropsPanel.vue.html +319 -0
  23. package/coverage/lcov-report/src/layouts/Resizer.vue.html +268 -0
  24. package/coverage/lcov-report/src/layouts/index.html +176 -0
  25. package/coverage/lcov-report/src/layouts/sidebar/ComponentListPanel.vue.html +358 -0
  26. package/coverage/lcov-report/src/layouts/sidebar/LayerMenu.vue.html +421 -0
  27. package/coverage/lcov-report/src/layouts/sidebar/LayerPanel.vue.html +775 -0
  28. package/coverage/lcov-report/src/layouts/sidebar/Sidebar.vue.html +226 -0
  29. package/coverage/lcov-report/src/layouts/sidebar/TabPane.vue.html +289 -0
  30. package/coverage/lcov-report/src/layouts/sidebar/index.html +176 -0
  31. package/coverage/lcov-report/src/layouts/workspace/PageBar.vue.html +718 -0
  32. package/coverage/lcov-report/src/layouts/workspace/Stage.vue.html +766 -0
  33. package/coverage/lcov-report/src/layouts/workspace/ViewerMenu.vue.html +532 -0
  34. package/coverage/lcov-report/src/layouts/workspace/Workspace.vue.html +604 -0
  35. package/coverage/lcov-report/src/layouts/workspace/index.html +161 -0
  36. package/coverage/lcov-report/src/services/BaseService.ts.html +643 -0
  37. package/coverage/lcov-report/src/services/componentList.ts.html +229 -0
  38. package/coverage/lcov-report/src/services/editor.ts.html +2077 -0
  39. package/coverage/lcov-report/src/services/events.ts.html +331 -0
  40. package/coverage/lcov-report/src/services/history.ts.html +478 -0
  41. package/coverage/lcov-report/src/services/index.html +206 -0
  42. package/coverage/lcov-report/src/services/props.ts.html +415 -0
  43. package/coverage/lcov-report/src/services/ui.ts.html +580 -0
  44. package/coverage/lcov-report/src/type.ts.html +850 -0
  45. package/coverage/lcov-report/src/utils/compose.ts.html +184 -0
  46. package/coverage/lcov-report/src/utils/config.ts.html +172 -0
  47. package/coverage/lcov-report/src/utils/editor.ts.html +838 -0
  48. package/coverage/lcov-report/src/utils/index.html +221 -0
  49. package/coverage/lcov-report/src/utils/index.ts.html +151 -0
  50. package/coverage/lcov-report/src/utils/logger.ts.html +226 -0
  51. package/coverage/lcov-report/src/utils/props.ts.html +796 -0
  52. package/coverage/lcov-report/src/utils/scroll-viewer.ts.html +733 -0
  53. package/coverage/lcov-report/src/utils/undo-redo.ts.html +313 -0
  54. package/coverage/lcov.info +3747 -0
  55. package/dist/style.css +68 -36
  56. package/dist/tmagic-editor.es.js +1431 -2048
  57. package/dist/tmagic-editor.es.js.map +1 -1
  58. package/dist/tmagic-editor.umd.js +1431 -2048
  59. package/dist/tmagic-editor.umd.js.map +1 -1
  60. package/dist/types/src/Editor.vue.d.ts +15 -1
  61. package/dist/types/src/components/ToolButton.vue.d.ts +12 -1
  62. package/dist/types/src/fields/UISelect.vue.d.ts +4 -1
  63. package/dist/types/src/index.d.ts +1 -0
  64. package/dist/types/src/layouts/CodeEditor.vue.d.ts +13 -4
  65. package/dist/types/src/layouts/Framework.vue.d.ts +15 -3
  66. package/dist/types/src/layouts/PropsPanel.vue.d.ts +2 -1
  67. package/dist/types/src/layouts/sidebar/ComponentListPanel.vue.d.ts +2 -1
  68. package/dist/types/src/layouts/sidebar/Sidebar.vue.d.ts +2 -3
  69. package/dist/types/src/layouts/sidebar/TabPane.vue.d.ts +14 -0
  70. package/dist/types/src/layouts/workspace/PageBar.vue.d.ts +10 -3
  71. package/dist/types/src/layouts/workspace/Workspace.vue.d.ts +9 -8
  72. package/dist/types/src/services/BaseService.d.ts +4 -1
  73. package/dist/types/src/services/editor.d.ts +5 -2
  74. package/dist/types/src/services/history.d.ts +1 -0
  75. package/dist/types/src/services/props.d.ts +2 -2
  76. package/dist/types/src/services/ui.d.ts +2 -0
  77. package/dist/types/src/type.d.ts +15 -9
  78. package/dist/types/src/utils/editor.d.ts +4 -3
  79. package/dist/types/src/utils/polyfills.d.ts +0 -0
  80. package/package.json +44 -12
  81. package/src/Editor.vue +10 -1
  82. package/src/components/ContentMenu.vue +107 -0
  83. package/src/components/ScrollViewer.vue +1 -1
  84. package/src/components/ToolButton.vue +69 -30
  85. package/src/fields/UISelect.vue +14 -6
  86. package/src/index.ts +2 -0
  87. package/src/layouts/CodeEditor.vue +20 -13
  88. package/src/layouts/Framework.vue +20 -7
  89. package/src/layouts/PropsPanel.vue +5 -4
  90. package/src/layouts/sidebar/ComponentListPanel.vue +25 -3
  91. package/src/layouts/sidebar/LayerMenu.vue +91 -96
  92. package/src/layouts/sidebar/LayerPanel.vue +31 -54
  93. package/src/layouts/sidebar/Sidebar.vue +13 -52
  94. package/src/layouts/sidebar/TabPane.vue +68 -0
  95. package/src/layouts/workspace/PageBar.vue +136 -13
  96. package/src/layouts/workspace/Stage.vue +53 -75
  97. package/src/layouts/workspace/ViewerMenu.vue +104 -67
  98. package/src/layouts/workspace/Workspace.vue +111 -22
  99. package/src/services/BaseService.ts +70 -22
  100. package/src/services/editor.ts +148 -39
  101. package/src/services/history.ts +7 -0
  102. package/src/services/props.ts +3 -3
  103. package/src/services/ui.ts +35 -5
  104. package/src/theme/common/var.scss +1 -0
  105. package/src/theme/component-list-panel.scss +2 -0
  106. package/src/theme/content-menu.scss +33 -26
  107. package/src/theme/layer-panel.scss +2 -1
  108. package/src/theme/nav-menu.scss +6 -2
  109. package/src/theme/page-bar.scss +35 -3
  110. package/src/theme/props-panel.scss +19 -17
  111. package/src/type.ts +16 -9
  112. package/src/utils/editor.ts +31 -6
  113. package/src/utils/polyfills.ts +8 -0
  114. package/src/utils/props.ts +20 -1
  115. package/dist/types/src/layouts/sidebar/LayerMenu.vue.d.ts +0 -31
  116. package/dist/types/src/layouts/sidebar/LayerPanel.vue.d.ts +0 -979
  117. package/dist/types/src/layouts/workspace/Stage.vue.d.ts +0 -193
  118. package/dist/types/src/layouts/workspace/ViewerMenu.vue.d.ts +0 -18
  119. package/tsconfig.json +0 -9
  120. package/vite.config.ts +0 -30
@@ -1,15 +1,12 @@
1
1
  <template>
2
- <div class="m-editor-workspace">
2
+ <div class="m-editor-workspace" tabindex="1" ref="workspace">
3
3
  <magic-stage
4
4
  :key="page?.id"
5
5
  :runtime-url="runtimeUrl"
6
+ :auto-scroll-into-view="autoScrollIntoView"
6
7
  :render="render"
7
8
  :moveable-options="moveableOptions"
8
9
  :can-select="canSelect"
9
- @select="selectHandler"
10
- @highlight="highlightHandler"
11
- @update="updateNodeHandler"
12
- @sort="sortNodeHandler"
13
10
  ></magic-stage>
14
11
 
15
12
  <slot name="workspace-content"></slot>
@@ -22,11 +19,13 @@
22
19
  </template>
23
20
 
24
21
  <script lang="ts">
25
- import { computed, defineComponent, inject, PropType } from 'vue';
22
+ import { computed, defineComponent, inject, onMounted, onUnmounted, PropType, ref } from 'vue';
23
+ import KeyController from 'keycon';
26
24
 
27
- import type { MComponent, MContainer, MPage } from '@tmagic/schema';
28
- import type { MoveableOptions, SortEventData } from '@tmagic/stage';
25
+ import type { MNode, MPage } from '@tmagic/schema';
26
+ import type { MoveableOptions } from '@tmagic/stage';
29
27
  import StageCore from '@tmagic/stage';
28
+ import { isPage } from '@tmagic/utils';
30
29
 
31
30
  import type { Services } from '@editor/type';
32
31
 
@@ -43,6 +42,7 @@ export default defineComponent({
43
42
 
44
43
  props: {
45
44
  runtimeUrl: String,
45
+ autoScrollIntoView: Boolean,
46
46
 
47
47
  render: {
48
48
  type: Function as PropType<() => HTMLDivElement>,
@@ -59,25 +59,114 @@ export default defineComponent({
59
59
 
60
60
  setup() {
61
61
  const services = inject<Services>('services');
62
+ const workspace = ref<HTMLDivElement>();
63
+ const node = computed(() => services?.editorService.get<MNode>('node'));
64
+ let keycon: KeyController;
62
65
 
63
- return {
64
- page: computed(() => services?.editorService.get<MPage>('page')),
66
+ const mouseenterHandler = () => {
67
+ workspace.value?.focus();
68
+ };
65
69
 
66
- selectHandler(el: HTMLElement) {
67
- services?.editorService.select(el.id);
68
- },
70
+ const mouseleaveHandler = () => {
71
+ workspace.value?.blur();
72
+ };
69
73
 
70
- updateNodeHandler(node: MComponent | MContainer | MPage) {
71
- services?.editorService.update(node);
72
- },
74
+ onMounted(() => {
75
+ workspace.value?.addEventListener('mouseenter', mouseenterHandler);
76
+ workspace.value?.addEventListener('mouseleave', mouseleaveHandler);
77
+
78
+ keycon = new KeyController(workspace.value);
79
+
80
+ const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
81
+
82
+ const ctrl = isMac ? 'meta' : 'ctrl';
83
+
84
+ keycon
85
+ .keydown((e) => {
86
+ console.log(e);
87
+ e.inputEvent.preventDefault();
88
+ })
89
+ .keyup('delete', (e) => {
90
+ e.inputEvent.preventDefault();
91
+ if (!node.value || isPage(node.value)) return;
92
+ services?.editorService.remove(node.value);
93
+ })
94
+ .keyup('backspace', (e) => {
95
+ e.inputEvent.preventDefault();
96
+ if (!node.value || isPage(node.value)) return;
97
+ services?.editorService.remove(node.value);
98
+ })
99
+ .keydown([ctrl, 'c'], () => {
100
+ node.value && services?.editorService.copy(node.value);
101
+ })
102
+ .keydown([ctrl, 'v'], () => {
103
+ node.value && services?.editorService.paste();
104
+ })
105
+ .keydown([ctrl, 'x'], () => {
106
+ if (!node.value || isPage(node.value)) return;
107
+ services?.editorService.copy(node.value);
108
+ services?.editorService.remove(node.value);
109
+ })
110
+ .keydown([ctrl, 'z'], () => {
111
+ services?.editorService.undo();
112
+ })
113
+ .keydown([ctrl, 'shift', 'z'], () => {
114
+ services?.editorService.redo();
115
+ })
116
+ .keydown('up', () => {
117
+ services?.editorService.move(0, -1);
118
+ })
119
+ .keydown('down', () => {
120
+ services?.editorService.move(0, 1);
121
+ })
122
+ .keydown('left', () => {
123
+ services?.editorService.move(-1, 0);
124
+ })
125
+ .keydown('right', () => {
126
+ services?.editorService.move(1, 0);
127
+ })
128
+ .keydown([ctrl, 'up'], () => {
129
+ services?.editorService.move(0, -10);
130
+ })
131
+ .keydown([ctrl, 'down'], () => {
132
+ services?.editorService.move(0, 10);
133
+ })
134
+ .keydown([ctrl, 'left'], () => {
135
+ services?.editorService.move(-10, 0);
136
+ })
137
+ .keydown([ctrl, 'right'], () => {
138
+ services?.editorService.move(10, 0);
139
+ })
140
+ .keydown('tab', () => {
141
+ services?.editorService.selectNextNode();
142
+ })
143
+ .keydown([ctrl, 'tab'], () => {
144
+ services?.editorService.selectNextPage();
145
+ })
146
+ .keydown([ctrl, '='], () => {
147
+ services?.uiService.zoom(0.1);
148
+ })
149
+ .keydown([ctrl, 'numpadplus'], () => {
150
+ services?.uiService.zoom(0.1);
151
+ })
152
+ .keydown([ctrl, '-'], () => {
153
+ services?.uiService.zoom(-0.1);
154
+ })
155
+ .keydown([ctrl, 'numpad-'], () => {
156
+ services?.uiService.zoom(-0.1);
157
+ });
158
+ });
159
+
160
+ onUnmounted(() => {
161
+ workspace.value?.removeEventListener('mouseenter', mouseenterHandler);
162
+ workspace.value?.removeEventListener('mouseleave', mouseleaveHandler);
163
+ keycon.destroy();
164
+ });
73
165
 
74
- sortNodeHandler(ev: SortEventData) {
75
- services?.editorService.sort(ev.src, ev.dist);
76
- },
166
+ return {
167
+ workspace,
77
168
 
78
- highlightHandler(el: HTMLElement) {
79
- services?.editorService.highlight(el.id);
80
- },
169
+ page: computed(() => services?.editorService.get<MPage>('page')),
81
170
  };
82
171
  },
83
172
  });
@@ -24,6 +24,46 @@ const methodName = (prefix: string, name: string) => `${prefix}${name[0].toUpper
24
24
 
25
25
  const isError = (error: any): boolean => Object.prototype.toString.call(error) === '[object Error]';
26
26
 
27
+ const doAction = async (
28
+ args: any[],
29
+ scope: any,
30
+ sourceMethod: any,
31
+ beforeMethodName: string,
32
+ afterMethodName: string,
33
+ fn: (args: any[], next?: Function | undefined) => Promise<void>,
34
+ ) => {
35
+ try {
36
+ let beforeArgs = args;
37
+
38
+ for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
39
+ let beforeReturnValue = (await beforeMethod(...beforeArgs)) || [];
40
+
41
+ if (isError(beforeReturnValue)) throw beforeReturnValue;
42
+
43
+ if (!Array.isArray(beforeReturnValue)) {
44
+ beforeReturnValue = [beforeReturnValue];
45
+ }
46
+
47
+ beforeArgs = beforeArgs.map((v: any, index: number) => {
48
+ if (typeof beforeReturnValue[index] === 'undefined') return v;
49
+ return beforeReturnValue[index];
50
+ });
51
+ }
52
+
53
+ let returnValue: any = await fn(beforeArgs, sourceMethod.bind(scope));
54
+
55
+ for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
56
+ returnValue = await afterMethod(returnValue, ...beforeArgs);
57
+
58
+ if (isError(returnValue)) throw returnValue;
59
+ }
60
+
61
+ return returnValue;
62
+ } catch (error) {
63
+ throw error;
64
+ }
65
+ };
66
+
27
67
  /**
28
68
  * 提供两种方式对Class进行扩展
29
69
  * 方法1:
@@ -74,8 +114,10 @@ const isError = (error: any): boolean => Object.prototype.toString.call(error) =
74
114
  export default class extends EventEmitter {
75
115
  private pluginOptionsList: Record<string, Function[]> = {};
76
116
  private middleware: Record<string, Function[]> = {};
117
+ private taskList: (() => Promise<void>)[] = [];
118
+ private doingTask = false;
77
119
 
78
- constructor(methods: string[]) {
120
+ constructor(methods: string[] = [], serialMethods: string[] = []) {
79
121
  super();
80
122
 
81
123
  methods.forEach((propertyName: string) => {
@@ -93,32 +135,28 @@ export default class extends EventEmitter {
93
135
  const fn = compose(this.middleware[propertyName]);
94
136
  Object.defineProperty(scope, propertyName, {
95
137
  value: async (...args: any[]) => {
96
- let beforeArgs = args;
97
-
98
- for (const beforeMethod of this.pluginOptionsList[beforeMethodName]) {
99
- let beforeReturnValue = (await beforeMethod(...beforeArgs)) || [];
100
-
101
- if (isError(beforeReturnValue)) throw beforeReturnValue;
102
-
103
- if (!Array.isArray(beforeReturnValue)) {
104
- beforeReturnValue = [beforeReturnValue];
105
- }
106
-
107
- beforeArgs = beforeArgs.map((v: any, index: number) => {
108
- if (typeof beforeReturnValue[index] === 'undefined') return v;
109
- return beforeReturnValue[index];
110
- });
138
+ if (!serialMethods.includes(propertyName)) {
139
+ return doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
111
140
  }
112
141
 
113
- let returnValue = await fn(beforeArgs, sourceMethod.bind(scope));
114
-
115
- for (const afterMethod of this.pluginOptionsList[afterMethodName]) {
116
- returnValue = await afterMethod(...beforeArgs, returnValue);
142
+ // 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2;
143
+ // 这里保证函数调用严格按顺序执行;
144
+ const promise = new Promise<any>((resolve, reject) => {
145
+ this.taskList.push(async () => {
146
+ try {
147
+ const value = await doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
148
+ resolve(value);
149
+ } catch (e) {
150
+ reject(e);
151
+ }
152
+ });
153
+ });
117
154
 
118
- if (isError(returnValue)) throw returnValue;
155
+ if (!this.doingTask) {
156
+ this.doTask();
119
157
  }
120
158
 
121
- return returnValue;
159
+ return promise;
122
160
  },
123
161
  });
124
162
  });
@@ -135,4 +173,14 @@ export default class extends EventEmitter {
135
173
  if (typeof method === 'function') this.pluginOptionsList[methodName].push(method);
136
174
  });
137
175
  }
176
+
177
+ private async doTask() {
178
+ this.doingTask = true;
179
+ let task = this.taskList.shift();
180
+ while (task) {
181
+ await task();
182
+ task = this.taskList.shift();
183
+ }
184
+ this.doingTask = false;
185
+ }
138
186
  }
@@ -22,8 +22,8 @@ import serialize from 'serialize-javascript';
22
22
 
23
23
  import type { Id, MApp, MComponent, MContainer, MNode, MPage } from '@tmagic/schema';
24
24
  import { NodeType } from '@tmagic/schema';
25
- import type StageCore from '@tmagic/stage';
26
- import { getNodePath, isPop } from '@tmagic/utils';
25
+ import StageCore from '@tmagic/stage';
26
+ import { getNodePath, isNumber, isPage, isPop } from '@tmagic/utils';
27
27
 
28
28
  import historyService, { StepValue } from '@editor/services/history';
29
29
  import propsService from '@editor/services/props';
@@ -54,24 +54,30 @@ class Editor extends BaseService {
54
54
  stage: null,
55
55
  highlightNode: null,
56
56
  modifiedNodeIds: new Map(),
57
+ pageLength: 0,
57
58
  });
58
59
 
59
60
  constructor() {
60
- super([
61
- 'getLayout',
62
- 'select',
63
- 'add',
64
- 'remove',
65
- 'update',
66
- 'sort',
67
- 'copy',
68
- 'paste',
69
- 'alignCenter',
70
- 'moveLayer',
71
- 'undo',
72
- 'redo',
73
- 'highlight',
74
- ]);
61
+ super(
62
+ [
63
+ 'getLayout',
64
+ 'select',
65
+ 'add',
66
+ 'remove',
67
+ 'update',
68
+ 'sort',
69
+ 'copy',
70
+ 'paste',
71
+ 'alignCenter',
72
+ 'moveLayer',
73
+ 'move',
74
+ 'undo',
75
+ 'redo',
76
+ 'highlight',
77
+ ],
78
+ // 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用
79
+ ['select', 'update', 'moveLayer'],
80
+ );
75
81
  }
76
82
 
77
83
  /**
@@ -85,6 +91,7 @@ class Editor extends BaseService {
85
91
  log('store set ', name, ' ', value);
86
92
 
87
93
  if (name === 'root') {
94
+ this.state.pageLength = (value as unknown as MApp)?.items?.length || 0;
88
95
  this.emit('root-change', value);
89
96
  }
90
97
  }
@@ -104,7 +111,7 @@ class Editor extends BaseService {
104
111
  * @returns {EditorNodeInfo}
105
112
  */
106
113
  public getNodeInfo(id: Id): EditorNodeInfo {
107
- const root = this.get<MApp | null>('root');
114
+ const root = toRaw(this.get<MApp | null>('root'));
108
115
  if (!root) return {};
109
116
 
110
117
  if (id === root.id) {
@@ -155,13 +162,15 @@ class Editor extends BaseService {
155
162
  /**
156
163
  * 只有容器拥有布局
157
164
  */
158
- public async getLayout(node: MNode): Promise<Layout> {
159
- if (node.layout) {
160
- return node.layout;
165
+ public async getLayout(parent: MNode, node?: MNode): Promise<Layout> {
166
+ if (node && typeof node !== 'function' && isFixed(node)) return Layout.FIXED;
167
+
168
+ if (parent.layout) {
169
+ return parent.layout;
161
170
  }
162
171
 
163
172
  // 如果该节点没有设置position,则认为是流式布局,例如获取root的布局时
164
- if (!node.style?.position) {
173
+ if (!parent.style?.position) {
165
174
  return Layout.RELATIVE;
166
175
  }
167
176
 
@@ -184,9 +193,45 @@ class Editor extends BaseService {
184
193
  } else {
185
194
  historyService.empty();
186
195
  }
196
+
197
+ this.emit('select', node);
198
+
187
199
  return node!;
188
200
  }
189
201
 
202
+ public async selectNextNode(): Promise<MNode> | never {
203
+ const node = toRaw(this.get('node'));
204
+
205
+ if (!node || isPage(node) || node.type === NodeType.ROOT) return node;
206
+
207
+ const parent = toRaw(this.getParentById(node.id));
208
+
209
+ if (!parent) return node;
210
+
211
+ const index = getNodeIndex(node, parent);
212
+
213
+ const nextNode = parent.items[index + 1] || parent.items[0];
214
+
215
+ await this.select(nextNode);
216
+ this.get<StageCore>('stage')?.select(nextNode.id);
217
+
218
+ return nextNode;
219
+ }
220
+
221
+ public async selectNextPage(): Promise<MNode> | never {
222
+ const root = toRaw(this.get<MApp>('root'));
223
+ const page = toRaw(this.get('page'));
224
+
225
+ const index = getNodeIndex(page, root);
226
+
227
+ const nextPage = root.items[index + 1] || root.items[0];
228
+
229
+ await this.select(nextPage);
230
+ this.get<StageCore>('stage')?.select(nextPage.id);
231
+
232
+ return nextPage;
233
+ }
234
+
190
235
  /**
191
236
  * 高亮指定节点
192
237
  * @param config 指定节点配置或者ID
@@ -194,8 +239,8 @@ class Editor extends BaseService {
194
239
  */
195
240
  public highlight(config: MNode | Id): void {
196
241
  const { node } = this.selectedConfigExceptionHandler(config);
197
- const currentHighligtNode = this.get('highlightNode');
198
- if (currentHighligtNode === node) return;
242
+ const currentHighlightNode = this.get('highlightNode');
243
+ if (currentHighlightNode === node) return;
199
244
  this.set('highlightNode', node);
200
245
  }
201
246
 
@@ -205,12 +250,14 @@ class Editor extends BaseService {
205
250
  * @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
206
251
  * @returns 添加后的节点
207
252
  */
208
- public async add({ type, ...config }: AddMNode, parent?: MContainer | null): Promise<MNode> {
253
+ public async add(addNode: AddMNode, parent?: MContainer | null): Promise<MNode> {
254
+ const { type, ...config } = addNode;
209
255
  const curNode = this.get<MContainer>('node');
210
256
 
211
257
  let parentNode: MNode | undefined;
258
+ const isPage = type === NodeType.PAGE;
212
259
 
213
- if (type === NodeType.PAGE) {
260
+ if (isPage) {
214
261
  parentNode = this.get<MApp>('root');
215
262
  // 由于支持中间件扩展,在parent参数为undefined时,parent会变成next函数
216
263
  } else if (parent && typeof parent !== 'function') {
@@ -223,8 +270,13 @@ class Editor extends BaseService {
223
270
 
224
271
  if (!parentNode) throw new Error('未找到父元素');
225
272
 
226
- const layout = await this.getLayout(parentNode);
227
- const newNode = initPosition({ ...toRaw(await propsService.getPropsValue(type)), ...config }, layout);
273
+ const layout = await this.getLayout(toRaw(parentNode), addNode as MNode);
274
+ const newNode = initPosition(
275
+ { ...toRaw(await propsService.getPropsValue(type, config)) },
276
+ layout,
277
+ parentNode,
278
+ this.get<StageCore>('stage'),
279
+ );
228
280
 
229
281
  if ((parentNode?.type === NodeType.ROOT || curNode.type === NodeType.ROOT) && newNode.type !== NodeType.PAGE) {
230
282
  throw new Error('app下不能添加组件');
@@ -232,15 +284,25 @@ class Editor extends BaseService {
232
284
 
233
285
  parentNode?.items?.push(newNode);
234
286
 
235
- await this.get<StageCore>('stage')?.add({ config: cloneDeep(newNode), root: cloneDeep(this.get('root')) });
287
+ const stage = this.get<StageCore | null>('stage');
288
+
289
+ await stage?.add({ config: cloneDeep(newNode), root: cloneDeep(this.get('root')) });
236
290
 
237
291
  await this.select(newNode);
238
292
 
239
293
  this.addModifiedNodeId(newNode.id);
240
- if (type !== NodeType.PAGE) {
294
+ if (!isPage) {
241
295
  this.pushHistoryState();
242
296
  }
243
297
 
298
+ stage?.select(newNode.id);
299
+
300
+ if (isPage) {
301
+ this.state.pageLength += 1;
302
+ }
303
+
304
+ this.emit('add', newNode);
305
+
244
306
  return newNode;
245
307
  }
246
308
 
@@ -265,17 +327,38 @@ class Editor extends BaseService {
265
327
  if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
266
328
 
267
329
  parent.items?.splice(index, 1);
268
- this.get<StageCore>('stage')?.remove({ id: node.id, root: this.get('root') });
330
+ const stage = this.get<StageCore | null>('stage');
331
+ stage?.remove({ id: node.id, root: this.get('root') });
269
332
 
270
333
  if (node.type === NodeType.PAGE) {
271
- await this.select(root.items[0] || root);
334
+ this.state.pageLength -= 1;
335
+
336
+ if (root.items[0]) {
337
+ await this.select(root.items[0]);
338
+ stage?.select(root.items[0].id);
339
+ } else {
340
+ this.set('node', null);
341
+ this.set('parent', null);
342
+ this.set('page', null);
343
+ this.set('stage', null);
344
+ this.set('highlightNode', null);
345
+ this.resetModifiedNodeId();
346
+ historyService.reset();
347
+
348
+ this.emit('remove', node);
349
+
350
+ return node;
351
+ }
272
352
  } else {
273
353
  await this.select(parent);
354
+ stage?.select(parent.id);
274
355
  }
275
356
 
276
357
  this.addModifiedNodeId(parent.id);
277
358
  this.pushHistoryState();
278
359
 
360
+ this.emit('remove', node);
361
+
279
362
  return node;
280
363
  }
281
364
 
@@ -324,11 +407,11 @@ class Editor extends BaseService {
324
407
 
325
408
  parentNodeItems[index] = newConfig;
326
409
 
327
- if (newConfig.id === this.get('node').id) {
410
+ if (`${newConfig.id}` === `${this.get('node').id}`) {
328
411
  this.set('node', newConfig);
329
412
  }
330
413
 
331
- this.get<StageCore>('stage')?.update({ config: cloneDeep(newConfig), root: this.get('root') });
414
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(newConfig), root: this.get('root') });
332
415
 
333
416
  if (newConfig.type === NodeType.PAGE) {
334
417
  this.set('page', newConfig);
@@ -337,6 +420,8 @@ class Editor extends BaseService {
337
420
  this.addModifiedNodeId(newConfig.id);
338
421
  this.pushHistoryState();
339
422
 
423
+ this.emit('update', newConfig);
424
+
340
425
  return newConfig;
341
426
  }
342
427
 
@@ -359,7 +444,7 @@ class Editor extends BaseService {
359
444
  await this.update(parent);
360
445
  await this.select(node);
361
446
 
362
- this.get<StageCore>('stage')?.update({ config: cloneDeep(node), root: this.get('root') });
447
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(node), root: this.get('root') });
363
448
 
364
449
  this.addModifiedNodeId(parent.id);
365
450
  this.pushHistoryState();
@@ -414,7 +499,7 @@ class Editor extends BaseService {
414
499
  public async alignCenter(config: MNode): Promise<MNode | void> {
415
500
  const parent = this.get<MContainer>('parent');
416
501
  const node = this.get<MNode>('node');
417
- const layout = await this.getLayout(parent);
502
+ const layout = await this.getLayout(toRaw(parent), toRaw(node));
418
503
  if (layout === Layout.RELATIVE) {
419
504
  return;
420
505
  }
@@ -424,7 +509,7 @@ class Editor extends BaseService {
424
509
  }
425
510
 
426
511
  await this.update(node);
427
- this.get<StageCore>('stage')?.update({ config: cloneDeep(toRaw(node)), root: this.get('root') });
512
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(toRaw(node)), root: this.get('root') });
428
513
  this.addModifiedNodeId(config.id);
429
514
  this.pushHistoryState();
430
515
 
@@ -449,7 +534,7 @@ class Editor extends BaseService {
449
534
  brothers.splice(index + parseInt(`${offset}`, 10), 0, brothers.splice(index, 1)[0]);
450
535
  }
451
536
 
452
- this.get<StageCore>('stage')?.update({ config: cloneDeep(toRaw(parent)), root: this.get('root') });
537
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(toRaw(parent)), root: this.get('root') });
453
538
  }
454
539
 
455
540
  /**
@@ -472,6 +557,26 @@ class Editor extends BaseService {
472
557
  return value;
473
558
  }
474
559
 
560
+ public async move(left: number, top: number) {
561
+ const node = toRaw(this.get('node'));
562
+ if (!node || isPage(node)) return;
563
+
564
+ const { style, id } = node;
565
+ if (!style || style.position !== 'absolute') return;
566
+
567
+ if (top && !isNumber(style.top)) return;
568
+ if (left && !isNumber(style.left)) return;
569
+
570
+ this.update({
571
+ id,
572
+ style: {
573
+ ...style,
574
+ left: Number(style.left) + left,
575
+ top: Number(style.top) + top,
576
+ },
577
+ });
578
+ }
579
+
475
580
  public destroy() {
476
581
  this.removeAllListeners();
477
582
  this.set('root', null);
@@ -508,7 +613,11 @@ class Editor extends BaseService {
508
613
  this.isHistoryStateChange = true;
509
614
  await this.update(value.data);
510
615
  this.set('modifiedNodeIds', value.modifiedNodeIds);
511
- setTimeout(() => value.nodeId && this.select(value.nodeId), 0);
616
+ setTimeout(async () => {
617
+ if (!value.nodeId) return;
618
+ await this.select(value.nodeId);
619
+ this.get<StageCore | null>('stage')?.select(value.nodeId);
620
+ }, 0);
512
621
  }
513
622
 
514
623
  private async toggleFixedPosition(dist: MNode, src: MNode, root: MApp) {
@@ -51,6 +51,13 @@ class History extends BaseService {
51
51
  this.on('change', this.setCanUndoRedo);
52
52
  }
53
53
 
54
+ public reset() {
55
+ this.state.pageSteps = {};
56
+ this.state.pageId = undefined;
57
+ this.state.canRedo = false;
58
+ this.state.canUndo = false;
59
+ }
60
+
54
61
  public changePage(page: MPage): void {
55
62
  if (!page) return;
56
63
 
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import { reactive } from 'vue';
20
- import { cloneDeep } from 'lodash-es';
20
+ import { cloneDeep, mergeWith } from 'lodash-es';
21
21
 
22
22
  import type { FormConfig } from '@tmagic/form';
23
23
  import type { MComponent, MNode } from '@tmagic/schema';
@@ -87,7 +87,7 @@ class Props extends BaseService {
87
87
  * @param type 组件类型
88
88
  * @returns 组件初始值
89
89
  */
90
- public async getPropsValue(type: string) {
90
+ public async getPropsValue(type: string, defaultValue = {}) {
91
91
  if (type === 'area') {
92
92
  const value = (await this.getPropsValue('button')) as MComponent;
93
93
  value.className = 'action-area';
@@ -100,7 +100,7 @@ class Props extends BaseService {
100
100
 
101
101
  return cloneDeep({
102
102
  ...getDefaultPropsValue(type),
103
- ...(this.state.propsValueMap[type] || {}),
103
+ ...mergeWith(this.state.propsValueMap[type] || {}, defaultValue),
104
104
  });
105
105
  }
106
106
  }