Files
pipu-music/docs/web.md
T
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

12 KiB
Raw Permalink Blame History

Веб-клиент (apps/web)

Стек

Задача Выбор
Сборка Vite, TypeScript (strict)
UI React 19
Роутинг TanStack Router (типобезопасные пути и search-параметры)
Серверное состояние TanStack Query (кэш, инвалидация, бесконечные списки)
Клиентское состояние Zustand (плеер, очередь, UI)
Стили CSS Modules + CSS-переменные из packages/design-tokens; тёмная и светлая темы (design.md)
Шрифты и иконки @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).

  • Сетка: топбар 60 px (логотип, поиск, меню профиля) / сайдбар 240 px (Главная, Коллекция → Плейлисты / История / Лайки / Дизлайки, Админка по роли) / контент / плеер. Личные плейлисты — 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.
  • В покое — медленное «дыхание». При воспроизведении — пульс с частотой 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.