/** * Main application logic for domain consensus plugin */ // Global state let currentView = 'overview'; let currentDomain = null; /** * Initialize application */ function init() { // Initialize ProfileModal if available if (window.ProfileModal && typeof window.ProfileModal.init === 'function') { window.ProfileModal.init(); } // Setup hash-based view switching setupHashNavigation(); // Setup WebSocket listeners setupWebSocketListeners(); // Connect WebSocket window.wsClient.connect(); // Update sidebar stats on initial load if (window.utils && window.utils.updateSidebarStats) { window.utils.updateSidebarStats(); } // Load initial view if (!window.location.hash || window.location.hash === '#') { window.location.hash = '#overview'; } // Always handle hash change on init to set up the view handleHashChange(); } /** * Setup hash-based navigation */ function setupHashNavigation() { // Listen for hash changes window.addEventListener('hashchange', handleHashChange); // Also listen for popstate (back/forward buttons) window.addEventListener('popstate', handleHashChange); } /** * Handle hash change */ function handleHashChange() { const hash = window.location.hash.slice(1) || 'overview'; // Handle domain-detail hash format const domainDetailMatch = hash.match(/^domain-detail:(.+)$/); if (domainDetailMatch) { const domain = decodeURIComponent(domainDetailMatch[1]); showView('domain-detail', domain); return; } // Map hash to view name const viewMap = { 'overview': 'overview', 'domains': 'domain-list', 'domain-detail': 'domain-detail' }; const view = viewMap[hash] || 'overview'; showView(view); } /** * Switch to a different view */ function showView(view, domain = null) { // Check if we're already on this view (and for domain-detail, same domain) // Do this BEFORE updating currentView const isDomainDetail = view === 'domain-detail'; const isSameView = currentView === view && (!isDomainDetail || currentDomain === domain); // If we're already on this view, just ensure hash is correct and return (without changing currentView) if (isSameView) { // Map view name to hash const hashMap = { 'overview': 'overview', 'domain-list': 'domains', 'domain-detail': 'domain-detail' }; const expectedHash = isDomainDetail && domain ? `#domain-detail:${encodeURIComponent(domain)}` : `#${hashMap[view] || view}`; // Only update hash if it's different if (window.location.hash !== expectedHash) { window.location.hash = expectedHash; } return; } // Update current view AFTER checking currentView = view; currentDomain = domain; // Hide all views and clean up charts document.querySelectorAll('.view-content').forEach(content => { content.classList.remove('active'); content.style.display = 'none'; content.style.visibility = 'hidden'; // Clean up charts when hiding overview view if (content.id === 'overview-view' && window.overviewView) { window.overviewView.destroy(); } // Clean up charts when hiding domain detail view if (content.id === 'domain-detail-view' && window.domainDetailView) { window.domainDetailView.destroy(); } }); // Update active tab document.querySelectorAll('.view-tab').forEach(tab => { const tabView = tab.dataset.view; if (tabView === view) { tab.classList.add('active'); } else { tab.classList.remove('active'); } }); // Show domain detail tab if viewing domain detail const domainDetailTab = document.getElementById('domainDetailTab'); // Map view name to hash const hashMap = { 'overview': 'overview', 'domain-list': 'domains', 'domain-detail': 'domain-detail' }; const expectedHash = view === 'domain-detail' && domain ? `#domain-detail:${encodeURIComponent(domain)}` : `#${hashMap[view] || view}`; // Only update hash if it's different to avoid recursive hash changes if (window.location.hash !== expectedHash) { window.location.hash = expectedHash; } if (view === 'domain-detail') { if (domainDetailTab) { domainDetailTab.classList.remove('hidden'); domainDetailTab.classList.add('active'); } } else { if (domainDetailTab) { domainDetailTab.classList.add('hidden'); domainDetailTab.classList.remove('active'); } } // Show and render the selected view let viewElement; let renderFunction; switch (view) { case 'overview': viewElement = document.getElementById('overview-view'); renderFunction = () => window.overviewView.render(); break; case 'domain-list': viewElement = document.getElementById('domain-list-view'); renderFunction = () => window.domainListView.render(); break; case 'domain-detail': viewElement = document.getElementById('domain-detail-view'); if (domain) { renderFunction = () => window.domainDetailView.render(domain); } else { // Try to extract domain from hash const hashMatch = window.location.hash.match(/^#domain-detail:(.+)$/); if (hashMatch) { const hashDomain = decodeURIComponent(hashMatch[1]); renderFunction = () => window.domainDetailView.render(hashDomain); } else { // Fallback to domain list showView('domain-list'); return; } } break; default: viewElement = document.getElementById('overview-view'); renderFunction = () => window.overviewView.render(); } if (viewElement) { viewElement.classList.add('active'); viewElement.style.display = 'flex'; viewElement.style.visibility = 'visible'; viewElement.style.flexDirection = 'column'; // Render the view after ensuring it's visible // Use a small timeout to ensure DOM is ready if (renderFunction) { setTimeout(() => { renderFunction(); }, 0); } } } /** * Show domain detail view */ function showDomainDetail(domain) { showView('domain-detail', domain); } /** * Setup WebSocket listeners */ function setupWebSocketListeners() { const ws = window.wsClient; ws.on('connected', () => { console.log('WebSocket connected'); }); ws.on('disconnected', () => { console.log('WebSocket disconnected'); }); ws.on('init', (data) => { console.log('WebSocket init:', data); // Update sidebar stats on init if (window.utils && window.utils.updateSidebarStats) { window.utils.updateSidebarStats(); } // Handle initial data if needed handleWebSocketUpdate(data.data || data); }); ws.on('update', (data) => { // Handle periodic update (fallback) handleWebSocketUpdate(data); }); ws.on('consensus-update', (data) => { // Handle real-time consensus change console.log('Consensus update detected:', data.changedDomains || 'all domains'); handleWebSocketUpdate(data); }); ws.on('domain-added', (data) => { console.log('Domain added:', data); if (data.state) { handleWebSocketUpdate(data.state); } else { handleWebSocketUpdate(data); } }); ws.on('domain-removed', (data) => { console.log('Domain removed:', data); if (data.state) { handleWebSocketUpdate(data.state); } else { handleWebSocketUpdate(data); } }); } /** * Handle WebSocket update */ function handleWebSocketUpdate(data) { // Show brief visual indicator for real-time updates (only for consensus-update type) if (data.changedDomains && data.changedDomains.length > 0) { showUpdateIndicator(); } // Always update sidebar stats on any update if (window.utils && window.utils.updateSidebarStats) { window.utils.updateSidebarStats(); } // Notify all views of update (they handle their own background refresh logic) if (window.overviewView) { window.overviewView.handleUpdate(data); } if (window.domainListView) { window.domainListView.handleUpdate(data); } if (window.domainDetailView) { window.domainDetailView.handleUpdate(data); } } /** * Show brief visual indicator for real-time updates */ function showUpdateIndicator() { const statusText = document.getElementById('statusText'); if (statusText) { const originalText = statusText.textContent; statusText.textContent = 'Updating...'; statusText.style.opacity = '0.7'; setTimeout(() => { statusText.textContent = originalText; statusText.style.opacity = '1'; }, 500); } } // Export app functions window.app = { showView, showDomainDetail }; // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }