333 lines
8.7 KiB
JavaScript
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();
|
|
}
|
|
|