feat: Lightroom-style workspace UI #2

Merged
iu merged 18 commits from feature/lightroom-workspace-ui into develop 2026-10-10 19:56:23 +00:00
Owner

Что сделано

Рабочий стол приложения в духе Adobe Lightroom: постоянный рейл альбомов, контекстная правая панель, плотная сетка, режим просмотра как часть рабочего стола, обложки с явным выбором и единый тёмный графитовый хром.

Основное:

  • Рабочий стол (шелл). Группа (app) с постоянным левым рейлом альбомов, топбаром и контекстной правой панелью; публичные маршруты (/login, /invites/[token], /share/[token]) остаются вне шелла.
  • Обложки. Album.cover_thumb_url, реальные миниатюры в карточках и рейле, пикер обложки в правой панели, FK albums.cover_photo_id -> photos(id) ON DELETE SET NULL.
  • Просмотр как маршрут. Lightbox-оверлей заменён маршрутом /albums/:id/photos/:photoId; полноэкранный режим, зум колесом к указателю, догрузка оригинала.
  • EXIF. Извлечение метаданных при загрузке в photos.exif, команда бэкфилла cmd/backfill-exif, раздел «Информация» с настраиваемыми атрибутами в профиле пользователя.
  • Метки фото, контекстные меню, удаление в корзину, перенос/копирование, счётчики фото и участников.
  • Плотность сетки, мультивыбор, адаптив (off-canvas рейл, bottom-sheet панель).
  • Темы. dark — графит Lightroom и дефолт, neutral — тёплый графит; токены --chrome/--canvas; стилизованные скроллбары.

Спеки

  • ui-workspace (новая)
  • albums, photos, auth (дельты)

Заметки

  • API-контракты сохраняются; ломающих изменений API нет.
  • Открытые изменения OpenSpec заархивированы, основные спеки синхронизированы.
## Что сделано Рабочий стол приложения в духе Adobe Lightroom: постоянный рейл альбомов, контекстная правая панель, плотная сетка, режим просмотра как часть рабочего стола, обложки с явным выбором и единый тёмный графитовый хром. Основное: - **Рабочий стол (шелл).** Группа `(app)` с постоянным левым рейлом альбомов, топбаром и контекстной правой панелью; публичные маршруты (`/login`, `/invites/[token]`, `/share/[token]`) остаются вне шелла. - **Обложки.** `Album.cover_thumb_url`, реальные миниатюры в карточках и рейле, пикер обложки в правой панели, FK `albums.cover_photo_id -> photos(id) ON DELETE SET NULL`. - **Просмотр как маршрут.** `Lightbox`-оверлей заменён маршрутом `/albums/:id/photos/:photoId`; полноэкранный режим, зум колесом к указателю, догрузка оригинала. - **EXIF.** Извлечение метаданных при загрузке в `photos.exif`, команда бэкфилла `cmd/backfill-exif`, раздел «Информация» с настраиваемыми атрибутами в профиле пользователя. - **Метки фото, контекстные меню, удаление в корзину, перенос/копирование, счётчики** фото и участников. - **Плотность сетки, мультивыбор, адаптив** (off-canvas рейл, bottom-sheet панель). - **Темы.** `dark` — графит Lightroom и дефолт, `neutral` — тёплый графит; токены `--chrome`/`--canvas`; стилизованные скроллбары. ## Спеки - `ui-workspace` (новая) - `albums`, `photos`, `auth` (дельты) ## Заметки - API-контракты сохраняются; ломающих изменений API нет. - Открытые изменения OpenSpec заархивированы, основные спеки синхронизированы.
iu added 18 commits 2026-10-10 19:55:30 +00:00
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)
- add fullscreen/fullscreen_exit icons and a top-bar toggle (right of themes)
- add back arrow left of the album title to return to the albums overview
- make neutral theme a warm dark graphite
- order switches as dark -> neutral -> light -> system
- replace neutral icon with contrast glyph
Backend:
- add PATCH /api/photos/:id to rename original_filename (uploader/owner/admin)
- emit photo.updated SSE event

Frontend:
- inline filename editor in the right panel
- viewer: back button (arrow_back) on the left, delete on the right
- delete confirmed via a modal dialog; filmstrip updates after deletion
- add PanelSection with persisted collapse state
- restructure album and photo panels into sections
- add "Информация" section with EXIF/metadata (size, type, resolution, taken, coords)
- fullscreen toggle button + "f" hotkey (both layouts)
- wheel zoom anchored to cursor; Space toggles 100% (1:1)
- pan when zoomed via drag/scrollbars; reset zoom on photo change
- hotkeys ignored while typing in inputs
- raise upload limit to 100 MB
- return 413 file_too_large on MaxBytesError instead of misleading
  "expected multipart form"
