perf(*): 创作工作台 - 优化首页按需加载与画布交互并修复历史迁移冲突
This commit is contained in:
@@ -6,7 +6,7 @@ import { SpotlightSurface } from "@/components/ui/aceternity/spotlight-surface";
|
||||
import { useCanvasOverlayLayer } from "@/components/canvas/canvas-overlay-layer";
|
||||
import { canvasThemes } from "@/lib/canvas-theme";
|
||||
import { aceternityMotion } from "@/lib/aceternity-motion";
|
||||
import { subscribeCanvasViewportPreview } from "@/lib/canvas/canvas-live-viewport";
|
||||
import { subscribeCanvasGraphicsViewportPreview, subscribeCanvasViewportPreview } from "@/lib/canvas/canvas-live-viewport";
|
||||
import { useThemeStore } from "@/stores/use-theme-store";
|
||||
import { CanvasNodeType, type CanvasNodeData, type ConnectionHandle, type Position, type ViewportTransform } from "@/types/canvas";
|
||||
|
||||
@@ -88,14 +88,8 @@ export function CanvasSelectionToolbar({ anchorRef, containerRef, count, childre
|
||||
);
|
||||
}
|
||||
|
||||
type NodePanelPlacement = "above" | "below";
|
||||
|
||||
// 拖动时保留当前上下方向,并允许短暂越过安全边界,避免面板在临界位置反复翻转。
|
||||
const NODE_PANEL_PLACEMENT_BUFFER = 32;
|
||||
|
||||
export function CanvasNodePanelOverlay({ node, viewport, containerRef, panelWidth, panelHeight = 190, dragOffset, isDragging = false, children }: { node: CanvasNodeData; viewport: ViewportTransform; containerRef: RefObject<HTMLDivElement | null>; panelWidth?: number; panelHeight?: number; dragOffset?: Position | null; isDragging?: boolean; children: ReactNode }) {
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const placementRef = useRef<NodePanelPlacement | null>(null);
|
||||
const { bringToFront, zIndex } = useCanvasOverlayLayer(`node-panel:${node.id}`, "var(--z-modal-overlay)");
|
||||
const initialWidth = resolveNodePanelWidth(node, viewport, panelWidth);
|
||||
const initialPosition = getNodePanelPosition(node, viewport, { width: containerRef.current?.clientWidth || 0, height: containerRef.current?.clientHeight || 0 }, initialWidth, panelHeight, dragOffset);
|
||||
@@ -108,36 +102,29 @@ export function CanvasNodePanelOverlay({ node, viewport, containerRef, panelWidt
|
||||
const container = containerRef.current;
|
||||
const panel = panelRef.current;
|
||||
if (!container || !panel) return;
|
||||
let liveViewport = viewport;
|
||||
let viewportSize = { width: container.clientWidth, height: container.clientHeight };
|
||||
const update = (nextViewport: ViewportTransform) => {
|
||||
liveViewport = nextViewport;
|
||||
const nextWidth = resolveNodePanelWidth(node, nextViewport, panelWidth);
|
||||
const nextHeight = panel.offsetHeight || panelHeight;
|
||||
panel.style.width = `${nextWidth}px`;
|
||||
panel.style.removeProperty("height");
|
||||
const position = getNodePanelPosition(
|
||||
node,
|
||||
nextViewport,
|
||||
{ width: container.clientWidth, height: container.clientHeight },
|
||||
viewportSize,
|
||||
nextWidth,
|
||||
nextHeight,
|
||||
panelHeight,
|
||||
dragOffset,
|
||||
isDragging ? placementRef.current : null,
|
||||
isDragging ? NODE_PANEL_PLACEMENT_BUFFER : 0,
|
||||
);
|
||||
if (isDragging && placementRef.current && placementRef.current !== position.placement) {
|
||||
panel.classList.remove("canvas-node-panel-placement-change");
|
||||
// 强制重排后重新加 class,确保每次上下翻转都能重新播放虚化动画。
|
||||
void panel.offsetWidth;
|
||||
panel.classList.add("canvas-node-panel-placement-change");
|
||||
}
|
||||
placementRef.current = isDragging ? position.placement : null;
|
||||
panel.style.left = `${position.left}px`;
|
||||
panel.style.top = `${position.top}px`;
|
||||
panel.style.transform = `translate3d(${position.left}px, ${position.top}px, 0)`;
|
||||
};
|
||||
update(viewport);
|
||||
const resizeObserver = new ResizeObserver(() => update(viewport));
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
viewportSize = { width: container.clientWidth, height: container.clientHeight };
|
||||
update(liveViewport);
|
||||
});
|
||||
resizeObserver.observe(container);
|
||||
resizeObserver.observe(panel);
|
||||
const unsubscribeViewport = subscribeCanvasViewportPreview(container, update);
|
||||
const unsubscribeViewport = subscribeCanvasGraphicsViewportPreview(container, update);
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
unsubscribeViewport();
|
||||
@@ -148,8 +135,9 @@ export function CanvasNodePanelOverlay({ node, viewport, containerRef, panelWidt
|
||||
<div
|
||||
ref={panelRef}
|
||||
data-canvas-no-zoom
|
||||
data-canvas-node-panel
|
||||
className="thin-scrollbar absolute max-w-[calc(100%_-_24px)] overflow-y-auto"
|
||||
style={{ left: initialPosition.left, top: initialPosition.top, width: initialWidth, maxHeight: "calc(100% - 84px)", zIndex }}
|
||||
style={{ left: 0, top: 0, transform: `translate3d(${initialPosition.left}px, ${initialPosition.top}px, 0)`, width: initialWidth, maxHeight: "calc(100% - 84px)", zIndex }}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
onPointerDownCapture={bringToFront}
|
||||
onFocusCapture={bringToFront}
|
||||
@@ -257,33 +245,18 @@ function getConnectionMenuPosition(position: Position, viewport: ViewportTransfo
|
||||
};
|
||||
}
|
||||
|
||||
function getNodePanelPosition(node: CanvasNodeData, viewport: ViewportTransform, viewportSize: { width: number; height: number }, panelWidth: number, panelHeight: number, dragOffset?: Position | null, lockedPlacement?: NodePanelPlacement | null, placementBuffer = 0) {
|
||||
export function getNodePanelPosition(node: CanvasNodeData, viewport: ViewportTransform, viewportSize: { width: number; height: number }, panelWidth: number, _panelHeight: number, dragOffset?: Position | null) {
|
||||
const gap = 10;
|
||||
const margin = 12;
|
||||
const topBoundary = 72;
|
||||
const offsetX = dragOffset?.x || 0;
|
||||
const offsetY = dragOffset?.y || 0;
|
||||
const nodeCenterX = viewport.x + (node.position.x + offsetX + node.width / 2) * viewport.k;
|
||||
const nodeTop = viewport.y + (node.position.y + offsetY) * viewport.k;
|
||||
const nodeBottom = viewport.y + (node.position.y + offsetY + node.height) * viewport.k;
|
||||
const maxLeft = Math.max(margin, viewportSize.width - panelWidth - margin);
|
||||
const left = clamp(nodeCenterX - panelWidth / 2, margin, maxLeft);
|
||||
const belowTop = nodeBottom + gap;
|
||||
const aboveTop = nodeTop - panelHeight - gap;
|
||||
const canFitBelow = belowTop + panelHeight <= viewportSize.height - margin;
|
||||
const canFitAbove = aboveTop >= topBoundary;
|
||||
let placement: NodePanelPlacement;
|
||||
if (lockedPlacement === "below") {
|
||||
placement = !canFitBelow && belowTop + panelHeight > viewportSize.height - margin + placementBuffer && canFitAbove ? "above" : "below";
|
||||
} else if (lockedPlacement === "above") {
|
||||
placement = !canFitAbove && aboveTop < topBoundary - placementBuffer && canFitBelow ? "below" : "above";
|
||||
} else {
|
||||
placement = canFitBelow ? "below" : "above";
|
||||
}
|
||||
const preferredTop = placement === "below" ? belowTop : aboveTop;
|
||||
return {
|
||||
left,
|
||||
top: clamp(preferredTop, topBoundary, Math.max(topBoundary, viewportSize.height - panelHeight - margin)),
|
||||
placement,
|
||||
top: nodeBottom + gap,
|
||||
placement: "below" as const,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user