mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-02 06:51:14 +08:00
cf1ea2646f
Move the ~30 module-scope pure functions out of the 3.5k-line project.tsx into three focused lib modules, with identical names and signatures. No behavior change; project.tsx imports them back. - canvas-node-factory.ts: createCanvasNode, *Metadata builders, referenceUrl, applyNodeConfigPatch. - canvas-node-geometry.ts: nodeBounds, group drop/snap, connection anchors, normalizeConnection, hidden-batch checks. - canvas-generation-helpers.ts: generation config/inputs, reference hydration, retry-source lookup, angle prompt builders, misc pure helpers. project.tsx drops from 3536 to ~3230 lines. Also removes imports that were only used by the moved helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
72 lines
3.5 KiB
TypeScript
72 lines
3.5 KiB
TypeScript
import { getNodeSpec, NODE_DEFAULT_SIZE } from "@/constant/canvas";
|
|
import { nodeSizeFromRatio } from "@/lib/canvas/canvas-node-size";
|
|
import type { AiConfig } from "@/stores/use-config-store";
|
|
import type { UploadedImage } from "@/services/image-storage";
|
|
import type { UploadedFile } from "@/services/file-storage";
|
|
import type { ReferenceImage } from "@/types/image";
|
|
import { CanvasNodeType, type CanvasImageGenerationType, type CanvasNodeData, type CanvasNodeMetadata, type CanvasNodeTypeId, type Position } from "@/types/canvas";
|
|
|
|
export function createCanvasNode(type: CanvasNodeTypeId, position: Position, metadata?: CanvasNodeMetadata): CanvasNodeData {
|
|
const spec = getNodeSpec(type);
|
|
const id = `${type}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
|
|
return {
|
|
id,
|
|
type,
|
|
title: spec.title,
|
|
position: {
|
|
x: position.x - spec.width / 2,
|
|
y: position.y - spec.height / 2,
|
|
},
|
|
width: spec.width,
|
|
height: spec.height,
|
|
metadata: { ...spec.metadata, ...metadata },
|
|
};
|
|
}
|
|
|
|
export function imageMetadata(image: UploadedImage): CanvasNodeMetadata {
|
|
return { content: image.url, storageKey: image.storageKey, status: "success", naturalWidth: image.width, naturalHeight: image.height, bytes: image.bytes, mimeType: image.mimeType };
|
|
}
|
|
|
|
export function videoMetadata(video: UploadedFile): CanvasNodeMetadata {
|
|
return { content: video.url, storageKey: video.storageKey, status: "success", naturalWidth: video.width, naturalHeight: video.height, bytes: video.bytes, mimeType: video.mimeType || "video/mp4", durationMs: video.durationMs };
|
|
}
|
|
|
|
export function audioMetadata(audio: UploadedFile): CanvasNodeMetadata {
|
|
return { content: audio.url, storageKey: audio.storageKey, status: "success", bytes: audio.bytes, mimeType: audio.mimeType || "audio/mpeg", durationMs: audio.durationMs };
|
|
}
|
|
|
|
export function referenceUrl(image: ReferenceImage) {
|
|
return image.storageKey || image.url || (!image.dataUrl.startsWith("data:") ? image.dataUrl : undefined);
|
|
}
|
|
|
|
export function buildImageGenerationMetadata(type: CanvasImageGenerationType, config: AiConfig, count: number, references: ReferenceImage[]): CanvasNodeMetadata {
|
|
return {
|
|
generationType: type,
|
|
model: config.model,
|
|
size: config.size,
|
|
quality: config.quality,
|
|
...(config.background ? { background: config.background } : {}),
|
|
count,
|
|
references: references.map(referenceUrl).filter((url): url is string => Boolean(url)),
|
|
};
|
|
}
|
|
|
|
export function buildAudioGenerationMetadata(config: AiConfig): CanvasNodeMetadata {
|
|
return {
|
|
model: config.model,
|
|
audioVoice: config.audioVoice,
|
|
audioFormat: config.audioFormat,
|
|
audioSpeed: config.audioSpeed,
|
|
audioInstructions: config.audioInstructions,
|
|
};
|
|
}
|
|
|
|
export function applyNodeConfigPatch(node: CanvasNodeData, patch: Partial<CanvasNodeData["metadata"]>) {
|
|
const safePatch = patch || {};
|
|
const next = { ...node, metadata: { ...node.metadata, ...safePatch } };
|
|
const spec = node.type === CanvasNodeType.Video ? NODE_DEFAULT_SIZE[CanvasNodeType.Video] : NODE_DEFAULT_SIZE[CanvasNodeType.Image];
|
|
const size = typeof safePatch.size === "string" && !node.metadata?.content ? nodeSizeFromRatio(safePatch.size, spec.width, spec.height) : null;
|
|
return size && (node.type === CanvasNodeType.Image || node.type === CanvasNodeType.Video) ? { ...next, ...size, position: { x: node.position.x + node.width / 2 - size.width / 2, y: node.position.y + node.height / 2 - size.height / 2 } } : next;
|
|
}
|