Use URL-based routing instead of local state

This commit is contained in:
Chris Wanstrath 2026-02-26 19:43:18 -08:00
parent 68274d8651
commit 45b1903e6b
5 changed files with 25 additions and 12 deletions

View File

@ -1,7 +1,7 @@
import { useEffect } from 'hono/jsx' import { useEffect } from 'hono/jsx'
import { openAppSelectorModal } from '../modals' import { openAppSelectorModal } from '../modals'
import { navigate } from '../router' import { navigate } from '../router'
import { dashboardTab, isNarrow, setDashboardTab } from '../state' import { dashboardTab, isNarrow } from '../state'
import { import {
AppSelectorChevron, AppSelectorChevron,
DashboardContainer, DashboardContainer,
@ -30,8 +30,7 @@ export function DashboardLanding({ render }: { render: () => void }) {
} }
const switchTab = (tab: typeof dashboardTab) => { const switchTab = (tab: typeof dashboardTab) => {
setDashboardTab(tab) navigate(tab === 'urls' ? '/' : `/${tab}`)
render()
if (tab === 'logs') scrollLogsToBottom() if (tab === 'logs') scrollLogsToBottom()
} }

View File

@ -1,5 +1,6 @@
import type { App } from '../../shared/types' import type { App } from '../../shared/types'
import { apps, getSelectedTab, setSelectedTab } from '../state' import { navigate } from '../router'
import { apps, getSelectedTab } from '../state'
import { Tab, TabBar } from '../styles' import { Tab, TabBar } from '../styles'
import { resetToolIframe } from '../tool-iframes' import { resetToolIframe } from '../tool-iframes'
@ -12,8 +13,7 @@ export function Nav({ app, render }: { app: App; render: () => void }) {
resetToolIframe(tab, app.name) resetToolIframe(tab, app.name)
return return
} }
setSelectedTab(app.name, tab) navigate(tab === 'overview' ? `/app/${app.name}` : `/app/${app.name}/${tab}`)
render()
} }
// Find all tools // Find all tools

View File

@ -1,4 +1,4 @@
import { setCurrentView, setSelectedApp } from './state' import { setCurrentView, setDashboardTab, setSelectedApp, setSelectedTab } from './state'
let _render: () => void let _render: () => void
@ -31,14 +31,27 @@ function route() {
const path = location.pathname const path = location.pathname
if (path.startsWith('/app/')) { if (path.startsWith('/app/')) {
const name = decodeURIComponent(path.slice(5)) const rest = decodeURIComponent(path.slice(5))
const slashIdx = rest.indexOf('/')
const name = slashIdx === -1 ? rest : rest.slice(0, slashIdx)
const tab = slashIdx === -1 ? 'overview' : rest.slice(slashIdx + 1)
setSelectedApp(name) setSelectedApp(name)
setSelectedTab(name, tab)
setCurrentView('dashboard') setCurrentView('dashboard')
} else if (path === '/settings') { } else if (path === '/settings') {
setSelectedApp(null) setSelectedApp(null)
setCurrentView('settings') setCurrentView('settings')
} else if (path === '/logs') {
setSelectedApp(null)
setDashboardTab('logs')
setCurrentView('dashboard')
} else if (path === '/metrics') {
setSelectedApp(null)
setDashboardTab('metrics')
setCurrentView('dashboard')
} else { } else {
setSelectedApp(null) setSelectedApp(null)
setDashboardTab('urls')
setCurrentView('dashboard') setCurrentView('dashboard')
} }

View File

@ -7,19 +7,18 @@ export let currentView: 'dashboard' | 'settings' = 'dashboard'
export let isNarrow: boolean = window.matchMedia('(max-width: 768px)').matches export let isNarrow: boolean = window.matchMedia('(max-width: 768px)').matches
export let selectedApp: string | null = null export let selectedApp: string | null = null
export let sidebarCollapsed: boolean = localStorage.getItem('sidebarCollapsed') === 'true' export let sidebarCollapsed: boolean = localStorage.getItem('sidebarCollapsed') === 'true'
export let dashboardTab: DashboardTab = (localStorage.getItem('dashboardTab') as DashboardTab) || 'urls' export let dashboardTab: DashboardTab = 'urls'
export let sidebarSection: 'apps' | 'tools' = (localStorage.getItem('sidebarSection') as 'apps' | 'tools') || 'apps' export let sidebarSection: 'apps' | 'tools' = (localStorage.getItem('sidebarSection') as 'apps' | 'tools') || 'apps'
// Server state (from SSE) // Server state (from SSE)
export let apps: App[] = [] export let apps: App[] = []
// Tab state // Tab state
export let appTabs: Record<string, string> = JSON.parse(localStorage.getItem('appTabs') || '{}') export let appTabs: Record<string, string> = {}
// State setters // State setters
export function setDashboardTab(tab: DashboardTab) { export function setDashboardTab(tab: DashboardTab) {
dashboardTab = tab dashboardTab = tab
localStorage.setItem('dashboardTab', tab)
} }
export function setCurrentView(view: 'dashboard' | 'settings') { export function setCurrentView(view: 'dashboard' | 'settings') {
@ -54,5 +53,4 @@ export const getSelectedTab = (appName: string | null) =>
export function setSelectedTab(appName: string | null, tab: string) { export function setSelectedTab(appName: string | null, tab: string) {
if (!appName) return if (!appName) return
appTabs[appName] = tab appTabs[appName] = tab
localStorage.setItem('appTabs', JSON.stringify(appTabs))
} }

View File

@ -115,7 +115,10 @@ app.get('/dist/:file', async c => {
}) })
// SPA routes — serve the shell for all client-side paths // SPA routes — serve the shell for all client-side paths
app.get('/app/:name/:tab', c => c.html(<Shell />))
app.get('/app/:name', c => c.html(<Shell />)) app.get('/app/:name', c => c.html(<Shell />))
app.get('/logs', c => c.html(<Shell />))
app.get('/metrics', c => c.html(<Shell />))
app.get('/settings', c => c.html(<Shell />)) app.get('/settings', c => c.html(<Shell />))
cleanupStalePublishers() cleanupStalePublishers()