@tmagic/stage 1.0.0-beta.1
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/tmagic-stage.es.js +18045 -0
- package/dist/tmagic-stage.es.js.map +1 -0
- package/dist/tmagic-stage.umd.js +18062 -0
- package/dist/tmagic-stage.umd.js.map +1 -0
- package/dist/types/src/StageCore.d.ts +44 -0
- package/dist/types/src/StageDragResize.d.ts +63 -0
- package/dist/types/src/StageMask.d.ts +79 -0
- package/dist/types/src/StageRender.d.ts +28 -0
- package/dist/types/src/const.d.ts +2 -0
- package/dist/types/src/index.d.ts +7 -0
- package/dist/types/src/types.d.ts +84 -0
- package/dist/types/src/util.d.ts +18 -0
- package/package.json +40 -0
- package/src/StageCore.ts +222 -0
- package/src/StageDragResize.ts +471 -0
- package/src/StageMask.ts +308 -0
- package/src/StageRender.ts +121 -0
- package/src/const.ts +23 -0
- package/src/index.ts +26 -0
- package/src/logger.ts +37 -0
- package/src/types.ts +121 -0
- package/src/util.ts +120 -0
- package/tsconfig.json +9 -0
- package/vite.config.ts +27 -0
package/src/StageMask.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { EventEmitter } from 'events';
|
|
20
|
+
|
|
21
|
+
import Guides from '@scena/guides';
|
|
22
|
+
import { isNumber } from 'lodash';
|
|
23
|
+
|
|
24
|
+
import { GHOST_EL_ID_PREFIX } from './const';
|
|
25
|
+
import type StageCore from './StageCore';
|
|
26
|
+
import type { CanSelect, StageMaskConfig } from './types';
|
|
27
|
+
import { MouseButton, ZIndex } from './types';
|
|
28
|
+
|
|
29
|
+
const wrapperClassName = 'editor-mask-wrapper';
|
|
30
|
+
|
|
31
|
+
const hideScrollbar = () => {
|
|
32
|
+
const style = globalThis.document.createElement('style');
|
|
33
|
+
style.innerHTML = `
|
|
34
|
+
.${wrapperClassName}::-webkit-scrollbar { width: 0 !important; display: none }
|
|
35
|
+
`;
|
|
36
|
+
globalThis.document.head.appendChild(style);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const createContent = (): HTMLDivElement => {
|
|
40
|
+
const el = globalThis.document.createElement('div');
|
|
41
|
+
el.className = 'editor-mask';
|
|
42
|
+
el.style.cssText = `
|
|
43
|
+
position: absolute;
|
|
44
|
+
top: 0;
|
|
45
|
+
left: 0;
|
|
46
|
+
transform: translate3d(0, 0, 0);
|
|
47
|
+
`;
|
|
48
|
+
return el;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const createWrapper = (): HTMLDivElement => {
|
|
52
|
+
const el = globalThis.document.createElement('div');
|
|
53
|
+
el.className = wrapperClassName;
|
|
54
|
+
el.style.cssText = `
|
|
55
|
+
position: absolute;
|
|
56
|
+
top: 0;
|
|
57
|
+
left: 0;
|
|
58
|
+
height: 100%;
|
|
59
|
+
width: 100%;
|
|
60
|
+
overflow: auto;
|
|
61
|
+
z-index: ${ZIndex.MASK};
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
hideScrollbar();
|
|
65
|
+
|
|
66
|
+
return el;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 蒙层
|
|
71
|
+
* @description 用于拦截页面的点击动作,避免点击时触发组件自身动作;在编辑器中点击组件应当是选中组件;
|
|
72
|
+
*/
|
|
73
|
+
export default class StageMask extends EventEmitter {
|
|
74
|
+
public content: HTMLDivElement = createContent();
|
|
75
|
+
public wrapper: HTMLDivElement = createWrapper();
|
|
76
|
+
public core: StageCore;
|
|
77
|
+
public page: HTMLElement | null = null;
|
|
78
|
+
|
|
79
|
+
public hGuides: Guides;
|
|
80
|
+
public vGuides: Guides;
|
|
81
|
+
|
|
82
|
+
private target: Element | null = null;
|
|
83
|
+
private resizeObserver: ResizeObserver | null = null;
|
|
84
|
+
private parentResizeObserver: ResizeObserver | null = null;
|
|
85
|
+
private canSelect: CanSelect;
|
|
86
|
+
|
|
87
|
+
constructor(config: StageMaskConfig) {
|
|
88
|
+
super();
|
|
89
|
+
|
|
90
|
+
this.core = config.core;
|
|
91
|
+
const { config: coreConfig } = config.core;
|
|
92
|
+
|
|
93
|
+
this.canSelect = coreConfig.canSelect || ((el: HTMLElement) => !!el.id);
|
|
94
|
+
this.content.addEventListener('mousedown', this.mouseDownHandler, true);
|
|
95
|
+
this.content.addEventListener('contextmenu', this.contextmenuHandler, true);
|
|
96
|
+
this.wrapper.appendChild(this.content);
|
|
97
|
+
|
|
98
|
+
this.hGuides = this.createGuides('horizontal');
|
|
99
|
+
this.vGuides = this.createGuides('vertical');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 设置成固定定位模式
|
|
104
|
+
*/
|
|
105
|
+
public setFixed(): void {
|
|
106
|
+
this.wrapper.scrollTo({
|
|
107
|
+
top: 0,
|
|
108
|
+
});
|
|
109
|
+
this.wrapper.style.overflow = 'hidden';
|
|
110
|
+
// 要等滚动条滚上去,才刷新选中框
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
this.core.dr.refresh();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 设置成绝对定位模式
|
|
118
|
+
*/
|
|
119
|
+
public setAbsolute(): void {
|
|
120
|
+
this.wrapper.style.overflow = 'auto';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 监听页面大小变化
|
|
125
|
+
* @description 同步页面与mask的大小
|
|
126
|
+
* @param page 页面Dom节点
|
|
127
|
+
*/
|
|
128
|
+
public observe(page: HTMLElement): void {
|
|
129
|
+
if (!page) return;
|
|
130
|
+
|
|
131
|
+
this.page = page;
|
|
132
|
+
this.resizeObserver?.disconnect();
|
|
133
|
+
|
|
134
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
135
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
136
|
+
const [entry] = entries;
|
|
137
|
+
const { clientHeight, clientWidth } = entry.target;
|
|
138
|
+
this.setHeight(clientHeight);
|
|
139
|
+
this.setWidth(clientWidth);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this.resizeObserver.observe(page);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 挂载Dom节点
|
|
148
|
+
* @param el 将蒙层挂载到该Dom节点上
|
|
149
|
+
*/
|
|
150
|
+
public mount(el: HTMLDivElement): void {
|
|
151
|
+
if (!this.content) throw new Error('content 不存在');
|
|
152
|
+
|
|
153
|
+
el.appendChild(this.wrapper);
|
|
154
|
+
|
|
155
|
+
this.parentResizeObserver = new ResizeObserver(() => {
|
|
156
|
+
this.vGuides.resize();
|
|
157
|
+
this.hGuides.resize();
|
|
158
|
+
});
|
|
159
|
+
this.parentResizeObserver.observe(el);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 销毁实例
|
|
164
|
+
*/
|
|
165
|
+
public destroy(): void {
|
|
166
|
+
this.content?.remove();
|
|
167
|
+
this.page = null;
|
|
168
|
+
this.resizeObserver?.disconnect();
|
|
169
|
+
this.parentResizeObserver?.disconnect();
|
|
170
|
+
this.removeAllListeners();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 是否显示标尺
|
|
175
|
+
* @param show 是否显示
|
|
176
|
+
*/
|
|
177
|
+
public showGuides(show = true) {
|
|
178
|
+
this.hGuides.setState({
|
|
179
|
+
showGuides: show,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
this.vGuides.setState({
|
|
183
|
+
showGuides: show,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 是否显示标尺
|
|
189
|
+
* @param show 是否显示
|
|
190
|
+
*/
|
|
191
|
+
public showRule(show = true) {
|
|
192
|
+
this.hGuides.setState({
|
|
193
|
+
rulerStyle: {
|
|
194
|
+
visibility: show ? '' : 'hidden',
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
this.vGuides.setState({
|
|
199
|
+
rulerStyle: {
|
|
200
|
+
visibility: show ? '' : 'hidden',
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 清空所有参考线
|
|
207
|
+
*/
|
|
208
|
+
public clearGuides() {
|
|
209
|
+
this.vGuides.setState({
|
|
210
|
+
defaultGuides: [],
|
|
211
|
+
});
|
|
212
|
+
this.hGuides.setState({
|
|
213
|
+
defaultGuides: [],
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 设置蒙层高度
|
|
219
|
+
* @param height 高度
|
|
220
|
+
*/
|
|
221
|
+
private setHeight(height: number | string): void {
|
|
222
|
+
this.content.style.height = isNumber(height) ? `${height}px` : height;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 设置蒙层宽度
|
|
227
|
+
* @param width 宽度
|
|
228
|
+
*/
|
|
229
|
+
private setWidth(width: number | string): void {
|
|
230
|
+
this.content.style.width = isNumber(width) ? `${width}px` : width;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 点击事件处理函数
|
|
235
|
+
* @param event 事件对象
|
|
236
|
+
*/
|
|
237
|
+
private mouseDownHandler = async (event: MouseEvent): Promise<void> => {
|
|
238
|
+
if (event.button !== MouseButton.LEFT) return;
|
|
239
|
+
|
|
240
|
+
// 点击的对象如果是选中框,则不需要再触发选中了,而可能是拖动行为
|
|
241
|
+
if ((event.target as HTMLDivElement).className.indexOf('moveable-control') !== -1) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.select(event);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
private mouseUpHandler = (): void => {
|
|
249
|
+
this.content?.removeEventListener('mouseup', this.mouseUpHandler, true);
|
|
250
|
+
this.emit('selected', this.target);
|
|
251
|
+
this.target = null;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
private contextmenuHandler = async (event: MouseEvent): Promise<void> => {
|
|
255
|
+
await this.select(event);
|
|
256
|
+
this.mouseUpHandler();
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
private async select(event: MouseEvent) {
|
|
260
|
+
const { renderer, zoom } = this.core;
|
|
261
|
+
|
|
262
|
+
const doc = renderer.contentWindow?.document;
|
|
263
|
+
let x = event.clientX;
|
|
264
|
+
let y = event.clientY;
|
|
265
|
+
|
|
266
|
+
if (renderer.iframe) {
|
|
267
|
+
const rect = renderer.iframe.getClientRects()[0];
|
|
268
|
+
if (rect) {
|
|
269
|
+
x = x - rect.left;
|
|
270
|
+
y = y - rect.top;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const els = doc?.elementsFromPoint(x / zoom, y / zoom) as HTMLElement[];
|
|
275
|
+
|
|
276
|
+
let stopped = false;
|
|
277
|
+
const stop = () => (stopped = true);
|
|
278
|
+
for (const el of els) {
|
|
279
|
+
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, stop))) {
|
|
280
|
+
if (stopped) break;
|
|
281
|
+
|
|
282
|
+
this.emit('select', el, event);
|
|
283
|
+
this.target = el;
|
|
284
|
+
// 如果是右键点击,这里的mouseup事件监听没有效果
|
|
285
|
+
this.content?.addEventListener('mouseup', this.mouseUpHandler, true);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private getGuidesStyle = (type: 'horizontal' | 'vertical') => ({
|
|
292
|
+
position: 'fixed',
|
|
293
|
+
left: type === 'horizontal' ? 0 : '-30px',
|
|
294
|
+
top: type === 'horizontal' ? '-30px' : 0,
|
|
295
|
+
width: type === 'horizontal' ? '100%' : '30px',
|
|
296
|
+
height: type === 'horizontal' ? '30px' : '100%',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
private createGuides = (type: 'horizontal' | 'vertical'): Guides =>
|
|
300
|
+
new Guides(this.wrapper, {
|
|
301
|
+
type,
|
|
302
|
+
displayDragPos: true,
|
|
303
|
+
backgroundColor: '#fff',
|
|
304
|
+
lineColor: '#000',
|
|
305
|
+
textColor: '#000',
|
|
306
|
+
style: this.getGuidesStyle(type),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { EventEmitter } from 'events';
|
|
20
|
+
|
|
21
|
+
import StageCore from './StageCore';
|
|
22
|
+
import type { Runtime, RuntimeWindow, StageRenderConfig } from './types';
|
|
23
|
+
import { getHost, isSameDomain } from './util';
|
|
24
|
+
|
|
25
|
+
export default class StageRender extends EventEmitter {
|
|
26
|
+
/** 组件的js、css执行的环境,直接渲染为当前window,iframe渲染则为iframe.contentWindow */
|
|
27
|
+
public contentWindow: RuntimeWindow | null = null;
|
|
28
|
+
|
|
29
|
+
public runtime: Runtime | null = null;
|
|
30
|
+
|
|
31
|
+
public iframe?: HTMLIFrameElement;
|
|
32
|
+
|
|
33
|
+
public runtimeUrl?: string;
|
|
34
|
+
|
|
35
|
+
public core: StageCore;
|
|
36
|
+
|
|
37
|
+
private render?: (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
|
|
38
|
+
|
|
39
|
+
constructor({ core }: StageRenderConfig) {
|
|
40
|
+
super();
|
|
41
|
+
|
|
42
|
+
this.core = core;
|
|
43
|
+
this.runtimeUrl = core.config.runtimeUrl || '';
|
|
44
|
+
this.render = core.config.render;
|
|
45
|
+
|
|
46
|
+
this.iframe = globalThis.document.createElement('iframe');
|
|
47
|
+
// 同源,直接加载
|
|
48
|
+
this.iframe.src = isSameDomain(this.runtimeUrl) ? this.runtimeUrl : '';
|
|
49
|
+
this.iframe.style.cssText = `
|
|
50
|
+
border: 0;
|
|
51
|
+
width: 100%;
|
|
52
|
+
height: 100%;
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
this.iframe.onload = async () => {
|
|
56
|
+
this.emit('onload');
|
|
57
|
+
if (this.render) {
|
|
58
|
+
const el = await this.render(this.core);
|
|
59
|
+
if (el) {
|
|
60
|
+
this.iframe?.contentDocument?.body?.appendChild(el);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public getMagicApi = () => ({
|
|
67
|
+
onPageElUpdate: (el: HTMLElement) => this.emit('page-el-update', el),
|
|
68
|
+
onRuntimeReady: (runtime: Runtime) => {
|
|
69
|
+
this.runtime = runtime;
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
globalThis.runtime = runtime;
|
|
72
|
+
this.emit('runtime-ready', runtime);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 挂载Dom节点
|
|
78
|
+
* @param el 将页面挂载到该Dom节点上
|
|
79
|
+
*/
|
|
80
|
+
public async mount(el: HTMLDivElement) {
|
|
81
|
+
if (this.iframe) {
|
|
82
|
+
if (!isSameDomain(this.runtimeUrl) && this.runtimeUrl) {
|
|
83
|
+
// 不同域,使用srcdoc发起异步请求,需要目标地址支持跨域
|
|
84
|
+
let html = await fetch(this.runtimeUrl).then((res) => res.text());
|
|
85
|
+
// 使用base, 解决相对路径或绝对路径的问题
|
|
86
|
+
const base = `${location.protocol}//${getHost(this.runtimeUrl)}`;
|
|
87
|
+
html = html.replace('<head>', `<head>\n<base href="${base}">`);
|
|
88
|
+
this.iframe.srcdoc = html;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
el.appendChild<HTMLIFrameElement>(this.iframe);
|
|
92
|
+
|
|
93
|
+
this.contentWindow = this.iframe?.contentWindow as RuntimeWindow;
|
|
94
|
+
|
|
95
|
+
this.contentWindow.magic = this.getMagicApi();
|
|
96
|
+
} else {
|
|
97
|
+
throw Error('mount 失败');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public getRuntime = (): Promise<Runtime> => {
|
|
102
|
+
if (this.runtime) return Promise.resolve(this.runtime);
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
const listener = (runtime: Runtime) => {
|
|
105
|
+
this.off('runtime-ready', listener);
|
|
106
|
+
resolve(runtime);
|
|
107
|
+
};
|
|
108
|
+
this.on('runtime-ready', listener);
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 销毁实例
|
|
114
|
+
*/
|
|
115
|
+
public destroy(): void {
|
|
116
|
+
this.contentWindow = null;
|
|
117
|
+
this.iframe?.remove();
|
|
118
|
+
this.iframe = undefined;
|
|
119
|
+
this.removeAllListeners();
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/const.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// 流式布局下拖动时需要clone一个镜像节点,镜像节点的id前缀
|
|
20
|
+
export const GHOST_EL_ID_PREFIX = 'ghost_el_';
|
|
21
|
+
|
|
22
|
+
// 默认放到缩小倍数
|
|
23
|
+
export const DEFAULT_ZOOM = 1;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import StageCore from './StageCore';
|
|
20
|
+
|
|
21
|
+
export type { MoveableOptions } from 'moveable';
|
|
22
|
+
export { default as StageRender } from './StageRender';
|
|
23
|
+
export { default as StageMask } from './StageMask';
|
|
24
|
+
export { default as StageDragResize } from './StageDragResize';
|
|
25
|
+
export * from './types';
|
|
26
|
+
export default StageCore;
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const log = (...args: any[]) => {
|
|
20
|
+
console.log(...args);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const info = (...args: any[]) => {
|
|
24
|
+
console.log(...args);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const warn = (...args: any[]) => {
|
|
28
|
+
console.warn(...args);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const debug = (...args: any[]) => {
|
|
32
|
+
console.debug(...args);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const error = (...args: any[]) => {
|
|
36
|
+
console.error(...args);
|
|
37
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { MoveableOptions } from 'react-moveable/declaration/types';
|
|
2
|
+
|
|
3
|
+
import { Id, MApp, MNode } from '@tmagic/schema';
|
|
4
|
+
|
|
5
|
+
import StageCore from './StageCore';
|
|
6
|
+
|
|
7
|
+
export type CanSelect = (el: HTMLElement, stop: () => boolean) => boolean | Promise<boolean>;
|
|
8
|
+
|
|
9
|
+
export type StageCoreConfig = {
|
|
10
|
+
/** 需要对齐的dom节点的CSS选择器字符串 */
|
|
11
|
+
snapElementQuerySelector?: string;
|
|
12
|
+
/** 放大倍数,默认1倍 */
|
|
13
|
+
zoom?: number;
|
|
14
|
+
canSelect?: CanSelect;
|
|
15
|
+
moveableOptions?: ((core?: StageCore) => MoveableOptions) | MoveableOptions;
|
|
16
|
+
/** runtime 的HTML地址,可以是一个HTTP地址,如果和编辑器不同域,需要设置跨域,也可以是一个相对或绝对路径 */
|
|
17
|
+
runtimeUrl?: string;
|
|
18
|
+
render?: (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export interface StageRenderConfig {
|
|
22
|
+
core: StageCore;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface StageMaskConfig {
|
|
26
|
+
core: StageCore;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface StageDragResizeConfig {
|
|
30
|
+
core: StageCore;
|
|
31
|
+
container: HTMLElement;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type Rect = {
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
} & Offset;
|
|
38
|
+
|
|
39
|
+
export interface Offset {
|
|
40
|
+
left: number;
|
|
41
|
+
top: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
46
|
+
*
|
|
47
|
+
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
|
48
|
+
*
|
|
49
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
50
|
+
* you may not use this file except in compliance with the License.
|
|
51
|
+
* You may obtain a copy of the License at
|
|
52
|
+
*
|
|
53
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
54
|
+
*
|
|
55
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
56
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
57
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
58
|
+
* See the License for the specific language governing permissions and
|
|
59
|
+
* limitations under the License.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
export interface UpdateEventData {
|
|
63
|
+
el: HTMLElement;
|
|
64
|
+
ghostEl: HTMLElement;
|
|
65
|
+
style: {
|
|
66
|
+
width: number;
|
|
67
|
+
height: number;
|
|
68
|
+
left?: number;
|
|
69
|
+
top?: number;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface SortEventData {
|
|
74
|
+
src: Id;
|
|
75
|
+
dist: Id;
|
|
76
|
+
root?: MApp;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface UpdateData {
|
|
80
|
+
config: MNode;
|
|
81
|
+
root: MApp;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface RemoveData {
|
|
85
|
+
id: Id;
|
|
86
|
+
root: MApp;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface Runtime {
|
|
90
|
+
beforeSelect?: (el: HTMLElement) => Promise<boolean> | boolean;
|
|
91
|
+
getSnapElements?: (el: HTMLElement) => HTMLElement[];
|
|
92
|
+
updateRootConfig: (config: MApp) => void;
|
|
93
|
+
updatePageId?: (id: Id) => void;
|
|
94
|
+
select?: (id: Id) => Promise<HTMLElement> | HTMLElement;
|
|
95
|
+
add?: (data: UpdateData) => void;
|
|
96
|
+
update?: (data: UpdateData) => void;
|
|
97
|
+
sortNode?: (data: SortEventData) => void;
|
|
98
|
+
remove?: (data: RemoveData) => void;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface Magic {
|
|
102
|
+
/** 当前页面的根节点变化时调用该方法,编辑器会同步该el和stage的大小,该方法由stage注入到iframe.contentWindow中 */
|
|
103
|
+
onPageElUpdate: (el: HTMLElement) => void;
|
|
104
|
+
|
|
105
|
+
onRuntimeReady: (runtime: Runtime) => void;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface RuntimeWindow extends Window {
|
|
109
|
+
magic: Magic;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export enum ZIndex {
|
|
113
|
+
MASK = '99999',
|
|
114
|
+
GHOST_EL = '99998',
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export enum MouseButton {
|
|
118
|
+
LEFT = 0,
|
|
119
|
+
MIDDLE = 1,
|
|
120
|
+
RIGHT = 2,
|
|
121
|
+
}
|