Files
ttstd-infinite-canvas/web/src/components/canvas/canvas-drawing-excalidraw-editor.tsx
爱笑的毛毛虫 9b3ec218e2 fix(canvas): 人物质感变体自动触发生成,修复缩略图不变 #91 (#92)
- use-canvas-media-tools.ts: createPortraitTextureNode 改为 generatePortraitTextureNode,
  节点创建后立即触发后端生成任务(与情绪/多视角/局部编辑保持一致):
  使用 nodeReferenceImage 获取源图引用,buildPortraitTexturePrompt 构建提示词,
  runBackendCanvasGenerationTask 执行生成,完成后更新缩略图
- project.tsx: 同步重命名 createPortraitTextureNode → generatePortraitTextureNode
- canvas-drawing-excalidraw-editor.tsx: getDimensions 参数补充 number 类型注解
- canvas-drawing-editor-modal.tsx: Pick 类型补充 engine 字段

Co-authored-by: sikongyue <3155157671@qq.com>
2026-08-03 20:11:13 +08:00

65 lines
3.2 KiB
TypeScript

import { forwardRef, useImperativeHandle, useRef } from "react";
import { Excalidraw, exportToBlob, getNonDeletedElements, serializeAsJSON } from "@excalidraw/excalidraw";
import type { ExcalidrawImperativeAPI, ExcalidrawInitialDataState } from "@excalidraw/excalidraw/types";
import "@excalidraw/excalidraw/index.css";
import type { CanvasDrawingEditorHandle, CanvasDrawingEditorProps } from "@/components/canvas/canvas-drawing-editor-types";
const DRAWING_RENDER_MAX_DIMENSION = 2048;
const DRAWING_RENDER_PADDING = 24;
export const CanvasDrawingExcalidrawEditor = forwardRef<CanvasDrawingEditorHandle, CanvasDrawingEditorProps>(function CanvasDrawingExcalidrawEditor({ snapshot, colorScheme, onReady }, ref) {
const apiRef = useRef<ExcalidrawImperativeAPI | null>(null);
useImperativeHandle(ref, () => ({
createSave: async () => {
const api = apiRef.current;
if (!api) throw new Error("Excalidraw 编辑器尚未准备完成");
const elements = api.getSceneElementsIncludingDeleted();
const appState = api.getAppState();
const files = api.getFiles();
const serialized = serializeAsJSON(elements, appState, files, "local");
const savedSnapshot = JSON.parse(serialized) as unknown;
const visibleElements = getNonDeletedElements(elements);
if (!visibleElements.length) return { snapshot: savedSnapshot, preview: null, render: null };
let outputWidth = 1;
let outputHeight = 1;
const blob = await exportToBlob({
elements: visibleElements,
appState: { ...appState, exportBackground: true, exportWithDarkMode: false, viewBackgroundColor: "#ffffff" },
files,
mimeType: "image/png",
exportPadding: DRAWING_RENDER_PADDING,
getDimensions: (width: number, height: number) => {
const scale = Math.min(4, DRAWING_RENDER_MAX_DIMENSION / Math.max(1, width, height));
outputWidth = Math.max(1, Math.round(width * scale));
outputHeight = Math.max(1, Math.round(height * scale));
return { width: outputWidth, height: outputHeight, scale };
},
});
const render = { blob, pageId: "excalidraw-page", width: outputWidth, height: outputHeight, mimeType: blob.type || "image/png", background: "white" as const };
return { snapshot: savedSnapshot, preview: blob, render };
},
}), []);
const initialData = snapshot && typeof snapshot === "object"
? { ...(snapshot as ExcalidrawInitialDataState), scrollToContent: true }
: { elements: [], appState: { viewBackgroundColor: "#ffffff" }, files: {}, scrollToContent: true };
return (
<Excalidraw
initialData={initialData}
langCode="zh-CN"
theme={colorScheme}
autoFocus
excalidrawAPI={(api) => {
apiRef.current = api;
api.setActiveTool({ type: "freedraw" });
onReady();
}}
UIOptions={{ canvasActions: { loadScene: false, saveToActiveFile: false } }}
/>
);
});