@tmagic/editor 1.4.6 → 1.4.8

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 (158) hide show
  1. package/dist/style.css +1 -1
  2. package/dist/tmagic-editor.js +636 -589
  3. package/dist/tmagic-editor.umd.cjs +634 -586
  4. package/package.json +15 -11
  5. package/types/editorProps.d.ts +0 -7
  6. package/types/layouts/PropsPanel.vue.d.ts +9 -9
  7. package/types/layouts/sidebar/layer/use-click.d.ts +1 -1
  8. package/types/services/codeBlock.d.ts +2 -1
  9. package/types/services/dataSource.d.ts +2 -1
  10. package/types/services/dep.d.ts +12 -2
  11. package/types/services/editor.d.ts +22 -4
  12. package/types/services/props.d.ts +7 -6
  13. package/types/type.d.ts +6 -0
  14. package/types/utils/editor.d.ts +1 -0
  15. package/types/utils/idle-task.d.ts +14 -0
  16. package/types/utils/operator.d.ts +2 -2
  17. package/src/Editor.vue +0 -211
  18. package/src/components/CodeBlockEditor.vue +0 -268
  19. package/src/components/CodeParams.vue +0 -60
  20. package/src/components/ContentMenu.vue +0 -193
  21. package/src/components/FloatingBox.vue +0 -178
  22. package/src/components/Icon.vue +0 -27
  23. package/src/components/Resizer.vue +0 -23
  24. package/src/components/ScrollBar.vue +0 -151
  25. package/src/components/ScrollViewer.vue +0 -128
  26. package/src/components/SearchInput.vue +0 -37
  27. package/src/components/SplitView.vue +0 -186
  28. package/src/components/ToolButton.vue +0 -138
  29. package/src/components/Tree.vue +0 -71
  30. package/src/components/TreeNode.vue +0 -148
  31. package/src/editorProps.ts +0 -124
  32. package/src/fields/Code.vue +0 -50
  33. package/src/fields/CodeLink.vue +0 -80
  34. package/src/fields/CodeSelect.vue +0 -132
  35. package/src/fields/CodeSelectCol.vue +0 -139
  36. package/src/fields/DataSourceFieldSelect.vue +0 -149
  37. package/src/fields/DataSourceFields.vue +0 -323
  38. package/src/fields/DataSourceInput.vue +0 -325
  39. package/src/fields/DataSourceMethodSelect.vue +0 -153
  40. package/src/fields/DataSourceMethods.vue +0 -104
  41. package/src/fields/DataSourceMocks.vue +0 -255
  42. package/src/fields/DataSourceSelect.vue +0 -98
  43. package/src/fields/EventSelect.vue +0 -301
  44. package/src/fields/KeyValue.vue +0 -137
  45. package/src/fields/PageFragmentSelect.vue +0 -63
  46. package/src/fields/UISelect.vue +0 -135
  47. package/src/hooks/index.ts +0 -24
  48. package/src/hooks/use-code-block-edit.ts +0 -83
  49. package/src/hooks/use-data-source-edit.ts +0 -46
  50. package/src/hooks/use-data-source-method.ts +0 -100
  51. package/src/hooks/use-editor-content-height.ts +0 -26
  52. package/src/hooks/use-filter.ts +0 -55
  53. package/src/hooks/use-float-box.ts +0 -76
  54. package/src/hooks/use-getso.ts +0 -35
  55. package/src/hooks/use-next-float-box-position.ts +0 -29
  56. package/src/hooks/use-node-status.ts +0 -48
  57. package/src/hooks/use-stage.ts +0 -129
  58. package/src/hooks/use-window-rect.ts +0 -20
  59. package/src/icons/AppManageIcon.vue +0 -15
  60. package/src/icons/CenterIcon.vue +0 -13
  61. package/src/icons/CodeIcon.vue +0 -28
  62. package/src/icons/FolderMinusIcon.vue +0 -22
  63. package/src/icons/PinIcon.vue +0 -25
  64. package/src/icons/PinnedIcon.vue +0 -25
  65. package/src/index.ts +0 -118
  66. package/src/initService.ts +0 -447
  67. package/src/layouts/AddPageBox.vue +0 -55
  68. package/src/layouts/CodeEditor.vue +0 -226
  69. package/src/layouts/Framework.vue +0 -163
  70. package/src/layouts/NavMenu.vue +0 -200
  71. package/src/layouts/PropsPanel.vue +0 -130
  72. package/src/layouts/page-bar/AddButton.vue +0 -48
  73. package/src/layouts/page-bar/PageBar.vue +0 -159
  74. package/src/layouts/page-bar/PageBarScrollContainer.vue +0 -156
  75. package/src/layouts/page-bar/SwitchTypeButton.vue +0 -45
  76. package/src/layouts/sidebar/ComponentListPanel.vue +0 -134
  77. package/src/layouts/sidebar/Sidebar.vue +0 -278
  78. package/src/layouts/sidebar/code-block/CodeBlockList.vue +0 -142
  79. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +0 -70
  80. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +0 -84
  81. package/src/layouts/sidebar/data-source/DataSourceList.vue +0 -160
  82. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +0 -106
  83. package/src/layouts/sidebar/layer/LayerMenu.vue +0 -128
  84. package/src/layouts/sidebar/layer/LayerNodeTool.vue +0 -32
  85. package/src/layouts/sidebar/layer/LayerPanel.vue +0 -119
  86. package/src/layouts/sidebar/layer/use-click.ts +0 -110
  87. package/src/layouts/sidebar/layer/use-drag.ts +0 -161
  88. package/src/layouts/sidebar/layer/use-keybinding.ts +0 -54
  89. package/src/layouts/sidebar/layer/use-node-status.ts +0 -114
  90. package/src/layouts/workspace/Breadcrumb.vue +0 -35
  91. package/src/layouts/workspace/Workspace.vue +0 -46
  92. package/src/layouts/workspace/viewer/NodeListMenu.vue +0 -115
  93. package/src/layouts/workspace/viewer/Stage.vue +0 -246
  94. package/src/layouts/workspace/viewer/StageOverlay.vue +0 -68
  95. package/src/layouts/workspace/viewer/ViewerMenu.vue +0 -145
  96. package/src/services/BaseService.ts +0 -230
  97. package/src/services/codeBlock.ts +0 -324
  98. package/src/services/componentList.ts +0 -58
  99. package/src/services/dataSource.ts +0 -209
  100. package/src/services/dep.ts +0 -80
  101. package/src/services/editor.ts +0 -1087
  102. package/src/services/events.ts +0 -93
  103. package/src/services/history.ts +0 -126
  104. package/src/services/keybinding.ts +0 -220
  105. package/src/services/props.ts +0 -265
  106. package/src/services/stageOverlay.ts +0 -212
  107. package/src/services/storage.ts +0 -155
  108. package/src/services/ui.ts +0 -156
  109. package/src/shims-vue.d.ts +0 -6
  110. package/src/theme/breadcrumb.scss +0 -6
  111. package/src/theme/code-block.scss +0 -6
  112. package/src/theme/code-editor.scss +0 -38
  113. package/src/theme/common/var.scss +0 -14
  114. package/src/theme/component-list-panel.scss +0 -95
  115. package/src/theme/content-menu.scss +0 -76
  116. package/src/theme/data-source-field.scss +0 -12
  117. package/src/theme/data-source-fields.scss +0 -13
  118. package/src/theme/data-source-input.scss +0 -18
  119. package/src/theme/data-source-methods.scss +0 -13
  120. package/src/theme/data-source.scss +0 -28
  121. package/src/theme/event.scss +0 -37
  122. package/src/theme/floating-box.scss +0 -32
  123. package/src/theme/framework.scss +0 -69
  124. package/src/theme/icon.scss +0 -12
  125. package/src/theme/index.scss +0 -10
  126. package/src/theme/key-value.scss +0 -20
  127. package/src/theme/layer-panel.scss +0 -26
  128. package/src/theme/layout.scss +0 -9
  129. package/src/theme/nav-menu.scss +0 -78
  130. package/src/theme/page-bar.scss +0 -89
  131. package/src/theme/page-fragment-select.scss +0 -14
  132. package/src/theme/props-panel.scss +0 -55
  133. package/src/theme/resizer.scss +0 -66
  134. package/src/theme/ruler.scss +0 -38
  135. package/src/theme/search-input.scss +0 -14
  136. package/src/theme/sidebar.scss +0 -79
  137. package/src/theme/stage.scss +0 -76
  138. package/src/theme/theme.scss +0 -27
  139. package/src/theme/tree.scss +0 -89
  140. package/src/theme/workspace.scss +0 -9
  141. package/src/type.ts +0 -746
  142. package/src/utils/compose.ts +0 -52
  143. package/src/utils/config.ts +0 -29
  144. package/src/utils/content-menu.ts +0 -95
  145. package/src/utils/data-source/formConfigs/base.ts +0 -30
  146. package/src/utils/data-source/formConfigs/http.ts +0 -60
  147. package/src/utils/data-source/index.ts +0 -236
  148. package/src/utils/editor.ts +0 -294
  149. package/src/utils/index.ts +0 -24
  150. package/src/utils/keybinding-config.ts +0 -125
  151. package/src/utils/logger.ts +0 -47
  152. package/src/utils/monaco-editor.ts +0 -8
  153. package/src/utils/operator.ts +0 -106
  154. package/src/utils/props.ts +0 -504
  155. package/src/utils/scroll-viewer.ts +0 -165
  156. package/src/utils/tree.ts +0 -15
  157. package/src/utils/undo-redo.ts +0 -76
  158. package/tsconfig.build.json +0 -16
