ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 50s
ci / ts / ts (packages + web) (push) Successful in 43s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 27s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m22s
- core: AuthSession keeps the access token in memory, refreshes it once for all parallel requests (single-flight) and retries them; refresh races between tabs are retried, reuse signs out; ApiError carries problem+json codes and field errors - web: TanStack Router with guards — guests go to /login, the bootstrap admin to /setup/admin, temporary passwords to /setup/password, admin pages by role - login, setup screens, Classic shell (top bar, role-based sidebar, empty player), settings: name, theme, language, password, devices - users admin: search, role filter, create with a one-time temporary password, reset, block, role change, delete as the policy allows; audit log for admins - ru/en strings for auth, admin and error codes; language and theme are saved in the profile Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
10 KiB
Markdown
104 lines
10 KiB
Markdown
# Веб-клиент (`apps/web`)
|
||
|
||
## Стек
|
||
|
||
| Задача | Выбор |
|
||
|---|---|
|
||
| Сборка | Vite, TypeScript (strict) |
|
||
| UI | React 19 |
|
||
| Роутинг | TanStack Router (типобезопасные пути и search-параметры) |
|
||
| Серверное состояние | TanStack Query (кэш, инвалидация, бесконечные списки) |
|
||
| Клиентское состояние | Zustand (плеер, очередь, UI) |
|
||
| Стили | CSS Modules + CSS-переменные из `packages/design-tokens`; тёмная и светлая темы ([design.md](design.md#2-токены)) |
|
||
| Шрифты и иконки | `@fontsource` Onest / Unbounded / JetBrains Mono; иконки — SVG Material Symbols Rounded (`@material-symbols/svg-400`), импортируются поштучно в `shared/ui/icons.ts` и рисуются CSS-маской (`currentColor`), поэтому в сборку попадают только используемые — всё встроено в сборку |
|
||
| Компоненты-примитивы | Radix UI (доступные меню, диалоги, слайдеры, popover поиска); в M1 — собственные `Menu` / `Dialog` в `shared/ui`, переход на Radix вместе с поиском (M2) |
|
||
| Анимация | Motion (переходы UI), Canvas 2D для кнопки волны (порт `wave.js` из прототипа) |
|
||
| Формы | React Hook Form + Zod (в M1 формы простые — контролируемые поля, валидация на сервере с ошибками полей) |
|
||
| i18n | i18next + react-i18next, словари из `packages/i18n` |
|
||
| API | `packages/api-client` (сгенерирован из OpenAPI, `openapi-typescript` + `openapi-fetch`) |
|
||
|
||
## Структура (feature-sliced)
|
||
|
||
```
|
||
apps/web/src/
|
||
├── app/ # провайдеры, роутер, глобальные стили, гарды
|
||
├── pages/ # login, setup-admin, setup-password, home, search, artist, album,
|
||
│ # collection/{history,likes,dislikes}, settings,
|
||
│ # admin/{users,sources,library,upload,upload-queue,settings}
|
||
├── widgets/ # app-shell (топбар, сайдбар), player-bar, wave-button, search-box, track-list
|
||
├── features/ # like-track, change-password, mood-picker, create-user, pattern-editor, ...
|
||
├── entities/ # track, album, artist, user — модели и маленькие UI-карточки
|
||
└── shared/ # ui-kit, lib, hooks, config
|
||
```
|
||
|
||
Правило: слой может импортировать только из нижележащих (`pages → widgets → features → entities → shared`),
|
||
проверяется `eslint-plugin-boundaries`.
|
||
|
||
## Ключевые компоненты
|
||
|
||
### App shell
|
||
Макет — `Pipu Web.dc.html`, каркас **Classic** ([design.md](design.md#4-каркас-и-экраны)).
|
||
- Сетка: топбар 60 px (логотип, поиск, меню профиля) / сайдбар 240 px (Главная, Коллекция → История / Лайки / Дизлайки,
|
||
Админка по роли) / контент / плеер.
|
||
- Плеер в двух видах, выбор в профиле: `bar` — полоса 88 px внизу (по умолчанию), `island` — плавающий остров.
|
||
- Ambient-фон за всем интерфейсом: три размытых дрейфующих пятна, одно — в цвете обложки текущего трека.
|
||
- Адаптивность (макетов нет): < 1024 px — сайдбар из иконок, < 768 px — нижняя навигация, как в мобильном.
|
||
|
||
### Поиск
|
||
- `SearchBox` — Radix Popover + combobox по WAI-ARIA (стрелки, Enter, Esc).
|
||
- Debounce 150 мс, отмена предыдущего запроса, `GET /search/suggest`.
|
||
- Enter без выбранного элемента → `/search?q=…`; Enter / клик по элементу → исполнитель / альбом / запуск трека.
|
||
- Горячая клавиша `/` или `Ctrl+K` — фокус в поиск.
|
||
|
||
### Плеер
|
||
- Движок: `HTMLAudioElement` → Web Audio граф (`MediaElementSource → Gain (нормализация) → Analyser → destination`).
|
||
`Analyser` даёт спектр для анимации кнопки волны (тот же origin — CORS не мешает).
|
||
- Два элемента `<audio>` для предзагрузки следующего трека и бесшовного перехода (опционально — кроссфейд 0–6 с в настройках).
|
||
- Media Session API: метаданные, обложка, кнопки ОС / наушников.
|
||
- Горячие клавиши: пробел — пауза, ←/→ — перемотка 10 с, Shift+←/→ — трек, L — лайк.
|
||
- Логика очереди, событий, сохранения позиции — из `packages/core` (общая с мобильным); веб реализует
|
||
только адаптер `AudioEngine`.
|
||
|
||
### Кнопка «Моя волна»
|
||
- Большой круг в центре главной (раскладка **Center**). Два вида на выбор пользователя: **сфера с шумом** (`orb`,
|
||
по умолчанию) и **жидкая форма** (`liquid`). Реализация — порт `wave.js` на TS, Canvas 2D; API и поведение —
|
||
[design.md](design.md#3-кнопка-моя-волна).
|
||
- В покое — медленное «дыхание». При воспроизведении — пульс с частотой BPM трека и силой от energy,
|
||
дополнительно амплитуда от RMS спектра (`AnalyserNode`); цвета — палитра обложки текущего трека.
|
||
- Hover — волны ускоряются и «тянутся» к курсору; нажатие — сжатие / отскок (spring).
|
||
- Та же кнопка, увеличенная и неинтерактивная, — декоративный фон экрана входа.
|
||
- Под кнопкой — `MoodPicker`: чипы занятий и настроений (одно занятие + одно настроение, повторный клик снимает выбор).
|
||
Смена во время игры волны — без остановки музыки.
|
||
- `prefers-reduced-motion` — замедленная версия без пульса, ambient-фон статичен.
|
||
|
||
### Профиль и настройки
|
||
Пароль, язык, тема (тёмная / светлая / системная), вид волны, вид плеера, ambient-фон, кроссфейд (0–6 с),
|
||
выравнивание громкости (EBU R128), выход. Настройки внешнего вида сохраняются в `ui_prefs` (`PATCH /me`).
|
||
|
||
### Админка
|
||
- Пользователи: таблица с поиском и фильтром по ролям, создание (диалог: логин, имя, роль; пароль генерируется сервером
|
||
и показывается один раз), сброс пароля (тот же диалог «Временный пароль» с кнопкой «копировать»), блокировка, смена роли (только админу видно).
|
||
- Хранилища: список с статусом, форма NFS / local, кнопка «Проверить»; редактор шаблонов — список регэкспов
|
||
с drag-and-drop порядка, подсветкой синтаксиса и ошибок компиляции, таблица предпросмотра с подсветкой групп.
|
||
- Библиотека: счётчики, прогресс скана и анализа (live), список ошибок.
|
||
- Хранилища → «Шаблон раскладки»: поле `write_template` с кнопками «Подобрать автоматически» и «Проверить»
|
||
(таблица расхождений реальный путь ↔ сгенерированный).
|
||
- **Предложить треки** (moderator, admin): зона drag-and-drop для файлов и папок, индикатор очереди «X из Y ГБ».
|
||
Перед загрузкой — Web Worker считает SHA-256 и читает теги (`music-metadata`), `POST /uploads/precheck` →
|
||
дубли сразу показываются как «уже есть: <трек>» и не загружаются. Загрузка — `tus-js-client`, до 4 параллельно,
|
||
пауза / возобновление. Ниже — «Мои предложения» со статусами и причинами отклонения.
|
||
- **Очередь на добавление** (admin): группы по альбомам (вариант `grouped` из прототипа), мини-плеер для прослушивания, inline-редактирование
|
||
метаданных (поле на весь альбом), live-превью итогового пути, бейджи «возможный дубль» / «лучшее качество»,
|
||
массовые одобрение / отклонение с причиной. Бейдж с числом ожидающих — в сайдбаре.
|
||
- **Настройки** (admin): лимит очереди (ГБ), лимит на модератора, макс. размер файла, хранилище по умолчанию.
|
||
|
||
## Производительность
|
||
|
||
- Длинные списки (история, лайки, треки альбома) — виртуализация (TanStack Virtual).
|
||
- Код-сплиттинг по страницам; админка — отдельный чанк, грузится только для ролей admin / moderator.
|
||
- Обложки — `srcset` 64 / 300 / 1200, `loading="lazy"`.
|
||
- Бюджет: первый JS-чанк < 200 КБ gzip, LCP < 1.5 с в локальной сети.
|
||
|
||
## Доступность
|
||
- Вся навигация с клавиатуры, видимый фокус, ARIA-роли у плеера и поиска, контраст AA.
|