@tmagic/editor 1.1.0-beta.5 → 1.1.0-beta.6

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 (98) hide show
  1. package/dist/{tmagic-editor.es.js → tmagic-editor.mjs} +1383 -1199
  2. package/dist/tmagic-editor.mjs.map +1 -0
  3. package/dist/tmagic-editor.umd.js +1396 -1211
  4. package/dist/tmagic-editor.umd.js.map +1 -1
  5. package/package.json +21 -19
  6. package/src/Editor.vue +14 -12
  7. package/src/components/ContentMenu.vue +87 -99
  8. package/src/components/ScrollViewer.vue +1 -1
  9. package/src/components/ToolButton.vue +150 -167
  10. package/src/fields/UISelect.vue +50 -70
  11. package/src/index.ts +1 -0
  12. package/src/layouts/AddPageBox.vue +2 -2
  13. package/src/layouts/Framework.vue +1 -1
  14. package/src/layouts/NavMenu.vue +2 -2
  15. package/src/layouts/PropsPanel.vue +1 -1
  16. package/src/layouts/Resizer.vue +1 -1
  17. package/src/layouts/sidebar/ComponentListPanel.vue +2 -2
  18. package/src/layouts/sidebar/LayerMenu.vue +2 -2
  19. package/src/layouts/sidebar/LayerPanel.vue +3 -3
  20. package/src/layouts/sidebar/Sidebar.vue +1 -1
  21. package/src/layouts/sidebar/TabPane.vue +2 -2
  22. package/src/layouts/workspace/PageBar.vue +53 -64
  23. package/src/layouts/workspace/Stage.vue +195 -222
  24. package/src/layouts/workspace/ViewerMenu.vue +142 -146
  25. package/src/layouts/workspace/Workspace.vue +11 -11
  26. package/src/services/BaseService.ts +1 -1
  27. package/src/services/componentList.ts +3 -3
  28. package/src/services/editor.ts +114 -133
  29. package/src/services/events.ts +1 -1
  30. package/src/services/history.ts +1 -1
  31. package/src/services/props.ts +18 -5
  32. package/src/services/storage.ts +133 -0
  33. package/src/services/ui.ts +2 -2
  34. package/src/type.ts +22 -6
  35. package/src/utils/config.ts +1 -1
  36. package/src/utils/editor.ts +1 -1
  37. package/src/utils/operator.ts +210 -0
  38. package/src/utils/props.ts +2 -2
  39. package/src/utils/scroll-viewer.ts +1 -1
  40. package/tsconfig.build.json +13 -0
  41. package/{dist/types/src → types}/Editor.vue.d.ts +7 -8
  42. package/types/components/ContentMenu.vue.d.ts +116 -0
  43. package/types/components/Icon.vue.d.ts +13 -0
  44. package/types/components/ScrollViewer.vue.d.ts +22 -0
  45. package/types/components/ToolButton.vue.d.ts +109 -0
  46. package/types/fields/Code.vue.d.ts +23 -0
  47. package/{dist/types/src → types}/fields/CodeLink.vue.d.ts +4 -6
  48. package/types/fields/UISelect.vue.d.ts +87 -0
  49. package/{dist/types/src → types}/index.d.ts +4 -1
  50. package/types/layouts/AddPageBox.vue.d.ts +4 -0
  51. package/{dist/types/src → types}/layouts/CodeEditor.vue.d.ts +11 -13
  52. package/types/layouts/Framework.vue.d.ts +22 -0
  53. package/types/layouts/NavMenu.vue.d.ts +25 -0
  54. package/types/layouts/PropsPanel.vue.d.ts +245 -0
  55. package/types/layouts/Resizer.vue.d.ts +12 -0
  56. package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +14 -0
  57. package/types/layouts/sidebar/LayerMenu.vue.d.ts +100 -0
  58. package/types/layouts/sidebar/LayerPanel.vue.d.ts +1099 -0
  59. package/{dist/types/src → types}/layouts/sidebar/Sidebar.vue.d.ts +3 -5
  60. package/types/layouts/sidebar/TabPane.vue.d.ts +14 -0
  61. package/types/layouts/workspace/PageBar.vue.d.ts +54 -0
  62. package/types/layouts/workspace/Stage.vue.d.ts +46 -0
  63. package/types/layouts/workspace/ViewerMenu.vue.d.ts +88 -0
  64. package/types/layouts/workspace/Workspace.vue.d.ts +6 -0
  65. package/{dist/types/src → types}/services/BaseService.d.ts +0 -0
  66. package/types/services/componentList.d.ts +14 -0
  67. package/{dist/types/src → types}/services/editor.d.ts +26 -11
  68. package/{dist/types/src → types}/services/events.d.ts +0 -0
  69. package/{dist/types/src → types}/services/history.d.ts +0 -0
  70. package/{dist/types/src → types}/services/props.d.ts +6 -0
  71. package/types/services/storage.d.ts +56 -0
  72. package/{dist/types/src → types}/services/ui.d.ts +1 -1
  73. package/{dist/types/src → types}/type.d.ts +15 -0
  74. package/{dist/types/src → types}/utils/compose.d.ts +1 -1
  75. package/{dist/types/src → types}/utils/config.d.ts +0 -0
  76. package/{dist/types/src → types}/utils/editor.d.ts +2 -2
  77. package/{dist/types/src → types}/utils/index.d.ts +0 -0
  78. package/{dist/types/src → types}/utils/logger.d.ts +0 -0
  79. package/types/utils/operator.d.ts +44 -0
  80. package/{dist/types/src → types}/utils/polyfills.d.ts +0 -0
  81. package/{dist/types/src → types}/utils/props.d.ts +2 -2
  82. package/{dist/types/src → types}/utils/scroll-viewer.d.ts +0 -0
  83. package/{dist/types/src → types}/utils/undo-redo.d.ts +0 -0
  84. package/dist/tmagic-editor.es.js.map +0 -1
  85. package/dist/types/src/components/Icon.vue.d.ts +0 -14
  86. package/dist/types/src/components/ScrollViewer.vue.d.ts +0 -24
  87. package/dist/types/src/fields/Code.vue.d.ts +0 -25
  88. package/dist/types/src/layouts/AddPageBox.vue.d.ts +0 -5
  89. package/dist/types/src/layouts/Framework.vue.d.ts +0 -24
  90. package/dist/types/src/layouts/NavMenu.vue.d.ts +0 -27
  91. package/dist/types/src/layouts/PropsPanel.vue.d.ts +0 -13
  92. package/dist/types/src/layouts/Resizer.vue.d.ts +0 -14
  93. package/dist/types/src/layouts/sidebar/ComponentListPanel.vue.d.ts +0 -16
  94. package/dist/types/src/layouts/sidebar/TabPane.vue.d.ts +0 -16
  95. package/dist/types/src/layouts/workspace/Workspace.vue.d.ts +0 -8
  96. package/dist/types/src/shims-vue.d.ts +0 -6
  97. package/dist/types/src/vite-env.d.ts +0 -1
  98. package/src/vite-env.d.ts +0 -1
