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
+28 -2
View File
@@ -51,7 +51,24 @@ func (s *Service) Create(ctx context.Context, userID string, in CreateInput) (*m
}
func (s *Service) List(ctx context.Context, userID string) ([]models.Album, error) {
return s.store.ListForUser(ctx, userID)
out, err := s.store.ListForUser(ctx, userID)
if err != nil {
return nil, err
}
for i := range out {
s.attachCover(ctx, &out[i])
}
return out, nil
}
// attachCover fills CoverThumbURL from the presigned thumbnail key, when present.
func (s *Service) attachCover(ctx context.Context, a *models.Album) {
if s.storage == nil || a == nil || a.CoverThumbKey == "" {
return
}
if url, err := s.storage.PresignGet(ctx, a.CoverThumbKey); err == nil {
a.CoverThumbURL = url
}
}
func (s *Service) Access(ctx context.Context, albumID, userID string) (*models.Album, string, error) {
@@ -76,7 +93,12 @@ func (s *Service) Access(ctx context.Context, albumID, userID string) (*models.A
}
func (s *Service) Get(ctx context.Context, albumID, userID string) (*models.Album, string, error) {
return s.Access(ctx, albumID, userID)
a, role, err := s.Access(ctx, albumID, userID)
if err != nil {
return nil, "", err
}
s.attachCover(ctx, a)
return a, role, nil
}
type UpdateInput struct {
@@ -112,6 +134,10 @@ func (s *Service) Update(ctx context.Context, albumID, userID string, in UpdateI
if err := s.store.Update(ctx, a); err != nil {
return nil, err
}
if refreshed, err := s.store.GetByID(ctx, a.ID); err == nil {
a = refreshed
}
s.attachCover(ctx, a)
s.hub.Publish(albumID, "album.updated", a)
return a, nil
}