// MusicRoom - Utilities module // DOM helpers, formatting, toast notifications (function() { const M = window.MusicRoom; // DOM selector helper M.$ = (s) => document.querySelector(s); // Format seconds as m:ss M.fmt = function(sec) { if (!sec || !isFinite(sec)) return "0:00"; const m = Math.floor(sec / 60); const s = Math.floor(sec % 60); return m + ":" + String(s).padStart(2, "0"); }; // Toast notifications M.showToast = function(message, duration = 4000) { const container = M.$("#toast-container"); const toast = document.createElement("div"); toast.className = "toast"; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.classList.add("fade-out"); setTimeout(() => toast.remove(), 300); }, duration); }; // Flash permission denied animation M.flashPermissionDenied = function() { const row = M.$("#progress-row"); row.classList.remove("denied"); void row.offsetWidth; // Trigger reflow to restart animation row.classList.add("denied"); setTimeout(() => row.classList.remove("denied"), 500); }; // Set track title (UI and document title) M.setTrackTitle = function(title) { M.currentTitle = title; const titleEl = M.$("#track-title"); const containerEl = M.$("#track-name"); const marqueeEl = containerEl.querySelector(".marquee-inner"); titleEl.textContent = title; document.title = title ? `${title} - MusicRoom` : "MusicRoom"; // Check if title overflows and needs scrolling requestAnimationFrame(() => { const needsScroll = titleEl.scrollWidth > containerEl.clientWidth; containerEl.classList.toggle("scrolling", needsScroll); // Duplicate text for seamless wrap-around scrolling if (needsScroll) { marqueeEl.innerHTML = `${title}   •   ${title}   •   `; } else { marqueeEl.innerHTML = `${title}`; } }); }; // Get current server time (extrapolated) M.getServerTime = function() { if (M.serverPaused) return M.serverTimestamp; return M.serverTimestamp + (Date.now() - M.lastServerUpdate) / 1000; }; // Check if current user can control playback M.canControl = function() { if (!M.currentUser) return false; if (M.currentUser.isAdmin) return true; return M.currentUser.permissions?.some(p => p.resource_type === "channel" && (p.resource_id === M.currentChannelId || p.resource_id === null) && p.permission === "control" ); }; })();