// Oberfläche der Erweiterung: Gerüst (Kopf, Inhalt, untere Navigation) und Weiterleitung zu den Ansichten. // Läuft im Popup, in der Seitenleiste (?mode=panel) und im eigenen Fenster (?mode=window). import { h, clear, icon, toast, errText } from './lib/dom.js'; import { getSession, getSettings, logout } from './lib/core.js'; import { renderLogin } from './views/login.js'; import { renderHome } from './views/home.js'; import { renderTickets } from './views/tickets.js'; import { renderTicket } from './views/ticket.js'; import { renderTasks } from './views/tasks.js'; import { renderCreate } from './views/create.js'; import { renderMore } from './views/more.js'; const app = document.getElementById('app'); const params = new URLSearchParams(location.search); const mode = params.get('mode') || (params.get('window') ? 'window' : 'popup'); if (mode !== 'popup') document.body.classList.add('wide'); const TABS = [['home', 'Start', 'home'], ['tickets', 'Tickets', 'list'], ['tasks', 'Aufgaben', 'check'], ['create', 'Neu', 'plus'], ['more', 'Mehr', 'menu']]; const VIEWS = { home: renderHome, tickets: renderTickets, tasks: renderTasks, create: renderCreate, more: renderMore, ticket: renderTicket }; const TITLES = { home: 'DeskHub', tickets: 'Tickets', tasks: 'Aufgaben', create: 'Neues Ticket', more: 'Mehr', ticket: 'Ticket' }; let session = null; let route = { view: 'home', params: {} }; const trail = []; let leaveHandlers = []; export function applyTheme(theme) { if (theme === 'light' || theme === 'dark') document.documentElement.dataset.theme = theme; else delete document.documentElement.dataset.theme; } async function runLeave() { const hs = leaveHandlers; leaveHandlers = []; for (const fn of hs) { try { await fn(); } catch { /* egal */ } } } const ctx = { get session() { return session; }, isPanel: mode === 'panel', isWindow: mode === 'window', applyTheme, go(view, p = {}) { if (view === 'ticket') trail.push(route); else trail.length = 0; navigate(view, p); }, back() { const prev = trail.pop() || { view: 'tickets', params: {} }; navigate(prev.view, prev.params); }, reload() { navigate(route.view, route.params); }, onLeave(fn) { leaveHandlers.push(fn); }, scrollToEnd() { const c = document.querySelector('.content'); if (c) c.scrollTop = c.scrollHeight; }, setStatusDot(status) { const d = document.getElementById('status-dot'); if (d) d.className = `statusdot ${status}`; }, loggedIn(s) { session = s; trail.length = 0; navigate('home'); }, logoutExpired() { session = null; renderLogin(app, ctx, { serverUrl: undefined, error: 'Die Anmeldung ist abgelaufen. Bitte neu anmelden.' }); }, async logout() { await runLeave(); await logout(); await chrome.runtime.sendMessage({ type: 'session-changed' }).catch(() => {}); session = null; renderLogin(app, ctx); }, async openPanel() { try { const win = await chrome.windows.getCurrent(); await chrome.sidePanel.open({ windowId: win.id }); if (mode === 'popup') window.close(); } catch (e) { toast(errText(e), 'err'); } }, async openWindow() { await chrome.windows.create({ url: chrome.runtime.getURL('app.html?mode=window'), type: 'popup', width: 460, height: 760 }); if (mode === 'popup') window.close(); }, }; async function navigate(view, p = {}) { await runLeave(); route = { view, params: p }; const isDetail = view === 'ticket'; const activeTab = isDetail ? (trail.at(-1)?.view === 'tickets' ? 'tickets' : trail.at(-1)?.view || 'tickets') : view; const content = h('div', { class: 'content' }); const body = h('div', { class: 'stack-host' }); content.append(body); const header = h('header', { class: 'top' }, isDetail ? h('button', { class: 'iconbtn', title: 'Zurück', onclick: () => ctx.back() }, icon('back', 18)) : h('img', { class: 'logo', src: 'icons/icon48.png', alt: '' }), h('div', { class: 'title' }, isDetail ? `#${p.number}` : TITLES[view] || 'DeskHub'), h('span', { id: 'status-dot', class: 'statusdot', title: 'Dein Status' }), h('button', { class: 'iconbtn', title: 'Neu laden', onclick: () => ctx.reload() }, icon('refresh', 16)), mode === 'popup' ? h('button', { class: 'iconbtn', title: 'In der Seitenleiste öffnen', onclick: () => ctx.openPanel() }, icon('panel', 16)) : null); const nav = h('nav', { class: 'nav', 'aria-label': 'Hauptmenü' }, TABS.map(([key, label, ic]) => h('button', { class: key === activeTab ? 'on' : '', onclick: () => ctx.go(key), 'aria-label': label }, icon(ic, 20), label))); app.replaceChildren(header, content, nav); chrome.runtime.sendMessage({ type: 'poll-now' }).catch(() => {}); // Zähler aktualisieren (zum Beispiel nach dem Öffnen eines Tickets) try { await VIEWS[view]?.(body, ctx, p); } catch (e) { body.replaceChildren(h('div', { class: 'msg err', role: 'alert' }, errText(e))); } } window.addEventListener('pagehide', () => { runLeave(); }); (async () => { applyTheme((await getSettings()).theme); session = await getSession(); if (!session) return renderLogin(app, ctx); chrome.runtime.sendMessage({ type: 'presence' }).catch(() => {}); const open = params.get('ticket'); if (open) { trail.push({ view: 'tickets', params: {} }); return navigate('ticket', { number: open }); } if (params.get('create')) return navigate('create'); if (params.get('q')) return navigate('tickets', { q: params.get('q') }); navigate(params.get('view') || 'home'); })();