feat: context menus, unified photo panel, tags, album trash and counters
- 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)
This commit is contained in:
@@ -205,9 +205,60 @@ owner/admin) и загрузка в цель (member+).
|
||||
### 24. Компактный верхний хром
|
||||
Верхняя панель приложения (`AppShell`) и верхняя панель режима просмотра (`Viewer`) сведены к
|
||||
минимуму: узкие вертикальные отступы (`py-0.5`/`py-1`), иконочные органы управления без рамок,
|
||||
иконки `size-4`, компактный переключатель темы. Функциональность (меню, панель, темы, полный
|
||||
иконки `size-4`, компактный переключатель темы. Верхняя панель просмотра — прозрачный оверлей
|
||||
(`absolute`, без фона и рамки, `pointer-events-none` с активными кнопками), поэтому не занимает
|
||||
место в раскладке и увеличивает область фото. Функциональность (меню, панель, темы, полный
|
||||
экран, навигация, скачивание, удаление) сохраняется.
|
||||
|
||||
### 25. Действия в режиме просмотра — в правой панели
|
||||
Скачивание и удаление убраны из верхней панели просмотра и перенесены в правую панель, в раздел
|
||||
«Действия», вместе с «Переместить» и «Копировать». Скачивание — ссылка на presigned оригинал;
|
||||
перенос/копирование — через `transfer`/`TransferDialog`; удаление инициируется стором
|
||||
`viewerActions`, а обрабатывает его страница просмотра (та же логика перехода к соседнему фото
|
||||
после удаления).
|
||||
|
||||
### 26. Единая панель фото
|
||||
Содержимое правой панели для одного фото (имя файла, «Действия», «Информация», «Комментарии»)
|
||||
вынесено в общий компонент `PhotoPanel`, который переиспользуется и в режиме просмотра
|
||||
(`viewer=true`), и при одиночном выборе фото в сетке — это одна и та же панель, а не копия.
|
||||
Отличие только в наборе действий: в просмотре нет «Открыть», а удаление идёт через стор
|
||||
`viewerActions` (обрабатывает страница просмотра с переходом к соседнему фото); при выборе в
|
||||
сетке удаление выполняет сам компонент и очищает выбор. Мультивыбор (>1) показывает только
|
||||
раздел «Действия».
|
||||
|
||||
### 27. Контекстное меню фото
|
||||
Правый клик по фото в сетке и по изображению в просмотре открывает контекстное меню с теми же
|
||||
пунктами, что и раздел «Действия». Меню — общий стор `contextMenu` (`x`, `y`, items) и глобальный
|
||||
компонент `ContextMenu` (портал), закрывается по клику вне, `Escape`, скроллу и ресайзу.
|
||||
Пункты формируются на местах: в сетке — «Открыть (просмотр)», «Скачать», «Переместить»,
|
||||
«Копировать», «Удалить» (для мультивыбора без «Скачать»); в просмотре — без «Открыть».
|
||||
|
||||
### 28. Удаление альбома и восстановление фото
|
||||
Удаление альбома не должно терять файлы: перед удалением альбома его фото мягко удаляются
|
||||
(`deleted_at`), а FK `photos.album_id` меняется на `ON DELETE SET NULL` (колонка nullable,
|
||||
миграция `000006`) — после удаления альбома фото остаются строками с `album_id = NULL` в
|
||||
корзине. Объекты S3 не удаляются (каскадный `DeletePrefix` убран: он ломал бы копии в других
|
||||
альбомах). `GET /trash` включает такие «без альбома» фото загрузчика. Восстановление с
|
||||
указанием целевого альбома (`POST /photos/:id/restore {album_id}`) переносит фото туда; без
|
||||
цели и без альбома возвращается ошибка `album_required`, и клиент предлагает выбрать альбом или
|
||||
создать новый.
|
||||
|
||||
### 29. Контекстное меню альбома и счётчики в рейле
|
||||
Правый клик по альбому (в рейле и на обзоре) открывает меню «Переименовать»/«Удалить» через
|
||||
общий стор `contextMenu`; диалоги переименования и подтверждения удаления — глобальный
|
||||
`AlbumActionsDialogs` (стор `albumActions`). В рейле у каждого альбома показано число фото
|
||||
(`album.photos_count`), у корзины — число удалённых фото (лёгкий `GET /trash/count`, стор
|
||||
`trash`).
|
||||
|
||||
### 30. Метки фото (область — пользователь)
|
||||
Метки принадлежат пользователю: таблицы `tags` (`user_id`, уникальность по `lower(name)`) и
|
||||
`photo_tags` (миграция `000007`). Фото отдаёт метки текущего пользователя (подзапрос
|
||||
`json_agg` в `photoColumns` по `$2`-viewer). Эндпоинты: `GET /tags?q=` (поиск по меткам
|
||||
пользователя), `POST /photos/:id/tags {name}` (создаёт метку при необходимости и привязывает,
|
||||
возвращает обновлённое фото), `DELETE /photos/:id/tags/:tag_id`. UI — раздел «Метки» в
|
||||
`PhotoPanel`: чипы с удалением и поле с автодополнением (поиск + «Создать «…»»). Права: метку
|
||||
можно ставить на фото альбома, доступного пользователю.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Много альбомов → много presign в списке] → presign это локальная крипто-операция без сети;
|
||||
|
||||
Reference in New Issue
Block a user