// 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 history M.toastHistory = []; // Toast notifications (log style - multiple visible) M.showToast = function(message, type = "info", duration = 5000) { const container = M.$("#toast-container"); const toast = document.createElement("div"); toast.className = "toast toast-" + type; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.classList.add("fade-out"); setTimeout(() => toast.remove(), 300); }, duration); // Add to history M.toastHistory.push({ message, type, time: new Date() }); M.updateToastHistory(); }; // Update toast history panel M.updateToastHistory = function() { const list = M.$("#toast-history-list"); if (!list) return; list.innerHTML = ""; // Show newest first const items = [...M.toastHistory].reverse().slice(0, 50); for (const item of items) { const div = document.createElement("div"); div.className = "history-item history-" + item.type; const time = item.time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); div.innerHTML = `${time} ${item.message}`; list.appendChild(div); } }; // Toggle toast history panel M.toggleToastHistory = function() { const panel = M.$("#toast-history"); if (panel) { panel.classList.toggle("hidden"); if (!panel.classList.contains("hidden")) { M.updateToastHistory(); } } }; // 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 containerEl = M.$("#track-name"); const marqueeEl = containerEl?.querySelector(".marquee-inner"); if (!containerEl || !marqueeEl) return; document.title = title ? `${title} - MusicRoom` : "MusicRoom"; // First set simple content to measure marqueeEl.innerHTML = `${title}`; // Check if title overflows and needs scrolling requestAnimationFrame(() => { const titleEl = M.$("#track-title"); const needsScroll = titleEl && titleEl.scrollWidth > containerEl.clientWidth; containerEl.classList.toggle("scrolling", needsScroll); // Duplicate text for seamless wrap-around scrolling if (needsScroll) { marqueeEl.innerHTML = `${title}   •   ${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" ); }; })();