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 ? (
) : 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 ? (
-
+ {typeof children === "function"
+ ? children(close)
+ : children}
+
) : mode === "hoverSelect" ? (