mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-05-03 16:26:40 +08:00
feat(editor,stage): 双击画布可以已弹层方向显示并显示完整的组件
This commit is contained in:
parent
22e8ae47f1
commit
e4af8cadb0
@ -4,11 +4,13 @@
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<slot name="content"></slot>
|
||||||
|
|
||||||
<ScrollBar
|
<ScrollBar
|
||||||
v-if="scrollHeight > wrapHeight"
|
v-if="scrollHeight > wrapHeight"
|
||||||
:scroll-size="scrollHeight"
|
:scroll-size="scrollHeight"
|
||||||
:size="wrapHeight"
|
|
||||||
:pos="vOffset"
|
:pos="vOffset"
|
||||||
|
:size="wrapHeight"
|
||||||
@scroll="vScrollHandler"
|
@scroll="vScrollHandler"
|
||||||
></ScrollBar>
|
></ScrollBar>
|
||||||
<ScrollBar
|
<ScrollBar
|
||||||
|
@ -5,14 +5,14 @@ import type { Id, MNode } from '@tmagic/schema';
|
|||||||
import { LayerNodeStatus, TreeNodeData } from '@editor/type';
|
import { LayerNodeStatus, TreeNodeData } from '@editor/type';
|
||||||
import { traverseNode } from '@editor/utils';
|
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>();
|
const map = new Map<Id, LayerNodeStatus>();
|
||||||
|
|
||||||
nodeData.forEach((node: MNode) =>
|
nodeData.forEach((node: MNode) =>
|
||||||
traverseNode(node, (node) => {
|
traverseNode(node, (node) => {
|
||||||
map.set(
|
map.set(
|
||||||
node.id,
|
node.id,
|
||||||
initalLayerNodeStatus?.get(node.id) || {
|
initialLayerNodeStatus?.get(node.id) || {
|
||||||
visible: true,
|
visible: true,
|
||||||
expand: false,
|
expand: false,
|
||||||
selected: 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"
|
@drop="dropHandler"
|
||||||
@dragover="dragoverHandler"
|
@dragover="dragoverHandler"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<NodeListMenu></NodeListMenu>
|
<NodeListMenu></NodeListMenu>
|
||||||
|
|
||||||
<Teleport to="body">
|
<template #content>
|
||||||
<ViewerMenu
|
<StageOverlay></StageOverlay>
|
||||||
ref="menu"
|
|
||||||
:is-multi-select="isMultiSelect"
|
<Teleport to="body">
|
||||||
:stage-content-menu="stageContentMenu"
|
<ViewerMenu
|
||||||
:custom-content-menu="customContentMenu"
|
ref="menu"
|
||||||
></ViewerMenu>
|
:is-multi-select="isMultiSelect"
|
||||||
</Teleport>
|
:stage-content-menu="stageContentMenu"
|
||||||
|
:custom-content-menu="customContentMenu"
|
||||||
|
></ViewerMenu>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -49,6 +54,7 @@ import { getConfig } from '@editor/utils/config';
|
|||||||
import { KeyBindingContainerKey } from '@editor/utils/keybinding-config';
|
import { KeyBindingContainerKey } from '@editor/utils/keybinding-config';
|
||||||
|
|
||||||
import NodeListMenu from './NodeListMenu.vue';
|
import NodeListMenu from './NodeListMenu.vue';
|
||||||
|
import StageOverlay from './StageOverlay.vue';
|
||||||
import ViewerMenu from './ViewerMenu.vue';
|
import ViewerMenu from './ViewerMenu.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -93,10 +99,13 @@ watchEffect(() => {
|
|||||||
|
|
||||||
services?.editorService.set('stage', markRaw(stage));
|
services?.editorService.set('stage', markRaw(stage));
|
||||||
|
|
||||||
stage?.mount(stageContainer.value);
|
stage.mount(stageContainer.value);
|
||||||
|
|
||||||
if (!node.value?.id) return;
|
if (!node.value?.id) {
|
||||||
stage?.on('runtime-ready', (rt) => {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stage.on('runtime-ready', (rt) => {
|
||||||
runtime = rt;
|
runtime = rt;
|
||||||
// toRaw返回的值是一个引用而非快照,需要cloneDeep
|
// toRaw返回的值是一个引用而非快照,需要cloneDeep
|
||||||
root.value && runtime?.updateRootConfig?.(cloneDeep(toRaw(root.value)));
|
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 {
|
.m-editor-stage-float-button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
|
@ -360,6 +360,7 @@ export default class ActionManager extends EventEmitter {
|
|||||||
this.container.removeEventListener('mousemove', this.mouseMoveHandler);
|
this.container.removeEventListener('mousemove', this.mouseMoveHandler);
|
||||||
this.container.removeEventListener('mouseleave', this.mouseLeaveHandler);
|
this.container.removeEventListener('mouseleave', this.mouseLeaveHandler);
|
||||||
this.container.removeEventListener('wheel', this.mouseWheelHandler);
|
this.container.removeEventListener('wheel', this.mouseWheelHandler);
|
||||||
|
this.container.removeEventListener('dblclick', this.dblclickHandler);
|
||||||
this.dr.destroy();
|
this.dr.destroy();
|
||||||
this.multiDr?.destroy();
|
this.multiDr?.destroy();
|
||||||
this.highlightLayer.destroy();
|
this.highlightLayer.destroy();
|
||||||
@ -512,6 +513,7 @@ export default class ActionManager extends EventEmitter {
|
|||||||
this.container.addEventListener('mousemove', this.mouseMoveHandler);
|
this.container.addEventListener('mousemove', this.mouseMoveHandler);
|
||||||
this.container.addEventListener('mouseleave', this.mouseLeaveHandler);
|
this.container.addEventListener('mouseleave', this.mouseLeaveHandler);
|
||||||
this.container.addEventListener('wheel', this.mouseWheelHandler);
|
this.container.addEventListener('wheel', this.mouseWheelHandler);
|
||||||
|
this.container.addEventListener('dblclick', this.dblclickHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -619,4 +621,8 @@ export default class ActionManager extends EventEmitter {
|
|||||||
private mouseWheelHandler = () => {
|
private mouseWheelHandler = () => {
|
||||||
this.clearHighlight();
|
this.clearHighlight();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private dblclickHandler = (event: MouseEvent) => {
|
||||||
|
this.emit('dblclick', event);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -46,8 +46,8 @@ export default class StageCore extends EventEmitter {
|
|||||||
public container?: HTMLDivElement;
|
public container?: HTMLDivElement;
|
||||||
public renderer: StageRender;
|
public renderer: StageRender;
|
||||||
public mask: StageMask;
|
public mask: StageMask;
|
||||||
|
public actionManager: ActionManager;
|
||||||
|
|
||||||
private actionManager: ActionManager;
|
|
||||||
private pageResizeObserver: ResizeObserver | null = null;
|
private pageResizeObserver: ResizeObserver | null = null;
|
||||||
private autoScrollIntoView: boolean | undefined;
|
private autoScrollIntoView: boolean | undefined;
|
||||||
private customizedRender?: CustomizeRender;
|
private customizedRender?: CustomizeRender;
|
||||||
@ -329,6 +329,9 @@ export default class StageCore extends EventEmitter {
|
|||||||
})
|
})
|
||||||
.on('multi-select', (selectedElList: HTMLElement[], event: MouseEvent) => {
|
.on('multi-select', (selectedElList: HTMLElement[], event: MouseEvent) => {
|
||||||
this.emit('multi-select', selectedElList, event);
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
import { guid } from '@tmagic/utils';
|
||||||
|
|
||||||
import { Mode, ZIndex } from './const';
|
import { Mode, ZIndex } from './const';
|
||||||
import type { TargetElement as ShadowElement, TargetShadowConfig, UpdateDragEl } from './types';
|
import type { TargetElement as ShadowElement, TargetShadowConfig, UpdateDragEl } from './types';
|
||||||
import { getTargetElStyle, isFixedParent } from './util';
|
import { getTargetElStyle, isFixedParent } from './util';
|
||||||
@ -26,7 +28,7 @@ export default class TargetShadow {
|
|||||||
public el?: ShadowElement;
|
public el?: ShadowElement;
|
||||||
public els: ShadowElement[] = [];
|
public els: ShadowElement[] = [];
|
||||||
|
|
||||||
private idPrefix = 'target_calibrate_';
|
private idPrefix = `target_calibrate_${guid()}`;
|
||||||
private container: HTMLElement;
|
private container: HTMLElement;
|
||||||
private scrollLeft = 0;
|
private scrollLeft = 0;
|
||||||
private scrollTop = 0;
|
private scrollTop = 0;
|
||||||
@ -46,7 +48,7 @@ export default class TargetShadow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.idPrefix) {
|
if (config.idPrefix) {
|
||||||
this.idPrefix = config.idPrefix;
|
this.idPrefix = `${config.idPrefix}_${guid()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.addEventListener('customScroll', this.scrollHandler);
|
this.container.addEventListener('customScroll', this.scrollHandler);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user