@tmagic/editor 1.3.11 → 1.3.13

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.3.11",
2
+ "version": "1.3.13",
3
3
  "name": "@tmagic/editor",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -42,14 +42,14 @@
42
42
  "dependencies": {
43
43
  "@babel/core": "^7.18.0",
44
44
  "@element-plus/icons-vue": "^2.3.1",
45
- "@tmagic/core": "1.3.11",
46
- "@tmagic/dep": "1.3.11",
47
- "@tmagic/design": "1.3.11",
48
- "@tmagic/form": "1.3.11",
49
- "@tmagic/schema": "1.3.11",
50
- "@tmagic/stage": "1.3.11",
51
- "@tmagic/table": "1.3.11",
52
- "@tmagic/utils": "1.3.11",
45
+ "@tmagic/core": "1.3.13",
46
+ "@tmagic/dep": "1.3.13",
47
+ "@tmagic/design": "1.3.13",
48
+ "@tmagic/form": "1.3.13",
49
+ "@tmagic/schema": "1.3.13",
50
+ "@tmagic/stage": "1.3.13",
51
+ "@tmagic/table": "1.3.13",
52
+ "@tmagic/utils": "1.3.13",
53
53
  "buffer": "^6.0.3",
54
54
  "color": "^3.1.3",
55
55
  "emmet-monaco-es": "^5.3.0",
@@ -63,8 +63,8 @@
63
63
  "vue": "^3.3.8"
64
64
  },
65
65
  "peerDependencies": {
66
- "@tmagic/design": "1.3.11",
67
- "@tmagic/form": "1.3.11",
66
+ "@tmagic/design": "1.3.13",
67
+ "@tmagic/form": "1.3.13",
68
68
  "monaco-editor": "^0.41.0",
69
69
  "vue": "^3.3.8"
70
70
  },
package/src/Editor.vue CHANGED
@@ -58,7 +58,11 @@
58
58
 
59
59
  <template #workspace>
60
60
  <slot name="workspace" :editorService="editorService">
61
- <Workspace :stage-content-menu="stageContentMenu" :custom-content-menu="customContentMenu">
61
+ <Workspace
62
+ :disabled-stage-overlay="disabledStageOverlay"
63
+ :stage-content-menu="stageContentMenu"
64
+ :custom-content-menu="customContentMenu"
65
+ >
62
66
  <template #stage><slot name="stage"></slot></template>
63
67
  <template #workspace-content><slot name="workspace-content" :editorService="editorService"></slot></template>
64
68
  </Workspace>
@@ -97,7 +101,7 @@
97
101
  </template>
98
102
 
99
103
  <script lang="ts" setup>
100
- import { provide, reactive } from 'vue';
104
+ import { provide } from 'vue';
101
105
 
102
106
  import type { MApp } from '@tmagic/schema';
103
107
 
@@ -115,6 +119,7 @@ import eventsService from './services/events';
115
119
  import historyService from './services/history';
116
120
  import keybindingService from './services/keybinding';
117
121
  import propsService from './services/props';
122
+ import stageOverlayService from './services/stageOverlay';
118
123
  import storageService from './services/storage';
119
124
  import uiService from './services/ui';
120
125
  import keybindingConfig from './utils/keybinding-config';
@@ -157,6 +162,7 @@ const services: Services = {
157
162
  depService,
158
163
  dataSourceService,
159
164
  keybindingService,
165
+ stageOverlayService,
160
166
  };
161
167
 
162
168
  initServiceEvents(props, emit, services);
@@ -164,28 +170,29 @@ initServiceState(props, services);
164
170
  keybindingService.register(keybindingConfig);
165
171
  keybindingService.registerEl('global');
166
172
 
173
+ const stageOptions = {
174
+ runtimeUrl: props.runtimeUrl,
175
+ autoScrollIntoView: props.autoScrollIntoView,
176
+ render: props.render,
177
+ moveableOptions: props.moveableOptions,
178
+ canSelect: props.canSelect,
179
+ updateDragEl: props.updateDragEl,
180
+ isContainer: props.isContainer,
181
+ containerHighlightClassName: props.containerHighlightClassName,
182
+ containerHighlightDuration: props.containerHighlightDuration,
183
+ containerHighlightType: props.containerHighlightType,
184
+ disabledDragStart: props.disabledDragStart,
185
+ renderType: props.renderType,
186
+ guidesOptions: props.guidesOptions,
187
+ disabledMultiSelect: props.disabledMultiSelect,
188
+ };
189
+
190
+ stageOverlayService.set('stageOptions', stageOptions);
191
+
167
192
  provide('services', services);
