From f7abca90cb37403e2fd81f4146d8dc8f3eea9200 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:49:36 +0200 Subject: [PATCH] LE POULET --- package.json | 6 +- public/index.html | 35 ++++---- public/script/downloadstatus.js | 32 +++++++- public/script/fetchinfo.js | 127 +++++++++++++++-------------- public/styles/layout/container.css | 13 +++ public/styles/layout/ctr-infos.css | 12 +++ public/styles/layout/form.css | 44 ++++++++++ public/styles/styles.css | 48 ++++++++++- 8 files changed, 234 insertions(+), 83 deletions(-) create mode 100644 public/styles/layout/form.css diff --git a/package.json b/package.json index f6fc735..78c419a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,9 @@ { "dependencies": { "express": "^5.1.0" - } + }, + "scripts": { + "start": "node server/server.js", + "dev": "nodemon server/server.js" +} } diff --git a/public/index.html b/public/index.html index 5d846f2..38e370a 100644 --- a/public/index.html +++ b/public/index.html @@ -9,26 +9,29 @@ +
-
- - MP3 seulement - - -
+
+ + MP3 seulement + + +
-
+
- + -
+
+ +
diff --git a/public/script/downloadstatus.js b/public/script/downloadstatus.js index 5392ce7..9376789 100644 --- a/public/script/downloadstatus.js +++ b/public/script/downloadstatus.js @@ -1,29 +1,55 @@ +// Récupération du formulaire de téléchargement const form = document.getElementById("downloadForm"); + +// Div où on affiche l'état de progression (en cours, erreur, succès) const statusDiv = document.getElementById("downloadStatus"); -form.addEventListener("submit", async (e) => { - e.preventDefault(); // empêche la soumission classique (rechargement) +// Récupération du bouton de validation pour pouvoir le désactiver pendant la requête +const button = form.querySelector("button"); +// On intercepte la soumission du formulaire +form.addEventListener("submit", async (e) => { + e.preventDefault(); // empêche le rechargement de la page par le navigateur + + // On désactive le bouton pour éviter les clics multiples + button.disabled = true; + + // Affiche un message d'attente à l'utilisateur statusDiv.textContent = "⏳ Téléchargement en cours..."; + // Prépare les données du formulaire sous forme URL-encoded const formData = new FormData(form); const params = new URLSearchParams(formData); try { + // Envoi de la requête POST vers /download avec les données du formulaire const res = await fetch("/download", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params, }); + // Si la réponse n'est pas un succès (code 4xx ou 5xx) if (!res.ok) { statusDiv.textContent = "❌ Erreur pendant le téléchargement."; return; } + // On récupère le texte de la réponse du serveur const text = await res.text(); - statusDiv.textContent = text; // affiche « Téléchargement terminé » ou autre message serveur + + // Affiche le message de fin de téléchargement (par exemple « Téléchargement terminé ») + statusDiv.textContent = text; } catch { + // Gestion d'erreur en cas de problème réseau ou autre statusDiv.textContent = "❌ Une erreur s’est produite."; + } finally { + // Réactive le bouton pour autoriser d'autres soumissions + button.disabled = false; + + // Efface le message de status après 5 secondes pour garder l'UI propre + setTimeout(() => { + statusDiv.textContent = ""; + }, 5000); } }); diff --git a/public/script/fetchinfo.js b/public/script/fetchinfo.js index 51c181b..89d81bf 100644 --- a/public/script/fetchinfo.js +++ b/public/script/fetchinfo.js @@ -1,73 +1,78 @@ -const urlInput = document.getElementById("UrlInput"); // Récupère le champ input où l'utilisateur entre l'URL -const infoDiv = document.getElementById("videoInfo"); // Récupère la div où on affichera les infos +document.addEventListener("DOMContentLoaded", () => { + const urlInput = document.getElementById("UrlInput"); + const infoDiv = document.getElementById("videoInfo"); -let lastFetchedUrl = ""; // Pour éviter de refaire plusieurs requêtes pour la même URL + let lastFetchedUrl = ""; -// On écoute chaque changement dans le champ URL -urlInput.addEventListener("input", async () => { - const url = urlInput.value; // On prend la valeur actuelle du champ + urlInput.addEventListener("input", async () => { + const url = urlInput.value.trim(); + console.log("Input détecté :", url); - // Si pas d'URL, ou URL trop courte, ou déjà traitée, on sort - if (!url || url.length < 5 || url === lastFetchedUrl) return; - - lastFetchedUrl = url; // On mémorise l'URL traitée - - try { - // Envoi d'une requête POST vers le backend /info avec l'URL dans le corps - const res = await fetch("/info", { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ url }) // Encode l'URL au format x-www-form-urlencoded - }); - - // Si la réponse n'est pas OK (erreur 4xx ou 5xx) - if (!res.ok) { - infoDiv.innerHTML = "❌ Impossible de récupérer les infos."; // Message d'erreur utilisateur + // Si URL vide ou trop courte : on vide et cache la div + if (!url || url.length < 5) { + infoDiv.innerHTML = ""; + infoDiv.classList.remove("visible"); + lastFetchedUrl = ""; return; } + // Si même URL que précédemment, on ne fait rien + if (url === lastFetchedUrl) return; - // On récupère la réponse JSON (les infos de la vidéo) - const data = await res.json(); + lastFetchedUrl = url; - // Formatage de la durée en minutes et secondes (ex: 4m 35s) - const minutes = Math.floor(data.duration / 60); - const seconds = data.duration % 60; - const durationStr = `${minutes}m ${seconds.toString().padStart(2, '0')}s`; + try { + const res = await fetch("/info", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ url }), + }); - // Formatage de la date d'upload depuis YYYYMMDD vers JJ/MM/AAAA - const date = data.upload_date || ""; - const readableDate = date - ? `${date.slice(6, 8)}/${date.slice(4, 6)}/${date.slice(0, 4)}` - : "Inconnue"; + if (!res.ok) { + infoDiv.innerHTML = "❌ Impossible de récupérer les infos."; + infoDiv.classList.remove("visible"); + return; + } - // Taille estimée en Mo, ou "Inconnue" si pas dispo - const sizeMB = data.filesize_approx - ? (data.filesize_approx / (1024 * 1024)).toFixed(2) + " Mo" - : "Inconnue"; + const data = await res.json(); - // Catégories en chaîne séparée par des virgules, ou "Non spécifiées" - const categories = data.categories - ? data.categories.join(", ") - : "Non spécifiées"; + const minutes = Math.floor(data.duration / 60); + const seconds = data.duration % 60; + const durationStr = `${minutes}m ${seconds.toString().padStart(2, "0")}s`; - // Mise à jour du contenu HTML de la div avec toutes les infos formatées - infoDiv.innerHTML = ` -

