more testing
Build and Publish / Metadata (push) Successful in 2s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 1m0s

This commit is contained in:
mats committed 2026-09-19 16:44:51 +02:00
1 parent 1aa264675e
commit 2da2649a2f
11 files changed
+293 -51

No files matched your search

+45 -3
View File
@@ -1,3 +1,6 @@
import { useCallback, useMemo } from "react"
import { useLocation } from "wouter"
// Route ↔ tab mapping. Tabs live in the URL (wouter) so deep links survive
// the OIDC login redirect at the ingress: users.k6n.net sends you back to
// exactly the view you requested, and URLs are shareable/bookmarkable.
@@ -12,14 +15,28 @@ export const TAB_PATHS: Record<string, string> = {
"/setup": "tab4",
"/trends": "tab5",
"/search": "tab6",
"/frontend": "tab7",
"/rum": "tab7",
"/frontend": "tab7",
}
/** Tab value for a location pathname, or null when the path isn't a tab. */
/** Splits "/issues/abc/trace/def" into base "/issues" and rest ["abc","trace","def"]. */
export function splitPath(path: string): { base: string; rest: string[] } {
const parts = path.split("/").filter(Boolean)
return { base: "/" + (parts[0] ?? ""), rest: parts.slice(1).map((p) => decodeURIComponent(p)) }
}
/** Builds a path below a tab base, e.g. joinPath("/issues", ["abc", "trace", "def"]). */
export function joinPath(base: string, rest: string[]): string {
return [base, ...rest.map((p) => encodeURIComponent(p))].join("/")
}
/**
* Tab value for a location pathname, or null when the path isn't a tab.
* Deeper paths ("/issues/<id>/trace/<id>") belong to their first segment's tab.
*/
export function tabForPath(path: string): string | null {
return TAB_PATHS[path] ?? null
return TAB_PATHS[splitPath(path).base] ?? null
}
/** Canonical path for a tab value, or null when the tab has no route. */
@@ -29,3 +46,28 @@ export function pathForTab(tab: string): string | null {
}
return null
}
/**
* Sub-route below the current tab: `segments` are the path parts after the
* tab base and `go` rewrites them, so selection state (which issue, which
* trace, ...) lives in the URL and survives reloads, back/forward and sharing.
*/
export function useSubRoute(): {
segments: string[]
go: (segments: string[], options?: { replace?: boolean }) => void
} {
const [location, navigate] = useLocation()
const { base, rest } = splitPath(location)
const key = rest.join("/")
// Stable identity per URL so effects can depend on `segments` directly.
// eslint-disable-next-line react-hooks/exhaustive-deps
const segments = useMemo(() => rest, [key])
const go = useCallback(
(next: string[], options?: { replace?: boolean }) => navigate(joinPath(base, next), options),
[base, navigate],
)
return { segments, go }
}
/** Short reference for showing an id to humans (full id stays in the URL). */
export const shortId = (id: string) => (id.length > 10 ? id.slice(0, 8) : id)