diff --git a/src/components/MyCreationPickerModal.tsx b/src/components/MyCreationPickerModal.tsx index 5a8e700f..434d70fa 100644 --- a/src/components/MyCreationPickerModal.tsx +++ b/src/components/MyCreationPickerModal.tsx @@ -38,6 +38,7 @@ interface HistoryItem { resultList?: { images?: string[] | null; originImages?: string[] | null; + thumbs?: string[] | null; video?: string | null; voice?: string | null; cover?: string | null; @@ -165,7 +166,7 @@ function getHistoryMediaUrl(item: HistoryItem, kind: HistoryTabKey): string | nu const mediaKind = getHistoryKind(item, kind); const result = firstResult(item); if (mediaKind === "image") { - return firstString(result?.originImages || undefined) || firstString(result?.images || undefined) || firstString(item.images) || null; + return firstString(result?.images || undefined) || firstString(result?.originImages || undefined) || firstString(item.images) || null; } if (mediaKind === "video") return result?.video || item.video || result?.cover || item.cover || null; return result?.voice || item.voice || item.audio || firstString(item.audios) || null; @@ -175,7 +176,7 @@ function getHistoryPreviewUrl(item: HistoryItem, kind: HistoryTabKey): string | const mediaKind = getHistoryKind(item, kind); const result = firstResult(item); if (mediaKind === "image") { - return firstString(result?.images || undefined) || firstString(item.thumbs) || firstString(item.images) || null; + return firstString(result?.thumbs || undefined) || firstString(item.thumbs) || firstString(result?.images || undefined) || firstString(item.images) || null; } if (mediaKind === "video") return result?.cover || item.coverThumb || item.cover || result?.video || item.video || null; return null; @@ -460,15 +461,7 @@ export function MyCreationPickerModal({ onClose }: MyCreationPickerModalProps) { mimeType: mediaKind === "audio" ? "audio/asset" : mediaKind === "video" ? "video/asset" : "image/asset", duration: item.duration ?? null, dimensions: item.width && item.height ? { width: item.width, height: item.height } : undefined, - resolver: mediaKind === "image" - ? { - type: "popi-asset", - id: item.id, - name: title, - url: mediaUrl, - thumbUrl: previewUrl || undefined, - } - : null, + resolver: null, }, position: getCanvasPosition(), addNode, diff --git a/src/utils/__tests__/smartMediaNode.test.ts b/src/utils/__tests__/smartMediaNode.test.ts index 9aeacdf8..f738a608 100644 --- a/src/utils/__tests__/smartMediaNode.test.ts +++ b/src/utils/__tests__/smartMediaNode.test.ts @@ -1,8 +1,27 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createSmartMediaNode } from "@/utils/smartMediaNode"; +class MockImage { + static shouldError = false; + onload: (() => void) | null = null; + onerror: (() => void) | null = null; + width = 1200; + height = 800; + + set src(_value: string) { + setTimeout(() => { + if (MockImage.shouldError) { + this.onerror?.(); + return; + } + this.onload?.(); + }, 0); + } +} + describe("createSmartMediaNode", () => { beforeEach(() => { + MockImage.shouldError = false; vi.stubGlobal("fetch", vi.fn(async () => ({ ok: true, json: async () => ({ success: true }), @@ -13,7 +32,33 @@ describe("createSmartMediaNode", () => { vi.unstubAllGlobals(); }); - it("passes media dimensions in node data", async () => { + it("uses read image dimensions before supplied dimensions", async () => { + vi.stubGlobal("Image", MockImage); + const addNode = vi.fn(() => "smartImage-1"); + + await createSmartMediaNode({ + source: { + kind: "image", + url: "data:image/png;base64,test", + filename: "test.png", + dimensions: { width: 1600, height: 900 }, + }, + position: { x: 100, y: 200 }, + addNode, + }); + + expect(addNode).toHaveBeenCalledWith( + "smartImage", + { x: 100, y: 200 }, + expect.objectContaining({ + dimensions: { width: 1200, height: 800 }, + }) + ); + }); + + it("falls back to supplied dimensions when image dimensions cannot be read", async () => { + MockImage.shouldError = true; + vi.stubGlobal("Image", MockImage); const addNode = vi.fn(() => "smartImage-1"); await createSmartMediaNode({ @@ -36,6 +81,30 @@ describe("createSmartMediaNode", () => { ); }); + it("reads image dimensions when dimensions are null", async () => { + vi.stubGlobal("Image", MockImage); + const addNode = vi.fn(() => "smartImage-1"); + + await createSmartMediaNode({ + source: { + kind: "image", + url: "https://example.com/test.png", + filename: "test.png", + dimensions: null, + }, + position: { x: 100, y: 200 }, + addNode, + }); + + expect(addNode).toHaveBeenCalledWith( + "smartImage", + { x: 100, y: 200 }, + expect.objectContaining({ + dimensions: { width: 1200, height: 800 }, + }) + ); + }); + it("keeps default node size when media has no dimensions", async () => { const addNode = vi.fn(() => "smartAudio-1"); diff --git a/src/utils/smartMediaNode.ts b/src/utils/smartMediaNode.ts index 33f7260b..b29303be 100644 --- a/src/utils/smartMediaNode.ts +++ b/src/utils/smartMediaNode.ts @@ -158,6 +158,18 @@ function resolveSmartImageNode(options: CreateSmartMediaNodeOptions, nodeId: str }); } +function isValidMediaDimensions( + dimensions: SmartMediaSource["dimensions"] | undefined +): dimensions is { width: number; height: number } { + return Boolean( + dimensions && + Number.isFinite(dimensions.width) && + Number.isFinite(dimensions.height) && + dimensions.width > 0 && + dimensions.height > 0 + ); +} + export async function readSmartImageDimensions(src: string): Promise<{ width: number; height: number } | null> { if (typeof window === "undefined") return null; @@ -224,9 +236,8 @@ export async function buildSmartMediaNodeData(source: SmartMediaSource): Promise const defaults = await buildDefaultGenerationNodeData(nodeType); if (source.kind === "image") { - const dimensions = source.dimensions === undefined - ? await readSmartImageDimensions(source.url) - : source.dimensions; + const readDimensions = await readSmartImageDimensions(source.url); + const dimensions = readDimensions ?? (isValidMediaDimensions(source.dimensions) ? source.dimensions : null); return { nodeType, data: {