feat: lightroom-style workspace UI with album covers

Backend:
- expose cover thumbnail URL on album responses (LEFT JOIN + presign)
- auto-assign cover only when empty; FK cover_photo_id -> photos ON DELETE SET NULL
- add photos_count / members_count to album responses; cover repo test

Frontend:
- split layouts into (app) shell (resizable rail + contextual panel) and (public)
- album rail + create-album dialog; album panel with cover picker / make-cover
- photo viewer route with filmstrip, replacing lightbox; share reuses it
- grid density slider, toolbar + drag-and-drop upload, dark graphite default

OpenSpec: add lightroom-workspace-ui change (proposal, design, specs, tasks)
This commit is contained in:
iu
2026-10-10 18:15:13 +03:00
parent b90e6b0267
commit 304cfedb75
47 changed files with 1942 additions and 601 deletions
+1 -1
View File
@@ -121,7 +121,7 @@ func (s *Service) processVariants(photoID, albumID string, data []byte) {
if err := s.store.SetVariants(ctx, photoID, thumbKey, previewKey); err != nil {
return
}
s.store.SetCover(ctx, albumID, photoID)
s.store.SetCoverIfEmpty(ctx, albumID, photoID)
if p, err := s.store.GetByID(ctx, photoID, ""); err == nil {
if err := s.attachURLs(ctx, p); err == nil {
s.hub.Publish(albumID, "photo.created", p)
+4 -2
View File
@@ -122,6 +122,8 @@ func (s *Store) Delete(ctx context.Context, id string) error {
return nil
}
func (s *Store) SetCover(ctx context.Context, albumID, photoID string) {
_, _ = s.pool.Exec(ctx, `UPDATE albums SET cover_photo_id = $2 WHERE id = $1`, albumID, photoID)
func (s *Store) SetCoverIfEmpty(ctx context.Context, albumID, photoID string) {
_, _ = s.pool.Exec(ctx,
`UPDATE albums SET cover_photo_id = $2 WHERE id = $1 AND cover_photo_id IS NULL`,
albumID, photoID)
}