Files
mrleo1nid a6c9fd8a00
ci / api / api (push) Successful in 50s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m0s
ci / db / db (push) Successful in 58s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m27s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m39s
ci / ts / ts (packages + web) (push) Successful in 2m40s
fix(ui): reflect update availability and config changes; expand mobile player metadata
2026-10-23 00:20:59 +03:00

121 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Веб-клиент (`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`), поэтому в сборку попадают только используемые — всё встроено в сборку |
| Компоненты-примитивы | Свои `Menu`, `Dialog`, поля и сегменты в `shared/ui` |
| Анимация | Canvas 2D для кнопки волны |
| Формы | Контролируемые поля; проверка на сервере, ошибки приходят по полям |
| 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, download, home, search, artist, album,
│ # collection/{playlists,history,likes,dislikes}, playlist, track, 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`.
## Ключевые компоненты
### App shell
Каркас **Classic** ([design.md](design.md#4-каркас-и-экраны)).
- Сетка: топбар 60 px (логотип, поиск, меню профиля) / сайдбар 240 px (Главная, Коллекция → Плейлисты / История / Лайки / Дизлайки,
Админка по роли) / контент / плеер. Личные плейлисты — [playlists.md](playlists.md).
- Плеер в двух видах, выбор в профиле: `bar` — полоса 88 px внизу (по умолчанию), `island` — плавающий остров.
- Ambient-фон за всем интерфейсом: три размытых дрейфующих пятна, одно — в цвете обложки текущего трека.
- Адаптивность: < 1024 px — сайдбар из иконок, < 768 px — нижняя навигация, как в мобильном.
### Поиск
- `SearchBox` — 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 — лайк.
- «Поделиться» копирует адрес страницы трека (`/track/{id}`) без токенов стрима; на телефоне в браузере открывается системный лист.
- Логика очереди, событий, сохранения позиции — из `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): группы по пачкам, прослушивание из staging, правка метаданных
метаданных (поле на весь альбом), live-превью итогового пути, бейджи «возможный дубль» / «лучшее качество»,
массовые одобрение / отклонение с причиной. Бейдж с числом ожидающих — в сайдбаре.
- **Настройки** (admin): лимит очереди (ГБ), лимит на модератора, макс. размер файла, хранилище по умолчанию.
## Производительность
- Длинные списки (история, лайки, треки альбома) — виртуализация (TanStack Virtual).
- Код-сплиттинг по страницам; админка — отдельный чанк, грузится только для ролей admin / moderator.
- Обложки — `srcset` 64 / 300 / 1200, `loading="lazy"`.
- Бюджет: первый JS-чанк < 200 КБ gzip, LCP < 1.5 с в локальной сети.
## Доступность
- Вся навигация с клавиатуры, видимый фокус, ARIA-роли у плеера и поиска, контраст AA.
## Доступность
Цель — WCAG 2.1 AA. Проверено axe-core на всех экранах в тёмной и светлой темах (с играющим треком и открытой
очередью) — нарушений нет. Правила, которые держим:
- текст на заливке акцента — только `--accent-fill` (белый текст ≥ 4.5:1), вторичный текст `--text3` — ≥ 4.5:1
на фоне и поверхностях;
- у каждого экрана есть `h1`; у кнопок-иконок — `aria-label`, у переключателей — `aria-pressed`;
- списки треков — `role="list"` / `listitem` (строка фокусируется, Enter запускает трек, лайк и ссылки — отдельные
элементы); прогресс-бары подписаны через `aria-labelledby`;
- выезжающие панели (очередь) забирают фокус при открытии, закрываются по Escape и возвращают фокус на кнопку;
- `:focus-visible` — обводка акцентом; `prefers-reduced-motion` — анимации и Ambient выключены, волна замедлена.
На экранах уже 768 px веб-плеер использует два ряда: обложка, название и исполнители
занимают верхний ряд целиком, управление и дополнительные действия — нижний.
Высота строки плеера в каркасе адаптируется к содержимому для вариантов bar и island.