diff --git a/web/src/components/editor/EditorPanel.tsx b/web/src/components/editor/EditorPanel.tsx new file mode 100644 index 0000000..8e5adaa --- /dev/null +++ b/web/src/components/editor/EditorPanel.tsx @@ -0,0 +1,39 @@ +import Editor, { type OnValidate } from "@monaco-editor/react"; +import { useState } from "react"; + +import type { MockFileNode } from "@/pages/workspace/mock-data"; + +interface EditorPanelProps { + file?: MockFileNode; +} + +export function EditorPanel({ file }: EditorPanelProps) { + const [, setMarkers] = useState[0]>([]); + + if (!file || file.kind !== "file") { + return ( +
+
+

No file selected

+
+
+ ); + } + + return ( +
+ +
+ ); +} diff --git a/web/src/components/layout/WorkspaceShell.tsx b/web/src/components/layout/WorkspaceShell.tsx index 4dc3b91..a8b698c 100644 --- a/web/src/components/layout/WorkspaceShell.tsx +++ b/web/src/components/layout/WorkspaceShell.tsx @@ -3,6 +3,8 @@ import { PanelLeftClose, PanelLeftOpen, Terminal } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; +import { EditorPanel } from "@/components/editor/EditorPanel"; +import { TerminalPanel } from "@/components/terminal/TerminalPanel"; import { FileExplorerPanel } from "@/components/workspace/FileExplorerPanel"; import { StatusBar } from "@/components/workspace/StatusBar"; import { useWorkspaceUiStore } from "@/lib/store/workspace-ui-store"; @@ -69,24 +71,9 @@ export function WorkspaceShell({ files }: WorkspaceShellProps) { {/* Main content area */} - {/* Editor placeholder */} + {/* Editor */} -
-
-

Editor placeholder

-
-

- Active file:{" "} - {activeFilePath} -

-

- {activeFile - ? `${activeFile.name} | ${activeFile.language ?? "plain text"} | ${activeFile.kind}` - : "No matching mock file"} -

-
-
-
+
{/* Terminal panel */} @@ -94,9 +81,7 @@ export function WorkspaceShell({ files }: WorkspaceShellProps) { <> -
- Terminal placeholder -
+
)} diff --git a/web/src/components/terminal/TerminalPanel.tsx b/web/src/components/terminal/TerminalPanel.tsx new file mode 100644 index 0000000..77c269b --- /dev/null +++ b/web/src/components/terminal/TerminalPanel.tsx @@ -0,0 +1,71 @@ +import { FitAddon } from "@xterm/addon-fit"; +import { Terminal } from "@xterm/xterm"; +import { useEffect, useRef, useState } from "react"; + +export function TerminalPanel() { + const containerRef = useRef(null); + const [failed, setFailed] = useState(false); + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + let terminal: Terminal | undefined; + let fitAddon: FitAddon | undefined; + + try { + terminal = new Terminal({ + cursorBlink: true, + convertEol: true, + fontFamily: + 'Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', + fontSize: 12, + theme: { + background: "#171717", + foreground: "#e5e5e5", + }, + }); + fitAddon = new FitAddon(); + terminal.loadAddon(fitAddon); + terminal.open(container); + terminal.writeln("codespace terminal"); + terminal.writeln("Mock session initialized. No process is attached."); + terminal.writeln(""); + terminal.writeln("$ pnpm test"); + terminal.writeln("mock: 3 files checked, 0 failures"); + terminal.write("$ "); + fitAddon.fit(); + } catch { + setFailed(true); + terminal?.dispose(); + fitAddon?.dispose(); + return; + } + + const handleResize = () => { + fitAddon?.fit(); + }; + + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + terminal?.dispose(); + fitAddon?.dispose(); + }; + }, []); + + if (failed) { + return ( +
+ Terminal failed to initialize +
+ ); + } + + return ( +
+
+
+ ); +} diff --git a/web/src/styles/index.css b/web/src/styles/index.css index 855a920..fe6f1fd 100644 --- a/web/src/styles/index.css +++ b/web/src/styles/index.css @@ -1,5 +1,6 @@ @import "tailwindcss"; @import "tw-animate-css"; +@import "@xterm/xterm/css/xterm.css"; @custom-variant dark (&:is(.dark *));