From b55aa0d2cb2ddda2a3f5ee411369dd142c8b3fb5 Mon Sep 17 00:00:00 2001 From: BFlat Date: Tue, 18 Aug 2026 11:06:28 +0700 Subject: [PATCH] Add last viewed badge for previously viewed media (#1910) for both desktop and webapp --- .../GenerationGalleryGrid.tsx | 14 +++++++---- .../GenerationGalleryList.tsx | 14 +++++++---- .../generation-gallery/useLightboxNav.ts | 23 +++++++++++++------ .../DesktopGenerationGallery.tsx | 3 +++ .../app/src/pages/PageAudio/CreateAudio.tsx | 5 ++++ .../app/src/pages/PageImage/TextToImage.tsx | 5 ++++ .../app/src/pages/PageVideo/ImageToVideo.tsx | 5 ++++ .../components/generation-list/src/index.ts | 1 + .../generation-list/src/lib/GalleryCard.tsx | 17 ++++++++++++-- .../generation-list/src/lib/GalleryRow.tsx | 15 ++++++++++-- .../src/lib/GenerationGridView.tsx | 4 ++++ .../src/lib/GenerationListView.tsx | 4 ++++ .../src/lib/last-viewed-store.ts | 20 ++++++++++++++++ 13 files changed, 109 insertions(+), 21 deletions(-) create mode 100644 frontend/libs/components/generation-list/src/lib/last-viewed-store.ts diff --git a/frontend/apps/artcraft-webapp/src/components/generation-gallery/GenerationGalleryGrid.tsx b/frontend/apps/artcraft-webapp/src/components/generation-gallery/GenerationGalleryGrid.tsx index 57d89f60e9..c3cdb8b3e5 100644 --- a/frontend/apps/artcraft-webapp/src/components/generation-gallery/GenerationGalleryGrid.tsx +++ b/frontend/apps/artcraft-webapp/src/components/generation-gallery/GenerationGalleryGrid.tsx @@ -4,9 +4,9 @@ import { Tooltip } from "@storyteller/ui-tooltip"; import { GenerationGridView, type GalleryItem, - is3DMediaClass, type RecreateSlotContext, useGallerySelectionStore, + useLastViewedGenerationStore, } from "@storyteller/ui-generation-list"; import { useRecreateFromPromptToken } from "../../lib/recreate"; import { useGalleryItemActions } from "./useGalleryItemActions"; @@ -34,6 +34,7 @@ export function GenerationGalleryGrid({ }: GenerationGalleryProps) { const selectionActive = useGallerySelectionStore((s) => s.active); const selectedIds = useGallerySelectionStore((s) => s.ids); + const lastViewedId = useLastViewedGenerationStore((s) => s.id); return ( ); } @@ -70,14 +72,16 @@ function CardRecreateButton({ mediaClass, kind, }: RecreateSlotContext) { - // Recreate only targets the image/video create pages; 3D generations have no - // recreate flow, so map to a valid class for the hook and render nothing. + // Recreate only targets the image/video create pages; 3D and audio + // generations have no recreate flow, so map to a valid class for the hook + // (it must run unconditionally) and render nothing. + const hasRecreateFlow = mediaClass === "image" || mediaClass === "video"; const { isRecreating, handleRecreate } = useRecreateFromPromptToken( promptToken, - is3DMediaClass(mediaClass) ? "image" : mediaClass, + hasRecreateFlow ? mediaClass : "image", ); - if (is3DMediaClass(mediaClass)) return null; + if (!hasRecreateFlow) return null; if (kind === "failed") { return ( diff --git a/frontend/apps/artcraft-webapp/src/components/generation-gallery/GenerationGalleryList.tsx b/frontend/apps/artcraft-webapp/src/components/generation-gallery/GenerationGalleryList.tsx index 8dfc44a444..b90e0e8123 100644 --- a/frontend/apps/artcraft-webapp/src/components/generation-gallery/GenerationGalleryList.tsx +++ b/frontend/apps/artcraft-webapp/src/components/generation-gallery/GenerationGalleryList.tsx @@ -4,9 +4,9 @@ import { Tooltip } from "@storyteller/ui-tooltip"; import { GenerationListView, type GalleryItem, - is3DMediaClass, type RecreateSlotContext, useGallerySelectionStore, + useLastViewedGenerationStore, } from "@storyteller/ui-generation-list"; import { toast } from "../toast/toast"; import { useRecreateFromPromptToken } from "../../lib/recreate"; @@ -38,6 +38,7 @@ export function GenerationGalleryList({ }: GenerationGalleryProps) { const selectionActive = useGallerySelectionStore((s) => s.active); const selectedIds = useGallerySelectionStore((s) => s.ids); + const lastViewedId = useLastViewedGenerationStore((s) => s.id); return ( ); } @@ -86,14 +88,16 @@ function RowRecreateButton({ mediaClass, kind, }: RecreateSlotContext) { - // Recreate only targets the image/video create pages; 3D generations have no - // recreate flow, so map to a valid class for the hook and render nothing. + // Recreate only targets the image/video create pages; 3D and audio + // generations have no recreate flow, so map to a valid class for the hook + // (it must run unconditionally) and render nothing. + const hasRecreateFlow = mediaClass === "image" || mediaClass === "video"; const { isRecreating, handleRecreate } = useRecreateFromPromptToken( promptToken, - is3DMediaClass(mediaClass) ? "image" : mediaClass, + hasRecreateFlow ? mediaClass : "image", ); - if (is3DMediaClass(mediaClass)) return null; + if (!hasRecreateFlow) return null; return (