ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m1s
ci / db / db (push) Successful in 1m16s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m12s
ci / api / api (push) Successful in 45s
ci / recommender / recommender (push) Successful in 1m50s
ci / ts / ts (packages + web) (push) Failing after 6m53s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m13s
- Introduced a new warning for users uploading non-FLAC or WAV formats, indicating that these formats should only be used as a last resort. - Added functionality to highlight files needing format attention in the upload queue. - Updated the UI to provide clearer guidance on acceptable audio formats during uploads. - Improved documentation to reflect changes in upload requirements and user interface updates.
115 lines
11 KiB
Markdown
115 lines
11 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`), поэтому в сборку попадают только используемые — всё встроено в сборку |
|
||
| Компоненты-примитивы | Свои `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, 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`.
|
||
|
||
## Ключевые компоненты
|
||
|
||
### App shell
|
||
Каркас **Classic** ([design.md](design.md#4-каркас-и-экраны)).
|
||
- Сетка: топбар 60 px (логотип, поиск, меню профиля) / сайдбар 240 px (Главная, Коллекция → История / Лайки / Дизлайки,
|
||
Админка по роли) / контент / плеер.
|
||
- Плеер в двух видах, выбор в профиле: `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 — лайк.
|
||
- Логика очереди, событий, сохранения позиции — из `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 выключены, волна замедлена.
|