(function() { const M = window.MusicRoom; document.addEventListener("DOMContentLoaded", () => { const addBtn = M.$("#btn-add"); const addPanel = M.$("#add-panel"); const addCloseBtn = M.$("#btn-add-close"); const uploadFilesBtn = M.$("#btn-upload-files"); const fileInput = M.$("#file-input"); const dropzone = M.$("#upload-dropzone"); const libraryPanel = M.$("#library-panel"); const tasksList = M.$("#tasks-list"); const tasksEmpty = M.$("#tasks-empty"); if (!addBtn || !fileInput || !dropzone) return; function openPanel() { addPanel.classList.remove("hidden", "closing"); addBtn.classList.add("hidden"); } function closePanel() { addPanel.classList.add("closing"); addBtn.classList.remove("hidden"); addPanel.addEventListener("animationend", () => { if (addPanel.classList.contains("closing")) { addPanel.classList.add("hidden"); addPanel.classList.remove("closing"); } }, { once: true }); } // Open add panel addBtn.onclick = () => { if (!M.currentUser) { M.showToast("Sign in to add tracks"); return; } openPanel(); }; // Close add panel addCloseBtn.onclick = () => { closePanel(); }; // Upload files option uploadFilesBtn.onclick = () => { fileInput.click(); }; // File input change fileInput.onchange = () => { if (fileInput.files.length > 0) { closePanel(); uploadFiles(fileInput.files); fileInput.value = ""; } }; // Drag and drop on library panel let dragCounter = 0; libraryPanel.ondragenter = (e) => { if (!M.currentUser) return; if (!e.dataTransfer.types.includes("Files")) return; e.preventDefault(); dragCounter++; dropzone.classList.remove("hidden"); }; libraryPanel.ondragleave = (e) => { e.preventDefault(); dragCounter--; if (dragCounter === 0) { dropzone.classList.add("hidden"); } }; libraryPanel.ondragover = (e) => { if (!M.currentUser) return; if (!e.dataTransfer.types.includes("Files")) return; e.preventDefault(); e.dataTransfer.dropEffect = "copy"; }; libraryPanel.ondrop = (e) => { e.preventDefault(); dragCounter = 0; dropzone.classList.add("hidden"); if (!M.currentUser) { M.showToast("Sign in to upload"); return; } const files = e.dataTransfer.files; if (files.length > 0) { uploadFiles(files); } }; // Task management function updateTasksEmpty() { const hasTasks = tasksList.children.length > 0; tasksEmpty.classList.toggle("hidden", hasTasks); } function createTask(filename) { const task = document.createElement("div"); task.className = "task-item"; task.innerHTML = ` ${filename} 0%
`; tasksList.appendChild(task); updateTasksEmpty(); // Switch to tasks tab const tasksTab = document.querySelector('.panel-tab[data-tab="tasks"]'); if (tasksTab) tasksTab.click(); return { setProgress(percent) { task.querySelector(".task-progress").textContent = `${Math.round(percent)}%`; task.querySelector(".task-bar").style.width = `${percent}%`; }, setComplete() { task.classList.add("complete"); task.querySelector(".task-progress").textContent = "Done"; task.querySelector(".task-bar").style.width = "100%"; // Remove after delay setTimeout(() => { task.remove(); updateTasksEmpty(); }, 3000); }, setError(msg) { task.classList.add("error"); task.querySelector(".task-progress").textContent = msg || "Failed"; // Remove after delay setTimeout(() => { task.remove(); updateTasksEmpty(); }, 5000); } }; } function uploadFile(file) { return new Promise((resolve) => { const task = createTask(file.name); const xhr = new XMLHttpRequest(); const formData = new FormData(); formData.append("file", file); xhr.upload.onprogress = (e) => { if (e.lengthComputable) { const percent = (e.loaded / e.total) * 100; task.setProgress(percent); } }; xhr.onload = () => { if (xhr.status === 200) { task.setComplete(); resolve({ success: true }); } else if (xhr.status === 409) { task.setError("Duplicate"); resolve({ success: false, duplicate: true }); } else { task.setError("Failed"); resolve({ success: false }); } }; xhr.onerror = () => { task.setError("Error"); resolve({ success: false }); }; xhr.open("POST", "/api/upload"); xhr.send(formData); }); } async function uploadFiles(files) { const validExts = [".mp3", ".ogg", ".flac", ".wav", ".m4a", ".aac", ".opus", ".wma", ".mp4"]; const audioFiles = [...files].filter(f => { const ext = f.name.toLowerCase().match(/\.[^.]+$/)?.[0]; return ext && validExts.includes(ext); }); if (audioFiles.length === 0) { M.showToast("No valid audio files"); return; } let uploaded = 0; let failed = 0; // Upload files in parallel (max 3 concurrent) const concurrency = 3; const queue = [...audioFiles]; const active = []; while (queue.length > 0 || active.length > 0) { while (active.length < concurrency && queue.length > 0) { const file = queue.shift(); const promise = uploadFile(file).then(result => { if (result.success) uploaded++; else failed++; active.splice(active.indexOf(promise), 1); }); active.push(promise); } if (active.length > 0) { await Promise.race(active); } } if (uploaded > 0) { M.showToast(`Uploaded ${uploaded} track${uploaded > 1 ? 's' : ''}${failed > 0 ? `, ${failed} failed` : ''}`); } else if (failed > 0) { M.showToast(`Upload failed`); } } }); })();