You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
4.2 KiB
JavaScript
142 lines
4.2 KiB
JavaScript
class Sidebar {
|
|
constructor(onSelectSurface) {
|
|
this.onSelectSurface = onSelectSurface;
|
|
this.sidebar = document.getElementById('sidebar');
|
|
this.overlay = document.getElementById('sidebar-overlay');
|
|
this.tree = document.getElementById('sidebar-tree');
|
|
this.menuBtn = document.getElementById('menu-btn');
|
|
this.closeBtn = document.getElementById('sidebar-close');
|
|
|
|
this.workspaces = [];
|
|
this.activeSurface = null;
|
|
this._loaded = false;
|
|
|
|
this.menuBtn.addEventListener('click', () => this.isOpen() ? this.close() : this.open());
|
|
this.closeBtn.addEventListener('click', () => this.close());
|
|
this.overlay.addEventListener('click', () => this.close());
|
|
|
|
// On desktop, sidebar is always visible via CSS (position: relative)
|
|
// Check on resize to close overlay if switching to desktop
|
|
this._onResize = () => {
|
|
if (window.innerWidth >= 1024) {
|
|
this.overlay.classList.remove('visible');
|
|
}
|
|
};
|
|
window.addEventListener('resize', this._onResize);
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && this.isOpen() && !this.isDesktop()) this.close();
|
|
});
|
|
|
|
this.render();
|
|
}
|
|
|
|
open() {
|
|
if (this.isDesktop()) {
|
|
this.sidebar.classList.remove('desktop-hidden');
|
|
} else {
|
|
this.sidebar.classList.add('open');
|
|
this.overlay.classList.add('visible');
|
|
}
|
|
}
|
|
|
|
close() {
|
|
if (this.isDesktop()) {
|
|
this.sidebar.classList.add('desktop-hidden');
|
|
} else {
|
|
this.sidebar.classList.remove('open');
|
|
this.overlay.classList.remove('visible');
|
|
}
|
|
}
|
|
|
|
isDesktop() {
|
|
return window.innerWidth >= 1024;
|
|
}
|
|
|
|
isOpen() {
|
|
if (this.isDesktop()) return !this.sidebar.classList.contains('desktop-hidden');
|
|
return this.sidebar.classList.contains('open');
|
|
}
|
|
|
|
setWorkspaces(workspaces) {
|
|
this.workspaces = workspaces;
|
|
this._loaded = true;
|
|
this.render();
|
|
}
|
|
|
|
setActiveSurface(surfaceRef) {
|
|
this.activeSurface = surfaceRef;
|
|
this.render();
|
|
}
|
|
|
|
render() {
|
|
this.tree.innerHTML = '';
|
|
|
|
if (this.workspaces.length === 0) {
|
|
const empty = document.createElement('div');
|
|
empty.className = 'sidebar-empty';
|
|
empty.textContent = this._loaded ? 'No workspaces' : 'Loading...';
|
|
this.tree.appendChild(empty);
|
|
return;
|
|
}
|
|
|
|
for (const ws of this.workspaces) {
|
|
// Workspace group label
|
|
const groupLabel = document.createElement('div');
|
|
groupLabel.className = 'ws-group-label';
|
|
groupLabel.textContent = ws.name || ws.ref;
|
|
this.tree.appendChild(groupLabel);
|
|
|
|
// Surfaces within workspace
|
|
for (const pane of ws.panes) {
|
|
for (const surface of pane.surfaces) {
|
|
const item = document.createElement('div');
|
|
item.className = 'surface-item';
|
|
if (surface.ref === this.activeSurface) {
|
|
item.classList.add('active');
|
|
}
|
|
|
|
const titleRaw = surface.title || surface.ref;
|
|
const status = this._parseStatus(titleRaw);
|
|
|
|
const dot = document.createElement('span');
|
|
dot.className = 'surface-status';
|
|
if (status.state === 'working') dot.classList.add('working');
|
|
else if (status.state === 'idle') dot.classList.add('idle');
|
|
|
|
const label = document.createElement('span');
|
|
label.className = 'ws-label';
|
|
label.textContent = status.title;
|
|
|
|
item.appendChild(dot);
|
|
item.appendChild(label);
|
|
|
|
item.addEventListener('click', () => {
|
|
this.onSelectSurface(ws.ref, surface.ref);
|
|
this.setActiveSurface(surface.ref);
|
|
if (!this.isDesktop()) this.close();
|
|
});
|
|
|
|
this.tree.appendChild(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_parseStatus(title) {
|
|
// Working: Braille spinner (U+2800-U+28FF), ⏺ (U+23FA), ● (U+25CF), ⬤ (U+2B24)
|
|
const workingRe = /^[\u2800-\u28FF\u23FA\u25CF\u2B24]\s*/;
|
|
// Idle/waiting: dingbats range — ✳ ✻ ✢ ✦ etc.
|
|
const idleRe = /^[\u2700-\u27BF]\s*/;
|
|
|
|
if (workingRe.test(title)) {
|
|
return { state: 'working', title: title.replace(workingRe, '') };
|
|
}
|
|
if (idleRe.test(title)) {
|
|
return { state: 'idle', title: title.replace(idleRe, '') };
|
|
}
|
|
return { state: null, title };
|
|
}
|
|
}
|
|
|
|
window.Sidebar = Sidebar;
|