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