Download web/src/components/Shell.tsx from Anish-g/btc-intel: direct link, hf CLI and curl.
- Browser
- Download file 7.68 kB
-
https://huggingface.co/spaces/Anish-g/btc-intel/resolve/main/web/src/components/Shell.tsx
- Command line
-
hf download hf://spaces/Anish-g/btc-intel/web/src/components/Shell.tsx
-
curl -L -o Shell.tsx https://huggingface.co/spaces/Anish-g/btc-intel/resolve/main/web/src/components/Shell.tsx
7.68 kB
| /** The frame every page sits in: a left rail with the wordmark, the sections, | |
| * an on-this-page list, and the theme toggle. Content to the right. */ | |
| import { useEffect, useState, type ReactNode } from "react"; | |
| import { Link, NavLink, useLocation, useParams } from "react-router-dom"; | |
| import { useBuildCheck } from "../lib/useBuildCheck"; | |
| import { useTheme } from "../lib/theme"; | |
| import { forgetTokens } from "../lib/tokens"; | |
| import { CAPABILITIES, stageFor } from "../content/capabilities"; | |
| import { CommandPalette } from "./CommandPalette"; | |
| import { DotArt } from "./DotArt"; | |
| /** ⌘ on a Mac, Ctrl everywhere else. Showing the wrong one teaches the wrong | |
| * shortcut, which is worse than showing none. */ | |
| const MOD = typeof navigator !== "undefined" && /Mac|iP(hone|ad)/.test(navigator.platform) | |
| ? "\u2318K" | |
| : "Ctrl K"; | |
| const PAGES = [ | |
| { to: "/", label: "Overview" }, | |
| { to: "/case", label: "This case" }, | |
| { to: "/monitor", label: "Live monitor" }, | |
| { to: "/alerts", label: "Alert queue" }, | |
| { to: "/investigate", label: "Investigation graph" }, | |
| { to: "/peers", label: "Peer lookup" }, | |
| { to: "/redteam", label: "Red team" }, | |
| { to: "/custody", label: "Chain of custody" }, | |
| ]; | |
| export interface Anchor { | |
| id: string; | |
| label: string; | |
| } | |
| export function Shell({ | |
| anchors, | |
| children, | |
| variant = "rail", | |
| }: { | |
| anchors?: Anchor[]; | |
| children: ReactNode; | |
| /** "top" puts the navigation in a bar across the page, for the showcase. */ | |
| variant?: "rail" | "top"; | |
| }) { | |
| const [theme, toggleTheme] = useTheme(); | |
| const build = useBuildCheck(); | |
| const [paletteOpen, setPaletteOpen] = useState(false); | |
| const [activeAnchor, setActiveAnchor] = useState<string | null>(null); | |
| const { pathname } = useLocation(); | |
| const params = useParams(); | |
| const stage = variant === "rail" ? stageFor(pathname) : null; | |
| // A case and a transaction are places too. Showing the open one in the rail | |
| // is how a reader learns the console has more than two pages. | |
| const open = params.id | |
| ? { label: `Case ${params.id.slice(0, 10)}…`, to: pathname } | |
| : params.txid | |
| ? { label: `Transaction ${params.txid.slice(0, 8)}…`, to: pathname } | |
| : params.peer | |
| ? { label: `Peer ${params.peer.slice(0, 15)}`, to: pathname } | |
| : null; | |
| useEffect(() => { | |
| const onKey = (event: KeyboardEvent) => { | |
| if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { | |
| event.preventDefault(); | |
| setPaletteOpen(true); | |
| } | |
| }; | |
| addEventListener("keydown", onKey); | |
| return () => removeEventListener("keydown", onKey); | |
| }, []); | |
| // Which section the reader is in. Observed, not computed on scroll, so the | |
| // rail never costs a frame while the table is being worked. | |
| useEffect(() => { | |
| if (!anchors?.length) return; | |
| setActiveAnchor(anchors[0].id); | |
| const observer = new IntersectionObserver( | |
| (entries) => { | |
| const seen = entries.filter((e) => e.isIntersecting); | |
| if (seen.length) setActiveAnchor(seen[0].target.id); | |
| }, | |
| { rootMargin: "-10% 0px -70% 0px" }, | |
| ); | |
| for (const anchor of anchors) { | |
| const node = document.getElementById(anchor.id); | |
| if (node) observer.observe(node); | |
| } | |
| return () => observer.disconnect(); | |
| }, [anchors, pathname]); | |
| return ( | |
| <div className={variant === "top" ? "shell shell-top" : "shell"}> | |
| <header className={variant === "top" ? "topbar" : "rail"}> | |
| <NavLink to="/" className="wordmark"> | |
| <span className="wordmark-mark" aria-hidden="true" /> | |
| btc-intel | |
| </NavLink> | |
| <nav className="rail-nav" aria-label="Sections"> | |
| {PAGES.map((page) => ( | |
| <NavLink key={page.to} to={page.to} end={page.to === "/"} className="rail-link"> | |
| {page.label} | |
| </NavLink> | |
| ))} | |
| {open && ( | |
| <NavLink to={open.to} className="rail-link" title={params.id ?? params.txid ?? params.peer}> | |
| <span className="mono" style={{ fontSize: "var(--fs-small)" }}>{open.label}</span> | |
| </NavLink> | |
| )} | |
| </nav> | |
| {variant === "rail" && anchors?.length ? ( | |
| <> | |
| <hr className="rail-divider" /> | |
| <p className="label">On this page</p> | |
| <nav className="subnav" aria-label="On this page"> | |
| {anchors.map((anchor) => ( | |
| <a | |
| key={anchor.id} | |
| href={`#${anchor.id}`} | |
| aria-current={activeAnchor === anchor.id} | |
| > | |
| {anchor.label} | |
| </a> | |
| ))} | |
| </nav> | |
| </> | |
| ) : null} | |
| <div className="rail-foot"> | |
| <button type="button" className="btn btn-ghost bracket" onClick={() => setPaletteOpen(true)}> | |
| Search <span className="muted">{MOD}</span> | |
| </button> | |
| <button | |
| type="button" | |
| className="btn btn-quiet" | |
| onClick={() => { | |
| forgetTokens(); | |
| toggleTheme(); | |
| }} | |
| aria-label={`Switch to ${theme === "dark" ? "light" : "dark"} theme`} | |
| > | |
| <span className="theme-swatch" aria-hidden="true" /> | |
| {theme === "dark" ? "light" : "dark"} | |
| </button> | |
| {variant === "rail" && ( | |
| <p className="label" style={{ marginTop: "var(--sp-2)" }}> | |
| Offline, synthetic data | |
| </p> | |
| )} | |
| </div> | |
| </header> | |
| <main className="content"> | |
| {(build.mismatch || build.unreachable) && ( | |
| <div className="notice build-notice" role="alert"> | |
| <span aria-hidden="true">⚠</span> | |
| <div> | |
| <p className="label"> | |
| {build.mismatch ? "The API is a different build" : "The API is not answering"} | |
| </p> | |
| <p className="soft" style={{ marginTop: "var(--sp-1)" }}> | |
| {build.mismatch ? ( | |
| <> | |
| This page was built from <code className="mono">{build.ours}</code>; the | |
| server at <code className="mono">{"/version"}</code> reports{" "} | |
| <code className="mono">{build.theirs}</code>, started{" "} | |
| {build.startedAt?.replace("T", " ").replace("+00:00", " UTC")}. Restart it | |
| with <code className="mono">offline/run_offline.sh</code> before trusting | |
| anything on screen. | |
| </> | |
| ) : ( | |
| <> | |
| Nothing answered <code className="mono">/version</code>. Either the API is | |
| not running, or it is an older build without that endpoint — start it with{" "} | |
| <code className="mono">offline/run_offline.sh</code>. | |
| </> | |
| )} | |
| </p> | |
| </div> | |
| </div> | |
| )} | |
| {stage && ( | |
| <Link to={`/#${stage.id}`} className="stage-banner" viewTransition> | |
| <DotArt name={stage.art} mode={stage.mode} cell={4} className="stage-banner-art" /> | |
| <span className="stage-banner-copy"> | |
| <span className="stage-num"> | |
| stage {String(CAPABILITIES.indexOf(stage) + 1).padStart(2, "0")} of{" "} | |
| {CAPABILITIES.length} | |
| </span> | |
| <span className="stage-banner-name">{stage.name}</span> | |
| <span className="stage-banner-title">{stage.title}</span> | |
| </span> | |
| </Link> | |
| )} | |
| {children} | |
| </main> | |
| <CommandPalette open={paletteOpen} onClose={() => setPaletteOpen(false)} /> | |
| </div> | |
| ); | |
| } | |