const VIEW_ROUTES = Object.freeze({ dashboard: '/dashboard', devices: '/devices', zones: '/zones', groups: '/groups', schedules: '/schedules', automations: '/automations', flows: '/flows', simulation: '/simulation', night: '/night-mode', homeassistant: '/home-assistant', settings: '/settings', logs: '/events', }); const ROUTE_VIEWS = Object.freeze(Object.fromEntries(Object.entries(VIEW_ROUTES).map(([view, path]) => [path, view]))); const HISTORY_TABS = Object.freeze(['overview', 'zones', 'devices', 'sensors', 'custom']); let routerInitialized = false; function currentHistoryPath() { const tab = HISTORY_TABS.includes(app.historyTab) ? app.historyTab : 'overview'; const params = new URLSearchParams(); const hours = $('#historyHours')?.value || new URLSearchParams(location.search).get('hours') || '24'; if (hours !== '24') params.set('hours', hours); if (tab === 'zones' && app.historyZone !== 'all') params.set('zone', app.historyZone); if (tab === 'devices' && app.historyDevice !== 'all') params.set('device', app.historyDevice); if (tab === 'sensors' && app.historySensor !== 'all') params.set('sensor', app.historySensor); if (tab === 'custom' && app.customChartSeries.length) params.set('chart', encodeChartSpec(app.customChartSeries)); const query = params.toString(); return `/history/${tab}${query ? `?${query}` : ''}`; } function pathForView(name) { return name === 'history' ? currentHistoryPath() : (VIEW_ROUTES[name] || VIEW_ROUTES.dashboard); } function appRelativePath(pathname = location.pathname) { if (!APP_BASE) return pathname || '/'; if (pathname === APP_BASE) return '/'; if (pathname.startsWith(`${APP_BASE}/`)) return pathname.slice(APP_BASE.length) || '/'; return pathname || '/'; } function updateBrowserUrl(path, replace = false) { const normalized = path.startsWith('/') ? path : `/${path}`; const target = `${APP_BASE}${normalized}` || normalized; const current = `${location.pathname}${location.search}`; if (current === target) return; history[replace ? 'replaceState' : 'pushState']({}, '', target); } function restoreCurrentRoute(previousView) { updateBrowserUrl(previousView === 'history' ? currentHistoryPath() : pathForView(previousView), true); } function applyRouteFromLocation() { const routePath = appRelativePath(); const parts = routePath.split('/').filter(Boolean); const first = parts[0] || 'dashboard'; const params = new URLSearchParams(location.search); const previousView = app.currentView; app.standaloneSimulation = first === 'simulation' && params.get('standalone') === '1'; document.body.classList.toggle('simulation-standalone', app.standaloneSimulation); if (first === 'simulation') { app.simulationScope = ['units', 'groups'].includes(params.get('scope')) ? params.get('scope') : 'units'; app.simulationTarget = params.get('target') || 'all'; } if (first === 'history') { app.historyTab = HISTORY_TABS.includes(parts[1]) ? parts[1] : 'overview'; app.historyZone = params.get('zone') || 'all'; app.historyDevice = params.get('device') || 'all'; app.historySensor = params.get('sensor') || 'all'; const hours = params.get('hours'); if (hours && ['6', '24', '168', '720', '2160', '8760'].includes(hours) && $('#historyHours')) $('#historyHours').value = hours; if (app.historyTab === 'custom' && params.get('chart')) app.customChartSeries = decodeChartSpec(params.get('chart')); if (!showView('history', { push: false, scroll: false })) restoreCurrentRoute(previousView); return; } const view = first === 'index.html' ? 'dashboard' : ROUTE_VIEWS[`/${first}`]; if (!showView(view || 'dashboard', { push: false, scroll: false })) { restoreCurrentRoute(previousView); return; } if (first === 'flows') { if (parts[1]) requestAnimationFrame(() => openFlowEditor(parts[1] === 'new' ? '' : parts[1], { push: false })); else if (!$('#flowEditor')?.hidden) closeFlowEditor({ push: false, force: true }); } if (!parts.length || first === 'index.html') updateBrowserUrl(VIEW_ROUTES.dashboard, true); } function initRouter() { if (!routerInitialized) { window.addEventListener('popstate', applyRouteFromLocation); routerInitialized = true; } applyRouteFromLocation(); }