diff --git a/packages/editor/src/components/ScrollViewer.vue b/packages/editor/src/components/ScrollViewer.vue
index d4583e0f..f886560c 100644
--- a/packages/editor/src/components/ScrollViewer.vue
+++ b/packages/editor/src/components/ScrollViewer.vue
@@ -4,11 +4,13 @@
+
+
) => {
+const createPageNodeStatus = (nodeData: TreeNodeData[], initialLayerNodeStatus?: Map) => {
const map = new Map();
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,
diff --git a/packages/editor/src/hooks/use-stage-overlay.ts b/packages/editor/src/hooks/use-stage-overlay.ts
new file mode 100644
index 00000000..01ba8cc2
--- /dev/null
+++ b/packages/editor/src/hooks/use-stage-overlay.ts
@@ -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');
+ const stageOptions = inject('stageOptions');
+
+ const wrapWidth = ref(0);
+ const wrapHeight = ref(0);
+ const stageOverlayVisible = ref(false);
+ const stageOverlay = ref();
+
+ 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,
+ };
+};
diff --git a/packages/editor/src/layouts/workspace/viewer/Stage.vue b/packages/editor/src/layouts/workspace/viewer/Stage.vue
index 1e4d0081..3ce7d6f1 100644
--- a/packages/editor/src/layouts/workspace/viewer/Stage.vue
+++ b/packages/editor/src/layouts/workspace/viewer/Stage.vue
@@ -22,16 +22,21 @@
@drop="dropHandler"
@dragover="dragoverHandler"
>
+
-
-
-
+
+
+
+
+
+
+
@@ -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)));
diff --git a/packages/editor/src/layouts/workspace/viewer/StageOverlay.vue b/packages/editor/src/layouts/workspace/viewer/StageOverlay.vue
new file mode 100644
index 00000000..d4a086a4
--- /dev/null
+++ b/packages/editor/src/layouts/workspace/viewer/StageOverlay.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/packages/editor/src/theme/stage.scss b/packages/editor/src/theme/stage.scss
index 9ae994b1..8f99244c 100644
--- a/packages/editor/src/theme/stage.scss
+++ b/packages/editor/src/theme/stage.scss
@@ -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%);
diff --git a/packages/stage/src/ActionManager.ts b/packages/stage/src/ActionManager.ts
index e6b9b708..77d8c1ba 100644
--- a/packages/stage/src/ActionManager.ts
+++ b/packages/stage/src/ActionManager.ts
@@ -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);
+ };
}
diff --git a/packages/stage/src/StageCore.ts b/packages/stage/src/StageCore.ts
index 6e556cb8..c3f87da3 100644
--- a/packages/stage/src/StageCore.ts
+++ b/packages/stage/src/StageCore.ts
@@ -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);
});
}
diff --git a/packages/stage/src/TargetShadow.ts b/packages/stage/src/TargetShadow.ts
index a6a21d5e..b68ea718 100644
--- a/packages/stage/src/TargetShadow.ts
+++ b/packages/stage/src/TargetShadow.ts
@@ -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);