168
193
 
169
194
  provide('codeOptions', props.codeOptions);
170
- provide(
171
- 'stageOptions',
172
- reactive({
173
- runtimeUrl: props.runtimeUrl,
174
- autoScrollIntoView: props.autoScrollIntoView,
175
- render: props.render,
176
- moveableOptions: props.moveableOptions,
177
- canSelect: props.canSelect,
178
- updateDragEl: props.updateDragEl,
179
- isContainer: props.isContainer,
180
- containerHighlightClassName: props.containerHighlightClassName,
181
- containerHighlightDuration: props.containerHighlightDuration,
182
- containerHighlightType: props.containerHighlightType,
183
- disabledDragStart: props.disabledDragStart,
184
- renderType: props.renderType,
185
- guidesOptions: props.guidesOptions,
186
- disabledMultiSelect: props.disabledMultiSelect,
187
- }),
188
- );
195
+ provide('stageOptions', stageOptions);
189
196
 
190
197
  defineExpose(services);
191
198
  </script>
@@ -27,7 +27,7 @@ import { OnDrag } from 'gesto';
27
27
  import Resizer from './Resizer.vue';
28
28
 
29
29
  defineOptions({
30
- name: 'MEditorLayout',
30
+ name: 'MEditorSplitView',
31
31
  });
32
32
 
33
33
  const emit = defineEmits(['update:left', 'change', 'update:right']);
