修复多树预览受左侧选中图片限制
移除 activeImageId 对预览拖动和缩放手势的门控 按树 wrapper 提供几何上下文并允许任意树操作 保留 activeImageId 仅用于左侧图片与图片级检查
This commit is contained in:
+23
-46
@@ -30,6 +30,7 @@ import {
|
||||
|
||||
import { findNodePageContext } from '../../../../features/ui-editor/nodeTransformGeometry';
|
||||
import type { Node } from '../../../../features/ui-editor/types/Node';
|
||||
import type { Node as UiNode } from '../../../../features/ui-editor/types/Node';
|
||||
import type { NodeId } from '../../../../features/ui-editor/types/NodeId';
|
||||
import type { UiEditorCanvasProjection } from '../../useUiEditorPage';
|
||||
import { UiNodeContextMenu } from '../UiNodeContextMenu';
|
||||
@@ -68,8 +69,7 @@ export function PreviewWorkspace({
|
||||
showOriginImage: boolean;
|
||||
showComponent: boolean;
|
||||
}) {
|
||||
const { activeImage, activeImageId, previewUrls, uiTrees, images } = canvas;
|
||||
const tree = canvas.tree ?? null;
|
||||
const { previewUrls, uiTrees, images } = canvas;
|
||||
const viewportElementRef = useRef<HTMLDivElement>(null);
|
||||
const viewportRef = useRef<CanvasViewport>({ x: 0, y: 0, scale: 0.5 });
|
||||
const handledFocusRequestIdRef = useRef<number | null>(null);
|
||||
@@ -116,34 +116,6 @@ export function PreviewWorkspace({
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() => {
|
||||
setPreviewTransforms(new Map());
|
||||
}, [activeImageId]);
|
||||
const activeImagePixelWidth = activeImage?.pixel_size[0];
|
||||
const activeImagePixelHeight = activeImage?.pixel_size[1];
|
||||
const activeImagePixelsPerUnit = activeImage?.pixels_per_unit;
|
||||
const logicalSize = useMemo(() => {
|
||||
if (
|
||||
activeImagePixelWidth === undefined ||
|
||||
activeImagePixelHeight === undefined ||
|
||||
activeImagePixelsPerUnit === undefined
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const ppu = activeImagePixelsPerUnit;
|
||||
if (!Number.isFinite(ppu) || ppu <= 0) return null;
|
||||
const width = activeImagePixelWidth / ppu;
|
||||
const height = activeImagePixelHeight / ppu;
|
||||
if (
|
||||
!Number.isFinite(width) ||
|
||||
!Number.isFinite(height) ||
|
||||
width <= 0 ||
|
||||
height <= 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return { width, height };
|
||||
}, [activeImagePixelHeight, activeImagePixelsPerUnit, activeImagePixelWidth]);
|
||||
const treeLayouts = useMemo(
|
||||
() =>
|
||||
uiTrees.map((item) => {
|
||||
@@ -176,13 +148,22 @@ export function PreviewWorkspace({
|
||||
);
|
||||
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
||||
}, [treeLayouts]);
|
||||
const logicalSizes = useMemo(
|
||||
() =>
|
||||
new Map(
|
||||
treeLayouts.map(
|
||||
({ tree: item, width, height }) =>
|
||||
[item.src_ui_design, { width, height }] as const,
|
||||
),
|
||||
),
|
||||
[treeLayouts],
|
||||
);
|
||||
|
||||
const nodeInteractions = useNodeTransformInteraction({
|
||||
activeImageId,
|
||||
canvas,
|
||||
logicalSize,
|
||||
trees: uiTrees,
|
||||
logicalSizes,
|
||||
spaceHeld,
|
||||
tree,
|
||||
keepChildrenUnchanged: canvas.keepChildrenUnchanged,
|
||||
viewportRef,
|
||||
onPreviewTransform: updatePreviewTransform,
|
||||
@@ -285,15 +266,12 @@ export function PreviewWorkspace({
|
||||
|
||||
useEffect(() => {
|
||||
const request = canvas.focusRequest;
|
||||
if (
|
||||
!request ||
|
||||
request.requestId === handledFocusRequestIdRef.current ||
|
||||
request.treeId !== activeImageId ||
|
||||
!tree ||
|
||||
!logicalSize
|
||||
) {
|
||||
if (!request || request.requestId === handledFocusRequestIdRef.current) {
|
||||
return;
|
||||
}
|
||||
const tree = uiTrees.find((item) => item.src_ui_design === request.treeId);
|
||||
const logicalSize = logicalSizes.get(request.treeId);
|
||||
if (!tree || !logicalSize) return;
|
||||
const context = findNodePageContext(tree.root, request.nodeId, {
|
||||
min: [0, 0],
|
||||
max: [logicalSize.width, logicalSize.height],
|
||||
@@ -317,13 +295,12 @@ export function PreviewWorkspace({
|
||||
),
|
||||
);
|
||||
}, [
|
||||
activeImageId,
|
||||
canvasSize.height,
|
||||
canvasSize.width,
|
||||
canvas.focusRequest,
|
||||
logicalSize,
|
||||
logicalSizes,
|
||||
setViewport,
|
||||
tree,
|
||||
uiTrees,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -350,7 +327,7 @@ export function PreviewWorkspace({
|
||||
handlePreviewZoomKeyDown(
|
||||
event,
|
||||
{
|
||||
hasZoomableViewport: logicalSize !== null,
|
||||
hasZoomableViewport: allBounds !== null,
|
||||
isFocused: previewFocusedRef.current,
|
||||
isHovered: previewHoveredRef.current,
|
||||
usesMetaModifier,
|
||||
@@ -374,7 +351,7 @@ export function PreviewWorkspace({
|
||||
window.removeEventListener('keyup', onKeyUp);
|
||||
window.removeEventListener('blur', onWindowBlur);
|
||||
};
|
||||
}, [logicalSize, resetToActualSize, zoomIn, zoomOut]);
|
||||
}, [allBounds, resetToActualSize, zoomIn, zoomOut]);
|
||||
|
||||
const handlePointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
|
||||
if (event.button === 0 && !isPreviewZoomInteractiveTarget(event.target)) {
|
||||
@@ -479,7 +456,7 @@ export function PreviewWorkspace({
|
||||
|
||||
const handleNodeContextMenu = (
|
||||
event: ReactMouseEvent<HTMLDivElement>,
|
||||
node: NonNullable<typeof tree>['root'],
|
||||
node: UiNode,
|
||||
isPageRoot: boolean,
|
||||
treeId?: string,
|
||||
) => {
|
||||
|
||||
@@ -23,6 +23,7 @@ export type { ResizeHandle } from '../../../../features/ui-editor/nodeTransformG
|
||||
type NodePointerDown = (
|
||||
event: ReactPointerEvent<HTMLDivElement>,
|
||||
node: UiNode,
|
||||
treeId?: string,
|
||||
) => void;
|
||||
|
||||
type UiTreeRendererProps = {
|
||||
@@ -50,6 +51,7 @@ type UiTreeRendererProps = {
|
||||
event: ReactPointerEvent<HTMLDivElement>,
|
||||
node: UiNode,
|
||||
handle: ResizeHandle,
|
||||
treeId?: string,
|
||||
) => void;
|
||||
onNodeResizePointerMove: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
||||
onNodeResizePointerUp: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
||||
@@ -178,7 +180,7 @@ function RenderNode({
|
||||
}}
|
||||
onPointerDown={
|
||||
receivesPointerGesture
|
||||
? (event) => onNodePointerDown(event, node)
|
||||
? (event) => onNodePointerDown(event, node, treeId)
|
||||
: undefined
|
||||
}
|
||||
onPointerMove={receivesPointerGesture ? onNodePointerMove : undefined}
|
||||
@@ -255,7 +257,7 @@ function RenderNode({
|
||||
touchAction: 'none',
|
||||
}}
|
||||
onPointerDown={(event) =>
|
||||
onNodeResizePointerDown(event, node, handle.id)
|
||||
onNodeResizePointerDown(event, node, handle.id, treeId)
|
||||
}
|
||||
onPointerMove={onNodeResizePointerMove}
|
||||
onPointerUp={onNodeResizePointerUp}
|
||||
|
||||
+42
-41
@@ -176,22 +176,20 @@ function emitPreviewTransforms(
|
||||
}
|
||||
|
||||
export function useNodeTransformInteraction({
|
||||
activeImageId,
|
||||
canvas,
|
||||
logicalSize,
|
||||
trees,
|
||||
logicalSizes,
|
||||
spaceHeld,
|
||||
tree,
|
||||
keepChildrenUnchanged,
|
||||
viewportRef,
|
||||
onPreviewTransform,
|
||||
previewRef,
|
||||
selectedNodeId,
|
||||
}: {
|
||||
activeImageId: UiEditorCanvasProjection['activeImageId'];
|
||||
canvas: Pick<UiEditorCanvasProjection, 'selectNode' | 'updateNodeTransform'>;
|
||||
logicalSize: { width: number; height: number } | null;
|
||||
trees: readonly UITree[];
|
||||
logicalSizes: ReadonlyMap<string, { width: number; height: number }>;
|
||||
spaceHeld: boolean;
|
||||
tree: UITree | null;
|
||||
keepChildrenUnchanged: boolean;
|
||||
viewportRef: RefObject<ViewportScale>;
|
||||
onPreviewTransform?: (
|
||||
@@ -229,36 +227,30 @@ export function useNodeTransformInteraction({
|
||||
return () => window.removeEventListener('blur', cancelGesture);
|
||||
}, [cancelGesture]);
|
||||
|
||||
useEffect(() => {
|
||||
const gesture = activeGestureRef.current;
|
||||
if (
|
||||
gesture &&
|
||||
(gesture.treeId !== activeImageId ||
|
||||
tree?.src_ui_design !== gesture.treeId)
|
||||
) {
|
||||
cancelGesture();
|
||||
}
|
||||
}, [activeImageId, cancelGesture, tree]);
|
||||
|
||||
const acceptsGestureEvent = useCallback(
|
||||
(event: ReactPointerEvent<HTMLDivElement>) => {
|
||||
const gesture = activeGestureRef.current;
|
||||
return (
|
||||
gesture !== null &&
|
||||
gesture.pointerId === event.pointerId &&
|
||||
gesture.treeId === activeImageId
|
||||
);
|
||||
return gesture !== null && gesture.pointerId === event.pointerId;
|
||||
},
|
||||
[activeImageId],
|
||||
[],
|
||||
);
|
||||
|
||||
const onNodePointerDown = useCallback(
|
||||
(event: ReactPointerEvent<HTMLDivElement>, node: UiNode) => {
|
||||
(
|
||||
event: ReactPointerEvent<HTMLDivElement>,
|
||||
node: UiNode,
|
||||
treeId?: string,
|
||||
) => {
|
||||
const tree = treeId
|
||||
? (trees.find((candidate) => candidate.src_ui_design === treeId) ??
|
||||
null)
|
||||
: null;
|
||||
const logicalSize = treeId ? (logicalSizes.get(treeId) ?? null) : null;
|
||||
if (
|
||||
activeGestureRef.current !== null ||
|
||||
event.button !== 0 ||
|
||||
spaceHeld ||
|
||||
!activeImageId ||
|
||||
!treeId ||
|
||||
node.id === tree?.root.id ||
|
||||
!isFiniteTransform(node.layout.transform)
|
||||
) {
|
||||
@@ -275,7 +267,7 @@ export function useNodeTransformInteraction({
|
||||
event.preventDefault();
|
||||
activeGestureRef.current = {
|
||||
kind: 'drag',
|
||||
treeId: activeImageId,
|
||||
treeId,
|
||||
nodeId: dragNode.id,
|
||||
pointerId: event.pointerId,
|
||||
target: event.currentTarget,
|
||||
@@ -292,14 +284,7 @@ export function useNodeTransformInteraction({
|
||||
),
|
||||
};
|
||||
},
|
||||
[
|
||||
activeImageId,
|
||||
keepChildrenUnchanged,
|
||||
logicalSize,
|
||||
spaceHeld,
|
||||
selectedNodeId,
|
||||
tree,
|
||||
],
|
||||
[keepChildrenUnchanged, logicalSizes, spaceHeld, selectedNodeId, trees],
|
||||
);
|
||||
|
||||
const onNodePointerMove = useCallback(
|
||||
@@ -309,6 +294,11 @@ export function useNodeTransformInteraction({
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
const tree =
|
||||
trees.find((candidate) => candidate.src_ui_design === gesture.treeId) ??
|
||||
null;
|
||||
const logicalSize = logicalSizes.get(gesture.treeId) ?? null;
|
||||
if (!tree || !logicalSize) return;
|
||||
if (!gesture.hasMoved && !passedDragThreshold(gesture, event)) return;
|
||||
const scale = viewportRef.current?.scale;
|
||||
if (!Number.isFinite(scale) || scale <= 0) {
|
||||
@@ -346,8 +336,8 @@ export function useNodeTransformInteraction({
|
||||
acceptsGestureEvent,
|
||||
cancelGesture,
|
||||
keepChildrenUnchanged,
|
||||
logicalSize,
|
||||
tree,
|
||||
logicalSizes,
|
||||
trees,
|
||||
viewportRef,
|
||||
],
|
||||
);
|
||||
@@ -389,12 +379,18 @@ export function useNodeTransformInteraction({
|
||||
event: ReactPointerEvent<HTMLDivElement>,
|
||||
node: UiNode,
|
||||
handle: ResizeHandle,
|
||||
treeId?: string,
|
||||
) => {
|
||||
const tree = treeId
|
||||
? (trees.find((candidate) => candidate.src_ui_design === treeId) ??
|
||||
null)
|
||||
: null;
|
||||
const logicalSize = treeId ? (logicalSizes.get(treeId) ?? null) : null;
|
||||
if (
|
||||
activeGestureRef.current !== null ||
|
||||
event.button !== 0 ||
|
||||
spaceHeld ||
|
||||
!activeImageId ||
|
||||
!treeId ||
|
||||
node.id === tree?.root.id ||
|
||||
!tree ||
|
||||
!logicalSize ||
|
||||
@@ -424,7 +420,7 @@ export function useNodeTransformInteraction({
|
||||
suppressNextNodeClickRef.current = false;
|
||||
activeGestureRef.current = {
|
||||
kind: 'resize',
|
||||
treeId: activeImageId,
|
||||
treeId,
|
||||
nodeId: node.id,
|
||||
pointerId: event.pointerId,
|
||||
target: event.currentTarget,
|
||||
@@ -445,7 +441,7 @@ export function useNodeTransformInteraction({
|
||||
),
|
||||
};
|
||||
},
|
||||
[activeImageId, keepChildrenUnchanged, logicalSize, spaceHeld, tree],
|
||||
[keepChildrenUnchanged, logicalSizes, spaceHeld, trees],
|
||||
);
|
||||
|
||||
const onNodeResizePointerMove = useCallback(
|
||||
@@ -459,6 +455,11 @@ export function useNodeTransformInteraction({
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
const tree =
|
||||
trees.find((candidate) => candidate.src_ui_design === gesture.treeId) ??
|
||||
null;
|
||||
const logicalSize = logicalSizes.get(gesture.treeId) ?? null;
|
||||
if (!tree || !logicalSize) return;
|
||||
if (!gesture.hasMoved && !passedDragThreshold(gesture, event)) return;
|
||||
const scale = viewportRef.current?.scale;
|
||||
if (!Number.isFinite(scale) || scale <= 0) {
|
||||
@@ -517,8 +518,8 @@ export function useNodeTransformInteraction({
|
||||
acceptsGestureEvent,
|
||||
cancelGesture,
|
||||
keepChildrenUnchanged,
|
||||
logicalSize,
|
||||
tree,
|
||||
logicalSizes,
|
||||
trees,
|
||||
viewportRef,
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user