Новый алгоритм редактирования: обработка идёт только на клиенте. - Фильтры «Свет» считает WASM-модуль на Rust (frontend/wasm/light, 1.5 КБ): он строит тональные кривые и обрабатывает пиксели. При недоступности модуля используется JS-реализация с тем же результатом — расхождение проверяется тестом. - Просмотр рисует видимую область фото с правками на canvas поверх исходной версии, поэтому изменения видны сразу, без ожидания рендера. - Правки уходят на сервер не сразу: через десять секунд без изменений или при переключении фото. Сервер сохраняет параметры и готовит варианты сетки и полноразмерную версию в фоне, о готовности сообщает через SSE — клиент подтягивает готовую версию.
814 lines
62 KiB
Markdown
814 lines
62 KiB
Markdown
# ui-workspace Specification
|
||
|
||
## Purpose
|
||
|
||
Рабочий стол веб-приложения: постоянный каркас с рейлом альбомов, контекстной панелью,
|
||
режимом просмотра фото и адаптивным поведением, обеспечивающий цельный «lightroom»-вид.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Рабочий стол приложения
|
||
Аутентифицированные экраны SHALL отображаться внутри постоянного каркаса с топбаром, левым
|
||
рейлом альбомов и контекстной правой панелью. Публичные экраны (вход, инвайт, share-ссылка)
|
||
SHALL отображаться вне каркаса, но в общей теме оформления.
|
||
|
||
#### Scenario: Внутренний экран
|
||
- **WHEN** аутентифицированный пользователь открывает список альбомов или альбом
|
||
- **THEN** экран отрисован внутри каркаса с рейлом и панелью, рейл не перезагружается при переходе
|
||
|
||
#### Scenario: Публичный экран
|
||
- **WHEN** пользователь открывает публичную share-ссылку без входа
|
||
- **THEN** экран отрисован без рейла и контекстной панели, но в тех же цветовых токенах
|
||
|
||
### Requirement: Рейл альбомов
|
||
Рейл SHALL постоянно показывать список доступных альбомов с их обложками и отмечать
|
||
активный альбом.
|
||
|
||
#### Scenario: Активный альбом
|
||
- **WHEN** пользователь находится в альбоме
|
||
- **THEN** соответствующий элемент рейла выделен, остальные показывают обложки или плейсхолдер
|
||
|
||
### Requirement: Создание альбома
|
||
Создание альбома SHALL выполняться через иконочную кнопку с подсказкой, открывающую
|
||
модальное окно ввода названия, вместо инлайн-формы.
|
||
|
||
#### Scenario: Создание через модалку
|
||
- **WHEN** пользователь нажимает кнопку создания, вводит название и подтверждает
|
||
- **THEN** система создаёт альбом и делает его активным
|
||
|
||
#### Scenario: Пустое название
|
||
- **WHEN** пользователь подтверждает пустое название
|
||
- **THEN** создание не выполняется
|
||
|
||
### Requirement: Контекстная панель альбома
|
||
Правая панель SHALL показывать превью обложки, название, счётчики фото и участников,
|
||
управление обложкой и переходы к участникам, настройкам и ссылкам. Элементы управления
|
||
обложкой SHALL быть доступны только owner и admin.
|
||
|
||
#### Scenario: Владелец альбома
|
||
- **WHEN** owner открывает альбом
|
||
- **THEN** панель показывает управление обложкой и переходы к участникам и настройкам
|
||
|
||
#### Scenario: Наблюдатель
|
||
- **WHEN** viewer открывает альбом
|
||
- **THEN** панель не показывает управление обложкой
|
||
|
||
### Requirement: Режим просмотра фотографии
|
||
Просмотр фото SHALL быть отдельным маршрутом внутри каркаса с крупным изображением,
|
||
лентой миниатюр снизу и панелью метаданных и комментариев. Тот же режим SHALL
|
||
использоваться на публичных share-страницах. Миниатюры нижней ленты SHALL давать обратную связь
|
||
при наведении указателя: изображение SHALL плавно увеличиваться внутри плитки; тот же эффект SHALL
|
||
применяться при фокусе с клавиатуры. Лента SHALL следовать за текущим фото: при переходе
|
||
стрелками или выбором в ленте активная миниатюра SHALL оставаться в поле зрения, а лента —
|
||
прокручиваться к ней.
|
||
|
||
#### Scenario: Открытие фото
|
||
- **WHEN** пользователь выбирает фото в сетке
|
||
- **THEN** открывается маршрут фото в каркасе, рейл и панель остаются доступны, адрес отражает фото
|
||
|
||
#### Scenario: Навигация в просмотре
|
||
- **WHEN** пользователь использует стрелки влево/вправо или ленту миниатюр
|
||
- **THEN** показывается соседнее фото, адрес обновляется
|
||
|
||
#### Scenario: Публичный просмотр
|
||
- **WHEN** посетитель share-ссылки открывает фото
|
||
- **THEN** используется тот же режим просмотра без рейла и панели
|
||
|
||
#### Scenario: Наведение на миниатюру
|
||
- **WHEN** пользователь наводит указатель на миниатюру в нижней ленте
|
||
- **THEN** изображение миниатюры плавно увеличивается внутри плитки
|
||
|
||
#### Scenario: Лента следует за фото
|
||
- **WHEN** пользователь листает фото стрелками влево/вправо
|
||
- **THEN** лента миниатюр прокручивается так, что активная миниатюра остаётся в поле зрения
|
||
|
||
### Requirement: Плотность сетки
|
||
|
||
Сетка SHALL давать пользователю управлять размером миниатюр: сдвиг регулятора вправо SHALL
|
||
увеличивать миниатюры, влево — уменьшать. Регулятор SHALL сопровождаться кнопками шага в
|
||
сторону уменьшения и увеличения. Выбранный размер SHALL сохраняться между сессиями и SHALL
|
||
быть общим для обзора альбомов и сетки фото альбома. Клавиши `+` и `-` SHALL менять размер
|
||
миниатюр на шаг так же, как кнопки регулятора; при вводе текста и в открытых диалогах либо
|
||
меню эти клавиши SHALL сохранять обычное действие. В режиме просмотра фото те же клавиши
|
||
SHALL изменять зум изображения, а не размер миниатюр.
|
||
|
||
#### Scenario: Изменение размера
|
||
|
||
- **WHEN** пользователь двигает регулятор плотности
|
||
- **THEN** размер миниатюр меняется, значение сохраняется и восстанавливается при следующем открытии
|
||
|
||
#### Scenario: Направление регулятора
|
||
|
||
- **WHEN** пользователь сдвигает регулятор вправо
|
||
- **THEN** миниатюры становятся крупнее; при сдвиге влево — мельче
|
||
|
||
#### Scenario: Кнопки шага
|
||
|
||
- **WHEN** пользователь нажимает кнопку увеличения или уменьшения рядом с регулятором
|
||
- **THEN** размер миниатюр меняется на один шаг и сохраняется
|
||
|
||
#### Scenario: Клавиши размера
|
||
|
||
- **WHEN** пользователь нажимает `+` или `-`
|
||
- **THEN** размер миниатюр меняется на один шаг, как при нажатии кнопок регулятора
|
||
|
||
#### Scenario: Клавиши в поле ввода
|
||
|
||
- **WHEN** фокус находится в поле ввода или открыт диалог либо меню
|
||
- **THEN** `+` и `-` не меняют размер миниатюр
|
||
|
||
#### Scenario: Единый размер в обоих местах
|
||
|
||
- **WHEN** пользователь меняет размер на обзоре альбомов или в сетке фото альбома
|
||
- **THEN** тот же размер применяется в обоих местах
|
||
|
||
### Requirement: Загрузка фотографий из рабочего стола
|
||
Загрузка SHALL запускаться действием в тулбаре; перетаскивание файлов над альбомом SHALL
|
||
показывать оверлей и загружать их; пустой альбом SHALL показывать подсказку-зону загрузки.
|
||
Загрузка SHALL быть доступна только ролям с правом загрузки; принимать SHALL изображения и
|
||
видео.
|
||
|
||
#### Scenario: Перетаскивание
|
||
- **WHEN** пользователь с правом загрузки перетаскивает файлы над альбомом
|
||
- **THEN** показывается оверлей загрузки, и файлы загружаются
|
||
|
||
#### Scenario: Пустой альбом
|
||
- **WHEN** пользователь открывает альбом без фото и имеет право загрузки
|
||
- **THEN** в центре показана зона-подсказка загрузки
|
||
|
||
#### Scenario: Загрузка видео
|
||
- **WHEN** пользователь с правом загрузки выбирает или перетаскивает видеофайл
|
||
- **THEN** видео загружается в альбом наравне с изображениями
|
||
|
||
### Requirement: Темы и графитовый хром
|
||
Тёмная тема SHALL использовать графитовый хром Lightroom и SHALL применяться по умолчанию;
|
||
нейтральная тема SHALL быть тёплым графитом; остальные темы SHALL оставаться доступными, а
|
||
скроллбары SHALL быть стилизованы в цвет темы.
|
||
|
||
#### Scenario: Первый вход
|
||
- **WHEN** пользователь впервые открывает приложение без сохранённой темы
|
||
- **THEN** применяется тёмная графитовая тема
|
||
|
||
#### Scenario: Смена темы
|
||
- **WHEN** пользователь выбирает другую тему
|
||
- **THEN** интерфейс, иконки и скроллбары меняются согласованно, и выбор сохраняется
|
||
|
||
#### Scenario: Нейтральная тема
|
||
- **WHEN** пользователь выбирает нейтральную тему
|
||
- **THEN** применяется тёплый графитовый хром, отличный от холодной тёмной темы
|
||
|
||
### Requirement: Адаптивность рабочего стола
|
||
На узких экранах рейл SHALL становиться выдвижной панелью слева, контекстная панель — выдвижной
|
||
панелью справа, а лента миниатюр — горизонтально прокручиваемой. Кнопка «Панель» на узком экране
|
||
SHALL выдвигать правую панель поверх содержимого; затемняющая область SHALL закрывать панель.
|
||
|
||
#### Scenario: Узкий экран
|
||
- **WHEN** ширина окна меньше порога рабочего стола
|
||
- **THEN** рейл скрыт за кнопкой открытия, панель сворачиваема, лента прокручивается горизонтально
|
||
|
||
#### Scenario: Открытие панели
|
||
- **WHEN** пользователь нажимает кнопку «Панель» на узком экране
|
||
- **THEN** правая панель выдвигается и становится видимой поверх содержимого
|
||
|
||
### Requirement: Доступность рабочего стола
|
||
Иконочные органы управления каркаса SHALL иметь доступные имена, панели SHALL быть
|
||
достижимы с клавиатуры с видимым фокусом, анимации SHALL учитывать
|
||
предпочтение уменьшенного движения.
|
||
|
||
#### Scenario: Клавиатурная навигация
|
||
- **WHEN** пользователь работает только с клавиатуры
|
||
- **THEN** органы управления каркаса достижимы, доступные имена присутствуют, фокус виден
|
||
|
||
### Requirement: Изменение ширины панелей
|
||
Пользователь SHALL иметь возможность менять ширину левой и правой панелей перетаскиванием, а
|
||
выбранная ширина SHALL сохраняться между сессиями.
|
||
|
||
#### Scenario: Перетаскивание разделителя
|
||
- **WHEN** пользователь тянет разделитель у панели на рабочем столе
|
||
- **THEN** панель меняет ширину в допустимых пределах
|
||
|
||
#### Scenario: Сохранение ширины
|
||
- **WHEN** пользователь перезагружает приложение
|
||
- **THEN** восстановленные панели имеют сохранённую ширину
|
||
|
||
### Requirement: Скрытие боковых панелей
|
||
В режиме просмотра альбома SHALL быть доступна кнопка скрытия левой и правой боковых панелей
|
||
в верхней панели сетки рядом с регулятором размера миниатюр и хоткей `Tab`. В скрытом режиме
|
||
панели SHALL не занимать место в раскладке, содержимое альбома SHALL занимать всю ширину,
|
||
а панели SHALL показываться при наведении указателя на левую или правую кромку окна и
|
||
скрываться после ухода указателя. Хоткей SHALL не срабатывать при вводе текста в поля и при
|
||
открытых модальных диалогах или меню. Размер миниатюр в скрытом режиме SHALL сохраняться:
|
||
вместо растянутых плиток сетка SHALL показывать больше фото в строке.
|
||
|
||
#### Scenario: Переключение кнопкой
|
||
- **WHEN** пользователь нажимает кнопку скрытия боковых панелей в альбоме
|
||
- **THEN** обе панели уезжают за края, а сетка фото занимает всю ширину окна
|
||
|
||
#### Scenario: Размер миниатюр при скрытых панелях
|
||
- **WHEN** боковые панели скрыты
|
||
- **THEN** плитки остаются того же размера, что и с видимыми панелями, а в строке помещается
|
||
больше фото
|
||
|
||
#### Scenario: Переключение хоткеем
|
||
- **WHEN** пользователь нажимает `Tab` в режиме просмотра альбома
|
||
- **THEN** боковые панели скрываются или показываются
|
||
|
||
#### Scenario: Появление у кромки
|
||
- **WHEN** панели скрыты, и указатель подходит к левой или правой кромке окна
|
||
- **THEN** соответствующая панель выезжает поверх содержимого
|
||
|
||
#### Scenario: Скрытие после ухода указателя
|
||
- **WHEN** указатель покидает показанную у кромки панель
|
||
- **THEN** панель снова уезжает за край
|
||
|
||
#### Scenario: Поля ввода и диалоги
|
||
- **WHEN** фокус находится в поле ввода или открыт модальный диалог либо меню
|
||
- **THEN** `Tab` выполняет обычную навигацию и не переключает панели
|
||
|
||
### Requirement: Сворачиваемые разделы панели
|
||
Контекстная панель SHALL группировать содержимое в сворачиваемые разделы, а состояние
|
||
сворачивания SHALL сохраняться; раздел «Информация» SHALL показывать метаданные фото,
|
||
включая извлечённые EXIF-атрибуты.
|
||
|
||
#### Scenario: Сворачивание раздела
|
||
- **WHEN** пользователь нажимает заголовок раздела
|
||
- **THEN** содержимое раздела скрывается или показывается, состояние сохраняется
|
||
|
||
#### Scenario: Метаданные фото
|
||
- **WHEN** открыт просмотр фото
|
||
- **THEN** раздел «Информация» показывает размер, тип, разрешение, дату съёмки, координаты и извлечённые EXIF-атрибуты
|
||
|
||
### Requirement: Полноэкранный просмотр фотографии
|
||
В режиме просмотра SHALL быть доступен переход в полноэкранный режим кнопкой и хоткеем `f`.
|
||
В полноэкранном режиме верхняя и нижняя панели SHALL скрываться и показываться у
|
||
соответствующей кромки экрана.
|
||
|
||
#### Scenario: Вход в полный экран
|
||
- **WHEN** пользователь нажимает кнопку полного экрана или клавишу `f`
|
||
- **THEN** просмотр разворачивается на весь экран, панели скрыты
|
||
|
||
#### Scenario: Панель по кромке
|
||
- **WHEN** курсор касается верхней или нижней кромки экрана
|
||
- **THEN** соответствующая панель показывается, а при уходе курсора скрывается сразу
|
||
|
||
#### Scenario: Выход из полного экрана
|
||
- **WHEN** пользователь повторно нажимает кнопку или клавишу `f`
|
||
- **THEN** полноэкранный режим закрывается
|
||
|
||
#### Scenario: Полноэкранный режим приложения
|
||
- **WHEN** включён полноэкранный режим приложения, и пользователь нажимает `f`
|
||
- **THEN** открывается полноэкранный просмотр фото (вложенно), а полноэкранный режим приложения не выключается
|
||
|
||
### Requirement: Зум в просмотре
|
||
Просмотр SHALL поддерживать зум колесом мыши без прокрутки, с привязкой к указателю и
|
||
верхним пределом 200% размеров фото. Клавиши `+` и `-` SHALL менять масштаб на шаг, сохраняя
|
||
точку в центре просмотра. `Space` SHALL возвращать изображение из любого зума во
|
||
вписанный вид, а из вписанного — в 100%. При зуме SHALL догружаться оригинал изображения.
|
||
Стрелки соседних фото SHALL появляться у левой/правой кромки и прятаться в зуме. Зум SHALL
|
||
работать и в полноэкранном режиме.
|
||
|
||
#### Scenario: Зум колесом к указателю
|
||
- **WHEN** пользователь вращает колесо мыши над фото
|
||
- **THEN** масштаб меняется относительно курсора, страница не прокручивается, предел — 200%
|
||
|
||
#### Scenario: Зум клавишами
|
||
- **WHEN** пользователь нажимает `+` или `-` в режиме просмотра фото
|
||
- **THEN** масштаб меняется на шаг, точка в центре просмотра остаётся на месте, а в полноэкранном
|
||
режиме зум работает так же
|
||
|
||
#### Scenario: Пробел
|
||
- **WHEN** пользователь нажимает `Space`
|
||
- **THEN** из вписанного вида изображение переходит в 100%, а из любого зума возвращается во вписанный
|
||
|
||
#### Scenario: Оригинал в зуме
|
||
- **WHEN** пользователь входит в зум
|
||
- **THEN** догружается оригинал изображения и подменяется по факту загрузки
|
||
|
||
#### Scenario: Стрелки соседних фото
|
||
- **WHEN** курсор находится у левой или правой кромки области просмотра
|
||
- **THEN** показывается соответствующая стрелка, скрываясь сразу после ухода; в зуме стрелки скрыты
|
||
|
||
#### Scenario: Панорамирование
|
||
- **WHEN** фото увеличено
|
||
- **THEN** пользователь может перемещать изображение перетаскиванием и прокруткой
|
||
|
||
### Requirement: Удаление фото из просмотра
|
||
Автор фото, владелец и админ альбома SHALL иметь возможность удалить текущее фото из режима
|
||
просмотра; удаление SHALL подтверждаться диалогом.
|
||
|
||
#### Scenario: Подтверждение удаления
|
||
- **WHEN** пользователь с правом удаления нажимает «Удалить»
|
||
- **THEN** открывается диалог подтверждения, и после подтверждения фото удаляется
|
||
|
||
#### Scenario: Лента после удаления
|
||
- **WHEN** фото удалено
|
||
- **THEN** просмотр переходит к соседнему фото, а лента миниатюр обновляется без удалённого
|
||
|
||
### Requirement: Возврат к обзору
|
||
Из альбома и из режима просмотра SHALL быть доступен возврат к обзору альбомов.
|
||
|
||
#### Scenario: Возврат из альбома
|
||
- **WHEN** пользователь нажимает кнопку «назад» слева от названия альбома
|
||
- **THEN** открывается обзор альбомов
|
||
|
||
#### Scenario: Возврат из просмотра
|
||
- **WHEN** пользователь нажимает кнопку «назад» в режиме просмотра
|
||
- **THEN** открывается альбом с сеткой фото
|
||
|
||
### Requirement: Настройки списка атрибутов информации
|
||
Раздел «Информация» SHALL предоставлять доступ к настройкам (иконка-шестерёнка), открывающим
|
||
модальное окно со списком EXIF-атрибутов, где пользователь SHALL отмечать отображаемые
|
||
атрибуты и задавать их подписи. В окне SHALL быть доступны действия «отметить все» и
|
||
«снять все». Настройки SHALL применяться ко всем фото и SHALL сохраняться в профиле
|
||
пользователя. При отсутствии сохранённых настроек SHALL отображаться базовый набор атрибутов
|
||
(камера, объектив, выдержка, диафрагма, ISO, фокусное расстояние).
|
||
|
||
#### Scenario: Открытие настроек
|
||
- **WHEN** пользователь нажимает иконку настроек в разделе «Информация»
|
||
- **THEN** открывается модальное окно со списком EXIF-атрибутов: галочки видимости и поля подписей, а также действия «отметить все»/«снять все»
|
||
|
||
#### Scenario: Значения по умолчанию
|
||
- **WHEN** у пользователя нет сохранённых настроек
|
||
- **THEN** в разделе «Информация» показан базовый набор атрибутов, а остальные доступны по элементу «…»
|
||
|
||
#### Scenario: Сохранение настроек
|
||
- **WHEN** пользователь отмечает атрибуты, задаёт подпись и сохраняет
|
||
- **THEN** раздел «Информация» учитывает изменения, а настройки сохраняются в профиле
|
||
|
||
#### Scenario: Сброс настроек
|
||
- **WHEN** пользователь сбрасывает настройки
|
||
- **THEN** сохранённые настройки удаляются из профиля, и отображается базовый набор с исходными подписями
|
||
|
||
### Requirement: Свёртка скрытых атрибутов
|
||
Если настройками часть атрибутов скрыта, в конце списка атрибутов SHALL отображаться элемент
|
||
«…»; нажатие на него SHALL раскрывать все атрибуты текущего фото, включая скрытые. Если
|
||
скрытых атрибутов нет, элемент «…» SHALL не отображаться.
|
||
|
||
#### Scenario: Скрытые атрибуты
|
||
- **WHEN** в разделе «Информация» есть скрытые настройками атрибуты
|
||
- **THEN** после видимых атрибутов показан элемент «…»
|
||
|
||
#### Scenario: Раскрытие всех атрибутов
|
||
- **WHEN** пользователь нажимает «…»
|
||
- **THEN** отображаются все атрибуты фото, включая скрытые
|
||
|
||
#### Scenario: Нет скрытых атрибутов
|
||
- **WHEN** ни один атрибут не скрыт
|
||
- **THEN** элемент «…» не отображается
|
||
|
||
### Requirement: Выбор фото в сетке
|
||
В режиме альбома одиночный выбор фото указателем SHALL отмечать его рамкой и показывать его
|
||
сведения в контекстной панели, не открывая просмотр; двойной выбор указателем SHALL открывать
|
||
режим просмотра фото. На сенсорных экранах одиночное касание по фото SHALL открывать режим
|
||
просмотра этого фото и не SHALL выделять его; выделение на сенсорных экранах SHALL выполняться
|
||
долгим нажатием.
|
||
|
||
#### Scenario: Одиночный выбор
|
||
- **WHEN** пользователь кликает по фото в сетке указателем
|
||
- **THEN** фото отмечено рамкой, правая панель показывает сведения об этом фото, просмотр не открывается
|
||
|
||
#### Scenario: Двойной выбор
|
||
- **WHEN** пользователь дважды кликает по фото в сетке указателем
|
||
- **THEN** открывается режим просмотра этого фото
|
||
|
||
#### Scenario: Касание на сенсорном экране
|
||
- **WHEN** на сенсорном устройстве пользователь касается фото в сетке
|
||
- **THEN** открывается режим просмотра этого фото, а отметка выделения не появляется
|
||
|
||
#### 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 содержать раздел «Действия» со строками-пунктами
|
||
(иконка + подпись) «Скачать», «Переместить», «Копировать» и «Удалить» (удаление — по правам).
|
||
Верхняя панель просмотра 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 содержать раздел «Метки» с чипами меток фото и полем добавления, дающим
|
||
поиск по меткам пользователя и создание новых. Изменение меток в панели SHALL сразу отражаться
|
||
в сетке и в режиме просмотра без перезагрузки страницы.
|
||
|
||
#### Scenario: Раздел «Метки»
|
||
- **WHEN** открыт просмотр или выбран один фото в сетке
|
||
- **THEN** в панели есть раздел «Метки» с текущими метками и полем добавления с поиском
|
||
|
||
#### Scenario: Обновление без перезагрузки
|
||
- **WHEN** пользователь добавляет или снимает метку в панели фото
|
||
- **THEN** сетка и режим просмотра сразу отражают изменение (например, бейдж и режим 360-панорамы), без перезагрузки страницы
|
||
|
||
### Requirement: Компактный верхний хром
|
||
Верхняя панель приложения и верхняя панель режима просмотра SHALL иметь минимальную высоту
|
||
(узкие отступы, иконочные органы управления), сохраняя все действия доступными. Верхняя панель
|
||
режима просмотра SHALL быть прозрачным оверлеем и SHALL не занимать место в раскладке,
|
||
увеличивая область фото. Кнопка «На весь экран» верхней панели приложения SHALL иметь хоткей
|
||
`Ctrl+F`; хоткей `f` полноэкранного просмотра SHALL срабатывать только без модификаторов, чтобы
|
||
`Ctrl+F` не перехватывался.
|
||
|
||
#### Scenario: Компактные верхние панели
|
||
- **WHEN** открыт список альбомов или режим просмотра фото
|
||
- **THEN** верхняя панель занимает минимум высоты, а её действия (навигация, полный экран, скачивание, удаление) доступны
|
||
|
||
#### Scenario: Полный экран по хоткею
|
||
- **WHEN** пользователь нажимает `Ctrl+F`
|
||
- **THEN** приложение переходит в полноэкранный режим или выходит из него, а в режиме просмотра
|
||
фото полноэкранным остаётся сам просмотр
|
||
|
||
#### Scenario: Прозрачная панель просмотра
|
||
- **WHEN** открыт режим просмотра фото
|
||
- **THEN** верхняя панель нарисована поверх фото без фона и не уменьшает область изображения
|
||
|
||
### Requirement: Курсор в сетке фото
|
||
Сетка SHALL хранить курсор — текущую ячейку, отдельную от выделения. Перемещение курсора SHALL
|
||
не менять выделение и не обновлять правую панель. Курсор SHALL отображаться контуром,
|
||
отличимым от рамки выделения, SHALL сохраняться при снятии выделения и SHALL устанавливаться
|
||
на фото при клике по нему мышью.
|
||
|
||
#### Scenario: Синхронизация с мышью
|
||
- **WHEN** пользователь кликает по фото в сетке
|
||
- **THEN** фото выделено, а курсор установлен на нём
|
||
|
||
#### Scenario: Перемещение не меняет выделение
|
||
- **WHEN** фокус в сетке и пользователь нажимает стрелку
|
||
- **THEN** курсор перемещается, а выделение и правая панель не меняются
|
||
|
||
#### Scenario: Курсор переживает снятие выделения
|
||
- **WHEN** выделение снято кликом по пустому месту или клавишей Esc
|
||
- **THEN** курсор остаётся на прежней ячейке
|
||
|
||
#### Scenario: Отображение курсора
|
||
- **WHEN** сетка в фокусе и у курсора есть ячейка
|
||
- **THEN** ячейка показывает контур, отличимый от рамки выделения, и он скрывается при уходе фокуса из сетки
|
||
|
||
### Requirement: Клавиши сетки фото
|
||
При фокусе в сетке клавиши SHALL управлять курсором и выделением: `←`/`→` — на соседнее фото с
|
||
переходом на соседнюю строку, `↑`/`↓` — на строку (`density.cols`) с упором в границы.
|
||
`Space` SHALL переключать выделение ячейки под курсором, `Shift`+стрелки — двигать курсор и
|
||
расширять выделение от якоря, `Enter` — открывать просмотр, `Esc` — снимать выделение. Вне
|
||
фокуса сетки клавиши SHALL не действовать.
|
||
|
||
#### Scenario: Горизонтальное перемещение
|
||
- **WHEN** курсор не в конце строки и нажато `→`
|
||
- **THEN** курсор переходит на следующее фото
|
||
|
||
#### Scenario: Перенос на соседнюю строку
|
||
- **WHEN** курсор в конце строки и нажато `→`
|
||
- **THEN** курсор переходит на первое фото следующей строки
|
||
|
||
#### Scenario: Вертикальные границы
|
||
- **WHEN** курсор в верхней или нижней строке и нажато `↑` или `↓`
|
||
- **THEN** курсор остаётся на месте, без переноса
|
||
|
||
#### Scenario: Переключение выделения
|
||
- **WHEN** нажато `Space`
|
||
- **THEN** ячейка под курсором добавляется или убирается из выделения, а правая панель обновляется
|
||
|
||
#### Scenario: Расширение выделения
|
||
- **WHEN** нажато `Shift` вместе со стрелкой
|
||
- **THEN** курсор двигается, а выделение расширяется от якоря до курсора
|
||
|
||
#### Scenario: Открытие просмотра
|
||
- **WHEN** нажато `Enter`
|
||
- **THEN** открывается просмотр фото под курсором
|
||
|
||
#### Scenario: Снятие выделения клавишей
|
||
- **WHEN** нажато `Esc`
|
||
- **THEN** выделение снимается, а курсор остаётся
|
||
|
||
#### Scenario: Прокрутка к курсору
|
||
- **WHEN** курсор перемещается за пределы видимой области сетки
|
||
- **THEN** сетка прокручивается так, чтобы ячейка под курсором стала видимой
|
||
|
||
#### Scenario: Догрузка при выходе за загруженные фото
|
||
- **WHEN** курсор на последнем загруженном фото и нажата стрелка в сторону следующих фото
|
||
- **THEN** следующая порция фото догружается, а курсор переходит на следующее фото
|
||
|
||
#### Scenario: Вне фокуса сетки
|
||
- **WHEN** фокус находится в поле ввода или диалоге и нажаты стрелки или `Space`
|
||
- **THEN** курсор и выделение сетки не меняются
|
||
|
||
### Requirement: Снятие выделения кликом по пустому месту
|
||
Клик по области сетки и её отступам, не по фото и не по органам управления тулбара, рейла и
|
||
панелей, SHALL снимать выделение и возвращать в правой панели свойства альбома. Фокус SHALL
|
||
оставаться в сетке, чтобы клавиши продолжали действовать.
|
||
|
||
#### Scenario: Клик по пустому месту
|
||
- **WHEN** есть выделение и пользователь кликает по пустому месту сетки или её отступам
|
||
- **THEN** выделение снято, а правая панель показывает свойства альбома
|
||
|
||
#### Scenario: Мультивыделение
|
||
- **WHEN** выделено несколько фото и пользователь кликает по пустому месту
|
||
- **THEN** выделение снимается полностью
|
||
|
||
#### Scenario: Клик по фото
|
||
- **WHEN** пользователь кликает по фото
|
||
- **THEN** выделение устанавливается на фото, а не снимается
|
||
|
||
#### Scenario: Клик по органам управления
|
||
- **WHEN** пользователь кликает по кнопке тулбара, элементу рейла или панели
|
||
- **THEN** выделение не снимается
|
||
|
||
#### Scenario: Фокус остаётся в сетке
|
||
- **WHEN** выделение снято кликом по пустому месту
|
||
- **THEN** стрелки продолжают двигать курсор без дополнительного Tab
|
||
|
||
### Requirement: Возврат фокуса из просмотра
|
||
При возврате в альбом из просмотра сетка SHALL выделить последнее просмотренное фото (одиночное),
|
||
поставить на него курсор, вернуть фокус в сетку и прокрутить к ячейке. Если фото нет среди
|
||
загруженных, сетка SHALL догружать страницы, пока оно не появится. Восстановление SHALL
|
||
применяться только к тому альбому, из которого был открыт просмотр.
|
||
|
||
#### Scenario: Возврат из просмотра
|
||
- **WHEN** пользователь открыл фото и закрыл просмотр
|
||
- **THEN** в сетке это фото выделено, курсор на нём, фокус в сетке, а ячейка прокручена в видимую область
|
||
|
||
#### Scenario: Последнее просмотренное фото
|
||
- **WHEN** пользователь пролистал несколько фото в просмотре и закрыл его
|
||
- **THEN** восстанавливается последнее просмотренное фото
|
||
|
||
#### Scenario: Фото вне первой страницы
|
||
- **WHEN** просмотренное фото отсутствует в первой загруженной странице
|
||
- **THEN** сетка догружает страницы, пока фото не появится, и только затем ставит на него курсор
|
||
|
||
#### Scenario: Возврат в другой альбом
|
||
- **WHEN** после просмотра пользователь открывает другой альбом
|
||
- **THEN** восстановление не применяется, выборка пуста
|
||
|
||
#### Scenario: Клавиатура после возврата
|
||
- **WHEN** фокус восстановлен после просмотра
|
||
- **THEN** стрелки сразу двигают курсор без дополнительного Tab
|
||
|
||
### Requirement: Просмотр 360-панорамы
|
||
Фото с тегом `pano360` SHALL открываться в режиме сферического обзора (equirect) вместо плоского
|
||
изображения. Перетаскивание SHALL менять направление взгляда и SHALL перемещать сцену вслед за
|
||
указателем по обеим осям согласованно, колесо — угол обзора, `Space` — сбрасывать вид. Остальные
|
||
фото SHALL просматриваться как прежде. При недоступности WebGL или сбое загрузки/декодирования
|
||
SHALL показываться плоское изображение. На публичных share-страницах панорамный режим SHALL не
|
||
активироваться.
|
||
|
||
#### Scenario: Обзор панорамы
|
||
- **WHEN** пользователь открывает фото с тегом `pano360`
|
||
- **THEN** показывается сферический обзор, и перетаскивание меняет направление взгляда
|
||
|
||
#### Scenario: Направление перетаскивания
|
||
- **WHEN** в режиме панорамы пользователь перетаскивает вправо
|
||
- **THEN** сцена перемещается вправо (направление взгляда поворачивается влево), согласованно с вертикальным перетаскиванием
|
||
|
||
#### Scenario: Обычное фото
|
||
- **WHEN** пользователь открывает фото без тега `pano360`
|
||
- **THEN** показывается обычное плоское изображение
|
||
|
||
#### Scenario: Управление обзором
|
||
- **WHEN** в режиме панорамы пользователь вращает колесо или нажимает `Space`
|
||
- **THEN** колесо меняет угол обзора, а `Space` сбрасывает направление взгляда и угол обзора
|
||
|
||
#### Scenario: Навигация между фото
|
||
- **WHEN** в режиме панорамы пользователь нажимает стрелки влево/вправо или выбирает миниатюру
|
||
- **THEN** открывается соседнее фото, панорамный режим включается или выключается по его тегу
|
||
|
||
#### Scenario: Нет WebGL
|
||
- **WHEN** WebGL недоступен или изображение не загружается
|
||
- **THEN** показывается плоское изображение
|
||
|
||
#### Scenario: Публичная share-страница
|
||
- **WHEN** фото открыто по публичной share-ссылке
|
||
- **THEN** панорамный режим не активируется, показывается плоское изображение
|
||
|
||
### Requirement: Резкость миниатюр
|
||
|
||
Сетка фото и обзор альбомов SHALL подбирать вариант изображения под фактический размер
|
||
плитки, чтобы миниатюры не отображались крупнее доступного разрешения.
|
||
|
||
#### Scenario: Крупные плитки
|
||
|
||
- **WHEN** пользователь увеличивает миниатюры до крупных плиток
|
||
- **THEN** показывается вариант изображения, достаточный для их размера, без расплывания
|
||
|
||
#### Scenario: Мелкие плитки
|
||
|
||
- **WHEN** миниатюры показаны мелкими плитками
|
||
- **THEN** показывается компактный вариант, чтобы не загружать крупное изображение
|
||
|
||
#### Scenario: Вариант ещё не готов
|
||
|
||
- **WHEN** для фото доступен только оригинал или только миниатюра
|
||
- **THEN** показывается доступный вариант, и сетка отображается корректно
|
||
|
||
### Requirement: Воспроизведение видео в просмотре
|
||
Видео в режиме просмотра SHALL воспроизводиться встроенным плеером с нативными контролами и
|
||
постером до готовности варианта; зум, панорамирование и режим панорамы SHALL к видео не
|
||
применяться, а действия скачивания, переноса, копирования и удаления SHALL оставаться доступными.
|
||
|
||
#### Scenario: Плеер
|
||
- **WHEN** пользователь открывает видео в режиме просмотра
|
||
- **THEN** показывается встроенный плеер, а до готовности варианта — постер
|
||
|
||
#### Scenario: Перемотка
|
||
- **WHEN** пользователь перематывает воспроизведение
|
||
- **THEN** плеер показывает запрошенный момент видео
|
||
|
||
#### Scenario: Готовность на лету
|
||
- **WHEN** вариант видео становится готов, пока просмотр открыт
|
||
- **THEN** плеер становится доступен без перезагрузки страницы
|
||
|
||
#### Scenario: Действия над видео
|
||
- **WHEN** открыто видео в режиме просмотра
|
||
- **THEN** доступны скачивание оригинала, перенос, копирование и удаление по правам
|
||
|
||
### Requirement: Плитка видео в сетке
|
||
Плитка видео в сетке SHALL показывать постер, признак видео и длительность; открытие плитки
|
||
SHALL открывать режим просмотра.
|
||
|
||
#### Scenario: Постер и признак
|
||
- **WHEN** видео показано в сетке
|
||
- **THEN** плитка показывает постер, значок воспроизведения и длительность
|
||
|
||
#### Scenario: Постер не готов
|
||
- **WHEN** постер ещё не готов
|
||
- **THEN** плитка показывает плейсхолдер и сетка отображается корректно
|
||
|
||
#### Scenario: Открытие из сетки
|
||
- **WHEN** пользователь открывает плитку видео
|
||
- **THEN** открывается режим просмотра видео
|
||
|
||
### Requirement: Очистка корзины в интерфейсе
|
||
Экран корзины SHALL содержать действие «Очистить корзину», доступное, когда в корзине есть
|
||
элементы. Действие SHALL открывать подтверждение и после подтверждения очищать корзину, а
|
||
список и счётчик корзины в рейле SHALL отражать результат.
|
||
|
||
#### Scenario: Наличие действия
|
||
- **WHEN** пользователь открывает корзину с элементами
|
||
- **THEN** на экране показано действие «Очистить корзину»
|
||
|
||
#### Scenario: Подтверждение очистки
|
||
- **WHEN** пользователь выбирает «Очистить корзину»
|
||
- **THEN** открывается диалог подтверждения, и только после подтверждения корзина очищается
|
||
|
||
#### Scenario: Обновление после очистки
|
||
- **WHEN** корзина очищена
|
||
- **THEN** на экране показывается пустая корзина, а счётчик корзины в рейле обновляется
|
||
|
||
### Requirement: Синхронизация счётчика корзины
|
||
Рейл SHALL обновлять число элементов корзины сразу после любого действия, меняющего
|
||
содержимое корзины, без перезагрузки страницы: помещения фото в корзину, восстановления,
|
||
окончательного удаления и удаления альбома с фото.
|
||
|
||
#### Scenario: Помещение фото в корзину
|
||
- **WHEN** пользователь удаляет одно или несколько фото
|
||
- **THEN** фото покидают сетку, а число элементов корзины в рейле увеличивается на их количество
|
||
|
||
#### Scenario: Восстановление из корзины
|
||
- **WHEN** пользователь восстанавливает фото из корзины
|
||
- **THEN** фото покидает список корзины, а число элементов корзины в рейле уменьшается на единицу
|
||
|
||
#### Scenario: Окончательное удаление
|
||
- **WHEN** пользователь окончательно удаляет фото из корзины
|
||
- **THEN** число элементов корзины в рейле уменьшается на единицу
|
||
|
||
#### Scenario: Удаление альбома
|
||
- **WHEN** пользователь удаляет альбом с фото
|
||
- **THEN** его фото попадают в корзину, а число элементов корзины в рейле увеличивается на их количество
|
||
|
||
### Requirement: Офлайн-режим веб-приложения
|
||
Веб-приложение SHALL хранить в офлайн-кеше браузера оболочку приложения, ответы API
|
||
(списки альбомов и фото) и варианты изображений: эскиз, превью и средний вариант.
|
||
Ключ кеша картинки SHALL определяться путём объекта без подписи, чтобы обновление подписи
|
||
не создавало новые записи. Оригиналы и видео SHALL не кешироваться. Размер кеша SHALL быть
|
||
ограничен, а более старые записи SHALL вытесняться первыми. При выходе из аккаунта кеш
|
||
SHALL очищаться. При отсутствии сети приложение SHALL открываться и показывать сохранённые
|
||
данные, а верхняя панель SHALL показывать индикатор «Офлайн».
|
||
|
||
#### Scenario: Просмотр без сети
|
||
- **WHEN** пользователь открывает ранее просмотренный альбом без подключения к сети
|
||
- **THEN** приложение открывается, показывает сохранённый список фото и их эскизы
|
||
|
||
#### Scenario: Смена подписи объекта
|
||
- **WHEN** подпись объекта обновляется, а содержимое остаётся прежним
|
||
- **THEN** используется та же запись кеша, а не новая
|
||
|
||
#### Scenario: Ограничение кеша
|
||
- **WHEN** кеш картинок превышает предел
|
||
- **THEN** самые старые записи вытесняются, а свежие остаются
|
||
|
||
#### Scenario: Выход из аккаунта
|
||
- **WHEN** пользователь выходит из аккаунта
|
||
- **THEN** офлайн-кеш картинок и данных очищается
|
||
|
||
#### Scenario: Индикатор офлайна
|
||
- **WHEN** браузер сообщает об отсутствии сети
|
||
- **THEN** верхняя панель показывает индикатор «Офлайн»
|
||
|
||
### Requirement: Вкладка «Редактирование»
|
||
Правая панель фото SHALL содержать вкладку «Редактирование» с инструментом «Свет»:
|
||
ползунки экспозиции, контраста, светов, теней, белых и чёрных, а также точечную кривую
|
||
с выбором канала. Правки SHALL применяться к фото сразу и отправляться на сервер после
|
||
десяти секунд без изменений либо при переключении фото. Переключатель «Сброс» SHALL
|
||
удалять параметры правок и отредактированную версию фото. Сетка и просмотр SHALL
|
||
показывать отредактированную версию фото, когда она отрендерена.
|
||
|
||
#### Scenario: Отложенная отправка
|
||
- **WHEN** пользователь меняет ползунок и не трогает панель десять секунд
|
||
- **THEN** правки отправляются на сервер, а сервер рендерит фото в фоне
|
||
|
||
#### Scenario: Вкладки панели
|
||
- **WHEN** открыта правая панель фото
|
||
- **THEN** видны вкладки «Сведения» и «Редактирование»
|
||
|
||
#### Scenario: Сохранение правок
|
||
- **WHEN** пользователь отпускает ползунок
|
||
- **THEN** правки сохраняются, а после рендера фото в сетке и просмотре показывается отредактированная версия
|
||
|
||
#### Scenario: Правки видны сразу
|
||
- **WHEN** пользователь меняет ползунок, а сервер ещё не отрендерил фото
|
||
- **THEN** видимая область фото в просмотре показывает правки без ожидания рендера
|
||
|
||
#### Scenario: Фильтры считаются в браузере
|
||
- **WHEN** браузер рисует предпросмотр правок
|
||
- **THEN** фильтры применяются WASM-модулем, а при его недоступности — JavaScript-реализацией с тем же результатом
|
||
|
||
#### Scenario: Сброс правок
|
||
- **WHEN** пользователь нажимает «Сброс» на вкладке «Редактирование»
|
||
- **THEN** параметры правок и отредактированная версия фото удаляются, а фото возвращается к исходному виду
|
||
|
||
#### Scenario: Зум при правках
|
||
- **WHEN** фото отредактировано, и пользователь увеличивает его
|
||
- **THEN** браузер рисует видимую область с правками, пока сервер не отрендерил фото, а зум не сбрасывается при обновлении фото
|