Interface web complète : formulaire d'ajout (URL directe ou recherche de chaîne) et panneau de file d'attente avec sondage de la progression. - views/downloadForm/ : sélecteur de mode (vidéo/audio/miniature) partagé, ajout direct par URL, recherche de chaîne (GET /api/channel) avec ajout par résultat. Construction DOM via createElement/textContent (jamais d'innerHTML avec du texte venant de YouTube), pas juste par prudence : ça évite aussi tout risque d'injection depuis un titre de vidéo. - views/queuePanel/ : sonde GET /api/queue toutes les 1,5s, affiche statut/ progression/erreur par élément, annulation et lien de téléchargement natif (<a download>, pas de fetch+blob). - api/ : clients fetch typés (channelApi, queueApi), chaque await dans son propre try/catch avec message d'erreur adapté (réseau vs HTTP vs JSON). - QueueItem gagne un champ title optionnel (purement informatif, jamais utilisé pour construire la commande yt-dlp) pour un affichage plus parlant qu'une URL brute. - client/tsconfig.json + build.mjs : cible ES2022 (nécessaire pour Error.cause, jusque-là ES2020). - Vitest ajouté côté client (fonctions pures de src/format.ts ; le câblage DOM est vérifié manuellement, pas de jsdom pour l'instant). Deux bugs réels trouvés et corrigés en testant dans un vrai navigateur (pas seulement en lisant le code) : - CSS : un attribut `hidden` sur un conteneur `display: flex` se faisait écraser par cette règle (même spécificité, la feuille de l'auteur gagne) - les deux panneaux d'options restaient visibles en même temps. - server/src/routes/channel.ts : ne mappait vers 502 que les YtDlpError (code de sortie non nul), pas les erreurs brutes de spawn (ex. binaire introuvable - ENOENT), qui remontaient donc en 500 générique. Corrigé + test de non-régression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
82 lines
1.1 KiB
SCSS
82 lines
1.1 KiB
SCSS
.queue-panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
|
|
h2 {
|
|
margin: 0;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
&__empty {
|
|
color: #666;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
&__list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
&__item {
|
|
border: 1px solid #eee;
|
|
border-left-width: 4px;
|
|
border-radius: 4px;
|
|
padding: 0.5rem 0.75rem;
|
|
|
|
&--completed {
|
|
border-left-color: #2e7d32;
|
|
}
|
|
&--failed {
|
|
border-left-color: #b00020;
|
|
}
|
|
&--running {
|
|
border-left-color: #1565c0;
|
|
}
|
|
&--cancelled {
|
|
border-left-color: #999;
|
|
}
|
|
&--pending {
|
|
border-left-color: #999;
|
|
}
|
|
}
|
|
|
|
&__item-title {
|
|
margin: 0;
|
|
font-size: 0.9rem;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
&__item-meta {
|
|
margin: 0.15rem 0 0;
|
|
font-size: 0.75rem;
|
|
color: #666;
|
|
}
|
|
|
|
&__item-progress {
|
|
width: 100%;
|
|
margin-top: 0.35rem;
|
|
}
|
|
|
|
&__item-error {
|
|
margin: 0.35rem 0 0;
|
|
font-size: 0.75rem;
|
|
color: #b00020;
|
|
}
|
|
|
|
&__item-actions {
|
|
margin-top: 0.35rem;
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
&__item-download {
|
|
font-size: 0.85rem;
|
|
}
|
|
}
|