/* Brand Lens — Library / repository view */ function Library({ recs, reload, goReview }) { const [filter, setFilter] = useState('all'); const [busy, setBusy] = useState(null); // {done,total} const [drag, setDrag] = useState(false); const [note, setNote] = useState(null); const fileRef = useRef(null); const importRef = useRef(null); const ordered = useMemo( () => [...recs].sort((a, b) => a.shuffleKey - b.shuffleKey), [recs] ); const counts = useMemo(() => { let reviewed = 0, aligned = 0, unrev = 0; for (const r of recs) { if (r.user) { reviewed++; if (r.ai && (r.ai.verdict === 'keep') === !!r.user.like) aligned++; } else unrev++; } return { total: recs.length, reviewed, aligned, unrev }; }, [recs]); const shown = ordered.filter((r) => { if (filter === 'all') return true; if (filter === 'unrev') return !r.user; if (filter === 'liked') return r.user && r.user.like; if (filter === 'disliked') return r.user && !r.user.like; return true; }); async function handleFiles(list) { if (!list || !list.length) return; setBusy({ done: 0, total: Array.from(list).filter((f) => /^image\//.test(f.type)).length }); await window.LensDB.addFiles(list, (done, total) => setBusy({ done, total })); setBusy(null); reload(); } async function removeOne(id) { await window.LensDB.del(id); reload(); } async function resetOne(id) { await window.LensDB.patch(id, { ai: null, user: null }); reload(); } async function clearAll() { if (!confirm('Remove ALL images and their feedback? This cannot be undone.')) return; await window.LensDB.clearAll(); reload(); } async function shuffle() { await window.LensDB.reshuffle(); reload(); } async function exportData() { const bundle = await window.LensDB.exportBundle(); const blob = new Blob([JSON.stringify(bundle)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); const d = new Date(); a.href = url; a.download = `brand-lens-backup-${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}.json`; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 2000); setNote('Backup downloaded — keep it safe or move it to another machine.'); } async function importData(file) { if (!file) return; try { const bundle = JSON.parse(await file.text()); const mode = recs.length > 0 ? (confirm(`Import ${bundle.count || (bundle.images || []).length} images.\n\nOK = MERGE into your current ${recs.length}.\nCancel = REPLACE everything with the backup.`) ? 'merge' : 'replace') : 'replace'; const added = await window.LensDB.importBundle(bundle, mode); reload(); setNote(`Imported ${added} images (${mode}).`); } catch (e) { setNote('Could not read that file — is it a Brand Lens backup?'); } } const filters = [ ['all', 'All', counts.total], ['unrev', 'Not reviewed', counts.unrev], ['liked', 'Kept', recs.filter((r) => r.user && r.user.like).length], ['disliked', 'Rejected', recs.filter((r) => r.user && !r.user.like).length], ]; return (
Your taste library. Drop in photos, stock, and clip-art — they're shuffled, not served in upload order. Review them blind, then watch a style guide build itself from your calls.
Nothing in this filter yet.
)}