// Reusable Drawer Component for desktop slide-in panels
class DrawerComponent {
constructor() {
this.detailsDrawer = null;
this.detailsDrawerContent = null;
this.detailsDrawerBackdrop = null;
this.activeDrawerComponent = null;
this.onCloseCallback = null;
}
// Determine if we should use desktop drawer behavior
isDesktop() {
try {
return window && window.innerWidth >= 1024; // desktop threshold
} catch (_) {
return false;
}
}
ensureDrawer() {
if (this.detailsDrawer) return;
// Create backdrop
this.detailsDrawerBackdrop = document.createElement('div');
this.detailsDrawerBackdrop.className = 'details-drawer-backdrop';
document.body.appendChild(this.detailsDrawerBackdrop);
// Create drawer
this.detailsDrawer = document.createElement('div');
this.detailsDrawer.className = 'details-drawer';
// Header with actions and close button
const header = document.createElement('div');
header.className = 'details-drawer-header';
header.innerHTML = `
Node Details
`;
this.detailsDrawer.appendChild(header);
// Content container
this.detailsDrawerContent = document.createElement('div');
this.detailsDrawerContent.className = 'details-drawer-content';
this.detailsDrawer.appendChild(this.detailsDrawerContent);
// Terminal panel container (positioned left of details drawer)
this.terminalPanelContainer = document.createElement('div');
this.terminalPanelContainer.className = 'terminal-panel-container';
document.body.appendChild(this.terminalPanelContainer);
document.body.appendChild(this.detailsDrawer);
// Close handlers
const close = () => this.closeDrawer();
header.querySelector('.drawer-close').addEventListener('click', close);
const terminalBtn = header.querySelector('.drawer-terminal-btn');
if (terminalBtn) {
terminalBtn.addEventListener('click', (e) => {
e.stopPropagation();
try {
const nodeIp = this.activeDrawerComponent && this.activeDrawerComponent.viewModel && this.activeDrawerComponent.viewModel.get('nodeIp');
window.TerminalPanel && window.TerminalPanel.open(this.terminalPanelContainer, nodeIp);
} catch (err) {
console.error('Failed to open terminal:', err);
}
});
}
this.detailsDrawerBackdrop.addEventListener('click', close);
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') close();
});
}
openDrawer(title, contentCallback, errorCallback, onCloseCallback) {
this.ensureDrawer();
this.onCloseCallback = onCloseCallback;
// Set drawer title
const titleEl = this.detailsDrawer.querySelector('.drawer-title');
if (titleEl) {
titleEl.textContent = title;
}
// Clear previous component if any
if (this.activeDrawerComponent && typeof this.activeDrawerComponent.unmount === 'function') {
try {
this.activeDrawerComponent.unmount();
} catch (_) {}
}
this.detailsDrawerContent.innerHTML = 'Loading detailed information...
';
// Execute content callback
try {
contentCallback(this.detailsDrawerContent, (component) => {
this.activeDrawerComponent = component;
});
} catch (error) {
logger.error('Failed to load drawer content:', error);
if (errorCallback) {
errorCallback(error);
} else {
this.detailsDrawerContent.innerHTML = `
Error loading content:
${this.escapeHtml ? this.escapeHtml(error.message) : error.message}
`;
}
}
// Open drawer
this.detailsDrawer.classList.add('open');
this.detailsDrawerBackdrop.classList.add('visible');
}
closeDrawer() {
if (this.detailsDrawer) this.detailsDrawer.classList.remove('open');
if (this.detailsDrawerBackdrop) this.detailsDrawerBackdrop.classList.remove('visible');
// Also close terminal panel if open
try { if (window.TerminalPanel) window.TerminalPanel.close(); } catch (_) {}
// Call close callback if provided
if (this.onCloseCallback) {
this.onCloseCallback();
this.onCloseCallback = null;
}
}
// Clean up drawer elements
destroy() {
if (this.detailsDrawer && this.detailsDrawer.parentNode) {
this.detailsDrawer.parentNode.removeChild(this.detailsDrawer);
}
if (this.detailsDrawerBackdrop && this.detailsDrawerBackdrop.parentNode) {
this.detailsDrawerBackdrop.parentNode.removeChild(this.detailsDrawerBackdrop);
}
this.detailsDrawer = null;
this.detailsDrawerContent = null;
this.detailsDrawerBackdrop = null;
this.activeDrawerComponent = null;
}
// Helper method for HTML escaping (can be overridden)
escapeHtml(text) {
if (typeof text !== 'string') return text;
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
window.DrawerComponent = DrawerComponent;