@@ -37,8 +37,6 @@ export interface EditorProps {
37
37
  layerContentMenu?: (MenuButton | MenuComponent)[];
38
38
  /** 画布右键菜单 */
39
39
  stageContentMenu?: (MenuButton | MenuComponent)[];
40
- /** 中间工作区域中画布渲染的内容 */
41
- render?: (stage: StageCore) => HTMLDivElement | Promise<HTMLDivElement>;
42
40
  /** 中间工作区域中画布通过iframe渲染时的页面url */
43
41
  runtimeUrl?: string;
44
42
  /** 是用iframe渲染还是直接渲染 */
@@ -60,16 +58,18 @@ export interface EditorProps {
60
58
  moveableOptions?: MoveableOptions | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions);
61
59
  /** 编辑器初始化时默认选中的组件ID */
62
60
  defaultSelected?: Id;
63
- canSelect?: (el: HTMLElement) => boolean | Promise<boolean>;
64
- isContainer?: (el: HTMLElement) => boolean | Promise<boolean>;
61
+ /** 拖入画布中容器时,识别到容器后给容器根dom加上的class */
65
62
  containerHighlightClassName?: string;
63
+ /** 拖入画布中容器时,悬停识别容器的时间 */
66
64
  containerHighlightDuration?: number;
65
+ /** 拖入画布中容器时,识别容器的操作类型 */
67
66
  containerHighlightType?: ContainerHighlightType;
67
+ /** 画布大小 */
68
68
  stageRect?: StageRect;
69
+ /** monaco editor 的配置 */
69
70
  codeOptions?: { [key: string]: any };
70
- updateDragEl?: UpdateDragEl;
71
+ /** 禁用鼠标左键按下时就开始拖拽,需要先选中再可以拖拽 */
71
72
  disabledDragStart?: boolean;
72
- extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
73
73
  /** 自定义依赖收集器,复制组件时会将关联依赖一并复制 */
74
74
  collectorOptions?: CustomTargetOptions;
75
75
  /** 标尺配置 */
@@ -78,10 +78,29 @@ export interface EditorProps {
78
78
  disabledMultiSelect?: boolean;
79
79
  /** 禁用页面片 */
80
80
  disabledPageFragment?: boolean;
81
+ /** 禁用双击在浮层中单独编辑选中组件 */
82
+ disabledStageOverlay?: boolean;
83
+ /** 中间工作区域中画布渲染的内容 */
84
+ render?: (stage: StageCore) => HTMLDivElement | Promise<HTMLDivElement>;
85
+ /** 选中时会在画布上复制出一个大小相同的dom,实际拖拽的是这个dom,此方法用于干预这个dom的生成方式 */
86
+ updateDragEl?: UpdateDragEl;
87
+ /** 用于设置画布上的dom是否可以被选中 */
88
+ canSelect?: (el: HTMLElement) => boolean | Promise<boolean>;
89
+ /** 用于设置画布上的dom是否可以被拖入其中 */
90
+ isContainer?: (el: HTMLElement) => boolean | Promise<boolean>;
91
+ /** 用于自定义组件树与画布的右键菜单 */
81
92
  customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
93
+ extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
82
94
  }
83
95
 
84
96
  export const defaultEditorProps = {
97
+ renderType: RenderType.IFRAME,
98
+ disabledMultiSelect: false,
99
+ disabledPageFragment: false,
100
+ disabledStageOverlay: false,
101
+ containerHighlightClassName: CONTAINER_HIGHLIGHT_CLASS_NAME,
102
+ containerHighlightDuration: 800,
103
+ containerHighlightType: ContainerHighlightType.DEFAULT,
85
104
  componentGroupList: () => [],
86
105
  datasourceList: () => [],
87
106
  menu: () => ({ left: [], right: [] }),
@@ -94,11 +113,5 @@ export const defaultEditorProps = {
94
113
  datasourceConfigs: () => ({}),
95
114
  canSelect: (el: HTMLElement) => Boolean(el.id),
96
115
  isContainer: (el: HTMLElement) => el.classList.contains('magic-ui-container'),
97
- containerHighlightClassName: CONTAINER_HIGHLIGHT_CLASS_NAME,
98
- containerHighlightDuration: 800,
99
- containerHighlightType: ContainerHighlightType.DEFAULT,
100
116
  codeOptions: () => ({}),
101
- renderType: RenderType.IFRAME,
102
- disabledMultiSelect: false,
103
- disabledPageFragment: false,
104
117
  };
@@ -106,16 +106,19 @@ const deleteHandler = () => {
106
106
  const selectNode = async (id: Id) => {
107
107
  await services?.editorService.select(id);
108
108
  services?.editorService.get('stage')?.select(id);
109
+ services?.stageOverlayService.get('stage')?.select(id);
109
110
  };
110
111
 
111
112
  const highlight = throttle((id: Id) => {
112
113
  services?.editorService.highlight(id);
113
114
  services?.editorService.get('stage')?.highlight(id);
115
+ services?.stageOverlayService.get('stage')?.highlight(id);
114
116
  }, 150);
115
117
 
116
118
  const unhightlight = () => {
117
119
  services?.editorService.set('highlightNode', null);
118
120
  services?.editorService.get('stage')?.clearHighlight();
121
+ services?.stageOverlayService.get('stage')?.clearHighlight();
119
122
  };
120
123
  </script>
121
124
 
@@ -33,7 +33,9 @@ export const useStage = (stageOptions: StageOptions) => {
33
33
  disabledDragStart: stageOptions.disabledDragStart,
34
34
  renderType: stageOptions.renderType,
35
35
  canSelect: (el, event, stop) => {
36
- const elCanSelect = stageOptions.canSelect(el);
36
+ if (!stageOptions.canSelect) return true;
37
+
38
+ const elCanSelect = stageOptions.canSelect?.(el);
37
39
  // 在组件联动过程中不能再往下选择,返回并触发 ui-select
38
40
  if (uiSelectMode.value && elCanSelect && event.type === 'mousedown') {
39
41
  document.dispatchEvent(new CustomEvent(UI_SELECT_MODE_EVENT_NAME, { detail: el }));
package/src/index.ts CHANGED
@@ -54,6 +54,7 @@ export { default as historyService } from './services/history';
54
54
  export { default as storageService } from './services/storage';
55
55
  export { default as eventsService } from './services/events';
56
56
  export { default as dataSourceService } from './services/dataSource';
57
+ export { default as stageOverlayService } from './services/stageOverlay';
57
58
  export { default as uiService } from './services/ui';
58
59
  export { default as codeBlockService } from './services/codeBlock';
59
60
  export { default as depService } from './services/dep';
@@ -108,8 +108,8 @@ const dragendHandler = () => {
108
108
  globalThis.clearTimeout(timeout);
109
109
  timeout = undefined;
110
110
  }
111
- const doc = stage.value?.renderer.contentWindow?.document;
112
- if (doc && stageOptions) {
111
+ const doc = stage.value?.renderer.getDocument();
112
+ if (doc && stageOptions?.containerHighlightClassName) {
113
113
  removeClassNameByClassName(doc, stageOptions.containerHighlightClassName);
114
114
  }
115
115
  clientX = 0;
@@ -26,6 +26,7 @@ export const useClick = (
26
26
  } else {
27
27
  await services?.editorService.select(data);
28
28
  services?.editorService.get('stage')?.select(data.id);
29
+ services?.stageOverlayService.get('stage')?.select(data.id);
29
30
  }
30
31
  };
31
32
 
@@ -50,6 +51,7 @@ export const useClick = (
50
51
 
51
52
  await services?.editorService.multiSelect(newNodes);
52
53
  services?.editorService.get('stage')?.multiSelect(newNodes);
54
+ services?.stageOverlayService.get('stage')?.multiSelect(newNodes);
53
55
  };
54
56
 
55
57
  const throttleTime = 300;
@@ -62,6 +64,7 @@ export const useClick = (
62
64
  const highlight = (data: TreeNodeData) => {
63
65
  services?.editorService?.highlight(data);
64
66
  services?.editorService?.get('stage')?.highlight(data.id);
67
+ services?.stageOverlayService?.get('stage')?.highlight(data.id);
65
68
  };
66
69
 
67
70
  const nodeClickHandler = (event: MouseEvent, data: TreeNodeData) => {
@@ -5,6 +5,7 @@
5
5
  <slot name="stage">
6
6
  <MagicStage
7
7
  v-if="page"
8
+ :disabled-stage-overlay="disabledStageOverlay"
8
9
  :stage-content-menu="stageContentMenu"
9
10
  :custom-content-menu="customContentMenu"
10
11
  ></MagicStage>
@@ -28,10 +29,16 @@ defineOptions({
28
29
  name: 'MEditorWorkspace',
29
30
  });
30
31
 
31
- defineProps<{
32
- stageContentMenu: (MenuButton | MenuComponent)[];
33
- customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
34
- }>();
32
+ withDefaults(
33
+ defineProps<{
34
+ stageContentMenu: (MenuButton | MenuComponent)[];
35
+ disabledStageOverlay?: boolean;
36
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
37
+ }>(),
38
+ {
39
+ disabledStageOverlay: false,
40
+ },
41
+ );
35
42
 
36
43
  const services = inject<Services>('services');
37
44
 
@@ -26,7 +26,7 @@
26
26
  <NodeListMenu></NodeListMenu>
27
27
 
28
28
  <template #content>
29
- <StageOverlay></StageOverlay>
29
+ <StageOverlay v-if="!disabledStageOverlay"></StageOverlay>
30
30
 
31
31
  <Teleport to="body">
32
32
  <ViewerMenu
@@ -61,10 +61,16 @@ defineOptions({
61
61
  name: 'MEditorStage',
62
62
  });
63
63
 
64
- defineProps<{
65
- stageContentMenu: (MenuButton | MenuComponent)[];
66
- customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
67
- }>();
64
+ withDefaults(
65
+ defineProps<{
66
+ stageContentMenu: (MenuButton | MenuComponent)[];
67
+ disabledStageOverlay?: boolean;
68
+ customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
69
+ }>(),
70
+ {
71
+ disabledStageOverlay: false,
72
+ },
73
+ );
68
74
 
69
75
  let stage: StageCore | null = null;
70
76
  let runtime: Runtime | null = null;
@@ -1,16 +1,66 @@
1
1
  <template>
2
- <div v-if="stageOverlayVisible" class="m-editor-stage-overlay" @click="closeOverlay">
3
- <TMagicIcon class="m-editor-stage-overlay-close" :size="20" @click="closeOverlay"><CloseBold /></TMagicIcon>
4
- <div ref="stageOverlay" class="m-editor-stage-overlay-container" @click.stop></div>
2
+ <div v-if="stageOverlayVisible" class="m-editor-stage-overlay" @click="closeOverlayHandler">
3
+ <TMagicIcon class="m-editor-stage-overlay-close" :size="20" @click="closeOverlayHandler"><CloseBold /></TMagicIcon>
4
+ <div ref="stageOverlay" class="m-editor-stage-overlay-container" :style="style" @click.stop></div>
5
5
  </div>
6
6
  </template>
7
7
 
8
8
  <script setup lang="ts">
9
+ import { computed, inject, ref, watch } from 'vue';
9
10
  import { CloseBold } from '@element-plus/icons-vue';
10
11
 
11
12
  import { TMagicIcon } from '@tmagic/design';
12
13
 
13
- import { useStageOverlay } from '@editor/hooks/use-stage-overlay';
14
+ import type { Services, StageOptions } from '@editor/type';
14
15
 
15
- const { stageOverlayVisible, stageOverlay, closeOverlay } = useStageOverlay();
16
+ const services = inject<Services>('services');
17
+
18
+ const stageOptions = inject<StageOptions>('stageOptions');
19
+
20
+ const stageOverlay = ref<HTMLDivElement>();
21
+
22
+ const stageOverlayVisible = computed(() => services?.stageOverlayService.get('stageOverlayVisible'));
23
+ const wrapWidth = computed(() => services?.stageOverlayService.get('wrapWidth') || 0);
24
+ const wrapHeight = computed(() => services?.stageOverlayService.get('wrapHeight') || 0);
25
+ const stage = computed(() => services?.editorService.get('stage'));
26
+
27
+ const style = computed(() => ({
28
+ width: `${wrapWidth.value}px`,
29
+ height: `${wrapHeight.value}px`,
30
+ }));
31
+
32
+ watch(stage, (stage) => {
33
+ if (stage) {
34
+ stage.on('dblclick', async (event: MouseEvent) => {
35
+ const el = await stage.actionManager.getElementFromPoint(event);
36
+ services?.stageOverlayService.openOverlay(el);
37
+ });
38
+ } else {
39
+ services?.stageOverlayService.closeOverlay();
40
+ }
41
+ });
42
+
43
+ watch(stageOverlay, (stageOverlay) => {
44
+ if (!services) return;
45
+
46
+ const subStage = services.stageOverlayService.createStage(stageOptions);
47
+ services?.stageOverlayService.set('stage', subStage);
48
+
49
+ if (stageOverlay && subStage) {
50
+ subStage.mount(stageOverlay);
51
+
52
+ const { mask, renderer } = subStage;
53
+
54
+ const { contentWindow } = renderer;
55
+ mask.showRule(false);
56
+
57
+ services?.stageOverlayService.updateOverlay();
58
+
59
+ contentWindow?.magic.onRuntimeReady({});
60
+ }
61
+ });
62
+
63
+ const closeOverlayHandler = () => {
64
+ services?.stageOverlayService.closeOverlay();
65
+ };
16
66
  </script>
@@ -24,6 +24,8 @@ import type { Id, MApp, MComponent, MContainer, MNode, MPage, MPageFragment } fr
24
24
  import { NodeType } from '@tmagic/schema';
25
25
  import { getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';
26
26
 
27
+ import BaseService from '@editor/services//BaseService';
28
+ import propsService from '@editor/services//props';
27
29
  import depService from '@editor/services/dep';
28
30
  import historyService from '@editor/services/history';
29
31
  import storageService, { Protocol } from '@editor/services/storage';
@@ -44,9 +46,6 @@ import {
44
46
  } from '@editor/utils/editor';
45
47
  import { beforePaste, getAddParent } from '@editor/utils/operator';
46
48
 
47
- import BaseService from './BaseService';
48
- import propsService from './props';
49
-
50
49
  class Editor extends BaseService {
51
50
  public state: StoreState = reactive({
52
51
  root: null,
@@ -810,7 +809,7 @@ class Editor extends BaseService {
810
809
  * @param targetId 容器ID
811
810
  */
812
811
  public async moveToContainer(config: MNode, targetId: Id): Promise<MNode | undefined> {
813
- const root = cloneDeep(this.get('root'));
812
+ const root = this.get('root');
814
813
  const { node, parent } = this.getNodeInfo(config.id, false);
815
814
  const target = this.getNodeById(targetId, false) as MContainer;
816
815
 
@@ -819,7 +818,7 @@ class Editor extends BaseService {
819
818
  const index = getNodeIndex(node.id, parent);
820
819
  parent.items?.splice(index, 1);
821
820
 
822
- await stage.remove({ id: node.id, parentId: parent.id, root });
821
+ await stage.remove({ id: node.id, parentId: parent.id, root: cloneDeep(root) });
823
822
 
824
823
  const layout = await this.getLayout(target);
825
824
 
@@ -835,7 +834,7 @@ class Editor extends BaseService {
835
834
  await stage.select(targetId);
836
835
 
837
836
  const targetParent = this.getParentById(target.id);
838
- await stage.update({ config: cloneDeep(target), parentId: targetParent?.id, root });
837
+ await stage.update({ config: cloneDeep(target), parentId: targetParent?.id, root: cloneDeep(root) });
839
838
 
840
839
  await this.select(newConfig);
841
840
  stage.select(newConfig.id);
@@ -893,6 +892,8 @@ class Editor extends BaseService {
893
892
  this.addModifiedNodeId(parent.id);
894
893
 
895
894
  this.pushHistoryState();
895
+
896
+ this.emit('drag-to', { index, targetIndex, config, parent, targetParent });
896
897
  }
897
898
 
898
899
  /**
@@ -0,0 +1,204 @@
1
+ import { reactive } from 'vue';
2
+
3
+ import StageCore from '@tmagic/stage';
4
+
5
+ import { useStage } from '@editor/hooks/use-stage';
6
+ import BaseService from '@editor/services//BaseService';
7
+ import editorService from '@editor/services//editor';
8
+ import type { StageOptions, StageOverlayState } from '@editor/type';
9
+
10
+ class StageOverlay extends BaseService {
11
+ private state: StageOverlayState = reactive({
12
+ wrapDiv: document.createElement('div'),
13
+ sourceEl: null,
14
+ contentEl: null,
15
+ stage: null,
16
+ stageOptions: null,
17
+ wrapWidth: 0,
18
+ wrapHeight: 0,
19
+ stageOverlayVisible: false,
20
+ });
21
+
22
+ constructor() {
23
+ super([
24
+ { name: 'openOverlay', isAsync: false },
25
+ { name: 'closeOverlay', isAsync: false },
26
+ { name: 'updateOverlay', isAsync: false },
27
+ { name: 'createStage', isAsync: false },
28
+ ]);
29
+
30
+ this.get('wrapDiv').classList.add('tmagic-editor-sub-stage-wrap');
31
+ }
32
+
33
+ public get<K extends keyof StageOverlayState>(name: K): StageOverlayState[K] {
34
+ return this.state[name];
35
+ }
36
+
37
+ public set<K extends keyof StageOverlayState, T extends StageOverlayState[K]>(name: K, value: T) {
38
+ this.state[name] = value;
39
+ }
40
+
41
+ public openOverlay(el: HTMLElement | undefined | null) {
42
+ const stageOptions = this.get('stageOptions');
43
+ if (!el || !stageOptions) return;
44
+
45
+ this.set('sourceEl', el);
46
+
47
+ this.createContentEl();
48
+
49
+ this.set('stageOverlayVisible', true);
50
+
51
+ editorService.on('update', this.updateHandler);
52
+ editorService.on('add', this.addHandler);
53
+ editorService.on('remove', this.removeHandler);
54
+ editorService.on('drag-to', this.updateHandler);
55
+ editorService.on('move-layer', this.updateHandler);
56
+ }
57
+
58
+ public closeOverlay() {
59
+ this.set('stageOverlayVisible', false);
60
+ const subStage = this.get('stage');
61
+ const wrapDiv = this.get('wrapDiv');
62
+ subStage?.destroy();
63
+ wrapDiv.remove();
64
+
65
+ this.set('stage', null);
66
+ this.set('sourceEl', null);
67
+ this.set('contentEl', null);
68
+
69
+ editorService.off('update', this.updateHandler);
70
+ editorService.off('add', this.addHandler);
71
+ editorService.off('remove', this.removeHandler);
72
+ editorService.off('drag-to', this.updateHandler);
73
+ editorService.off('move-layer', this.updateHandler);
74
+ }
75
+
76
+ public updateOverlay() {
77
+ const sourceEl = this.get('sourceEl');
78
+
79
+ if (!sourceEl) return;
80
+
81
+ const { scrollWidth, scrollHeight } = sourceEl;
82
+
83
+ this.set('wrapWidth', scrollWidth);
84
+ this.set('wrapHeight', scrollHeight);
85
+ }
86
+
87
+ public createStage(stageOptions: StageOptions = {}) {
88
+ return useStage({
89
+ ...stageOptions,
90
+ runtimeUrl: '',
91
+ autoScrollIntoView: false,
92
+ render: async (stage: StageCore) => {
93
+ this.copyDocumentElement();
94
+
95
+ const rootEls = stage.renderer.getDocument()?.body.children;
96
+ if (rootEls) {
97
+ Array.from(rootEls).forEach((element) => {
98
+ if (['SCRIPT', 'STYLE'].includes(element.tagName)) {
99
+ return;
100
+ }
101
+ element.remove();
102
+ });
103
+ }
104
+
105
+ const wrapDiv = this.get('wrapDiv');
106
+
107
+ await this.render();
108
+
109
+ return wrapDiv;
110
+ },
111
+ });
112
+ }
113
+
114
+ private createContentEl() {
115
+ const sourceEl = this.get('sourceEl');
116
+ if (!sourceEl) return;
117
+
118
+ const contentEl = sourceEl.cloneNode(true) as HTMLElement;
119
+ this.set('contentEl', contentEl);
120
+
121
+ contentEl.style.position = 'static';
122
+ contentEl.style.overflow = 'visible';
123
+ }
124
+
125
+ private copyDocumentElement() {
126
+ const subStage = this.get('stage');
127
+ const stage = editorService.get('stage');
128
+
129
+ const doc = subStage?.renderer.getDocument();
130
+ const documentElement = stage?.renderer.getDocument()?.documentElement;
131
+
132
+ if (doc && documentElement) {
133
+ doc.replaceChild(documentElement.cloneNode(true), doc.documentElement);
134
+ }
135
+ }
136
+
137
+ private async render() {
138
+ this.createContentEl();
139
+
140
+ const contentEl = this.get('contentEl');
141
+ const sourceEl = this.get('sourceEl');
142
+ const wrapDiv = this.get('wrapDiv');
143
+ const subStage = this.get('stage');
144
+ const stageOptions = this.get('stageOptions');
145
+
146
+ if (!contentEl) return;
147
+
148
+ wrapDiv.style.cssText = `
149
+ width: ${sourceEl?.scrollWidth}px;
150
+ height: ${sourceEl?.scrollHeight}px;
151
+ background-color: #fff;
152
+ `;
153
+
154
+ Array.from(wrapDiv.children).forEach((element) => {
155
+ element.remove();
156
+ });
157
+ wrapDiv.appendChild(contentEl);
158
+
159
+ setTimeout(() => {
160
+ subStage?.renderer.contentWindow?.magic.onPageElUpdate(wrapDiv);
161
+ });
162
+
163
+ if (await stageOptions?.canSelect?.(contentEl)) {
164
+ subStage?.select(contentEl);
165
+ }
166
+ }
167
+
168
+ private updateHandler = () => {
169
+ setTimeout(() => {
170
+ this.render();
171
+ this.updateOverlay();
172
+
173
+ this.updateSelectStatus();
174
+ });
175
+ };
176
+
177
+ private addHandler = () => {
178
+ this.render();
179
+ this.updateOverlay();
180
+
181
+ this.updateSelectStatus();
182
+ };
183
+
184
+ private removeHandler = () => {
185
+ this.render();
186
+ this.updateOverlay();
187
+
188
+ this.updateSelectStatus();
189
+ };
190
+
191
+ private updateSelectStatus() {
192
+ const subStage = this.get('stage');
193
+ const nodes = editorService.get('nodes');
194
+ if (nodes.length > 1) {
195
+ subStage?.multiSelect(nodes.map((n) => n.id));
196
+ } else {
197
+ subStage?.select(nodes[0].id);
198
+ }
199
+ }
200
+ }
201
+
202
+ export type StageOverlayService = StageOverlay;
203
+
204
+ export default new StageOverlay();