diff --git a/services/web-ui/public/visuals.jsx b/services/web-ui/public/visuals.jsx index 754d245..b69988f 100644 --- a/services/web-ui/public/visuals.jsx +++ b/services/web-ui/public/visuals.jsx @@ -1,32 +1,47 @@ -// visuals.jsx - reusable visual elements: thumbnails, waveforms, sparklines, filmstrips +// visuals.jsx - reusable visual elements -function AssetThumb({ asset, size = "md" }) { - const aspect = size === "tall" ? "9 / 16" : "16 / 9"; - const seed = asset.seed || 1; +const _thumbCache = new Map(); - if (asset.type === "audio") { +function AssetThumb({ asset, size = 'md' }) { + const aspect = size === 'tall' ? '9 / 16' : '16 / 9'; + const [thumbUrl, setThumbUrl] = React.useState(_thumbCache.get(asset.id) || null); + + React.useEffect(() => { + if (!asset.id || thumbUrl || !asset.thumbnail_s3_key) return; + let cancelled = false; + fetch('/api/v1/assets/' + asset.id + '/thumbnail', { credentials: 'include' }) + .then(r => r.ok ? r.json() : null) + .then(d => { if (!cancelled && d && d.url) { _thumbCache.set(asset.id, d.url); setThumbUrl(d.url); } }) + .catch(() => {}); + return () => { cancelled = true; }; + }, [asset.id, asset.thumbnail_s3_key]); + + if (asset.type === 'audio' || asset.media_type === 'audio') { return (