nandi/oripublic Fork 0
5d476ff5f3dc3b7db3afaccaedc7d9661981449e
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
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
/**
 * Workspace is the right-hand panel: the file tree next to the Preview /
 * Editor / Terminal tabs. Panes are hidden rather than unmounted when
 * another tab is active — the terminal's shell session and the editor's
 * unsaved state survive tab switches (and the panel being collapsed).
 * The file tree column is resizable: its width lives in the workspace
 * store and reaches the CSS through the `--filetree-width` custom property.
 *
 * @example
 * <Workspace />  // visibility is driven by the workspace store
 */

import { type CSSProperties, type ReactNode, useEffect, useState } from "react";
import {
	MAX_FILE_TREE_WIDTH,
	MIN_FILE_TREE_WIDTH,
	type WorkspaceTab,
	resetFileTreeWidth,
	setFileTreeWidth,
	showTab,
	useWorkspace,
} from "../workspace";
import { EditorPane } from "./EditorPane";
import { FileTree } from "./FileTree";
import { PreviewPane } from "./PreviewPane";
import { ResizeHandle } from "./ResizeHandle";
import { TerminalPane } from "./TerminalPane";

const tabs: { id: WorkspaceTab; label: string }[] = [
	{ id: "preview", label: "Preview" },
	{ id: "editor", label: "Editor" },
	{ id: "terminal", label: "Terminal" },
];

export function Workspace() {
	const workspace = useWorkspace();
	// The terminal (and its shell) starts on first activation only.
	const [terminalStarted, setTerminalStarted] = useState(false);
	useEffect(() => {
		if (workspace.open && workspace.tab === "terminal") {
			setTerminalStarted(true);
		}
	}, [workspace.open, workspace.tab]);

	const style = {
		"--filetree-width": `${workspace.fileTreeWidth}px`,
	} as CSSProperties;

	return (
		<aside
			className={`workspace${workspace.open ? "" : " workspace-hidden"}`}
			aria-label="workspace"
			style={style}
		>
			<FileTree />
			<ResizeHandle
				label="Resize the file tree"
				width={workspace.fileTreeWidth}
				min={MIN_FILE_TREE_WIDTH}
				max={MAX_FILE_TREE_WIDTH}
				onResize={setFileTreeWidth}
				onReset={resetFileTreeWidth}
			/>
			<div className="workspace-main">
				<nav className="workspace-tabs" aria-label="workspace tabs">
					{tabs.map((tab) => (
						<button
							key={tab.id}
							type="button"
							className={`workspace-tab${workspace.tab === tab.id ? " workspace-tab-active" : ""}`}
							onClick={() => showTab(tab.id)}
						>
							{tab.label}
						</button>
					))}
				</nav>
				<div className="workspace-content">
					<Pane visible={workspace.tab === "preview"}>
						<PreviewPane path={workspace.previewPath} />
					</Pane>
					<Pane visible={workspace.tab === "editor"}>
						<EditorPane path={workspace.editorPath} />
					</Pane>
					<Pane visible={workspace.tab === "terminal"}>
						{terminalStarted ? (
							<TerminalPane />
						) : (
							<p className="pane-empty">starting</p>
						)}
					</Pane>
				</div>
			</div>
		</aside>
	);
}

/** Pane hides its children without unmounting them. */
function Pane({
	visible,
	children,
}: { visible: boolean; children: ReactNode }) {
	return (
		<div className={`workspace-pane${visible ? "" : " workspace-pane-hidden"}`}>
			{children}
		</div>
	);
}