docs: spec de vista grid estilo YouTube para la seccion Videos
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
# Diseño: vista de grid estilo YouTube para la sección Vídeos
|
||||
|
||||
Fecha: 2026-08-22
|
||||
Estado: aprobado en conversación
|
||||
|
||||
## Problema
|
||||
|
||||
La sección Vídeos solo tiene visualización de tabla. Para navegar el catálogo
|
||||
visualmente (miniaturas grandes, explorar por canal/tema) falta una vista tipo
|
||||
YouTube: grid de tarjetas con miniatura 16:9, duración y metadatos compactos.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Añadir un conmutador tabla ⇄ grid en la sección Vídeos. El grid replica el
|
||||
look de YouTube (tarjetas 16:9) manteniendo toda la funcionalidad existente:
|
||||
filtros, orden, selección masiva ("Download .md" / "Audio") y paginación.
|
||||
|
||||
## Decisiones tomadas en conversación
|
||||
|
||||
| Pregunta | Decisión |
|
||||
|---|---|
|
||||
| ¿Selección masiva en grid? | Sí, checkboxes como en la tabla; la barra masiva se comparte |
|
||||
| ¿"Square" literal o estilo YouTube? | Miniatura 16:9 estilo YouTube (no recorte 1:1) |
|
||||
| ¿Vista inicial y persistencia? | Tabla por defecto; la elección se recuerda en `localStorage` |
|
||||
| ¿Estructura en index.html? | Dos bloques hermanos alternados con `x-if`; filtros/barra/paginación compartidos |
|
||||
|
||||
## Cambios
|
||||
|
||||
### 1. Estado y persistencia (`static/app.js`)
|
||||
|
||||
- Nuevo campo en el store de `videos`: `view`, valores `'table' | 'grid'`,
|
||||
default `'table'`.
|
||||
- En `init()`: leer `localStorage["videos-view"]` y aceptarlo solo si es
|
||||
`'table'` o `'grid'` (mismo patrón defensivo que `videos-size`).
|
||||
- Método `setVideoView(v)`: asigna y guarda en `localStorage`. No toca
|
||||
`syncURL`/`hydrateURL`: es preferencia de UI como `videos-size`, no estado
|
||||
navegable.
|
||||
|
||||
### 2. Conmutador de vista (`index.html`)
|
||||
|
||||
- Control segmentado de dos botones icono (lista / grid) alineado a la
|
||||
derecha, en una fila fina inmediatamente encima del contenedor de
|
||||
resultados.
|
||||
- Una sola instancia visible en ambas vistas: fuera del `<thead>` de la
|
||||
tabla a propósito, porque el grid no tiene cabecera donde colgarlo.
|
||||
- Botón activo con `accent-grad btn-primary`, el mismo tratamiento que usa el
|
||||
número de página activo en la paginación; aria-pressed en cada botón.
|
||||
|
||||
### 3. Tarjetas del grid (`index.html`)
|
||||
|
||||
- Contenedor: `grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-5 gap-4`.
|
||||
- Tarjeta: bloque `glass` redondeado, cursor-pointer, hover con borde rose
|
||||
(mismo lenguaje que las tarjetas de canales). Click → `openVideo(id)`;
|
||||
clase `row-selected` si está seleccionada.
|
||||
- Miniatura: `/api/thumbnails/{id}`, `aspect-video w-full object-cover`,
|
||||
esquinas superiores redondeadas, mismo `onerror="this.style.visibility='hidden'"`
|
||||
que la tabla.
|
||||
- Badge de duración abajo-derecha sobre la miniatura: `ts(v.duration)`,
|
||||
font-mono text-xs, fondo negro semitransparente.
|
||||
- Checkbox de selección arriba-izquierda sobre la miniatura: aparece al
|
||||
hover de la tarjeta o permanece visible si está seleccionada; click con
|
||||
`.stop` → `toggleSelect(id)`.
|
||||
- Cuerpo:
|
||||
- Título con clamp a 2 líneas (`line-clamp-2`), text-zinc-100.
|
||||
- Línea de metadatos: `chanName(channel_id)` · `fmtNum(view_count)` vistas ·
|
||||
`videoDate(v)` (con `~` si estimada; tooltip `videoDateTitle(v)`).
|
||||
- Pills de estado y candado de bloqueo idénticos a los de la tabla
|
||||
(`statusClass`, `isBlocked`, `blockLabel`, `blockTitle`).
|
||||
|
||||
### 4. Comportamiento compartido (sin cambios)
|
||||
|
||||
- Filtros, barra masiva, paginación y select de orden: intactos, aplican a
|
||||
ambas vistas.
|
||||
- Estados loading/vacío duplicados dentro del bloque del grid con los mismos
|
||||
mensajes ("loading…", "No videos match these filters.", "Clear filters").
|
||||
- Las acciones por vídeo `.md`/`Process`/`Clip` **no** se duplican en las
|
||||
tarjetas: quedan en la tabla y en la vista de detalle (look limpio tipo
|
||||
YouTube; un click abre el detalle que las tiene todas).
|
||||
|
||||
## Errores cubiertos
|
||||
|
||||
| Caso | Comportamiento |
|
||||
|---|---|
|
||||
| Miniatura ausente | `onerror` oculta el `<img>`; la tarjeta conserva proporción por el contenedor aspect-video |
|
||||
| Valor inválido/corrupto en localStorage | Se ignora y cae a `'table'` |
|
||||
| Selección activa + cambio de vista | `videos.selected` no se toca; la barra masiva sigue funcionando en ambas |
|
||||
|
||||
## Fuera de alcance
|
||||
|
||||
- No se toca backend ni endpoints: `/api/videos` ya devuelve todos los campos
|
||||
que la tarjeta necesita.
|
||||
- Sin acciones por tarjeta, sin hover-preview de vídeo, sin infinite scroll.
|
||||
- Sin tests automatizados nuevos: el repo no tiene infra de tests de frontend
|
||||
(pytest es backend sin red). Verificación manual.
|
||||
|
||||
## Verificación manual
|
||||
|
||||
Con `start-server.bat`:
|
||||
|
||||
1. Alternar tabla ⇄ grid: ambas renderizan los mismos vídeos del filtro activo.
|
||||
2. Marcar 3 tarjetas → barra masiva aparece → "Download .md" encola el job.
|
||||
3. Paginar en grid: sin repeticiones ni saltos (mismo desempate que tabla).
|
||||
4. Recargar la página: la vista elegida se conserva; primera visita → tabla.
|
||||
5. La tabla existente funciona exactamente igual que antes del cambio.
|
||||
Reference in New Issue
Block a user