nandi/oripublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

Workspace.tsx · 107 lines · 3.0 KBTypeScript Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1/**
2 * Workspace is the right-hand panel: the file tree next to the Preview /
3 * Editor / Terminal tabs. Panes are hidden rather than unmounted when
4 * another tab is active — the terminal's shell session and the editor's
5 * unsaved state survive tab switches (and the panel being collapsed).
6 * The file tree column is resizable: its width lives in the workspace
7 * store and reaches the CSS through the `--filetree-width` custom property.
8 *
9 * @example
10 * <Workspace /> // visibility is driven by the workspace store
11 */
12
13import { type CSSProperties, type ReactNode, useEffect, useState } from "react";
14import {
15 MAX_FILE_TREE_WIDTH,
16 MIN_FILE_TREE_WIDTH,
17 type WorkspaceTab,
18 resetFileTreeWidth,
19 setFileTreeWidth,
20 showTab,
21 useWorkspace,
22} from "../workspace";
23import { EditorPane } from "./EditorPane";
24import { FileTree } from "./FileTree";
25import { PreviewPane } from "./PreviewPane";
26import { ResizeHandle } from "./ResizeHandle";
27import { TerminalPane } from "./TerminalPane";
28
29const tabs: { id: WorkspaceTab; label: string }[] = [
30 { id: "preview", label: "Preview" },
31 { id: "editor", label: "Editor" },
32 { id: "terminal", label: "Terminal" },
33];
34
35export function Workspace() {
36 const workspace = useWorkspace();
37 // The terminal (and its shell) starts on first activation only.
38 const [terminalStarted, setTerminalStarted] = useState(false);
39 useEffect(() => {
40 if (workspace.open && workspace.tab === "terminal") {
41 setTerminalStarted(true);
42 }
43 }, [workspace.open, workspace.tab]);
44
45 const style = {
46 "--filetree-width": `${workspace.fileTreeWidth}px`,
47 } as CSSProperties;
48
49 return (
50 <aside
51 className={`workspace${workspace.open ? "" : " workspace-hidden"}`}
52 aria-label="workspace"
53 style={style}
54 >
55 <FileTree />
56 <ResizeHandle
57 label="Resize the file tree"
58 width={workspace.fileTreeWidth}
59 min={MIN_FILE_TREE_WIDTH}
60 max={MAX_FILE_TREE_WIDTH}
61 onResize={setFileTreeWidth}
62 onReset={resetFileTreeWidth}
63 />
64 <div className="workspace-main">
65 <nav className="workspace-tabs" aria-label="workspace tabs">
66 {tabs.map((tab) => (
67 <button
68 key={tab.id}
69 type="button"
70 className={`workspace-tab${workspace.tab === tab.id ? " workspace-tab-active" : ""}`}
71 onClick={() => showTab(tab.id)}
72 >
73 {tab.label}
74 </button>
75 ))}
76 </nav>
77 <div className="workspace-content">
78 <Pane visible={workspace.tab === "preview"}>
79 <PreviewPane path={workspace.previewPath} />
80 </Pane>
81 <Pane visible={workspace.tab === "editor"}>
82 <EditorPane path={workspace.editorPath} />
83 </Pane>
84 <Pane visible={workspace.tab === "terminal"}>
85 {terminalStarted ? (
86 <TerminalPane />
87 ) : (
88 <p className="pane-empty">starting</p>
89 )}
90 </Pane>
91 </div>
92 </div>
93 </aside>
94 );
95}
96
97/** Pane hides its children without unmounting them. */
98function Pane({
99 visible,
100 children,
101}: { visible: boolean; children: ReactNode }) {
102 return (
103 <div className={`workspace-pane${visible ? "" : " workspace-pane-hidden"}`}>
104 {children}
105 </div>
106 );
107}