// Grid & List views function Card({ file, idx, focused, selected, onSelect, onOpen, onPick, onReject, onRate, onColor, onTag, onRemoveTag, onSetTagFilter, setFocused, density }) { const [hover, setHover] = React.useState(false); const [showTag, setShowTag] = React.useState(false); const [tagVal, setTagVal] = React.useState(''); // Default to 3:4 portrait if no dimensions provided const ratio = file.h && file.w ? file.h / file.w : 1.33; return (
{ setHover(true); setFocused(idx); }} onMouseLeave={() => setHover(false)} onClick={onOpen} style={{ position: 'relative', cursor: 'zoom-in', background: 'var(--bg-1)', borderRadius: 6, overflow: 'hidden', border: `1px solid ${selected ? 'var(--accent)' : focused ? 'var(--line-3)' : 'var(--line-1)'}`, transition: 'border-color 140ms', animation: file.isNew ? 'fade-up 220ms ease' : 'none', }}> {/* Image */}
{file.filename}
{/* Color strip — left edge */} {file.color && (
)} {/* Top-left badges */}
{file.pick && (
Pick
)} {file.reject && (
Reject
)} {file.blur && !file.pick && !file.reject && (
⚠ Soft
)}
{/* Top-right — file type pill + select */}
{file.file_type}
{/* Quick actions on hover — bottom right */}
e.stopPropagation()}> { window.open(file.download_url || `/download/${encodeURIComponent(file.filename)}`, '_blank'); }} icon={}/>
{/* Gradient bottom */}
{/* Filename bottom-left */}
{file.filename}
{/* Meta row */} {density !== 'tight' && (
{(file.tags||[]).map(t => ( { e.stopPropagation(); onSetTagFilter(t); }} onRemove={() => onRemoveTag(t)}>{t} ))} {showTag ? ( e.stopPropagation()} onChange={e => setTagVal(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { onTag(tagVal.trim().toLowerCase()); setTagVal(''); setShowTag(false); } if (e.key === 'Escape') { setShowTag(false); setTagVal(''); } }} onBlur={() => { if (tagVal.trim()) onTag(tagVal.trim().toLowerCase()); setShowTag(false); setTagVal(''); }} placeholder="tag…" style={{ background: 'var(--bg-3)', border: '1px solid var(--line-3)', borderRadius: 99, padding: '1px 7px', fontSize: 10, color: 'var(--ink-0)', outline: 'none', width: 60, }}/> ) : ( )}
{file.size}
)}
); } function QuickBtn({ title, active, onClick, glyph, icon, accent, reject }) { const bg = active && accent ? 'var(--accent)' : active && reject ? 'var(--reject)' : 'var(--glass-bg)'; const fg = active && accent ? '#1a1200' : active && reject ? '#fff' : 'var(--glass-ink)'; const border = active && accent ? 'var(--accent)' : active && reject ? 'var(--reject)' : 'var(--line-2)'; return ( ); } function colorHex(c) { return { red: '#e5484d', orange: '#ff9b50', yellow: '#f4b942', green: '#52c878', blue: '#70a5ff', purple: '#ba80f0' }[c] || 'transparent'; } function GridView({ items, focused, setFocused, selected, actions, density = 'normal' }) { const [screenW, setScreenW] = React.useState(window.innerWidth); React.useEffect(() => { const fn = () => setScreenW(window.innerWidth); window.addEventListener('resize', fn); return () => window.removeEventListener('resize', fn); }, []); let cols; if (screenW < 540) cols = 2; else if (screenW < 768) cols = 3; else cols = density === 'tight' ? 6 : density === 'loose' ? 3 : 4; return (
{items.map((f, idx) => ( actions.toggleSelect(f.filename, v)} onOpen={() => actions.openLightbox(idx)} onPick={() => actions.pick(f.filename)} onReject={() => actions.reject(f.filename)} onRate={(r) => actions.rate(f.filename, r)} onColor={(c) => actions.color(f.filename, c)} onTag={(t) => t && actions.tag(f.filename, t)} onRemoveTag={(t) => actions.removeTag(f.filename, t)} onSetTagFilter={(t) => actions.setTagFilter(t)} /> ))}
); } function ListView({ items, focused, setFocused, selected, actions }) { return (
{['', '', 'Filename', 'Type', 'Size', 'Rating', 'Tags', 'Captured', ''].map((h, i) => (
{h}
))}
{items.map((f, idx) => (
actions.openLightbox(idx)} onMouseEnter={() => setFocused(idx)} style={{ display: 'grid', gridTemplateColumns: '24px 56px 1.5fr 60px 80px 110px 1.2fr 110px 44px', gap: 10, padding: '8px 14px', alignItems: 'center', borderBottom: '1px solid var(--line-1)', background: selected.has(f.filename) ? 'var(--accent-soft)' : focused === idx ? 'var(--bg-2)' : 'transparent', cursor: 'pointer', transition: 'background 100ms', opacity: f.reject ? 0.55 : 1, }}>
{f.pick &&
} {f.reject &&
}
{f.color && } {f.filename}
{f.file_type} {f.size} actions.rate(f.filename, r)} size={11}/>
e.stopPropagation()}> {(f.tags||[]).slice(0,3).map(t => actions.setTagFilter(t)}>{t})}
{new Date(f.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
e.stopPropagation()} style={{ display: 'flex', gap: 2, justifyContent: 'flex-end' }}> } onClick={() => window.open(f.download_url || `/download/${encodeURIComponent(f.filename)}`, '_blank')}/>
))}
); } Object.assign(window, { GridView, ListView, colorHex });