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', 'energy', 'network', '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 === 'energy' && app.historyEnergyTargets?.length) params.set('targets', app.historyEnergyTargets.join(',')); if (tab === 'energy' && app.historyEnergyInterval !== 'daily') params.set('interval', app.historyEnergyInterval); if (tab === 'energy' && app.historyEnergyCompare !== 'none') params.set('compare', app.historyEnergyCompare); if (tab === 'network' && app.historyNetworkTarget !== 'all') params.set('target', app.historyNetworkTarget); 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'; if (app.historyTab === 'energy') { const targetParam = params.get('targets') || params.get('device') || ''; app.historyEnergyTargets = targetParam ? targetParam.split(',').filter(Boolean).slice(0, 8) : (app.historyEnergyTargets || []); app.historyEnergyDevice = app.historyEnergyTargets[0] || ''; if (['hourly', 'daily', 'weekly', 'monthly'].includes(params.get('interval'))) app.historyEnergyInterval = params.get('interval'); if (['none', 'previous_day', 'previous_period', 'previous_year'].includes(params.get('compare'))) app.historyEnergyCompare = params.get('compare'); } app.historyNetworkTarget = params.get('target') || 'all'; app.historySensor = params.get('sensor') || 'all'; const hours = params.get('hours'); const validHours = hours && ['6', '24', '168', '720', '2160', '8760'].includes(hours) ? hours : null; if (app.historyTab === 'energy') { app.historyEnergyHours = validHours || app.historyEnergyHours || '720'; if ($('#historyHours')) $('#historyHours').value = app.historyEnergyHours; } else if (validHours && $('#historyHours')) { $('#historyHours').value = validHours; } 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(); }