- client-side per-file and total size pre-check with a clear message
Thin scrollbars; thumb uses --border (hover --text-subtle), transparent
track. WebKit + Firefox, follows the active theme.
- fullscreen: auto-hide top toolbar and filmstrip, revealed at top/bottom
  screen edge and hidden 1s after the cursor leaves
- arrows < >: edge-reveal in both normal and fullscreen; hide 300ms after
  leaving; hidden while zoomed
- zoom: wheel zooms only (no scroll, overscroll contained), anchored to the
  pointer, capped at 200%; Space toggles 100%/fit; pan via drag/scroll
Reflect post-planning work in proposal, design, specs (albums/photos/
ui-workspace) and tasks: photo rename, viewer delete/zoom/fullscreen,
resizable and collapsible panels with EXIF, upload size limit, album
counts, themes/scrollbars, back navigation.
- in zoom, preload the full-resolution original and swap it in once loaded
  (preview is shown until then); revert to preview on zoom exit
- Space returns to the fitted view from any zoom level; from fit it goes to 100%
- derive zoom scale from the photo's true dimensions so 100% is accurate
- backend: switch EXIF parser to dsoprea/go-exif/v3 (all recognised tags, numeric normalization); store in photos.exif jsonb (migration 000003); add storage.Get and cmd/backfill-exif to backfill existing photos from S3 originals
- frontend: info panel renders all EXIF attributes with RU labels, enum translation and units (1/N, f/N, mm, EV); service tags hidden
- viewer: remove make-cover action (cover managed from panel only), icon-only download
- openspec: sync lightroom-workspace-ui artifacts (EXIF, cover-from-panel) and add plan for configurable info attributes/labels stored in user profile
- backend: users.preferences jsonb (migration 000004), Me returns preferences, PATCH /auth/me/preferences with validation (visible list + labels)
- frontend: gear in info section opens PhotoInfoSettingsDialog; base attribute set shown by default, remaining under ellipsis; per-attribute visibility and custom labels persisted to the profile; reset clears settings from the profile
- frontend: dialog has select-all/deselect-all icon buttons (no 'show all'); EXIF constants extracted to lib/exif.ts
- fix: render modal overlays via portal to document.body so they center in the viewport despite transformed panel/rail ancestors
- openspec: sync lightroom-workspace-ui artifacts (spec/design/proposal/tasks 9.8)
- remove all hide timers in Viewer; top/bottom bars and arrows now show/hide immediately
- arrows reveal only within the viewer bounds (no longer stay visible when the pointer is over the left/right panels)
- f (and the viewer button) toggle the viewer's own fullscreen via nested fullscreen, so f works inside app-level fullscreen without turning it off
- single click selects a photo (ring + aria-pressed) and shows its info in the right panel without navigating; double click opens the viewer
- shared 'selection' store; panel uses the route photo in viewer mode and the selected photo in album mode; selection resets on album change
- openspec: sync lightroom-workspace-ui (grid-selection requirement, viewer chrome wording without timers, nested f fullscreen, tasks 9.9/9.10)
- backend: soft delete + trash (photos.deleted_at, migration 000005), GET /trash, restore, permanent delete with storage-key refcount (S3 objects removed only when no other album references them); batch endpoints photo-batch/{delete,move,copy} - metadata only, copies clone rows sharing storage keys
- frontend: Ctrl/Shift multiselect; right-panel 'Действия' as icon text rows (Открыть (просмотр), Переместить, Копировать, Удалить); drag selected photos onto rail album -> TransferDialog; /trash route; trash entry as last item in the left rail; empty right panel hidden on overview
- frontend: compact top bars (app header and viewer), compact theme switcher
- openspec: sync lightroom-workspace-ui artifacts (photos/ui-workspace specs, design #20-24, tasks 9.11-9.17)
- backend: deleting an album moves its photos to the trash without losing files (photos.album_id nullable, FK ON DELETE SET NULL, migration 000006; dropped S3 prefix delete); restore accepts an album target (album_required when the album is gone); user-scoped tags (000007) with GET /tags, POST/DELETE /photos/:id/tags, tags included in photo responses; GET /trash/count
- frontend: right-click context menus for photos (grid + viewer) and albums (rail + overview); single shared PhotoPanel reused in viewer and gallery selection (not a copy); viewer download/delete moved to the panel 'Действия'; transparent compact viewer top bar; trash entry as last rail item with per-album photo counts and trash count; album rename/delete dialogs
- openspec: sync lightroom-workspace-ui artifacts (specs photos/ui-workspace, design #24-30, tasks 9.17-9.24)
- synced delta specs into main specs: albums (+3 reqs, MODIFIED deleting album -> photos to trash), auth (+1), photos (+8), and created new ui-workspace spec (29 reqs)
- moved the change to openspec/changes/archive/2026-10-10-lightroom-workspace-ui/
iu merged commit 9effc0fc82 into develop 2026-10-10 19:56:22 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: iu/ds-galo#2