Skip to content

Commit 41a080e

Browse files
authored
Merge pull request #2 from hyperbrowserai/file-system-component
File system component
2 parents a978f8c + e4d3ba0 commit 41a080e

24 files changed

Lines changed: 2712 additions & 1840 deletions

‎README.md‎

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ TypeScript-first React component package scaffold with:
1919

2020
- `npm run typecheck`: Validate TypeScript.
2121
- `npm run build`: Build ESM, CJS, and declaration outputs.
22-
- `npm run test:visual`: Build package and start visual harness server.
22+
- `npm run test:visual`: Start the visual harness server.
2323

2424
## Sandbox terminal usage
2525

@@ -243,6 +243,96 @@ Terminal primitives support:
243243

244244
You can also create a reusable spreadable config with `createTerminalTheme(...)`.
245245

246+
## Filesystem workspace usage
247+
248+
The public filesystem path is built from two layers:
249+
250+
- `HyperbrowserFileWorkspace`: ready-to-use Hyperbrowser-backed filesystem browser
251+
- `FileWorkspace`: lower-level browser shell when you want to bring your own adapter
252+
253+
Import the packaged stylesheet once in your app entrypoint:
254+
255+
```tsx
256+
import "@hyperbrowser/ui/styles.css";
257+
```
258+
259+
### Basic `HyperbrowserFileWorkspace` example
260+
261+
```tsx
262+
import { useCallback } from "react";
263+
import {
264+
createFileWorkspaceTheme,
265+
HyperbrowserFileWorkspace,
266+
type HyperbrowserFilesystemBrowserAuthParams,
267+
} from "@hyperbrowser/ui";
268+
import "@hyperbrowser/ui/styles.css";
269+
270+
export function SandboxFiles({
271+
sandboxId,
272+
}: {
273+
sandboxId: string;
274+
}) {
275+
const getRuntimeBrowserAuth = useCallback(
276+
async ({
277+
browserAuthEndpoint,
278+
sandboxId: resolvedSandboxId,
279+
signal,
280+
}: HyperbrowserFilesystemBrowserAuthParams) => {
281+
if (!browserAuthEndpoint) {
282+
throw new Error("Sandbox filesystem auth endpoint is unavailable.");
283+
}
284+
285+
const response = await fetch(browserAuthEndpoint, {
286+
method: "POST",
287+
signal,
288+
});
289+
290+
if (!response.ok) {
291+
throw new Error(
292+
`Failed to get filesystem auth for sandbox ${resolvedSandboxId ?? sandboxId}.`,
293+
);
294+
}
295+
296+
return response.json();
297+
},
298+
[sandboxId],
299+
);
300+
301+
const filesystemTheme = createFileWorkspaceTheme("basic", {
302+
appearance: "dark",
303+
});
304+
305+
return (
306+
<HyperbrowserFileWorkspace
307+
{...filesystemTheme}
308+
getRuntimeBrowserAuth={getRuntimeBrowserAuth}
309+
sandboxId={sandboxId}
310+
style={{ minHeight: 720 }}
311+
title="Sandbox Files"
312+
workspacePath="/workspace"
313+
/>
314+
);
315+
}
316+
```
317+
318+
Notes:
319+
320+
- `getRuntimeBrowserAuth(...)` receives `{ signal, sandboxId, browserAuthEndpoint }`.
321+
- `sandboxId` is the standard customer path.
322+
- `runtimeBaseUrl + bootstrapUrl` is also supported for already-bootstrapped runtime sessions.
323+
- `apiBaseUrl` is only needed when you want the library to call the control-plane endpoint directly or when you need a non-default control-plane base.
324+
325+
### Filesystem theming
326+
327+
Filesystem theming supports:
328+
329+
- `preset`: one of `basic`, `atlas`, `ledger`
330+
- `appearance`: `"dark"` or `"light"`
331+
- `chromeTheme`: partial chrome overrides
332+
- `editorTheme`: partial editor typography overrides
333+
334+
You can create a reusable spreadable config with `createFileWorkspaceTheme(...)`.
335+
246336
## VNC component usage
247337

248338
`HyperbrowserVncViewer` renders a noVNC viewer using a Hyperbrowser session token and