@@ -1,1087 +0,0 @@
1
- /*
2
- * Tencent is pleased to support the open source community by making TMagicEditor available.
3
- *
4
- * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
5
- *
6
- * Licensed under the Apache License, Version 2.0 (the "License");
7
- * you may not use this file except in compliance with the License.
8
- * You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing, software
13
- * distributed under the License is distributed on an "AS IS" BASIS,
14
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- * See the License for the specific language governing permissions and
16
- * limitations under the License.
17
- */
18
-
19
- import { reactive, toRaw } from 'vue';
20
- import { cloneDeep, get, isObject, mergeWith, uniq } from 'lodash-es';
21
- import { Writable } from 'type-fest';
22
-
23
- import { DepTargetType } from '@tmagic/dep';
24
- import type { Id, MApp, MComponent, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema';
25
- import { NodeType } from '@tmagic/schema';
26
- import { calcValueByFontsize, getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';
27
-
28
- import BaseService from '@editor/services//BaseService';
29
- import propsService from '@editor/services//props';
30
- import depService from '@editor/services/dep';
31
- import historyService from '@editor/services/history';
32
- import storageService, { Protocol } from '@editor/services/storage';
33
- import type {
34
- AddMNode,
35
- AsyncHookPlugin,
36
- EditorNodeInfo,
37
- PastePosition,
38
- StepValue,
39
- StoreState,
40
- StoreStateKey,
41
- } from '@editor/type';
42
- import { LayerOffset, Layout } from '@editor/type';
43
- import {
44
- change2Fixed,
45
- COPY_STORAGE_KEY,
46
- Fixed2Other,
47
- fixNodePosition,
48
- getInitPositionStyle,
49
- getNodeIndex,
50
- getPageFragmentList,
51
- getPageList,
52
- isFixed,
53
- setChildrenLayout,
54
- setLayout,
55
- } from '@editor/utils/editor';
56
- import { beforePaste, getAddParent } from '@editor/utils/operator';
57
-
58
- const canUsePluginMethods = {
59
- async: [
60
- 'getLayout',
61
- 'highlight',
62
- 'select',
63
- 'multiSelect',
64
- 'doAdd',
65
- 'add',
66
- 'doRemove',
67
- 'remove',
68
- 'doUpdate',
69
- 'update',
70
- 'sort',
71
- 'copy',
72
- 'paste',
73
- 'doPaste',
74
- 'doAlignCenter',
75
- 'alignCenter',
76
- 'moveLayer',
77
- 'moveToContainer',
78
- 'dragTo',
79
- 'undo',
80
- 'redo',
81
- 'move',
82
- ] as const,
83
- sync: [],
84
- };
85
-
86
- type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
87
-
88
- class Editor extends BaseService {
89
- public state: StoreState = reactive({
90
- root: null,
91
- page: null,
92
- parent: null,
93
- node: null,
94
- nodes: [],
95
- stage: null,
96
- stageLoading: true,
97
- highlightNode: null,
98
- modifiedNodeIds: new Map(),
99
- pageLength: 0,
100
- pageFragmentLength: 0,
101
- disabledMultiSelect: false,
102
- });
103
- private isHistoryStateChange = false;
104
-
105
- constructor() {
106
- super(
107
- canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })),
108
- // 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用
109
- ['select', 'update', 'moveLayer'],
110
- );
111
- }
112
-
113
- /**
114
- * 设置当前指点节点配置
115
- * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength
116
- * @param value MNode
117
- */
118
- public set<K extends StoreStateKey, T extends StoreState[K]>(name: K, value: T) {
119
- const preValue = this.state[name];
120
- this.state[name] = value;
121
-
122
- // set nodes时将node设置为nodes第一个元素
123
- if (name === 'nodes' && Array.isArray(value)) {
124
- this.set('node', value[0]);
125
- }
126
-
127
- if (name === 'root') {
128
- if (Array.isArray(value)) {
129
- throw new Error('root 不能为数组');
130
- }
131
-
132
- if (value && isObject(value)) {
133
- const app = value as MApp;
134
- this.state.pageLength = getPageList(app).length || 0;
135
- this.state.pageFragmentLength = getPageFragmentList(app).length || 0;
136
- this.state.stageLoading = this.state.pageLength !== 0;
137
- } else {
138
- this.state.pageLength = 0;
139
- this.state.pageFragmentLength = 0;
140
- this.state.stageLoading = false;
141
- }
142
-
143
- this.emit('root-change', value, preValue);
144
- }
145
- }
146
-
147
- /**
148
- * 获取当前指点节点配置
149
- * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'stage' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength'
150
- * @returns MNode
151
- */
152
- public get<K extends StoreStateKey>(name: K): StoreState[K] {
153
- return this.state[name];
154
- }
155
-
156
- /**
157
- * 根据id获取组件、组件的父组件以及组件所属的页面节点
158
- * @param {number | string} id 组件id
159
- * @param {boolean} raw 是否使用toRaw
160
- * @returns {EditorNodeInfo}
161
- */
162
- public getNodeInfo(id: Id, raw = true): EditorNodeInfo {
163
- let root = this.get('root');
164
- if (raw) {
165
- root = toRaw(root);
166
- }
167
-
168
- const info: EditorNodeInfo = {
169
- node: null,
170
- parent: null,
171
- page: null,
172
- };
173
-
174
- if (!root) return info;
175
-
176
- if (id === root.id) {
177
- info.node = root;
178
- return info;
179
- }
180
-
181
- const path = getNodePath(id, root.items);
182
-
183
- if (!path.length) return info;
184
-
185
- path.unshift(root);
186
-
187
- info.node = path[path.length - 1] as MComponent;
188
- info.parent = path[path.length - 2] as MContainer;
189
-
190
- path.forEach((item) => {
191
- if (isPage(item) || isPageFragment(item)) {
192
- info.page = item as MPage | MPageFragment;
193
- return;
194
- }
195
- });
196
-
197
- return info;
198
- }
199
-
200
- /**
201
- * 根据ID获取指点节点配置
202
- * @param id 组件ID
203
- * @param {boolean} raw 是否使用toRaw
204
- * @returns 组件节点配置
205
- */
206
- public getNodeById(id: Id, raw = true): MNode | null {
207
- const { node } = this.getNodeInfo(id, raw);
208
- return node;
209
- }
210
-
211
- /**
212
- * 根据ID获取指点节点的父节点配置
213
- * @param id 组件ID
214
- * @param {boolean} raw 是否使用toRaw
215
- * @returns 指点组件的父节点配置
216
- */
217
- public getParentById(id: Id, raw = true): MContainer | null {
218
- const { parent } = this.getNodeInfo(id, raw);
219
- return parent;
220
- }
221
-
222
- /**
223
- * 只有容器拥有布局
224
- */
225
- public async getLayout(parent: MNode, node?: MNode | null): Promise<Layout> {
226
- if (node && typeof node !== 'function' && isFixed(node)) return Layout.FIXED;
227
-
228
- if (parent.layout) {
229
- return parent.layout;
230
- }
231
-
232
- // 如果该节点没有设置position,则认为是流式布局,例如获取root的布局时
233
- if (!parent.style?.position) {
234
- return Layout.RELATIVE;
235
- }
236
-
237
- return Layout.ABSOLUTE;
238
- }
239
-
240
- /**
241
- * 选中指定节点(将指定节点设置成当前选中状态)
242
- * @param config 指定节点配置或者ID
243
- * @returns 当前选中的节点配置
244
- */
245
- public async select(config: MNode | Id): Promise<MNode> | never {
246
- const { node, page, parent } = this.selectedConfigExceptionHandler(config);
247
- this.set('nodes', node ? [node] : []);
248
- this.set('page', page);
249
- this.set('parent', parent);
250
-
251
- if (page) {
252
- historyService.changePage(toRaw(page));
253
- } else {
254
- historyService.resetState();
255
- }
256
-
257
- if (node?.id) {
258
- this.get('stage')
259
- ?.renderer.runtime?.getApp?.()
260
- ?.page?.emit(
261
- 'editor:select',
262
- {
263
- node,
264
- page,
265
- parent,
266
- },
267
- getNodePath(node.id, this.get('root')?.items),
268
- );
269
- }
270
-
271
- this.emit('select', node);
272
-
273
- return node!;
274
- }
275
-
276
- public async selectNextNode(): Promise<MNode | null> | never {
277
- const node = toRaw(this.get('node'));
278
-
279
- if (!node || isPage(node) || node.type === NodeType.ROOT) return node;
280
-
281
- const parent = toRaw(this.getParentById(node.id));
282
-
283
- if (!parent) return node;
284
-
285
- const index = getNodeIndex(node.id, parent);
286
-
287
- const nextNode = parent.items[index + 1] || parent.items[0];
288
-
289
- await this.select(nextNode);
290
- this.get('stage')?.select(nextNode.id);
291
-
292
- return nextNode;
293
- }
294
-
295
- public async selectNextPage(): Promise<MNode> | never {
296
- const root = toRaw(this.get('root'));
297
- const page = toRaw(this.get('page'));
298
-
299
- if (!page) throw new Error('page不能为空');
300
- if (!root) throw new Error('root不能为空');
301
-
302
- const index = getNodeIndex(page.id, root);
303
-
304
- const nextPage = root.items[index + 1] || root.items[0];
305
-
306
- await this.select(nextPage);
307
- this.get('stage')?.select(nextPage.id);
308
-
309
- return nextPage;
310
- }
311
-
312
- /**
313
- * 高亮指定节点
314
- * @param config 指定节点配置或者ID
315
- * @returns 当前高亮的节点配置
316
- */
317
- public highlight(config: MNode | Id): void {
318
- const { node } = this.selectedConfigExceptionHandler(config);
319
- const currentHighlightNode = this.get('highlightNode');
320
- if (currentHighlightNode === node) return;
321
- this.set('highlightNode', node);
322
- }
323
-
324
- /**
325
- * 多选
326
- * @param ids 指定节点ID
327
- * @returns 加入多选的节点配置
328
- */
329
- public multiSelect(ids: Id[]): void {
330
- const nodes: MNode[] = [];
331
- const idsUnique = uniq(ids);
332
- idsUnique.forEach((id) => {
333
- const { node } = this.getNodeInfo(id);
334
- if (!node) return;
335
- nodes.push(node);
336
- });
337
- this.set('nodes', nodes);
338
- }
339
-
340
- public selectRoot() {
341
- const root = this.get('root');
342
- if (!root) return;
343
-
344
- this.set('nodes', [root]);
345
- this.set('parent', null);
346
- this.set('page', null);
347
- this.set('stage', null);
348
- this.set('highlightNode', null);
349
- }
350
-
351
- public async doAdd(node: MNode, parent: MContainer): Promise<MNode> {
352
- const root = this.get('root');
353
-
354
- if (!root) throw new Error('root为空');
355
-
356
- const curNode = this.get('node');
357
- const stage = this.get('stage');
358
-
359
- if (!curNode) throw new Error('当前选中节点为空');
360
-
361
- if ((parent.type === NodeType.ROOT || curNode?.type === NodeType.ROOT) && !(isPage(node) || isPageFragment(node))) {
362
- throw new Error('app下不能添加组件');
363
- }
364
-
365
- if (parent.id !== curNode.id && !(isPage(node) || isPageFragment(node))) {
366
- const index = parent.items.indexOf(curNode);
367
- parent.items?.splice(index + 1, 0, node);
368
- } else {
369
- // 新增节点添加到配置中
370
- parent.items?.push(node);
371
- }
372
-
373
- const layout = await this.getLayout(toRaw(parent), node as MNode);
374
- node.style = getInitPositionStyle(node.style, layout);
375
-
376
- await stage?.add({
377
- config: cloneDeep(node),
378
- parent: cloneDeep(parent),
379
- parentId: parent.id,
380
- root: cloneDeep(root),
381
- });
382
-
383
- const newStyle = fixNodePosition(node, parent, stage);
384
-
385
- if (newStyle && (newStyle.top !== node.style.top || newStyle.left !== node.style.left)) {
386
- node.style = newStyle;
387
- await stage?.update({ config: cloneDeep(node), parentId: parent.id, root: cloneDeep(root) });
388
- }
389
-
390
- this.addModifiedNodeId(node.id);
391
-
392
- return node;
393
- }
394
-
395
- /**
396
- * 向指点容器添加组件节点
397
- * @param addConfig 将要添加的组件节点配置
398
- * @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
399
- * @returns 添加后的节点
400
- */
401
- public async add(addNode: AddMNode | MNode[], parent?: MContainer | null): Promise<MNode | MNode[]> {
402
- const stage = this.get('stage');
403
-
404
- // 新增多个组件只存在于粘贴多个组件,粘贴的是一个完整的config,所以不再需要getPropsValue
405
- const addNodes = [];
406
- if (!Array.isArray(addNode)) {
407
- const { type, inputEvent, ...config } = addNode;
408
-
409
- if (!type) throw new Error('组件类型不能为空');
410
-
411
- addNodes.push({ ...toRaw(await propsService.getPropsValue(type, config)) });
412
- } else {
413
- addNodes.push(...addNode);
414
- }
415
-
416
- const newNodes = await Promise.all(
417
- addNodes.map((node) => {
418
- const root = this.get('root');
419
- if ((isPage(node) || isPageFragment(node)) && root) {
420
- return this.doAdd(node, root);
421
- }
422
- const parentNode = parent && typeof parent !== 'function' ? parent : getAddParent(node);
423
- if (!parentNode) throw new Error('未找到父元素');
424
- return this.doAdd(node, parentNode);
425
- }),
426
- );
427
-
428
- if (newNodes.length > 1) {
429
- const newNodeIds = newNodes.map((node) => node.id);
430
- // 触发选中样式
431
- stage?.multiSelect(newNodeIds);
432
- await this.multiSelect(newNodeIds);
433
- } else {
434
- await this.select(newNodes[0]);
435
-
436
- if (isPage(newNodes[0])) {
437
- this.state.pageLength += 1;
438
- } else if (isPageFragment(newNodes[0])) {
439
- this.state.pageFragmentLength += 1;
440
- } else {
441
- // 新增页面,这个时候页面还有渲染出来,此时select会出错,在runtime-ready的时候回去select
442
- stage?.select(newNodes[0].id);
443
- }
444
- }
445
-
446
- if (!(isPage(newNodes[0]) || isPageFragment(newNodes[0]))) {
447
- this.pushHistoryState();
448
- }
449
-
450
- this.emit('add', newNodes);
451
-
452
- return Array.isArray(addNode) ? newNodes : newNodes[0];
453
- }
454
-
455
- public async doRemove(node: MNode): Promise<void> {
456
- const root = this.get('root');
457
- if (!root) throw new Error('root不能为空');
458
-
459
- const { parent, node: curNode } = this.getNodeInfo(node.id, false);
460
-
461
- if (!parent || !curNode) throw new Error('找不要删除的节点');
462
-
463
- const index = getNodeIndex(curNode.id, parent);
464
-
465
- if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
466
-
467
- parent.items?.splice(index, 1);
468
- const stage = this.get('stage');
469
- stage?.remove({ id: node.id, parentId: parent.id, root: cloneDeep(root) });
470
-
471
- const selectDefault = async (pages: MNode[]) => {
472
- if (pages[0]) {
473
- await this.select(pages[0]);
474
- stage?.select(pages[0].id);
475
- } else {
476
- this.selectRoot();
477
-
478
- historyService.resetPage();
479
- }
480
- };
481
-
482
- const rootItems = root.items || [];
483
-
484
- if (isPage(node)) {
485
- this.state.pageLength -= 1;
486
-
487
- await selectDefault(getPageList(root));
488
- } else if (isPageFragment(node)) {
489
- this.state.pageFragmentLength -= 1;
490
-
491
- await selectDefault(getPageFragmentList(root));
492
- } else {
493
- await this.select(parent);
494
- stage?.select(parent.id);
495
-
496
- this.addModifiedNodeId(parent.id);
497
- }
498
-
499
- if (!rootItems.length) {
500
- this.resetModifiedNodeId();
501
- historyService.reset();
502
- }
503
- }
504
-
505
- /**
506
- * 删除组件
507
- * @param {Object} node
508
- */
509
- public async remove(nodeOrNodeList: MNode | MNode[]): Promise<void> {
510
- const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList];
511
-
512
- await Promise.all(nodes.map((node) => this.doRemove(node)));
513
-
514
- if (!(isPage(nodes[0]) || isPageFragment(nodes[0]))) {
515
- // 更新历史记录
516
- this.pushHistoryState();
517
- }
518
-
519
- this.emit('remove', nodes);
520
- }
521
-
522
- public async doUpdate(config: MNode) {
523
- const root = this.get('root');
524
- if (!root) throw new Error('root为空');
525
-
526
- if (!config?.id) throw new Error('没有配置或者配置缺少id值');
527
-
528
- const info = this.getNodeInfo(config.id, false);
529
-
530
- if (!info.node) throw new Error(`获取不到id为${config.id}的节点`);
531
-
532
- const node = cloneDeep(toRaw(info.node));
533
-
534
- let newConfig = await this.toggleFixedPosition(toRaw(config), node, root);
535
-
536
- newConfig = mergeWith(cloneDeep(node), newConfig, (objValue, srcValue, key) => {
537
- if (typeof srcValue === 'undefined' && Object.hasOwn(newConfig, key)) {
538
- return '';
539
- }
540
-
541
- if (isObject(srcValue) && Array.isArray(objValue)) {
542
- // 原来的配置是数组,新的配置是对象,则直接使用新的值
543
- return srcValue;
544
- }
545
- if (Array.isArray(srcValue)) {
546
- return srcValue;
547
- }
548
- });
549
-
550
- if (!newConfig.type) throw new Error('配置缺少type值');
551
-
552
- if (newConfig.type === NodeType.ROOT) {
553
- this.set('root', newConfig as MApp);
554
- return newConfig;
555
- }
556
-
557
- const { parent } = info;
558
- if (!parent) throw new Error('获取不到父级节点');
559
-
560
- const parentNodeItems = parent.items;
561
- const index = getNodeIndex(newConfig.id, parent);
562
-
563
- if (!parentNodeItems || typeof index === 'undefined' || index === -1) throw new Error('更新的节点未找到');
564
-
565
- const newLayout = await this.getLayout(newConfig);
566
- const layout = await this.getLayout(node);
567
- if (Array.isArray(newConfig.items) && newLayout !== layout) {
568
- newConfig = setChildrenLayout(newConfig as MContainer, newLayout);
569
- }
570
-
571
- parentNodeItems[index] = newConfig;
572
-
573
- // 将update后的配置更新到nodes中
574
- const nodes = this.get('nodes');
575
- const targetIndex = nodes.findIndex((nodeItem: MNode) => `${nodeItem.id}` === `${newConfig.id}`);
576
- nodes.splice(targetIndex, 1, newConfig);
577
- this.set('nodes', [...nodes]);
578
-
579
- this.get('stage')?.update({
580
- config: cloneDeep(newConfig),
581
- parentId: parent.id,
582
- root: cloneDeep(root),
583
- });
584
-
585
- if (isPage(newConfig) || isPageFragment(newConfig)) {
586
- this.set('page', newConfig as MPage | MPageFragment);
587
- }
588
-
589
- this.addModifiedNodeId(newConfig.id);
590
-
591
- return newConfig;
592
- }
593
-
594
- /**
595
- * 更新节点
596
- * @param config 新的节点配置,配置中需要有id信息
597
- * @returns 更新后的节点配置
598
- */
599
- public async update(config: MNode | MNode[]): Promise<MNode | MNode[]> {
600
- const nodes = Array.isArray(config) ? config : [config];
601
-
602
- const newNodes = await Promise.all(nodes.map((node) => this.doUpdate(node)));
603
-
604
- if (newNodes[0]?.type !== NodeType.ROOT) {
605
- this.pushHistoryState();
606
- }
607
-
608
- this.emit('update', newNodes);
609
- return Array.isArray(config) ? newNodes : newNodes[0];
610
- }
611
-
612
- /**
613
- * 将id为id1的组件移动到id为id2的组件位置上,例如:[1,2,3,4] -> sort(1,3) -> [2,1,3,4]
614
- * @param id1 组件ID
615
- * @param id2 组件ID
616
- * @returns void
617
- */
618
- public async sort(id1: Id, id2: Id): Promise<void> {
619
- const root = this.get('root');
620
- if (!root) throw new Error('root为空');
621
-
622
- const node = this.get('node');
623
- if (!node) throw new Error('当前节点为空');
624
-
625
- const parent = cloneDeep(toRaw(this.get('parent')));
626
- if (!parent) throw new Error('父节点为空');
627
-
628
- const index2 = parent.items.findIndex((node: MNode) => `${node.id}` === `${id2}`);
629
- // 在 id1 的兄弟组件中若无 id2 则直接 return
630
- if (index2 < 0) return;
631
- const index1 = parent.items.findIndex((node: MNode) => `${node.id}` === `${id1}`);
632
-
633
- parent.items.splice(index2, 0, ...parent.items.splice(index1, 1));
634
-
635
- await this.update(parent);
636
- await this.select(node);
637
-
638
- this.get('stage')?.update({
639
- config: cloneDeep(node),
640
- parentId: parent.id,
641
- root: cloneDeep(root),
642
- });
643
-
644
- this.addModifiedNodeId(parent.id);
645
- this.pushHistoryState();
646
- }
647
-
648
- /**
649
- * 将组件节点配置存储到localStorage中
650
- * @param config 组件节点配置
651
- * @returns
652
- */
653
- public copy(config: MNode | MNode[]): void {
654
- storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], {
655
- protocol: Protocol.OBJECT,
656
- });
657
- }
658
-
659
- /**
660
- * 复制时会带上组件关联的依赖
661
- * @param config 组件节点配置
662
- * @returns
663
- */
664
- public copyWithRelated(config: MNode | MNode[]): void {
665
- const copyNodes: MNode[] = Array.isArray(config) ? config : [config];
666
- // 关联的组件也一并复制
667
- depService.clearByType(DepTargetType.RELATED_COMP_WHEN_COPY);
668
- depService.collect(copyNodes, true, DepTargetType.RELATED_COMP_WHEN_COPY);
669
- const customTarget = depService.getTarget(
670
- DepTargetType.RELATED_COMP_WHEN_COPY,
671
- DepTargetType.RELATED_COMP_WHEN_COPY,
672
- );
673
- if (customTarget) {
674
- Object.keys(customTarget.deps).forEach((nodeId: Id) => {
675
- const node = this.getNodeById(nodeId);
676
- if (!node) return;
677
- customTarget!.deps[nodeId].keys.forEach((key) => {
678
- const relateNodeId = get(node, key);
679
- const isExist = copyNodes.find((node) => node.id === relateNodeId);
680
- if (!isExist) {
681
- const relateNode = this.getNodeById(relateNodeId);
682
- if (relateNode) {
683
- copyNodes.push(relateNode);
684
- }
685
- }
686
- });
687
- });
688
- }
689
- storageService.setItem(COPY_STORAGE_KEY, copyNodes, {
690
- protocol: Protocol.OBJECT,
691
- });
692
- }
693
-
694
- /**
695
- * 从localStorage中获取节点,然后添加到当前容器中
696
- * @param position 粘贴的坐标
697
- * @returns 添加后的组件节点配置
698
- */
699
- public async paste(position: PastePosition = {}): Promise<MNode | MNode[] | void> {
700
- const config: MNode[] = storageService.getItem(COPY_STORAGE_KEY);
701
- if (!Array.isArray(config)) return;
702
-
703
- const node = this.get('node');
704
-
705
- let parent: MContainer | null = null;
706
- // 粘贴的组件为当前选中组件的副本时,则添加到当前选中组件的父组件中
707
- if (config.length === 1 && config[0].id === node?.id) {
708
- parent = this.get('parent');
709
- if (parent?.type === NodeType.ROOT) {
710
- parent = this.get('page');
711
- }
712
- }
713
- const pasteConfigs = await this.doPaste(config, position);
714
- propsService.replaceRelateId(config, pasteConfigs);
715
- return this.add(pasteConfigs, parent);
716
- }
717
-
718
- public async doPaste(config: MNode[], position: PastePosition = {}): Promise<MNode[]> {
719
- propsService.clearRelateId();
720
- const pasteConfigs = beforePaste(position, cloneDeep(config));
721
- return pasteConfigs;
722
- }
723
-
724
- public async doAlignCenter(config: MNode): Promise<MNode> {
725
- const parent = this.getParentById(config.id);
726
-
727
- if (!parent) throw new Error('找不到父节点');
728
-
729
- const node = cloneDeep(toRaw(config));
730
- const layout = await this.getLayout(parent, node);
731
- if (layout === Layout.RELATIVE) {
732
- return config;
733
- }
734
-
735
- if (!node.style) return config;
736
-
737
- const stage = this.get('stage');
738
- const doc = stage?.renderer.contentWindow?.document;
739
-
740
- if (doc) {
741
- const el = doc.getElementById(`${node.id}`);
742
- const parentEl = layout === Layout.FIXED ? doc.body : el?.offsetParent;
743
- if (parentEl && el) {
744
- node.style.left = calcValueByFontsize(doc, (parentEl.clientWidth - el.clientWidth) / 2);
745
- node.style.right = '';
746
- }
747
- } else if (parent.style && isNumber(parent.style?.width) && isNumber(node.style?.width)) {
748
- node.style.left = (parent.style.width - node.style.width) / 2;
749
- node.style.right = '';
750
- }
751
-
752
- return node;
753
- }
754
-
755
- /**
756
- * 将指点节点设置居中
757
- * @param config 组件节点配置
758
- * @returns 当前组件节点配置
759
- */
760
- public async alignCenter(config: MNode | MNode[]): Promise<MNode | MNode[]> {
761
- const nodes = Array.isArray(config) ? config : [config];
762
- const stage = this.get('stage');
763
-
764
- const newNodes = await Promise.all(nodes.map((node) => this.doAlignCenter(node)));
765
-
766
- const newNode = await this.update(newNodes);
767
-
768
- if (newNodes.length > 1) {
769
- await stage?.multiSelect(newNodes.map((node) => node.id));
770
- } else {
771
- await stage?.select(newNodes[0].id);
772
- }
773
-
774
- return newNode;
775
- }
776
-
777
- /**
778
- * 移动当前选中节点位置
779
- * @param offset 偏移量
780
- */
781
- public async moveLayer(offset: number | LayerOffset): Promise<void> {
782
- const root = this.get('root');
783
- if (!root) throw new Error('root为空');
784
-
785
- const parent = this.get('parent');
786
- if (!parent) throw new Error('父节点为空');
787
-
788
- const node = this.get('node');
789
- if (!node) throw new Error('当前节点为空');
790
-
791
- const brothers: MNode[] = parent.items || [];
792
- const index = brothers.findIndex((item) => `${item.id}` === `${node?.id}`);
793
-
794
- // 流式布局与绝对定位布局操作的相反的
795
- const layout = await this.getLayout(parent, node);
796
- const isRelative = layout === Layout.RELATIVE;
797
-
798
- let offsetIndex: number;
799
- if (offset === LayerOffset.TOP) {
800
- offsetIndex = isRelative ? 0 : brothers.length;
801
- } else if (offset === LayerOffset.BOTTOM) {
802
- offsetIndex = isRelative ? brothers.length : 0;
803
- } else {
804
- offsetIndex = index + (isRelative ? -offset : offset);
805
- }
806
-
807
- if ((offsetIndex > 0 && offsetIndex > brothers.length) || offsetIndex < 0) {
808
- return;
809
- }
810
- brothers.splice(index, 1);
811
- brothers.splice(offsetIndex, 0, node);
812
-
813
- const grandparent = this.getParentById(parent.id);
814
-
815
- this.get('stage')?.update({
816
- config: cloneDeep(toRaw(parent)),
817
- parentId: grandparent?.id,
818
- root: cloneDeep(root),
819
- });
820
-
821
- this.addModifiedNodeId(parent.id);
822
- this.pushHistoryState();
823
-
824
- this.emit('move-layer', offset);
825
- }
826
-
827
- /**
828
- * 移动到指定容器中
829
- * @param config 需要移动的节点
830
- * @param targetId 容器ID
831
- */
832
- public async moveToContainer(config: MNode, targetId: Id): Promise<MNode | undefined> {
833
- const root = this.get('root');
834
- const { node, parent } = this.getNodeInfo(config.id, false);
835
- const target = this.getNodeById(targetId, false) as MContainer;
836
-
837
- const stage = this.get('stage');
838
- if (root && node && parent && stage) {
839
- const index = getNodeIndex(node.id, parent);
840
- parent.items?.splice(index, 1);
841
-
842
- await stage.remove({ id: node.id, parentId: parent.id, root: cloneDeep(root) });
843
-
844
- const layout = await this.getLayout(target);
845
-
846
- const newConfig = mergeWith(cloneDeep(node), config, (objValue, srcValue) => {
847
- if (Array.isArray(srcValue)) {
848
- return srcValue;
849
- }
850
- });
851
- newConfig.style = getInitPositionStyle(newConfig.style, layout);
852
-
853
- target.items.push(newConfig);
854
-
855
- await stage.select(targetId);
856
-
857
- const targetParent = this.getParentById(target.id);
858
- await stage.update({ config: cloneDeep(target), parentId: targetParent?.id, root: cloneDeep(root) });
859
-
860
- await this.select(newConfig);
861
- stage.select(newConfig.id);
862
-
863
- this.addModifiedNodeId(target.id);
864
- this.addModifiedNodeId(parent.id);
865
- this.pushHistoryState();
866
-
867
- return newConfig;
868
- }
869
- }
870
-
871
- public async dragTo(config: MNode, targetParent: MContainer, targetIndex: number) {
872
- if (!targetParent || !Array.isArray(targetParent.items)) return;
873
-
874
- const { parent, node: curNode } = this.getNodeInfo(config.id, false);
875
- if (!parent || !curNode) throw new Error('找不要删除的节点');
876
-
877
- const index = getNodeIndex(curNode.id, parent);
878
-
879
- if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
880
-
881
- if (parent.id === targetParent.id) {
882
- if (index === targetIndex) return;
883
-
884
- if (index < targetIndex) {
885
- targetIndex -= 1;
886
- }
887
- }
888
-
889
- const layout = await this.getLayout(parent);
890
- const newLayout = await this.getLayout(targetParent);
891
-
892
- if (newLayout !== layout) {
893
- setLayout(config, newLayout);
894
- }
895
-
896
- parent.items?.splice(index, 1);
897
-
898
- targetParent.items?.splice(targetIndex, 0, config);
899
-
900
- const page = this.get('page');
901
- const root = this.get('root');
902
- const stage = this.get('stage');
903
-
904
- if (stage && page && root) {
905
- stage.update({
906
- config: cloneDeep(page),
907
- parentId: root.id,
908
- root: cloneDeep(root),
909
- });
910
- }
911
-
912
- this.addModifiedNodeId(config.id);
913
- this.addModifiedNodeId(parent.id);
914
-
915
- this.pushHistoryState();
916
-
917
- this.emit('drag-to', { index, targetIndex, config, parent, targetParent });
918
- }
919
-
920
- /**
921
- * 撤销当前操作
922
- * @returns 上一次数据
923
- */
924
- public async undo(): Promise<StepValue | null> {
925
- const value = historyService.undo();
926
- await this.changeHistoryState(value);
927
- return value;
928
- }
929
-
930
- /**
931
- * 恢复到下一步
932
- * @returns 下一步数据
933
- */
934
- public async redo(): Promise<StepValue | null> {
935
- const value = historyService.redo();
936
- await this.changeHistoryState(value);
937
- return value;
938
- }
939
-
940
- public async move(left: number, top: number) {
941
- const node = toRaw(this.get('node'));
942
- if (!node || isPage(node)) return;
943
-
944
- const { style, id, type } = node;
945
- if (!style || !['absolute', 'fixed'].includes(style.position)) return;
946
-
947
- const update = (style: { [key: string]: any }) =>
948
- this.update({
949
- id,
950
- type,
951
- style,
952
- });
953
-
954
- if (top) {
955
- if (isNumber(style.top)) {
956
- update({
957
- ...style,
958
- top: Number(style.top) + Number(top),
959
- bottom: '',
960
- });
961
- } else if (isNumber(style.bottom)) {
962
- update({
963
- ...style,
964
- bottom: Number(style.bottom) - Number(top),
965
- top: '',
966
- });
967
- }
968
- }
969
-
970
- if (left) {
971
- if (isNumber(style.left)) {
972
- update({
973
- ...style,
974
- left: Number(style.left) + Number(left),
975
- right: '',
976
- });
977
- } else if (isNumber(style.right)) {
978
- update({
979
- ...style,
980
- right: Number(style.right) - Number(left),
981
- left: '',
982
- });
983
- }
984
- }
985
- }
986
-
987
- public resetState() {
988
- this.set('root', null);
989
- this.set('node', null);
990
- this.set('nodes', []);
991
- this.set('page', null);
992
- this.set('parent', null);
993
- this.set('stage', null);
994
- this.set('highlightNode', null);
995
- this.set('modifiedNodeIds', new Map());
996
- this.set('pageLength', 0);
997
- }
998
-
999
- public destroy() {
1000
- this.removeAllListeners();
1001
- this.resetState();
1002
- this.removeAllPlugins();
1003
- }
1004
-
1005
- public resetModifiedNodeId() {
1006
- this.get('modifiedNodeIds').clear();
1007
- }
1008
-
1009
- public usePlugin(options: AsyncHookPlugin<AsyncMethodName, Editor>): void {
1010
- super.usePlugin(options);
1011
- }
1012
-
1013
- private addModifiedNodeId(id: Id) {
1014
- if (!this.isHistoryStateChange) {
1015
- this.get('modifiedNodeIds').set(id, id);
1016
- }
1017
- }
1018
-
1019
- private pushHistoryState() {
1020
- const curNode = cloneDeep(toRaw(this.get('node')));
1021
- const page = this.get('page');
1022
- if (!this.isHistoryStateChange && curNode && page) {
1023
- historyService.push({
1024
- data: cloneDeep(toRaw(page)),
1025
- modifiedNodeIds: this.get('modifiedNodeIds'),
1026
- nodeId: curNode.id,
1027
- });
1028
- }
1029
- this.isHistoryStateChange = false;
1030
- }
1031
-
1032
- private async changeHistoryState(value: StepValue | null) {
1033
- if (!value) return;
1034
-
1035
- this.isHistoryStateChange = true;
1036
- await this.update(value.data);
1037
- this.set('modifiedNodeIds', value.modifiedNodeIds);
1038
- setTimeout(async () => {
1039
- if (!value.nodeId) return;
1040
- await this.select(value.nodeId);
1041
- this.get('stage')?.select(value.nodeId);
1042
- }, 0);
1043
- this.emit('history-change', value.data);
1044
- }
1045
-
1046
- private async toggleFixedPosition(dist: MNode, src: MNode, root: MApp) {
1047
- const newConfig = cloneDeep(dist);
1048
-
1049
- if (!isPop(src) && newConfig.style?.position) {
1050
- if (isFixed(newConfig) && !isFixed(src)) {
1051
- newConfig.style = change2Fixed(newConfig, root);
1052
- } else if (!isFixed(newConfig) && isFixed(src)) {
1053
- newConfig.style = await Fixed2Other(newConfig, root, this.getLayout);
1054
- }
1055
- }
1056
-
1057
- return newConfig;
1058
- }
1059
-
1060
- private selectedConfigExceptionHandler(config: MNode | Id): EditorNodeInfo {
1061
- let id: Id;
1062
- if (typeof config === 'string' || typeof config === 'number') {
1063
- id = config;
1064
- } else {
1065
- id = config.id;
1066
- }
1067
- if (!id) {
1068
- throw new Error('没有ID,无法选中');
1069
- }
1070
-
1071
- const { node, parent, page } = this.getNodeInfo(id);
1072
- if (!node) throw new Error('获取不到组件信息');
1073
-
1074
- if (node.id === this.state.root?.id) {
1075
- throw new Error('不能选根节点');
1076
- }
1077
- return {
1078
- node,
1079
- parent,
1080
- page,
1081
- };
1082
- }
1083
- }
1084
-
1085
- export type EditorService = Editor;
1086
-
1087
- export default new Editor();