@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
@@ -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';
@@ -33,11 +33,11 @@ import {
33
33
  change2Fixed,
34
34
  COPY_STORAGE_KEY,
35
35
  Fixed2Other,
36
+ generatePageNameByApp,
36
37
  getNodeIndex,
37
38
  initPosition,
38
39
  isFixed,
39
40
  setLayout,
40
- setNewItemId,
41
41
  } from '@editor/utils/editor';
42
42
  import { log } from '@editor/utils/logger';
43
43
 
@@ -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
  }
@@ -101,10 +108,14 @@ class Editor extends BaseService {
101
108
  /**
102
109
  * 根据id获取组件、组件的父组件以及组件所属的页面节点
103
110
  * @param {number | string} id 组件id
111
+ * @param {boolean} raw 是否使用toRaw
104
112
  * @returns {EditorNodeInfo}
105
113
  */
106
- public getNodeInfo(id: Id): EditorNodeInfo {
107
- const root = this.get<MApp | null>('root');
114
+ public getNodeInfo(id: Id, raw = true): EditorNodeInfo {
115
+ let root = this.get<MApp | null>('root');
116
+ if (raw) {
117
+ root = toRaw(root);
118
+ }
108
119
  if (!root) return {};
109
120
 
110
121
  if (id === root.id) {
@@ -134,34 +145,38 @@ class Editor extends BaseService {
134
145
  /**
135
146
  * 根据ID获取指点节点配置
136
147
  * @param id 组件ID
148
+ * @param {boolean} raw 是否使用toRaw
137
149
  * @returns 组件节点配置
138
150
  */
139
- public getNodeById(id: Id): MNode | undefined {
140
- const { node } = this.getNodeInfo(id);
151
+ public getNodeById(id: Id, raw = true): MNode | undefined {
152
+ const { node } = this.getNodeInfo(id, raw);
141
153
  return node;
142
154
  }
143
155
 
144
156
  /**
145
157
  * 根据ID获取指点节点的父节点配置
146
158
  * @param id 组件ID
159
+ * @param {boolean} raw 是否使用toRaw
147
160
  * @returns 指点组件的父节点配置
148
161
  */
149
- public getParentById(id: Id): MContainer | undefined {
162
+ public getParentById(id: Id, raw = true): MContainer | undefined {
150
163
  if (!this.get<MApp | null>('root')) return;
151
- const { parent } = this.getNodeInfo(id);
164
+ const { parent } = this.getNodeInfo(id, raw);
152
165
  return parent;
153
166
  }
154
167
 
155
168
  /**
156
169
  * 只有容器拥有布局
157
170
  */
158
- public async getLayout(node: MNode): Promise<Layout> {
159
- if (node.layout) {
160
- return node.layout;
171
+ public async getLayout(parent: MNode, node?: MNode): Promise<Layout> {
172
+ if (node && typeof node !== 'function' && isFixed(node)) return Layout.FIXED;
173
+
174
+ if (parent.layout) {
175
+ return parent.layout;
161
176
  }
162
177
 
163
178
  // 如果该节点没有设置position,则认为是流式布局,例如获取root的布局时
164
- if (!node.style?.position) {
179
+ if (!parent.style?.position) {
165
180
  return Layout.RELATIVE;
166
181
  }
167
182
 
@@ -184,9 +199,59 @@ class Editor extends BaseService {
184
199
  } else {
185
200
  historyService.empty();
186
201
  }
202
+
203
+ if (node?.id) {
204
+ this.get<StageCore>('stage')
205
+ ?.renderer.runtime?.getApp?.()
206
+ ?.emit(
207
+ 'editor:select',
208
+ {
209
+ node,
210
+ page,
211
+ parent,
212
+ },
213
+ getNodePath(node.id, this.get<MApp>('root').items),
214
+ );
215
+ }
216
+
217
+ this.emit('select', node);
218
+
187
219
  return node!;
188
220
  }
189
221
 
222
+ public async selectNextNode(): Promise<MNode> | never {
223
+ const node = toRaw(this.get('node'));
224
+
225
+ if (!node || isPage(node) || node.type === NodeType.ROOT) return node;
226
+
227
+ const parent = toRaw(this.getParentById(node.id));
228
+
229
+ if (!parent) return node;
230
+
231
+ const index = getNodeIndex(node, parent);
232
+
233
+ const nextNode = parent.items[index + 1] || parent.items[0];
234
+
235
+ await this.select(nextNode);
236
+ this.get<StageCore>('stage')?.select(nextNode.id);
237
+
238
+ return nextNode;
239
+ }
240
+
241
+ public async selectNextPage(): Promise<MNode> | never {
242
+ const root = toRaw(this.get<MApp>('root'));
243
+ const page = toRaw(this.get('page'));
244
+
245
+ const index = getNodeIndex(page, root);
246
+
247
+ const nextPage = root.items[index + 1] || root.items[0];
248
+
249
+ await this.select(nextPage);
250
+ this.get<StageCore>('stage')?.select(nextPage.id);
251
+
252
+ return nextPage;
253
+ }
254
+
190
255
  /**
191
256
  * 高亮指定节点
192
257
  * @param config 指定节点配置或者ID
@@ -194,8 +259,8 @@ class Editor extends BaseService {
194
259
  */
195
260
  public highlight(config: MNode | Id): void {
196
261
  const { node } = this.selectedConfigExceptionHandler(config);
197
- const currentHighligtNode = this.get('highlightNode');
198
- if (currentHighligtNode === node) return;
262
+ const currentHighlightNode = this.get('highlightNode');
263
+ if (currentHighlightNode === node) return;
199
264
  this.set('highlightNode', node);
200
265
  }
201
266
 
@@ -205,12 +270,14 @@ class Editor extends BaseService {
205
270
  * @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
206
271
  * @returns 添加后的节点
207
272
  */
208
- public async add({ type, ...config }: AddMNode, parent?: MContainer | null): Promise<MNode> {
273
+ public async add(addNode: AddMNode, parent?: MContainer | null): Promise<MNode> {
274
+ const { type, ...config } = addNode;
209
275
  const curNode = this.get<MContainer>('node');
210
276
 
211
277
  let parentNode: MNode | undefined;
278
+ const isPage = type === NodeType.PAGE;
212
279
 
213
- if (type === NodeType.PAGE) {
280
+ if (isPage) {
214
281
  parentNode = this.get<MApp>('root');
215
282
  // 由于支持中间件扩展,在parent参数为undefined时,parent会变成next函数
216
283
  } else if (parent && typeof parent !== 'function') {
@@ -218,13 +285,18 @@ class Editor extends BaseService {
218
285
  } else if (curNode.items) {
219
286
  parentNode = curNode;
220
287
  } else {
221
- parentNode = this.getParentById(curNode.id);
288
+ parentNode = this.getParentById(curNode.id, false);
222
289
  }
223
290
 
224
291
  if (!parentNode) throw new Error('未找到父元素');
225
292
 
226
- const layout = await this.getLayout(parentNode);
227
- const newNode = initPosition({ ...toRaw(await propsService.getPropsValue(type)), ...config }, layout);
293
+ const layout = await this.getLayout(toRaw(parentNode), addNode as MNode);
294
+ const newNode = initPosition(
295
+ { ...toRaw(await propsService.getPropsValue(type, config)) },
296
+ layout,
297
+ parentNode,
298
+ this.get<StageCore>('stage'),
299
+ );
228
300
 
229
301
  if ((parentNode?.type === NodeType.ROOT || curNode.type === NodeType.ROOT) && newNode.type !== NodeType.PAGE) {
230
302
  throw new Error('app下不能添加组件');
@@ -232,15 +304,25 @@ class Editor extends BaseService {
232
304
 
233
305
  parentNode?.items?.push(newNode);
234
306
 
235
- await this.get<StageCore>('stage')?.add({ config: cloneDeep(newNode), root: cloneDeep(this.get('root')) });
307
+ const stage = this.get<StageCore | null>('stage');
308
+
309
+ await stage?.add({ config: cloneDeep(newNode), root: cloneDeep(this.get('root')) });
236
310
 
237
311
  await this.select(newNode);
238
312
 
239
313
  this.addModifiedNodeId(newNode.id);
240
- if (type !== NodeType.PAGE) {
314
+ if (!isPage) {
241
315
  this.pushHistoryState();
242
316
  }
243
317
 
318
+ stage?.select(newNode.id);
319
+
320
+ if (isPage) {
321
+ this.state.pageLength += 1;
322
+ }
323
+
324
+ this.emit('add', newNode);
325
+
244
326
  return newNode;
245
327
  }
246
328
 
@@ -256,7 +338,7 @@ class Editor extends BaseService {
256
338
 
257
339
  if (!root) throw new Error('没有root');
258
340
 
259
- const { parent, node: curNode } = this.getNodeInfo(node.id);
341
+ const { parent, node: curNode } = this.getNodeInfo(node.id, false);
260
342
 
261
343
  if (!parent || !curNode) throw new Error('找不要删除的节点');
262
344
 
@@ -265,17 +347,38 @@ class Editor extends BaseService {
265
347
  if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
266
348
 
267
349
  parent.items?.splice(index, 1);
268
- this.get<StageCore>('stage')?.remove({ id: node.id, root: this.get('root') });
350
+ const stage = this.get<StageCore | null>('stage');
351
+ stage?.remove({ id: node.id, root: this.get('root') });
269
352
 
270
353
  if (node.type === NodeType.PAGE) {
271
- await this.select(root.items[0] || root);
354
+ this.state.pageLength -= 1;
355
+
356
+ if (root.items[0]) {
357
+ await this.select(root.items[0]);
358
+ stage?.select(root.items[0].id);
359
+ } else {
360
+ this.set('node', null);
361
+ this.set('parent', null);
362
+ this.set('page', null);
363
+ this.set('stage', null);
364
+ this.set('highlightNode', null);
365
+ this.resetModifiedNodeId();
366
+ historyService.reset();
367
+
368
+ this.emit('remove', node);
369
+
370
+ return node;
371
+ }
272
372
  } else {
273
373
  await this.select(parent);
374
+ stage?.select(parent.id);
274
375
  }
275
376
 
276
377
  this.addModifiedNodeId(parent.id);
277
378
  this.pushHistoryState();
278
379
 
380
+ this.emit('remove', node);
381
+
279
382
  return node;
280
383
  }
281
384
 
@@ -287,7 +390,7 @@ class Editor extends BaseService {
287
390
  public async update(config: MNode): Promise<MNode> {
288
391
  if (!config?.id) throw new Error('没有配置或者配置缺少id值');
289
392
 
290
- const info = this.getNodeInfo(config.id);
393
+ const info = this.getNodeInfo(config.id, false);
291
394
 
292
395
  if (!info.node) throw new Error(`获取不到id为${config.id}的节点`);
293
396
 
@@ -324,11 +427,11 @@ class Editor extends BaseService {
324
427
 
325
428
  parentNodeItems[index] = newConfig;
326
429
 
327
- if (newConfig.id === this.get('node').id) {
430
+ if (`${newConfig.id}` === `${this.get('node').id}`) {
328
431
  this.set('node', newConfig);
329
432
  }
330
433
 
331
- this.get<StageCore>('stage')?.update({ config: cloneDeep(newConfig), root: this.get('root') });
434
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(newConfig), root: this.get('root') });
332
435
 
333
436
  if (newConfig.type === NodeType.PAGE) {
334
437
  this.set('page', newConfig);
@@ -337,6 +440,8 @@ class Editor extends BaseService {
337
440
  this.addModifiedNodeId(newConfig.id);
338
441
  this.pushHistoryState();
339
442
 
443
+ this.emit('update', newConfig);
444
+
340
445
  return newConfig;
341
446
  }
342
447
 
@@ -359,7 +464,7 @@ class Editor extends BaseService {
359
464
  await this.update(parent);
360
465
  await this.select(node);
361
466
 
362
- this.get<StageCore>('stage')?.update({ config: cloneDeep(node), root: this.get('root') });
467
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(node), root: this.get('root') });
363
468
 
364
469
  this.addModifiedNodeId(parent.id);
365
470
  this.pushHistoryState();
@@ -395,7 +500,7 @@ class Editor extends BaseService {
395
500
  return;
396
501
  }
397
502
 
398
- setNewItemId(config, this.get('root'));
503
+ await propsService.setNewItemId(config, this.get('root'));
399
504
  if (config.style) {
400
505
  config.style = {
401
506
  ...config.style,
@@ -403,6 +508,10 @@ class Editor extends BaseService {
403
508
  };
404
509
  }
405
510
 
511
+ if (isPage(config)) {
512
+ config.name = generatePageNameByApp(this.get('root'));
513
+ }
514
+
406
515
  return await this.add(config);
407
516
  }
408
517
 
@@ -414,17 +523,28 @@ class Editor extends BaseService {
414
523
  public async alignCenter(config: MNode): Promise<MNode | void> {
415
524
  const parent = this.get<MContainer>('parent');
416
525
  const node = this.get<MNode>('node');
417
- const layout = await this.getLayout(parent);
526
+ const layout = await this.getLayout(toRaw(parent), toRaw(node));
418
527
  if (layout === Layout.RELATIVE) {
419
528
  return;
420
529
  }
421
530
 
422
- if (parent.style?.width && node.style?.width) {
531
+ if (!node.style) return;
532
+
533
+ const stage = this.get<StageCore>('stage');
534
+ const doc = stage?.renderer.contentWindow?.document;
535
+
536
+ if (doc) {
537
+ const parentEl = doc.getElementById(`${parent.id}`);
538
+ const el = doc.getElementById(`${node.id}`);
539
+ if (parentEl && el) {
540
+ node.style.left = (parentEl.clientWidth - el.clientWidth) / 2;
541
+ }
542
+ } else if (parent.style && isNumber(parent.style?.width) && isNumber(node.style?.width)) {
423
543
  node.style.left = (parent.style.width - node.style.width) / 2;
424
544
  }
425
545
 
426
546
  await this.update(node);
427
- this.get<StageCore>('stage')?.update({ config: cloneDeep(toRaw(node)), root: this.get('root') });
547
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(toRaw(node)), root: this.get('root') });
428
548
  this.addModifiedNodeId(config.id);
429
549
  this.pushHistoryState();
430
550
 
@@ -441,15 +561,15 @@ class Editor extends BaseService {
441
561
  const brothers: MNode[] = parent?.items || [];
442
562
  const index = brothers.findIndex((item) => `${item.id}` === `${node?.id}`);
443
563
 
444
- if (offset === LayerOffset.BOTTOM) {
564
+ if (offset === LayerOffset.TOP) {
445
565
  brothers.splice(brothers.length - 1, 0, brothers.splice(index, 1)[0]);
446
- } else if (offset === LayerOffset.TOP) {
566
+ } else if (offset === LayerOffset.BOTTOM) {
447
567
  brothers.splice(0, 0, brothers.splice(index, 1)[0]);
448
568
  } else {
449
569
  brothers.splice(index + parseInt(`${offset}`, 10), 0, brothers.splice(index, 1)[0]);
450
570
  }
451
571
 
452
- this.get<StageCore>('stage')?.update({ config: cloneDeep(toRaw(parent)), root: this.get('root') });
572
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(toRaw(parent)), root: this.get('root') });
453
573
  }
454
574
 
455
575
  /**
@@ -472,6 +592,26 @@ class Editor extends BaseService {
472
592
  return value;
473
593
  }
474
594
 
595
+ public async move(left: number, top: number) {
596
+ const node = toRaw(this.get('node'));
597
+ if (!node || isPage(node)) return;
598
+
599
+ const { style, id } = node;
600
+ if (!style || style.position !== 'absolute') return;
601
+
602
+ if (top && !isNumber(style.top)) return;
603
+ if (left && !isNumber(style.left)) return;
604
+
605
+ this.update({
606
+ id,
607
+ style: {
608
+ ...style,
609
+ left: Number(style.left) + left,
610
+ top: Number(style.top) + top,
611
+ },
612
+ });
613
+ }
614
+
475
615
  public destroy() {
476
616
  this.removeAllListeners();
477
617
  this.set('root', null);
@@ -508,7 +648,11 @@ class Editor extends BaseService {
508
648
  this.isHistoryStateChange = true;
509
649
  await this.update(value.data);
510
650
  this.set('modifiedNodeIds', value.modifiedNodeIds);
511
- setTimeout(() => value.nodeId && this.select(value.nodeId), 0);
651
+ setTimeout(async () => {
652
+ if (!value.nodeId) return;
653
+ await this.select(value.nodeId);
654
+ this.get<StageCore | null>('stage')?.select(value.nodeId);
655
+ }, 0);
512
656
  }
513
657
 
514
658
  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,11 +17,12 @@
17
17
  */
18
18
 
19
19
  import { reactive } from 'vue';
20
- import { cloneDeep } from 'lodash-es';
20
+ import { cloneDeep, mergeWith, random } from 'lodash-es';
21
21
 
22
22
  import type { FormConfig } from '@tmagic/form';
23
- import type { MComponent, MNode } from '@tmagic/schema';
24
- import { toLine } from '@tmagic/utils';
23
+ import type { Id, MComponent, MNode, MPage } from '@tmagic/schema';
24
+ import { NodeType } from '@tmagic/schema';
25
+ import { isPop, toLine } from '@tmagic/utils';
25
26
 
26
27
  import type { PropsState } from '@editor/type';
27
28
  import { DEFAULT_CONFIG, fillConfig, getDefaultPropsValue } from '@editor/utils/props';
@@ -35,7 +36,7 @@ class Props extends BaseService {
35
36
  });
36
37
 
37
38
  constructor() {
38
- super(['setPropsConfig', 'getPropsConfig', 'setPropsValue', 'getPropsValue']);
39
+ super(['setPropsConfig', 'getPropsConfig', 'setPropsValue', 'getPropsValue', 'createId', 'setNewItemId']);
39
40
  }
40
41
 
41
42
  public setPropsConfigs(configs: Record<string, FormConfig>) {
@@ -87,7 +88,7 @@ class Props extends BaseService {
87
88
  * @param type 组件类型
88
89
  * @returns 组件初始值
89
90
  */
90
- public async getPropsValue(type: string) {
91
+ public async getPropsValue(type: string, defaultValue: Record<string, any> = {}) {
91
92
  if (type === 'area') {
92
93
  const value = (await this.getPropsValue('button')) as MComponent;
93
94
  value.className = 'action-area';
@@ -98,13 +99,67 @@ class Props extends BaseService {
98
99
  return value;
99
100
  }
100
101
 
101
- return cloneDeep({
102
- ...getDefaultPropsValue(type),
103
- ...(this.state.propsValueMap[type] || {}),
104
- });
102
+ const data = cloneDeep(defaultValue as any);
103
+
104
+ await this.setNewItemId(data);
105
+
106
+ return {
107
+ ...getDefaultPropsValue(type, await this.createId(type)),
108
+ ...mergeWith(cloneDeep(this.state.propsValueMap[type] || {}), data),
109
+ };
110
+ }
111
+
112
+ public async createId(type: string | number): Promise<string> {
113
+ return `${type}_${random(10000, false)}`;
114
+ }
115
+
116
+ /**
117
+ * 将组件与组件的子元素配置中的id都设置成一个新的ID
118
+ * @param {Object} config 组件配置
119
+ */
120
+ /* eslint no-param-reassign: ["error", { "props": false }] */
121
+ public async setNewItemId(config: MNode, parent?: MPage) {
122
+ const oldId = config.id;
123
+
124
+ config.id = await this.createId(config.type || 'component');
125
+
126
+ // 只有弹窗在页面下的一级子元素才有效
127
+ if (isPop(config) && parent?.type === NodeType.PAGE) {
128
+ updatePopId(oldId, config.id, parent);
129
+ }
130
+
131
+ if (config.items && Array.isArray(config.items)) {
132
+ for (const item of config.items) {
133
+ await this.setNewItemId(item, config as MPage);
134
+ }
135
+ }
105
136
  }
106
137
  }
107
138
 
139
+ /**
140
+ * 复制页面时,需要把组件下关联的弹窗id换测复制出来的弹窗的id
141
+ * @param {number} oldId 复制的源弹窗id
142
+ * @param {number} popId 新的弹窗id
143
+ * @param {Object} pageConfig 页面配置
144
+ */
145
+ const updatePopId = (oldId: Id, popId: Id, pageConfig: MPage) => {
146
+ pageConfig.items?.forEach((config) => {
147
+ if (config.pop === oldId) {
148
+ config.pop = popId;
149
+ return;
150
+ }
151
+
152
+ if (config.popId === oldId) {
153
+ config.popId = popId;
154
+ return;
155
+ }
156
+
157
+ if (Array.isArray(config.items)) {
158
+ updatePopId(oldId, popId, config as MPage);
159
+ }
160
+ });
161
+ };
162
+
108
163
  export type PropsService = Props;
109
164
 
110
165
  export default new Props();
@@ -25,6 +25,17 @@ import type { GetColumnWidth, SetColumnWidth, StageRect, UiState } from '@editor
25
25
 
26
26
  import BaseService from './BaseService';
27
27
 
28
+ const DEFAULT_LEFT_COLUMN_WIDTH = 310;
29
+ const DEFAULT_RIGHT_COLUMN_WIDTH = 480;
30
+
31
+ const COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorColumnWidthData';
32
+
33
+ const defaultColumnWidth = {
34
+ left: DEFAULT_LEFT_COLUMN_WIDTH,
35
+ center: globalThis.document.body.clientWidth - DEFAULT_LEFT_COLUMN_WIDTH - DEFAULT_RIGHT_COLUMN_WIDTH,
36
+ right: DEFAULT_RIGHT_COLUMN_WIDTH,
37
+ };
38
+
28
39
  const state = reactive<UiState>({
29
40
  uiSelectMode: false,
30
41
  showSrc: false,
@@ -37,13 +48,10 @@ const state = reactive<UiState>({
37
48
  width: 375,
38
49
  height: 817,
39
50
  },
40
- columnWidth: {
41
- left: 310,
42
- center: globalThis.document.body.clientWidth - 310 - 400,
43
- right: 400,
44
- },
51
+ columnWidth: defaultColumnWidth,
45
52
  showGuides: true,
46
53
  showRule: true,
54
+ propsPanelSize: 'small',
47
55
  });
48
56
 
49
57
  class Ui extends BaseService {
@@ -54,6 +62,16 @@ class Ui extends BaseService {
54
62
  center: 'auto',
55
63
  });
56
64
  });
65
+
66
+ const columnWidthCacheData = globalThis.localStorage.getItem(COLUMN_WIDTH_STORAGE_KEY);
67
+ if (columnWidthCacheData) {
68
+ try {
69
+ const columnWidthCache = JSON.parse(columnWidthCacheData);
70
+ this.setColumnWidth(columnWidthCache);
71
+ } catch (e) {
72
+ console.error(e);
73
+ }
74
+ }
57
75
  }
58
76
 
59
77
  public set<T = any>(name: keyof UiState, value: T) {
@@ -88,6 +106,11 @@ class Ui extends BaseService {
88
106
  return (state as any)[name];
89
107
  }
90
108
 
109
+ public zoom(zoom: number) {
110
+ this.set('zoom', (this.get<number>('zoom') * 100 + zoom * 100) / 100);
111
+ if (this.get<number>('zoom') < 0.1) this.set('zoom', 0.1);
112
+ }
113
+
91
114
  private setColumnWidth({ left, center, right }: SetColumnWidth) {
92
115
  const columnWidth = {
93
116
  ...toRaw(this.get<GetColumnWidth>('columnWidth')),
@@ -104,10 +127,17 @@ class Ui extends BaseService {
104
127
  if (!center || center === 'auto') {
105
128
  const bodyWidth = globalThis.document.body.clientWidth;
106
129
  columnWidth.center = bodyWidth - (columnWidth?.left || 0) - (columnWidth?.right || 0);
130
+ if (columnWidth.center <= 0) {
131
+ columnWidth.left = defaultColumnWidth.left;
132
+ columnWidth.center = defaultColumnWidth.center;
133
+ columnWidth.right = defaultColumnWidth.right;
134
+ }
107
135
  } else {
108
136
  columnWidth.center = center;
109
137
  }
110
138
 
139
+ globalThis.localStorage.setItem(COLUMN_WIDTH_STORAGE_KEY, JSON.stringify(columnWidth));
140
+
111
141
  state.columnWidth = columnWidth;
112
142
  }
113
143
 
@@ -2,6 +2,7 @@ $--theme-color: #2882e0;
2
2
 
3
3
  $--font-color: #070303;
4
4
  $--border-color: #d9dbdd;
5
+ $--hover-color: #f3f5f9;
5
6
 
6
7
  $--nav-height: 35px;
7
8
  $--nav-color: #070303;
@@ -12,6 +12,7 @@
12
12
  top: 0;
13
13
  left: 0;
14
14
  box-sizing: border-box;
15
+ z-index: 1;
15
16
 
16
17
  .el-input__prefix {
17
18
  padding: 10px;
@@ -62,6 +63,7 @@
62
63
  color: $--font-color;
63
64
  flex-direction: column;
64
65
  width: 42px;
66
+ cursor: pointer;
65
67
 
66
68
  i {
67
69
  font-size: 20px;