diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx index 8d98f78b33..e45d6bad14 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/Activity.tsx @@ -224,39 +224,43 @@ export function Activity() { } > -
- {jobs.length > 0 && ( -
- {jobs.map((job) => ( - - ))} -
- )} - - {isFirstLoad.current ? ( -
- -

Retrieving Activities

-
- ) : jobs.length === 0 && completedItems.length === 0 ? ( -
-

No activities yet

-
- ) : ( -
-
- {completedItems.map((item) => ( - + {(close) => ( +
+ {jobs.length > 0 && ( +
+ {jobs.map((job) => ( + ))}
-
- )} -
+ )} + + {isFirstLoad.current ? ( +
+ +

Retrieving Activities

+
+ ) : jobs.length === 0 && completedItems.length === 0 ? ( +
+

No activities yet

+
+ ) : ( +
+
+ {completedItems.map((item) => ( +
+ +
+ ))} +
+
+ )} +
+ )} ); diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx index e2bbcad3dc..5a5c1f109a 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/GenerateModals/CompletedCard.tsx @@ -1,7 +1,11 @@ import { useState } from "react"; import dayjs from "dayjs"; -import { LightboxModal } from "@storyteller/ui-lightbox-modal"; -import { GetCdnOrigin } from "~/api/GetCdnOrigin"; +import { + galleryModalLightboxMediaId, + galleryModalLightboxVisible, + galleryModalLightboxImage, +} from "@storyteller/ui-gallery-modal"; +import type { GalleryItem } from "@storyteller/ui-gallery-modal"; interface Props { job: { @@ -13,18 +17,16 @@ interface Props { maybe_media_links_thumbnail?: string; maybe_result?: { entity_token?: string; + media_links?: { + cdn_url?: string; + }; }; }; } export function CompletedCard({ job }: Props) { const [loadError, setLoadError] = useState(false); - const [isModalOpen, setIsModalOpen] = useState(false); - const cdnOrigin = GetCdnOrigin(); - const mediaPath = job?.public_bucket_path || ""; - const downloadLink = `${cdnOrigin}${mediaPath}`; - //const imageUrl = downloadLink; - const fullImageUrl = downloadLink; + // Info: Gallery modal will handle displaying full media. Compute thumbnail only. // NB: Try to use the thumbnail instead of the link to the full asset. // Passing around full asset URLs and putting them into image tags is causing CORS errors if those assets are not images. @@ -36,7 +38,19 @@ export function CompletedCard({ job }: Props) { <>
setIsModalOpen(true)} + onClick={() => { + galleryModalLightboxMediaId.value = + job.maybe_result?.entity_token || null; + galleryModalLightboxImage.value = { + id: job.maybe_result?.entity_token || job.token, + label: job.maybe_title || "Image", + thumbnail: thumbnailUrl, + fullImage: job.maybe_result?.media_links?.cdn_url || thumbnailUrl, + createdAt: job.updated_at, + mediaClass: undefined, + } as GalleryItem; + galleryModalLightboxVisible.value = true; + }} >
@@ -70,25 +84,7 @@ export function CompletedCard({ job }: Props) { */}
- setIsModalOpen(false)} - isOpen={isModalOpen} - onClose={() => setIsModalOpen(false)} - imageUrl={fullImageUrl} - imageAlt={job.maybe_title ?? "unknown"} - onImageError={() => setLoadError(true)} - title={job.maybe_title} - createdAt={job.updated_at} - mediaId={job.maybe_result?.entity_token} - downloadUrl={downloadLink} - additionalInfo={ - job.maybe_style_name && ( -
- Style: {job.maybe_style_name} -
- ) - } - /> + {/* LightboxModal removed – now using top level GalleryModal */} ); } 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 5ea96be2ec..f42fba5640 100644 --- a/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx +++ b/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx @@ -23,6 +23,9 @@ import { galleryModalVisibleDuringDrag, galleryReopenAfterDragSignal, galleryModalVisibleViewMode, + galleryModalLightboxMediaId, + galleryModalLightboxImage, + galleryModalLightboxVisible, } from "./galleryModalSignals"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { @@ -85,10 +88,9 @@ export const GalleryModal = React.memo( forceFilter, }: GalleryModalProps) => { const [loading, setLoading] = useState(false); - const [lightboxImage, setLightboxImage] = useState( - null - ); - const [isLightboxVisible, setIsLightboxVisible] = useState(false); + // Lightbox state is now handled via signals + const lightboxImageSignal = galleryModalLightboxImage; + const lightboxVisibleSignal = galleryModalLightboxVisible; const [failedImageUrls] = useState>(new Set()); const [username, setUsername] = useState(""); const forceFilterRef = useRef(forceFilter); @@ -114,7 +116,7 @@ export const GalleryModal = React.memo( const [hasMore, setHasMore] = useState(true); const pageSize = 25; - const imageUrl = lightboxImage?.fullImage || ""; + const imageUrl = lightboxImageSignal.value?.fullImage || ""; const api = useMemo(() => new GalleryModalApi(), []); const usersApi = useMemo(() => new UsersApi(), []); @@ -296,17 +298,18 @@ export const GalleryModal = React.memo( if (mode === "select" && onSelectItem) { onSelectItem(item.id); } else { - setLightboxImage(item); - setIsLightboxVisible(true); + lightboxImageSignal.value = item; + lightboxVisibleSignal.value = true; + galleryModalLightboxMediaId.value = item.id; } }, [mode, onSelectItem] ); const handleCloseLightbox = useCallback(() => { - setIsLightboxVisible(false); - // Wait for animation to complete before removing the image - setTimeout(() => setLightboxImage(null), 300); + lightboxVisibleSignal.value = false; + lightboxImageSignal.value = null; + galleryModalLightboxMediaId.value = null; }, []); const handleDeselectAll = useCallback(() => { @@ -322,6 +325,18 @@ export const GalleryModal = React.memo( useSignals(); + // Synchronize lightbox image when external media id signal is set + useEffect(() => { + if (galleryModalLightboxMediaId.value && allItems.length > 0) { + const target = allItems.find( + (it) => it.id === galleryModalLightboxMediaId.value + ); + if (target) { + lightboxImageSignal.value = target; + } + } + }, [galleryModalLightboxMediaId.value, allItems]); + // Compute gap class based on gridColumns let gapClass = "gap-0.5"; if (gridColumns <= 4) gapClass = "gap-1.5"; @@ -625,25 +640,23 @@ export const GalleryModal = React.memo(
- - mode === "view" - ? (galleryModalVisibleViewMode.value = false) - : onClose?.() - } - imageUrl={imageUrl} - imageAlt={lightboxImage?.label || ""} - onImageError={() => imageUrl && handleImageError(imageUrl)} - title={lightboxImage?.label} - createdAt={lightboxImage?.createdAt} - downloadUrl={imageUrl} - mediaId={lightboxImage?.id} - onDownloadClicked={onDownloadClicked} - onAddToSceneClicked={onAddToSceneClicked} - mediaClass={lightboxImage?.mediaClass} - /> + {mode === "view" && ( + (galleryModalVisibleViewMode.value = false)} + imageUrl={imageUrl} + imageAlt={lightboxImageSignal.value?.label || ""} + onImageError={() => imageUrl && handleImageError(imageUrl)} + title={lightboxImageSignal.value?.label} + createdAt={lightboxImageSignal.value?.createdAt} + downloadUrl={imageUrl} + mediaId={lightboxImageSignal.value?.id} + onDownloadClicked={onDownloadClicked} + onAddToSceneClicked={onAddToSceneClicked} + mediaClass={lightboxImageSignal.value?.mediaClass} + /> + )} ); } diff --git a/frontend/libs/components/gallery-modal/src/lib/galleryModalSignals.ts b/frontend/libs/components/gallery-modal/src/lib/galleryModalSignals.ts index 2347f2bcdc..6fd8bcd032 100644 --- a/frontend/libs/components/gallery-modal/src/lib/galleryModalSignals.ts +++ b/frontend/libs/components/gallery-modal/src/lib/galleryModalSignals.ts @@ -1,5 +1,11 @@ import { signal } from "@preact/signals-react"; +// Gallery Modal Signals export const galleryModalVisibleDuringDrag = signal(true); export const galleryReopenAfterDragSignal = signal(false); export const galleryModalVisibleViewMode = signal(false); + +// Lightbox Modal Signals +export const galleryModalLightboxMediaId = signal(null); +export const galleryModalLightboxVisible = signal(false); +export const galleryModalLightboxImage = signal(null); 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 df59dee523..7067efa90e 100644 --- a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx +++ b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx @@ -270,7 +270,7 @@ export function LightboxModal({ )} - {downloadUrl && + {/* {downloadUrl && (onDownloadClicked ? ( - ))} + ))} */} + + {onDownloadClicked && downloadUrl && ( + + )}
) : null}
diff --git a/frontend/libs/components/popover/src/lib/popover.tsx b/frontend/libs/components/popover/src/lib/popover.tsx index 463ce069bc..7c2781bd10 100644 --- a/frontend/libs/components/popover/src/lib/popover.tsx +++ b/frontend/libs/components/popover/src/lib/popover.tsx @@ -40,7 +40,7 @@ interface PopoverMenuProps { showIconsInList?: boolean; mode?: "default" | "toggle" | "button" | "hoverSelect"; triggerLabel?: string | ReactNode; - children?: ReactNode; + children?: ReactNode | ((close: () => void) => ReactNode); buttonClassName?: string; panelClassName?: string; onPanelAction?: (action: string) => void; @@ -269,7 +269,11 @@ export const PopoverMenu = ({ )} {mode === "default" && children ? ( -
{children}
+
+ {typeof children === "function" + ? children(close) + : children} +
) : mode === "hoverSelect" ? (
{items.map((item, index) => (