‎package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"default": "./dist/esm/index.js"
2323
},
2424
"./styles.css": "./dist/styles/styles.css",
25+
"./filesystem.css": "./dist/styles/filesystem.css",
2526
"./terminal-core.css": "./dist/styles/terminal-core.css",
2627
"./terminal.css": "./dist/styles/terminal.css",
2728
"./package.json": "./package.json"
@@ -34,7 +35,7 @@
3435
"build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:types && node ./scripts/write-dist-package-jsons.mjs && node ./scripts/copy-static-assets.mjs",
3536
"prepublishOnly": "npm run build",
3637
"typecheck": "tsc -p ./tsconfig.base.json --noEmit",
37-
"test:visual": "npm run build && vite --config ./tests/visual/vite.config.mjs"
38+
"test:visual": "vite --config ./tests/visual/vite.config.mjs"
3839
},
3940
"peerDependencies": {
4041
"react": ">=18 <20",
@@ -44,7 +45,6 @@
4445
"@xterm/addon-fit": "^0.11.0",
4546
"@xterm/xterm": "^6.0.0",
4647
"hls.js": "^1.6.13",
47-
"monaco-editor": "^0.52.2",
4848
"react-vnc": "^3.2.0"
4949
},
5050
"devDependencies": {

‎scripts/copy-static-assets.mjs‎

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ const sourceTerminalCoreStylesPath = path.join(
1111
"terminal-core.css"
1212
);
1313
const sourceTerminalStylesPath = path.join(rootDir, "src", "styles", "terminal.css");
14+
const sourceFilesystemStylesPath = path.join(
15+
rootDir,
16+
"src",
17+
"styles",
18+
"filesystem.css"
19+
);
1420
const xtermStylesPath = path.join(
1521
rootDir,
1622
"node_modules",
@@ -23,14 +29,22 @@ const terminalCoreBundle = (xtermStyles, terminalCoreStyles) =>
2329
`${xtermStyles.trim()}\n\n${terminalCoreStyles.trim()}\n`;
2430
const terminalBundle = (xtermStyles, terminalCoreStyles, terminalStyles) =>
2531
`${terminalCoreBundle(xtermStyles, terminalCoreStyles).trim()}\n\n${terminalStyles.trim()}\n`;
32+
const fullStylesBundle = (
33+
xtermStyles,
34+
terminalCoreStyles,
35+
terminalStyles,
36+
filesystemStyles
37+
) => `${terminalBundle(xtermStyles, terminalCoreStyles, terminalStyles).trim()}\n\n${filesystemStyles.trim()}\n`;
2638

2739
await mkdir(distStylesDir, { recursive: true });
2840

29-
const [xtermStyles, terminalCoreStyles, terminalStyles] = await Promise.all([
30-
readFile(xtermStylesPath, "utf8"),
31-
readFile(sourceTerminalCoreStylesPath, "utf8"),
32-
readFile(sourceTerminalStylesPath, "utf8"),
33-
]);
41+
const [xtermStyles, terminalCoreStyles, terminalStyles, filesystemStyles] =
42+
await Promise.all([
43+
readFile(xtermStylesPath, "utf8"),
44+
readFile(sourceTerminalCoreStylesPath, "utf8"),
45+
readFile(sourceTerminalStylesPath, "utf8"),
46+
readFile(sourceFilesystemStylesPath, "utf8"),
47+
]);
3448

3549
await Promise.all([
3650
writeFile(
@@ -43,9 +57,19 @@ await Promise.all([
4357
terminalBundle(xtermStyles, terminalCoreStyles, terminalStyles),
4458
"utf8"
4559
),
60+
writeFile(
61+
path.join(distStylesDir, "filesystem.css"),
62+
`${filesystemStyles.trim()}\n`,
63+
"utf8"
64+
),
4665
writeFile(
4766
path.join(distStylesDir, "styles.css"),
48-
terminalBundle(xtermStyles, terminalCoreStyles, terminalStyles),
67+
fullStylesBundle(
68+
xtermStyles,
69+
terminalCoreStyles,
70+
terminalStyles,
71+
filesystemStyles
72+
),
4973
"utf8"
5074
),
5175
]);

0 commit comments

Comments
 (0)