feat: add web editor and terminal panels
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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<Parameters<OnValidate>[0]>([]);
|
||||||
|
|
||||||
|
if (!file || file.kind !== "file") {
|
||||||
|
return (
|
||||||
|
<section className="flex h-full w-full items-center justify-center bg-background text-sm text-muted-foreground">
|
||||||
|
<div className="text-center">
|
||||||
|
<p>No file selected</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="h-full w-full overflow-hidden bg-background">
|
||||||
|
<Editor
|
||||||
|
key={file.path}
|
||||||
|
theme="vs-dark"
|
||||||
|
language={file.language ?? "typescript"}
|
||||||
|
value={file.content ?? ""}
|
||||||
|
onValidate={setMarkers}
|
||||||
|
options={{
|
||||||
|
automaticLayout: true,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
readOnly: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ import { PanelLeftClose, PanelLeftOpen, Terminal } from "lucide-react";
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Separator } from "@/components/ui/separator";
|
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 { FileExplorerPanel } from "@/components/workspace/FileExplorerPanel";
|
||||||
import { StatusBar } from "@/components/workspace/StatusBar";
|
import { StatusBar } from "@/components/workspace/StatusBar";
|
||||||
import { useWorkspaceUiStore } from "@/lib/store/workspace-ui-store";
|
import { useWorkspaceUiStore } from "@/lib/store/workspace-ui-store";
|
||||||
@@ -69,24 +71,9 @@ export function WorkspaceShell({ files }: WorkspaceShellProps) {
|
|||||||
{/* Main content area */}
|
{/* Main content area */}
|
||||||
<Panel>
|
<Panel>
|
||||||
<PanelGroup direction="vertical">
|
<PanelGroup direction="vertical">
|
||||||
{/* Editor placeholder */}
|
{/* Editor */}
|
||||||
<Panel>
|
<Panel>
|
||||||
<div className="flex h-full w-full items-center justify-center border-r border-border text-sm text-muted-foreground">
|
<EditorPanel file={activeFile} />
|
||||||
<div className="text-center">
|
|
||||||
<p className="mb-2">Editor placeholder</p>
|
|
||||||
<div className="space-y-1 text-xs">
|
|
||||||
<p>
|
|
||||||
Active file:{" "}
|
|
||||||
<code className="text-foreground">{activeFilePath}</code>
|
|
||||||
</p>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
{activeFile
|
|
||||||
? `${activeFile.name} | ${activeFile.language ?? "plain text"} | ${activeFile.kind}`
|
|
||||||
: "No matching mock file"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
{/* Terminal panel */}
|
{/* Terminal panel */}
|
||||||
@@ -94,9 +81,7 @@ export function WorkspaceShell({ files }: WorkspaceShellProps) {
|
|||||||
<>
|
<>
|
||||||
<PanelResizeHandle className="h-1 bg-border transition-colors hover:bg-accent" />
|
<PanelResizeHandle className="h-1 bg-border transition-colors hover:bg-accent" />
|
||||||
<Panel defaultSize={30} minSize={10} maxSize={70}>
|
<Panel defaultSize={30} minSize={10} maxSize={70}>
|
||||||
<div className="flex h-full w-full items-center justify-center border-r border-border text-sm text-muted-foreground">
|
<TerminalPanel />
|
||||||
Terminal placeholder
|
|
||||||
</div>
|
|
||||||
</Panel>
|
</Panel>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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<HTMLDivElement>(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 (
|
||||||
|
<section className="flex h-full w-full items-center justify-center bg-background text-sm text-muted-foreground">
|
||||||
|
Terminal failed to initialize
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="h-full w-full overflow-hidden bg-[#171717] p-2">
|
||||||
|
<div ref={containerRef} className="h-full w-full" />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tw-animate-css";
|
@import "tw-animate-css";
|
||||||
|
@import "@xterm/xterm/css/xterm.css";
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user