// screens-library.jsx
function Library({ navigate, onOpenAsset, openProject }) {
const { ASSETS: ALL_ASSETS, BINS, PROJECTS } = window.ZAMPP_DATA;
const [view, setView] = React.useState('grid');
const [filter, setFilter] = React.useState('all');
const [search, setSearch] = React.useState('');
let assets = openProject
? ALL_ASSETS.filter(function(a) { return a.project_id === openProject.id; })
: ALL_ASSETS;
if (filter !== 'all') assets = assets.filter(function(a) { return a.status === filter; });
if (search) assets = assets.filter(function(a) { return a.name.toLowerCase().includes(search.toLowerCase()); });
const displayTitle = openProject ? openProject.name : 'All Assets';
const errorCount = ALL_ASSETS.filter(function(a) { return a.status === 'error'; }).length;
const recentCount = ALL_ASSETS.filter(function(a) { return (Date.now() - new Date(a.created_at)) < 86400000; }).length;
return (
{displayTitle}
· {assets.length} assets
{['all', 'ready', 'processing', 'live', 'error'].map(function(f) {
return (
);
})}
{assets.length === 0 ? (
No assets match this filter.
) : view === 'grid' ? (
{assets.map(function(a) { return
; })}
) : (
Name
Duration
Resolution
Codec
Size
Updated
{assets.map(function(a) {
return (
{a.duration}
{a.res}
{a.codec || '—'}
{a.size}
{a.updated}
);
})}
)}
);
}
function AssetCard({ asset, onOpen }) {
const [hoverStream, setHoverStream] = React.useState(null);
const [hovered, setHovered] = React.useState(false);
const timerRef = React.useRef(null);
const hlsRef = React.useRef(null);
const videoRef = React.useRef(null);
const handleMouseEnter = function() {
timerRef.current = setTimeout(function() {
setHovered(true);
if (!hoverStream) {
window.ZAMPP_API.fetch('/assets/' + asset.id + '/stream')
.then(function(r) { if (r && r.url) setHoverStream(r); })
.catch(function() {});
}
}, 350);
};
const handleMouseLeave = function() {
clearTimeout(timerRef.current);
setHovered(false);
};
// HLS wiring
React.useEffect(function() {
if (!hovered || !hoverStream || hoverStream.type !== 'hls' || !videoRef.current) return;
if (!window.Hls) return;
hlsRef.current = new window.Hls({ maxBufferLength: 10 });
hlsRef.current.loadSource(hoverStream.url);
hlsRef.current.attachMedia(videoRef.current);
return function() {
if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; }
};
}, [hovered, hoverStream]);
const showVideo = hovered && hoverStream;
return (
{asset.status === 'live' && LIVE}
{asset.status === 'processing' && Processing}
{asset.status === 'error' && Error}
{(asset.type === 'video' || !asset.type) && asset.duration !== '—' &&
{asset.duration}
}
{asset.name}
{asset.res}
·
{asset.size}
);
}
function binIcon(name) {
return { grid: 'library', live: 'record', film: 'film', proxy: 'proxy', audio: 'audio', package: 'package' }[name] || 'folder';
}
window.Library = Library;
window.AssetCard = AssetCard;