// 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 */}
{/* 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 });