export const pageWindowId = 'page'; export interface TerminalWindow { id: string; startupCommand: string; } export interface ViewerImage { src: string; alt: string; } export interface ImageViewerWindow { id: string; images: ViewerImage[]; currentIndex: number; } export interface SystemMonitorWindow { id: string; } export interface FloatingRect { x: number; y: number; width: number; height: number; } class WindowManager { terminals = $state([]); imageViewers = $state([]); systemMonitors = $state([]); floatingRects = $state>({}); focusedId = $state(pageWindowId); maximizedId = $state(null); launcherOpen = $state(false); private nextTerminalNumber = 1; private nextImageViewerNumber = 1; private nextSystemMonitorNumber = 1; get extraWindowIds(): string[] { return [ ...this.terminals.map((terminal) => terminal.id), ...this.imageViewers.map((viewer) => viewer.id), ...this.systemMonitors.map((monitor) => monitor.id) ]; } isFloating(id: string): boolean { return id in this.floatingRects; } openTerminal(startupCommand = '') { const terminal = { id: `kitty-${this.nextTerminalNumber++}`, startupCommand }; this.terminals.push(terminal); this.focus(terminal.id); } openImageViewer(images: ViewerImage[], currentIndex: number) { const viewer = { id: `imv-${this.nextImageViewerNumber++}`, images, currentIndex }; this.imageViewers.push(viewer); this.focus(viewer.id); } openSystemMonitor() { const monitor = { id: `btop-${this.nextSystemMonitorNumber++}` }; this.systemMonitors.push(monitor); this.focus(monitor.id); } closeWindow(id: string) { this.terminals = this.terminals.filter((terminal) => terminal.id !== id); this.imageViewers = this.imageViewers.filter((viewer) => viewer.id !== id); this.systemMonitors = this.systemMonitors.filter((monitor) => monitor.id !== id); delete this.floatingRects[id]; if (this.maximizedId === id) this.maximizedId = null; if (this.focusedId === id) this.focusedId = this.extraWindowIds.at(-1) ?? pageWindowId; } closeAllTerminals() { this.terminals = []; if (this.maximizedId?.startsWith('kitty-')) this.maximizedId = null; if (this.focusedId.startsWith('kitty-')) this.focusedId = this.extraWindowIds.at(-1) ?? pageWindowId; } focus(id: string) { this.focusedId = id; if (this.maximizedId && this.maximizedId !== id) this.maximizedId = null; } toggleFloating(id: string) { if (this.isFloating(id)) delete this.floatingRects[id]; else this.floatingRects[id] = null; if (this.maximizedId === id) this.maximizedId = null; this.focusedId = id; } setFloatingRect(id: string, rect: FloatingRect) { if (this.isFloating(id)) this.floatingRects[id] = rect; } toggleMaximize(id: string) { this.maximizedId = this.maximizedId === id ? null : id; this.focusedId = id; } cycleFocus(openWindowIds: string[], step: number) { if (openWindowIds.length === 0) return; const index = openWindowIds.indexOf(this.focusedId); const nextIndex = (index + step + openWindowIds.length) % openWindowIds.length; this.focus(openWindowIds[nextIndex]); } } export const windowManager = new WindowManager();