返回 CodeWhale
install-binary.tsx
根目录 / web / components / install-binary.tsx
1 "use client";
2
3 import { useEffect, useState } from "react";
4 import {
5 detectFromBrowserSignals,
6 type Arch,
7 type UserAgentArchitecture,
8 } from "@/lib/install-platform";
9 import { SNIPPETS, VERIFY } from "@/lib/install-binary-snippets";
10 import { InstallCodeBlock } from "./install-code-block";
11
12 const LABELS: Record<Arch, string> = {
13 "macos-arm64": "macOS · Apple Silicon",
14 "macos-x64": "macOS · Intel",
15 "linux-x64": "Linux · x64",
16 "linux-arm64": "Linux · arm64",
17 "windows-x64": "Windows · x64",
18 "windows-arm64": "Windows · arm64",
19 };
20
21 interface NavigatorWithUserAgentData extends Navigator {
22 userAgentData?: {
23 getHighEntropyValues(hints: string[]): Promise<UserAgentArchitecture>;
24 };
25 }
26
27 async function detect(): Promise<Arch> {
28 if (typeof navigator === "undefined") return "macos-arm64";
29 const browserNavigator = navigator as NavigatorWithUserAgentData;
30 let architecture: UserAgentArchitecture | undefined;
31 if (navigator.userAgent.toLowerCase().includes("win")) {
32 try {
33 architecture = await browserNavigator.userAgentData?.getHighEntropyValues([
34 "architecture",
35 "bitness",
36 ]);
37 } catch {
38 // The manual platform buttons and frozen-UA fallback remain available.
39 }
40 }
41 return detectFromBrowserSignals(navigator.userAgent, architecture);
42 }
43
44 interface Props {
45 copyLabel?: string;
46 copiedLabel?: string;
47 verifyHeading?: string;
48 }
49
50 export function InstallBinary({ copyLabel, copiedLabel, verifyHeading = "Verify checksum" }: Props) {
51 const [arch, setArch] = useState<Arch>("macos-arm64");
52
53 useEffect(() => {
54 let active = true;
55 void detect().then((detected) => {
56 if (active) setArch(detected);
57 });
58 return () => {
59 active = false;
60 };
61 }, []);
62
63 return (
64 <div>
65 <div className="flex flex-wrap gap-0 mb-3 hairline-t hairline-b hairline-l hairline-r">
66 {(Object.keys(SNIPPETS) as Arch[]).map((a, i) => (
67 <button
68 key={a}
69 onClick={() => setArch(a)}
70 className={`px-3 py-1.5 font-mono text-[0.7rem] tracking-wider transition-colors ${
71 i > 0 ? "hairline-l" : ""
72 } ${arch === a ? "bg-ink text-paper" : "bg-paper hover:bg-paper-deep"}`}
73 >
74 {LABELS[a]}
75 </button>
76 ))}
77 </div>
78
79 <InstallCodeBlock cmd={SNIPPETS[arch]} copyLabel={copyLabel} copiedLabel={copiedLabel} />
80
81 <div className="mt-4">
82 <div className="eyebrow mb-2">{verifyHeading}</div>
83 <InstallCodeBlock cmd={VERIFY[arch]} copyLabel={copyLabel} copiedLabel={copiedLabel} />
84 </div>
85 </div>
86 );
87 }
88
88 lines Plain Text