feat(editor,stage): 双击画布可以已弹层方向显示并显示完整的组件

This commit is contained in:
roymondchen 2024-01-12 16:36:59 +08:00
parent 22e8ae47f1
commit e4af8cadb0
9 changed files with 237 additions and 17 deletions

View File

@ -4,11 +4,13 @@
<slot></slot>
</div>
<slot name="content"></slot>
<ScrollBar
v-if="scrollHeight > wrapHeight"
:scroll-size="scrollHeight"
:size="wrapHeight"
:pos="vOffset"
:size="wrapHeight"
@scroll="vScrollHandler"
></ScrollBar>
<ScrollBar

View File

@ -5,14 +5,14 @@ import type { Id, MNode } from '@tmagic/schema';
import { LayerNodeStatus, TreeNodeData } from '@editor/type';
import { traverseNode } from '@editor/utils';
const createPageNodeStatus = (nodeData: TreeNodeData[], initalLayerNodeStatus?: Map<Id, LayerNodeStatus>) => {
const createPageNodeStatus = (nodeData: TreeNodeData[], initialLayerNodeStatus?: Map<Id, LayerNodeStatus>) => {
const map = new Map<Id, LayerNodeStatus>();
nodeData.forEach((node: MNode) =>
traverseNode(node, (node) => {
map.set(
node.id,
initalLayerNodeStatus?.get(node.id) || {
initialLayerNodeStatus?.get(node.id) || {
visible: true,
expand: false,
selected: false,

View File

@ -0,0 +1,158 @@
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,
};
};

View File

@ -22,16 +22,21 @@
@drop="dropHandler"
@dragover="dragoverHandler"
></div>
<NodeListMenu></NodeListMenu>
<Teleport to="body">
<ViewerMenu
ref="menu"
:is-multi-select="isMultiSelect"
:stage-content-menu="stageContentMenu"
:custom-content-menu="customContentMenu"
></ViewerMenu>
</Teleport>
<template #content>
<StageOverlay></StageOverlay>
<Teleport to="body">
<ViewerMenu
ref="menu"
:is-multi-select="isMultiSelect"
:stage-content-menu="stageContentMenu"
:custom-content-menu="customContentMenu"
></ViewerMenu>
</Teleport>
</template>
</ScrollViewer>
</template>
@ -49,6 +54,7 @@ import { getConfig } from '@editor/utils/config';
import { KeyBindingContainerKey } from '@editor/utils/keybinding-config';
import NodeListMenu from './NodeListMenu.vue';
import StageOverlay from './StageOverlay.vue';
import ViewerMenu from './ViewerMenu.vue';
defineOptions({
@ -93,10 +99,13 @@ watchEffect(() => {
services?.editorService.set('stage', markRaw(stage));
stage?.mount(stageContainer.value);
stage.mount(stageContainer.value);
if (!node.value?.id) return;
stage?.on('runtime-ready', (rt) => {
if (!node.value?.id) {
return;
}
stage.on('runtime-ready', (rt) => {
runtime = rt;
// toRawcloneDeep
root.value && runtime?.updateRootConfig?.(cloneDeep(toRaw(root.value)));

View File

@ -0,0 +1,16 @@
<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>
</template>
<script setup lang="ts">
import { CloseBold } from '@element-plus/icons-vue';
import { TMagicIcon } from '@tmagic/design';
import { useStageOverlay } from '@editor/hooks/use-stage-overlay';
const { stageOverlayVisible, stageOverlay, closeOverlay } = useStageOverlay();
</script>

View File

@ -26,6 +26,30 @@
}
}
.m-editor-stage-overlay {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
z-index: 20;
overflow: auto;
}
.m-editor-stage-overlay-container {
position: relative;
flex-shrink: 0;
margin: auto;
}
.m-editor-stage-overlay-close.tmagic-design-icon {
position: fixed;
right: 10px;
top: 10px;
}
.m-editor-stage-float-button {
cursor: pointer;
transform: translateY(-50%);

View File

@ -360,6 +360,7 @@ export default class ActionManager extends EventEmitter {
this.container.removeEventListener('mousemove', this.mouseMoveHandler);
this.container.removeEventListener('mouseleave', this.mouseLeaveHandler);
this.container.removeEventListener('wheel', this.mouseWheelHandler);
this.container.removeEventListener('dblclick', this.dblclickHandler);
this.dr.destroy();
this.multiDr?.destroy();
this.highlightLayer.destroy();
@ -512,6 +513,7 @@ export default class ActionManager extends EventEmitter {
this.container.addEventListener('mousemove', this.mouseMoveHandler);
this.container.addEventListener('mouseleave', this.mouseLeaveHandler);
this.container.addEventListener('wheel', this.mouseWheelHandler);
this.container.addEventListener('dblclick', this.dblclickHandler);
}
/**
@ -619,4 +621,8 @@ export default class ActionManager extends EventEmitter {
private mouseWheelHandler = () => {
this.clearHighlight();
};
private dblclickHandler = (event: MouseEvent) => {
this.emit('dblclick', event);
};
}

View File

@ -46,8 +46,8 @@ export default class StageCore extends EventEmitter {
public container?: HTMLDivElement;
public renderer: StageRender;
public mask: StageMask;
public actionManager: ActionManager;
private actionManager: ActionManager;
private pageResizeObserver: ResizeObserver | null = null;
private autoScrollIntoView: boolean | undefined;
private customizedRender?: CustomizeRender;
@ -329,6 +329,9 @@ export default class StageCore extends EventEmitter {
})
.on('multi-select', (selectedElList: HTMLElement[], event: MouseEvent) => {
this.emit('multi-select', selectedElList, event);
})
.on('dblclick', (event: MouseEvent) => {
this.emit('dblclick', event);
});
}

View File

@ -15,6 +15,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { guid } from '@tmagic/utils';
import { Mode, ZIndex } from './const';
import type { TargetElement as ShadowElement, TargetShadowConfig, UpdateDragEl } from './types';
import { getTargetElStyle, isFixedParent } from './util';
@ -26,7 +28,7 @@ export default class TargetShadow {
public el?: ShadowElement;
public els: ShadowElement[] = [];
private idPrefix = 'target_calibrate_';
private idPrefix = `target_calibrate_${guid()}`;
private container: HTMLElement;
private scrollLeft = 0;
private scrollTop = 0;
@ -46,7 +48,7 @@ export default class TargetShadow {
}
if (config.idPrefix) {
this.idPrefix = config.idPrefix;
this.idPrefix = `${config.idPrefix}_${guid()}`;
}
this.container.addEventListener('customScroll', this.scrollHandler);