@tmagic/editor 1.0.6 → 1.1.0-beta.10
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.
- package/dist/style.css +5 -1
- package/dist/{tmagic-editor.es.js → tmagic-editor.mjs} +1522 -1177
- package/dist/tmagic-editor.mjs.map +1 -0
- package/dist/tmagic-editor.umd.js +1606 -1259
- package/dist/tmagic-editor.umd.js.map +1 -1
- package/package.json +22 -20
- package/src/Editor.vue +41 -15
- package/src/components/ContentMenu.vue +87 -99
- package/src/components/Icon.vue +2 -2
- package/src/components/ScrollViewer.vue +2 -2
- package/src/components/ToolButton.vue +151 -168
- package/src/fields/UISelect.vue +49 -70
- package/src/index.ts +2 -1
- package/src/layouts/AddPageBox.vue +3 -3
- package/src/layouts/Framework.vue +1 -1
- package/src/layouts/NavMenu.vue +2 -2
- package/src/layouts/PropsPanel.vue +1 -1
- package/src/layouts/Resizer.vue +1 -1
- package/src/layouts/sidebar/ComponentListPanel.vue +43 -2
- package/src/layouts/sidebar/LayerMenu.vue +3 -3
- package/src/layouts/sidebar/LayerPanel.vue +13 -4
- package/src/layouts/sidebar/Sidebar.vue +1 -1
- package/src/layouts/sidebar/TabPane.vue +3 -3
- package/src/layouts/workspace/PageBar.vue +63 -192
- package/src/layouts/workspace/PageBarScrollContainer.vue +111 -0
- package/src/layouts/workspace/Stage.vue +208 -188
- package/src/layouts/workspace/ViewerMenu.vue +141 -148
- package/src/layouts/workspace/Workspace.vue +11 -11
- package/src/services/BaseService.ts +5 -10
- package/src/services/componentList.ts +3 -3
- package/src/services/editor.ts +247 -140
- package/src/services/events.ts +1 -1
- package/src/services/history.ts +1 -1
- package/src/services/props.ts +63 -11
- package/src/services/storage.ts +133 -0
- package/src/services/ui.ts +10 -4
- package/src/theme/component-list-panel.scss +5 -0
- package/src/theme/framework.scss +1 -1
- package/src/theme/stage.scss +2 -2
- package/src/type.ts +28 -7
- package/src/utils/config.ts +1 -1
- package/src/utils/editor.ts +53 -35
- package/src/utils/operator.ts +109 -0
- package/src/utils/props.ts +2 -24
- package/src/utils/scroll-viewer.ts +1 -1
- package/tsconfig.build.json +13 -0
- package/{dist/types/src → types}/Editor.vue.d.ts +44 -9
- package/types/components/ContentMenu.vue.d.ts +116 -0
- package/types/components/Icon.vue.d.ts +13 -0
- package/types/components/ScrollViewer.vue.d.ts +22 -0
- package/types/components/ToolButton.vue.d.ts +109 -0
- package/types/fields/Code.vue.d.ts +23 -0
- package/{dist/types/src → types}/fields/CodeLink.vue.d.ts +4 -6
- package/types/fields/UISelect.vue.d.ts +83 -0
- package/{dist/types/src → types}/index.d.ts +4 -1
- package/types/layouts/AddPageBox.vue.d.ts +4 -0
- package/{dist/types/src → types}/layouts/CodeEditor.vue.d.ts +11 -13
- package/types/layouts/Framework.vue.d.ts +22 -0
- package/types/layouts/NavMenu.vue.d.ts +25 -0
- package/types/layouts/PropsPanel.vue.d.ts +245 -0
- package/types/layouts/Resizer.vue.d.ts +12 -0
- package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +14 -0
- package/types/layouts/sidebar/LayerMenu.vue.d.ts +100 -0
- package/types/layouts/sidebar/LayerPanel.vue.d.ts +1099 -0
- package/{dist/types/src → types}/layouts/sidebar/Sidebar.vue.d.ts +3 -5
- package/types/layouts/sidebar/TabPane.vue.d.ts +14 -0
- package/types/layouts/workspace/PageBar.vue.d.ts +54 -0
- package/types/layouts/workspace/PageBarScrollContainer.vue.d.ts +48 -0
- package/types/layouts/workspace/Stage.vue.d.ts +46 -0
- package/types/layouts/workspace/ViewerMenu.vue.d.ts +88 -0
- package/types/layouts/workspace/Workspace.vue.d.ts +6 -0
- package/{dist/types/src → types}/services/BaseService.d.ts +0 -0
- package/types/services/componentList.d.ts +14 -0
- package/{dist/types/src → types}/services/editor.d.ts +27 -14
- package/{dist/types/src → types}/services/events.d.ts +0 -0
- package/{dist/types/src → types}/services/history.d.ts +0 -0
- package/{dist/types/src → types}/services/props.d.ts +26 -2
- package/types/services/storage.d.ts +56 -0
- package/{dist/types/src → types}/services/ui.d.ts +1 -1
- package/{dist/types/src → types}/type.d.ts +21 -1
- package/{dist/types/src → types}/utils/compose.d.ts +1 -1
- package/{dist/types/src → types}/utils/config.d.ts +0 -0
- package/{dist/types/src → types}/utils/editor.d.ts +11 -4
- package/{dist/types/src → types}/utils/index.d.ts +0 -0
- package/{dist/types/src → types}/utils/logger.d.ts +0 -0
- package/types/utils/operator.d.ts +21 -0
- package/{dist/types/src → types}/utils/polyfills.d.ts +0 -0
- package/{dist/types/src → types}/utils/props.d.ts +2 -22
- package/{dist/types/src → types}/utils/scroll-viewer.d.ts +0 -0
- package/{dist/types/src → types}/utils/undo-redo.d.ts +0 -0
- package/dist/tmagic-editor.es.js.map +0 -1
- package/dist/types/src/components/Icon.vue.d.ts +0 -14
- package/dist/types/src/components/ScrollViewer.vue.d.ts +0 -24
- package/dist/types/src/fields/Code.vue.d.ts +0 -25
- package/dist/types/src/layouts/AddPageBox.vue.d.ts +0 -5
- package/dist/types/src/layouts/Framework.vue.d.ts +0 -24
- package/dist/types/src/layouts/NavMenu.vue.d.ts +0 -27
- package/dist/types/src/layouts/PropsPanel.vue.d.ts +0 -13
- package/dist/types/src/layouts/Resizer.vue.d.ts +0 -14
- package/dist/types/src/layouts/sidebar/ComponentListPanel.vue.d.ts +0 -14
- package/dist/types/src/layouts/sidebar/TabPane.vue.d.ts +0 -16
- package/dist/types/src/layouts/workspace/Workspace.vue.d.ts +0 -8
- package/dist/types/src/shims-vue.d.ts +0 -6
- package/dist/types/src/vite-env.d.ts +0 -1
- package/src/vite-env.d.ts +0 -1
package/src/services/props.ts
CHANGED
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { reactive } from 'vue';
|
|
20
|
-
import { cloneDeep, mergeWith
|
|
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 '
|
|
28
|
-
import { DEFAULT_CONFIG, fillConfig
|
|
27
|
+
import type { PropsState } from '../type';
|
|
28
|
+
import { DEFAULT_CONFIG, fillConfig } from '../utils/props';
|
|
29
29
|
|
|
30
30
|
import BaseService from './BaseService';
|
|
31
31
|
|
|
@@ -36,7 +36,15 @@ class Props extends BaseService {
|
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
constructor() {
|
|
39
|
-
super([
|
|
39
|
+
super([
|
|
40
|
+
'setPropsConfig',
|
|
41
|
+
'getPropsConfig',
|
|
42
|
+
'setPropsValue',
|
|
43
|
+
'getPropsValue',
|
|
44
|
+
'createId',
|
|
45
|
+
'setNewItemId',
|
|
46
|
+
'getDefaultPropsValue',
|
|
47
|
+
]);
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
public setPropsConfigs(configs: Record<string, FormConfig>) {
|
|
@@ -88,7 +96,7 @@ class Props extends BaseService {
|
|
|
88
96
|
* @param type 组件类型
|
|
89
97
|
* @returns 组件初始值
|
|
90
98
|
*/
|
|
91
|
-
public async getPropsValue(type: string, defaultValue: Record<string, any> = {}) {
|
|
99
|
+
public async getPropsValue(type: string, { inputEvent, ...defaultValue }: Record<string, any> = {}) {
|
|
92
100
|
if (type === 'area') {
|
|
93
101
|
const value = (await this.getPropsValue('button')) as MComponent;
|
|
94
102
|
value.className = 'action-area';
|
|
@@ -99,18 +107,39 @@ class Props extends BaseService {
|
|
|
99
107
|
return value;
|
|
100
108
|
}
|
|
101
109
|
|
|
102
|
-
const data =
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
const [id, defaultPropsValue, data] = await Promise.all([
|
|
111
|
+
this.createId(type),
|
|
112
|
+
this.getDefaultPropsValue(type),
|
|
113
|
+
this.setNewItemId(
|
|
114
|
+
cloneDeep({
|
|
115
|
+
type,
|
|
116
|
+
...defaultValue,
|
|
117
|
+
} as any),
|
|
118
|
+
),
|
|
119
|
+
]);
|
|
105
120
|
|
|
106
121
|
return {
|
|
107
|
-
|
|
108
|
-
...
|
|
122
|
+
id,
|
|
123
|
+
...defaultPropsValue,
|
|
124
|
+
...mergeWith({}, cloneDeep(this.state.propsValueMap[type] || {}), data),
|
|
109
125
|
};
|
|
110
126
|
}
|
|
111
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
|
+
|
|
112
141
|
public async createId(type: string | number): Promise<string> {
|
|
113
|
-
return `${type}_${
|
|
142
|
+
return `${type}_${this.guid()}`;
|
|
114
143
|
}
|
|
115
144
|
|
|
116
145
|
/**
|
|
@@ -133,6 +162,29 @@ class Props extends BaseService {
|
|
|
133
162
|
await this.setNewItemId(item, config as MPage);
|
|
134
163
|
}
|
|
135
164
|
}
|
|
165
|
+
|
|
166
|
+
return config;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 获取默认属性配置
|
|
171
|
+
* @param type 组件类型
|
|
172
|
+
* @returns Object
|
|
173
|
+
*/
|
|
174
|
+
public async getDefaultPropsValue(type: string) {
|
|
175
|
+
return ['page', 'container'].includes(type)
|
|
176
|
+
? {
|
|
177
|
+
type,
|
|
178
|
+
layout: 'absolute',
|
|
179
|
+
style: {},
|
|
180
|
+
name: type,
|
|
181
|
+
items: [],
|
|
182
|
+
}
|
|
183
|
+
: {
|
|
184
|
+
type,
|
|
185
|
+
style: {},
|
|
186
|
+
name: type,
|
|
187
|
+
};
|
|
136
188
|
}
|
|
137
189
|
}
|
|
138
190
|
|
|
@@ -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();
|
package/src/services/ui.ts
CHANGED
|
@@ -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 '
|
|
24
|
-
import type { GetColumnWidth, SetColumnWidth, StageRect, UiState } from '
|
|
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
|
-
|
|
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
|
-
|
|
166
|
+
// 60/80是为了不要让画布太过去贴住四周(这样好看些)
|
|
167
|
+
return Math.min((width - 60) / stageWidth || 1, (height - 80) / stageHeight || 1);
|
|
162
168
|
}
|
|
163
169
|
}
|
|
164
170
|
|
package/src/theme/framework.scss
CHANGED
package/src/theme/stage.scss
CHANGED
|
@@ -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 '
|
|
27
|
-
import type { EditorService } from '
|
|
28
|
-
import type { EventsService } from '
|
|
29
|
-
import type { HistoryService } from '
|
|
30
|
-
import type { PropsService } from '
|
|
31
|
-
import type {
|
|
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;
|
|
@@ -49,9 +51,13 @@ export interface Services {
|
|
|
49
51
|
export interface StageOptions {
|
|
50
52
|
runtimeUrl: string;
|
|
51
53
|
autoScrollIntoView: boolean;
|
|
54
|
+
containerHighlightClassName: string;
|
|
55
|
+
containerHighlightDuration: number;
|
|
56
|
+
containerHighlightType: ContainerHighlightType;
|
|
52
57
|
render: () => HTMLDivElement;
|
|
53
58
|
moveableOptions: MoveableOptions | ((core?: StageCore) => MoveableOptions);
|
|
54
59
|
canSelect: (el: HTMLElement) => boolean | Promise<boolean>;
|
|
60
|
+
isContainer: (el: HTMLElement) => boolean | Promise<boolean>;
|
|
55
61
|
updateDragEl: (el: HTMLDivElement) => void;
|
|
56
62
|
}
|
|
57
63
|
|
|
@@ -61,6 +67,7 @@ export interface StoreState {
|
|
|
61
67
|
parent: MContainer | null;
|
|
62
68
|
node: MNode | null;
|
|
63
69
|
highlightNode: MNode | null;
|
|
70
|
+
nodes: MNode[];
|
|
64
71
|
stage: StageCore | null;
|
|
65
72
|
modifiedNodeIds: Map<Id, Id>;
|
|
66
73
|
pageLength: number;
|
|
@@ -122,9 +129,23 @@ export interface EditorNodeInfo {
|
|
|
122
129
|
export interface AddMNode {
|
|
123
130
|
type: string;
|
|
124
131
|
name?: string;
|
|
132
|
+
inputEvent?: DragEvent;
|
|
125
133
|
[key: string]: any;
|
|
126
134
|
}
|
|
127
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
|
+
|
|
128
149
|
/**
|
|
129
150
|
* 菜单按钮
|
|
130
151
|
*/
|
package/src/utils/config.ts
CHANGED
package/src/utils/editor.ts
CHANGED
|
@@ -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 '
|
|
24
|
+
import { Layout } from '../type';
|
|
25
25
|
|
|
26
26
|
export const COPY_STORAGE_KEY = '$MagicEditorCopyData';
|
|
27
27
|
|
|
@@ -81,21 +81,17 @@ export const getNodeIndex = (node: MNode, parent: MContainer | MApp): number =>
|
|
|
81
81
|
return items.findIndex((item: MNode) => `${item.id}` === `${node.id}`);
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
export const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
};
|
|
91
|
-
return node;
|
|
92
|
-
};
|
|
84
|
+
export const getRelativeStyle = (style: Record<string, any> = {}): Record<string, any> => ({
|
|
85
|
+
...style,
|
|
86
|
+
position: 'relative',
|
|
87
|
+
top: 0,
|
|
88
|
+
left: 0,
|
|
89
|
+
});
|
|
93
90
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
let height = style.height || 0;
|
|
91
|
+
const getMiddleTop = (node: MNode, parentNode: MNode, stage: StageCore | null) => {
|
|
92
|
+
let height = node.style?.height || 0;
|
|
97
93
|
|
|
98
|
-
if (!stage || typeof style
|
|
94
|
+
if (!stage || typeof node.style?.top !== 'undefined' || !parentNode.style) return node.style?.top;
|
|
99
95
|
|
|
100
96
|
if (!isNumber(height)) {
|
|
101
97
|
height = 0;
|
|
@@ -105,43 +101,40 @@ const setTop2Middle = (node: MNode, parentNode: MNode, stage: StageCore) => {
|
|
|
105
101
|
|
|
106
102
|
if (isPage(parentNode)) {
|
|
107
103
|
const { scrollTop = 0, wrapperHeight } = stage.mask;
|
|
108
|
-
|
|
109
|
-
} else {
|
|
110
|
-
style.top = (parentHeight - height) / 2;
|
|
104
|
+
return (wrapperHeight - height) / 2 + scrollTop;
|
|
111
105
|
}
|
|
112
106
|
|
|
113
|
-
return
|
|
107
|
+
return (parentHeight - height) / 2;
|
|
114
108
|
};
|
|
115
109
|
|
|
116
|
-
export const
|
|
110
|
+
export const getInitPositionStyle = (style: Record<string, any> = {}, layout: Layout) => {
|
|
117
111
|
if (layout === Layout.ABSOLUTE) {
|
|
118
|
-
|
|
112
|
+
return {
|
|
113
|
+
...style,
|
|
119
114
|
position: 'absolute',
|
|
120
|
-
...setTop2Middle(node, parentNode, stage),
|
|
121
115
|
};
|
|
122
|
-
return node;
|
|
123
116
|
}
|
|
124
117
|
|
|
125
118
|
if (layout === Layout.RELATIVE) {
|
|
126
|
-
return
|
|
119
|
+
return getRelativeStyle(style);
|
|
127
120
|
}
|
|
128
121
|
|
|
129
|
-
return
|
|
122
|
+
return style;
|
|
130
123
|
};
|
|
131
124
|
|
|
132
125
|
export const setLayout = (node: MNode, layout: Layout) => {
|
|
133
126
|
node.items?.forEach((child: MNode) => {
|
|
134
127
|
if (isPop(child)) return;
|
|
135
128
|
|
|
136
|
-
|
|
129
|
+
const style = child.style || {};
|
|
137
130
|
|
|
138
131
|
// 是 fixed 不做处理
|
|
139
|
-
if (
|
|
132
|
+
if (style.position === 'fixed') return;
|
|
140
133
|
|
|
141
134
|
if (layout !== Layout.RELATIVE) {
|
|
142
|
-
|
|
135
|
+
style.position = 'absolute';
|
|
143
136
|
} else {
|
|
144
|
-
|
|
137
|
+
child.style = getRelativeStyle(style);
|
|
145
138
|
child.style.right = 'auto';
|
|
146
139
|
child.style.bottom = 'auto';
|
|
147
140
|
}
|
|
@@ -161,11 +154,10 @@ export const change2Fixed = (node: MNode, root: MApp) => {
|
|
|
161
154
|
offset.top = offset.top + globalThis.parseFloat(value.style?.top || 0);
|
|
162
155
|
});
|
|
163
156
|
|
|
164
|
-
|
|
157
|
+
return {
|
|
165
158
|
...(node.style || {}),
|
|
166
159
|
...offset,
|
|
167
160
|
};
|
|
168
|
-
return node;
|
|
169
161
|
};
|
|
170
162
|
|
|
171
163
|
export const Fixed2Other = async (
|
|
@@ -186,23 +178,23 @@ export const Fixed2Other = async (
|
|
|
186
178
|
offset.left = offset.left - globalThis.parseFloat(value.style?.left || 0);
|
|
187
179
|
offset.top = offset.top - globalThis.parseFloat(value.style?.top || 0);
|
|
188
180
|
});
|
|
181
|
+
const style = node.style || {};
|
|
189
182
|
|
|
190
183
|
const parent = path.pop();
|
|
191
184
|
if (!parent) {
|
|
192
|
-
return
|
|
185
|
+
return getRelativeStyle(style);
|
|
193
186
|
}
|
|
194
187
|
|
|
195
188
|
const layout = await getLayout(parent);
|
|
196
189
|
if (layout !== Layout.RELATIVE) {
|
|
197
|
-
|
|
198
|
-
...
|
|
190
|
+
return {
|
|
191
|
+
...style,
|
|
199
192
|
...offset,
|
|
200
193
|
position: 'absolute',
|
|
201
194
|
};
|
|
202
|
-
return node;
|
|
203
195
|
}
|
|
204
196
|
|
|
205
|
-
return
|
|
197
|
+
return getRelativeStyle(style);
|
|
206
198
|
};
|
|
207
199
|
|
|
208
200
|
export const getGuideLineFromCache = (key: string): number[] => {
|
|
@@ -219,3 +211,29 @@ export const getGuideLineFromCache = (key: string): number[] => {
|
|
|
219
211
|
|
|
220
212
|
return [];
|
|
221
213
|
};
|
|
214
|
+
|
|
215
|
+
export const fixNodeLeft = (config: MNode, parent: MContainer, doc?: Document) => {
|
|
216
|
+
if (!doc || !config.style || !isNumber(config.style.left)) return config.style?.left;
|
|
217
|
+
|
|
218
|
+
const el = doc.getElementById(`${config.id}`);
|
|
219
|
+
const parentEl = doc.getElementById(`${parent.id}`);
|
|
220
|
+
|
|
221
|
+
const left = Number(config.style?.left) || 0;
|
|
222
|
+
if (el && parentEl && el.offsetWidth + left > parentEl.offsetWidth) {
|
|
223
|
+
return parentEl.offsetWidth - el.offsetWidth;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return config.style.left;
|
|
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, editorService.get('root'));
|
|
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
|
+
};
|