@@ -17,45 +17,43 @@
17
17
  */
18
18
 
19
19
  import { reactive, toRaw } from 'vue';
20
- import { cloneDeep, mergeWith } from 'lodash-es';
21
- import serialize from 'serialize-javascript';
20
+ import { cloneDeep, mergeWith, uniq } from 'lodash-es';
22
21
 
23
22
  import type { Id, MApp, MComponent, MContainer, MNode, MPage } from '@tmagic/schema';
24
23
  import { NodeType } from '@tmagic/schema';
25
24
  import StageCore from '@tmagic/stage';
26
25
  import { getNodePath, isNumber, isPage, isPop } from '@tmagic/utils';
27
26
 
28
- import historyService, { StepValue } from '@editor/services/history';
29
- import propsService from '@editor/services/props';
30
- import type { AddMNode, EditorNodeInfo, StoreState } from '@editor/type';
31
- import { LayerOffset, Layout } from '@editor/type';
27
+ import historyService, { StepValue } from '../services/history';
28
+ import storageService, { Protocol } from '../services/storage';
29
+ import type { AddMNode, EditorNodeInfo, PastePosition, StoreState } from '../type';
30
+ import { LayerOffset, Layout } from '../type';
32
31
  import {
33
32
  change2Fixed,
34
33
  COPY_STORAGE_KEY,
35
34
  Fixed2Other,
36
- fixNodeLeft,
37
- generatePageNameByApp,
38
35
  getInitPositionStyle,
39
36
  getNodeIndex,
40
37
  isFixed,
41
38
  setLayout,
42
- } from '@editor/utils/editor';
39
+ } from '../utils/editor';
40
+ import { beforeAdd, beforePaste, beforeRemove, notifyAddToStage } from '../utils/operator';
43
41
 
