feat: photo multiselect, move/copy, trash and compact chrome
- 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)
This commit is contained in:
@@ -174,6 +174,40 @@ EXIF извлекается при загрузке и хранится в `phot
|
||||
маршрута, а в альбоме — у выбора; выбор сбрасывается при смене альбома. Альтернатива — хранить
|
||||
выбор в URL (query) — избыточно для локального выделения.
|
||||
|
||||
### 20. Корзина: мягкое удаление и подсчёт ссылок
|
||||
Удаление фото — мягкое (`photos.deleted_at`, миграция `000005_photo_trash`): списки и просмотр
|
||||
исключают такие фото, `GET /trash` отдаёт их по альбомам пользователя (владелец или участник).
|
||||
Восстановление снимает `deleted_at`. Окончательное удаление удаляет запись, и только если
|
||||
после этого `storage_key` больше нигде не встречается (копии в других альбомах разделяют
|
||||
объект), удаляются объекты S3 (оригинал, thumb, preview). Альтернатива — удалять S3 сразу —
|
||||
порвала бы копии.
|
||||
|
||||
### 21. Перенос и копирование — только метаданные
|
||||
Перенос меняет `photos.album_id`; копирование создаёт новую запись с тем же `storage_key`,
|
||||
thumb/preview и метаданными (клон). Объекты S3 не дублируются, поэтому «ссылки в любых
|
||||
альбомах» — это строки photos с общим `storage_key`. Права: удаление в источнике (автор или
|
||||
owner/admin) и загрузка в цель (member+).
|
||||
|
||||
### 22. Мультивыбор и трансфер через общие сторы
|
||||
Выбор — стор `selection` (набор фото + anchor); Ctrl/Cmd — toggle, Shift — диапазон по
|
||||
индексам текущей сетки. Действия инициируют диалог: стор `transfer` хранит ids, необязательный
|
||||
целевой альбом и действие; глобальный `TransferDialog` (портал) выбирает альбом и
|
||||
перенос/копию. Перетаскивание с сетки на альбом рейла передаёт ids через `dataTransfer` типа
|
||||
`application/x-galo-photos`; дроп-цель — элементы рейла. Обработчик загрузки показывает оверлей
|
||||
только для типа `Files`, поэтому при переносе фото зона загрузки не появляется.
|
||||
|
||||
### 23. Рейл: вход в корзину и скрытие панели
|
||||
В рейле альбомов последним элементом списка добавлена «Корзина» (иконка корзины), ведущая на
|
||||
маршрут `/trash` со списком удалённых фото и действиями «Вернуть»/«Удалить навсегда». Пустая
|
||||
правая панель на обзоре скрыта: `AppShell` получает `panelVisible`, который layout выключает на
|
||||
корневом маршруте.
|
||||
|
||||
### 24. Компактный верхний хром
|
||||
Верхняя панель приложения (`AppShell`) и верхняя панель режима просмотра (`Viewer`) сведены к
|
||||
минимуму: узкие вертикальные отступы (`py-0.5`/`py-1`), иконочные органы управления без рамок,
|
||||
иконки `size-4`, компактный переключатель темы. Функциональность (меню, панель, темы, полный
|
||||
экран, навигация, скачивание, удаление) сохраняется.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Много альбомов → много presign в списке] → presign это локальная крипто-операция без сети;
|
||||
|
||||
@@ -48,10 +48,18 @@
|
||||
(клик раскрывает все). Настройки хранятся в профиле пользователя (server-side), применяются
|
||||
ко всем фото, а при отсутствии настроек показывается базовый набор; «Сбросить» удаляет
|
||||
настройки из профиля.
|
||||
- **Мультивыбор и операции.** Ctrl/Shift-выбор нескольких фото в сетке; в правой панели —
|
||||
раздел «Действия» с пунктами «Открыть (просмотр)», «Переместить», «Копировать», «Удалить»;
|
||||
перетаскивание выделенных фото на альбом в рейле открывает диалог перенос/копирование.
|
||||
Перенос и копирование меняют только метаданные (объекты S3 не трогаются). Любое удаление
|
||||
помещает фото в корзину; объекты S3 удаляются лишь когда на них нет ссылок в других
|
||||
альбомах. Корзина — последний элемент списка альбомов в левом рейле (иконка корзины); на
|
||||
обзоре убрана пустая правая панель.
|
||||
- **Счётчики.** В ответе альбома — число фото и участников.
|
||||
- **Темы и хром.** Существующие 4 темы сохраняются; `dark` — графит Lightroom и дефолт,
|
||||
`neutral` — тёплый графит; переключатель упорядочен тёмная → нейтральная → светлая →
|
||||
системная; добавляются токены `--chrome`/`--canvas`; скроллбары стилизованы в тон темы.
|
||||
Верхняя панель приложения и верхняя панель режима просмотра — компактные (минимум высоты).
|
||||
- **Адаптив.** На узких экранах рейл становится off-canvas drawer, правая панель —
|
||||
bottom-sheet, filmstrip — горизонтальным скроллом.
|
||||
|
||||
@@ -66,7 +74,7 @@
|
||||
|
||||
- `albums`: отображение обложки, счётчики фото/участников и явный выбор обложки.
|
||||
- `photos`: автообложка только при отсутствии выбора; переименование фото; лимит размера
|
||||
загрузки; извлечение и хранение EXIF-метаданных.
|
||||
загрузки; извлечение и хранение EXIF-метаданных; перенос/копирование и корзина.
|
||||
- `auth`: хранение пользовательских настроек отображения информации в профиле.
|
||||
|
||||
## Impact
|
||||
|
||||
@@ -53,3 +53,37 @@
|
||||
#### Scenario: Фото без метаданных
|
||||
- **WHEN** загружается фото без читаемых EXIF
|
||||
- **THEN** данные фото не содержат атрибутов, а загрузка выполняется успешно
|
||||
|
||||
### Requirement: Перенос и копирование фото между альбомами
|
||||
Пользователь с правом удаления в исходном альбоме и правом загрузки в целевом SHALL иметь
|
||||
возможность перенести или скопировать фото в другой альбом. Операции SHALL изменять только
|
||||
метаданные и SHALL не изменять объекты хранилища.
|
||||
|
||||
#### Scenario: Перенос
|
||||
- **WHEN** пользователь переносит фото в другой альбом
|
||||
- **THEN** фото исчезает из исходного альбома и появляется в целевом, объекты S3 не меняются
|
||||
|
||||
#### Scenario: Копирование
|
||||
- **WHEN** пользователь копирует фото в другой альбом
|
||||
- **THEN** в целевом альбоме появляется новая запись, ссылающаяся на те же объекты S3
|
||||
|
||||
### Requirement: Корзина фото
|
||||
Удаление фото SHALL помещать его в корзину (мягкое удаление). Восстановление SHALL возвращать
|
||||
фото в его альбом. Окончательное удаление SHALL удалять запись, а объекты хранилища SHALL
|
||||
удаляться только когда на них не остаётся ссылок ни в одном альбоме.
|
||||
|
||||
#### Scenario: Удаление в корзину
|
||||
- **WHEN** пользователь удаляет фото
|
||||
- **THEN** фото пропадает из альбома и появляется в корзине
|
||||
|
||||
#### Scenario: Восстановление
|
||||
- **WHEN** пользователь восстанавливает фото из корзины
|
||||
- **THEN** фото возвращается в свой альбом
|
||||
|
||||
#### Scenario: Окончательное удаление без ссылок
|
||||
- **WHEN** пользователь удаляет фото из корзины навсегда, и на те же объекты нет ссылок в других альбомах
|
||||
- **THEN** запись и объекты хранилища удаляются
|
||||
|
||||
#### Scenario: Окончательное удаление при наличии ссылок
|
||||
- **WHEN** на объект хранилища ссылается копия в другом альбоме
|
||||
- **THEN** запись удаляется, а объект хранилища сохраняется
|
||||
|
||||
@@ -277,3 +277,49 @@ SHALL отображаться вне каркаса, но в общей тем
|
||||
#### Scenario: Смена альбома
|
||||
- **WHEN** пользователь переходит в другой альбом
|
||||
- **THEN** отметка выбора сбрасывается
|
||||
|
||||
### Requirement: Мультивыбор фото в сетке
|
||||
В режиме альбома пользователь SHALL выбирать несколько фото: обычный клик — одно фото,
|
||||
Ctrl/Cmd-клик — добавить или убрать, Shift-клик — диапазон. Выбранные фото SHALL быть отмечены.
|
||||
|
||||
#### Scenario: Мультивыбор
|
||||
- **WHEN** пользователь кликает по фото с зажатым Ctrl/Cmd или Shift
|
||||
- **THEN** соответствующие фото отмечены, а панель показывает количество выбранных
|
||||
|
||||
### Requirement: Действия с выбранными фото
|
||||
Когда выбрано одно или несколько фото, правая панель SHALL показывать раздел «Действия» со
|
||||
строками-пунктами (иконка + подпись) «Открыть (просмотр)», «Переместить», «Копировать» и
|
||||
«Удалить»; удаление SHALL подтверждаться диалогом.
|
||||
|
||||
#### Scenario: Действия
|
||||
- **WHEN** выбрано одно или несколько фото
|
||||
- **THEN** панель показывает раздел «Действия» с пунктами «Открыть (просмотр)», «Переместить», «Копировать» и «Удалить»
|
||||
|
||||
### Requirement: Перетаскивание выбранных фото на альбом
|
||||
Выбранные фото SHALL перетаскиваться на альбом в рейле; при отпускании SHALL открываться
|
||||
диалог выбора переноса или копирования с указанием целевого альбома. Во время такого
|
||||
перетаскивания зона загрузки файлов SHALL не показываться.
|
||||
|
||||
#### Scenario: Перетаскивание на альбом
|
||||
- **WHEN** пользователь перетаскивает выделенные фото на альбом в рейле и отпускает
|
||||
- **THEN** открывается диалог с выбором переноса/копирования и целевым альбомом
|
||||
|
||||
### Requirement: Корзина в списке альбомов
|
||||
Рейл альбомов SHALL содержать вход в корзину последним элементом списка (с иконкой корзины).
|
||||
Экран обзора альбомов SHALL не показывать пустую правую панель.
|
||||
|
||||
#### Scenario: Вход в корзину
|
||||
- **WHEN** пользователь открывает приложение
|
||||
- **THEN** в левом рейле последним элементом списка показана «Корзина» с иконкой корзины
|
||||
|
||||
#### Scenario: Обзор без панели
|
||||
- **WHEN** пользователь открывает обзор альбомов
|
||||
- **THEN** правая панель не отображается
|
||||
|
||||
### Requirement: Компактный верхний хром
|
||||
Верхняя панель приложения и верхняя панель режима просмотра SHALL иметь минимальную высоту
|
||||
(узкие отступы, иконочные органы управления), сохраняя все действия доступными.
|
||||
|
||||
#### Scenario: Компактные верхние панели
|
||||
- **WHEN** открыт список альбомов или режим просмотра фото
|
||||
- **THEN** верхняя панель занимает минимум высоты, а её действия (навигация, полный экран, скачивание, удаление) доступны
|
||||
|
||||
@@ -69,3 +69,10 @@
|
||||
- [x] 9.8 Базовый набор атрибутов по умолчанию при пустых настройках (модель `visible` вместо `hidden`); в диалоге — иконки «отметить все»/«снять все» вместо «Показать всё»; «Сбросить» удаляет настройки из профиля (`PATCH {}`). Проверка: Playwright — дефолт даёт базовый набор и «…», снятие/отметка всех, сброс очищает профиль (12/12).
|
||||
- [x] 9.9 Выбор фото в сетке: одиночный клик выделяет фото рамкой и показывает его сведения в правой панели, двойной — открывает просмотр; стор `selection`, сброс при смене альбома. Проверка: Playwright — выделение без навигации, панель показывает инфо, двойной клик открывает просмотр (5/5).
|
||||
- [x] 9.10 Вьюер и модалки: убраны таймеры автоскрытия (панели/стрелки мгновенно), стрелки появляются только в границах вьюера, `f`/кнопка переключают вложенный fullscreen вьюера (не выключают режим приложения); модалки рендерятся порталом в `document.body` (центрирование во вьюпорте). Проверка: Playwright — стрелки 4/4, fullscreen+f 6/6, центрирование 2/2.
|
||||
- [x] 9.11 Корзина: миграция `000005_photo_trash` (`deleted_at`), мягкое удаление (`DELETE /photos/:id`), `GET /trash`, `POST /photos/:id/restore`, `DELETE /photos/:id/permanent` с удалением объектов S3 только при отсутствии ссылок на `storage_key`. Проверка: удаление → в корзине, восстановление возвращает, копия сохраняет объект.
|
||||
- [x] 9.12 Перенос/копирование: `POST /photo-batch/{delete,move,copy}`, клон записи с общими объектами S3 (метаданные), события SSE для источника и цели. Проверка: копия даёт N фото в цели, перенос убирает из источника.
|
||||
- [x] 9.13 Фронтенд: стор `selection` (Ctrl/Shift-мультивыбор), действия в панели «Удалить/Переместить/Копировать», drag выделенных на альбом рейла, глобальный `TransferDialog`. Проверка: Playwright — мультивыбор и copy 9/9, drag+move 4/4.
|
||||
- [x] 9.14 Рейл: вход «Корзина» последним элементом списка с иконкой, скрытие пустой правой панели (`panelVisible`), маршрут `/trash` с восстановлением и окончательным удалением. Проверка: в рейле есть вход в корзину, на обзоре нет правой панели.
|
||||
- [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`.
|
||||
|
||||
Reference in New Issue
Block a user