diff --git a/frontend/libs/components/gallery-modal/src/lib/GalleryDraggableItem.tsx b/frontend/libs/components/gallery-modal/src/lib/GalleryDraggableItem.tsx index a60de4530..d0f02842a 100644 --- a/frontend/libs/components/gallery-modal/src/lib/GalleryDraggableItem.tsx +++ b/frontend/libs/components/gallery-modal/src/lib/GalleryDraggableItem.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCheck, @@ -7,6 +7,7 @@ import { faTrashCan, faUpload, } from "@fortawesome/pro-solid-svg-icons"; +import { LoadingSpinner } from "@storyteller/ui-loading-spinner"; import { twMerge } from "tailwind-merge"; import galleryDnd from "./galleryDnd"; import { Tooltip } from "@storyteller/ui-tooltip"; @@ -57,6 +58,34 @@ export const GalleryDraggableItem: React.FC = ({ const imgRef = useRef(null); const dragStarted = useRef(false); + // For freshly-completed videos the backend may still be generating the + // preview GIF, so the thumbnail URL 404s for a while. Show a spinner and + // refresh the image every 5s until it loads. + const isVideo = item.mediaClass === "video"; + const [retryAttempt, setRetryAttempt] = useState(0); + const [videoLoaded, setVideoLoaded] = useState(false); + + useEffect(() => { + setRetryAttempt(0); + setVideoLoaded(false); + }, [item.thumbnail]); + + useEffect(() => { + if (!isVideo || videoLoaded || !item.thumbnail) return; + const t = setInterval(() => setRetryAttempt((n) => n + 1), 5000); + return () => clearInterval(t); + }, [isVideo, videoLoaded, item.thumbnail]); + + const imgSrc = + isVideo && item.thumbnail && retryAttempt > 0 + ? `${item.thumbnail}${item.thumbnail.includes("?") ? "&" : "?"}_r=${retryAttempt}` + : item.thumbnail ?? undefined; + + const handleImgError = (e: React.SyntheticEvent) => { + if (isVideo) return; + onImageError(e); + }; + const handleDelete = () => { showActionReminder({ reminderType: "default", @@ -174,22 +203,33 @@ export const GalleryDraggableItem: React.FC = ({ Image not available ) : ( - {item.label} + {item.label} { + if (isVideo) setVideoLoaded(true); + }} + onError={handleImgError} + /> + {isVideo && !videoLoaded && ( +
+ +
)} - draggable={false} - onError={onImageError} - /> + )} {selected && (