perf(canvas): 画布媒体 - 优化媒体加载、播放器与节点交互 (#357)

Merge origin/main at fb089b2 and retain canvas media performance, player layout, and interaction updates.

Co-authored-by: ddCat <2695092045@qq.com>
This commit is contained in:
wurendao007andddCat authored and GitHub committed 2026-08-31 09:00:37 +08:00
1 parent 21facff923
commit 8ebceecbb6
58 files changed
+2662 -207

No files matched your search

+46 -5
View File
@@ -93,6 +93,7 @@ import { CanvasProjectSelectionToolbar } from "./canvas-project-selection-toolba
import { CanvasProjectStatusDialogs } from "./canvas-project-status-dialogs";
import { CanvasProjectWorldLayers } from "./canvas-project-world-layers";
import { CanvasNodeActionContext } from "@/components/canvas/canvas-node-action-context";
import { bringCanvasNodeToFront, type CanvasNodeStackOrder } from "@/lib/canvas/canvas-node-stack-order";
import { PortraitClearanceModal } from "@/components/canvas/portrait-clearance/portrait-clearance-modal";
import { CanvasNodeGraphContext, type CanvasNodeGraphContextValue } from "@/components/canvas/canvas-node-graph-context";
import { CanvasRefreshShell } from "./canvas-refresh-shell";
@@ -239,6 +240,10 @@ function InfiniteCanvasPage() {
nodesRef.current = next;
setNodesState(next);
}, []);
const [nodeStackOrder, setNodeStackOrder] = useState<CanvasNodeStackOrder>([]);
const bringNodeToFront = useCallback((nodeId: string) => {
setNodeStackOrder((current) => bringCanvasNodeToFront(current, nodeId));
}, []);
const [connections, setConnections] = useState<CanvasConnection[]>([]);
const [chatSessions, setChatSessions] = useState<CanvasAssistantSession[]>([]);
const [activeChatId, setActiveChatId] = useState<string | null>(null);
@@ -323,8 +328,17 @@ function InfiniteCanvasPage() {
useEffect(() => {
didInitialCenterRef.current = false;
setNodeStackOrder([]);
}, [projectId]);
useEffect(() => {
const nodeIds = new Set(nodes.map((node) => node.id));
setNodeStackOrder((current) => {
const next = current.filter((nodeId) => nodeIds.has(nodeId));
return next.length === current.length ? current : next;
});
}, [nodes]);
const connectionsRef = useRef(connections);
const chatSessionsRef = useRef(chatSessions);
const activeChatIdRef = useRef(activeChatId);
@@ -1002,6 +1016,9 @@ function InfiniteCanvasPage() {
}, []);
const handleSelectedNodeClick = useCallback((node: CanvasNodeData) => {
// Selection is transient, but the LibTV-style paint order survives
// deselection so a clicked lower node stays above its neighbours.
if (node.type !== CanvasNodeType.Frame) bringNodeToFront(node.id);
if (node.type === CanvasNodeType.Drawing) {
setDialogNodeId(null);
setDrawingNodeId(node.id);
@@ -1020,6 +1037,23 @@ function InfiniteCanvasPage() {
return currentNode?.type === CanvasNodeType.Config ? current : node.id;
});
}
}, [bringNodeToFront, nodesRef]);
const handleNodeBringToFront = useCallback((nodeId: string) => {
const node = nodesRef.current.find((item) => item.id === nodeId);
if (node && node.type !== CanvasNodeType.Frame) bringNodeToFront(nodeId);
}, [bringNodeToFront, nodesRef]);
const handleNodeDragEnd = useCallback((nodeId: string) => {
const node = nodesRef.current.find((item) => item.id === nodeId);
if (!node || node.type === CanvasNodeType.Script || node.type === CanvasNodeType.Drawing || node.type === PORTRAIT_CLEARANCE_NODE_TYPE) {
setDialogNodeId(null);
return;
}
// A drag selects a new node even though it is not a click. Keep the
// generation editor bound to the node most recently moved so a stale
// panel from the previous node cannot reappear after mouse-up.
setDialogNodeId(node.id);
}, [nodesRef]);
const handleCanvasDeselect = useCallback(() => {
@@ -1042,7 +1076,9 @@ function InfiniteCanvasPage() {
cancelPendingConnectionCreate,
onCanvasSelectionStart: handleCanvasSelectionStart,
onNodeInteractionStart: handleNodeInteractionStart,
onNodeBringToFront: handleNodeBringToFront,
onNodeClick: handleSelectedNodeClick,
onNodeDragEnd: handleNodeDragEnd,
onBatchConnectionTarget: handleBatchConnectionTargetClick,
onLinkedFolderDrop: archiveNodesToLinkedFolder,
onDeselect: handleCanvasDeselect,
@@ -1279,6 +1315,10 @@ function InfiniteCanvasPage() {
});
}, [mentionReferencesByNodeId, setNodes]);
const dialogNode = dialogNodeId ? nodeById.get(dialogNodeId) || null : null;
// dragPreview is published on the same pointer-down frame as isNodeDragging.
// Treat either signal as moving so floating editors disappear before the
// first preview transform is painted and never affect drag layout.
const isCanvasNodeMoving = isNodeDragging || Boolean(dragPreview?.nodeIds.size);
const subtitleNode = subtitleNodeId ? nodeById.get(subtitleNodeId) || null : null;
const timelineNode = timelineNodeId ? nodeById.get(timelineNodeId) || null : null;
const frameNode = frameDialogNodeId ? nodeById.get(frameDialogNodeId) || null : null;
@@ -2177,6 +2217,7 @@ function InfiniteCanvasPage() {
connectionTargetNodeId={connectionTargetNodeId}
nodeById={nodeById}
visibleNodes={visibleNodes}
nodeStackOrder={nodeStackOrder}
frameChildrenById={frameChildrenById}
linkedFolderPreviewNodesById={linkedFolderPreviewNodesById}
dragPreview={dragPreview}
@@ -2332,7 +2373,7 @@ function InfiniteCanvasPage() {
) : null}
</div>
{angleNode?.metadata?.content ? (
{angleNode?.metadata?.content && !isCanvasNodeMoving ? (
<CanvasNodePanelOverlay
node={angleNode}
viewport={viewport}
@@ -2352,7 +2393,7 @@ function InfiniteCanvasPage() {
</CanvasNodePanelOverlay>
) : null}
{emotionNode?.metadata?.content ? (
{emotionNode?.metadata?.content && !isCanvasNodeMoving ? (
<CanvasEmotionWorkspace
node={emotionNode}
viewport={viewport}
@@ -2366,7 +2407,7 @@ function InfiniteCanvasPage() {
/>
) : null}
{dialogNode && dialogNode.type !== CanvasNodeType.Script && dialogNode.type !== CanvasNodeType.Drawing && !selectionBox ? (
{dialogNode && dialogNode.type !== CanvasNodeType.Script && dialogNode.type !== CanvasNodeType.Drawing && !selectionBox && !isCanvasNodeMoving ? (
<CanvasNodePanelOverlay
node={dialogNode}
viewport={viewport}
@@ -2391,7 +2432,7 @@ function InfiniteCanvasPage() {
/>
) : null}
{selectedNodeBounds && !selectionBox && !isNodeDragging ? (
{selectedNodeBounds && !selectionBox && !isCanvasNodeMoving ? (
<CanvasProjectSelectionToolbar
anchorRef={selectionBoundsElementRef}
containerRef={containerRef}
@@ -2422,7 +2463,7 @@ function InfiniteCanvasPage() {
) : null}
<CanvasNodeToolbar
node={isNodeDragging || nodeImageSettingsOpen || emotionNodeId ? null : toolbarNode}
node={isCanvasNodeMoving || nodeImageSettingsOpen || emotionNodeId ? null : toolbarNode}
workspaceMode={workspaceMode}
viewport={viewport}
containerRef={containerRef}