import { createApiClient, createStatusManager, escapeHtml, getStoredApiKey, getStoredDisplayName, formatBytes, formatDate, setStoredApiKey, setStoredDisplayName, } from "./common.js"; const state = { apiKey: getStoredApiKey(), displayName: getStoredDisplayName(), queue: [], current: null, library: [], status: "idle", }; const elements = { status: document.getElementById("status"), apiKeyInput: document.getElementById("apiKey"), displayNameInput: document.getElementById("displayName"), settingsForm: document.getElementById("settingsForm"), uploadForm: document.getElementById("uploadForm"), fileInput: document.getElementById("musicFile"), notesInput: document.getElementById("uploadNotes"), permanentCheckbox: document.getElementById("permanentFile"), enqueueCheckbox: document.getElementById("enqueueFile"), currentTrack: document.getElementById("currentTrack"), queueList: document.getElementById("queueList"), queueEmpty: document.getElementById("queueEmpty"), libraryList: document.getElementById("libraryList"), libraryEmpty: document.getElementById("libraryEmpty"), refreshBtn: document.getElementById("refreshBtn"), pauseResumeBtn: document.getElementById("pauseResumeBtn"), playerButtons: document.querySelectorAll("[data-player-action]"), heroUploadBtn: document.getElementById("heroUploadBtn"), heroQueueBtn: document.getElementById("heroQueueBtn"), uploadSection: document.getElementById("uploadSection"), queueSection: document.getElementById("queueSection"), }; let pollIntervalId = null; const { setStatus, clearStatus } = createStatusManager(elements.status); const apiFetch = createApiClient({ basePath: "/api", getApiKey: () => state.apiKey, }); async function refreshAll({ silent = false } = {}) { if (!silent) { clearStatus(); } try { const [queueData, libraryData] = await Promise.all([ apiFetch("/queue"), apiFetch("/library"), ]); state.current = queueData.current; state.queue = queueData.upcoming || []; state.library = libraryData.tracks || []; state.status = queueData.status || "idle"; renderCurrentTrack(); renderQueue(); renderLibrary(); updatePauseResumeButton(); } catch (error) { setStatus(`Erreur: ${error.message}`, "error", false); } } function renderCurrentTrack() { if (!elements.currentTrack) return; if (!state.current) { elements.currentTrack.classList.add("player-current--empty"); elements.currentTrack.innerHTML = "En attente de lecture..."; return; } elements.currentTrack.classList.remove("player-current--empty"); const track = state.current; const permanence = track.permanent ? "Permanent" : "Temporaire"; elements.currentTrack.innerHTML = `
${escapeHtml(track.title || track.originalName)}
${permanence} • ${formatBytes(track.size)} • Ajouté par ${escapeHtml( track.uploader || "inconnu" )}
Commencé à ${formatDate(track.createdAt)}
`; } function renderQueue() { if (!elements.queueList) return; elements.queueList.innerHTML = ""; if (!state.queue || state.queue.length === 0) { elements.queueEmpty.style.display = "block"; return; } elements.queueEmpty.style.display = "none"; state.queue.forEach((track, index) => { const li = document.createElement("li"); li.className = "media-item media-item--queue"; const permanence = track.permanent ? "Permanent" : "Temporaire"; li.innerHTML = `
${escapeHtml(track.title || track.originalName)}
${permanence} • ${formatBytes(track.size)} • Ajouté par ${escapeHtml( track.uploader || "inconnu" )}
`; li.querySelectorAll("button").forEach((btn) => { btn.addEventListener("click", (event) => { const action = event.currentTarget.dataset.action; const trackId = event.currentTarget.dataset.track; handleQueueAction(action, trackId, index); }); }); elements.queueList.appendChild(li); }); } function renderLibrary() { if (!elements.libraryList) return; elements.libraryList.innerHTML = ""; if (!state.library || state.library.length === 0) { elements.libraryEmpty.style.display = "block"; return; } elements.libraryEmpty.style.display = "none"; state.library.forEach((track) => { const li = document.createElement("li"); li.className = "media-item media-item--library"; li.innerHTML = `
${escapeHtml(track.originalName)}
${formatBytes(track.size)} • Ajouté le ${formatDate(track.createdAt)}
`; li.querySelectorAll("button").forEach((btn) => { btn.addEventListener("click", (event) => { const action = event.currentTarget.dataset.libraryAction; const libraryId = event.currentTarget.dataset.libraryId; handleLibraryAction(action, libraryId); }); }); elements.libraryList.appendChild(li); }); } function updatePauseResumeButton() { if (!elements.pauseResumeBtn) return; const normalizedStatus = typeof state.status === "string" ? state.status.toLowerCase() : "idle"; const isPaused = normalizedStatus === "paused"; const isPlaying = normalizedStatus === "playing"; if (isPaused) { elements.pauseResumeBtn.textContent = "Reprendre"; elements.pauseResumeBtn.dataset.playerAction = "resume"; } else { elements.pauseResumeBtn.textContent = "Pause"; elements.pauseResumeBtn.dataset.playerAction = "pause"; } const actionable = isPaused || isPlaying; elements.pauseResumeBtn.disabled = !actionable; } async function handleQueueAction(action, trackId, index) { try { if (action === "remove") { await apiFetch(`/queue/${trackId}`, { method: "DELETE" }); } else if (action === "up" || action === "down") { const delta = action === "up" ? -1 : 1; const newPosition = Math.max(0, Math.min(index + delta, state.queue.length - 1)); if (newPosition === index) return; await apiFetch(`/queue/${trackId}/move`, { method: "PATCH", body: { position: newPosition }, }); } await refreshAll({ silent: true }); } catch (error) { setStatus(`Impossible de mettre à jour la file: ${error.message}`, "error"); } } async function handleLibraryAction(action, libraryId) { try { if (action === "enqueue") { await apiFetch(`/library/${libraryId}/enqueue`, { method: "POST", body: { requestedBy: state.displayName || "web-ui" }, }); } else if (action === "delete") { if (!confirm("Supprimer ce fichier permanent ?")) { return; } await apiFetch(`/library/${libraryId}`, { method: "DELETE" }); } await refreshAll({ silent: true }); } catch (error) { setStatus(`Action bibliothèque impossible: ${error.message}`, "error"); } } function startPolling() { clearInterval(pollIntervalId); pollIntervalId = setInterval(() => refreshAll({ silent: true }), 5000); } function bindEvents() { elements.settingsForm?.addEventListener("submit", (event) => { event.preventDefault(); state.apiKey = elements.apiKeyInput.value.trim(); state.displayName = elements.displayNameInput.value.trim(); setStoredApiKey(state.apiKey); setStoredDisplayName(state.displayName); setStatus("Paramètres sauvegardés", "success"); refreshAll({ silent: true }); }); elements.uploadForm?.addEventListener("submit", async (event) => { event.preventDefault(); const file = elements.fileInput.files[0]; if (!file) { setStatus("Merci de sélectionner un fichier audio", "error"); return; } try { const data = new FormData(); data.append("file", file); data.append("permanent", elements.permanentCheckbox.checked ? "true" : "false"); data.append("enqueue", elements.enqueueCheckbox.checked ? "true" : "false"); if (state.displayName) { data.append("uploader", state.displayName); } if (elements.notesInput.value) { data.append("notes", elements.notesInput.value.trim()); } setStatus("Upload en cours...", "info", false); await apiFetch("/uploads", { method: "POST", body: data }); elements.uploadForm.reset(); elements.enqueueCheckbox.checked = true; setStatus("Fichier envoyé avec succès", "success"); await refreshAll({ silent: true }); } catch (error) { setStatus(`Échec de l'upload: ${error.message}`, "error"); } }); elements.refreshBtn?.addEventListener("click", () => refreshAll()); elements.heroUploadBtn?.addEventListener("click", () => elements.uploadSection?.scrollIntoView({ behavior: "smooth" }) ); elements.heroQueueBtn?.addEventListener("click", () => elements.queueSection?.scrollIntoView({ behavior: "smooth" }) ); elements.playerButtons?.forEach((button) => { button.addEventListener("click", async () => { const action = button.dataset.playerAction; try { await apiFetch(`/player/${action}`, { method: "POST" }); await refreshAll({ silent: true }); } catch (error) { setStatus(`Action player impossible: ${error.message}`, "error"); } }); }); } function initialize() { elements.apiKeyInput.value = state.apiKey; elements.displayNameInput.value = state.displayName; bindEvents(); updatePauseResumeButton(); refreshAll(); startPolling(); } initialize();