diff --git a/components/canvas/LiblibVideoCanvasReplica.tsx b/components/canvas/LiblibVideoCanvasReplica.tsx index fcc3fcf63..223eb1061 100644 --- a/components/canvas/LiblibVideoCanvasReplica.tsx +++ b/components/canvas/LiblibVideoCanvasReplica.tsx @@ -32,6 +32,7 @@ import { type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode, + type WheelEvent as ReactWheelEvent, useCallback, useEffect, useMemo, @@ -234,6 +235,9 @@ const TOOLBAR_ITEMS: Array<{ id: Exclude; label: string; icon ]; const DEFAULT_VIEWPORT = { x: 0, y: 0, zoom: 1 }; +const PRESET_VIEWPORT = { x: 144, y: 100, zoom: 0.77 }; +const PRESET_WORKFLOW_CENTER = { x: 1024, y: 512 }; +const CANVAS_EXTENT = 20000; const TARGET_AVATAR = "https://liblibai-online.liblib.cloud/web/1656e2f1b3c74f1ba823b610f1b2cbdf/0c3eb5e2-1dc0-405c-a823-07b2570237f8.jpg?x-oss-process=image/resize,w_100,m_lfit/format,webp"; @@ -753,27 +757,27 @@ function createStarterWorkflow( if (starterTitle === "角色三视图") { const preset = "character-three-view"; - const distance = 370; + const distance = 100; const left = createPresetMediaNode({ action: "IMAGE_RESOURCE", - height: 446, + height: 520, mediaType: "image", mediaUrl: CHARACTER_IMAGE_URL, preset, title: "角色图", - width: 734, - x: Math.round(center.x - 734 - distance / 2), + width: 874, + x: Math.round(center.x - 874 - distance / 2), y: Math.round(center.y - 320), }); const right = createPresetMediaNode({ action: "IMAGE_GENERATE", generated: true, - height: 446, + height: 520, mediaType: "image", mediaUrl: CHARACTER_THREE_VIEW_URL, preset, title: "角色三视图", - width: 556, + width: 660, x: Math.round(center.x + distance / 2), y: Math.round(center.y - 320), }); @@ -1121,14 +1125,11 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib const createStarter = useCallback( (starter: (typeof STARTER_CARDS)[number]) => { - const center = - typeof window === "undefined" - ? { x: 1000, y: 520 } - : toCanvasPoint(window.innerWidth / 2, window.innerHeight / 2); - const workflow = createStarterWorkflow(starter.title, center); + const workflow = createStarterWorkflow(starter.title, PRESET_WORKFLOW_CENTER); if (workflow) { addNodes(workflow.nodes, workflow.edges); setSelectedNodeId(workflow.selectedId); + setViewport(PRESET_VIEWPORT); setCanvasCommand(null); setNodeContextMenu(null); setActivePanel(null); @@ -1156,7 +1157,7 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib })); addNodes(newNodes, newEdges); }, - [addNodes, toCanvasPoint] + [addNodes] ); const updateNodeDraft = useCallback((key: string, value: unknown) => { @@ -1253,6 +1254,37 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib })); }, []); + const zoomAtPoint = useCallback((delta: number, clientX: number, clientY: number) => { + setViewport((current) => { + const nextZoom = Math.min(2.4, Math.max(0.3, Number((current.zoom + delta).toFixed(2)))); + if (nextZoom === current.zoom) return current; + const worldX = (clientX - current.x) / current.zoom; + const worldY = (clientY - current.y) / current.zoom; + return { + x: Math.round(clientX - worldX * nextZoom), + y: Math.round(clientY - worldY * nextZoom), + zoom: nextZoom, + }; + }); + }, []); + + const onCanvasWheel = useCallback( + (event: ReactWheelEvent) => { + if ((event.target as HTMLElement).closest("[data-canvas-overlay]")) return; + event.preventDefault(); + if (event.ctrlKey || event.metaKey) { + zoomAtPoint(event.deltaY > 0 ? -0.08 : 0.08, event.clientX, event.clientY); + return; + } + setViewport((current) => ({ + ...current, + x: Math.round(current.x - event.deltaX), + y: Math.round(current.y - event.deltaY), + })); + }, + [zoomAtPoint] + ); + const copyShareLink = useCallback(async () => { const url = typeof window === "undefined" @@ -1280,13 +1312,14 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib onPointerDown={startCanvasDrag} onPointerMove={onPointerMove} onPointerUp={onPointerUp} + onWheel={onCanvasWheel} ref={canvasRef} >
{nodes.map((node) => ( @@ -1976,7 +2009,7 @@ function CharacterPresetToolbar() { return ( <> -
+
setToolbarMenu((current) => current === "panorama" ? null : "panorama")} /> setToolbarMenu((current) => current === "orbit" ? null : "orbit")} /> setToolbarMenu((current) => current === "lighting" ? null : "lighting")} /> @@ -2291,8 +2324,14 @@ function PresetComposerPanel({ thumbnailUrl?: string; title: string; }) { + const [draftPrompt, setDraftPrompt] = useState(prompt); + + useEffect(() => { + setDraftPrompt(prompt); + }, [prompt]); + return ( -
+
{title} @@ -2302,7 +2341,12 @@ function PresetComposerPanel({
) : null}
-
{prompt}
+