@tmagic/editor 1.1.0-beta.1 → 1.1.0-beta.12

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 (106) hide show
  1. package/dist/style.css +5 -1
  2. package/dist/{tmagic-editor.es.js → tmagic-editor.mjs} +1449 -1205
  3. package/dist/tmagic-editor.mjs.map +1 -0
  4. package/dist/tmagic-editor.umd.js +1463 -1219
  5. package/dist/tmagic-editor.umd.js.map +1 -1
  6. package/package.json +23 -21
  7. package/src/Editor.vue +24 -15
  8. package/src/components/ContentMenu.vue +87 -99
  9. package/src/components/Icon.vue +1 -1
  10. package/src/components/ScrollViewer.vue +2 -2
  11. package/src/components/ToolButton.vue +150 -167
  12. package/src/fields/UISelect.vue +51 -72
  13. package/src/index.ts +2 -1
  14. package/src/layouts/AddPageBox.vue +2 -2
  15. package/src/layouts/Framework.vue +1 -1
  16. package/src/layouts/NavMenu.vue +2 -2
  17. package/src/layouts/PropsPanel.vue +1 -1
  18. package/src/layouts/Resizer.vue +1 -1
  19. package/src/layouts/sidebar/ComponentListPanel.vue +6 -17
  20. package/src/layouts/sidebar/LayerMenu.vue +2 -2
  21. package/src/layouts/sidebar/LayerPanel.vue +52 -12
  22. package/src/layouts/sidebar/Sidebar.vue +1 -1
  23. package/src/layouts/sidebar/TabPane.vue +2 -2
  24. package/src/layouts/workspace/PageBar.vue +63 -192
  25. package/src/layouts/workspace/PageBarScrollContainer.vue +111 -0
  26. package/src/layouts/workspace/Stage.vue +198 -219
  27. package/src/layouts/workspace/ViewerMenu.vue +140 -147
  28. package/src/layouts/workspace/Workspace.vue +14 -12
  29. package/src/services/BaseService.ts +5 -10
  30. package/src/services/componentList.ts +3 -3
  31. package/src/services/editor.ts +208 -147
  32. package/src/services/events.ts +1 -1
  33. package/src/services/history.ts +1 -1
  34. package/src/services/props.ts +74 -49
  35. package/src/services/storage.ts +133 -0
  36. package/src/services/ui.ts +10 -4
  37. package/src/theme/component-list-panel.scss +5 -0
  38. package/src/theme/framework.scss +1 -1
  39. package/src/theme/stage.scss +2 -2
  40. package/src/type.ts +25 -7
  41. package/src/utils/config.ts +1 -1
  42. package/src/utils/editor.ts +20 -12
  43. package/src/utils/operator.ts +109 -0
  44. package/src/utils/props.ts +2 -27
  45. package/src/utils/scroll-viewer.ts +1 -1
  46. package/tsconfig.build.json +13 -0
  47. package/{dist/types/src → types}/Editor.vue.d.ts +17 -9
  48. package/types/components/ContentMenu.vue.d.ts +116 -0
  49. package/types/components/Icon.vue.d.ts +13 -0
  50. package/types/components/ScrollViewer.vue.d.ts +22 -0
  51. package/types/components/ToolButton.vue.d.ts +109 -0
  52. package/types/fields/Code.vue.d.ts +23 -0
  53. package/{dist/types/src → types}/fields/CodeLink.vue.d.ts +4 -6
  54. package/types/fields/UISelect.vue.d.ts +83 -0
  55. package/{dist/types/src → types}/index.d.ts +4 -1
  56. package/types/layouts/AddPageBox.vue.d.ts +4 -0
  57. package/{dist/types/src → types}/layouts/CodeEditor.vue.d.ts +11 -13
  58. package/types/layouts/Framework.vue.d.ts +22 -0
  59. package/types/layouts/NavMenu.vue.d.ts +25 -0
  60. package/types/layouts/PropsPanel.vue.d.ts +245 -0
  61. package/types/layouts/Resizer.vue.d.ts +12 -0
  62. package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +14 -0
  63. package/types/layouts/sidebar/LayerMenu.vue.d.ts +100 -0
  64. package/types/layouts/sidebar/LayerPanel.vue.d.ts +1101 -0
  65. package/{dist/types/src → types}/layouts/sidebar/Sidebar.vue.d.ts +3 -5
  66. package/types/layouts/sidebar/TabPane.vue.d.ts +14 -0
  67. package/types/layouts/workspace/PageBar.vue.d.ts +54 -0
  68. package/types/layouts/workspace/PageBarScrollContainer.vue.d.ts +48 -0
  69. package/types/layouts/workspace/Stage.vue.d.ts +46 -0
  70. package/types/layouts/workspace/ViewerMenu.vue.d.ts +88 -0
  71. package/types/layouts/workspace/Workspace.vue.d.ts +6 -0
  72. package/{dist/types/src → types}/services/BaseService.d.ts +0 -0
  73. package/types/services/componentList.d.ts +14 -0
  74. package/{dist/types/src → types}/services/editor.d.ts +22 -14
  75. package/{dist/types/src → types}/services/events.d.ts +0 -0
  76. package/{dist/types/src → types}/services/history.d.ts +0 -0
  77. package/types/services/props.d.ts +97 -0
  78. package/types/services/storage.d.ts +56 -0
  79. package/{dist/types/src → types}/services/ui.d.ts +1 -1
  80. package/{dist/types/src → types}/type.d.ts +18 -1
  81. package/{dist/types/src → types}/utils/compose.d.ts +1 -1
  82. package/{dist/types/src → types}/utils/config.d.ts +0 -0
  83. package/{dist/types/src → types}/utils/editor.d.ts +6 -3
  84. package/{dist/types/src → types}/utils/index.d.ts +0 -0
  85. package/{dist/types/src → types}/utils/logger.d.ts +0 -0
  86. package/types/utils/operator.d.ts +21 -0
  87. package/{dist/types/src → types}/utils/polyfills.d.ts +0 -0
  88. package/{dist/types/src → types}/utils/props.d.ts +2 -23
  89. package/{dist/types/src → types}/utils/scroll-viewer.d.ts +0 -0
  90. package/{dist/types/src → types}/utils/undo-redo.d.ts +0 -0
  91. package/dist/tmagic-editor.es.js.map +0 -1
  92. package/dist/types/src/components/Icon.vue.d.ts +0 -14
  93. package/dist/types/src/components/ScrollViewer.vue.d.ts +0 -24
  94. package/dist/types/src/fields/Code.vue.d.ts +0 -25
  95. package/dist/types/src/layouts/AddPageBox.vue.d.ts +0 -5
  96. package/dist/types/src/layouts/Framework.vue.d.ts +0 -24
  97. package/dist/types/src/layouts/NavMenu.vue.d.ts +0 -27
  98. package/dist/types/src/layouts/PropsPanel.vue.d.ts +0 -13
  99. package/dist/types/src/layouts/Resizer.vue.d.ts +0 -14
  100. package/dist/types/src/layouts/sidebar/ComponentListPanel.vue.d.ts +0 -16
  101. package/dist/types/src/layouts/sidebar/TabPane.vue.d.ts +0 -16
  102. package/dist/types/src/layouts/workspace/Workspace.vue.d.ts +0 -8
  103. package/dist/types/src/services/props.d.ts +0 -42
  104. package/dist/types/src/shims-vue.d.ts +0 -6
  105. package/dist/types/src/vite-env.d.ts +0 -1
  106. package/src/vite-env.d.ts +0 -1
