feat: add file explorer with tree view and IDE layout

Backend: editor service for directory tree reading and file CRUD,
editor routes at /api/editor with path traversal protection.

Frontend: FileExplorer tree component with expand/collapse directories,
IDELayout with sidebar + header + outlet, wired into App routing.
Editor now receives state as props from App for cross-component file loading.
This commit is contained in:
plenarius
2026-04-20 19:09:19 -04:00
parent eaca2d8a6c
commit 02ca134743
8 changed files with 473 additions and 7 deletions
+19 -5
View File
@@ -1,9 +1,18 @@
import { useCallback, useMemo } from "react";
import { MonacoEditor } from "../components/editor/MonacoEditor";
import { EditorTabs } from "../components/editor/EditorTabs";
import { useEditorState } from "../hooks/useEditorState";
export function Editor() {
interface EditorProps {
editorState: ReturnType<typeof import("../hooks/useEditorState").useEditorState>;
}
function displayName(tabKey: string): string {
const parts = tabKey.split(":");
const filePath = parts.length > 1 ? parts.slice(1).join(":") : tabKey;
return filePath.split("/").pop() ?? filePath;
}
export function Editor({ editorState }: EditorProps) {
const {
openTabs,
activeTab,
@@ -12,7 +21,7 @@ export function Editor() {
closeFile,
updateContent,
getContent,
} = useEditorState();
} = editorState;
const tabs = useMemo(
() =>
@@ -24,6 +33,11 @@ export function Editor() {
);
const activeContent = activeTab ? (getContent(activeTab) ?? "") : "";
const activeFilePath = activeTab
? activeTab.includes(":")
? activeTab.split(":").slice(1).join(":")
: activeTab
: "";
const handleChange = useCallback(
(value: string) => {
@@ -35,7 +49,7 @@ export function Editor() {
);
return (
<div className="flex h-screen flex-col" style={{ backgroundColor: "var(--forge-bg)" }}>
<div className="flex h-full flex-col" style={{ backgroundColor: "var(--forge-bg)" }}>
<EditorTabs
tabs={tabs}
activeTab={activeTab}
@@ -46,7 +60,7 @@ export function Editor() {
{activeTab ? (
<MonacoEditor
key={activeTab}
filePath={activeTab}
filePath={activeFilePath}
content={activeContent}
onChange={handleChange}
/>