From 2db399a766e6715e1b73bc362fbb901fc62f0d1b Mon Sep 17 00:00:00 2001 From: Snow Lee Date: Mon, 13 Jul 2026 10:31:01 -0700 Subject: [PATCH] =?UTF-8?q?feat(webapp):=20[phase=202]=20file=20browsing?= =?UTF-8?q?=20=E2=80=94=20tree,=20listings,=20files,=20upload,=20share,=20?= =?UTF-8?q?palette?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FileTree with fold state, lone-root auto-open, reveal-on-deep-link; folder listings with heat dots and the journals-backed change feed - FileView: markdown (HTML transformed BEFORE render, link clicks delegated — React re-applies dangerouslySetInnerHTML markup on unrelated updates, so post-commit DOM patching loses handlers), images, text, download card - breadcrumbs, per-route scroll restoration (location.key memo) - topbar actions: share dialog (mint/copy/open/revoke), history/upload/ download buttons, ⋯ overflow menu; upload via upload/init direct or relay path, then tree refresh + open - ⌘K command palette: fuzzy files/projects/actions with stemming - e2e: 11 new browse specs (22 total green in ~12s); helpers cache one session cookie per identity to stay under the 10/min auth rate limit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt --- docs/react-migration-prd.md | 33 +- internal/webapp/frontend/e2e/browse.spec.ts | 129 ++++++ internal/webapp/frontend/e2e/helpers.ts | 23 +- internal/webapp/frontend/e2e/hub.spec.ts | 9 +- internal/webapp/frontend/src/api/http.ts | 12 + internal/webapp/frontend/src/api/types.ts | 67 +++ internal/webapp/frontend/src/apps/Browser.tsx | 382 ++++++++++++++++++ internal/webapp/frontend/src/apps/HubApp.tsx | 29 +- .../webapp/frontend/src/apps/VolumeApp.tsx | 29 +- .../frontend/src/components/Breadcrumbs.tsx | 33 ++ .../frontend/src/components/FileTree.tsx | 95 +++++ .../frontend/src/components/FileView.tsx | 155 +++++++ .../frontend/src/components/FolderListing.tsx | 123 ++++++ .../frontend/src/components/HistoryRow.tsx | 89 ++++ .../frontend/src/components/Palette.tsx | 167 ++++++++ .../frontend/src/components/ShareDialog.tsx | 59 +++ .../webapp/frontend/src/components/shell.tsx | 9 +- .../webapp/frontend/src/hooks/useBrowse.ts | 101 +++++ internal/webapp/frontend/src/upload.ts | 90 +++++ internal/webapp/frontend/src/util.ts | 41 ++ .../webapp/static/assets/index-BoqNtp9Y.js | 11 - .../webapp/static/assets/index-DAT78awr.js | 11 + internal/webapp/static/index.html | 2 +- 23 files changed, 1646 insertions(+), 53 deletions(-) create mode 100644 internal/webapp/frontend/e2e/browse.spec.ts create mode 100644 internal/webapp/frontend/src/apps/Browser.tsx create mode 100644 internal/webapp/frontend/src/components/Breadcrumbs.tsx create mode 100644 internal/webapp/frontend/src/components/FileTree.tsx create mode 100644 internal/webapp/frontend/src/components/FileView.tsx create mode 100644 internal/webapp/frontend/src/components/FolderListing.tsx create mode 100644 internal/webapp/frontend/src/components/HistoryRow.tsx create mode 100644 internal/webapp/frontend/src/components/Palette.tsx create mode 100644 internal/webapp/frontend/src/components/ShareDialog.tsx create mode 100644 internal/webapp/frontend/src/hooks/useBrowse.ts create mode 100644 internal/webapp/frontend/src/upload.ts create mode 100644 internal/webapp/frontend/src/util.ts delete mode 100644 internal/webapp/static/assets/index-BoqNtp9Y.js create mode 100644 internal/webapp/static/assets/index-DAT78awr.js diff --git a/docs/react-migration-prd.md b/docs/react-migration-prd.md index 0e7e5db..caa8d16 100644 --- a/docs/react-migration-prd.md +++ b/docs/react-migration-prd.md @@ -137,16 +137,27 @@ refresh; invalidate after uploads/renames/admin actions — mirror today's or the unknown-id fallback bounces off the stale list. ### Phase 2 — file browsing (long pole) -- [ ] Tree with expansion persistence, active marking, reveal-in-tree. -- [ ] Folder listing incl. heat dots (members) + folder history strip. -- [ ] File view: server-rendered markdown injected; wikilinks + relative - links rewritten (`fixLinks` semantics); meta/provenance line. -- [ ] Breadcrumbs; per-route scroll restoration. -- [ ] Download + raw file view; share button state; share dialog. -- [ ] Drag-drop upload (presign and relay paths) with refresh after commit. -- [ ] Command palette (⌘K: file names, projects, actions — port the - palette overlay from the old shell) and the topbar overflow menu - (`more-btn`/`more-menu`) for narrow viewports. +- [x] Tree with expansion persistence, active marking, reveal-in-tree + (deep links unfold the way to the file — e2e covered). +- [x] Folder listing incl. heat dots (members) + folder history strip. +- [x] File view: server-rendered markdown; wikilinks + relative links; + meta/provenance line. LESSON (do not regress): never patch the + dangerouslySetInnerHTML subtree after commit (the classic fixLinks + approach) — React re-applies the markup on unrelated updates and + silently discards DOM patches. Instead: transform the HTML string + before rendering (img src, target=_blank) and handle link clicks by + delegation on the container (`FileView.tsx`). +- [x] Breadcrumbs; per-route scroll restoration (location.key memo, + restore on POP, re-applied as async sections grow). +- [x] Download + raw file view; share button state; share dialog (e2e: + mint → public fetch → revoke → 404). +- [x] Upload via the topbar button + file picker (direct/relay per + upload/init) with tree refresh + open after commit. +- [x] Command palette (⌘K fuzzy files/projects/actions) and the topbar + overflow menu. + Harness note: helpers.login caches one session cookie per identity — + the server rate-limits credential POSTs (10/min/IP) and per-spec + form logins trip it with flaky timeouts. ### Phase 3 — project home, insights, history - [ ] Project home at `/`: connect guide (3 tabs: "Claude Code & @@ -226,7 +237,7 @@ file path; reload on `/insights`. - [x] Phase 0 - [x] Phase 1 -- [ ] Phase 2 +- [x] Phase 2 - [ ] Phase 3 - [ ] Phase 4 - [ ] Phase 5 diff --git a/internal/webapp/frontend/e2e/browse.spec.ts b/internal/webapp/frontend/e2e/browse.spec.ts new file mode 100644 index 0000000..bb07420 --- /dev/null +++ b/internal/webapp/frontend/e2e/browse.spec.ts @@ -0,0 +1,129 @@ +import { test, expect } from "@playwright/test"; +import { login, wikiId } from "./helpers"; + +// Phase 2: tree, folder listings (heat dots + change feed), file views +// (markdown/wikilinks/images), breadcrumbs, upload, share, palette. + +test("tree lists the seeded folders and files", async ({ page }) => { + await login(page); + await expect(page.locator('#tree .row[data-path="notes"]')).toBeVisible(); + await expect(page.locator('#tree .row[data-path="index.md"]')).toBeVisible(); + await expect(page.locator('#tree .row[data-path="guide.md"]')).toBeVisible(); +}); + +test("markdown file: rendered content, crumb, meta, download + share buttons", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.click('#tree .row[data-path="index.md"]'); + await page.waitForURL(`/${pid}/index.md`); + await expect(page.locator("#content h1")).toHaveText("Wiki"); + await expect(page.locator("#crumb")).toContainText("index.md"); + await expect(page.locator("#meta")).toContainText("alice@x.io"); + await expect(page.locator("#download")).toBeVisible(); + await expect(page.locator("#share-btn")).toBeVisible(); +}); + +test("wikilink navigates to the target file", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.goto(`/${pid}/index.md`); + await page.click('#content a:has-text("guide")'); + await page.waitForURL(`/${pid}/guide.md`); + await expect(page.locator("#content")).toContainText("Second version"); +}); + +test("folder listing: counts, change feed, heat dot on a read file", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.click('#tree .row[data-path="notes"]'); + await page.waitForURL(`/${pid}/notes`); + await expect(page.locator(".dl-title")).toContainText("notes"); + await expect(page.locator(".dl-sub")).toContainText("1 folder"); + await expect(page.locator(".dl-sub")).toContainText("1 file"); + await expect(page.locator(".dl-history .dl-h3")).toHaveText("Recent changes"); + await expect(page.locator(".dl-history .hentry").first()).toBeVisible(); + // notes/readme.md has seeded agent reads → a heat dot on its row + await expect(page.locator('.dl-row[title="notes/readme.md"] .heatdot')).toBeVisible(); +}); + +test("image file renders an ", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.goto(`/${pid}/assets/logo.png`); + await expect(page.locator("#content img")).toBeVisible(); +}); + +test("breadcrumb ancestor opens that folder", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.goto(`/${pid}/notes/deep/topic.md`); + await expect(page.locator("#content h1")).toHaveText("Topic"); + await page.click('#crumb .crumb-seg[title="notes"]'); + await page.waitForURL(`/${pid}/notes`); + await expect(page.locator(".dl-title")).toContainText("notes"); +}); + +test("deep file link resolves after a hard reload", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.goto(`/${pid}/notes/readme.md`); + await expect(page.locator("#content h1")).toHaveText("Notes"); + await page.reload(); + await expect(page.locator("#content h1")).toHaveText("Notes"); + // The tree unfolds the way to the deep-linked file + await expect(page.locator('#tree .row[data-path="notes/readme.md"]')).toBeVisible(); +}); + +test("back/forward walks file → folder → file", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.goto(`/${pid}/index.md`); + await page.click('#tree .row[data-path="notes"]'); + await page.waitForURL(`/${pid}/notes`); + await page.goBack(); + await expect(page.locator("#content h1")).toHaveText("Wiki"); + await page.goForward(); + await expect(page.locator(".dl-title")).toContainText("notes"); +}); + +test("palette (⌘K) fuzzy-jumps to a file", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.keyboard.press("ControlOrMeta+k"); + await expect(page.locator("#palette")).toBeVisible(); + await page.fill("#palette-input", "topic"); + await page.keyboard.press("Enter"); + await page.waitForURL(`/${pid}/notes/deep/topic.md`); + await expect(page.locator("#content h1")).toHaveText("Topic"); +}); + +test("share mints a public link that serves the file, revoke kills it", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.goto(`/${pid}/guide.md`); + await page.click("#share-btn"); + const url = await page.locator(".modal-url").textContent(); + expect(url).toContain("/s/"); + const publicRes = await page.request.get(url!); + expect(publicRes.status()).toBe(200); + expect(await publicRes.text()).toContain("Second version"); + await page.click(".modal .ai-del"); // revoke + await expect(page.locator("#toast")).toContainText("revoked"); + const gone = await page.request.get(url!); + expect(gone.status()).toBe(404); +}); + +test("upload into the selected folder, then the file opens", async ({ page }) => { + await login(page); + const pid = await wikiId(page); + await page.goto(`/${pid}/notes`); + await page.locator("#upload-btn").waitFor(); + await page.setInputFiles('input[type="file"]', { + name: "dropped.md", + mimeType: "text/markdown", + buffer: Buffer.from("# Dropped\n\nUploaded through the browser.\n"), + }); + await page.waitForURL(`/${pid}/notes/dropped.md`); + await expect(page.locator("#content h1")).toHaveText("Dropped"); + await expect(page.locator('#tree .row[data-path="notes/dropped.md"]')).toBeVisible(); +}); diff --git a/internal/webapp/frontend/e2e/helpers.ts b/internal/webapp/frontend/e2e/helpers.ts index 4cd46f6..b291a3b 100644 --- a/internal/webapp/frontend/e2e/helpers.ts +++ b/internal/webapp/frontend/e2e/helpers.ts @@ -4,13 +4,32 @@ export const ADMIN = "e2e@example.com"; export const MEMBER = "member@example.com"; export const PASSWORD = "e2e-pass-1"; -// Signs in through the server-rendered /auth pages and waits for the SPA -// shell to render. +// One real form login per identity per run, then the session cookie is +// reused — the server rate-limits credential POSTs to 10/min per IP, which +// a fresh login in every spec would trip. +type Cookies = Awaited["cookies"]>>; +const sessions = new Map(); + +// Signs in (through the server-rendered /auth pages on first use) and waits +// for the SPA shell to render. export async function login(page: Page, email: string = ADMIN) { + const cached = sessions.get(email); + if (cached) { + await page.context().addCookies(cached); + await page.goto("/"); + await page.waitForSelector("#sidebar"); + return; + } await page.goto("/"); await page.waitForURL(/auth\/login/); await page.fill('input[name="email"]', email); await page.fill('input[name="password"]', PASSWORD); await page.click("form button"); await page.waitForSelector("#sidebar"); + sessions.set(email, await page.context().cookies()); +} + +export async function wikiId(page: Page): Promise { + const out = await (await page.request.get("/api/projects")).json(); + return out.projects.find((p: { name: string }) => p.name === "wiki").id; } diff --git a/internal/webapp/frontend/e2e/hub.spec.ts b/internal/webapp/frontend/e2e/hub.spec.ts index fcdef90..0c350f0 100644 --- a/internal/webapp/frontend/e2e/hub.spec.ts +++ b/internal/webapp/frontend/e2e/hub.spec.ts @@ -1,15 +1,10 @@ -import { test, expect, Page } from "@playwright/test"; -import { login, MEMBER, PASSWORD } from "./helpers"; +import { test, expect } from "@playwright/test"; +import { login, wikiId, MEMBER, PASSWORD } from "./helpers"; // Phase 1: shell, session flags, project list/selection, routing, empty // state, invite accept. Mutating specs (project creation) run last — // specs share one seeded hub per run. -async function wikiId(page: Page): Promise { - const out = await (await page.request.get("/api/projects")).json(); - return out.projects.find((p: { name: string }) => p.name === "wiki").id; -} - test("landing selects the first project and rewrites the URL", async ({ page }) => { await login(page); const pid = await wikiId(page); diff --git a/internal/webapp/frontend/src/api/http.ts b/internal/webapp/frontend/src/api/http.ts index b214eca..2d858c6 100644 --- a/internal/webapp/frontend/src/api/http.ts +++ b/internal/webapp/frontend/src/api/http.ts @@ -16,6 +16,18 @@ export async function getJSON(url: string): Promise { return r.json(); } +/* fetch wrapper for methods without a body-returning helper */ +export async function api(method: string, url: string, body?: unknown): Promise { + const opt: RequestInit = { method }; + if (body !== undefined) { + opt.headers = { "Content-Type": "application/json" }; + opt.body = JSON.stringify(body); + } + const r = await fetch(url, opt); + if (!r.ok) throw new Error(await r.text()); + return r.status === 204 ? ({} as T) : r.json(); +} + export async function postJSON(url: string, body?: unknown): Promise { const r = await fetch(url, { method: "POST", diff --git a/internal/webapp/frontend/src/api/types.ts b/internal/webapp/frontend/src/api/types.ts index 4b546e9..b91bd2b 100644 --- a/internal/webapp/frontend/src/api/types.ts +++ b/internal/webapp/frontend/src/api/types.ts @@ -65,3 +65,70 @@ export interface InviteAccepted { export interface PendingList { pending: Array<{ id: string; email: string; name: string }>; } + +// GET .../tree (handleTree → Node, server.go) +export interface Node { + name: string; + path: string; + dir?: boolean; + size?: number; + time?: string; + author?: string; + device?: string; + children?: Node[]; +} + +// GET .../render (handleRender, server.go) +export interface RenderDoc { + path: string; + html: string; + size: number; + time?: string; + author?: string; + device?: string; +} + +// GET .../heat (handleHeat, reads.go) — counts only, never who. +export interface HeatEntry { + human?: number; + agent?: number; + share?: number; + readers?: number; + last?: string; +} +export type HeatMap = Record; + +// GET .../history (HistoryEntry, history.go) +export interface DeviceInfo { + id?: string; + name?: string; + os?: string; + ip?: string; +} +export interface HistoryEntry { + time: string; + kind: string; // add | edit | delete (older servers: raw "put") + path: string; + size?: number; + blob?: string; + user?: string; + user_name?: string; + author?: string; + device: DeviceInfo; + note?: string; +} + +// POST .../shares (handleShareCreate, shares.go) +export interface ShareCreated { + token: string; + url: string; +} + +// POST .../upload/init (handleUploadInit, upload.go) +export interface UploadPlan { + mode: "direct" | "server"; + exists?: boolean; + url?: string; + method?: string; + headers?: Record; +} diff --git a/internal/webapp/frontend/src/apps/Browser.tsx b/internal/webapp/frontend/src/apps/Browser.tsx new file mode 100644 index 0000000..bb09295 --- /dev/null +++ b/internal/webapp/frontend/src/apps/Browser.tsx @@ -0,0 +1,382 @@ +import { + useCallback, + useEffect, + useRef, + useState, + type ReactNode, +} from "react"; +import { useLocation, useNavigate, useNavigationType } from "react-router-dom"; +import { useQueryClient } from "@tanstack/react-query"; +import type { Project, ServerConfig } from "../api/types"; +import { useHeat, useTree } from "../hooks/useBrowse"; +import { urlForPath, urlForView, type Route } from "../router"; +import { uploadFile } from "../upload"; +import { copyText } from "../util"; +import { toast } from "../toast"; +import { AppShell, Icon, Topbar, closeSidebarOnMobile } from "../components/shell"; +import { FileTree, ancestorsOf } from "../components/FileTree"; +import { Breadcrumbs } from "../components/Breadcrumbs"; +import { FolderListing } from "../components/FolderListing"; +import { FileView } from "../components/FileView"; +import { ShareDialog } from "../components/ShareDialog"; +import { Palette, type PaletteItem } from "../components/Palette"; + +// The browsing surface shared by hub projects and single-volume mode: the +// file tree, folder listings, file views, and every topbar action. Sidebar +// chrome (vault header, project nav, org bar) is injected by the caller; +// key this component by project id so tree state resets on project switch. +export default function Browser(props: { + config: ServerConfig; + apiBase: string; + route: Route; + hub: boolean; + project?: Project; + projects?: Project[]; + canInsights?: boolean; + sidebar: { vault: ReactNode; projectsNav?: ReactNode; orgBar?: ReactNode }; + home?: ReactNode; // hub landing view (project home); default prompt otherwise +}) { + const { config, apiBase, route, hub, project } = props; + const navigate = useNavigate(); + const location = useLocation(); + const navType = useNavigationType(); + const qc = useQueryClient(); + + const { tree, flatFiles, dirIndex, loaded } = useTree(apiBase, !hub || !!project); + const heatMap = useHeat(apiBase, hub && !!project && !!config.reads?.enabled); + + const path = route.path; + const isDir = !!path && dirIndex.has(path); + const isFile = !!path && loaded && !dirIndex.has(path); + const listingShowing = isDir && !route.view; + + /* ---- tree expansion ---- */ + const [expanded, setExpanded] = useState>(() => new Set()); + const firstLoad = useRef(true); + useEffect(() => { + // First render of the tree: every folder starts closed, except a lone + // root folder — opening it spares the user a single shut folder. + if (!tree || !firstLoad.current) return; + firstLoad.current = false; + const rootDirs = (tree.children || []).filter((c) => c.dir); + if (rootDirs.length === 1) setExpanded((s) => new Set(s).add(rootDirs[0].path)); + }, [tree]); + useEffect(() => { + // Opening any path (tree click, palette, wikilink, deep link) unfolds + // the way to it; a selected folder itself opens too. + if (!path || !loaded) return; + setExpanded((s) => { + const next = new Set(s); + for (const a of ancestorsOf(path)) next.add(a); + if (dirIndex.has(path)) next.add(path); + return next; + }); + const row = document.querySelector(`#tree .row[data-path="${CSS.escape(path)}"]`); + if (row) row.scrollIntoView({ block: "nearest" }); + }, [path, loaded, dirIndex]); + const onToggle = useCallback((p: string) => { + setExpanded((s) => { + const next = new Set(s); + if (next.has(p)) next.delete(p); + else next.add(p); + return next; + }); + }, []); + + /* ---- per-route scroll restoration ---- + Back/forward returns to where the reader was; fresh navigations start + at the top. Views call onRendered when their content lands (and again + when async sections grow), and we re-apply the target until it fits. */ + const contentRef = useRef(null); + const memo = useRef(new Map()); + const scrollGoal = useRef({ key: "", want: 0, attempts: 0 }); + useEffect(() => { + scrollGoal.current = { + key: location.key, + want: navType === "POP" ? (memo.current.get(location.key) ?? 0) : 0, + attempts: 0, + }; + }, [location.key, navType]); + const onRendered = useCallback(() => { + const c = contentRef.current; + const g = scrollGoal.current; + if (!c || g.key !== location.key || g.attempts >= 3) return; + g.attempts++; + c.scrollTo({ top: g.want, behavior: "instant" }); + }, [location.key]); + const onScroll = useCallback(() => { + if (contentRef.current) memo.current.set(location.key, contentRef.current.scrollTop); + }, [location.key]); + + /* ---- navigation ---- */ + const openPath = useCallback( + (p: string) => { + navigate(urlForPath(p, project?.id)); + closeSidebarOnMobile(); + }, + [navigate, project?.id], + ); + const openHistory = useCallback( + (target: string) => navigate(urlForView("history", project?.id, target)), + [navigate, project?.id], + ); + + /* ---- topbar state + actions ---- */ + const [meta, setMeta] = useState(""); + const [uploadStatus, setUploadStatus] = useState(""); + const [share, setShare] = useState<{ url: string; copied: boolean } | null>(null); + const [moreOpen, setMoreOpen] = useState(false); + const [paletteOpen, setPaletteOpen] = useState(false); + const uploadInput = useRef(null); + const downloadRef = useRef(null); + + const canShare = hub && !!project && isFile; + const canHistory = hub && !!project; + const canUpload = !!config.upload?.enabled && (!hub || !!project); + const canDownload = isFile; + const canMore = isFile || (hub && !!project && isDir); + const downloadURL = apiBase + "download?path=" + encodeURIComponent(path); + + const shareNow = useCallback(async () => { + // Shares are per-file; a selected folder has nothing to mint. + try { + const r = await fetch(apiBase + "shares", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ path }), + }); + if (!r.ok) throw new Error(await r.text()); + const s = await r.json(); + const copied = await copyText(s.url); + setShare({ url: s.url, copied }); + } catch (err) { + toast("Share failed: " + (err as Error).message, true); + } + }, [apiBase, path]); + + const historyNow = useCallback(() => { + if (!path) return openHistory(""); + openHistory(isDir ? path + "/" : path); + }, [path, isDir, openHistory]); + + const uploadNow = useCallback(() => uploadInput.current?.click(), []); + const onUploadPick = async () => { + const input = uploadInput.current!; + const file = input.files?.[0]; + input.value = ""; + if (!file) return; + // A selected folder receives the upload; a selected file means "next + // to it". + const dir = !path ? "" : isDir ? path : path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : ""; + const dest = dir ? dir + "/" + file.name : file.name; + try { + setUploadStatus(`Uploading ${dest}…`); + await uploadFile(apiBase, dest, file); + setUploadStatus(`Uploaded ${dest}`); + await qc.invalidateQueries({ queryKey: ["tree", apiBase] }); + openPath(dest); + } catch (err) { + setUploadStatus("Upload failed: " + (err as Error).message); + } + }; + + useEffect(() => { + // Any navigation clears a stale upload status from the meta slot. + setUploadStatus(""); + }, [location.key]); + + /* ---- ⌘K palette ---- */ + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { + e.preventDefault(); + setPaletteOpen((v) => !v); + } + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, []); + + const paletteCandidates = useCallback((): PaletteItem[] => { + const items: PaletteItem[] = []; + const add = (icon: string, label: string, kind: string, run: () => void) => + items.push({ icon, label, kind, run }); + if (hub && project && path) { + if (isFile) add("share", "Share: " + path, "action", shareNow); + add("hist", "History: " + path, "action", historyNow); + if (isFile) add("download", "Download: " + path, "action", () => downloadRef.current?.click()); + } + if (hub && project) add("hist", "History: whole project", "action", () => openHistory("")); + if (canUpload) add("upload", "Upload a file…", "action", uploadNow); + if (hub) { + for (const p of props.projects || []) { + if (!project || p.id !== project.id) { + add("folder", "Switch to project: " + p.name, "project", () => navigate("/" + p.id)); + } + } + } + if (config.auth?.enabled) { + add("power", "Sign out", "action", () => (window.location.href = "/auth/logout")); + } + for (const d of dirIndex.keys()) add("folder", d, "folder", () => openPath(d)); + for (const f of flatFiles) add("doc", f.path, "file", () => openPath(f.path)); + return items; + }, [hub, project, path, isFile, canUpload, config.auth?.enabled, dirIndex, flatFiles, props.projects, shareNow, historyNow, uploadNow, openHistory, openPath, navigate]); + + /* ---- "⋯ More" menu (secondary actions on narrow screens) ---- */ + useEffect(() => { + if (!moreOpen) return; + const close = () => setMoreOpen(false); + document.addEventListener("click", close); + return () => document.removeEventListener("click", close); + }, [moreOpen]); + + /* ---- content view ---- */ + let contentClass = "markdown"; + let view: ReactNode; + if (route.view === "insights" || route.view === "history") { + // Phase 3 delivers these views. + contentClass = "view"; + view =
{route.view} view is on its way.
; + } else if (path) { + if (!loaded) { + view =
Loading…
; + } else if (isDir) { + contentClass = "view"; + view = ( + + ); + } else { + view = ( + + ); + } + } else if (props.home) { + contentClass = "view"; + view = props.home; + } else { + view =
Select a file to read it.
; + } + + const crumb = path ? : null; + + const topbar = ( + + + {canShare && ( + + )} + {canHistory && ( + + )} + {canUpload && ( + + )} + + {canDownload && ( + + Download + + )} + {canMore && ( + + )} + {moreOpen && ( + + )} + + } + /> + ); + + return ( + <> + + } + topbar={topbar} + contentClass={contentClass} + contentRef={contentRef} + onContentScroll={onScroll} + > + {view} + + {share && setShare(null)} />} + setPaletteOpen(false)} candidates={paletteCandidates} /> + + ); +} diff --git a/internal/webapp/frontend/src/apps/HubApp.tsx b/internal/webapp/frontend/src/apps/HubApp.tsx index 1b4990a..b4e5930 100644 --- a/internal/webapp/frontend/src/apps/HubApp.tsx +++ b/internal/webapp/frontend/src/apps/HubApp.tsx @@ -9,6 +9,7 @@ import { ProjectNav } from "../components/ProjectNav"; import { OrgBar } from "../components/OrgBar"; import { EmptyState } from "../components/EmptyState"; import { toast } from "../toast"; +import Browser from "./Browser"; export default function HubApp({ config }: { config: ServerConfig }) { const location = useLocation(); @@ -69,6 +70,9 @@ export default function HubApp({ config }: { config: ServerConfig }) { // account that owns an org (or is a hub admin) gets it, whatever project // is open. The panels it opens arrive in Phase 4. const gearTarget = org && org.role === "owner" ? org : ownedOrg; + // Insights (embedded on the project home and behind the ⋯ menu) is for + // hub admins and owners of the project's org. + const canInsights = isAdmin || (org ? org.role === "owner" : false); const vault = ( } - orgBar={ {}} />} - topbar={} - > - {/* Content views (project home, files, insights, history) arrive in - Phases 2–3. */} -
Select a file to read it.
- + , + orgBar: {}} />, + }} + /> ); } diff --git a/internal/webapp/frontend/src/apps/VolumeApp.tsx b/internal/webapp/frontend/src/apps/VolumeApp.tsx index ff48c8c..9687f19 100644 --- a/internal/webapp/frontend/src/apps/VolumeApp.tsx +++ b/internal/webapp/frontend/src/apps/VolumeApp.tsx @@ -1,21 +1,30 @@ -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; +import { useLocation } from "react-router-dom"; import type { ServerConfig } from "../api/types"; -import { AppShell, Topbar, VaultHeader } from "../components/shell"; +import { VaultHeader } from "../components/shell"; +import { parseRoute } from "../router"; +import Browser from "./Browser"; -// Single-volume mode: one folder, no projects or orgs. File browsing -// arrives in Phase 2. +// Single-volume mode: one folder, no projects or orgs — but the full +// browsing surface (tree, listings, files, upload when enabled). export default function VolumeApp({ config }: { config: ServerConfig }) { + const location = useLocation(); const name = config.volume || "BearDrive"; useEffect(() => { document.title = config.brand || name; }, [config, name]); + const route = useMemo( + () => parseRoute(location.pathname, "volume"), + [location.pathname], + ); return ( - } - topbar={} - > -
Select a file to read it.
-
+ }} + /> ); } diff --git a/internal/webapp/frontend/src/components/Breadcrumbs.tsx b/internal/webapp/frontend/src/components/Breadcrumbs.tsx new file mode 100644 index 0000000..b8c10a0 --- /dev/null +++ b/internal/webapp/frontend/src/components/Breadcrumbs.tsx @@ -0,0 +1,33 @@ +// Every ancestor segment is a link to that folder's listing; the last +// segment is the current page. +export function Breadcrumbs({ + path, + onOpenFolder, +}: { + path: string; + onOpenFolder: (dir: string) => void; +}) { + const parts = path.split("/"); + let acc = ""; + return ( + <> + {parts.map((seg, i) => { + acc = acc ? acc + "/" + seg : seg; + const target = acc; + const last = i === parts.length - 1; + return ( + + {i > 0 && /} + {last ? ( + {seg} + ) : ( + onOpenFolder(target)}> + {seg} + + )} + + ); + })} + + ); +} diff --git a/internal/webapp/frontend/src/components/FileTree.tsx b/internal/webapp/frontend/src/components/FileTree.tsx new file mode 100644 index 0000000..689b55a --- /dev/null +++ b/internal/webapp/frontend/src/components/FileTree.tsx @@ -0,0 +1,95 @@ +import type { Node } from "../api/types"; +import { Icon, closeSidebarOnMobile } from "./shell"; + +// The sidebar file tree. The chevron only folds; the row selects (opens a +// folder's listing / a file). Clicking the folder whose listing is already +// showing folds/unfolds it, like a plain tree. +export function FileTree(props: { + root: Node | undefined; + expanded: Set; + onToggle: (path: string) => void; + currentPath: string; + listingShowing: boolean; // current view is a folder listing + onOpen: (path: string) => void; +}) { + return ( + + ); +} + +type RowProps = Omit[0], "root">; + +function TreeChildren({ nodes, ...rest }: RowProps & { nodes: Node[] }) { + return ( +
    + {nodes.map((n) => ( + + ))} +