44
42
  import BaseService from './BaseService';
45
43
 
46
44
  class Editor extends BaseService {
47
- private isHistoryStateChange = false;
48
-
49
- private state = reactive<StoreState>({
45
+ public state: StoreState = reactive({
50
46
  root: null,
51
47
  page: null,
52
48
  parent: null,
53
49
  node: null,
50
+ nodes: [],
54
51
  stage: null,
55
52
  highlightNode: null,
56
53
  modifiedNodeIds: new Map(),
57
54
  pageLength: 0,
58
55
  });
56
+ private isHistoryStateChange = false;
59
57
 
60
58
  constructor() {
61
59
  super(
@@ -83,13 +81,16 @@ class Editor extends BaseService {
83
81
 
84
82
  /**
85
83
  * 设置当前指点节点配置
86
- * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode'
84
+ * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes'
87
85
  * @param value MNode
88
86
  * @returns MNode
89
87
  */
90
88
  public set<T = MNode>(name: keyof StoreState, value: T) {
91
89
  this.state[name] = value as any;
92
-
90
+ // set nodes时将node设置为nodes第一个元素
91
+ if (name === 'nodes') {
92
+ this.set('node', (value as unknown as MNode[])[0]);
93
+ }
93
94
  if (name === 'root') {
94
95
  this.state.pageLength = (value as unknown as MApp)?.items?.length || 0;
95
96
  this.emit('root-change', value);
@@ -98,7 +99,7 @@ class Editor extends BaseService {
98
99
 
99
100
  /**
100
101
  * 获取当前指点节点配置
101
- * @param name 'root' | 'page' | 'parent' | 'node'
102
+ * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes'
102
103
  * @returns MNode
103
104
  */
104
105
  public get<T = MNode>(name: keyof StoreState): T {
@@ -190,7 +191,7 @@ class Editor extends BaseService {
190
191
  */
191
192
  public async select(config: MNode | Id): Promise<MNode> | never {
192
193
  const { node, page, parent } = this.selectedConfigExceptionHandler(config);
193
- this.set('node', node);
194
+ this.set('nodes', [node]);
194
195
  this.set('page', page || null);
195
196
  this.set('parent', parent || null);
196
197
 
@@ -265,57 +266,66 @@ class Editor extends BaseService {
265
266
  }
266
267
 
267
268
  /**
268
- * 向指点容器添加组件节点
269
- * @param addConfig 将要添加的组件节点配置
270
- * @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
271
- * @returns 添加后的节点
269
+ * 多选
270
+ * @param ids 指定节点ID
271
+ * @returns 加入多选的节点配置
272
272
  */
273
- public async add(addNode: AddMNode, parent?: MContainer | null): Promise<MNode> {
274
- // 加入inputEvent是为给业务扩展时可以获取到更多的信息,只有在使用拖拽添加组件时才有改对象
275
- const { type, inputEvent, ...config } = addNode;
276
- const curNode = this.get<MContainer>('node');
277
-
278
- let parentNode: MContainer | undefined;
279
- const isPage = type === NodeType.PAGE;
273
+ public multiSelect(ids: Id[]): void {
274
+ const nodes: MNode[] = [];
275
+ const idsUnique = uniq(ids);
276
+ idsUnique.forEach((id) => {
277
+ const { node } = this.getNodeInfo(id);
278
+ if (!node) return;
279
+ nodes.push(node);
280
+ });
281
+ this.set('nodes', nodes);
282
+ }
280
283
 
281
- if (isPage) {
282
- parentNode = this.get<MApp>('root');
283
- // 由于支持中间件扩展,在parent参数为undefined时,parent会变成next函数
284
- } else if (parent && typeof parent !== 'function') {
285
- parentNode = parent;
286
- } else if (curNode.items) {
287
- parentNode = curNode;
288
- } else {
289
- parentNode = this.getParentById(curNode.id, false);
290
- }
284
+ /**
285
+ * 批量向容器添加节点
286
+ * @param configs 将要添加的节点数组
287
+ * @returns 添加后的节点
288
+ */
289
+ public async multiAdd(configs: MNode[]): Promise<MNode[]> {
290
+ const stage = this.get<StageCore | null>('stage');
291
+ const newNodes: MNode[] = await Promise.all(
292
+ configs.map(async (configItem: MNode): Promise<MNode> => {
293
+ // 新增元素到配置
294
+ const { parentNode, newNode, layout } = await beforeAdd(configItem as AddMNode);
295
+ // 将新增元素事件通知到stage以更新渲染
296
+ await notifyAddToStage(parentNode, newNode, layout);
297
+ return newNode;
298
+ }),
299
+ );
300
+ const newNodeIds: Id[] = newNodes.map((node) => node.id);
291
301
 
292
- if (!parentNode) throw new Error('未找到父元素');
302
+ // 增加历史记录 多选不可能选中page
303
+ this.addModifiedNodeId(newNodeIds.join('-'));
304
+ this.pushHistoryState();
293
305
 
294
- const layout = await this.getLayout(toRaw(parentNode), addNode as MNode);
295
- const newNode = { ...toRaw(await propsService.getPropsValue(type, config)) };
296
- newNode.style = getInitPositionStyle(newNode.style, layout, parentNode, this.get<StageCore>('stage'));
306
+ // 触发选中样式
307
+ stage?.multiSelect(newNodeIds);
297
308
 
298
- if ((parentNode?.type === NodeType.ROOT || curNode.type === NodeType.ROOT) && newNode.type !== NodeType.PAGE) {
299
- throw new Error('app下不能添加组件');
300
- }
309
+ this.emit('multiAdd', newNodes);
301
310
 
302
- parentNode?.items?.push(newNode);
311
+ return newNodes;
312
+ }
303
313
 
314
+ /**
315
+ * 向指点容器添加组件节点
316
+ * @param addConfig 将要添加的组件节点配置
317
+ * @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
318
+ * @returns 添加后的节点
319
+ */
320
+ public async add(addNode: AddMNode, parent?: MContainer | null): Promise<MNode> {
304
321
  const stage = this.get<StageCore | null>('stage');
305
- const root = this.get<MApp>('root');
306
-
307
- await stage?.add({ config: cloneDeep(newNode), parent: cloneDeep(parentNode), root: cloneDeep(root) });
308
-
309
- if (layout === Layout.ABSOLUTE) {
310
- const fixedLeft = fixNodeLeft(newNode, parentNode, stage?.renderer.contentWindow?.document);
311
- if (typeof fixedLeft !== 'undefined') {
312
- newNode.style.left = fixedLeft;
313
- await stage?.update({ config: cloneDeep(newNode), root: cloneDeep(root) });
314
- }
315
- }
316
-
322
+ // 新增元素到配置
323
+ const { parentNode, newNode, layout, isPage } = await beforeAdd(addNode, parent);
324
+ // 将新增元素事件通知到stage以更新渲染
325
+ await notifyAddToStage(parentNode, newNode, layout);
326
+ // 更新编辑器选中元素
317
327
  await this.select(newNode);
318
-
328
+ // 增加历史记录
319
329
  this.addModifiedNodeId(newNode.id);
320
330
  if (!isPage) {
321
331
  this.pushHistoryState();
@@ -338,50 +348,18 @@ class Editor extends BaseService {
338
348
  * @param {Object} node
339
349
  * @return {Object} 删除的组件配置
340
350
  */
341
- public async remove(node: MNode): Promise<MNode | void> {
342
- if (!node?.id) return;
343
-
344
- const root = this.get<MApp | null>('root');
345
-
346
- if (!root) throw new Error('没有root');
347
-
348
- const { parent, node: curNode } = this.getNodeInfo(node.id, false);
349
-
350
- if (!parent || !curNode) throw new Error('找不要删除的节点');
351
-
352
- const index = getNodeIndex(curNode, parent);
353
-
354
- if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
355
-
356
- parent.items?.splice(index, 1);
357
- const stage = this.get<StageCore | null>('stage');
358
- stage?.remove({ id: node.id, root: cloneDeep(this.get('root')) });
359
-
360
- if (node.type === NodeType.PAGE) {
361
- this.state.pageLength -= 1;
362
-
363
- if (root.items[0]) {
364
- await this.select(root.items[0]);
365
- stage?.select(root.items[0].id);
366
- } else {
367
- this.set('node', null);
368
- this.set('parent', null);
369
- this.set('page', null);
370
- this.set('stage', null);
371
- this.set('highlightNode', null);
372
- this.resetModifiedNodeId();
373
- historyService.reset();
374
-
375
- this.emit('remove', node);
376
-
377
- return node;
378
- }
379
- } else {
380
- await this.select(parent);
381
- stage?.select(parent.id);
351
+ public async remove(nodeOrNodeList: MNode | MNode[]): Promise<MNode | MNode[]> {
352
+ if (Array.isArray(nodeOrNodeList)) {
353
+ // 多选批量删除
354
+ const nodes = nodeOrNodeList;
355
+ return this.multiRemove(nodes);
382
356
  }
383
-
384
- this.addModifiedNodeId(parent.id);
357
+ const node = nodeOrNodeList;
358
+ const removeParent = await beforeRemove(node);
359
+ // 删除的是页面
360
+ if (!removeParent) return node;
361
+ // 更新历史记录
362
+ this.addModifiedNodeId(removeParent.id);
385
363
  this.pushHistoryState();
386
364
 
387
365
  this.emit('remove', node);
@@ -389,6 +367,25 @@ class Editor extends BaseService {
389
367
  return node;
390
368
  }
391
369
 
370
+ /**
371
+ * 批量删除
372
+ * @param nodes 批量删除的节点
373
+ * @returns 批量删除的节点
374
+ */
375
+ public async multiRemove(nodes: MNode[]): Promise<MNode[]> {
376
+ await Promise.all(
377
+ nodes.map(async (removeNode) => {
378
+ await beforeRemove(removeNode);
379
+ }),
380
+ );
381
+ const nodeIds = nodes.map((node) => node.id);
382
+ this.addModifiedNodeId(nodeIds.join('-'));
383
+ this.pushHistoryState();
384
+
385
+ this.emit('multiRemove', nodes);
386
+ return nodes;
387
+ }
388
+
392
389
  /**
393
390
  * 更新节点
394
391
  * @param config 新的节点配置,配置中需要有id信息
@@ -434,9 +431,11 @@ class Editor extends BaseService {
434
431
 
435
432
  parentNodeItems[index] = newConfig;
436
433
 
437
- if (`${newConfig.id}` === `${this.get('node').id}`) {
438
- this.set('node', newConfig);
439
- }
434
+ // 将update后的配置更新到nodes中
435
+ const nodes = this.get('nodes');
436
+ const targetIndex = nodes.findIndex((nodeItem: MNode) => `${nodeItem.id}` === `${newConfig.id}`);
437
+ nodes.splice(targetIndex, 1, newConfig);
438
+ this.set('nodes', nodes);
440
439
 
441
440
  this.get<StageCore | null>('stage')?.update({ config: cloneDeep(newConfig), root: cloneDeep(this.get('root')) });
442
441
 
@@ -482,44 +481,25 @@ class Editor extends BaseService {
482
481
  * @param config 组件节点配置
483
482
  * @returns 组件节点配置
484
483
  */
485
- public async copy(config: MNode): Promise<void> {
486
- globalThis.localStorage.setItem(COPY_STORAGE_KEY, serialize(config));
484
+ public async copy(config: MNode | MNode[]): Promise<void> {
485
+ await storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], {
486
+ protocol: Protocol.OBJECT,
487
+ });
487
488
  }
488
489
 
489
490
  /**
490
491
  * 从localStorage中获取节点,然后添加到当前容器中
491
- * @param position 如果设置,指定组件位置
492
+ * @param position 粘贴的坐标
492
493
  * @returns 添加后的组件节点配置
493
494
  */
494
- public async paste(position: { left?: number; top?: number } = {}): Promise<MNode | void> {
495
- const configStr = globalThis.localStorage.getItem(COPY_STORAGE_KEY);
496
- // eslint-disable-next-line prefer-const
497
- let config: any = {};
498
- if (!configStr) {
499
- return;
500
- }
501
-
502
- try {
503
- // eslint-disable-next-line no-eval
504
- eval(`config = ${configStr}`);
505
- } catch (e) {
506
- console.error(e);
507
- return;
508
- }
495
+ public async paste(position: PastePosition = {}): Promise<MNode[] | void> {
496
+ const config = await storageService.getItem(COPY_STORAGE_KEY);
509
497
 
510
- config = await propsService.setNewItemId(config, this.get('root'));
511
- if (config.style) {
512
- config.style = {
513
- ...config.style,
514
- ...position,
515
- };
516
- }
498
+ if (!config) return;
517
499
 
518
- if (isPage(config)) {
519
- config.name = generatePageNameByApp(this.get('root'));
520
- }
500
+ const pasteConfigs = await beforePaste(position, config);
521
501
 
522
- return await this.add(config);
502
+ return await this.multiAdd(pasteConfigs);
523
503
  }
524
504
 
525
505
  /**
@@ -673,6 +653,7 @@ class Editor extends BaseService {
673
653
  this.removeAllListeners();
674
654
  this.set('root', null);
675
655
  this.set('node', null);
656
+ this.set('nodes', []);
676
657
  this.set('page', null);
677
658
  this.set('parent', null);
678
659
  }
@@ -22,7 +22,7 @@ import { cloneDeep } from 'lodash-es';
22
22
  import { DEFAULT_EVENTS, DEFAULT_METHODS, EventOption } from '@tmagic/core';
23
23
  import { toLine } from '@tmagic/utils';
24
24
 
25
- import type { ComponentGroup } from '@editor/type';
25
+ import type { ComponentGroup } from '../type';
26
26
 
27
27
  import BaseService from './BaseService';
28
28
 
@@ -20,7 +20,7 @@ import { reactive } from 'vue';
20
20
 
21
21
  import type { Id, MPage } from '@tmagic/schema';
22
22
 
23
- import { UndoRedo } from '@editor/utils/undo-redo';
23
+ import { UndoRedo } from '../utils/undo-redo';
24
24
 
25
25
  import BaseService from './BaseService';
26
26
 
@@ -17,15 +17,15 @@
17
17
  */
18
18
 
19
19
  import { reactive } from 'vue';
20
- import { cloneDeep, mergeWith, random } from 'lodash-es';
20
+ import { cloneDeep, mergeWith } from 'lodash-es';
21
21
 
22
22
  import type { FormConfig } from '@tmagic/form';
23
23
  import type { Id, MComponent, MNode, MPage } from '@tmagic/schema';
24
24
  import { NodeType } from '@tmagic/schema';
25
25
  import { isPop, toLine } from '@tmagic/utils';
26
26
 
27
- import type { PropsState } from '@editor/type';
28
- import { DEFAULT_CONFIG, fillConfig } from '@editor/utils/props';
27
+ import type { PropsState } from '../type';
28
+ import { DEFAULT_CONFIG, fillConfig } from '../utils/props';
29
29
 
30
30
  import BaseService from './BaseService';
31
31
 
@@ -121,12 +121,25 @@ class Props extends BaseService {
121
121
  return {
122
122
  id,
123
123
  ...defaultPropsValue,
124
- ...mergeWith(cloneDeep(this.state.propsValueMap[type] || {}), data),
124
+ ...mergeWith({}, cloneDeep(this.state.propsValueMap[type] || {}), data),
125
125
  };
126
126
  }
127
127
 
128
+ /**
129
+ * 生成指定位数的GUID,无【-】格式
130
+ * @param digit 位数,默认值8
131
+ * @returns
132
+ */
133
+ guid(digit = 8): string {
134
+ return 'x'.repeat(digit).replace(/[xy]/g, (c) => {
135
+ const r = (Math.random() * 16) | 0;
136
+ const v = c == 'x' ? r : (r & 0x3) | 0x8;
137
+ return v.toString(16);
138
+ });
139
+ }
140
+
128
141
  public async createId(type: string | number): Promise<string> {
129
- return `${type}_${random(10000, false)}`;
142
+ return `${type}_${this.guid()}`;
130
143
  }
131
144
 
132
145
  /**
@@ -0,0 +1,133 @@
1
+ import serialize from 'serialize-javascript';
2
+
3
+ import BaseService from './BaseService';
4
+
5
+ interface Options {
6
+ namespace?: string;
7
+ protocol?: Protocol;
8
+ }
9
+
10
+ export enum Protocol {
11
+ OBJECT = 'object',
12
+ JSON = 'json',
13
+ STRING = 'string',
14
+ NUMBER = 'number',
15
+ BOOLEAN = 'boolean',
16
+ }
17
+
18
+ /**
19
+ * 数据存储服务
20
+ */
21
+ export class WebStorage extends BaseService {
22
+ private storage: Storage = globalThis.localStorage;
23
+ private namespace = 'tmagic';
24
+
25
+ constructor() {
26
+ super(['getStorage', 'getNamespace', 'clear', 'getItem', 'removeItem', 'setItem']);
27
+ }
28
+
29
+ /**
30
+ * 获取数据存储对象,可以通过
31
+ * const storageService = new StorageService();
32
+ * storageService.usePlugin({
33
+ * // 替换存储对象为 sessionStorage
34
+ * async afterGetStorage(): Promise<Storage> {
35
+ * return window.sessionStorage;
36
+ * },
37
+ * });
38
+ */
39
+ public async getStorage(): Promise<Storage> {
40
+ return this.storage;
41
+ }
42
+
43
+ public async getNamespace(): Promise<string> {
44
+ return this.namespace;
45
+ }
46
+
47
+ /**
48
+ * 清理,支持storageService.usePlugin
49
+ */
50
+ public async clear(): Promise<void> {
51
+ const storage = await this.getStorage();
52
+ storage.clear();
53
+ }
54
+ /**
55
+ * 获取存储项,支持storageService.usePlugin
56
+ */
57
+ public async getItem(key: string, options: Options = {}): Promise<any> {
58
+ const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]);
59
+ const { protocol = options.protocol, item } = this.getValueAndProtocol(
60
+ storage.getItem(`${options.namespace || namespace}:${key}`),
61
+ );
62
+
63
+ if (item === null) return null;
64
+
65
+ switch (protocol) {
66
+ case Protocol.OBJECT:
67
+ // eslint-disable-next-line no-eval
68
+ return eval(`(${item})`);
69
+ case Protocol.JSON:
70
+ return JSON.parse(item);
71
+ case Protocol.NUMBER:
72
+ return Number(item);
73
+ case Protocol.BOOLEAN:
74
+ return Boolean(item);
75
+ default:
76
+ return item;
77
+ }
78
+ }
79
+ /**
80
+ * 获取指定索引位置的key
81
+ */
82
+ public async key(index: number): Promise<string | null> {
83
+ const storage = await this.getStorage();
84
+ return storage.key(index);
85
+ }
86
+
87
+ /**
88
+ * 移除存储项,支持storageService.usePlugin
89
+ */
90
+ public async removeItem(key: string, options: Options = {}): Promise<void> {
91
+ const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]);
92
+ storage.removeItem(`${options.namespace || namespace}:${key}`);
93
+ }
94
+
95
+ /**
96
+ * 设置存储项,支持storageService.usePlugin
97
+ */
98
+ public async setItem(key: string, value: any, options: Options = {}): Promise<void> {
99
+ const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]);
100
+ let item = value;
101
+ const protocol = options.protocol ? `${options.protocol}:` : '';
102
+ if (typeof value === Protocol.STRING || typeof value === Protocol.NUMBER) {
103
+ item = `${protocol}${value}`;
104
+ } else {
105
+ item = `${protocol}${serialize(value)}`;
106
+ }
107
+ storage.setItem(`${options.namespace || namespace}:${key}`, item);
108
+ }
109
+
110
+ private getValueAndProtocol(value: string | null) {
111
+ let protocol = '';
112
+
113
+ if (value === null) {
114
+ return {
115
+ item: value,
116
+ protocol,
117
+ };
118
+ }
119
+
120
+ const item = value.replace(new RegExp(`^(${Object.values(Protocol).join('|')})(:)(.+)`), (_$0, $1, _$2, $3) => {
121
+ protocol = $1;
122
+ return $3;
123
+ });
124
+
125
+ return {
126
+ protocol,
127
+ item,
128
+ };
129
+ }
130
+ }
131
+
132
+ export type StorageService = WebStorage;
133
+ export default new WebStorage();
@@ -20,8 +20,8 @@ import { reactive, toRaw } from 'vue';
20
20
 
21
21
  import type StageCore from '@tmagic/stage';
22
22
 
23
- import editorService from '@editor/services/editor';
24
- import type { GetColumnWidth, SetColumnWidth, StageRect, UiState } from '@editor/type';
23
+ import editorService from '../services/editor';
24
+ import type { GetColumnWidth, SetColumnWidth, StageRect, UiState } from '../type';
25
25
 
26
26
  import BaseService from './BaseService';
27
27
 
package/src/type.ts CHANGED
@@ -23,12 +23,13 @@ import type { Id, MApp, MContainer, MNode, MPage } from '@tmagic/schema';
23
23
  import type StageCore from '@tmagic/stage';
24
24
  import type { MoveableOptions } from '@tmagic/stage';
25
25
 
26
- import type { ComponentListService } from '@editor/services/componentList';
27
- import type { EditorService } from '@editor/services/editor';
28
- import type { EventsService } from '@editor/services/events';
29
- import type { HistoryService } from '@editor/services/history';
30
- import type { PropsService } from '@editor/services/props';
31
- import type { UiService } from '@editor/services/ui';
26
+ import type { ComponentListService } from './services/componentList';
27
+ import type { EditorService } from './services/editor';
28
+ import type { EventsService } from './services/events';
29
+ import type { HistoryService } from './services/history';
30
+ import type { PropsService } from './services/props';
31
+ import type { StorageService } from './services/storage';
32
+ import type { UiService } from './services/ui';
32
33
 
33
34
  export type BeforeAdd = (config: MNode, parent: MContainer) => Promise<MNode> | MNode;
34
35
  export type GetConfig = (config: FormConfig) => Promise<FormConfig> | FormConfig;
@@ -40,6 +41,7 @@ export interface InstallOptions {
40
41
  export interface Services {
41
42
  editorService: EditorService;
42
43
  historyService: HistoryService;
44
+ storageService: StorageService;
43
45
  eventsService: EventsService;
44
46
  propsService: PropsService;
45
47
  componentListService: ComponentListService;
@@ -64,6 +66,7 @@ export interface StoreState {
64
66
  parent: MContainer | null;
65
67
  node: MNode | null;
66
68
  highlightNode: MNode | null;
69
+ nodes: MNode[];
67
70
  stage: StageCore | null;
68
71
  modifiedNodeIds: Map<Id, Id>;
69
72
  pageLength: number;
@@ -129,6 +132,19 @@ export interface AddMNode {
129
132
  [key: string]: any;
130
133
  }
131
134
 
135
+ export interface PastePosition {
136
+ left?: number;
137
+ top?: number;
138
+ /**
139
+ * 粘贴位置X方向偏移量
140
+ */
141
+ offsetX?: number;
142
+ /**
143
+ * 粘贴位置Y方向偏移量
144
+ */
145
+ offsetY?: number;
146
+ }
147
+
132
148
  /**
133
149
  * 菜单按钮
134
150
  */
@@ -16,7 +16,7 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
- import { InstallOptions } from '@editor/type';
19
+ import { InstallOptions } from '../type';
20
20
 
21
21
  let $TMAGIC_EDITOR: InstallOptions = {} as any;
22
22
 
@@ -21,7 +21,7 @@ import { NodeType } from '@tmagic/schema';
21
21
  import type StageCore from '@tmagic/stage';
22
22
  import { getNodePath, isNumber, isPage, isPop } from '@tmagic/utils';
23
23
 
24
- import { Layout } from '@editor/type';
24
+ import { Layout } from '../type';
25
25
 
26
26
  export const COPY_STORAGE_KEY = '$MagicEditorCopyData';
27
27