export const MAX_CANVAS_TEMPLATE_REPLACE_SOURCES = 5; export type CanvasTemplateReplaceSource = { id: string; imageUrl: string; title: string; origin: "canvas" | "upload"; sourceNodeId?: string; }; function normalizeSource( source: CanvasTemplateReplaceSource | null | undefined ): CanvasTemplateReplaceSource | null { if (!source) return null; const imageUrl = String(source.imageUrl || "").trim(); if (!/^https?:\/\//i.test(imageUrl)) return null; return { id: String(source.id || imageUrl).trim() || imageUrl, imageUrl, title: String(source.title || "替换素材").trim() || "替换素材", origin: source.origin === "upload" ? "upload" : "canvas", sourceNodeId: String(source.sourceNodeId || "").trim() || undefined, }; } export function normalizeCanvasTemplateReplaceSources( sources: Array ): CanvasTemplateReplaceSource[] { const seenUrls = new Set(); const normalized: CanvasTemplateReplaceSource[] = []; for (const source of sources) { const nextSource = normalizeSource(source); if (!nextSource || seenUrls.has(nextSource.imageUrl)) continue; seenUrls.add(nextSource.imageUrl); normalized.push(nextSource); if (normalized.length >= MAX_CANVAS_TEMPLATE_REPLACE_SOURCES) break; } return normalized; } export function createCanvasTemplateReplaceSlots( sources: Array ): Array { const slots = normalizeCanvasTemplateReplaceSources(sources); return Array.from( { length: MAX_CANVAS_TEMPLATE_REPLACE_SOURCES }, (_, index) => slots[index] || null ); } export function replaceCanvasTemplateSourceSlot( slots: Array, slotIndex: number, source: CanvasTemplateReplaceSource | null ): Array { const nextSlots = createCanvasTemplateReplaceSlots([]); slots.slice(0, MAX_CANVAS_TEMPLATE_REPLACE_SOURCES).forEach((item, index) => { nextSlots[index] = normalizeSource(item); }); if (slotIndex < 0 || slotIndex >= MAX_CANVAS_TEMPLATE_REPLACE_SOURCES) { return nextSlots; } const normalizedSource = normalizeSource(source); if (normalizedSource) { nextSlots.forEach((item, index) => { if (index !== slotIndex && item?.imageUrl === normalizedSource.imageUrl) { nextSlots[index] = null; } }); } nextSlots[slotIndex] = normalizedSource; return nextSlots; }