Перенос дизайна в server-rendered веб-UI (web-ui)
Новая capability web-ui: презентационный перенос готового дизайна
(семантический HTML + единый jellybit.css, тёмная тема по настройке ОС)
в html/template без шага сборки.
- Встроенная (go:embed) отдача статики под /static с Cache-Control и
cache-busting (?v=<hash> по содержимому css/js).
- Шрифты IBM Plex self-hosted (@font-face, cyrillic+latin), без CDN.
- Наколеночный менеджер зависимостей: вендор (htmx + шрифты) не хранится
в репо (gitignore), идемпотентно добывается `task assets` по
web/assets.manifest с проверкой sha256; task build/run зависят от assets.
- Общие партиалы: шапка, бейдж статуса (карта всех 14 состояний),
виджет «файл источника → раскладка» (общий для review и download).
- Страницы: список с фильтром/поиском, ревью, новая страница просмотра
загрузки (/download/{id}). deleted скрыт по умолчанию.
- Превью раскладки берётся из единой логики internal/layout
(buildFileRows), без дублирования правил имён в шаблонах.
- Убраны meta-refresh и инлайн-стили; copyHash на vanilla с fallback
на execCommand и честной индикацией (целевой деплой — HTTP LAN).
Вне scope (отдельный change): живые обновления прогресса и раздел
раздачи, клиентский режим ручной раскладки файл→серия (с Alpine.js).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"git.vakhrushev.me/av/jellybit/internal/layout"
|
||||
"git.vakhrushev.me/av/jellybit/internal/recognize"
|
||||
)
|
||||
|
||||
// fileRow — строка виджета «файл источника → раскладка», общего для экранов
|
||||
// ревью и просмотра загрузки. Целевой путь берётся из превью (единая логика
|
||||
// internal/layout), а не вычисляется в шаблоне.
|
||||
type fileRow struct {
|
||||
Src string
|
||||
Dst string // целевой путь; пусто — файл не раскладывается
|
||||
RoleLabel string
|
||||
Linked bool
|
||||
Ignored bool
|
||||
}
|
||||
|
||||
// buildFileRows сшивает файлы плана с целевыми путями из превью раскладки.
|
||||
func buildFileRows(plan recognize.Plan, preview []layout.Link) []fileRow {
|
||||
dstBySrc := make(map[string]string, len(preview))
|
||||
for _, l := range preview {
|
||||
dstBySrc[l.Src] = l.Dst
|
||||
}
|
||||
rows := make([]fileRow, 0, len(plan.Files))
|
||||
for _, f := range plan.Files {
|
||||
dst := dstBySrc[f.Src]
|
||||
rows = append(rows, fileRow{
|
||||
Src: f.Src,
|
||||
Dst: dst,
|
||||
RoleLabel: roleLabel(string(f.Role)),
|
||||
Linked: dst != "",
|
||||
Ignored: f.Role == "ignore",
|
||||
})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// roleLabel — человекочитаемая роль файла раскладки.
|
||||
func roleLabel(role string) string {
|
||||
switch role {
|
||||
case "episode":
|
||||
return "эпизод"
|
||||
case "main", "movie":
|
||||
return "фильм"
|
||||
case "subtitle":
|
||||
return "субтитры"
|
||||
case "extra":
|
||||
return "допматериал"
|
||||
case "sample":
|
||||
return "семпл"
|
||||
case "ignore":
|
||||
return "игнор"
|
||||
default:
|
||||
return role
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user