import MeridianWM from './wm.js'; import State from './state.js'; import Bus from './bus.js'; const Registry = (() => { const APPS = {}; let _launcherOpen = false; function registerApp(def) { if (def.id) { APPS[def.id] = def; } } function unregisterApp(id) { delete APPS[id]; } function getApp(id) { return APPS[id] || null; } function launchApp(id) { const def = APPS[id]; if (!def) { console.warn(`App ${id} not registered`); return; } const title = def.title || id; const w = def.width || 640; const h = def.height || 480; // Create window with a render-callback; wm will mount container then call it. const win = MeridianWM.openWindow({ app: id, title, w, h, content: (container) => { // Tag container for per-app CSS scoping container.setAttribute('data-app', id); const api = { state: State, bus: Bus, close: () => MeridianWM.closeWindow(win && win.id), setTitle: (t) => { const el = document.getElementById(win && win.id); if (el) { const titleEl = el.querySelector('.m-window-title'); if (titleEl) titleEl.textContent = t; } }, }; try { def.render(container, api); } catch (e) { container.textContent = `Error loading ${id}: ${e.message}`; console.error(`[${id}] render error`, e); } }, }); closeLauncher(); } function getRegisteredApps() { return Object.values(APPS); } function toggleLauncher() { _launcherOpen = !_launcherOpen; const popover = document.getElementById('launcher-popover'); const btn = document.getElementById('launcher'); if (popover) { if (_launcherOpen) { renderLauncherApps(popover); popover.classList.remove('hidden'); } else { popover.classList.add('hidden'); } } if (btn) { btn.classList.toggle('active', _launcherOpen); } return _launcherOpen; } function closeLauncher() { _launcherOpen = false; const popover = document.getElementById('launcher-popover'); const btn = document.getElementById('launcher'); if (popover) popover.classList.add('hidden'); if (btn) btn.classList.remove('active'); } function renderLauncherApps(popover) { const apps = getRegisteredApps(); popover.innerHTML = ''; if (apps.length === 0) { popover.innerHTML = '
No apps registered
'; return; } for (const app of apps) { const btn = document.createElement('button'); btn.className = 'launcher-app-btn'; btn.setAttribute('aria-label', `Launch ${app.title || app.id}`); btn.innerHTML = `
${app.icon || '◆'}
${escapeHtml(app.title || app.id)}
`; btn.addEventListener('click', () => launchApp(app.id)); popover.appendChild(btn); } } function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function init() { const btn = document.getElementById('launcher'); if (btn) { btn.addEventListener('click', toggleLauncher); } // Close launcher when clicking elsewhere document.addEventListener('mousedown', (e) => { if (_launcherOpen && !e.target.closest('#launcher') && !e.target.closest('#launcher-popover')) { closeLauncher(); } }); } return { APPS, registerApp, unregisterApp, getApp, launchApp, getRegisteredApps, toggleLauncher, init }; })(); export default Registry; export { Registry };