const appsBtn = document.getElementById("apps-btn"); const appsDropdown = document.getElementById("apps-dropdown"); const settingsBtn = document.getElementById("settings-btn"); const settingsPanel = document.getElementById("settings-panel"); const userAvatarBtn = document.getElementById("userAvatar"); const userMenu = document.getElementById("userMenu"); // Global product configuration window.productConfig = null; // Load product configuration and apply white-label settings async function loadProductConfig() { try { const response = await fetch("/api/product"); if (response.ok) { window.productConfig = await response.json(); applyProductConfig(window.productConfig); } } catch (e) { console.warn("Failed to load product config:", e); } } // Apply product configuration to the UI function applyProductConfig(config) { if (!config) return; // Update page title with product name if (config.name && config.name !== "General Bots") { document.title = document.title.replace("General Bots", config.name); // Update any visible branding text document.querySelectorAll("[data-brand]").forEach((el) => { el.textContent = el.textContent.replace("General Bots", config.name); }); } // Apply default theme if specified and user hasn't set one if (config.theme && !localStorage.getItem("gb-theme")) { document.body.setAttribute("data-theme", config.theme); localStorage.setItem("gb-theme", config.theme); } // Filter apps based on enabled list if (config.apps && Array.isArray(config.apps)) { let effectiveApps = config.apps; // Check if we have compiled_features info to filter even further // This ensures we don't show apps that are enabled in config but not compiled in binary if (config.compiled_features && Array.isArray(config.compiled_features)) { const compiledSet = new Set( config.compiled_features.map((f) => f.toLowerCase()), ); effectiveApps = effectiveApps.filter( (app) => compiledSet.has(app.toLowerCase()) || app.toLowerCase() === "settings" || app.toLowerCase() === "auth" || app.toLowerCase() === "admin", // Admin usually contains settings which is always there ); // Also call a helper to hide UI elements for non-compiled features explicitly // This handles features that might not be "apps" but are UI sections hideNonCompiledUI(compiledSet); } filterAppsByConfig(effectiveApps); // Check if there are any visible apps after filtering const hasVisibleApps = effectiveApps.length > 0; // Hide apps menu button if menu launcher is disabled or if there are no apps to show if (config.menu_launcher_enabled === false || !hasVisibleApps) { const appsButton = document.getElementById("appsButton"); if (appsButton) { appsButton.style.display = "none"; } const appsMenuContainer = document.querySelector(".apps-menu-container"); if (appsMenuContainer) { appsMenuContainer.style.display = "none"; } } } // Hide omnibox if search is disabled if (config.search_enabled === false) { const omnibox = document.getElementById("omnibox"); if (omnibox) { omnibox.style.display = "none"; } const headerCenter = document.querySelector(".header-center"); if (headerCenter) { headerCenter.style.display = "none"; } } // Apply custom logo if (config.logo) { const logoEl = document.querySelector(".header-logo img, .logo img"); if (logoEl) logoEl.src = config.logo; } // Apply custom primary color if (config.primary_color) { document.documentElement.style.setProperty( "--primary", config.primary_color, ); } // Update docs URL if (config.docs_url) { document.querySelectorAll('a[href*="docs.pragmatismo"]').forEach((el) => { el.href = config.docs_url; }); } // Update copyright if (config.copyright) { document.querySelectorAll(".copyright, .footer-copyright").forEach((el) => { el.textContent = config.copyright; }); } } // Hide UI elements that require features not compiled in the binary function hideNonCompiledUI(compiledSet) { // Hide elements with data-feature attribute that aren't in compiled set document.querySelectorAll("[data-feature]").forEach((el) => { const feature = el.getAttribute("data-feature").toLowerCase(); // Allow settings/admin as they are usually core if ( !compiledSet.has(feature) && feature !== "settings" && feature !== "admin" ) { el.style.display = "none"; el.classList.add("hidden-uncompiled"); } }); // Also look for specific sections that might map to features // e.g. .feature-mail, .feature-meet classes compiledSet.forEach((feature) => { // This loop defines what IS available. // Logic should be inverse: find all feature- classes and hide if not in set // But scanning all classes is expensive. // Better to rely on data-feature or explicit app hiding which filterAppsByConfig does. }); } // Filter visible apps based on enabled list function filterAppsByConfig(enabledApps) { const enabledSet = new Set(enabledApps.map((a) => a.toLowerCase())); // Hide nav items for disabled apps document.querySelectorAll("[data-app]").forEach((el) => { const appName = el.getAttribute("data-app").toLowerCase(); if (!enabledSet.has(appName)) { el.style.display = "none"; } }); // Hide app items in dropdown document.querySelectorAll(".app-item").forEach((el) => { const href = el.getAttribute("href") || el.getAttribute("hx-get") || ""; const appMatch = href.match( /\/(chat|mail|calendar|drive|tasks|docs|paper|sheet|slides|meet|research|sources|analytics|admin|monitoring|settings)/i, ); if (appMatch) { const appName = appMatch[1].toLowerCase(); if (!enabledSet.has(appName)) { el.style.display = "none"; } } }); } // Check if an app is enabled function isAppEnabled(appName) { if (!window.productConfig || !window.productConfig.apps) return true; return window.productConfig.apps .map((a) => a.toLowerCase()) .includes(appName.toLowerCase()); } // Load product config on page load loadProductConfig(); if (appsBtn) { appsBtn.addEventListener("click", (e) => { e.stopPropagation(); const isOpen = appsDropdown.classList.toggle("show"); appsBtn.setAttribute("aria-expanded", isOpen); settingsPanel.classList.remove("show"); }); } if (settingsBtn) { settingsBtn.addEventListener("click", (e) => { e.stopPropagation(); const isOpen = settingsPanel.classList.toggle("show"); settingsBtn.setAttribute("aria-expanded", isOpen); appsDropdown.classList.remove("show"); if (userMenu) { userMenu.style.display = "none"; userAvatarBtn.setAttribute("aria-expanded", "false"); } }); } if (userAvatarBtn) { userAvatarBtn.addEventListener("click", (e) => { e.stopPropagation(); const isOpen = userMenu.style.display === "none" || userMenu.style.display === ""; userMenu.style.display = isOpen ? "block" : "none"; userAvatarBtn.setAttribute("aria-expanded", isOpen); if (appsDropdown) appsDropdown.classList.remove("show"); if (settingsPanel) settingsPanel.classList.remove("show"); if (appsBtn) appsBtn.setAttribute("aria-expanded", "false"); if (settingsBtn) settingsBtn.setAttribute("aria-expanded", "false"); }); } document.addEventListener("click", (e) => { if ( appsDropdown && !appsDropdown.contains(e.target) && !appsBtn.contains(e.target) ) { appsDropdown.classList.remove("show"); appsBtn.setAttribute("aria-expanded", "false"); } if ( settingsPanel && !settingsPanel.contains(e.target) && !settingsBtn.contains(e.target) ) { settingsPanel.classList.remove("show"); settingsBtn.setAttribute("aria-expanded", "false"); } if ( userMenu && !userMenu.contains(e.target) && !userAvatarBtn.contains(e.target) ) { userMenu.style.display = "none"; userAvatarBtn.setAttribute("aria-expanded", "false"); } }); document.addEventListener("keydown", (e) => { if (e.key === "Escape") { if (appsDropdown) appsDropdown.classList.remove("show"); if (settingsPanel) settingsPanel.classList.remove("show"); if (userMenu) userMenu.style.display = "none"; if (appsBtn) appsBtn.setAttribute("aria-expanded", "false"); if (settingsBtn) settingsBtn.setAttribute("aria-expanded", "false"); if (userAvatarBtn) userAvatarBtn.setAttribute("aria-expanded", "false"); } }); document.addEventListener("keydown", (e) => { if (e.altKey && !e.ctrlKey && !e.shiftKey) { const shortcuts = { 1: "chat", 2: "drive", 3: "tasks", 4: "mail", 5: "calendar", 6: "meet", 7: "paper", 8: "research", 9: "sources", 0: "analytics", a: "admin", m: "monitoring", }; if (shortcuts[e.key]) { e.preventDefault(); const link = document.querySelector(`a[href="#${shortcuts[e.key]}"]`); if (link) link.click(); if (appsDropdown) appsDropdown.classList.remove("show"); } if (e.key === ",") { e.preventDefault(); if (settingsPanel) settingsPanel.classList.toggle("show"); } if (e.key === "s") { e.preventDefault(); const settingsLink = document.querySelector(`a[href="#settings"]`); if (settingsLink) settingsLink.click(); } } }); document.body.addEventListener("htmx:afterSwap", (e) => { if (e.detail.target.id === "main-content") { // Close settings panel on navigation if (settingsPanel) settingsPanel.classList.remove("show"); // Note: Active class handling is done in htmx-app.js updateActiveNav() } }); const themeOptions = document.querySelectorAll(".theme-option"); const savedTheme = localStorage.getItem("gb-theme") || "sentient"; document.body.setAttribute("data-theme", savedTheme); document .querySelector(`.theme-option[data-theme="${savedTheme}"]`) ?.classList.add("active"); function updateThemeColor(theme) { const themeColors = { dark: "#3b82f6", light: "#3b82f6", blue: "#0ea5e9", purple: "#a855f7", green: "#22c55e", orange: "#f97316", sentient: "#d4f505", cyberpunk: "#ff00ff", retrowave: "#ff6b9d", vapordream: "#a29bfe", y2kglow: "#00ff00", arcadeflash: "#ffff00", discofever: "#ff1493", grungeera: "#8b4513", jazzage: "#d4af37", mellowgold: "#daa520", midcenturymod: "#e07b39", polaroidmemories: "#e6b89c", saturdaycartoons: "#ff6347", seasidepostcard: "#20b2aa", typewriter: "#2f2f2f", "3dbevel": "#0000ff", xeroxui: "#4a86cf", xtreegold: "#ffff00", }; const metaTheme = document.querySelector('meta[name="theme-color"]'); if (metaTheme) { metaTheme.setAttribute("content", themeColors[theme] || "#d4f505"); } } updateThemeColor(savedTheme); themeOptions.forEach((option) => { option.addEventListener("click", () => { const theme = option.getAttribute("data-theme"); document.body.setAttribute("data-theme", theme); localStorage.setItem("gb-theme", theme); themeOptions.forEach((o) => o.classList.remove("active")); option.classList.add("active"); updateThemeColor(theme); }); }); window.setTheme = function (theme, element) { document.body.setAttribute("data-theme", theme); localStorage.setItem("gb-theme", theme); // Update theme options in header settings panel themeOptions.forEach((o) => { o.classList.toggle("active", o.getAttribute("data-theme") === theme); }); // Update theme options in settings page (if present) document.querySelectorAll(".theme-option").forEach((opt) => { opt.classList.toggle("active", opt.getAttribute("data-theme") === theme); }); // If element was passed, ensure it's marked active if (element) { element.classList.add("active"); } updateThemeColor(theme); // Show toast notification if available if (window.showNotification) { window.showNotification("Theme updated", "success"); } }; function toggleQuickSetting(el) { el.classList.toggle("active"); const setting = el.id.replace("toggle-", ""); localStorage.setItem(`gb-${setting}`, el.classList.contains("active")); } ["notifications", "sound", "compact"].forEach((setting) => { const saved = localStorage.getItem(`gb-${setting}`); const toggle = document.getElementById(`toggle-${setting}`); if (toggle && saved !== null) { toggle.classList.toggle("active", saved === "true"); } }); function showKeyboardShortcuts() { window.showNotification( "Alt+1-9,0 for apps, Alt+A Admin, Alt+M Monitoring, Alt+S Settings, Alt+, quick settings", "info", 8000, ); } function announceToScreenReader(message) { const liveRegion = document.getElementById("aria-live"); if (liveRegion) { liveRegion.textContent = message; setTimeout(() => { liveRegion.textContent = ""; }, 1000); } } document.body.addEventListener("htmx:beforeRequest", function (e) { const target = e.detail.target; if (target && target.id === "main-content") { target.setAttribute("aria-busy", "true"); announceToScreenReader("Loading content..."); } }); document.body.addEventListener("htmx:afterSwap", function (e) { const target = e.detail.target; if (target && target.id === "main-content") { target.setAttribute("aria-busy", "false"); target.focus(); announceToScreenReader("Content loaded"); } }); document.body.addEventListener("htmx:responseError", function (e) { const target = e.detail.target; if (target) { target.setAttribute("aria-busy", "false"); } announceToScreenReader("Error loading content. Please try again."); }); document.addEventListener("keydown", function (e) { const appsGrid = document.querySelector(".apps-grid"); if (!appsGrid || !appsGrid.closest(".show")) return; const items = Array.from(appsGrid.querySelectorAll(".app-item")); const currentIndex = items.findIndex( (item) => item === document.activeElement, ); if (currentIndex === -1) return; let newIndex = currentIndex; const columns = 3; switch (e.key) { case "ArrowRight": newIndex = Math.min(currentIndex + 1, items.length - 1); break; case "ArrowLeft": newIndex = Math.max(currentIndex - 1, 0); break; case "ArrowDown": newIndex = Math.min(currentIndex + columns, items.length - 1); break; case "ArrowUp": newIndex = Math.max(currentIndex - columns, 0); break; case "Home": newIndex = 0; break; case "End": newIndex = items.length - 1; break; default: return; } if (newIndex !== currentIndex) { e.preventDefault(); items[newIndex].focus(); } }); window.showNotification = function (message, type = "info", duration = 5000) { const container = document.getElementById("notifications"); if (!container) return; const notification = document.createElement("div"); notification.className = `notification ${type}`; notification.innerHTML = `