@@ -17,15 +17,14 @@
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
- import type { Id, MComponent, MNode, MPage } from '@tmagic/schema';
24
- import { NodeType } from '@tmagic/schema';
25
- import { isPop, toLine } from '@tmagic/utils';
23
+ import type { MComponent, MNode } from '@tmagic/schema';
24
+ import { toLine } from '@tmagic/utils';
26
25
 
27
- import type { PropsState } from '@editor/type';
28
- import { DEFAULT_CONFIG, fillConfig, getDefaultPropsValue } from '@editor/utils/props';
26
+ import type { PropsState } from '../type';
27
+ import { fillConfig } from '../utils/props';
29
28
 
30
29
  import BaseService from './BaseService';
31
30
 
@@ -36,7 +35,16 @@ class Props extends BaseService {
36
35
  });
37
36
 
38
37
  constructor() {
39
- super(['setPropsConfig', 'getPropsConfig', 'setPropsValue', 'getPropsValue', 'createId', 'setNewItemId']);
38
+ super([
39
+ 'setPropsConfig',
40
+ 'getPropsConfig',
41
+ 'setPropsValue',
42
+ 'getPropsValue',
43
+ 'createId',
44
+ 'setNewItemId',
45
+ 'fillConfig',
46
+ 'getDefaultPropsValue',
47
+ ]);
40
48
  }
41
49
 
