import { memo, useCallback, useState, useRef } from "react"; import { useMountEffect } from "../../hooks/useMountEffect"; interface CompositionThumbnailProps { previewUrl: string; label: string; labelColor: string; selector?: string; selectorIndex?: number; seekTime?: number; duration?: number; width?: number; height?: number; } const CLIP_HEIGHT = 66; const THUMBNAIL_URL_VERSION = "v3"; export function buildCompositionThumbnailUrl({ previewUrl, seekTime = 2, duration = 5, selector, selectorIndex, origin, }: { previewUrl: string; seekTime?: number; duration?: number; selector?: string; selectorIndex?: number; origin: string; }): string { const thumbnailBase = previewUrl .replace("/preview/comp/", "/thumbnail/") .replace(/\/preview$/, "/thumbnail/index.html"); const midTime = seekTime + duration / 2; const thumbnailUrl = new URL(thumbnailBase, origin); thumbnailUrl.searchParams.set("t", midTime.toFixed(2)); thumbnailUrl.searchParams.set("v", THUMBNAIL_URL_VERSION); if (selector) { thumbnailUrl.searchParams.set("selector", selector); if (selectorIndex != null && selectorIndex > 0) { thumbnailUrl.searchParams.set("selectorIndex", String(selectorIndex)); } } return thumbnailUrl.toString(); } export const CompositionThumbnail = memo(function CompositionThumbnail({ previewUrl, label, labelColor, selector, selectorIndex, seekTime = 2, duration = 5, }: CompositionThumbnailProps) { const [containerWidth, setContainerWidth] = useState(0); const [loaded, setLoaded] = useState(false); const [aspect, setAspect] = useState(16 / 9); const roRef = useRef(null); const setContainerRef = useCallback((el: HTMLDivElement | null) => { roRef.current?.disconnect(); if (!el) return; const measured = el.parentElement?.clientWidth || el.clientWidth; setContainerWidth(measured); const target = el.parentElement || el; roRef.current = new ResizeObserver(([entry]) => { setContainerWidth(entry.contentRect.width); }); roRef.current.observe(target); }, []); useMountEffect(() => () => { roRef.current?.disconnect(); }); const url = buildCompositionThumbnailUrl({ previewUrl, seekTime, duration, selector, selectorIndex, origin: window.location.origin, }); const frameW = Math.max(48, Math.round(CLIP_HEIGHT * aspect)); const frameCount = containerWidth > 0 ? Math.max(1, Math.ceil(containerWidth / frameW)) : 1; return (
{ const img = e.currentTarget; if (img.naturalWidth > 0 && img.naturalHeight > 0) { setAspect(img.naturalWidth / img.naturalHeight); } setLoaded(true); }} className="hidden" /> {loaded && (
{Array.from({ length: frameCount }).map((_, i) => (
))}
)}
{label}
); });