91 lines
5.3 KiB
JavaScript
91 lines
5.3 KiB
JavaScript
// 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');
|
|
})();
|