42
50
  public setPropsConfigs(configs: Record<string, FormConfig>) {
@@ -46,13 +54,17 @@ class Props extends BaseService {
46
54
  this.emit('props-configs-change');
47
55
  }
48
56
 
57
+ public async fillConfig(config: FormConfig) {
58
+ return fillConfig(config);
59
+ }
60
+
49
61
  /**
50
62
  * 为指定类型组件设置组件属性表单配置
51
63
  * @param type 组件类型
52
64
  * @param config 组件属性表单配置
53
65
  */
54
- public setPropsConfig(type: string, config: FormConfig) {
55
- this.state.propsConfigMap[type] = fillConfig(Array.isArray(config) ? config : [config]);
66
+ public async setPropsConfig(type: string, config: FormConfig) {
67
+ this.state.propsConfigMap[type] = await this.fillConfig(Array.isArray(config) ? config : [config]);
56
68
  }
57
69
 
58
70
  /**
@@ -65,7 +77,7 @@ class Props extends BaseService {
65
77
  return await this.getPropsConfig('button');
66
78
  }
67
79
 
68
- return cloneDeep(this.state.propsConfigMap[type] || DEFAULT_CONFIG);
80
+ return cloneDeep(this.state.propsConfigMap[type] || (await this.fillConfig([])));
69
81
  }
70
82
 
71
83
  public setPropsValues(values: Record<string, MNode>) {
@@ -88,7 +100,7 @@ class Props extends BaseService {
88
100
  * @param type 组件类型
89
101
  * @returns 组件初始值
90
102
  */
91
- public async getPropsValue(type: string, defaultValue: Record<string, any> = {}) {
103
+ public async getPropsValue(type: string, { inputEvent, ...defaultValue }: Record<string, any> = {}) {
92
104
  if (type === 'area') {
93
105
  const value = (await this.getPropsValue('button')) as MComponent;
94
106
  value.className = 'action-area';
@@ -99,18 +111,26 @@ class Props extends BaseService {
99
111
  return value;
100
112
  }
101
113
 
102
- const data = cloneDeep(defaultValue as any);
103
-
104
- await this.setNewItemId(data);
114
+ const [id, defaultPropsValue, data] = await Promise.all([
115
+ this.createId(type),
116
+ this.getDefaultPropsValue(type),
117
+ this.setNewItemId(
118
+ cloneDeep({
119
+ type,
120
+ ...defaultValue,
121
+ } as any),
122
+ ),
123
+ ]);
105
124
 
106
125
  return {
107
- ...getDefaultPropsValue(type, await this.createId(type)),
108
- ...mergeWith(cloneDeep(this.state.propsValueMap[type] || {}), data),
126
+ id,
127
+ ...defaultPropsValue,
128
+ ...mergeWith({}, cloneDeep(this.state.propsValueMap[type] || {}), data),
109
129
  };
110
130
  }
111
131
 
112
132
  public async createId(type: string | number): Promise<string> {
113
- return `${type}_${random(10000, false)}`;
133
+ return `${type}_${this.guid()}`;
114
134
  }
115
135
 
116
136
  /**
@@ -118,47 +138,52 @@ class Props extends BaseService {
118
138
  * @param {Object} config 组件配置
119
139
  */
120
140
  /* eslint no-param-reassign: ["error", { "props": false }] */
121
- public async setNewItemId(config: MNode, parent?: MPage) {
122
- const oldId = config.id;
123
-
141
+ public async setNewItemId(config: MNode) {
124
142
  config.id = await this.createId(config.type || 'component');
125
143
 
126
- // 只有弹窗在页面下的一级子元素才有效
127
- if (isPop(config) && parent?.type === NodeType.PAGE) {
128
- updatePopId(oldId, config.id, parent);
129
- }
130
-
131
144
  if (config.items && Array.isArray(config.items)) {
132
145
  for (const item of config.items) {
133
- await this.setNewItemId(item, config as MPage);
146
+ await this.setNewItemId(item);
134
147
  }
135
148
  }
136
- }
137
- }
138
149
 
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
- }
150
+ return config;
151
+ }
151
152
 
152
- if (config.popId === oldId) {
153
- config.popId = popId;
154
- return;
155
- }
153
+ /**
154
+ * 获取默认属性配置
155
+ * @param type 组件类型
156
+ * @returns Object
157
+ */
158
+ public async getDefaultPropsValue(type: string) {
159
+ return ['page', 'container'].includes(type)
160
+ ? {
161
+ type,
162
+ layout: 'absolute',
163
+ style: {},
164
+ name: type,
165
+ items: [],
166
+ }
167
+ : {
168
+ type,
169
+ style: {},
170
+ name: type,
171
+ };
172
+ }
156
173
 
157
- if (Array.isArray(config.items)) {
158
- updatePopId(oldId, popId, config as MPage);
159
- }
160
- });
161
- };
174
+ /**
175
+ * 生成指定位数的GUID,无【-】格式
176
+ * @param digit 位数,默认值8
177
+ * @returns
178
+ */
179
+ private guid(digit = 8): string {
180
+ return 'x'.repeat(digit).replace(/[xy]/g, (c) => {
181
+ const r = (Math.random() * 16) | 0;
182
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
183
+ return v.toString(16);
184
+ });
185
+ }
186
+ }
162
187
 
