feat: initial Luma implementation
Full self-hosted photo manager with: - Ingestion worker (chokidar + sharp + exifr, dHash perceptual hashing) - SQLite schema with WAL mode (photos, tags, collections, duplicate_pairs) - Express API with keyset pagination, tag/collection/search/date filters - React + Vite UI: timeline, lightbox, detail panel, tag/collection editors - Duplicate detection (Union-Find grouping) with review + dismiss UI - Map view (Leaflet + OpenStreetMap, marker clustering) - Docker multi-stage build + Compose with inline env config - GitHub Actions: CI build check + ghcr.io image publish on main/tags Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
03e6d6b628
commit
e0f4e8ff0d
32 files changed
+4845
No files matched your search
@@ -0,0 +1,36 @@
|
||||
import PhotoCard from './PhotoCard.jsx';
|
||||
|
||||
export default function MonthGroup({ label, photos, startIdx, onSelect }) {
|
||||
return (
|
||||
<section style={{ marginBottom: 8 }}>
|
||||
<h2 style={{
|
||||
padding: '18px 6px 8px',
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
color: '#666',
|
||||
letterSpacing: 0.4,
|
||||
textTransform: 'uppercase',
|
||||
userSelect: 'none',
|
||||
}}>
|
||||
{label}
|
||||
<span style={{ marginLeft: 8, color: '#3a3a3a', fontWeight: 400 }}>
|
||||
{photos.length.toLocaleString()}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(160px, 1fr))',
|
||||
gap: 3,
|
||||
}}>
|
||||
{photos.map((photo, i) => (
|
||||
<PhotoCard
|
||||
key={photo.id}
|
||||
photo={photo}
|
||||
onClick={() => onSelect(startIdx + i)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user