From dbedb2593028383e2ea6466c4e9203088b45c170 Mon Sep 17 00:00:00 2001 From: BFlat Date: Mon, 20 Oct 2025 14:00:29 +0700 Subject: [PATCH] handle thumbnails error (#883) --- .../resources/gifs/artcraft-logo-load.gif | Bin 0 -> 37707 bytes .../components/signaled/TopBar/TaskQueue.tsx | 31 +++-- frontend/libs/common/src/index.ts | 1 + frontend/libs/common/src/lib/utils/index.ts | 1 + .../common/src/lib/utils/thumbnail-utils.ts | 120 ++++++++++++++++++ .../gallery-modal/src/lib/gallery-modal.tsx | 22 ++-- .../lightbox-modal/src/lib/lightbox-modal.tsx | 41 +++--- 7 files changed, 166 insertions(+), 50 deletions(-) create mode 100644 frontend/apps/editor3d/app/public/resources/gifs/artcraft-logo-load.gif create mode 100644 frontend/libs/common/src/lib/utils/index.ts create mode 100644 frontend/libs/common/src/lib/utils/thumbnail-utils.ts diff --git a/frontend/apps/editor3d/app/public/resources/gifs/artcraft-logo-load.gif b/frontend/apps/editor3d/app/public/resources/gifs/artcraft-logo-load.gif new file mode 100644 index 0000000000000000000000000000000000000000..6d5e2bcac26b0a18e67ae6b621b9653a9959ca00 GIT binary patch literal 37707 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Ξ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 literal 0 HcmV?d00001 diff --git a/frontend/apps/editor3d/app/src/components/signaled/TopBar/TaskQueue.tsx b/frontend/apps/editor3d/app/src/components/signaled/TopBar/TaskQueue.tsx index 9a222e6a7c..2216cd537a 100644 --- a/frontend/apps/editor3d/app/src/components/signaled/TopBar/TaskQueue.tsx +++ b/frontend/apps/editor3d/app/src/components/signaled/TopBar/TaskQueue.tsx @@ -32,6 +32,11 @@ import { import { CloseButton } from "@storyteller/ui-close-button"; import { ActionReminderModal } from "@storyteller/ui-action-reminder-modal"; import { TaskMediaFileClass } from "@storyteller/api-enums"; +import { + getThumbnailUrl, + THUMBNAIL_SIZES, + getPlaceholderForMediaClass, +} from "@storyteller/common"; type InProgressTask = { id: string; @@ -132,15 +137,10 @@ const CompletedCard = ({ src={task.thumbnailUrl} alt={task.title} onError={(e) => { - console.log("Failed to load thumbnail", e); - let errorPlaceholder = "/resources/placeholders/placeholder.png"; - switch (task.mediaFileClass) { - case TaskMediaFileClass.Video: - errorPlaceholder = - "/resources/placeholders/placeholder_play.png"; - break; - } - e.currentTarget.src = errorPlaceholder; + e.currentTarget.src = getPlaceholderForMediaClass( + task.mediaFileClass, + ); + e.currentTarget.style.opacity = "0.3"; }} className="h-full w-full object-cover" /> @@ -290,13 +290,12 @@ export const TaskQueue = () => { .map((t: TaskQueueItem) => ({ id: t.id, ...formatTitleParts(t), - thumbnailUrl: t.completed_item?.primary_media_file - ?.maybe_thumbnail_url_template - ? t.completed_item?.primary_media_file?.maybe_thumbnail_url_template.replace( - "{WIDTH}", - "250", - ) - : undefined, + thumbnailUrl: + getThumbnailUrl( + t.completed_item?.primary_media_file + ?.maybe_thumbnail_url_template, + { width: THUMBNAIL_SIZES.MEDIUM }, + ) || undefined, imageUrls: t.completed_item?.primary_media_file?.cdn_url ? [t.completed_item?.primary_media_file?.cdn_url] : [], diff --git a/frontend/libs/common/src/index.ts b/frontend/libs/common/src/index.ts index 86bf6b115f..46c3b1c3f7 100644 --- a/frontend/libs/common/src/index.ts +++ b/frontend/libs/common/src/index.ts @@ -2,3 +2,4 @@ export * from "./lib/assets"; export * from "./lib/enums"; export * from "./lib/interfaces"; export * from "./lib/types"; +export * from "./lib/utils"; diff --git a/frontend/libs/common/src/lib/utils/index.ts b/frontend/libs/common/src/lib/utils/index.ts new file mode 100644 index 0000000000..2df68b71a0 --- /dev/null +++ b/frontend/libs/common/src/lib/utils/index.ts @@ -0,0 +1 @@ +export * from "./thumbnail-utils"; diff --git a/frontend/libs/common/src/lib/utils/thumbnail-utils.ts b/frontend/libs/common/src/lib/utils/thumbnail-utils.ts new file mode 100644 index 0000000000..5392a2f372 --- /dev/null +++ b/frontend/libs/common/src/lib/utils/thumbnail-utils.ts @@ -0,0 +1,120 @@ +export const THUMBNAIL_SIZES = { + SMALL: 128, + MEDIUM: 250, + LARGE: 512, + EXTRA_LARGE: 1024, +} as const; + +export type ThumbnailSize = + (typeof THUMBNAIL_SIZES)[keyof typeof THUMBNAIL_SIZES]; + +interface ThumbnailOptions { + width?: ThumbnailSize | number; + addCors?: boolean; +} + +export function getThumbnailUrl( + urlTemplate: string | null | undefined, + options: ThumbnailOptions = {} +): string | null { + if (!urlTemplate) return null; + + const { width = THUMBNAIL_SIZES.MEDIUM, addCors = false } = options; + + let url = urlTemplate.replace("{WIDTH}", width.toString()); + + if (addCors) { + const corsUrl = addCorsParam(url); + if (corsUrl) url = corsUrl; + } + + return url; +} + +export function addCorsParam(url: string | null | undefined): string | null { + if (!url) return null; + return `${url}?cors=1`; +} + +interface MediaThumbnailOptions { + size?: ThumbnailSize | number; + addCors?: boolean; +} + +export function getMediaThumbnail( + mediaLinks: + | { + maybe_thumbnail_template?: string | null; + cdn_url?: string | null; + maybe_video_previews?: { + animated?: string | null; + } | null; + } + | null + | undefined, + mediaClass: string | undefined, + options: MediaThumbnailOptions = {} +): string | null { + if (!mediaLinks) return null; + + const { size = THUMBNAIL_SIZES.MEDIUM, addCors = false } = options; + + let thumbnailUrl: string | null = null; + + if (mediaClass === "video" && mediaLinks.maybe_video_previews?.animated) { + thumbnailUrl = mediaLinks.maybe_video_previews.animated; + } else if (mediaLinks.maybe_thumbnail_template) { + thumbnailUrl = getThumbnailUrl(mediaLinks.maybe_thumbnail_template, { + width: size, + }); + } else if (mediaLinks.cdn_url) { + thumbnailUrl = mediaLinks.cdn_url; + } + + if (thumbnailUrl && addCors) { + thumbnailUrl = addCorsParam(thumbnailUrl); + } + + return thumbnailUrl; +} + +export function getContextImageThumbnail( + contextImage: { + media_links: { + maybe_thumbnail_template?: string | null; + cdn_url: string; + }; + }, + options: MediaThumbnailOptions = {} +): { thumbnail: string; fullSize: string } { + const { size = THUMBNAIL_SIZES.SMALL } = options; + const fullSizeWidth = THUMBNAIL_SIZES.LARGE; + + const thumbnail = contextImage.media_links.maybe_thumbnail_template + ? getThumbnailUrl(contextImage.media_links.maybe_thumbnail_template, { + width: size, + }) || contextImage.media_links.cdn_url + : contextImage.media_links.cdn_url; + + const fullSize = contextImage.media_links.maybe_thumbnail_template + ? getThumbnailUrl(contextImage.media_links.maybe_thumbnail_template, { + width: fullSizeWidth, + }) || contextImage.media_links.cdn_url + : contextImage.media_links.cdn_url; + + return { thumbnail, fullSize }; +} + +export const PLACEHOLDER_IMAGES = { + DEFAULT: "/resources/gifs/artcraft-logo-load.gif", + VIDEO: "/resources/gifs/artcraft-logo-load.gif", +} as const; + +export function getPlaceholderForMediaClass(mediaClass?: string): string { + switch (mediaClass) { + case "video": + return PLACEHOLDER_IMAGES.VIDEO; + default: + return PLACEHOLDER_IMAGES.DEFAULT; + } +} diff --git a/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx b/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx index 8344ef9362..cef3522d5b 100644 --- a/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx +++ b/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx @@ -19,6 +19,11 @@ import { import { twMerge } from "tailwind-merge"; import { GalleryDraggableItem } from "./GalleryDraggableItem"; import { useSignals } from "@preact/signals-react/runtime"; +import { + getThumbnailUrl, + THUMBNAIL_SIZES, + PLACEHOLDER_IMAGES, +} from "@storyteller/common"; import { galleryModalVisibleDuringDrag, galleryReopenAfterDragSignal, @@ -262,7 +267,6 @@ export const GalleryModal = React.memo( response = await api.listUserMediaFiles(query); if (response.success && response.data) { - const thumbnail_size = 250; const newItems = response.data .filter( (item: any) => item.media_type !== FilterMediaType.SCENE_JSON @@ -272,15 +276,12 @@ export const GalleryModal = React.memo( label: getLabel(item), thumbnail: item.media_class === "video" - ? item.media_links.maybe_video_previews.animated + ? item.media_links.maybe_video_previews?.animated : item.media_class === "dimensional" ? item.cover_image?.maybe_cover_image_public_bucket_url - : item.media_links.maybe_thumbnail_template?.replace( - "{WIDTH}", - thumbnail_size.toString() - ), - // TODO(bt): Thumbnail template URL may be wrong for videos and other asset types, but I'm - // trying to plumb it through for the inpainting editor, which is just images. + : getThumbnailUrl(item.media_links.maybe_thumbnail_template, { + width: THUMBNAIL_SIZES.MEDIUM, + }), thumbnailUrlTemplate: item.media_links.maybe_thumbnail_template, fullImage: item.media_links.cdn_url, createdAt: item.created_at, @@ -289,7 +290,6 @@ export const GalleryModal = React.memo( (item.filter_media_classes ? item.filter_media_classes[0] : "image"), - // assetType for 3D assets so drop handlers can decide between character vs object assetType: item.media_class === "dimensional" ? item.maybe_animation_type || @@ -641,9 +641,9 @@ export const GalleryModal = React.memo( selected={selectedItemIds.includes(item.id)} onClick={() => handleItemClick(item)} onImageError={(e) => { - // NB: Replace the broken thumbnail with a placeholder. e.currentTarget.src = - "/resources/placeholders/placeholder.png"; + PLACEHOLDER_IMAGES.DEFAULT; + e.currentTarget.style.opacity = "0.3"; handleImageError(item.thumbnail!); }} disableTooltipAndBadge={mode === "select"} diff --git a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx index 6b5a1c7933..dc5cd6db6f 100644 --- a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx +++ b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx @@ -26,6 +26,12 @@ import { } from "@storyteller/model-list"; import useEmblaCarousel from "embla-carousel-react"; import type { EmblaOptionsType } from "embla-carousel"; +import { + addCorsParam, + getContextImageThumbnail, + THUMBNAIL_SIZES, + PLACEHOLDER_IMAGES, +} from "@storyteller/common"; interface LightboxModalProps { isOpen: boolean; @@ -301,7 +307,7 @@ export function LightboxModal({ setMediaLoaded(false); const img = new Image(); - img.src = `${selectedImageUrl}?cors=1`; + img.src = addCorsParam(selectedImageUrl) || selectedImageUrl; const handleLoad = () => setMediaLoaded(true); const handleError = () => setMediaLoaded(true); @@ -376,7 +382,7 @@ export function LightboxModal({
{`${imageAlt { @@ -384,7 +390,8 @@ export function LightboxModal({ if (idx === selectedIndex) { setMediaLoaded(true); e.currentTarget.src = - "/resources/placeholders/placeholder.png"; + PLACEHOLDER_IMAGES.DEFAULT; + e.currentTarget.style.opacity = "0.3"; } }} onLoad={() => { @@ -421,7 +428,7 @@ export function LightboxModal({ )} > {`Thumbnail @@ -520,7 +527,6 @@ export function LightboxModal({ )}
- {/* Context Images */} {contextImages && contextImages.length > 0 && (
@@ -528,30 +534,19 @@ export function LightboxModal({
{contextImages.map((contextImage, index) => { - const thumbnailUrl = contextImage.media_links - .maybe_thumbnail_template - ? contextImage.media_links.maybe_thumbnail_template.replace( - "{WIDTH}", - "128" - ) - : contextImage.media_links.cdn_url; - - const fullSizeUrl = contextImage.media_links - .maybe_thumbnail_template - ? contextImage.media_links.maybe_thumbnail_template.replace( - "{WIDTH}", - "512" - ) - : contextImage.media_links.cdn_url; + const { thumbnail, fullSize } = + getContextImageThumbnail(contextImage, { + size: THUMBNAIL_SIZES.SMALL, + }); return (
setRefPreviewUrl(fullSizeUrl)} + onClick={() => setRefPreviewUrl(fullSize)} > {`Reference @@ -743,7 +738,7 @@ export function LightboxModal({
Reference preview