163
188
  export type PropsService = Props;
164
189
 
@@ -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
 
@@ -155,10 +155,16 @@ class Ui extends BaseService {
155
155
  const { stageRect, stageContainerRect } = state;
156
156
  const { height, width } = stageContainerRect;
157
157
  if (!width || !height) return 1;
158
- if (width > stageRect.width && height > stageRect.height) {
158
+
159
+ // 30为标尺的大小
160
+ const stageWidth = stageRect.width + 30;
161
+ const stageHeight = stageRect.height + 30;
162
+
163
+ if (width > stageWidth && height > stageHeight) {
159
164
  return 1;
160
165
  }
161
- return Math.min((width - 100) / stageRect.width || 1, (height - 100) / stageRect.height || 1);
166
+ // 60/80是为了不要让画布太过去贴住四周(这样好看些)
167
+ return Math.min((width - 60) / stageWidth || 1, (height - 80) / stageHeight || 1);
162
168
  }
163
169
  }
164
170
 
@@ -86,6 +86,11 @@
86
86
  }
87
87
  }
88
88
 
89
+ img {
90
+ max-width: 100%;
91
+ max-height: 100%;
92
+ }
93
+
89
94
  span {
90
95
  font-size: 12px;
91
96
  text-align: center;
@@ -35,7 +35,7 @@
35
35
  justify-content: center;
36
36
  align-items: center;
37
37
  flex-direction: column;
38
- height: calc(100% - $--page-bar-height);
38
+ height: calc(100% - #{$--page-bar-height});
39
39
  }
40
40
 
41
41
  &-content {
@@ -1,7 +1,7 @@
1
1
  .m-editor-stage {
2
2
  position: relative;
3
3
  width: 100%;
4
- height: calc(100% - $--page-bar-height);
4
+ height: calc(100% - #{$--page-bar-height});
5
5
  overflow: hidden;
6
6
  display: flex;
7
7
  justify-content: center;
@@ -13,9 +13,9 @@
13
13
  height: 100%;
14
14
  z-index: 0;
15
15
  position: relative;
16
- border: 1px solid $--border-color;
17
16
  transition: transform 0.3s;
18
17
  box-sizing: content-box;
18
+ box-shadow: rgba(0, 0, 0, 0.04) 0px 3px 5px;
19
19
 
20
20
  &::-webkit-scrollbar {
21
21
  width: 0 !important;
package/src/type.ts CHANGED
@@ -21,14 +21,15 @@ import type { Component } from 'vue';
21
21
  import type { FormConfig } from '@tmagic/form';
22
22
  import type { Id, MApp, MContainer, MNode, MPage } from '@tmagic/schema';
23
23
  import type StageCore from '@tmagic/stage';
24
- import type { MoveableOptions } from '@tmagic/stage';
24
+ import type { ContainerHighlightType, 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;
@@ -51,6 +53,7 @@ export interface StageOptions {
51
53
  autoScrollIntoView: boolean;
52
54
  containerHighlightClassName: string;
53
55
  containerHighlightDuration: number;
56
+ containerHighlightType: ContainerHighlightType;
54
57
  render: () => HTMLDivElement;
55
58
  moveableOptions: MoveableOptions | ((core?: StageCore) => MoveableOptions);
56
59
  canSelect: (el: HTMLElement) => boolean | Promise<boolean>;
@@ -64,6 +67,7 @@ export interface StoreState {
64
67
  parent: MContainer | null;
65
68
  node: MNode | null;
66
69
  highlightNode: MNode | null;
70
+ nodes: MNode[];
67
71
  stage: StageCore | null;
68
72
  modifiedNodeIds: Map<Id, Id>;
69
73
  pageLength: number;
@@ -125,9 +129,23 @@ export interface EditorNodeInfo {
125
129
  export interface AddMNode {
126
130
  type: string;
127
131
  name?: string;
132
+ inputEvent?: DragEvent;
128
133
  [key: string]: any;
129
134
  }
130
135
 
136
+ export interface PastePosition {
137
+ left?: number;
138
+ top?: number;
139
+ /**
140
+ * 粘贴位置X方向偏移量
141
+ */
142
+ offsetX?: number;
143
+ /**
144
+ * 粘贴位置Y方向偏移量
145
+ */
146
+ offsetY?: number;
147
+ }
148
+
131
149
  /**
132
150
  * 菜单按钮
133
151
  */
@@ -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
 
@@ -88,10 +88,10 @@ export const getRelativeStyle = (style: Record<string, any> = {}): Record<string
88
88
  left: 0,
89
89
  });
90
90
 
91
- const getMiddleTop = (style: Record<string, any> = {}, parentNode: MNode, stage: StageCore) => {
92
- let height = style.height || 0;
91
+ const getMiddleTop = (node: MNode, parentNode: MNode, stage: StageCore | null) => {
92
+ let height = node.style?.height || 0;
93
93
 
94
- if (!stage || typeof style.top !== 'undefined' || !parentNode.style) return style.top;
94
+ if (!stage || typeof node.style?.top !== 'undefined' || !parentNode.style) return node.style?.top;
95
95
 
96
96
  if (!isNumber(height)) {
97
97
  height = 0;
@@ -103,20 +103,15 @@ const getMiddleTop = (style: Record<string, any> = {}, parentNode: MNode, stage:
103
103
  const { scrollTop = 0, wrapperHeight } = stage.mask;
104
104
  return (wrapperHeight - height) / 2 + scrollTop;
105
105
  }
106
+
106
107
  return (parentHeight - height) / 2;
107
108
  };
108
109
 
109
- export const getInitPositionStyle = (
110
- style: Record<string, any> = {},
111
- layout: Layout,
112
- parentNode: MNode,
113
- stage: StageCore,
114
- ) => {
110
+ export const getInitPositionStyle = (style: Record<string, any> = {}, layout: Layout) => {
115
111
  if (layout === Layout.ABSOLUTE) {
116
112
  return {
117
113
  ...style,
118
114
  position: 'absolute',
119
- top: getMiddleTop(style, parentNode, stage),
120
115
  };
121
116
  }
122
117
 
@@ -223,9 +218,22 @@ export const fixNodeLeft = (config: MNode, parent: MContainer, doc?: Document) =
223
218
  const el = doc.getElementById(`${config.id}`);
224
219
  const parentEl = doc.getElementById(`${parent.id}`);
225
220
 
226
- if (el && parentEl && el.offsetWidth + config.style?.left > parentEl.offsetWidth) {
221
+ const left = Number(config.style?.left) || 0;
222
+ if (el && parentEl && el.offsetWidth + left > parentEl.offsetWidth) {
227
223
  return parentEl.offsetWidth - el.offsetWidth;
228
224
  }
229
225
 
230
226
  return config.style.left;
231
227
  };
228
+
229
+ export const fixNodePosition = (config: MNode, parent: MContainer, stage: StageCore | null) => {
230
+ if (config.style?.position !== 'absolute') {
231
+ return config.style;
232
+ }
233
+
234
+ return {
235
+ ...(config.style || {}),
236
+ top: getMiddleTop(config, parent, stage),
237
+ left: fixNodeLeft(config, parent, stage?.renderer.contentWindow?.document),
238
+ };
239
+ };
@@ -0,0 +1,109 @@
1
+ import { toRaw } from 'vue';
2
+ import { isEmpty } from 'lodash-es';
3
+
4
+ import { Id, MApp, MContainer, MNode } from '@tmagic/schema';
5
+ import StageCore from '@tmagic/stage';
6
+ import { isPage } from '@tmagic/utils';
7
+
8
+ import editorService from '../services/editor';
9
+ import propsService from '../services/props';
10
+ import { AddMNode, PastePosition } from '../type';
11
+ import { generatePageNameByApp, getInitPositionStyle } from '../utils/editor';
12
+
13
+ /**
14
+ * 粘贴前置操作:返回分配了新id以及校准了坐标的配置
15
+ * @param position 粘贴的坐标
16
+ * @param config 待粘贴的元素配置(复制时保存的那份配置)
17
+ * @returns
18
+ */
19
+ export const beforePaste = async (position: PastePosition, config: MNode[]): Promise<MNode[]> => {
20
+ if (!config[0]?.style) return config;
21
+ const curNode = editorService.get<MContainer>('node');
22
+ // 将数组中第一个元素的坐标作为参照点
23
+ const { left: referenceLeft, top: referenceTop } = config[0].style;
24
+ // 坐标校准后的粘贴数据
25
+ const pasteConfigs: MNode[] = await Promise.all(
26
+ config.map(async (configItem: MNode): Promise<MNode> => {
27
+ // 解构 position 对象,从 position 删除 offsetX、offsetY字段
28
+ const { offsetX = 0, offsetY = 0, ...positionClone } = position;
29
+ let pastePosition = positionClone;
30
+
31
+ if (!isEmpty(pastePosition) && curNode.items) {
32
+ // 如果没有传入粘贴坐标则可能为键盘操作,不再转换
33
+ // 如果粘贴时选中了容器,则将元素粘贴到容器内,坐标需要转换为相对于容器的坐标
34
+ pastePosition = getPositionInContainer(pastePosition, curNode.id);
35
+ }
36
+
37
+ // 将所有待粘贴元素坐标相对于多选第一个元素坐标重新计算,以保证多选粘贴后元素间距不变
38
+ if (pastePosition.left && configItem.style?.left) {
39
+ pastePosition.left = configItem.style.left - referenceLeft + pastePosition.left;
40
+ }
41
+ if (pastePosition.top && configItem.style?.top) {
42
+ pastePosition.top = configItem.style?.top - referenceTop + pastePosition.top;
43
+ }
44
+
45
+ const pasteConfig = await propsService.setNewItemId(configItem);
46
+
47
+ if (pasteConfig.style) {
48
+ const { left, top } = pasteConfig.style;
49
+ // 判断能转换为数字时,做粘贴偏移量计算
50
+ if (typeof left === 'number' || (!!left && !isNaN(Number(left)))) {
51
+ pasteConfig.style.left = Number(left) + offsetX;
52
+ }
53
+ if (typeof top === 'number' || (!!top && !isNaN(Number(top)))) {
54
+ pasteConfig.style.top = Number(top) + offsetY;
55
+ }
56
+
57
+ pasteConfig.style = {
58
+ ...pasteConfig.style,
59
+ ...pastePosition,
60
+ };
61
+ }
62
+ if (isPage(pasteConfig)) {
63
+ pasteConfig.name = generatePageNameByApp(editorService.get('root'));
64
+ }
65
+ return pasteConfig as MNode;
66
+ }),
67
+ );
68
+ return pasteConfigs;
69
+ };
70
+
71
+ /**
72
+ * 将元素粘贴到容器内时,将相对于画布坐标转换为相对于容器的坐标
73
+ * @param position PastePosition 粘贴时相对于画布的坐标
74
+ * @param id 元素id
75
+ * @returns PastePosition 转换后的坐标
76
+ */
77
+ export const getPositionInContainer = (position: PastePosition = {}, id: Id) => {
78
+ let { left = 0, top = 0 } = position;
79
+ const parentEl = editorService.get<StageCore>('stage')?.renderer?.contentWindow?.document.getElementById(`${id}`);
80
+ const parentElRect = parentEl?.getBoundingClientRect();
81
+ left = left - (parentElRect?.left || 0);
82
+ top = top - (parentElRect?.top || 0);
83
+ return {
84
+ left,
85
+ top,
86
+ };
87
+ };
88
+
89
+ export const getAddParent = (node: MNode) => {
90
+ const curNode = editorService.get<MContainer>('node');
91
+
92
+ let parentNode;
93
+ if (isPage(node)) {
94
+ parentNode = editorService.get<MApp>('root');
95
+ } else if (curNode.items) {
96
+ parentNode = curNode;
97
+ } else {
98
+ parentNode = editorService.getParentById(curNode.id, false);
99
+ }
100
+ return parentNode;
101
+ };
102
+
103
+ export const getDefaultConfig = async (addNode: AddMNode, parentNode: MContainer) => {
104
+ const { type, inputEvent, ...config } = addNode;
105
+ const layout = await editorService.getLayout(toRaw(parentNode), addNode as MNode);
106
+ const newNode = { ...toRaw(await propsService.getPropsValue(type, config)) };
107
+ newNode.style = getInitPositionStyle(newNode.style, layout);
108
+ return newNode;
109
+ };