mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 17:08:13 +00:00
lightbox and download button fixes
This commit is contained in:
1 parent
a7270f4685
commit
e97e5c239e
6 files changed
+128
-92
No files matched your search
@@ -224,39 +224,43 @@ export function Activity() {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="max-h-[480px] overflow-y-auto">
|
||||
{jobs.length > 0 && (
|
||||
<div>
|
||||
{jobs.map((job) => (
|
||||
<InProgressCard key={job.job_token} job={job} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isFirstLoad.current ? (
|
||||
<div className="flex h-48 w-full flex-col justify-center gap-4 p-4 text-center align-middle">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
spin
|
||||
size="2x"
|
||||
className="text-gray-400"
|
||||
/>
|
||||
<h3 className="text-gray-300">Retrieving Activities</h3>
|
||||
</div>
|
||||
) : jobs.length === 0 && completedItems.length === 0 ? (
|
||||
<div className="flex h-48 w-full flex-col justify-center gap-4 p-4 text-center align-middle">
|
||||
<h3 className="text-lg text-gray-300">No activities yet</h3>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
{completedItems.map((item) => (
|
||||
<CompletedCard key={item.token} job={item} />
|
||||
{(close) => (
|
||||
<div className="max-h-[480px] overflow-y-auto">
|
||||
{jobs.length > 0 && (
|
||||
<div>
|
||||
{jobs.map((job) => (
|
||||
<InProgressCard key={job.job_token} job={job} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isFirstLoad.current ? (
|
||||
<div className="flex h-48 w-full flex-col justify-center gap-4 p-4 text-center align-middle">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
spin
|
||||
size="2x"
|
||||
className="text-gray-400"
|
||||
/>
|
||||
<h3 className="text-gray-300">Retrieving Activities</h3>
|
||||
</div>
|
||||
) : jobs.length === 0 && completedItems.length === 0 ? (
|
||||
<div className="flex h-48 w-full flex-col justify-center gap-4 p-4 text-center align-middle">
|
||||
<h3 className="text-lg text-gray-300">No activities yet</h3>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
{completedItems.map((item) => (
|
||||
<div key={item.token} onClick={close}>
|
||||
<CompletedCard job={item} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</PopoverMenu>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
+24
-28
@@ -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) {
|
||||
<>
|
||||
<div
|
||||
className="flex w-full items-center justify-between rounded-lg p-2 text-start transition-all duration-150 hover:cursor-pointer hover:bg-white/10"
|
||||
onClick={() => 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;
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex aspect-square h-14 w-14 justify-center overflow-hidden rounded-lg border border-[#A9A9A9]/50 bg-black/60">
|
||||
@@ -70,25 +84,7 @@ export function CompletedCard({ job }: Props) {
|
||||
</a> */}
|
||||
</div>
|
||||
|
||||
<LightboxModal
|
||||
onCloseGallery={() => 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 && (
|
||||
<div className="text-sm text-white/60">
|
||||
Style: {job.maybe_style_name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
/>
|
||||
{/* LightboxModal removed – now using top level GalleryModal */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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<GalleryItem | null>(
|
||||
null
|
||||
);
|
||||
const [isLightboxVisible, setIsLightboxVisible] = useState(false);
|
||||
// Lightbox state is now handled via signals
|
||||
const lightboxImageSignal = galleryModalLightboxImage;
|
||||
const lightboxVisibleSignal = galleryModalLightboxVisible;
|
||||
const [failedImageUrls] = useState<Set<string>>(new Set());
|
||||
const [username, setUsername] = useState<string>("");
|
||||
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(
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<LightboxModal
|
||||
isOpen={isLightboxVisible}
|
||||
onClose={handleCloseLightbox}
|
||||
onCloseGallery={() =>
|
||||
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" && (
|
||||
<LightboxModal
|
||||
isOpen={lightboxVisibleSignal.value}
|
||||
onClose={handleCloseLightbox}
|
||||
onCloseGallery={() => (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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<string | null>(null);
|
||||
export const galleryModalLightboxVisible = signal(false);
|
||||
export const galleryModalLightboxImage = signal<any>(null);
|
||||
@@ -270,7 +270,7 @@ export function LightboxModal({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{downloadUrl &&
|
||||
{/* {downloadUrl &&
|
||||
(onDownloadClicked ? (
|
||||
<Button
|
||||
icon={faDownToLine}
|
||||
@@ -294,7 +294,20 @@ export function LightboxModal({
|
||||
>
|
||||
<Button icon={faDownToLine}>Download</Button>
|
||||
</a>
|
||||
))}
|
||||
))} */}
|
||||
|
||||
{onDownloadClicked && downloadUrl && (
|
||||
<Button
|
||||
icon={faDownToLine}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
gtagEvent("download_clicked");
|
||||
await onDownloadClicked(downloadUrl, mediaClass);
|
||||
}}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -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 = ({
|
||||
</div>
|
||||
)}
|
||||
{mode === "default" && children ? (
|
||||
<div className="text-sm text-white">{children}</div>
|
||||
<div className="text-sm text-white">
|
||||
{typeof children === "function"
|
||||
? children(close)
|
||||
: children}
|
||||
</div>
|
||||
) : mode === "hoverSelect" ? (
|
||||
<div className="flex flex-col gap-0 text-sm text-white">
|
||||
{items.map((item, index) => (
|
||||
|
||||
Reference in new issue
Block a user