+ ); +} + +function TreeNode({ node: n, ...rest }: RowProps & { node: Node }) { + const { expanded, onToggle, currentPath, listingShowing, onOpen } = rest; + const open = n.dir ? expanded.has(n.path) : false; + const click = () => { + if (n.dir) { + // Folding beats re-opening when this folder's listing is already up. + if (currentPath === n.path && listingShowing) { + onToggle(n.path); + return; + } + } + onOpen(n.path); + if (!n.dir) closeSidebarOnMobile(); + }; + return ( +
  • +
    { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + click(); + } + }} + > + { + if (!n.dir) return; + e.stopPropagation(); + onToggle(n.path); + }} + > + + + + + + {n.name} +
    + {n.dir && } +
  • + ); +} + +/* Every ancestor folder of a path (for unfolding the way to it). */ +export function ancestorsOf(filePath: string): string[] { + const parts = filePath.split("/"); + const out: string[] = []; + let acc = ""; + for (let i = 0; i < parts.length - 1; i++) { + acc = acc ? acc + "/" + parts[i] : parts[i]; + out.push(acc); + } + return out; +} diff --git a/internal/webapp/frontend/src/components/FileView.tsx b/internal/webapp/frontend/src/components/FileView.tsx new file mode 100644 index 0000000..e3faf9a --- /dev/null +++ b/internal/webapp/frontend/src/components/FileView.tsx @@ -0,0 +1,155 @@ +import { useEffect, useMemo } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { getJSON } from "../api/http"; +import type { HeatMap, Node, RenderDoc } from "../api/types"; +import { heatTotal, heatText } from "../hooks/useBrowse"; +import { IMG_EXT, MD_EXT, TEXT_EXT, joinPath } from "../util"; + +export function FileView(props: { + apiBase: string; + path: string; + heatMap: HeatMap | null; + flatFiles: Node[]; + onOpenFile: (path: string) => void; + onMeta: (meta: string) => void; + onRendered?: () => void; +}) { + const { apiBase, path, onMeta } = props; + const fileURL = apiBase + "file?path=" + encodeURIComponent(path); + + useEffect(() => () => onMeta(""), [path, onMeta]); // leaving a file clears its meta line + + if (MD_EXT.test(path)) return ; + if (IMG_EXT.test(path)) { + return ; + } + if (TEXT_EXT.test(path)) return ; + return ( +
    +
    {path.split("/").pop()}
    +

    No preview for this file type.

    + getJSON(apiBase + "render?path=" + encodeURIComponent(path)), + }); + + // Rewrite the HTML BEFORE rendering (relative image sources, external + // link targets) rather than patching the live DOM afterwards: React owns + // the dangerouslySetInnerHTML subtree and may re-apply the markup on any + // update, silently discarding post-commit DOM patches. Link navigation + // is delegated on the container for the same reason. + const html = useMemo( + () => (doc ? transformHTML(doc.html, path, apiBase) : ""), + [doc, path, apiBase], + ); + + useEffect(() => { + if (!doc) return; + const parts: string[] = []; + if (doc.author) parts.push(doc.author + (doc.device ? " on " + doc.device : "")); + if (doc.time) parts.push(new Date(doc.time).toLocaleString()); + const he = heatMap && heatMap[doc.path]; + if (he && heatTotal(he)) parts.push(heatText(he) + " / 30d"); + onMeta(parts.join(" · ")); + onRendered?.(); + }, [doc, heatMap, onMeta, onRendered]); + + if (error) return
    Could not load file: {(error as Error).message}
    ; + if (!doc) return null; + // Server-rendered, server-sanitized markdown — same trust model as the + // classic app assigning innerHTML. + return ( +
    handleLinkClick(e, path, flatFiles, onOpenFile)} + /> + ); +} + +/* Delegated click handling for rendered-markdown links: wiki: targets + resolve by basename, relative links resolve against the current file's + folder, everything else keeps its native behavior. */ +function handleLinkClick( + e: React.MouseEvent, + p: string, + flatFiles: Node[], + openFile: (path: string) => void, +) { + const a = (e.target as HTMLElement).closest("a"); + if (!a || !(e.currentTarget as HTMLElement).contains(a)) return; + const href = a.getAttribute("href") || ""; + const dir = p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : ""; + if (href.startsWith("wiki:")) { + e.preventDefault(); + openWikilink(decodeURIComponent(href.slice(5)), flatFiles, openFile); + } else if (!/^([a-z]+:|\/|#)/i.test(href)) { + e.preventDefault(); + openFile(joinPath(dir, decodeURIComponent(href))); + } +} + +/* String-level rewrite of the server's HTML: relative image sources point + at the file API, external links open in a new tab. */ +function transformHTML(html: string, p: string, apiBase: string): string { + const dir = p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : ""; + const fileURL = (path: string) => apiBase + "file?path=" + encodeURIComponent(path); + const parsed = new DOMParser().parseFromString(html, "text/html"); + for (const img of parsed.querySelectorAll("img")) { + const src = img.getAttribute("src") || ""; + if (!/^([a-z]+:|\/)/i.test(src)) img.setAttribute("src", fileURL(joinPath(dir, src))); + } + for (const a of parsed.querySelectorAll("a")) { + const href = a.getAttribute("href") || ""; + if (/^https?:/i.test(href)) { + a.setAttribute("target", "_blank"); + a.setAttribute("rel", "noopener"); + } + } + return parsed.body.innerHTML; +} + +function ImgView({ src, alt, onRendered }: { src: string; alt: string; onRendered?: () => void }) { + return {alt}; +} + +function TextView(props: Parameters[0] & { fileURL: string }) { + const { path, fileURL, onRendered } = props; + const { data, error } = useQuery({ + queryKey: ["text", fileURL], + queryFn: async () => { + const r = await fetch(fileURL); + if (!r.ok) throw new Error(await r.text()); + return r.text(); + }, + }); + useEffect(() => { + if (data != null) onRendered?.(); + }, [data, onRendered]); + if (error) return
    Could not load file: {(error as Error).message}
    ; + if (data == null) return null; + return ( +
    +      {data}
    +    
    + ); +} + +function openWikilink(target: string, flatFiles: Node[], openFile: (path: string) => void) { + const want = target.toLowerCase(); + const hit = + flatFiles.find((f) => f.path.toLowerCase() === want || f.path.toLowerCase() === want + ".md") || + flatFiles.find((f) => { + const n = f.name.toLowerCase(); + return n === want || n === want + ".md"; + }); + if (hit) openFile(hit.path); +} diff --git a/internal/webapp/frontend/src/components/FolderListing.tsx b/internal/webapp/frontend/src/components/FolderListing.tsx new file mode 100644 index 0000000..687fe3c --- /dev/null +++ b/internal/webapp/frontend/src/components/FolderListing.tsx @@ -0,0 +1,123 @@ +import { useEffect } from "react"; +import type { HeatMap, Node } from "../api/types"; +import { heatFor, heatLevel, heatText, useFolderHistory } from "../hooks/useBrowse"; +import { humanSize } from "../util"; +import { Icon } from "./shell"; +import { HistoryRow } from "./HistoryRow"; + +export function FolderListing(props: { + node: Node; + heatMap: HeatMap | null; + hub: boolean; // hub feeds exist; a plain-folder viewer has no journals + apiBase: string; + onOpen: (path: string) => void; + onFullHistory: (prefix: string) => void; + onRendered?: () => void; // scroll restoration: content height just grew +}) { + const { node, heatMap, onOpen } = props; + const kids = (node.children || []) + .slice() + .sort((a, b) => Number(b.dir || false) - Number(a.dir || false) || a.name.localeCompare(b.name)); + const dirs = kids.filter((c) => c.dir).length; + const files = kids.length - dirs; + const counts: string[] = []; + if (dirs) counts.push(dirs + (dirs === 1 ? " folder" : " folders")); + if (files) counts.push(files + (files === 1 ? " file" : " files")); + const folderHeat = heatFor(heatMap, node.path, true); + if (folderHeat) counts.push(heatText(folderHeat) + " in 30 days"); + + return ( +
    +

    + + + + {node.name} +

    +

    {counts.join(" · ") || "Empty folder"}

    + {kids.length === 0 ? ( +
    Nothing in this folder yet.
    + ) : ( +
    + {kids.map((c) => { + let meta = ""; + if (c.dir) { + const n = (c.children || []).length; + meta = n + (n === 1 ? " item" : " items"); + } else { + meta = [c.size ? humanSize(c.size) : "", c.time ? new Date(c.time).toLocaleDateString() : ""] + .filter(Boolean) + .join(" · "); + } + const he = heatFor(heatMap, c.path, !!c.dir); + if (he) meta = heatText(he) + (meta ? " · " + meta : ""); + return ( +
    onOpen(c.path)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onOpen(c.path); + } + }} + > + + + + {c.name} + {he && } + {meta} +
    + ); + })} +
    + )} + {props.hub && ( + props.onFullHistory(node.path + "/")} + onRendered={props.onRendered} + /> + )} +
    + ); +} + +/* The folder's change feed, straight from the journals: files added, + edited, and deleted anywhere under it, newest first. */ +function FolderHistory(props: { + apiBase: string; + prefix: string; + onOpen: (path: string) => void; + onFullHistory: () => void; + onRendered?: () => void; +}) { + const entries = useFolderHistory(props.apiBase, props.prefix, true); + const { onRendered } = props; + useEffect(() => { + // The feed adds height after the listing rendered; a restored scroll + // position (back/forward) may only fit now. + if (entries && entries.length && onRendered) onRendered(); + }, [entries, onRendered]); + if (!entries || entries.length === 0) return null; + return ( +
    +

    Recent changes

    +
    + {entries.map((e, i) => ( + + ))} +
    + +
    + ); +} diff --git a/internal/webapp/frontend/src/components/HistoryRow.tsx b/internal/webapp/frontend/src/components/HistoryRow.tsx new file mode 100644 index 0000000..a09266e --- /dev/null +++ b/internal/webapp/frontend/src/components/HistoryRow.tsx @@ -0,0 +1,89 @@ +import { useState } from "react"; +import type { HistoryEntry } from "../api/types"; +import { humanSize } from "../util"; +import { Icon } from "./shell"; + +/* One change as a row: what happened (added / edited / deleted), to which + file, by whom, from where — with the note (session link) expandable. */ +const KIND_ICON: Record = { add: "plus", edit: "edit", delete: "x" }; +const KIND_LABEL: Record = { add: "added", edit: "edited", delete: "deleted" }; + +export function HistoryRow({ + entry: e, + onOpen, +}: { + entry: HistoryEntry; + onOpen: (path: string) => void; +}) { + const [noteOpen, setNoteOpen] = useState(false); + const kind = e.kind === "put" ? "edit" : e.kind; // older servers report raw "put" ops + const who = e.user_name ? `${e.user_name} <${e.user}>` : e.user || e.author || "unknown"; + const dev = [e.device.name || e.device.id, e.device.os, e.device.ip].filter(Boolean).join(" · "); + const clickable = kind !== "delete"; + const open = (ev: React.MouseEvent | React.KeyboardEvent) => { + if ((ev.target as HTMLElement).tagName === "A") return; + if (clickable) onOpen(e.path); + }; + return ( +
    { + if (clickable && (ev.key === "Enter" || ev.key === " ")) { + ev.preventDefault(); + onOpen(e.path); + } + }} + > +
    + + + + {e.path} + {KIND_LABEL[kind] || kind} + {new Date(e.time).toLocaleString()} +
    +
    + {who} + {dev} + {e.size ? humanSize(e.size) : ""} +
    + {e.note && ( +
    + )} +
    + ); +} diff --git a/internal/webapp/frontend/src/components/Palette.tsx b/internal/webapp/frontend/src/components/Palette.tsx new file mode 100644 index 0000000..fae0147 --- /dev/null +++ b/internal/webapp/frontend/src/components/Palette.tsx @@ -0,0 +1,167 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import { Icon } from "./shell"; + +/* ---- command palette (⌘K / Ctrl+K) ---- + One box for everything: fuzzy-jump to any file, switch projects, and run + quick actions (share, history, upload, download, sign out). */ + +export interface PaletteItem { + icon: string; + label: string; + kind: string; // action | project | folder | file + run: () => void; +} + +/* Subsequence fuzzy match. Returns a score (higher = better), plus the + matched positions for highlighting; null when it doesn't match. */ +function fuzzy(query: string, text: string): { score: number; hits: number[] } | null { + if (!query) return { score: 0, hits: [] }; + const q = query.toLowerCase(); + const t = text.toLowerCase(); + let ti = 0, + score = 0, + streak = 0; + const hits: number[] = []; + for (let qi = 0; qi < q.length; qi++) { + const found = t.indexOf(q[qi], ti); + if (found === -1) return null; + streak = found === ti ? streak + 1 : 1; + score += streak * 3; // consecutive runs + if (found === 0 || "/ -_.".includes(t[found - 1])) score += 8; // word starts + hits.push(found); + ti = found + 1; + } + score -= Math.floor(t.length / 8); // mild preference for short targets + return { score, hits }; +} + +/* Match a query against a label, tolerating a simple English plural so + "ideas" still finds idea.md. Tries the raw query first, then a lightly + de-pluralized form (…ies→…y, …es→…, …s→…). */ +function fuzzyStemmed(query: string, label: string) { + const m = fuzzy(query, label); + if (m) return m; + const q = query.toLowerCase(); + let stem: string | null = null; + if (q.length > 3 && q.endsWith("ies")) stem = q.slice(0, -3) + "y"; + else if (q.length > 3 && q.endsWith("es")) stem = q.slice(0, -2); + else if (q.length > 2 && q.endsWith("s")) stem = q.slice(0, -1); + return stem ? fuzzy(stem, label) : null; +} + +function Highlight({ text, hits }: { text: string; hits: number[] }) { + const out: React.ReactNode[] = []; + let last = 0; + hits.forEach((h, i) => { + if (h > last) out.push(text.slice(last, h)); + out.push({text[h]}); + last = h + 1; + }); + out.push(text.slice(last)); + return {out}; +} + +export function Palette({ + open, + onClose, + candidates, +}: { + open: boolean; + onClose: () => void; + candidates: () => PaletteItem[]; +}) { + const [query, setQuery] = useState(""); + const [sel, setSel] = useState(0); + const input = useRef(null); + const listRef = useRef(null); + + const items = useMemo(() => { + if (!open) return []; + const scored: Array = []; + for (const c of candidates()) { + const m = fuzzyStemmed(query, c.label); + if (m) scored.push({ ...c, score: m.score, hits: m.hits }); + } + scored.sort((a, b) => b.score - a.score); + return scored.slice(0, 40); + }, [open, query, candidates]); + + useEffect(() => { + if (open) { + setQuery(""); + setSel(0); + input.current?.focus(); + } + }, [open]); + useEffect(() => setSel(0), [query]); + useEffect(() => { + listRef.current?.children[sel]?.scrollIntoView({ block: "nearest" }); + }, [sel, items]); + + const run = (item: PaletteItem) => { + onClose(); + item.run(); + }; + + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") { + e.preventDefault(); + onClose(); + } else if (e.key === "ArrowDown" || e.key === "ArrowUp") { + e.preventDefault(); + const n = items.length; + if (n) setSel((s) => (s + (e.key === "ArrowDown" ? 1 : n - 1)) % n); + } else if (e.key === "Enter") { + e.preventDefault(); + if (items[sel]) run(items[sel]); + } + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open, items, sel]); + + if (!open) return null; + return ( +
    e.target === e.currentTarget && onClose()}> + +
    + ); +} diff --git a/internal/webapp/frontend/src/components/ShareDialog.tsx b/internal/webapp/frontend/src/components/ShareDialog.tsx new file mode 100644 index 0000000..da1682b --- /dev/null +++ b/internal/webapp/frontend/src/components/ShareDialog.tsx @@ -0,0 +1,59 @@ +import { api } from "../api/http"; +import { copyText } from "../util"; +import { toast } from "../toast"; + +/* A clear, explicitly-public share confirmation: warns that anyone with the + link can view, and offers copy / open / revoke. */ +export function ShareDialog({ + url, + copied, + onClose, +}: { + url: string; + copied: boolean; + onClose: () => void; +}) { + const token = url.split("/s/")[1]; + return ( +
    e.target === e.currentTarget && onClose()}> +
    +

    Public link created

    +

    + Anyone with this link can view this file — no account needed. It always shows the + latest version until you revoke it. +

    +
    {url}
    +
    + + + + +
    +
    +
    + ); +} diff --git a/internal/webapp/frontend/src/components/shell.tsx b/internal/webapp/frontend/src/components/shell.tsx index 1c13ebd..5b268e4 100644 --- a/internal/webapp/frontend/src/components/shell.tsx +++ b/internal/webapp/frontend/src/components/shell.tsx @@ -26,6 +26,8 @@ export function AppShell(props: { orgBar?: ReactNode; topbar: ReactNode; contentClass?: string; + contentRef?: React.Ref; + onContentScroll?: () => void; children: ReactNode; }) { return ( @@ -39,7 +41,12 @@ export function AppShell(props: {
    {props.topbar} -
    +
    {props.children}
    diff --git a/internal/webapp/frontend/src/hooks/useBrowse.ts b/internal/webapp/frontend/src/hooks/useBrowse.ts new file mode 100644 index 0000000..2bac1bc --- /dev/null +++ b/internal/webapp/frontend/src/hooks/useBrowse.ts @@ -0,0 +1,101 @@ +import { useMemo } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { getJSON } from "../api/http"; +import type { HeatEntry, HeatMap, HistoryEntry, Node } from "../api/types"; + +// The volume's file tree, polled so synced changes appear without a +// reload. react-query's structural sharing keeps identical polls from +// re-rendering (the classic app compared JSON strings for the same +// reason). +export function useTree(apiBase: string, enabled = true) { + const q = useQuery({ + queryKey: ["tree", apiBase], + queryFn: () => getJSON(apiBase + "tree"), + enabled, + refetchInterval: 15_000, + }); + // Flattened lookups: every file (wikilink resolution, palette) and every + // directory (folder listings, path-kind dispatch). + const index = useMemo(() => { + const flatFiles: Node[] = []; + const dirIndex = new Map(); + const walk = (n: Node) => { + for (const c of n.children || []) { + if (c.dir) { + dirIndex.set(c.path, c); + walk(c); + } else { + flatFiles.push(c); + } + } + }; + if (q.data) walk(q.data); + return { flatFiles, dirIndex }; + }, [q.data]); + return { tree: q.data, ...index, loaded: !!q.data }; +} + +/* ---- read heat ---- + 30-day read counts per path from the heat API (hub only). Counts only — + the server never says who read what. */ +export function useHeat(apiBase: string, enabled: boolean) { + const q = useQuery({ + queryKey: ["heat", apiBase], + queryFn: () => getJSON<{ entries: HeatMap }>(apiBase + "heat?days=30"), + enabled, + staleTime: 60_000, + refetchInterval: 60_000, + }); + return q.data?.entries ?? null; +} + +// The folder's change feed, straight from the journals (hub only). +export function useFolderHistory(apiBase: string, prefix: string, enabled: boolean) { + const q = useQuery({ + queryKey: ["history", apiBase, "prefix", prefix, 20], + queryFn: () => + getJSON<{ entries: HistoryEntry[] }>( + apiBase + "history?prefix=" + encodeURIComponent(prefix) + "&n=20", + ), + enabled, + staleTime: 15_000, + }); + return q.data?.entries ?? null; +} + +/* Heat for one listing entry: a file's own bucket, or the subtree sum for a + folder. Null when there is nothing to show. */ +export function heatFor(heatMap: HeatMap | null, path: string, isDir: boolean): HeatEntry | null { + if (!heatMap) return null; + if (!isDir) return heatMap[path] || null; + const agg = { human: 0, agent: 0, share: 0 }; + for (const [p, e] of Object.entries(heatMap)) { + if (!p.startsWith(path + "/")) continue; + agg.human += e.human || 0; + agg.agent += e.agent || 0; + agg.share += e.share || 0; + } + return agg.human || agg.agent || agg.share ? agg : null; +} + +export function heatTotal(e: HeatEntry): number { + return (e.human || 0) + (e.agent || 0) + (e.share || 0); +} + +export function heatText(e: HeatEntry): string { + const total = heatTotal(e); + if (!total) return ""; + let s = total + (total === 1 ? " read" : " reads"); + if (e.agent) s += " (" + e.agent + " agent)"; + return s; +} + +/* Dot intensity 1–4, log-ish steps: 1–2, 3–9, 10–29, 30+ reads. */ +export function heatLevel(e: HeatEntry): number { + const total = heatTotal(e); + if (!total) return 0; + if (total < 3) return 1; + if (total < 10) return 2; + if (total < 30) return 3; + return 4; +} diff --git a/internal/webapp/frontend/src/upload.ts b/internal/webapp/frontend/src/upload.ts new file mode 100644 index 0000000..1c858d3 --- /dev/null +++ b/internal/webapp/frontend/src/upload.ts @@ -0,0 +1,90 @@ +import type { UploadPlan } from "./api/types"; + +/* The client asks the server how to upload (upload/init): "direct" hands + back a short-lived presigned URL and the bytes go straight to the object + store; "server" means relay the bytes through the bdrive server. */ +export async function uploadFile(apiBase: string, dest: string, file: File): Promise { + const buf = await file.arrayBuffer(); + const sha = await sha256Hex(buf); + const post = async (url: string, body: unknown) => { + const r = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + if (!r.ok) throw new Error(await r.text()); + return r.json(); + }; + const req = { path: dest, sha256: sha, size: file.size }; + const plan: UploadPlan = await post(apiBase + "upload/init", req); + if (plan.mode === "direct") { + if (!plan.exists) { + // identical content already in the store? skip the PUT + const r = await fetch(plan.url!, { + method: plan.method || "PUT", + headers: plan.headers || {}, + body: buf, + }); + if (!r.ok) throw new Error("storage upload failed: " + r.status); + } + await post(apiBase + "upload/commit", req); + } else { + const r = await fetch(apiBase + "upload/content?path=" + encodeURIComponent(dest), { + method: "PUT", + body: buf, + }); + if (!r.ok) throw new Error(await r.text()); + } +} + +async function sha256Hex(buf: ArrayBuffer): Promise { + if (crypto.subtle) { + const d = await crypto.subtle.digest("SHA-256", buf); + return [...new Uint8Array(d)].map((b) => b.toString(16).padStart(2, "0")).join(""); + } + return sha256Fallback(new Uint8Array(buf)); // plain-http origins have no crypto.subtle +} + +/* Minimal SHA-256 (FIPS 180-4) for non-secure contexts. */ +function sha256Fallback(bytes: Uint8Array): string { + const K = new Uint32Array([ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + ]); + const H = new Uint32Array([ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + ]); + const rr = (x: number, n: number) => (x >>> n) | (x << (32 - n)); + const len = bytes.length; + const padded = new Uint8Array(((((len + 8) >> 6) + 1) << 6)); + padded.set(bytes); + padded[len] = 0x80; + const dv = new DataView(padded.buffer); + dv.setUint32(padded.length - 8, Math.floor((len * 8) / 0x100000000)); + dv.setUint32(padded.length - 4, (len * 8) >>> 0); + const w = new Uint32Array(64); + for (let off = 0; off < padded.length; off += 64) { + for (let i = 0; i < 16; i++) w[i] = dv.getUint32(off + i * 4); + for (let i = 16; i < 64; i++) { + const s0 = rr(w[i - 15], 7) ^ rr(w[i - 15], 18) ^ (w[i - 15] >>> 3); + const s1 = rr(w[i - 2], 17) ^ rr(w[i - 2], 19) ^ (w[i - 2] >>> 10); + w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0; + } + let [a, b, c, d, e, f, g, h] = H as unknown as number[]; + for (let i = 0; i < 64; i++) { + const S1 = rr(e, 6) ^ rr(e, 11) ^ rr(e, 25); + const t1 = (h + S1 + ((e & f) ^ (~e & g)) + K[i] + w[i]) >>> 0; + const S0 = rr(a, 2) ^ rr(a, 13) ^ rr(a, 22); + const t2 = (S0 + ((a & b) ^ (a & c) ^ (b & c))) >>> 0; + h = g; g = f; f = e; e = (d + t1) >>> 0; d = c; c = b; b = a; a = (t1 + t2) >>> 0; + } + H[0] += a; H[1] += b; H[2] += c; H[3] += d; H[4] += e; H[5] += f; H[6] += g; H[7] += h; + } + return [...H].map((x) => (x >>> 0).toString(16).padStart(8, "0")).join(""); +} diff --git a/internal/webapp/frontend/src/util.ts b/internal/webapp/frontend/src/util.ts new file mode 100644 index 0000000..80d8d4d --- /dev/null +++ b/internal/webapp/frontend/src/util.ts @@ -0,0 +1,41 @@ +export const MD_EXT = /\.(md|markdown)$/i; +export const IMG_EXT = /\.(png|jpe?g|gif|svg|webp|ico|bmp|avif)$/i; +export const TEXT_EXT = + /\.(txt|log|json|ya?ml|toml|csv|go|py|js|ts|jsx|tsx|sh|bash|zsh|rb|rs|c|h|cpp|java|kt|swift|sql|html|css|xml|ini|conf|env|mod|sum|jsonl)$/i; + +export function humanSize(n: number): string { + if (n < 1024) return n + " B"; + const units = ["KB", "MB", "GB", "TB"]; + let i = -1; + do { + n /= 1024; + i++; + } while (n >= 1024 && i < units.length - 1); + return n.toFixed(1) + " " + units[i]; +} + +// Resolve a relative link against a directory, folding "." and "..". +export function joinPath(dir: string, rel: string): string { + const parts = (dir ? dir.split("/") : []).concat(rel.split("/")); + const out: string[] = []; + for (const s of parts) { + if (s === "" || s === ".") continue; + if (s === "..") out.pop(); + else out.push(s); + } + return out.join("/"); +} + +/* clipboard copy that never throws on a non-HTTPS origin (where + navigator.clipboard is undefined). Returns true on success. */ +export async function copyText(text: string): Promise { + try { + if (navigator.clipboard) { + await navigator.clipboard.writeText(text); + return true; + } + } catch { + /* fall through */ + } + return false; +} diff --git a/internal/webapp/static/assets/index-BoqNtp9Y.js b/internal/webapp/static/assets/index-BoqNtp9Y.js deleted file mode 100644 index de4f3a3..0000000 --- a/internal/webapp/static/assets/index-BoqNtp9Y.js +++ /dev/null @@ -1,11 +0,0 @@ -(function(){const c=document.createElement("link").relList;if(c&&c.supports&&c.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const d of o)if(d.type==="childList")for(const m of d.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&r(m)}).observe(document,{childList:!0,subtree:!0});function s(o){const d={};return o.integrity&&(d.integrity=o.integrity),o.referrerPolicy&&(d.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?d.credentials="include":o.crossOrigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function r(o){if(o.ep)return;o.ep=!0;const d=s(o);fetch(o.href,d)}})();var Fs={exports:{}},Bn={};var _d;function h0(){if(_d)return Bn;_d=1;var n=Symbol.for("react.transitional.element"),c=Symbol.for("react.fragment");function s(r,o,d){var m=null;if(d!==void 0&&(m=""+d),o.key!==void 0&&(m=""+o.key),"key"in o){d={};for(var b in o)b!=="key"&&(d[b]=o[b])}else d=o;return o=d.ref,{$$typeof:n,type:r,key:m,ref:o!==void 0?o:null,props:d}}return Bn.Fragment=c,Bn.jsx=s,Bn.jsxs=s,Bn}var Dd;function d0(){return Dd||(Dd=1,Fs.exports=h0()),Fs.exports}var H=d0(),ks={exports:{}},I={};var xd;function m0(){if(xd)return I;xd=1;var n=Symbol.for("react.transitional.element"),c=Symbol.for("react.portal"),s=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),d=Symbol.for("react.consumer"),m=Symbol.for("react.context"),b=Symbol.for("react.forward_ref"),y=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),T=Symbol.for("react.lazy"),O=Symbol.for("react.activity"),_=Symbol.iterator;function L(S){return S===null||typeof S!="object"?null:(S=_&&S[_]||S["@@iterator"],typeof S=="function"?S:null)}var Q={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},B=Object.assign,q={};function X(S,N,w){this.props=S,this.context=N,this.refs=q,this.updater=w||Q}X.prototype.isReactComponent={},X.prototype.setState=function(S,N){if(typeof S!="object"&&typeof S!="function"&&S!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,S,N,"setState")},X.prototype.forceUpdate=function(S){this.updater.enqueueForceUpdate(this,S,"forceUpdate")};function G(){}G.prototype=X.prototype;function V(S,N,w){this.props=S,this.context=N,this.refs=q,this.updater=w||Q}var ct=V.prototype=new G;ct.constructor=V,B(ct,X.prototype),ct.isPureReactComponent=!0;var st=Array.isArray;function Et(){}var k={H:null,A:null,T:null,S:null},rt=Object.prototype.hasOwnProperty;function zt(S,N,w){var K=w.ref;return{$$typeof:n,type:S,key:N,ref:K!==void 0?K:null,props:w}}function $t(S,N){return zt(S.type,N,S.props)}function Wt(S){return typeof S=="object"&&S!==null&&S.$$typeof===n}function Dt(S){var N={"=":"=0",":":"=2"};return"$"+S.replace(/[=:]/g,function(w){return N[w]})}var It=/\/+/g;function Ee(S,N){return typeof S=="object"&&S!==null&&S.key!=null?Dt(""+S.key):N.toString(36)}function Nt(S){switch(S.status){case"fulfilled":return S.value;case"rejected":throw S.reason;default:switch(typeof S.status=="string"?S.then(Et,Et):(S.status="pending",S.then(function(N){S.status==="pending"&&(S.status="fulfilled",S.value=N)},function(N){S.status==="pending"&&(S.status="rejected",S.reason=N)})),S.status){case"fulfilled":return S.value;case"rejected":throw S.reason}}throw S}function x(S,N,w,K,P){var lt=typeof S;(lt==="undefined"||lt==="boolean")&&(S=null);var mt=!1;if(S===null)mt=!0;else switch(lt){case"bigint":case"string":case"number":mt=!0;break;case"object":switch(S.$$typeof){case n:case c:mt=!0;break;case T:return mt=S._init,x(mt(S._payload),N,w,K,P)}}if(mt)return P=P(S),mt=K===""?"."+Ee(S,0):K,st(P)?(w="",mt!=null&&(w=mt.replace(It,"$&/")+"/"),x(P,N,w,"",function(Xa){return Xa})):P!=null&&(Wt(P)&&(P=$t(P,w+(P.key==null||S&&S.key===P.key?"":(""+P.key).replace(It,"$&/")+"/")+mt)),N.push(P)),1;mt=0;var Pt=K===""?".":K+":";if(st(S))for(var xt=0;xt>>1,Tt=x[pt];if(0>>1;pto(w,W))Ko(P,w)?(x[pt]=P,x[K]=W,pt=K):(x[pt]=w,x[N]=W,pt=N);else if(Ko(P,W))x[pt]=P,x[K]=W,pt=K;else break t}}return Y}function o(x,Y){var W=x.sortIndex-Y.sortIndex;return W!==0?W:x.id-Y.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var d=performance;n.unstable_now=function(){return d.now()}}else{var m=Date,b=m.now();n.unstable_now=function(){return m.now()-b}}var y=[],p=[],T=1,O=null,_=3,L=!1,Q=!1,B=!1,q=!1,X=typeof setTimeout=="function"?setTimeout:null,G=typeof clearTimeout=="function"?clearTimeout:null,V=typeof setImmediate<"u"?setImmediate:null;function ct(x){for(var Y=s(p);Y!==null;){if(Y.callback===null)r(p);else if(Y.startTime<=x)r(p),Y.sortIndex=Y.expirationTime,c(y,Y);else break;Y=s(p)}}function st(x){if(B=!1,ct(x),!Q)if(s(y)!==null)Q=!0,Et||(Et=!0,Dt());else{var Y=s(p);Y!==null&&Nt(st,Y.startTime-x)}}var Et=!1,k=-1,rt=5,zt=-1;function $t(){return q?!0:!(n.unstable_now()-ztx&&$t());){var pt=O.callback;if(typeof pt=="function"){O.callback=null,_=O.priorityLevel;var Tt=pt(O.expirationTime<=x);if(x=n.unstable_now(),typeof Tt=="function"){O.callback=Tt,ct(x),Y=!0;break e}O===s(y)&&r(y),ct(x)}else r(y);O=s(y)}if(O!==null)Y=!0;else{var S=s(p);S!==null&&Nt(st,S.startTime-x),Y=!1}}break t}finally{O=null,_=W,L=!1}Y=void 0}}finally{Y?Dt():Et=!1}}}var Dt;if(typeof V=="function")Dt=function(){V(Wt)};else if(typeof MessageChannel<"u"){var It=new MessageChannel,Ee=It.port2;It.port1.onmessage=Wt,Dt=function(){Ee.postMessage(null)}}else Dt=function(){X(Wt,0)};function Nt(x,Y){k=X(function(){x(n.unstable_now())},Y)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(x){x.callback=null},n.unstable_forceFrameRate=function(x){0>x||125pt?(x.sortIndex=W,c(p,x),s(y)===null&&x===s(p)&&(B?(G(k),k=-1):B=!0,Nt(st,W-pt))):(x.sortIndex=Tt,c(y,x),Q||L||(Q=!0,Et||(Et=!0,Dt()))),x},n.unstable_shouldYield=$t,n.unstable_wrapCallback=function(x){var Y=_;return function(){var W=_;_=Y;try{return x.apply(this,arguments)}finally{_=W}}}})(Is)),Is}var Nd;function v0(){return Nd||(Nd=1,Ws.exports=y0()),Ws.exports}var Ps={exports:{}},kt={};var Hd;function p0(){if(Hd)return kt;Hd=1;var n=hf();function c(y){var p="https://react.dev/errors/"+y;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(c){console.error(c)}}return n(),Ps.exports=p0(),Ps.exports}var Bd;function S0(){if(Bd)return Qn;Bd=1;var n=v0(),c=hf(),s=g0();function r(t){var e="https://react.dev/errors/"+t;if(1Tt||(t.current=pt[Tt],pt[Tt]=null,Tt--)}function w(t,e){Tt++,pt[Tt]=t.current,t.current=e}var K=S(null),P=S(null),lt=S(null),mt=S(null);function Pt(t,e){switch(w(lt,e),w(P,t),w(K,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?Ih(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=Ih(e),t=Ph(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}N(K),w(K,t)}function xt(){N(K),N(P),N(lt)}function Xa(t){t.memoizedState!==null&&w(mt,t);var e=K.current,l=Ph(e,t.type);e!==l&&(w(P,t),w(K,l))}function Jn(t){P.current===t&&(N(K),N(P)),mt.current===t&&(N(mt),jn._currentValue=W)}var Di,zf;function Hl(t){if(Di===void 0)try{throw Error()}catch(l){var e=l.stack.trim().match(/\n( *(at )?)/);Di=e&&e[1]||"",zf=-1)":-1u||v[a]!==A[u]){var D=` -`+v[a].replace(" at new "," at ");return t.displayName&&D.includes("")&&(D=D.replace("",t.displayName)),D}while(1<=a&&0<=u);break}}}finally{xi=!1,Error.prepareStackTrace=l}return(l=t?t.displayName||t.name:"")?Hl(l):""}function Xm(t,e){switch(t.tag){case 26:case 27:case 5:return Hl(t.type);case 16:return Hl("Lazy");case 13:return t.child!==e&&e!==null?Hl("Suspense Fallback"):Hl("Suspense");case 19:return Hl("SuspenseList");case 0:case 15:return Ui(t.type,!1);case 11:return Ui(t.type.render,!1);case 1:return Ui(t.type,!0);case 31:return Hl("Activity");default:return""}}function Mf(t){try{var e="",l=null;do e+=Xm(t,l),l=t,t=t.return;while(t);return e}catch(a){return` -Error generating stack: `+a.message+` -`+a.stack}}var ji=Object.prototype.hasOwnProperty,Ni=n.unstable_scheduleCallback,Hi=n.unstable_cancelCallback,Zm=n.unstable_shouldYield,Vm=n.unstable_requestPaint,fe=n.unstable_now,Km=n.unstable_getCurrentPriorityLevel,_f=n.unstable_ImmediatePriority,Df=n.unstable_UserBlockingPriority,Fn=n.unstable_NormalPriority,Jm=n.unstable_LowPriority,xf=n.unstable_IdlePriority,Fm=n.log,km=n.unstable_setDisableYieldValue,Za=null,re=null;function sl(t){if(typeof Fm=="function"&&km(t),re&&typeof re.setStrictMode=="function")try{re.setStrictMode(Za,t)}catch{}}var oe=Math.clz32?Math.clz32:Im,$m=Math.log,Wm=Math.LN2;function Im(t){return t>>>=0,t===0?32:31-($m(t)/Wm|0)|0}var kn=256,$n=262144,Wn=4194304;function ql(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function In(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var u=0,i=t.suspendedLanes,f=t.pingedLanes;t=t.warmLanes;var h=a&134217727;return h!==0?(a=h&~i,a!==0?u=ql(a):(f&=h,f!==0?u=ql(f):l||(l=h&~t,l!==0&&(u=ql(l))))):(h=a&~i,h!==0?u=ql(h):f!==0?u=ql(f):l||(l=a&~t,l!==0&&(u=ql(l)))),u===0?0:e!==0&&e!==u&&(e&i)===0&&(i=u&-u,l=e&-e,i>=l||i===32&&(l&4194048)!==0)?e:u}function Va(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function Pm(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Uf(){var t=Wn;return Wn<<=1,(Wn&62914560)===0&&(Wn=4194304),t}function qi(t){for(var e=[],l=0;31>l;l++)e.push(t);return e}function Ka(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function ty(t,e,l,a,u,i){var f=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var h=t.entanglements,v=t.expirationTimes,A=t.hiddenUpdates;for(l=f&~l;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var iy=/[\n"\\]/g;function Oe(t){return t.replace(iy,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function Gi(t,e,l,a,u,i,f,h){t.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?t.type=f:t.removeAttribute("type"),e!=null?f==="number"?(e===0&&t.value===""||t.value!=e)&&(t.value=""+Te(e)):t.value!==""+Te(e)&&(t.value=""+Te(e)):f!=="submit"&&f!=="reset"||t.removeAttribute("value"),e!=null?Xi(t,f,Te(e)):l!=null?Xi(t,f,Te(l)):a!=null&&t.removeAttribute("value"),u==null&&i!=null&&(t.defaultChecked=!!i),u!=null&&(t.checked=u&&typeof u!="function"&&typeof u!="symbol"),h!=null&&typeof h!="function"&&typeof h!="symbol"&&typeof h!="boolean"?t.name=""+Te(h):t.removeAttribute("name")}function Vf(t,e,l,a,u,i,f,h){if(i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(t.type=i),e!=null||l!=null){if(!(i!=="submit"&&i!=="reset"||e!=null)){wi(t);return}l=l!=null?""+Te(l):"",e=e!=null?""+Te(e):l,h||e===t.value||(t.value=e),t.defaultValue=e}a=a??u,a=typeof a!="function"&&typeof a!="symbol"&&!!a,t.checked=h?t.checked:!!a,t.defaultChecked=!!a,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(t.name=f),wi(t)}function Xi(t,e,l){e==="number"&&eu(t.ownerDocument)===t||t.defaultValue===""+l||(t.defaultValue=""+l)}function ia(t,e,l,a){if(t=t.options,e){e={};for(var u=0;u"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Fi=!1;if(Ve)try{var $a={};Object.defineProperty($a,"passive",{get:function(){Fi=!0}}),window.addEventListener("test",$a,$a),window.removeEventListener("test",$a,$a)}catch{Fi=!1}var rl=null,ki=null,au=null;function If(){if(au)return au;var t,e=ki,l=e.length,a,u="value"in rl?rl.value:rl.textContent,i=u.length;for(t=0;t=Pa),nr=" ",ur=!1;function ir(t,e){switch(t){case"keyup":return Ny.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function cr(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var ra=!1;function qy(t,e){switch(t){case"compositionend":return cr(e);case"keypress":return e.which!==32?null:(ur=!0,nr);case"textInput":return t=e.data,t===nr&&ur?null:t;default:return null}}function By(t,e){if(ra)return t==="compositionend"||!tc&&ir(t,e)?(t=If(),au=ki=rl=null,ra=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1=e)return{node:l,offset:e-t};t=a}t:{for(;l;){if(l.nextSibling){l=l.nextSibling;break t}l=l.parentNode}l=void 0}l=yr(l)}}function pr(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?pr(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function gr(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=eu(t.document);e instanceof t.HTMLIFrameElement;){try{var l=typeof e.contentWindow.location.href=="string"}catch{l=!1}if(l)t=e.contentWindow;else break;e=eu(t.document)}return e}function ac(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var Vy=Ve&&"documentMode"in document&&11>=document.documentMode,oa=null,nc=null,an=null,uc=!1;function Sr(t,e,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;uc||oa==null||oa!==eu(a)||(a=oa,"selectionStart"in a&&ac(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),an&&ln(an,a)||(an=a,a=$u(nc,"onSelect"),0>=f,u-=f,Qe=1<<32-oe(e)+u|l<et?(it=J,J=null):it=J.sibling;var ht=C(E,J,R[et],U);if(ht===null){J===null&&(J=it);break}t&&J&&ht.alternate===null&&e(E,J),g=i(ht,g,et),ot===null?F=ht:ot.sibling=ht,ot=ht,J=it}if(et===R.length)return l(E,J),ft&&Je(E,et),F;if(J===null){for(;etet?(it=J,J=null):it=J.sibling;var Ul=C(E,J,ht.value,U);if(Ul===null){J===null&&(J=it);break}t&&J&&Ul.alternate===null&&e(E,J),g=i(Ul,g,et),ot===null?F=Ul:ot.sibling=Ul,ot=Ul,J=it}if(ht.done)return l(E,J),ft&&Je(E,et),F;if(J===null){for(;!ht.done;et++,ht=R.next())ht=j(E,ht.value,U),ht!==null&&(g=i(ht,g,et),ot===null?F=ht:ot.sibling=ht,ot=ht);return ft&&Je(E,et),F}for(J=a(J);!ht.done;et++,ht=R.next())ht=M(J,E,et,ht.value,U),ht!==null&&(t&&ht.alternate!==null&&J.delete(ht.key===null?et:ht.key),g=i(ht,g,et),ot===null?F=ht:ot.sibling=ht,ot=ht);return t&&J.forEach(function(o0){return e(E,o0)}),ft&&Je(E,et),F}function bt(E,g,R,U){if(typeof R=="object"&&R!==null&&R.type===B&&R.key===null&&(R=R.props.children),typeof R=="object"&&R!==null){switch(R.$$typeof){case L:t:{for(var F=R.key;g!==null;){if(g.key===F){if(F=R.type,F===B){if(g.tag===7){l(E,g.sibling),U=u(g,R.props.children),U.return=E,E=U;break t}}else if(g.elementType===F||typeof F=="object"&&F!==null&&F.$$typeof===rt&&Jl(F)===g.type){l(E,g.sibling),U=u(g,R.props),rn(U,R),U.return=E,E=U;break t}l(E,g);break}else e(E,g);g=g.sibling}R.type===B?(U=Gl(R.props.children,E.mode,U,R.key),U.return=E,E=U):(U=du(R.type,R.key,R.props,null,E.mode,U),rn(U,R),U.return=E,E=U)}return f(E);case Q:t:{for(F=R.key;g!==null;){if(g.key===F)if(g.tag===4&&g.stateNode.containerInfo===R.containerInfo&&g.stateNode.implementation===R.implementation){l(E,g.sibling),U=u(g,R.children||[]),U.return=E,E=U;break t}else{l(E,g);break}else e(E,g);g=g.sibling}U=hc(R,E.mode,U),U.return=E,E=U}return f(E);case rt:return R=Jl(R),bt(E,g,R,U)}if(Nt(R))return Z(E,g,R,U);if(Dt(R)){if(F=Dt(R),typeof F!="function")throw Error(r(150));return R=F.call(R),$(E,g,R,U)}if(typeof R.then=="function")return bt(E,g,bu(R),U);if(R.$$typeof===V)return bt(E,g,vu(E,R),U);Eu(E,R)}return typeof R=="string"&&R!==""||typeof R=="number"||typeof R=="bigint"?(R=""+R,g!==null&&g.tag===6?(l(E,g.sibling),U=u(g,R),U.return=E,E=U):(l(E,g),U=oc(R,E.mode,U),U.return=E,E=U),f(E)):l(E,g)}return function(E,g,R,U){try{fn=0;var F=bt(E,g,R,U);return Ta=null,F}catch(J){if(J===Ea||J===gu)throw J;var ot=de(29,J,null,E.mode);return ot.lanes=U,ot.return=E,ot}}}var kl=Gr(!0),Xr=Gr(!1),yl=!1;function Rc(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ac(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function vl(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function pl(t,e,l){var a=t.updateQueue;if(a===null)return null;if(a=a.shared,(dt&2)!==0){var u=a.pending;return u===null?e.next=e:(e.next=u.next,u.next=e),a.pending=e,e=hu(t),Cr(t,null,l),e}return ou(t,a,e,l),hu(t)}function on(t,e,l){if(e=e.updateQueue,e!==null&&(e=e.shared,(l&4194048)!==0)){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Nf(t,l)}}function Cc(t,e){var l=t.updateQueue,a=t.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var u=null,i=null;if(l=l.firstBaseUpdate,l!==null){do{var f={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};i===null?u=i=f:i=i.next=f,l=l.next}while(l!==null);i===null?u=i=e:i=i.next=e}else u=i=e;l={baseState:a.baseState,firstBaseUpdate:u,lastBaseUpdate:i,shared:a.shared,callbacks:a.callbacks},t.updateQueue=l;return}t=l.lastBaseUpdate,t===null?l.firstBaseUpdate=e:t.next=e,l.lastBaseUpdate=e}var zc=!1;function hn(){if(zc){var t=ba;if(t!==null)throw t}}function dn(t,e,l,a){zc=!1;var u=t.updateQueue;yl=!1;var i=u.firstBaseUpdate,f=u.lastBaseUpdate,h=u.shared.pending;if(h!==null){u.shared.pending=null;var v=h,A=v.next;v.next=null,f===null?i=A:f.next=A,f=v;var D=t.alternate;D!==null&&(D=D.updateQueue,h=D.lastBaseUpdate,h!==f&&(h===null?D.firstBaseUpdate=A:h.next=A,D.lastBaseUpdate=v))}if(i!==null){var j=u.baseState;f=0,D=A=v=null,h=i;do{var C=h.lane&-536870913,M=C!==h.lane;if(M?(ut&C)===C:(a&C)===C){C!==0&&C===Sa&&(zc=!0),D!==null&&(D=D.next={lane:0,tag:h.tag,payload:h.payload,callback:null,next:null});t:{var Z=t,$=h;C=e;var bt=l;switch($.tag){case 1:if(Z=$.payload,typeof Z=="function"){j=Z.call(bt,j,C);break t}j=Z;break t;case 3:Z.flags=Z.flags&-65537|128;case 0:if(Z=$.payload,C=typeof Z=="function"?Z.call(bt,j,C):Z,C==null)break t;j=O({},j,C);break t;case 2:yl=!0}}C=h.callback,C!==null&&(t.flags|=64,M&&(t.flags|=8192),M=u.callbacks,M===null?u.callbacks=[C]:M.push(C))}else M={lane:C,tag:h.tag,payload:h.payload,callback:h.callback,next:null},D===null?(A=D=M,v=j):D=D.next=M,f|=C;if(h=h.next,h===null){if(h=u.shared.pending,h===null)break;M=h,h=M.next,M.next=null,u.lastBaseUpdate=M,u.shared.pending=null}}while(!0);D===null&&(v=j),u.baseState=v,u.firstBaseUpdate=A,u.lastBaseUpdate=D,i===null&&(u.shared.lanes=0),Tl|=f,t.lanes=f,t.memoizedState=j}}function Zr(t,e){if(typeof t!="function")throw Error(r(191,t));t.call(e)}function Vr(t,e){var l=t.callbacks;if(l!==null)for(t.callbacks=null,t=0;ti?i:8;var f=x.T,h={};x.T=h,Kc(t,!1,e,l);try{var v=u(),A=x.S;if(A!==null&&A(h,v),v!==null&&typeof v=="object"&&typeof v.then=="function"){var D=tv(v,a);vn(t,e,D,ge(t))}else vn(t,e,a,ge(t))}catch(j){vn(t,e,{then:function(){},status:"rejected",reason:j},ge())}finally{Y.p=i,f!==null&&h.types!==null&&(f.types=h.types),x.T=f}}function iv(){}function Zc(t,e,l,a){if(t.tag!==5)throw Error(r(476));var u=Ro(t).queue;Oo(t,u,e,W,l===null?iv:function(){return Ao(t),l(a)})}function Ro(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:W,baseState:W,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:We,lastRenderedState:W},next:null};var l={};return e.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:We,lastRenderedState:l},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function Ao(t){var e=Ro(t);e.next===null&&(e=t.alternate.memoizedState),vn(t,e.next.queue,{},ge())}function Vc(){return Vt(jn)}function Co(){return jt().memoizedState}function zo(){return jt().memoizedState}function cv(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var l=ge();t=vl(l);var a=pl(e,t,l);a!==null&&(ce(a,e,l),on(a,e,l)),e={cache:bc()},t.payload=e;return}e=e.return}}function sv(t,e,l){var a=ge();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},xu(t)?_o(e,l):(l=fc(t,e,l,a),l!==null&&(ce(l,t,a),Do(l,e,a)))}function Mo(t,e,l){var a=ge();vn(t,e,l,a)}function vn(t,e,l,a){var u={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(xu(t))_o(e,u);else{var i=t.alternate;if(t.lanes===0&&(i===null||i.lanes===0)&&(i=e.lastRenderedReducer,i!==null))try{var f=e.lastRenderedState,h=i(f,l);if(u.hasEagerState=!0,u.eagerState=h,he(h,f))return ou(t,e,u,0),Ot===null&&ru(),!1}catch{}if(l=fc(t,e,u,a),l!==null)return ce(l,t,a),Do(l,e,a),!0}return!1}function Kc(t,e,l,a){if(a={lane:2,revertLane:Rs(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},xu(t)){if(e)throw Error(r(479))}else e=fc(t,l,a,2),e!==null&&ce(e,t,2)}function xu(t){var e=t.alternate;return t===tt||e!==null&&e===tt}function _o(t,e){Ra=Ru=!0;var l=t.pending;l===null?e.next=e:(e.next=l.next,l.next=e),t.pending=e}function Do(t,e,l){if((l&4194048)!==0){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Nf(t,l)}}var pn={readContext:Vt,use:zu,useCallback:Mt,useContext:Mt,useEffect:Mt,useImperativeHandle:Mt,useLayoutEffect:Mt,useInsertionEffect:Mt,useMemo:Mt,useReducer:Mt,useRef:Mt,useState:Mt,useDebugValue:Mt,useDeferredValue:Mt,useTransition:Mt,useSyncExternalStore:Mt,useId:Mt,useHostTransitionStatus:Mt,useFormState:Mt,useActionState:Mt,useOptimistic:Mt,useMemoCache:Mt,useCacheRefresh:Mt};pn.useEffectEvent=Mt;var xo={readContext:Vt,use:zu,useCallback:function(t,e){return te().memoizedState=[t,e===void 0?null:e],t},useContext:Vt,useEffect:mo,useImperativeHandle:function(t,e,l){l=l!=null?l.concat([t]):null,_u(4194308,4,go.bind(null,e,t),l)},useLayoutEffect:function(t,e){return _u(4194308,4,t,e)},useInsertionEffect:function(t,e){_u(4,2,t,e)},useMemo:function(t,e){var l=te();e=e===void 0?null:e;var a=t();if($l){sl(!0);try{t()}finally{sl(!1)}}return l.memoizedState=[a,e],a},useReducer:function(t,e,l){var a=te();if(l!==void 0){var u=l(e);if($l){sl(!0);try{l(e)}finally{sl(!1)}}}else u=e;return a.memoizedState=a.baseState=u,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:u},a.queue=t,t=t.dispatch=sv.bind(null,tt,t),[a.memoizedState,t]},useRef:function(t){var e=te();return t={current:t},e.memoizedState=t},useState:function(t){t=Lc(t);var e=t.queue,l=Mo.bind(null,tt,e);return e.dispatch=l,[t.memoizedState,l]},useDebugValue:Gc,useDeferredValue:function(t,e){var l=te();return Xc(l,t,e)},useTransition:function(){var t=Lc(!1);return t=Oo.bind(null,tt,t.queue,!0,!1),te().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,l){var a=tt,u=te();if(ft){if(l===void 0)throw Error(r(407));l=l()}else{if(l=e(),Ot===null)throw Error(r(349));(ut&127)!==0||Wr(a,e,l)}u.memoizedState=l;var i={value:l,getSnapshot:e};return u.queue=i,mo(Pr.bind(null,a,i,t),[t]),a.flags|=2048,Ca(9,{destroy:void 0},Ir.bind(null,a,i,l,e),null),l},useId:function(){var t=te(),e=Ot.identifierPrefix;if(ft){var l=Le,a=Qe;l=(a&~(1<<32-oe(a)-1)).toString(32)+l,e="_"+e+"R_"+l,l=Au++,0<\/script>",i=i.removeChild(i.firstChild);break;case"select":i=typeof a.is=="string"?f.createElement("select",{is:a.is}):f.createElement("select"),a.multiple?i.multiple=!0:a.size&&(i.size=a.size);break;default:i=typeof a.is=="string"?f.createElement(u,{is:a.is}):f.createElement(u)}}i[Xt]=e,i[ee]=a;t:for(f=e.child;f!==null;){if(f.tag===5||f.tag===6)i.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===e)break t;for(;f.sibling===null;){if(f.return===null||f.return===e)break t;f=f.return}f.sibling.return=f.return,f=f.sibling}e.stateNode=i;t:switch(Jt(i,u,a),u){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break t;case"img":a=!0;break t;default:a=!1}a&&Pe(e)}}return At(e),is(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,l),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==a&&Pe(e);else{if(typeof a!="string"&&e.stateNode===null)throw Error(r(166));if(t=lt.current,pa(e)){if(t=e.stateNode,l=e.memoizedProps,a=null,u=Zt,u!==null)switch(u.tag){case 27:case 5:a=u.memoizedProps}t[Xt]=e,t=!!(t.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||$h(t.nodeValue,l)),t||dl(e,!0)}else t=Wu(t).createTextNode(a),t[Xt]=e,e.stateNode=t}return At(e),null;case 31:if(l=e.memoizedState,t===null||t.memoizedState!==null){if(a=pa(e),l!==null){if(t===null){if(!a)throw Error(r(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[Xt]=e}else Xl(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;At(e),t=!1}else l=vc(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=l),t=!0;if(!t)return e.flags&256?(ye(e),e):(ye(e),null);if((e.flags&128)!==0)throw Error(r(558))}return At(e),null;case 13:if(a=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(u=pa(e),a!==null&&a.dehydrated!==null){if(t===null){if(!u)throw Error(r(318));if(u=e.memoizedState,u=u!==null?u.dehydrated:null,!u)throw Error(r(317));u[Xt]=e}else Xl(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;At(e),u=!1}else u=vc(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=u),u=!0;if(!u)return e.flags&256?(ye(e),e):(ye(e),null)}return ye(e),(e.flags&128)!==0?(e.lanes=l,e):(l=a!==null,t=t!==null&&t.memoizedState!==null,l&&(a=e.child,u=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(u=a.alternate.memoizedState.cachePool.pool),i=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(i=a.memoizedState.cachePool.pool),i!==u&&(a.flags|=2048)),l!==t&&l&&(e.child.flags|=8192),qu(e,e.updateQueue),At(e),null);case 4:return xt(),t===null&&Ms(e.stateNode.containerInfo),At(e),null;case 10:return ke(e.type),At(e),null;case 19:if(N(Ut),a=e.memoizedState,a===null)return At(e),null;if(u=(e.flags&128)!==0,i=a.rendering,i===null)if(u)Sn(a,!1);else{if(_t!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(i=Ou(t),i!==null){for(e.flags|=128,Sn(a,!1),t=i.updateQueue,e.updateQueue=t,qu(e,t),e.subtreeFlags=0,t=l,l=e.child;l!==null;)zr(l,t),l=l.sibling;return w(Ut,Ut.current&1|2),ft&&Je(e,a.treeForkCount),e.child}t=t.sibling}a.tail!==null&&fe()>wu&&(e.flags|=128,u=!0,Sn(a,!1),e.lanes=4194304)}else{if(!u)if(t=Ou(i),t!==null){if(e.flags|=128,u=!0,t=t.updateQueue,e.updateQueue=t,qu(e,t),Sn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!i.alternate&&!ft)return At(e),null}else 2*fe()-a.renderingStartTime>wu&&l!==536870912&&(e.flags|=128,u=!0,Sn(a,!1),e.lanes=4194304);a.isBackwards?(i.sibling=e.child,e.child=i):(t=a.last,t!==null?t.sibling=i:e.child=i,a.last=i)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=fe(),t.sibling=null,l=Ut.current,w(Ut,u?l&1|2:l&1),ft&&Je(e,a.treeForkCount),t):(At(e),null);case 22:case 23:return ye(e),_c(),a=e.memoizedState!==null,t!==null?t.memoizedState!==null!==a&&(e.flags|=8192):a&&(e.flags|=8192),a?(l&536870912)!==0&&(e.flags&128)===0&&(At(e),e.subtreeFlags&6&&(e.flags|=8192)):At(e),l=e.updateQueue,l!==null&&qu(e,l.retryQueue),l=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),a=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),a!==l&&(e.flags|=2048),t!==null&&N(Kl),null;case 24:return l=null,t!==null&&(l=t.memoizedState.cache),e.memoizedState.cache!==l&&(e.flags|=2048),ke(Ht),At(e),null;case 25:return null;case 30:return null}throw Error(r(156,e.tag))}function dv(t,e){switch(mc(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return ke(Ht),xt(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return Jn(e),null;case 31:if(e.memoizedState!==null){if(ye(e),e.alternate===null)throw Error(r(340));Xl()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(ye(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(r(340));Xl()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return N(Ut),null;case 4:return xt(),null;case 10:return ke(e.type),null;case 22:case 23:return ye(e),_c(),t!==null&&N(Kl),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return ke(Ht),null;case 25:return null;default:return null}}function eh(t,e){switch(mc(e),e.tag){case 3:ke(Ht),xt();break;case 26:case 27:case 5:Jn(e);break;case 4:xt();break;case 31:e.memoizedState!==null&&ye(e);break;case 13:ye(e);break;case 19:N(Ut);break;case 10:ke(e.type);break;case 22:case 23:ye(e),_c(),t!==null&&N(Kl);break;case 24:ke(Ht)}}function bn(t,e){try{var l=e.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var u=a.next;l=u;do{if((l.tag&t)===t){a=void 0;var i=l.create,f=l.inst;a=i(),f.destroy=a}l=l.next}while(l!==u)}}catch(h){vt(e,e.return,h)}}function bl(t,e,l){try{var a=e.updateQueue,u=a!==null?a.lastEffect:null;if(u!==null){var i=u.next;a=i;do{if((a.tag&t)===t){var f=a.inst,h=f.destroy;if(h!==void 0){f.destroy=void 0,u=e;var v=l,A=h;try{A()}catch(D){vt(u,v,D)}}}a=a.next}while(a!==i)}}catch(D){vt(e,e.return,D)}}function lh(t){var e=t.updateQueue;if(e!==null){var l=t.stateNode;try{Vr(e,l)}catch(a){vt(t,t.return,a)}}}function ah(t,e,l){l.props=Wl(t.type,t.memoizedProps),l.state=t.memoizedState;try{l.componentWillUnmount()}catch(a){vt(t,e,a)}}function En(t,e){try{var l=t.ref;if(l!==null){switch(t.tag){case 26:case 27:case 5:var a=t.stateNode;break;case 30:a=t.stateNode;break;default:a=t.stateNode}typeof l=="function"?t.refCleanup=l(a):l.current=a}}catch(u){vt(t,e,u)}}function Ye(t,e){var l=t.ref,a=t.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(u){vt(t,e,u)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(u){vt(t,e,u)}else l.current=null}function nh(t){var e=t.type,l=t.memoizedProps,a=t.stateNode;try{t:switch(e){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break t;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(u){vt(t,t.return,u)}}function cs(t,e,l){try{var a=t.stateNode;Hv(a,t.type,l,e),a[ee]=e}catch(u){vt(t,t.return,u)}}function uh(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&zl(t.type)||t.tag===4}function ss(t){t:for(;;){for(;t.sibling===null;){if(t.return===null||uh(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&zl(t.type)||t.flags&2||t.child===null||t.tag===4)continue t;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function fs(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(t,e):(e=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,e.appendChild(t),l=l._reactRootContainer,l!=null||e.onclick!==null||(e.onclick=Ze));else if(a!==4&&(a===27&&zl(t.type)&&(l=t.stateNode,e=null),t=t.child,t!==null))for(fs(t,e,l),t=t.sibling;t!==null;)fs(t,e,l),t=t.sibling}function Bu(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?l.insertBefore(t,e):l.appendChild(t);else if(a!==4&&(a===27&&zl(t.type)&&(l=t.stateNode),t=t.child,t!==null))for(Bu(t,e,l),t=t.sibling;t!==null;)Bu(t,e,l),t=t.sibling}function ih(t){var e=t.stateNode,l=t.memoizedProps;try{for(var a=t.type,u=e.attributes;u.length;)e.removeAttributeNode(u[0]);Jt(e,a,l),e[Xt]=t,e[ee]=l}catch(i){vt(t,t.return,i)}}var tl=!1,Qt=!1,rs=!1,ch=typeof WeakSet=="function"?WeakSet:Set,Gt=null;function mv(t,e){if(t=t.containerInfo,xs=ni,t=gr(t),ac(t)){if("selectionStart"in t)var l={start:t.selectionStart,end:t.selectionEnd};else t:{l=(l=t.ownerDocument)&&l.defaultView||window;var a=l.getSelection&&l.getSelection();if(a&&a.rangeCount!==0){l=a.anchorNode;var u=a.anchorOffset,i=a.focusNode;a=a.focusOffset;try{l.nodeType,i.nodeType}catch{l=null;break t}var f=0,h=-1,v=-1,A=0,D=0,j=t,C=null;e:for(;;){for(var M;j!==l||u!==0&&j.nodeType!==3||(h=f+u),j!==i||a!==0&&j.nodeType!==3||(v=f+a),j.nodeType===3&&(f+=j.nodeValue.length),(M=j.firstChild)!==null;)C=j,j=M;for(;;){if(j===t)break e;if(C===l&&++A===u&&(h=f),C===i&&++D===a&&(v=f),(M=j.nextSibling)!==null)break;j=C,C=j.parentNode}j=M}l=h===-1||v===-1?null:{start:h,end:v}}else l=null}l=l||{start:0,end:0}}else l=null;for(Us={focusedElem:t,selectionRange:l},ni=!1,Gt=e;Gt!==null;)if(e=Gt,t=e.child,(e.subtreeFlags&1028)!==0&&t!==null)t.return=e,Gt=t;else for(;Gt!==null;){switch(e=Gt,i=e.alternate,t=e.flags,e.tag){case 0:if((t&4)!==0&&(t=e.updateQueue,t=t!==null?t.events:null,t!==null))for(l=0;l title"))),Jt(i,a,l),i[Xt]=t,wt(i),a=i;break t;case"link":var f=dd("link","href",u).get(a+(l.href||""));if(f){for(var h=0;hbt&&(f=bt,bt=$,$=f);var E=vr(h,$),g=vr(h,bt);if(E&&g&&(M.rangeCount!==1||M.anchorNode!==E.node||M.anchorOffset!==E.offset||M.focusNode!==g.node||M.focusOffset!==g.offset)){var R=j.createRange();R.setStart(E.node,E.offset),M.removeAllRanges(),$>bt?(M.addRange(R),M.extend(g.node,g.offset)):(R.setEnd(g.node,g.offset),M.addRange(R))}}}}for(j=[],M=h;M=M.parentNode;)M.nodeType===1&&j.push({element:M,left:M.scrollLeft,top:M.scrollTop});for(typeof h.focus=="function"&&h.focus(),h=0;hl?32:l,x.T=null,l=ps,ps=null;var i=Rl,f=ul;if(Yt=0,xa=Rl=null,ul=0,(dt&6)!==0)throw Error(r(331));var h=dt;if(dt|=4,gh(i.current),yh(i,i.current,f,l),dt=h,zn(0,!1),re&&typeof re.onPostCommitFiberRoot=="function")try{re.onPostCommitFiberRoot(Za,i)}catch{}return!0}finally{Y.p=u,x.T=a,qh(t,e)}}function Qh(t,e,l){e=Ae(l,e),e=$c(t.stateNode,e,2),t=pl(t,e,2),t!==null&&(Ka(t,2),we(t))}function vt(t,e,l){if(t.tag===3)Qh(t,t,l);else for(;e!==null;){if(e.tag===3){Qh(e,t,l);break}else if(e.tag===1){var a=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(Ol===null||!Ol.has(a))){t=Ae(l,t),l=Lo(2),a=pl(e,l,2),a!==null&&(Yo(l,a,e,t),Ka(a,2),we(a));break}}e=e.return}}function Es(t,e,l){var a=t.pingCache;if(a===null){a=t.pingCache=new pv;var u=new Set;a.set(e,u)}else u=a.get(e),u===void 0&&(u=new Set,a.set(e,u));u.has(l)||(ds=!0,u.add(l),t=Tv.bind(null,t,e,l),e.then(t,t))}function Tv(t,e,l){var a=t.pingCache;a!==null&&a.delete(e),t.pingedLanes|=t.suspendedLanes&l,t.warmLanes&=~l,Ot===t&&(ut&l)===l&&(_t===4||_t===3&&(ut&62914560)===ut&&300>fe()-Yu?(dt&2)===0&&Ua(t,0):ms|=l,Da===ut&&(Da=0)),we(t)}function Lh(t,e){e===0&&(e=Uf()),t=wl(t,e),t!==null&&(Ka(t,e),we(t))}function Ov(t){var e=t.memoizedState,l=0;e!==null&&(l=e.retryLane),Lh(t,l)}function Rv(t,e){var l=0;switch(t.tag){case 31:case 13:var a=t.stateNode,u=t.memoizedState;u!==null&&(l=u.retryLane);break;case 19:a=t.stateNode;break;case 22:a=t.stateNode._retryCache;break;default:throw Error(r(314))}a!==null&&a.delete(e),Lh(t,l)}function Av(t,e){return Ni(t,e)}var Ju=null,Na=null,Ts=!1,Fu=!1,Os=!1,Cl=0;function we(t){t!==Na&&t.next===null&&(Na===null?Ju=Na=t:Na=Na.next=t),Fu=!0,Ts||(Ts=!0,zv())}function zn(t,e){if(!Os&&Fu){Os=!0;do for(var l=!1,a=Ju;a!==null;){if(t!==0){var u=a.pendingLanes;if(u===0)var i=0;else{var f=a.suspendedLanes,h=a.pingedLanes;i=(1<<31-oe(42|t)+1)-1,i&=u&~(f&~h),i=i&201326741?i&201326741|1:i?i|2:0}i!==0&&(l=!0,Xh(a,i))}else i=ut,i=In(a,a===Ot?i:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(i&3)===0||Va(a,i)||(l=!0,Xh(a,i));a=a.next}while(l);Os=!1}}function Cv(){Yh()}function Yh(){Fu=Ts=!1;var t=0;Cl!==0&&Bv()&&(t=Cl);for(var e=fe(),l=null,a=Ju;a!==null;){var u=a.next,i=wh(a,e);i===0?(a.next=null,l===null?Ju=u:l.next=u,u===null&&(Na=l)):(l=a,(t!==0||(i&3)!==0)&&(Fu=!0)),a=u}Yt!==0&&Yt!==5||zn(t),Cl!==0&&(Cl=0)}function wh(t,e){for(var l=t.suspendedLanes,a=t.pingedLanes,u=t.expirationTimes,i=t.pendingLanes&-62914561;0h)break;var D=v.transferSize,j=v.initiatorType;D&&Wh(j)&&(v=v.responseEnd,f+=D*(v"u"?null:document;function fd(t,e,l){var a=Ha;if(a&&typeof e=="string"&&e){var u=Oe(e);u='link[rel="'+t+'"][href="'+u+'"]',typeof l=="string"&&(u+='[crossorigin="'+l+'"]'),sd.has(u)||(sd.add(u),t={rel:t,crossOrigin:l,href:e},a.querySelector(u)===null&&(e=a.createElement("link"),Jt(e,"link",t),wt(e),a.head.appendChild(e)))}}function Kv(t){il.D(t),fd("dns-prefetch",t,null)}function Jv(t,e){il.C(t,e),fd("preconnect",t,e)}function Fv(t,e,l){il.L(t,e,l);var a=Ha;if(a&&t&&e){var u='link[rel="preload"][as="'+Oe(e)+'"]';e==="image"&&l&&l.imageSrcSet?(u+='[imagesrcset="'+Oe(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(u+='[imagesizes="'+Oe(l.imageSizes)+'"]')):u+='[href="'+Oe(t)+'"]';var i=u;switch(e){case"style":i=qa(t);break;case"script":i=Ba(t)}xe.has(i)||(t=O({rel:"preload",href:e==="image"&&l&&l.imageSrcSet?void 0:t,as:e},l),xe.set(i,t),a.querySelector(u)!==null||e==="style"&&a.querySelector(xn(i))||e==="script"&&a.querySelector(Un(i))||(e=a.createElement("link"),Jt(e,"link",t),wt(e),a.head.appendChild(e)))}}function kv(t,e){il.m(t,e);var l=Ha;if(l&&t){var a=e&&typeof e.as=="string"?e.as:"script",u='link[rel="modulepreload"][as="'+Oe(a)+'"][href="'+Oe(t)+'"]',i=u;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":i=Ba(t)}if(!xe.has(i)&&(t=O({rel:"modulepreload",href:t},e),xe.set(i,t),l.querySelector(u)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(Un(i)))return}a=l.createElement("link"),Jt(a,"link",t),wt(a),l.head.appendChild(a)}}}function $v(t,e,l){il.S(t,e,l);var a=Ha;if(a&&t){var u=na(a).hoistableStyles,i=qa(t);e=e||"default";var f=u.get(i);if(!f){var h={loading:0,preload:null};if(f=a.querySelector(xn(i)))h.loading=5;else{t=O({rel:"stylesheet",href:t,"data-precedence":e},l),(l=xe.get(i))&&Ls(t,l);var v=f=a.createElement("link");wt(v),Jt(v,"link",t),v._p=new Promise(function(A,D){v.onload=A,v.onerror=D}),v.addEventListener("load",function(){h.loading|=1}),v.addEventListener("error",function(){h.loading|=2}),h.loading|=4,Pu(f,e,a)}f={type:"stylesheet",instance:f,count:1,state:h},u.set(i,f)}}}function Wv(t,e){il.X(t,e);var l=Ha;if(l&&t){var a=na(l).hoistableScripts,u=Ba(t),i=a.get(u);i||(i=l.querySelector(Un(u)),i||(t=O({src:t,async:!0},e),(e=xe.get(u))&&Ys(t,e),i=l.createElement("script"),wt(i),Jt(i,"link",t),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(u,i))}}function Iv(t,e){il.M(t,e);var l=Ha;if(l&&t){var a=na(l).hoistableScripts,u=Ba(t),i=a.get(u);i||(i=l.querySelector(Un(u)),i||(t=O({src:t,async:!0,type:"module"},e),(e=xe.get(u))&&Ys(t,e),i=l.createElement("script"),wt(i),Jt(i,"link",t),l.head.appendChild(i)),i={type:"script",instance:i,count:1,state:null},a.set(u,i))}}function rd(t,e,l,a){var u=(u=lt.current)?Iu(u):null;if(!u)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(e=qa(l.href),l=na(u).hoistableStyles,a=l.get(e),a||(a={type:"style",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){t=qa(l.href);var i=na(u).hoistableStyles,f=i.get(t);if(f||(u=u.ownerDocument||u,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},i.set(t,f),(i=u.querySelector(xn(t)))&&!i._p&&(f.instance=i,f.state.loading=5),xe.has(t)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},xe.set(t,l),i||Pv(u,t,l,f.state))),e&&a===null)throw Error(r(528,""));return f}if(e&&a!==null)throw Error(r(529,""));return null;case"script":return e=l.async,l=l.src,typeof l=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(e=Ba(l),l=na(u).hoistableScripts,a=l.get(e),a||(a={type:"script",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function qa(t){return'href="'+Oe(t)+'"'}function xn(t){return'link[rel="stylesheet"]['+t+"]"}function od(t){return O({},t,{"data-precedence":t.precedence,precedence:null})}function Pv(t,e,l,a){t.querySelector('link[rel="preload"][as="style"]['+e+"]")?a.loading=1:(e=t.createElement("link"),a.preload=e,e.addEventListener("load",function(){return a.loading|=1}),e.addEventListener("error",function(){return a.loading|=2}),Jt(e,"link",l),wt(e),t.head.appendChild(e))}function Ba(t){return'[src="'+Oe(t)+'"]'}function Un(t){return"script[async]"+t}function hd(t,e,l){if(e.count++,e.instance===null)switch(e.type){case"style":var a=t.querySelector('style[data-href~="'+Oe(l.href)+'"]');if(a)return e.instance=a,wt(a),a;var u=O({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(t.ownerDocument||t).createElement("style"),wt(a),Jt(a,"style",u),Pu(a,l.precedence,t),e.instance=a;case"stylesheet":u=qa(l.href);var i=t.querySelector(xn(u));if(i)return e.state.loading|=4,e.instance=i,wt(i),i;a=od(l),(u=xe.get(u))&&Ls(a,u),i=(t.ownerDocument||t).createElement("link"),wt(i);var f=i;return f._p=new Promise(function(h,v){f.onload=h,f.onerror=v}),Jt(i,"link",a),e.state.loading|=4,Pu(i,l.precedence,t),e.instance=i;case"script":return i=Ba(l.src),(u=t.querySelector(Un(i)))?(e.instance=u,wt(u),u):(a=l,(u=xe.get(i))&&(a=O({},l),Ys(a,u)),t=t.ownerDocument||t,u=t.createElement("script"),wt(u),Jt(u,"link",a),t.head.appendChild(u),e.instance=u);case"void":return null;default:throw Error(r(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(a=e.instance,e.state.loading|=4,Pu(a,l.precedence,t));return e.instance}function Pu(t,e,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),u=a.length?a[a.length-1]:null,i=u,f=0;f title"):null)}function t0(t,e,l){if(l===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;return e.rel==="stylesheet"?(t=e.disabled,typeof e.precedence=="string"&&t==null):!0;case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function yd(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function e0(t,e,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var u=qa(a.href),i=e.querySelector(xn(u));if(i){e=i._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=ei.bind(t),e.then(t,t)),l.state.loading|=4,l.instance=i,wt(i);return}i=e.ownerDocument||e,a=od(a),(u=xe.get(u))&&Ls(a,u),i=i.createElement("link"),wt(i);var f=i;f._p=new Promise(function(h,v){f.onload=h,f.onerror=v}),Jt(i,"link",a),l.instance=i}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(l,e),(e=l.state.preload)&&(l.state.loading&3)===0&&(t.count++,l=ei.bind(t),e.addEventListener("load",l),e.addEventListener("error",l))}}var ws=0;function l0(t,e){return t.stylesheets&&t.count===0&&ai(t,t.stylesheets),0ws?50:800)+e);return t.unsuspend=l,function(){t.unsuspend=null,clearTimeout(a),clearTimeout(u)}}:null}function ei(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)ai(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var li=null;function ai(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,li=new Map,e.forEach(a0,t),li=null,ei.call(t))}function a0(t,e){if(!(e.state.loading&4)){var l=li.get(t);if(l)var a=l.get(null);else{l=new Map,li.set(t,l);for(var u=t.querySelectorAll("link[data-precedence],style[data-precedence]"),i=0;i"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(c){console.error(c)}}return n(),$s.exports=S0(),$s.exports}var E0=b0();var df=/^(?:[a-z][a-z0-9+.-]*:|[\\/]{2})/i,im=/^[\\/]{2}/;function T0(n,c){return c+n.replace(/\\/g,"/")}var Ld="popstate";function Yd(n){return typeof n=="object"&&n!=null&&"pathname"in n&&"search"in n&&"hash"in n&&"state"in n&&"key"in n}function O0(n={}){function c(r,o){let d=o.state?.masked,{pathname:m,search:b,hash:y}=d||r.location;return af("",{pathname:m,search:b,hash:y},o.state&&o.state.usr||null,o.state&&o.state.key||"default",d?{pathname:r.location.pathname,search:r.location.search,hash:r.location.hash}:void 0)}function s(r,o){return typeof o=="string"?o:Yn(o)}return A0(c,s,null,n)}function Lt(n,c){if(n===!1||n===null||typeof n>"u")throw new Error(c)}function qe(n,c){if(!n){typeof console<"u"&&console.warn(c);try{throw new Error(c)}catch{}}}function R0(){return Math.random().toString(36).substring(2,10)}function wd(n,c){return{usr:n.state,key:n.key,idx:c,masked:n.mask?{pathname:n.pathname,search:n.search,hash:n.hash}:void 0}}function af(n,c,s=null,r,o){return{pathname:typeof n=="string"?n:n.pathname,search:"",hash:"",...typeof c=="string"?Zn(c):c,state:s,key:c&&c.key||r||R0(),mask:o}}function Yn({pathname:n="/",search:c="",hash:s=""}){return c&&c!=="?"&&(n+=c.charAt(0)==="?"?c:"?"+c),s&&s!=="#"&&(n+=s.charAt(0)==="#"?s:"#"+s),n}function Zn(n){let c={};if(n){let s=n.indexOf("#");s>=0&&(c.hash=n.substring(s),n=n.substring(0,s));let r=n.indexOf("?");r>=0&&(c.search=n.substring(r),n=n.substring(0,r)),n&&(c.pathname=n)}return c}function A0(n,c,s,r={}){let{window:o=document.defaultView,v5Compat:d=!1}=r,m=o.history,b="POP",y=null,p=T();p==null&&(p=0,m.replaceState({...m.state,idx:p},""));function T(){return(m.state||{idx:null}).idx}function O(){b="POP";let q=T(),X=q==null?null:q-p;p=q,y&&y({action:b,location:B.location,delta:X})}function _(q,X){b="PUSH";let G=Yd(q)?q:af(B.location,q,X);p=T()+1;let V=wd(G,p),ct=B.createHref(G.mask||G);try{m.pushState(V,"",ct)}catch(st){if(st instanceof DOMException&&st.name==="DataCloneError")throw st;o.location.assign(ct)}d&&y&&y({action:b,location:B.location,delta:1})}function L(q,X){b="REPLACE";let G=Yd(q)?q:af(B.location,q,X);p=T();let V=wd(G,p),ct=B.createHref(G.mask||G);m.replaceState(V,"",ct),d&&y&&y({action:b,location:B.location,delta:0})}function Q(q){return C0(o,q)}let B={get action(){return b},get location(){return n(o,m)},listen(q){if(y)throw new Error("A history only accepts one active listener");return o.addEventListener(Ld,O),y=q,()=>{o.removeEventListener(Ld,O),y=null}},createHref(q){return c(o,q)},createURL:Q,encodeLocation(q){let X=Q(q);return{pathname:X.pathname,search:X.search,hash:X.hash}},push:_,replace:L,go(q){return m.go(q)}};return B}function C0(n,c,s=!1){let r="http://localhost";n&&(r=n.location.origin!=="null"?n.location.origin:n.location.href),Lt(r,"No window.location.(origin|href) available to create URL");let o=typeof c=="string"?c:Yn(c);return o=o.replace(/ $/,"%20"),!s&&im.test(o)&&(o=r+o),new URL(o,r)}function cm(n,c,s="/"){return z0(n,c,s,!1)}function z0(n,c,s,r,o){let d=typeof c=="string"?Zn(c):c,m=cl(d.pathname||"/",s);if(m==null)return null;let b=M0(n),y=null,p=L0(m);for(let T=0;y==null&&T{let T={relativePath:p===void 0?m.path||"":p,caseSensitive:m.caseSensitive===!0,childrenIndex:b,route:m};if(T.relativePath.startsWith("/")){if(!T.relativePath.startsWith(r)&&y)return;Lt(T.relativePath.startsWith(r),`Absolute route path "${T.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),T.relativePath=T.relativePath.slice(r.length)}let O=He([r,T.relativePath]),_=s.concat(T);m.children&&m.children.length>0&&(Lt(m.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${O}".`),sm(m.children,c,_,O,y)),!(m.path==null&&!m.index)&&c.push({path:O,score:q0(O,m.index),routesMeta:_.map((L,Q)=>{let[B,q]=om(L.relativePath,L.caseSensitive,Q===_.length-1);return{...L,matcher:B,compiledParams:q}})})};return n.forEach((m,b)=>{if(m.path===""||!m.path?.includes("?"))d(m,b);else for(let y of fm(m.path))d(m,b,!0,y)}),c}function fm(n){let c=n.split("/");if(c.length===0)return[];let[s,...r]=c,o=s.endsWith("?"),d=s.replace(/\?$/,"");if(r.length===0)return o?[d,""]:[d];let m=fm(r.join("/")),b=[];return b.push(...m.map(y=>y===""?d:[d,y].join("/"))),o&&b.push(...m),b.map(y=>n.startsWith("/")&&y===""?"/":y)}function _0(n){n.sort((c,s)=>c.score!==s.score?s.score-c.score:B0(c.routesMeta.map(r=>r.childrenIndex),s.routesMeta.map(r=>r.childrenIndex)))}var D0=/^:[\w-]+$/,x0=3,U0=2,j0=1,N0=10,H0=-2,Gd=n=>n==="*";function q0(n,c){let s=n.split("/"),r=s.length;return s.some(Gd)&&(r+=H0),c&&(r+=U0),s.filter(o=>!Gd(o)).reduce((o,d)=>o+(D0.test(d)?x0:d===""?j0:N0),r)}function B0(n,c){return n.length===c.length&&n.slice(0,-1).every((r,o)=>r===c[o])?n[n.length-1]-c[c.length-1]:0}function Q0(n,c,s=!1){let{routesMeta:r}=n,o={},d="/",m=[];for(let b=0;b{if(T==="*"){let Q=b[_]||"";m=d.slice(0,d.length-Q.length).replace(/(.)\/+$/,"$1")}const L=b[_];return O&&!L?p[T]=void 0:p[T]=(L||"").replace(/%2F/g,"/"),p},{}),pathname:d,pathnameBase:m,pattern:n}}function om(n,c=!1,s=!0){qe(n==="*"||!n.endsWith("*")||n.endsWith("/*"),`Route path "${n}" will be treated as if it were "${n.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${n.replace(/\*$/,"/*")}".`);let r=[],o="^"+n.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(m,b,y,p,T)=>{if(r.push({paramName:b,isOptional:y!=null}),y){let O=T.charAt(p+m.length);return O&&O!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return n.endsWith("*")?(r.push({paramName:"*"}),o+=n==="*"||n==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):s?o+="\\/*$":n!==""&&n!=="/"&&(o+="(?:(?=\\/|$))"),[new RegExp(o,c?void 0:"i"),r]}function L0(n){try{return n.split("/").map(c=>decodeURIComponent(c).replace(/\//g,"%2F")).join("/")}catch(c){return qe(!1,`The URL path "${n}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${c}).`),n}}function cl(n,c){if(c==="/")return n;if(!n.toLowerCase().startsWith(c.toLowerCase()))return null;let s=c.endsWith("/")?c.length-1:c.length,r=n.charAt(s);return r&&r!=="/"?null:n.slice(s)||"/"}function Y0(n,c="/"){let{pathname:s,search:r="",hash:o=""}=typeof n=="string"?Zn(n):n,d;return s?(s=hm(s),s.startsWith("/")?d=Xd(s.substring(1),"/"):d=Xd(s,c)):d=c,{pathname:d,search:X0(r),hash:Z0(o)}}function Xd(n,c){let s=Si(c).split("/");return n.split("/").forEach(o=>{o===".."?s.length>1&&s.pop():o!=="."&&s.push(o)}),s.length>1?s.join("/"):"/"}function tf(n,c,s,r){return`Cannot include a '${n}' character in a manually specified \`to.${c}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${s}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function w0(n){return n.filter((c,s)=>s===0||c.route.path&&c.route.path.length>0)}function mf(n){let c=w0(n);return c.map((s,r)=>r===c.length-1?s.pathname:s.pathnameBase)}function Ei(n,c,s,r=!1){let o;typeof n=="string"?o=Zn(n):(o={...n},Lt(!o.pathname||!o.pathname.includes("?"),tf("?","pathname","search",o)),Lt(!o.pathname||!o.pathname.includes("#"),tf("#","pathname","hash",o)),Lt(!o.search||!o.search.includes("#"),tf("#","search","hash",o)));let d=n===""||o.pathname==="",m=d?"/":o.pathname,b;if(m==null)b=s;else{let O=c.length-1;if(!r&&m.startsWith("..")){let _=m.split("/");for(;_[0]==="..";)_.shift(),O-=1;o.pathname=_.join("/")}b=O>=0?c[O]:"/"}let y=Y0(o,b),p=m&&m!=="/"&&m.endsWith("/"),T=(d||m===".")&&s.endsWith("/");return!y.pathname.endsWith("/")&&(p||T)&&(y.pathname+="/"),y}var hm=n=>n.replace(/[\\/]{2,}/g,"/"),He=n=>hm(n.join("/")),Si=n=>n.replace(/\/+$/,""),G0=n=>Si(n).replace(/^\/*/,"/"),X0=n=>!n||n==="?"?"":n.startsWith("?")?n:"?"+n,Z0=n=>!n||n==="#"?"":n.startsWith("#")?n:"#"+n,V0=class{constructor(n,c,s,r=!1){this.status=n,this.statusText=c||"",this.internal=r,s instanceof Error?(this.data=s.toString(),this.error=s):this.data=s}};function K0(n){return n!=null&&typeof n.status=="number"&&typeof n.statusText=="string"&&typeof n.internal=="boolean"&&"data"in n}function J0(n){let c=n.map(s=>s.route.path).filter(Boolean);return He(c)||"/"}var dm=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function mm(n,c){let s=n;if(typeof s!="string"||!df.test(s))return{absoluteURL:void 0,isExternal:!1,to:s};let r=s,o=!1;if(dm)try{let d=new URL(window.location.href),m=im.test(s)?new URL(T0(s,d.protocol)):new URL(s),b=cl(m.pathname,c);m.origin===d.origin&&b!=null?s=b+m.search+m.hash:o=!0}catch{qe(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:o,to:s}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var ym=["POST","PUT","PATCH","DELETE"];new Set(ym);var F0=["GET",...ym];new Set(F0);var k0=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];function $0(n){try{return k0.includes(new URL(n).protocol)}catch{return!1}}var wa=z.createContext(null);wa.displayName="DataRouter";var Ti=z.createContext(null);Ti.displayName="DataRouterState";var vm=z.createContext(!1);function W0(){return z.useContext(vm)}var pm=z.createContext({isTransitioning:!1});pm.displayName="ViewTransition";var I0=z.createContext(new Map);I0.displayName="Fetchers";var P0=z.createContext(null);P0.displayName="Await";var be=z.createContext(null);be.displayName="Navigation";var Oi=z.createContext(null);Oi.displayName="Location";var Ge=z.createContext({outlet:null,matches:[],isDataRoute:!1});Ge.displayName="Route";var yf=z.createContext(null);yf.displayName="RouteError";var gm="REACT_ROUTER_ERROR",tp="REDIRECT",ep="ROUTE_ERROR_RESPONSE";function lp(n){if(n.startsWith(`${gm}:${tp}:{`))try{let c=JSON.parse(n.slice(28));if(typeof c=="object"&&c&&typeof c.status=="number"&&typeof c.statusText=="string"&&typeof c.location=="string"&&typeof c.reloadDocument=="boolean"&&typeof c.replace=="boolean")return c}catch{}}function ap(n){if(n.startsWith(`${gm}:${ep}:{`))try{let c=JSON.parse(n.slice(40));if(typeof c=="object"&&c&&typeof c.status=="number"&&typeof c.statusText=="string")return new V0(c.status,c.statusText,c.data)}catch{}}function np(n,{relative:c}={}){Lt(Ga(),"useHref() may be used only in the context of a component.");let{basename:s,navigator:r}=z.useContext(be),{hash:o,pathname:d,search:m}=Vn(n,{relative:c}),b=d;return s!=="/"&&(b=d==="/"?s:He([s,d])),r.createHref({pathname:b,search:m,hash:o})}function Ga(){return z.useContext(Oi)!=null}function Be(){return Lt(Ga(),"useLocation() may be used only in the context of a component."),z.useContext(Oi).location}var Sm="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function bm(n){z.useContext(be).static||z.useLayoutEffect(n)}function Ri(){let{isDataRoute:n}=z.useContext(Ge);return n?pp():up()}function up(){Lt(Ga(),"useNavigate() may be used only in the context of a component.");let n=z.useContext(wa),{basename:c,navigator:s}=z.useContext(be),{matches:r}=z.useContext(Ge),{pathname:o}=Be(),d=JSON.stringify(mf(r)),m=z.useRef(!1);return bm(()=>{m.current=!0}),z.useCallback((y,p={})=>{if(qe(m.current,Sm),!m.current)return;if(typeof y=="number"){s.go(y);return}let T=Ei(y,JSON.parse(d),o,p.relative==="path");n==null&&c!=="/"&&(T.pathname=T.pathname==="/"?c:He([c,T.pathname])),(p.replace?s.replace:s.push)(T,p.state,p)},[c,s,d,o,n])}z.createContext(null);function Vn(n,{relative:c}={}){let{matches:s}=z.useContext(Ge),{pathname:r}=Be(),o=JSON.stringify(mf(s));return z.useMemo(()=>Ei(n,JSON.parse(o),r,c==="path"),[n,o,r,c])}function ip(n,c,s){Lt(Ga(),"useRoutes() may be used only in the context of a component.");let{navigator:r}=z.useContext(be),{matches:o}=z.useContext(Ge),d=o[o.length-1],m=d?d.params:{},b=d?d.pathname:"/",y=d?d.pathnameBase:"/",p=d&&d.route;{let q=p&&p.path||"";Tm(b,!p||q.endsWith("*")||q.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${b}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. - -Please change the parent to .`)}let T=Be(),O;O=T;let _=O.pathname||"/",L=_;if(y!=="/"){let q=y.replace(/^\//,"").split("/");L="/"+_.replace(/^\//,"").split("/").slice(q.length).join("/")}let Q=s&&s.state.matches.length?s.state.matches.map(q=>Object.assign(q,{route:s.manifest[q.route.id]||q.route})):cm(n,{pathname:L});return qe(p||Q!=null,`No routes matched location "${O.pathname}${O.search}${O.hash}" `),qe(Q==null||Q[Q.length-1].route.element!==void 0||Q[Q.length-1].route.Component!==void 0||Q[Q.length-1].route.lazy!==void 0,`Matched leaf route at location "${O.pathname}${O.search}${O.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`),op(Q&&Q.map(q=>Object.assign({},q,{params:Object.assign({},m,q.params),pathname:He([y,r.encodeLocation?r.encodeLocation(q.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:q.pathname]),pathnameBase:q.pathnameBase==="/"?y:He([y,r.encodeLocation?r.encodeLocation(q.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:q.pathnameBase])})),o,s)}function cp(){let n=vp(),c=K0(n)?`${n.status} ${n.statusText}`:n instanceof Error?n.message:JSON.stringify(n),s=n instanceof Error?n.stack:null,r="rgba(200,200,200, 0.5)",o={padding:"0.5rem",backgroundColor:r},d={padding:"2px 4px",backgroundColor:r},m=null;return console.error("Error handled by React Router default ErrorBoundary:",n),m=z.createElement(z.Fragment,null,z.createElement("p",null,"💿 Hey developer 👋"),z.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",z.createElement("code",{style:d},"ErrorBoundary")," or"," ",z.createElement("code",{style:d},"errorElement")," prop on your route.")),z.createElement(z.Fragment,null,z.createElement("h2",null,"Unexpected Application Error!"),z.createElement("h3",{style:{fontStyle:"italic"}},c),s?z.createElement("pre",{style:o},s):null,m)}var sp=z.createElement(cp,null),Em=class extends z.Component{constructor(n){super(n),this.state={location:n.location,revalidation:n.revalidation,error:n.error}}static getDerivedStateFromError(n){return{error:n}}static getDerivedStateFromProps(n,c){return c.location!==n.location||c.revalidation!=="idle"&&n.revalidation==="idle"?{error:n.error,location:n.location,revalidation:n.revalidation}:{error:n.error!==void 0?n.error:c.error,location:c.location,revalidation:n.revalidation||c.revalidation}}componentDidCatch(n,c){this.props.onError?this.props.onError(n,c):console.error("React Router caught the following error during render",n)}render(){let n=this.state.error;if(this.context&&typeof n=="object"&&n&&"digest"in n&&typeof n.digest=="string"){const s=ap(n.digest);s&&(n=s)}let c=n!==void 0?z.createElement(Ge.Provider,{value:this.props.routeContext},z.createElement(yf.Provider,{value:n,children:this.props.component})):this.props.children;return this.context?z.createElement(fp,{error:n},c):c}};Em.contextType=vm;var ef=new WeakMap;function fp({children:n,error:c}){let{basename:s}=z.useContext(be);if(typeof c=="object"&&c&&"digest"in c&&typeof c.digest=="string"){let r=lp(c.digest);if(r){let o=ef.get(c);if(o)throw o;let d=mm(r.location,s),m=d.absoluteURL||d.to;if($0(m))throw new Error("Invalid redirect location");if(dm&&!ef.get(c))if(d.isExternal||r.reloadDocument)window.location.href=m;else{const b=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(d.to,{replace:r.replace}));throw ef.set(c,b),b}return z.createElement("meta",{httpEquiv:"refresh",content:`0;url=${m}`})}}return n}function rp({routeContext:n,match:c,children:s}){let r=z.useContext(wa);return r&&r.static&&r.staticContext&&(c.route.errorElement||c.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=c.route.id),z.createElement(Ge.Provider,{value:n},s)}function op(n,c=[],s){let r=s?.state;if(n==null){if(!r)return null;if(r.errors)n=r.matches;else if(c.length===0&&!r.initialized&&r.matches.length>0)n=r.matches;else return null}let o=n,d=r?.errors;if(d!=null){let T=o.findIndex(O=>O.route.id&&d?.[O.route.id]!==void 0);Lt(T>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(d).join(",")}`),o=o.slice(0,Math.min(o.length,T+1))}let m=!1,b=-1;if(s&&r){m=r.renderFallback;for(let T=0;T=0?o=o.slice(0,b+1):o=[o[0]];break}}}}let y=s?.onError,p=r&&y?(T,O)=>{y(T,{location:r.location,params:r.matches?.[0]?.params??{},pattern:J0(r.matches),errorInfo:O})}:void 0;return o.reduceRight((T,O,_)=>{let L,Q=!1,B=null,q=null;r&&(L=d&&O.route.id?d[O.route.id]:void 0,B=O.route.errorElement||sp,m&&(b<0&&_===0?(Tm("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),Q=!0,q=null):b===_&&(Q=!0,q=O.route.hydrateFallbackElement||null)));let X=c.concat(o.slice(0,_+1)),G=()=>{let V;return L?V=B:Q?V=q:O.route.Component?V=z.createElement(O.route.Component,null):O.route.element?V=O.route.element:V=T,z.createElement(rp,{match:O,routeContext:{outlet:T,matches:X,isDataRoute:r!=null},children:V})};return r&&(O.route.ErrorBoundary||O.route.errorElement||_===0)?z.createElement(Em,{location:r.location,revalidation:r.revalidation,component:B,error:L,children:G(),routeContext:{outlet:null,matches:X,isDataRoute:!0},onError:p}):G()},null)}function vf(n){return`${n} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function hp(n){let c=z.useContext(wa);return Lt(c,vf(n)),c}function dp(n){let c=z.useContext(Ti);return Lt(c,vf(n)),c}function mp(n){let c=z.useContext(Ge);return Lt(c,vf(n)),c}function pf(n){let c=mp(n),s=c.matches[c.matches.length-1];return Lt(s.route.id,`${n} can only be used on routes that contain a unique "id"`),s.route.id}function yp(){return pf("useRouteId")}function vp(){let n=z.useContext(yf),c=dp("useRouteError"),s=pf("useRouteError");return n!==void 0?n:c.errors?.[s]}function pp(){let{router:n}=hp("useNavigate"),c=pf("useNavigate"),s=z.useRef(!1);return bm(()=>{s.current=!0}),z.useCallback(async(o,d={})=>{qe(s.current,Sm),s.current&&(typeof o=="number"?await n.navigate(o):await n.navigate(o,{fromRouteId:c,...d}))},[n,c])}var Zd={};function Tm(n,c,s){!c&&!Zd[n]&&(Zd[n]=!0,qe(!1,s))}z.memo(gp);function gp({routes:n,manifest:c,future:s,state:r,isStatic:o,onError:d}){return ip(n,void 0,{manifest:c,state:r,isStatic:o,onError:d})}function Sp({to:n,replace:c,state:s,relative:r}){Lt(Ga()," may be used only in the context of a component.");let{static:o}=z.useContext(be);qe(!o," must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.");let{matches:d}=z.useContext(Ge),{pathname:m}=Be(),b=Ri(),y=Ei(n,mf(d),m,r==="path"),p=JSON.stringify(y);return z.useEffect(()=>{b(JSON.parse(p),{replace:c,state:s,relative:r})},[b,p,r,c,s]),null}function bp({basename:n="/",children:c=null,location:s,navigationType:r="POP",navigator:o,static:d=!1,useTransitions:m}){Lt(!Ga(),"You cannot render a inside another . You should never have more than one in your app.");let b=n.replace(/^\/*/,"/"),y=z.useMemo(()=>({basename:b,navigator:o,static:d,useTransitions:m,future:{}}),[b,o,d,m]);typeof s=="string"&&(s=Zn(s));let{pathname:p="/",search:T="",hash:O="",state:_=null,key:L="default",mask:Q}=s,B=z.useMemo(()=>{let q=cl(p,b);return q==null?null:{location:{pathname:q,search:T,hash:O,state:_,key:L,mask:Q},navigationType:r}},[b,p,T,O,_,L,r,Q]);return qe(B!=null,` is not able to match the URL "${p}${T}${O}" because it does not start with the basename, so the won't render anything.`),B==null?null:z.createElement(be.Provider,{value:y},z.createElement(Oi.Provider,{children:c,value:B}))}var di="get",mi="application/x-www-form-urlencoded";function Ai(n){return typeof HTMLElement<"u"&&n instanceof HTMLElement}function Ep(n){return Ai(n)&&n.tagName.toLowerCase()==="button"}function Tp(n){return Ai(n)&&n.tagName.toLowerCase()==="form"}function Op(n){return Ai(n)&&n.tagName.toLowerCase()==="input"}function Rp(n){return!!(n.metaKey||n.altKey||n.ctrlKey||n.shiftKey)}function Ap(n,c){return n.button===0&&(!c||c==="_self")&&!Rp(n)}var oi=null;function Cp(){if(oi===null)try{new FormData(document.createElement("form"),0),oi=!1}catch{oi=!0}return oi}var zp=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function lf(n){return n!=null&&!zp.has(n)?(qe(!1,`"${n}" is not a valid \`encType\` for \`
    \`/\`\` and will default to "${mi}"`),null):n}function Mp(n,c){let s,r,o,d,m;if(Tp(n)){let b=n.getAttribute("action");r=b?cl(b,c):null,s=n.getAttribute("method")||di,o=lf(n.getAttribute("enctype"))||mi,d=new FormData(n)}else if(Ep(n)||Op(n)&&(n.type==="submit"||n.type==="image")){let b=n.form;if(b==null)throw new Error('Cannot submit a