mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-04-06 03:57:56 +08:00
feat(editor,stage): 双击画布可以已弹层方向显示并显示完整的组件
This commit is contained in:
parent
22e8ae47f1
commit
e4af8cadb0
@ -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
|
||||
|
@ -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,
|
||||
|
158
packages/editor/src/hooks/use-stage-overlay.ts
Normal file
158
packages/editor/src/hooks/use-stage-overlay.ts
Normal 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,
|
||||
};
|
||||
};
|
@ -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;
|
||||
// toRaw返回的值是一个引用而非快照,需要cloneDeep
|
||||
root.value && runtime?.updateRootConfig?.(cloneDeep(toRaw(root.value)));
|
||||
|
@ -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>
|
@ -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%);
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user