Files
p2ns/plugin-sites/domain.consensus/www/js/app.js
T
2025-12-17 20:49:34 -05:00

333 lines
8.7 KiB
JavaScript

/**
* 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();
}