import { A_Caller, A_Inject } from "@adaas/a-concept"; import { A_SignalBus } from "@adaas/a-utils/a-signal"; import { Are, AreEvent, AreNode } from "@adaas/are"; import { AreHTMLNode } from "src"; import { AppRegistry } from "../runtime/AppRegistry.fragment"; import { OSRoute } from "../signals/OSRoute.signal"; /** * Dock — the bottom app launcher. * * Shows the currently installed apps plus a Launchpad (+) button. Clicking an * app dispatches an {@link OSRoute} to focus it; the Dock itself also listens * for OSRoute so a freshly installed app reveals its icon and the running app's * running-dot lights up. * * Every available app gets a (hidden) button wired up front, so the engine * binds its `@click` handler once at mount. Reacting to a route signal is then * a cheap imperative DOM toggle — no subtree teardown, no re-binding. */ export class OsDock extends Are { protected _route: string = document.location.pathname || '/'; @Are.Template template( @A_Inject(A_Caller) node: AreNode, @A_Inject(AppRegistry) registry: AppRegistry, ) { node.setContent(this.build(registry)); } @Are.Signal(OSRoute) onRoute( @A_Inject(OSRoute) signal: OSRoute, @A_Inject(AppRegistry) registry: AppRegistry, ) { this._route = signal.path; const activeId = this._route.match(/^\/app\/([^/]+)/)?.[1]; let anyVisible = false; for (const app of registry.available()) { const btn = document.getElementById(`dock-app-${app.id}`); if (!btn) continue; if (registry.isInstalled(app.id)) { btn.hidden = false; anyVisible = true; } btn.classList.toggle('running', app.id === activeId); } const sep = document.getElementById('dock-sep'); if (sep) sep.hidden = !anyVisible; const lp = document.getElementById('dock-launchpad'); if (lp) lp.classList.toggle('running', this._route === '/launchpad'); } @Are.EventHandler open( @A_Inject(AreEvent) event: AreEvent, @A_Inject(A_SignalBus) bus: A_SignalBus, ) { (event.get('native') as MouseEvent)?.preventDefault(); const id = event.get('args')?.[0] as string; if (!id) return; const path = `/app/${id}`; history.pushState({}, '', path); bus.next(new OSRoute(path)); } @Are.EventHandler launchpad( @A_Inject(AreEvent) event: AreEvent, @A_Inject(A_SignalBus) bus: A_SignalBus, ) { (event.get('native') as MouseEvent)?.preventDefault(); history.pushState({}, '', '/launchpad'); bus.next(new OSRoute('/launchpad')); } protected build(registry: AppRegistry): string { const activeId = this._route.match(/^\/app\/([^/]+)/)?.[1]; const apps = registry.available().map(app => { const installed = registry.isInstalled(app.id); const running = app.id === activeId ? 'running' : ''; return ` `; }).join(''); const anyInstalled = registry.installed().length > 0; const launchpadActive = this._route === '/launchpad' ? 'running' : ''; return `