import { memo, useCallback, useEffect, useRef, useState, type ReactNode } from "react"; import { createPortal } from "react-dom"; import { ChevronLeftIcon, ChevronRightIcon, XIcon } from "lucide-react"; import { Button } from "../ui/button"; import type { ExpandedImageItem, ExpandedImagePreview } from "./ExpandedImagePreview"; import { resolveExternalWebLinkHost } from "./externalLinkContextMenu"; import { useAssetUrlRefresh, useAssetUrlState } from "../../assets/assetUrls"; import { OpenMediaLink } from "../media/OpenMediaLink"; import { MediaActions, type MediaActionSource } from "../media/MediaActions"; import { MediaVideoPlayer } from "../media/MediaVideoPlayer"; import { isContextMenuOpen } from "../../contextMenuFallback"; import { composerFloatingLayerProps } from "./composerEventScope"; interface ExpandedImageDialogProps { preview: ExpandedImagePreview; onClose: () => void; } const EXPANDED_MEDIA_STATE_CLASS_NAME = "flex aspect-auto h-48 min-h-0 w-[min(92vw,32rem)] flex-col items-center justify-center gap-3 rounded-lg border border-border/70 bg-black p-6 text-center text-sm text-white shadow-2xl"; function ExpandedMediaFailure({ children }: { children: ReactNode }) { return (
{children}
); } function ExpandedVideo({ item }: { readonly item: ExpandedImageItem }) { const asset = item.actionsSource?.asset; const assetUrl = useAssetUrlState(asset?.environmentId ?? null, asset?.resource ?? null); const refreshAssetUrl = useAssetUrlRefresh(asset?.environmentId ?? null, asset?.resource ?? null); const src = asset ? assetUrl._tag === "Success" ? assetUrl.url + (item.srcFragment ?? "") : null : item.src; return ( ); } export const ExpandedImageDialog = memo(function ExpandedImageDialog({ preview, onClose, }: ExpandedImageDialogProps) { const [imageOffset, setImageOffset] = useState(0); const [failedImageSrc, setFailedImageSrc] = useState(null); const index = (preview.index + imageOffset + preview.images.length) % preview.images.length; const item = preview.images[index]; const source: MediaActionSource = item?.actionsSource ?? { kind: item?.type === "video" ? "video" : "image", name: item?.name ?? "Media", src: item?.src ?? null, }; const openFile = source.onOpenFile; const actionsSource: MediaActionSource = openFile ? { ...source, onOpenFile: () => { openFile(); onClose(); }, } : source; const navigateImage = useCallback((direction: -1 | 1) => { setImageOffset((current) => current + direction); }, []); // The element that opened the preview gets focus back on close. Without // this a close button click leaves focus on the unmounted dialog, and the // composer that owned the opener reads that as a blur and rests. const openerRef = useRef(null); useEffect(() => { openerRef.current = document.activeElement; return () => { const opener = openerRef.current; if (opener instanceof HTMLElement && opener.isConnected) { opener.focus({ preventScroll: true }); } }; }, []); useEffect(() => { const onKeyDown = (event: globalThis.KeyboardEvent) => { if (event.defaultPrevented || isContextMenuOpen()) { return; } if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); onClose(); return; } if (preview.images.length <= 1) return; if (event.key === "ArrowLeft") { event.preventDefault(); event.stopPropagation(); navigateImage(-1); return; } if (event.key !== "ArrowRight") return; event.preventDefault(); event.stopPropagation(); navigateImage(1); }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, [navigateImage, onClose, preview.images.length]); if (!item) return null; const mediaLabel = item.type === "video" ? "video" : "image"; const openOriginalLink = item.originalUrl && resolveExternalWebLinkHost(item.originalUrl) !== null ? ( ) : null; return createPortal(
)}
{item.type === "video" ? ( ) : item.src === null || failedImageSrc === item.src ? (

{openOriginalLink ? "This image could not be loaded." : "Image unavailable. The file may have been moved or deleted."}

{openOriginalLink}
) : ( {item.name} setFailedImageSrc(item.src)} /> )}

{item.name} {preview.images.length > 1 ? ` (${index + 1}/${preview.images.length})` : ""}

{preview.images.length > 1 && ( )}
, document.body, ); });