${data.title}

- - - `; - } catch (e) { - // Si une erreur survient lors de la récupération ou du parsing - infoDiv.innerHTML = "❌ Erreur lors de la récupération."; - } + const date = data.upload_date || ""; + const readableDate = date + ? `${date.slice(6, 8)}/${date.slice(4, 6)}/${date.slice(0, 4)}` + : "Inconnue"; + + const sizeMB = data.filesize_approx + ? (data.filesize_approx / (1024 * 1024)).toFixed(2) + " Mo" + : "Inconnue"; + + const categories = data.categories + ? data.categories.join(", ") + : "Non spécifiées"; + + infoDiv.innerHTML = ` +

${data.title}

+ Thumbnail + + `; + // Affiche la div avec la classe pour transition douce + infoDiv.classList.add("visible"); + } catch (e) { + console.error("Erreur lors de la récupération :", e); + infoDiv.innerHTML = "❌ Erreur lors de la récupération."; + infoDiv.classList.remove("visible"); + } + }); }); diff --git a/public/styles/layout/container.css b/public/styles/layout/container.css index e69de29..15de33e 100644 --- a/public/styles/layout/container.css +++ b/public/styles/layout/container.css @@ -0,0 +1,13 @@ +.container { + position: fixed; + top: 0; + left: 0; + right: 0; + background-color: #121212; + padding: 1em; + box-shadow: 0 2px 5px rgba(0,0,0,0.5); + z-index: 1000; + display: flex; + flex-direction: column; + align-items: center; +} \ No newline at end of file diff --git a/public/styles/layout/ctr-infos.css b/public/styles/layout/ctr-infos.css index e69de29..ed71cf6 100644 --- a/public/styles/layout/ctr-infos.css +++ b/public/styles/layout/ctr-infos.css @@ -0,0 +1,12 @@ +#downloadStatus { + min-height: 1.5em; /* pour garder la place même vide */ + font-weight: bold; + text-align: center; + color: #007bff; +} + +label { + display: flex; + align-items: center; + font-size: 0.9em; +} \ No newline at end of file diff --git a/public/styles/layout/form.css b/public/styles/layout/form.css new file mode 100644 index 0000000..a85d857 --- /dev/null +++ b/public/styles/layout/form.css @@ -0,0 +1,44 @@ +form { + background-color: #fff; + padding: 2em; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0,0,0,0.1); + display: flex; + flex-direction: column; + width: 100%; + gap: 1em; +} + +form input[type="text"], +form input[type="url"], +form select { + padding: 0.75em; + border: 1px solid #ccc; + border-radius: 8px; +} + +form input[type="checkbox"] { + margin-right: 0.5em; +} + +form button { + background-color: #007bff; + color: #fff; + border: none; + padding: 0.75em; + border-radius: 8px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +form button:hover { + background-color: #0056b3; +} + +form#downloadForm { + display: flex; + gap: 0.5em; + max-width: 600px; + width: 100%; + justify-content: center; +} \ No newline at end of file diff --git a/public/styles/styles.css b/public/styles/styles.css index 4890f27..05ade30 100644 --- a/public/styles/styles.css +++ b/public/styles/styles.css @@ -1,3 +1,7 @@ +@import url("layout/form.css"); +@import url("layout/container.css"); +@import url("layout/ctr-infos.css"); + *{ margin: 0; padding: 0; @@ -5,6 +9,46 @@ } -body{ - background-color:rgb(83, 83, 83) ; +body { + margin: 0; + font-family: Arial, sans-serif; + background-color: #f5f5f5; + color: #333; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +#downloadStatus { + margin-top: 0.5em; + font-weight: bold; + color: #eee; + min-height: 1.5em; +} + +#videoInfo { + margin-top: 100px; + padding: 1em; + max-width: 600px; + margin-left: auto; + margin-right: auto; + border-radius: 8px; + color: #fff; + box-shadow: 0 0 10px rgba(0,0,0,0.5); + transition: opacity 0.5s ease, max-height 0.5s ease; + opacity: 0; + max-height: 0; + overflow: hidden; +} + +#videoInfo.visible { + opacity: 1; + max-height: 1000px; +} + +label { + display: flex; + align-items: center; + font-size: 0.9em; } \ No newline at end of file