fix(client): page sans overflow, listes scrollables, miniatures agrandies
- Layout en hauteur pleine (html/body/.shell à 100%/100vh, overflow hidden sur body) : la page ne défile plus jamais. Seules download-form__results et queue-panel__list défilent en interne (flex:1 1 0 + min-height:0 + overflow-y:auto), propagé depuis .shell via min-height:0 à chaque niveau flex descendant. - .shell__main : ratio de colonnes 3fr/2fr -> 2fr/1fr et max-width 960px -> 1280px, pour une liste de vidéos plus large. - Miniatures de résultats agrandies (96x54 -> 144x81, ratio 16:9) et titre sur 2 lignes (line-clamp) plutôt qu'une troncature sur une seule ligne, pour mieux différencier les vidéos au premier coup d'œil. Vérifié en navigateur avec des données de test (16 éléments de file, 12 résultats factices injectés) : bodyScrollHeight reste égal à windowInnerHeight (page sans overflow) alors que les deux listes ont un scrollHeight très supérieur à leur clientHeight (défilement interne réel, pas juste visuel). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
915a2af0de
commit
d8dc263690
5 changed files with 62 additions and 15 deletions
|
|
@ -1,10 +1,11 @@
|
|||
.shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
|
||||
&__header {
|
||||
flex: 0 0 auto;
|
||||
padding: 1rem 1.5rem;
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
|
|
@ -17,13 +18,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
// min-height: 0 est nécessaire ici (et à chaque niveau descendant
|
||||
// jusqu'aux listes scrollables) : par défaut un enfant flex ne
|
||||
// rétrécit jamais sous la taille de son contenu, ce qui empêcherait
|
||||
// tout overflow interne et ferait déborder toute la page à la place.
|
||||
&__main {
|
||||
flex: 1;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: 1.25rem;
|
||||
padding: 1.5rem;
|
||||
max-width: 960px;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
|
@ -31,16 +38,20 @@
|
|||
|
||||
@media (min-width: 800px) {
|
||||
&__main {
|
||||
grid-template-columns: 3fr 2fr;
|
||||
align-items: start;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
&__panel {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 12px;
|
||||
padding: 1.25rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,16 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// La page elle-même ne défile jamais : seules les listes internes (file
|
||||
// d'attente, résultats de recherche) le font, voir Shell.scss et les
|
||||
// vues concernées (chacune doit propager height:100%/min-height:0
|
||||
// jusqu'à son propre élément scrollable pour que ça fonctionne).
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--color-bg);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,16 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.85rem;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
|
||||
// Par défaut, aucun enfant direct ne grandit/rétrécit (taille fixée à
|
||||
// son contenu) - seul __results (règle plus bas, même spécificité
|
||||
// mais déclarée après donc prioritaire) doit remplir l'espace restant
|
||||
// et défiler en interne.
|
||||
> * {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
|
|
@ -83,26 +93,28 @@
|
|||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
max-height: 65vh;
|
||||
gap: 0.6rem;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
gap: 0.85rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
padding: 0.5rem;
|
||||
padding: 0.6rem;
|
||||
}
|
||||
|
||||
&__result-thumb {
|
||||
width: 96px;
|
||||
height: 54px;
|
||||
width: 144px;
|
||||
height: 81px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
&__result-info {
|
||||
|
|
@ -112,10 +124,11 @@
|
|||
|
||||
&__result-title {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
font-size: 0.92rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__result-meta {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,15 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
|
||||
// Par défaut, aucun enfant direct ne grandit/rétrécit - seul __list
|
||||
// (règle plus bas, même spécificité mais déclarée après donc
|
||||
// prioritaire) doit remplir l'espace restant et défiler en interne.
|
||||
> * {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
|
|
@ -33,6 +42,9 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__item {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ Pas de `docker-compose.yml` pour l'instant (une seule image, pas encore de dépe
|
|||
- `src/views/downloadForm/` : formulaire d'ajout, deux entrées possibles (URL directe, ou recherche de chaîne via `GET /api/channel` puis ajout par résultat) partageant un même sélecteur de mode (vidéo/audio/miniature + options). Le mode courant est lu depuis le DOM à l'ajout, pas stocké en état séparé.
|
||||
- `src/views/queuePanel/` : sonde `GET /api/queue` toutes les 1,5s (pas de push temps réel pour l'instant, voir Statut) et affiche chaque élément (titre ou URL à défaut, statut, barre de progression, erreur). Un même bouton retire l'élément quel que soit son état - libellé "Annuler" s'il est en attente/en cours, "Supprimer" une fois terminé (`DELETE /api/queue/:id` gère les deux cas côté serveur). Lien "Télécharger" natif (`<a download>`) uniquement si un fichier est encore disponible ; bouton "Tout télécharger" dans l'en-tête qui déclenche, avec un léger décalage entre chacun (évite le blocage navigateur des téléchargements multiples simultanés), tous les téléchargements prêts. Expose `refresh()` pour un rafraîchissement immédiat après un ajout.
|
||||
- Piège CSS relevé en testant dans un vrai navigateur (pas seulement les tests) : un attribut `hidden` combiné à une règle `display: flex` sur le même sélecteur se fait écraser par cette règle (même spécificité, la feuille de l'auteur gagne sur celle du user-agent) - toujours ajouter `&[hidden] { display: none; }` quand un conteneur togglé porte aussi un `display` explicite.
|
||||
- Layout en hauteur pleine page (`html`/`body`/`.shell` à `height:100%`/`100vh`, `overflow:hidden` sur `body`) : la page elle-même ne défile jamais, seules `download-form__results` et `queue-panel__list` le font (chacune `flex:1 1 0; min-height:0; overflow-y:auto` dans un parent qui propage `height:100%`/`min-height:0` depuis `.shell__panel`). `min-height: 0` est nécessaire à *chaque* niveau flex descendant - un enfant flex ne rétrécit jamais sous la taille de son contenu par défaut, ce qui ferait déborder la page entière au lieu de faire défiler juste la liste. Chaque vue neutralise le grow/shrink de ses enfants directs non-scrollables via `> * { flex: 0 0 auto; }`, puis la règle plus spécifique de l'élément scrollable (même spécificité, déclarée après donc prioritaire) le fait remplir l'espace restant.
|
||||
- Tests : Vitest (`client/test/`), uniquement sur les fonctions pures (`format.ts`) pour l'instant - pas de configuration jsdom, le câblage DOM (formulaires, rendu de liste) est vérifié manuellement dans le navigateur plutôt que testé automatiquement.
|
||||
- `src/styles/base.scss` : palette et styles de base partagés (custom properties CSS sur `:root`, pas de `$variables` Sass - une variable Sass ne traverserait pas les fichiers, chaque vue étant compilée/importée séparément ; une custom property CSS si, puisqu'elle se résout à l'exécution dans le navigateur une fois tout le CSS regroupé par esbuild). Importé une fois depuis `app.ts`.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue