feat: filter sidebar, ingest progress, starred photos
- Left filter sidebar: All Photos / Favorites / Date by Year / Tags / Collections with counts, See more/less toggles, and blue active-row highlight - SSE ingest badge in header: pulsing dot + filename + indexed count during scan; auto-refreshes the library when a scan finishes - Starred/Favorites: starred column on photos, star button in lightbox toolbar, Favorites row in sidebar, ?starred=1 filter on /api/photos - GET /api/library/stats endpoint (totals, years, tags, collections) - GET /api/ingest/stream SSE endpoint (polls ingest_status table every 1.5s) - Worker writes ingest_status (active, current file, scanned count) to SQLite Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
4df32f9f32
commit
f0e237ab2b
10 files changed
+541
-127
No files matched your search
@@ -0,0 +1,53 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
|
||||
export default function IngestBadge({ onScanComplete }) {
|
||||
const [status, setStatus] = useState(null);
|
||||
const wasActive = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const es = new EventSource('/api/ingest/stream');
|
||||
|
||||
es.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
setStatus(data);
|
||||
if (wasActive.current && !data.active) {
|
||||
onScanComplete?.();
|
||||
}
|
||||
wasActive.current = !!data.active;
|
||||
};
|
||||
|
||||
es.onerror = () => {
|
||||
// Reconnect is handled automatically by EventSource
|
||||
};
|
||||
|
||||
return () => es.close();
|
||||
}, []);
|
||||
|
||||
if (!status?.active) return null;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 7,
|
||||
fontSize: 12, color: '#888',
|
||||
overflow: 'hidden', maxWidth: 340,
|
||||
}}>
|
||||
<span style={{
|
||||
width: 7, height: 7, borderRadius: '50%',
|
||||
background: '#4d9eff', flexShrink: 0,
|
||||
animation: 'pulse-dot 1.4s ease-in-out infinite',
|
||||
}} />
|
||||
<span style={{ whiteSpace: 'nowrap', color: '#aaa' }}>
|
||||
Scanning · {status.scanned.toLocaleString()} indexed
|
||||
</span>
|
||||
{status.current && (
|
||||
<span style={{
|
||||
color: '#444', overflow: 'hidden',
|
||||
textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
||||
fontSize: 11,
|
||||
}}>
|
||||
{status.current}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user