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 это локальная крипто-операция без сети;
|
||||
|
||||
@@ -35,10 +35,19 @@
|
||||
(камера, объектив, экспозиция, ISO, дата съёмки, GPS и др.) и хранит их в `photos.exif`;
|
||||
они возвращаются вместе с фото и показываются в разделе «Информация». Для ранее загруженных
|
||||
фото есть команда бэкфилла метаданных из оригиналов.
|
||||
- **Метки фото.** Пользователь присваивает фото свои метки: создаёт новые и выбирает из списка
|
||||
с поиском; метки показываются и снимаются в разделе «Метки» панели фото.
|
||||
- **Контекстные меню.** Правый клик по фото (в сетке и в просмотре) открывает меню с пунктами
|
||||
раздела «Действия»; правый клик по альбому (в рейле и на обзоре) — меню «Переименовать»/
|
||||
«Удалить».
|
||||
- **Удаление альбома.** Удаление альбома помещает его фото в корзину, не удаляя объекты
|
||||
хранилища; при восстановлении фото без альбома предлагается выбрать существующий альбом или
|
||||
создать новый.
|
||||
- **Просмотр.** Полноэкранный режим (кнопка и `f`) с автоскрытием верхней/нижней панелей и
|
||||
стрелок у кромок; зум колесом к указателю (без прокрутки, максимум 200%), `Space`
|
||||
возвращает из любого зума во вписанный вид, в зуме догружается оригинал изображения;
|
||||
удаление текущего фото с модальным подтверждением; возврат к обзору.
|
||||
действия просмотра (скачать, удалить, перенести, копировать) — в правой панели, в разделе
|
||||
«Действия»; удаление с модальным подтверждением; возврат к обзору.
|
||||
- **Разделы и панели.** Правая панель — сворачиваемые разделы (состояние сохраняется), с
|
||||
разделом «Информация» (EXIF/метаданные); левая и правая панели ресайзятся перетаскиванием.
|
||||
Выбор фото в сетке показывается в панели (одиночный клик), двойной клик открывает просмотр.
|
||||
@@ -55,7 +64,8 @@
|
||||
помещает фото в корзину; объекты S3 удаляются лишь когда на них нет ссылок в других
|
||||
альбомах. Корзина — последний элемент списка альбомов в левом рейле (иконка корзины); на
|
||||
обзоре убрана пустая правая панель.
|
||||
- **Счётчики.** В ответе альбома — число фото и участников.
|
||||
- **Счётчики.** В ответе альбома — число фото и участников; в рейле у каждого альбома показано
|
||||
число фото, у корзины — число удалённых фото.
|
||||
- **Темы и хром.** Существующие 4 темы сохраняются; `dark` — графит Lightroom и дефолт,
|
||||
`neutral` — тёплый графит; переключатель упорядочен тёмная → нейтральная → светлая →
|
||||
системная; добавляются токены `--chrome`/`--canvas`; скроллбары стилизованы в тон темы.
|
||||
@@ -72,9 +82,10 @@
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `albums`: отображение обложки, счётчики фото/участников и явный выбор обложки.
|
||||
- `albums`: отображение обложки, счётчики фото/участников, явный выбор обложки и удаление в
|
||||
корзину.
|
||||
- `photos`: автообложка только при отсутствии выбора; переименование фото; лимит размера
|
||||
загрузки; извлечение и хранение EXIF-метаданных; перенос/копирование и корзина.
|
||||
загрузки; извлечение и хранение EXIF-метаданных; перенос/копирование, корзина и метки.
|
||||
- `auth`: хранение пользовательских настроек отображения информации в профиле.
|
||||
|
||||
## Impact
|
||||
@@ -89,7 +100,11 @@
|
||||
`github.com/dsoprea/go-exif/v3` в `photos.exif`, миграция `000003_photo_exif`, команда
|
||||
`cmd/backfill-exif` и `storage.Get`); миграция `users.preferences`,
|
||||
`models.User.Preferences`, `internal/auth` (`Me` отдаёт настройки,
|
||||
`PATCH /auth/me/preferences` с валидацией).
|
||||
`PATCH /auth/me/preferences` с валидацией); миграции `000005_photo_trash` (мягкое удаление),
|
||||
`000006_photo_album_nullable` (FK `photos.album_id` → `ON DELETE SET NULL`, удаление альбома
|
||||
в корзину), `000007_photo_tags` (метки); `GET /trash`/`GET /trash/count`,
|
||||
`POST /photos/:id/restore` (c `album_id`), `DELETE /photos/:id/permanent`,
|
||||
`POST /photo-batch/{delete,move,copy}`, `GET /tags`, `POST/DELETE /photos/:id/tags`.
|
||||
- **Frontend:** реструктуризация `+layout.svelte` в группы `(app)`/`(public)`, `AppShell`
|
||||
с ресайзом панелей и кнопкой полного экрана; компоненты рейла, контекстной панели со
|
||||
сворачиваемыми разделами и EXIF, модалки создания/пикера обложки/подтверждения,
|
||||
@@ -97,7 +112,9 @@
|
||||
маршрут `albums/[id]/photos/[photoId]`; слот действий в `PanelSection`, модалка настроек
|
||||
атрибутов с пользовательскими подписями, стор настроек, фильтрация и «…» в панели;
|
||||
новые значения токенов темы и стили скроллбаров; `types.ts` (+`cover_thumb_url`, счётчики,
|
||||
`photos_count`, `preferences`); новые иконки в реестре.
|
||||
`photos_count`, `preferences`, `tags`); общие компоненты `PhotoPanel`, `ContextMenu`,
|
||||
`TransferDialog`, `AlbumActionsDialogs`; маршрут `/trash`; сторы `selection`, `transfer`,
|
||||
`contextMenu`, `albumActions`, `trash`, `viewerActions`; новые иконки в реестре.
|
||||
- **Спеки:** `ui-workspace` (новая), `albums`, `photos`, `auth` (дельты). `ui-iconography` не
|
||||
затрагивается — иконки по-прежнему наследуют цвет темы.
|
||||
- **Совместимость:** API-контракты сохраняются; добавляется поле ответа и один FK. Ломающих
|
||||
|
||||
@@ -87,3 +87,32 @@
|
||||
#### Scenario: Окончательное удаление при наличии ссылок
|
||||
- **WHEN** на объект хранилища ссылается копия в другом альбоме
|
||||
- **THEN** запись удаляется, а объект хранилища сохраняется
|
||||
|
||||
### Requirement: Удаление альбома и восстановление фото без альбома
|
||||
Удаление альбома SHALL помещать его фото в корзину, не удаляя объекты хранилища. Если при
|
||||
восстановлении фото его альбом отсутствует, система SHALL предложить выбрать существующий
|
||||
альбом или создать новый.
|
||||
|
||||
#### Scenario: Удаление альбома
|
||||
- **WHEN** владелец удаляет альбом
|
||||
- **THEN** его фото оказываются в корзине без привязки к альбому, файлы сохраняются
|
||||
|
||||
#### Scenario: Восстановление без альбома
|
||||
- **WHEN** пользователь восстанавливает фото, у которого нет альбома
|
||||
- **THEN** система предлагает выбрать альбом или создать новый и восстанавливает фото в него
|
||||
|
||||
### Requirement: Метки фото
|
||||
Пользователь SHALL присваивать фото свои метки (область — пользователь): создавать новые и
|
||||
выбирать из списка с поиском; метки SHALL отдаваться вместе с фото и сниматься.
|
||||
|
||||
#### Scenario: Создание метки
|
||||
- **WHEN** пользователь вводит новую метку и подтверждает
|
||||
- **THEN** метка создаётся и привязывается к фото
|
||||
|
||||
#### Scenario: Выбор из списка с поиском
|
||||
- **WHEN** пользователь вводит часть названия метки
|
||||
- **THEN** показываются подходящие метки пользователя для выбора
|
||||
|
||||
#### Scenario: Снятие метки
|
||||
- **WHEN** пользователь убирает метку с фото
|
||||
- **THEN** метка отвязывается от фото
|
||||
|
||||
@@ -316,10 +316,69 @@ Ctrl/Cmd-клик — добавить или убрать, Shift-клик —
|
||||
- **WHEN** пользователь открывает обзор альбомов
|
||||
- **THEN** правая панель не отображается
|
||||
|
||||
### Requirement: Действия в режиме просмотра
|
||||
В режиме просмотра правая панель SHALL содержать раздел «Действия» со строками-пунктами
|
||||
(иконка + подпись) «Скачать», «Переместить», «Копировать» и «Удалить» (удаление — по правам).
|
||||
Верхняя панель просмотра SHALL не дублировать скачивание и удаление.
|
||||
|
||||
#### Scenario: Действия в просмотре
|
||||
- **WHEN** открыт режим просмотра фото
|
||||
- **THEN** раздел «Действия» содержит «Скачать», «Переместить», «Копировать» и «Удалить», а в верхней панели этих кнопок нет
|
||||
|
||||
#### Scenario: Удаление из панели
|
||||
- **WHEN** пользователь удаляет фото из раздела «Действия»
|
||||
- **THEN** открывается подтверждение, и после удаления просмотр переходит к соседнему фото
|
||||
|
||||
### Requirement: Контекстное меню фото
|
||||
Правый клик по фото в сетке и по изображению в режиме просмотра SHALL открывать контекстное
|
||||
меню с пунктами раздела «Действия» («Скачать», «Переместить», «Копировать», «Удалить»; в сетке
|
||||
также «Открыть (просмотр)»), выполняющими те же действия.
|
||||
|
||||
#### Scenario: Меню в сетке
|
||||
- **WHEN** пользователь кликает правой кнопкой по фото в сетке
|
||||
- **THEN** открывается меню с пунктами «Открыть (просмотр)», «Скачать», «Переместить», «Копировать», «Удалить»
|
||||
|
||||
#### Scenario: Меню в просмотре
|
||||
- **WHEN** пользователь кликает правой кнопкой по изображению в режиме просмотра
|
||||
- **THEN** открывается меню с пунктами «Скачать», «Переместить», «Копировать», «Удалить»
|
||||
|
||||
### Requirement: Контекстное меню альбома
|
||||
Правый клик по альбому в рейле и по карточке на обзоре SHALL открывать меню с пунктами
|
||||
«Переименовать» и «Удалить»; переименование SHALL открывать диалог, удаление — подтверждение.
|
||||
|
||||
#### Scenario: Меню альбома
|
||||
- **WHEN** пользователь кликает правой кнопкой по альбому
|
||||
- **THEN** открывается меню с «Переименовать» и «Удалить»
|
||||
|
||||
#### Scenario: Удаление альбома
|
||||
- **WHEN** пользователь подтверждает удаление альбома
|
||||
- **THEN** альбом удаляется, а его фото попадают в корзину
|
||||
|
||||
### Requirement: Счётчики в рейле
|
||||
Рейл SHALL показывать число фото у каждого альбома и число элементов в корзине.
|
||||
|
||||
#### Scenario: Счётчики
|
||||
- **WHEN** пользователь смотрит на рейл альбомов
|
||||
- **THEN** у каждого альбома показано число фото, а у корзины — число удалённых фото
|
||||
|
||||
### Requirement: Метки в панели фото
|
||||
Панель фото SHALL содержать раздел «Метки» с чипами меток фото и полем добавления, дающим
|
||||
поиск по меткам пользователя и создание новых.
|
||||
|
||||
#### Scenario: Раздел «Метки»
|
||||
- **WHEN** открыт просмотр или выбран один фото в сетке
|
||||
- **THEN** в панели есть раздел «Метки» с текущими метками и полем добавления с поиском
|
||||
|
||||
### Requirement: Компактный верхний хром
|
||||
Верхняя панель приложения и верхняя панель режима просмотра SHALL иметь минимальную высоту
|
||||
(узкие отступы, иконочные органы управления), сохраняя все действия доступными.
|
||||
(узкие отступы, иконочные органы управления), сохраняя все действия доступными. Верхняя панель
|
||||
режима просмотра SHALL быть прозрачным оверлеем и SHALL не занимать место в раскладке,
|
||||
увеличивая область фото.
|
||||
|
||||
#### Scenario: Компактные верхние панели
|
||||
- **WHEN** открыт список альбомов или режим просмотра фото
|
||||
- **THEN** верхняя панель занимает минимум высоты, а её действия (навигация, полный экран, скачивание, удаление) доступны
|
||||
|
||||
#### Scenario: Прозрачная панель просмотра
|
||||
- **WHEN** открыт режим просмотра фото
|
||||
- **THEN** верхняя панель нарисована поверх фото без фона и не уменьшает область изображения
|
||||
|
||||
@@ -76,3 +76,10 @@
|
||||
- [x] 9.15 Проверка: `cd frontend && npm run check`, `cd backend && go test ./...`, `make build`, `openspec validate "lightroom-workspace-ui"`.
|
||||
- [x] 9.16 Раздел «Действия» в панели — пункты-надписи с иконками (не кнопки): «Открыть (просмотр)», «Переместить», «Копировать», «Удалить»; надпись «Выбрано:» убрана. Проверка: Playwright — «Открыть (просмотр)» открывает просмотр, раздел присутствует (4/4).
|
||||
- [x] 9.17 Компактный верхний хром: верхняя панель приложения и верхняя панель режима просмотра сведены к минимуму (узкие отступы, иконочные кнопки `size-4`, компактный переключатель темы). Проверка: высота `<header>` ~31px, верхней панели просмотра ~29px; `npm run check`.
|
||||
- [x] 9.18 Действия просмотра перенесены в правую панель: в разделе «Действия» — «Скачать», «Переместить», «Копировать», «Удалить»; из верхней панели просмотра скачивание и удаление убраны; удаление через стор `viewerActions` и страницу просмотра. Проверка: Playwright — верх без скачать/удалить, панель с действиями, удаление из панели переходит к соседнему фото (9/9).
|
||||
- [x] 9.19 Единая панель фото: содержимое для одного фото вынесено в общий компонент `PhotoPanel`, переиспользуемый в просмотре и при одиночном выборе в сетке (не копия); мультивыбор — только «Действия». Проверка: Playwright — просмотр 9/9, выбор 5/5, мультивыбор 9/9; `npm run check`.
|
||||
- [x] 9.20 Контекстное меню фото (правая кнопка) в сетке и просмотре с пунктами из «Действий»; общий стор `contextMenu` и глобальный `ContextMenu`. Проверка: Playwright — меню в сетке (5 пунктов), меню в просмотре (4 пункта), «Открыть» из меню и «Переместить» → диалог (12/12).
|
||||
- [x] 9.21 Контекстное меню альбома («Переименовать»/«Удалить») в рейле и на обзоре; диалоги в `AlbumActionsDialogs`. Проверка: Playwright — меню, переименование, удаление (4/4).
|
||||
- [x] 9.22 Удаление альбома помещает фото в корзину без потери файлов (FK `photos.album_id` → `ON DELETE SET NULL`, миграция `000006`); восстановление без альбома предлагает выбрать/создать альбом (`album_required` + `POST /photos/:id/restore {album_id}`). Проверка: Playwright — удалить альбом → фото в корзине, диалог восстановления, фото в новом альбоме (7/7).
|
||||
- [x] 9.23 Счётчики в рейле: число фото у альбома и число элементов корзины (`GET /trash/count`, стор `trash`); обновление счётчика альбома после загрузки/удаления. Проверка: Playwright — альбом 2, корзина 0→1 (3/3).
|
||||
- [x] 9.24 Метки фото (область — пользователь): таблицы `tags`/`photo_tags` (миграция `000007`), `GET /tags?q=`, `POST /photos/:id/tags`, `DELETE /photos/:id/tags/:tag_id`, метки в ответе фото; раздел «Метки» в панели с чипами, поиском и созданием. Проверка: API — создание/поиск/снятие; Playwright — создание, снятие, выбор из поиска (4/4).
|
||||
|
||||
Reference in New Issue
Block a user