mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-09-09 17:57:41 +08:00
feat(editor,stage): 完善双击画布可以已弹层方向显示并显示完整的组件
This commit is contained in:
parent
3613237350
commit
c30e7d340b
@ -58,7 +58,11 @@
|
||||
|
||||
<template #workspace>
|
||||
<slot name="workspace" :editorService="editorService">
|
||||
<Workspace :stage-content-menu="stageContentMenu" :custom-content-menu="customContentMenu">
|
||||
<Workspace
|
||||
:disabled-stage-overlay="disabledStageOverlay"
|
||||
:stage-content-menu="stageContentMenu"
|
||||
:custom-content-menu="customContentMenu"
|
||||
>
|
||||
<template #stage><slot name="stage"></slot></template>
|
||||
<template #workspace-content><slot name="workspace-content" :editorService="editorService"></slot></template>
|
||||
</Workspace>
|
||||
@ -97,7 +101,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { provide, reactive } from 'vue';
|
||||
import { provide } from 'vue';
|
||||
|
||||
import type { MApp } from '@tmagic/schema';
|
||||
|
||||
@ -115,6 +119,7 @@ import eventsService from './services/events';
|
||||
import historyService from './services/history';
|
||||
import keybindingService from './services/keybinding';
|
||||
import propsService from './services/props';
|
||||
import stageOverlayService from './services/stageOverlay';
|
||||
import storageService from './services/storage';
|
||||
import uiService from './services/ui';
|
||||
import keybindingConfig from './utils/keybinding-config';
|
||||
@ -157,6 +162,7 @@ const services: Services = {
|
||||
depService,
|
||||
dataSourceService,
|
||||
keybindingService,
|
||||
stageOverlayService,
|
||||
};
|
||||
|
||||
initServiceEvents(props, emit, services);
|
||||
@ -164,28 +170,29 @@ initServiceState(props, services);
|
||||
keybindingService.register(keybindingConfig);
|
||||
keybindingService.registerEl('global');
|
||||
|
||||
const stageOptions = {
|
||||
runtimeUrl: props.runtimeUrl,
|
||||
autoScrollIntoView: props.autoScrollIntoView,
|
||||
render: props.render,
|
||||
moveableOptions: props.moveableOptions,
|
||||
canSelect: props.canSelect,
|
||||
updateDragEl: props.updateDragEl,
|
||||
isContainer: props.isContainer,
|
||||
containerHighlightClassName: props.containerHighlightClassName,
|
||||
containerHighlightDuration: props.containerHighlightDuration,
|
||||
containerHighlightType: props.containerHighlightType,
|
||||
disabledDragStart: props.disabledDragStart,
|
||||
renderType: props.renderType,
|
||||
guidesOptions: props.guidesOptions,
|
||||
disabledMultiSelect: props.disabledMultiSelect,
|
||||
};
|
||||
|
||||
stageOverlayService.set('stageOptions', stageOptions);
|
||||
|
||||
provide('services', services);
|
||||
|
||||
provide('codeOptions', props.codeOptions);
|
||||
provide(
|
||||
'stageOptions',
|
||||
reactive({
|
||||
runtimeUrl: props.runtimeUrl,
|
||||
autoScrollIntoView: props.autoScrollIntoView,
|
||||
render: props.render,
|
||||
moveableOptions: props.moveableOptions,
|
||||
canSelect: props.canSelect,
|
||||
updateDragEl: props.updateDragEl,
|
||||
isContainer: props.isContainer,
|
||||
containerHighlightClassName: props.containerHighlightClassName,
|
||||
containerHighlightDuration: props.containerHighlightDuration,
|
||||
containerHighlightType: props.containerHighlightType,
|
||||
disabledDragStart: props.disabledDragStart,
|
||||
renderType: props.renderType,
|
||||
guidesOptions: props.guidesOptions,
|
||||
disabledMultiSelect: props.disabledMultiSelect,
|
||||
}),
|
||||
);
|
||||
provide('stageOptions', stageOptions);
|
||||
|
||||
defineExpose(services);
|
||||
</script>
|
||||
|
@ -27,7 +27,7 @@ import { OnDrag } from 'gesto';
|
||||
import Resizer from './Resizer.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'MEditorLayout',
|
||||
name: 'MEditorSplitView',
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:left', 'change', 'update:right']);
|
||||
|
@ -78,6 +78,8 @@ export interface EditorProps {
|
||||
disabledMultiSelect?: boolean;
|
||||
/** 禁用页面片 */
|
||||
disabledPageFragment?: boolean;
|
||||
/** 禁用双击在浮层中单独编辑选中组件 */
|
||||
disabledStageOverlay?: boolean;
|
||||
/** 中间工作区域中画布渲染的内容 */
|
||||
render?: (stage: StageCore) => HTMLDivElement | Promise<HTMLDivElement>;
|
||||
/** 选中时会在画布上复制出一个大小相同的dom,实际拖拽的是这个dom,此方法用于干预这个dom的生成方式 */
|
||||
@ -95,6 +97,7 @@ export const defaultEditorProps = {
|
||||
renderType: RenderType.IFRAME,
|
||||
disabledMultiSelect: false,
|
||||
disabledPageFragment: false,
|
||||
disabledStageOverlay: false,
|
||||
containerHighlightClassName: CONTAINER_HIGHLIGHT_CLASS_NAME,
|
||||
containerHighlightDuration: 800,
|
||||
containerHighlightType: ContainerHighlightType.DEFAULT,
|
||||
|
@ -1,158 +0,0 @@
|
||||
import { computed, inject, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import type StageCore from '@tmagic/stage';
|
||||
|
||||
import type { Services, StageOptions } from '@editor/type';
|
||||
|
||||
import { useStage } from './use-stage';
|
||||
|
||||
export const useStageOverlay = () => {
|
||||
const services = inject<Services>('services');
|
||||
const stageOptions = inject<StageOptions>('stageOptions');
|
||||
|
||||
const wrapWidth = ref(0);
|
||||
const wrapHeight = ref(0);
|
||||
const stageOverlayVisible = ref(false);
|
||||
const stageOverlay = ref<HTMLDivElement>();
|
||||
|
||||
const stage = computed(() => services?.editorService.get('stage'));
|
||||
|
||||
let subStage: StageCore | null = null;
|
||||
|
||||
const div = document.createElement('div');
|
||||
let selectEl: HTMLElement | null = null;
|
||||
|
||||
const render = () => {
|
||||
if (!selectEl) return;
|
||||
|
||||
const content = selectEl.cloneNode(true) as HTMLElement;
|
||||
content.style.position = 'static';
|
||||
Array.from(div.children).forEach((element) => {
|
||||
element.remove();
|
||||
});
|
||||
div.appendChild(content);
|
||||
|
||||
subStage?.renderer.contentWindow?.magic.onPageElUpdate(div);
|
||||
|
||||
subStage?.select(content);
|
||||
};
|
||||
|
||||
const copyDocumentElement = () => {
|
||||
const doc = subStage?.renderer.getDocument();
|
||||
const documentElement = stage.value?.renderer.getDocument()?.documentElement;
|
||||
|
||||
if (doc && documentElement) {
|
||||
doc.replaceChild(documentElement.cloneNode(true), doc.documentElement);
|
||||
}
|
||||
};
|
||||
|
||||
const updateOverlay = () => {
|
||||
if (!selectEl) return;
|
||||
|
||||
const { scrollWidth, scrollHeight } = selectEl;
|
||||
|
||||
stageOverlay.value!.style.width = `${scrollWidth}px`;
|
||||
stageOverlay.value!.style.height = `${scrollHeight}px`;
|
||||
|
||||
wrapWidth.value = scrollWidth;
|
||||
wrapHeight.value = scrollHeight;
|
||||
};
|
||||
|
||||
const updateHandler = () => {
|
||||
render();
|
||||
updateOverlay();
|
||||
};
|
||||
|
||||
const addHandler = () => {
|
||||
render();
|
||||
updateOverlay();
|
||||
};
|
||||
|
||||
const removeHandler = () => {
|
||||
render();
|
||||
updateOverlay();
|
||||
};
|
||||
|
||||
const openOverlay = async (el: HTMLElement) => {
|
||||
selectEl = el;
|
||||
|
||||
stageOverlayVisible.value = true;
|
||||
|
||||
if (!stageOverlay.value) {
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
if (!stageOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
subStage = useStage({
|
||||
...stageOptions,
|
||||
runtimeUrl: '',
|
||||
autoScrollIntoView: false,
|
||||
render(stage: StageCore) {
|
||||
copyDocumentElement();
|
||||
|
||||
const rootEl = stage.renderer.getDocument()?.getElementById('app');
|
||||
if (rootEl) {
|
||||
rootEl.remove();
|
||||
}
|
||||
|
||||
div.style.cssText = `
|
||||
width: ${el.scrollWidth}px;
|
||||
height: ${el.scrollHeight}px;
|
||||
background-color: #fff;
|
||||
`;
|
||||
|
||||
render();
|
||||
|
||||
return div;
|
||||
},
|
||||
});
|
||||
subStage.mount(stageOverlay.value!);
|
||||
|
||||
const { mask, renderer } = subStage;
|
||||
|
||||
const { contentWindow } = renderer;
|
||||
mask.showRule(false);
|
||||
|
||||
updateOverlay();
|
||||
|
||||
contentWindow?.magic.onRuntimeReady({});
|
||||
|
||||
services?.editorService.on('update', updateHandler);
|
||||
services?.editorService.on('add', addHandler);
|
||||
services?.editorService.on('remove', removeHandler);
|
||||
};
|
||||
|
||||
const closeOverlay = () => {
|
||||
stageOverlayVisible.value = false;
|
||||
subStage?.destroy();
|
||||
subStage = null;
|
||||
|
||||
services?.editorService.off('update', updateHandler);
|
||||
services?.editorService.off('add', addHandler);
|
||||
services?.editorService.off('remove', removeHandler);
|
||||
};
|
||||
|
||||
watch(stage, (stage) => {
|
||||
if (stage) {
|
||||
stage.on('dblclick', async (event: MouseEvent) => {
|
||||
const el = await stage.actionManager.getElementFromPoint(event);
|
||||
if (el) {
|
||||
openOverlay(el);
|
||||
}
|
||||
});
|
||||
} else if (subStage) {
|
||||
closeOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
wrapWidth,
|
||||
wrapHeight,
|
||||
stageOverlayVisible,
|
||||
stageOverlay,
|
||||
closeOverlay,
|
||||
};
|
||||
};
|
@ -33,7 +33,9 @@ export const useStage = (stageOptions: StageOptions) => {
|
||||
disabledDragStart: stageOptions.disabledDragStart,
|
||||
renderType: stageOptions.renderType,
|
||||
canSelect: (el, event, stop) => {
|
||||
const elCanSelect = stageOptions.canSelect(el);
|
||||
if (!stageOptions.canSelect) return true;
|
||||
|
||||
const elCanSelect = stageOptions.canSelect?.(el);
|
||||
// 在组件联动过程中不能再往下选择,返回并触发 ui-select
|
||||
if (uiSelectMode.value && elCanSelect && event.type === 'mousedown') {
|
||||
document.dispatchEvent(new CustomEvent(UI_SELECT_MODE_EVENT_NAME, { detail: el }));
|
||||
|
@ -54,6 +54,7 @@ export { default as historyService } from './services/history';
|
||||
export { default as storageService } from './services/storage';
|
||||
export { default as eventsService } from './services/events';
|
||||
export { default as dataSourceService } from './services/dataSource';
|
||||
export { default as stageOverlayService } from './services/stageOverlay';
|
||||
export { default as uiService } from './services/ui';
|
||||
export { default as codeBlockService } from './services/codeBlock';
|
||||
export { default as depService } from './services/dep';
|
||||
|
@ -108,8 +108,8 @@ const dragendHandler = () => {
|
||||
globalThis.clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
const doc = stage.value?.renderer.contentWindow?.document;
|
||||
if (doc && stageOptions) {
|
||||
const doc = stage.value?.renderer.getDocument();
|
||||
if (doc && stageOptions?.containerHighlightClassName) {
|
||||
removeClassNameByClassName(doc, stageOptions.containerHighlightClassName);
|
||||
}
|
||||
clientX = 0;
|
||||
|
@ -5,6 +5,7 @@
|
||||
<slot name="stage">
|
||||
<MagicStage
|
||||
v-if="page"
|
||||
:disabled-stage-overlay="disabledStageOverlay"
|
||||
:stage-content-menu="stageContentMenu"
|
||||
:custom-content-menu="customContentMenu"
|
||||
></MagicStage>
|
||||
@ -28,10 +29,16 @@ defineOptions({
|
||||
name: 'MEditorWorkspace',
|
||||
});
|
||||
|
||||
defineProps<{
|
||||
stageContentMenu: (MenuButton | MenuComponent)[];
|
||||
customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
|
||||
}>();
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
stageContentMenu: (MenuButton | MenuComponent)[];
|
||||
disabledStageOverlay?: boolean;
|
||||
customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
|
||||
}>(),
|
||||
{
|
||||
disabledStageOverlay: false,
|
||||
},
|
||||
);
|
||||
|
||||
const services = inject<Services>('services');
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
<NodeListMenu></NodeListMenu>
|
||||
|
||||
<template #content>
|
||||
<StageOverlay></StageOverlay>
|
||||
<StageOverlay v-if="!disabledStageOverlay"></StageOverlay>
|
||||
|
||||
<Teleport to="body">
|
||||
<ViewerMenu
|
||||
@ -61,10 +61,16 @@ defineOptions({
|
||||
name: 'MEditorStage',
|
||||
});
|
||||
|
||||
defineProps<{
|
||||
stageContentMenu: (MenuButton | MenuComponent)[];
|
||||
customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
|
||||
}>();
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
stageContentMenu: (MenuButton | MenuComponent)[];
|
||||
disabledStageOverlay?: boolean;
|
||||
customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
|
||||
}>(),
|
||||
{
|
||||
disabledStageOverlay: false,
|
||||
},
|
||||
);
|
||||
|
||||
let stage: StageCore | null = null;
|
||||
let runtime: Runtime | null = null;
|
||||
|
@ -1,16 +1,66 @@
|
||||
<template>
|
||||
<div v-if="stageOverlayVisible" class="m-editor-stage-overlay" @click="closeOverlay">
|
||||
<TMagicIcon class="m-editor-stage-overlay-close" :size="20" @click="closeOverlay"><CloseBold /></TMagicIcon>
|
||||
<div ref="stageOverlay" class="m-editor-stage-overlay-container" @click.stop></div>
|
||||
<div v-if="stageOverlayVisible" class="m-editor-stage-overlay" @click="closeOverlayHandler">
|
||||
<TMagicIcon class="m-editor-stage-overlay-close" :size="20" @click="closeOverlayHandler"><CloseBold /></TMagicIcon>
|
||||
<div ref="stageOverlay" class="m-editor-stage-overlay-container" :style="style" @click.stop></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
import { CloseBold } from '@element-plus/icons-vue';
|
||||
|
||||
import { TMagicIcon } from '@tmagic/design';
|
||||
|
||||
import { useStageOverlay } from '@editor/hooks/use-stage-overlay';
|
||||
import type { Services, StageOptions } from '@editor/type';
|
||||
|
||||
const { stageOverlayVisible, stageOverlay, closeOverlay } = useStageOverlay();
|
||||
const services = inject<Services>('services');
|
||||
|
||||
const stageOptions = inject<StageOptions>('stageOptions');
|
||||
|
||||
const stageOverlay = ref<HTMLDivElement>();
|
||||
|
||||
const stageOverlayVisible = computed(() => services?.stageOverlayService.get('stageOverlayVisible'));
|
||||
const wrapWidth = computed(() => services?.stageOverlayService.get('wrapWidth') || 0);
|
||||
const wrapHeight = computed(() => services?.stageOverlayService.get('wrapHeight') || 0);
|
||||
const stage = computed(() => services?.editorService.get('stage'));
|
||||
|
||||
const style = computed(() => ({
|
||||
width: `${wrapWidth.value}px`,
|
||||
height: `${wrapHeight.value}px`,
|
||||
}));
|
||||
|
||||
watch(stage, (stage) => {
|
||||
if (stage) {
|
||||
stage.on('dblclick', async (event: MouseEvent) => {
|
||||
const el = await stage.actionManager.getElementFromPoint(event);
|
||||
services?.stageOverlayService.openOverlay(el);
|
||||
});
|
||||
} else {
|
||||
services?.stageOverlayService.closeOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
watch(stageOverlay, (stageOverlay) => {
|
||||
if (!services) return;
|
||||
|
||||
const subStage = services.stageOverlayService.createStage(stageOptions);
|
||||
services?.stageOverlayService.set('stage', subStage);
|
||||
|
||||
if (stageOverlay && subStage) {
|
||||
subStage.mount(stageOverlay);
|
||||
|
||||
const { mask, renderer } = subStage;
|
||||
|
||||
const { contentWindow } = renderer;
|
||||
mask.showRule(false);
|
||||
|
||||
services?.stageOverlayService.updateOverlay();
|
||||
|
||||
contentWindow?.magic.onRuntimeReady({});
|
||||
}
|
||||
});
|
||||
|
||||
const closeOverlayHandler = () => {
|
||||
services?.stageOverlayService.closeOverlay();
|
||||
};
|
||||
</script>
|
||||
|
@ -24,6 +24,8 @@ import type { Id, MApp, MComponent, MContainer, MNode, MPage, MPageFragment } fr
|
||||
import { NodeType } from '@tmagic/schema';
|
||||
import { getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';
|
||||
|
||||
import BaseService from '@editor/services//BaseService';
|
||||
import propsService from '@editor/services//props';
|
||||
import depService from '@editor/services/dep';
|
||||
import historyService from '@editor/services/history';
|
||||
import storageService, { Protocol } from '@editor/services/storage';
|
||||
@ -44,9 +46,6 @@ import {
|
||||
} from '@editor/utils/editor';
|
||||
import { beforePaste, getAddParent } from '@editor/utils/operator';
|
||||
|
||||
import BaseService from './BaseService';
|
||||
import propsService from './props';
|
||||
|
||||
class Editor extends BaseService {
|
||||
public state: StoreState = reactive({
|
||||
root: null,
|
||||
|
182
packages/editor/src/services/stageOverlay.ts
Normal file
182
packages/editor/src/services/stageOverlay.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { reactive } from 'vue';
|
||||
|
||||
import StageCore from '@tmagic/stage';
|
||||
|
||||
import { useStage } from '@editor/hooks/use-stage';
|
||||
import BaseService from '@editor/services//BaseService';
|
||||
import editorService from '@editor/services//editor';
|
||||
import type { StageOptions, StageOverlayState } from '@editor/type';
|
||||
|
||||
class StageOverlay extends BaseService {
|
||||
private state: StageOverlayState = reactive({
|
||||
wrapDiv: document.createElement('div'),
|
||||
sourceEl: null,
|
||||
contentEl: null,
|
||||
stage: null,
|
||||
stageOptions: null,
|
||||
wrapWidth: 0,
|
||||
wrapHeight: 0,
|
||||
stageOverlayVisible: false,
|
||||
});
|
||||
|
||||
constructor() {
|
||||
super([
|
||||
{ name: 'openOverlay', isAsync: false },
|
||||
{ name: 'closeOverlay', isAsync: false },
|
||||
{ name: 'updateOverlay', isAsync: false },
|
||||
{ name: 'createStage', isAsync: false },
|
||||
]);
|
||||
|
||||
this.get('wrapDiv').classList.add('tmagic-editor-sub-stage-wrap');
|
||||
}
|
||||
|
||||
public get<K extends keyof StageOverlayState>(name: K): StageOverlayState[K] {
|
||||
return this.state[name];
|
||||
}
|
||||
|
||||
public set<K extends keyof StageOverlayState, T extends StageOverlayState[K]>(name: K, value: T) {
|
||||
this.state[name] = value;
|
||||
}
|
||||
|
||||
public openOverlay(el: HTMLElement | undefined | null) {
|
||||
const stageOptions = this.get('stageOptions');
|
||||
if (!el || !stageOptions) return;
|
||||
|
||||
this.set('sourceEl', el);
|
||||
|
||||
this.createContentEl();
|
||||
|
||||
this.set('stageOverlayVisible', true);
|
||||
|
||||
editorService.on('update', this.updateHandler);
|
||||
editorService.on('add', this.addHandler);
|
||||
editorService.on('remove', this.removeHandler);
|
||||
}
|
||||
|
||||
public closeOverlay() {
|
||||
this.set('stageOverlayVisible', false);
|
||||
const subStage = this.get('stage');
|
||||
const wrapDiv = this.get('wrapDiv');
|
||||
subStage?.destroy();
|
||||
wrapDiv.remove();
|
||||
|
||||
this.set('stage', null);
|
||||
this.set('sourceEl', null);
|
||||
this.set('contentEl', null);
|
||||
|
||||
editorService.off('update', this.updateHandler);
|
||||
editorService.off('add', this.addHandler);
|
||||
editorService.off('remove', this.removeHandler);
|
||||
}
|
||||
|
||||
public updateOverlay() {
|
||||
const sourceEl = this.get('sourceEl');
|
||||
|
||||
if (!sourceEl) return;
|
||||
|
||||
const { scrollWidth, scrollHeight } = sourceEl;
|
||||
|
||||
this.set('wrapWidth', scrollWidth);
|
||||
this.set('wrapHeight', scrollHeight);
|
||||
}
|
||||
|
||||
public createStage(stageOptions: StageOptions = {}) {
|
||||
return useStage({
|
||||
...stageOptions,
|
||||
runtimeUrl: '',
|
||||
autoScrollIntoView: false,
|
||||
render: async (stage: StageCore) => {
|
||||
this.copyDocumentElement();
|
||||
|
||||
const rootEls = stage.renderer.getDocument()?.body.children;
|
||||
if (rootEls) {
|
||||
Array.from(rootEls).forEach((element) => {
|
||||
if (['SCRIPT', 'STYLE'].includes(element.tagName)) {
|
||||
return;
|
||||
}
|
||||
element.remove();
|
||||
});
|
||||
}
|
||||
|
||||
const wrapDiv = this.get('wrapDiv');
|
||||
const sourceEl = this.get('sourceEl');
|
||||
|
||||
wrapDiv.style.cssText = `
|
||||
width: ${sourceEl?.scrollWidth}px;
|
||||
height: ${sourceEl?.scrollHeight}px;
|
||||
background-color: #fff;
|
||||
`;
|
||||
|
||||
await this.render();
|
||||
|
||||
return wrapDiv;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private createContentEl() {
|
||||
const sourceEl = this.get('sourceEl');
|
||||
if (!sourceEl) return;
|
||||
|
||||
const contentEl = sourceEl.cloneNode(true) as HTMLElement;
|
||||
this.set('contentEl', contentEl);
|
||||
|
||||
contentEl.style.position = 'static';
|
||||
contentEl.style.overflow = 'visible';
|
||||
}
|
||||
|
||||
private copyDocumentElement() {
|
||||
const subStage = this.get('stage');
|
||||
const stage = editorService.get('stage');
|
||||
|
||||
const doc = subStage?.renderer.getDocument();
|
||||
const documentElement = stage?.renderer.getDocument()?.documentElement;
|
||||
|
||||
if (doc && documentElement) {
|
||||
doc.replaceChild(documentElement.cloneNode(true), doc.documentElement);
|
||||
}
|
||||
}
|
||||
|
||||
private async render() {
|
||||
this.createContentEl();
|
||||
|
||||
const contentEl = this.get('contentEl');
|
||||
const wrapDiv = this.get('wrapDiv');
|
||||
const subStage = this.get('stage');
|
||||
const stageOptions = this.get('stageOptions');
|
||||
|
||||
if (!contentEl) return;
|
||||
|
||||
Array.from(wrapDiv.children).forEach((element) => {
|
||||
element.remove();
|
||||
});
|
||||
wrapDiv.appendChild(contentEl);
|
||||
|
||||
setTimeout(() => {
|
||||
subStage?.renderer.contentWindow?.magic.onPageElUpdate(wrapDiv);
|
||||
});
|
||||
|
||||
if (await stageOptions?.canSelect?.(contentEl)) {
|
||||
subStage?.select(contentEl);
|
||||
}
|
||||
}
|
||||
|
||||
private updateHandler = () => {
|
||||
this.render();
|
||||
this.updateOverlay();
|
||||
};
|
||||
|
||||
private addHandler = () => {
|
||||
this.render();
|
||||
this.updateOverlay();
|
||||
};
|
||||
|
||||
private removeHandler = () => {
|
||||
this.render();
|
||||
this.updateOverlay();
|
||||
};
|
||||
}
|
||||
|
||||
export type StageOverlayService = StageOverlay;
|
||||
|
||||
export default new StageOverlay();
|
@ -32,7 +32,7 @@
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
z-index: 20;
|
||||
overflow: auto;
|
||||
@ -42,11 +42,12 @@
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
margin: auto;
|
||||
box-shadow: rgba(0, 0, 0, 0.04) 0px 3px 5px;
|
||||
}
|
||||
|
||||
.m-editor-stage-overlay-close.tmagic-design-icon {
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
right: 20px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ import type { EventsService } from './services/events';
|
||||
import type { HistoryService } from './services/history';
|
||||
import type { KeybindingService } from './services/keybinding';
|
||||
import type { PropsService } from './services/props';
|
||||
import type { StageOverlayService } from './services/stageOverlay';
|
||||
import type { StorageService } from './services/storage';
|
||||
import type { UiService } from './services/ui';
|
||||
import type { UndoRedo } from './utils/undo-redo';
|
||||
@ -118,22 +119,23 @@ export interface Services {
|
||||
depService: DepService;
|
||||
dataSourceService: DataSourceService;
|
||||
keybindingService: KeybindingService;
|
||||
stageOverlayService: StageOverlayService;
|
||||
}
|
||||
|
||||
export interface StageOptions {
|
||||
runtimeUrl: string;
|
||||
autoScrollIntoView: boolean;
|
||||
containerHighlightClassName: string;
|
||||
containerHighlightDuration: number;
|
||||
containerHighlightType: ContainerHighlightType;
|
||||
runtimeUrl?: string;
|
||||
autoScrollIntoView?: boolean;
|
||||
containerHighlightClassName?: string;
|
||||
containerHighlightDuration?: number;
|
||||
containerHighlightType?: ContainerHighlightType;
|
||||
disabledDragStart?: boolean;
|
||||
render: (stage: StageCore) => HTMLDivElement | Promise<HTMLDivElement>;
|
||||
moveableOptions: MoveableOptions | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions);
|
||||
canSelect: (el: HTMLElement) => boolean | Promise<boolean>;
|
||||
isContainer: (el: HTMLElement) => boolean | Promise<boolean>;
|
||||
updateDragEl: UpdateDragEl;
|
||||
renderType: RenderType;
|
||||
guidesOptions: Partial<GuidesOptions>;
|
||||
render?: (stage: StageCore) => HTMLDivElement | Promise<HTMLDivElement>;
|
||||
moveableOptions?: MoveableOptions | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions);
|
||||
canSelect?: (el: HTMLElement) => boolean | Promise<boolean>;
|
||||
isContainer?: (el: HTMLElement) => boolean | Promise<boolean>;
|
||||
updateDragEl?: UpdateDragEl;
|
||||
renderType?: RenderType;
|
||||
guidesOptions?: Partial<GuidesOptions>;
|
||||
disabledMultiSelect?: boolean;
|
||||
}
|
||||
|
||||
@ -160,6 +162,17 @@ export interface PropsState {
|
||||
relateIdMap: Record<Id, Id>;
|
||||
}
|
||||
|
||||
export interface StageOverlayState {
|
||||
wrapDiv: HTMLDivElement;
|
||||
sourceEl: HTMLElement | null;
|
||||
contentEl: HTMLElement | null;
|
||||
stage: StageCore | null;
|
||||
stageOptions: StageOptions | null;
|
||||
wrapWidth: number;
|
||||
wrapHeight: number;
|
||||
stageOverlayVisible: boolean;
|
||||
}
|
||||
|
||||
export interface ComponentGroupState {
|
||||
list: ComponentGroup[];
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ export default class ActionManager extends EventEmitter {
|
||||
private getTargetElement: GetTargetElement;
|
||||
private getElementsFromPoint: GetElementsFromPoint;
|
||||
private canSelect: CanSelect;
|
||||
private isContainer: IsContainer;
|
||||
private isContainer?: IsContainer;
|
||||
private getRenderDocument: GetRenderDocument;
|
||||
private disabledMultiSelect = false;
|
||||
private config: ActionManagerConfig;
|
||||
@ -328,7 +328,7 @@ export default class ActionManager extends EventEmitter {
|
||||
const els = this.getElementsFromPoint(event);
|
||||
|
||||
for (const el of els) {
|
||||
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.isContainer(el)) && !excludeElList.includes(el)) {
|
||||
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.isContainer?.(el)) && !excludeElList.includes(el)) {
|
||||
addClassName(el, doc, this.containerHighlightClassName);
|
||||
break;
|
||||
}
|
||||
|
@ -252,6 +252,10 @@ export default class StageCore extends EventEmitter {
|
||||
* 监听页面大小变化
|
||||
*/
|
||||
private observePageResize(page: HTMLElement): void {
|
||||
if (this.pageResizeObserver) {
|
||||
this.pageResizeObserver.disconnect();
|
||||
}
|
||||
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
this.pageResizeObserver = new ResizeObserver((entries) => {
|
||||
this.mask.pageResize(entries);
|
||||
|
@ -66,7 +66,7 @@ export interface StageCoreConfig {
|
||||
/** 放大倍数,默认1倍 */
|
||||
zoom?: number;
|
||||
canSelect?: CanSelect;
|
||||
isContainer: IsContainer;
|
||||
isContainer?: IsContainer;
|
||||
containerHighlightClassName?: string;
|
||||
containerHighlightDuration?: number;
|
||||
containerHighlightType?: ContainerHighlightType;
|
||||
@ -91,7 +91,7 @@ export interface ActionManagerConfig {
|
||||
disabledDragStart?: boolean;
|
||||
disabledMultiSelect?: boolean;
|
||||
canSelect?: CanSelect;
|
||||
isContainer: IsContainer;
|
||||
isContainer?: IsContainer;
|
||||
getRootContainer: GetRootContainer;
|
||||
getRenderDocument: GetRenderDocument;
|
||||
updateDragEl?: UpdateDragEl;
|
||||
|
@ -66,6 +66,7 @@ export const getTargetElStyle = (el: TargetElement, zIndex?: ZIndex) => {
|
||||
width: ${el.clientWidth}px;
|
||||
height: ${el.clientHeight}px;
|
||||
border: ${border};
|
||||
opacity: 0;
|
||||
${typeof zIndex !== 'undefined' ? `z-index: ${zIndex};` : ''}
|
||||
`;
|
||||
};
|
||||
|
@ -187,6 +187,13 @@ const moveableOptions = (config?: CustomizeMoveableOptionsCallbackConfig): Movea
|
||||
options.resizable = !isPage;
|
||||
options.rotatable = !isPage;
|
||||
|
||||
// 双击后在弹层中编辑时,根组件不能拖拽
|
||||
if (config?.targetEl?.parentElement?.classList.contains('tmagic-editor-sub-stage-wrap')) {
|
||||
options.draggable = false;
|
||||
options.resizable = false;
|
||||
options.rotatable = false;
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
:render="render"
|
||||
:can-select="canSelect"
|
||||
:disabled-page-fragment="true"
|
||||
:disabled-stage-overlay="true"
|
||||
:stage-rect="{ width: 'calc(100% - 70px)', height: '100%' }"
|
||||
:moveable-options="{ resizable: false }"
|
||||
>
|
||||
|
Loading…
x
Reference in New Issue
Block a user