feat: add initial desktop view implementation

This commit is contained in:
2025-09-15 20:57:30 +02:00
parent 8476c76637
commit 6f1e194545
3 changed files with 177 additions and 6 deletions

View File

@@ -19,6 +19,97 @@ class ClusterMembersComponent extends Component {
this.render();
}
}, 200);
// Drawer state for desktop
this.detailsDrawer = null;
this.detailsDrawerContent = null;
this.detailsDrawerBackdrop = null;
this.activeDrawerComponent = 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 close button
const header = document.createElement('div');
header.className = 'details-drawer-header';
header.innerHTML = `
<div class="drawer-title">Node Details</div>
<button class="drawer-close" aria-label="Close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
`;
this.detailsDrawer.appendChild(header);
// Content container
this.detailsDrawerContent = document.createElement('div');
this.detailsDrawerContent.className = 'details-drawer-content';
this.detailsDrawer.appendChild(this.detailsDrawerContent);
document.body.appendChild(this.detailsDrawer);
// Close handlers
const close = () => this.closeDrawer();
header.querySelector('.drawer-close').addEventListener('click', close);
this.detailsDrawerBackdrop.addEventListener('click', close);
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') close();
});
}
openDrawerForMember(memberIp) {
this.ensureDrawer();
// Clear previous component if any
if (this.activeDrawerComponent && typeof this.activeDrawerComponent.unmount === 'function') {
try { this.activeDrawerComponent.unmount(); } catch (_) {}
}
this.detailsDrawerContent.innerHTML = '<div class="loading-details">Loading detailed information...</div>';
// Load and mount NodeDetails into drawer
const nodeDetailsVM = new NodeDetailsViewModel();
const nodeDetailsComponent = new NodeDetailsComponent(this.detailsDrawerContent, nodeDetailsVM, this.eventBus);
this.activeDrawerComponent = nodeDetailsComponent;
nodeDetailsVM.loadNodeDetails(memberIp).then(() => {
nodeDetailsComponent.mount();
}).catch((error) => {
logger.error('Failed to load node details for drawer:', error);
this.detailsDrawerContent.innerHTML = `
<div class="error">
<strong>Error loading node details:</strong><br>
${this.escapeHtml(error.message)}
</div>
`;
});
// 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');
}
mount() {
@@ -409,8 +500,16 @@ class ClusterMembersComponent extends Component {
this.addEventListener(card, 'click', async (e) => {
if (e.target === expandIcon) return;
const isExpanding = !card.classList.contains('expanded');
// On desktop, open slide-in drawer instead of inline expand
if (this.isDesktop()) {
e.preventDefault();
e.stopPropagation();
this.openDrawerForMember(memberIp);
return;
}
// Mobile/low-res: keep inline expand/collapse
const isExpanding = !card.classList.contains('expanded');
if (isExpanding) {
await this.expandCard(card, memberIp, memberDetails);
} else {
@@ -422,9 +521,11 @@ class ClusterMembersComponent extends Component {
if (expandIcon) {
this.addEventListener(expandIcon, 'click', async (e) => {
e.stopPropagation();
if (this.isDesktop()) {
this.openDrawerForMember(memberIp);
return;
}
const isExpanding = !card.classList.contains('expanded');
if (isExpanding) {
await this.expandCard(card, memberIp, memberDetails);
} else {