Better file tree

This commit is contained in:
2026-02-03 15:56:02 +01:00
parent 9af328eb6b
commit b80a3be82d
4 changed files with 74 additions and 66 deletions
+1 -60
View File
@@ -35,7 +35,6 @@
"@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.10",
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"@sinm/react-file-tree": "^1.1.1",
"@uiw/codemirror-theme-vscode": "^4.25.4", "@uiw/codemirror-theme-vscode": "^4.25.4",
"@uiw/react-codemirror": "^4.25.4", "@uiw/react-codemirror": "^4.25.4",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
@@ -2745,16 +2744,6 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@sinm/react-file-tree": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@sinm/react-file-tree/-/react-file-tree-1.1.1.tgz",
"integrity": "sha512-XlHLLNV18zWx7mYEH5gtlBb7jYFX7GaBA5jmhsG9YXhgwyyK33JgymrbU+IHDDBakAoZ+xISLKzu2ldzKbA/wg==",
"license": "MIT",
"dependencies": {
"@types/react-virtualized": "^9.21.15",
"react-virtualized": "^9.22.3"
}
},
"node_modules/@swc/helpers": { "node_modules/@swc/helpers": {
"version": "0.5.15", "version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -3108,16 +3097,11 @@
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
}, },
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"license": "MIT"
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.2.10", "version": "19.2.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
@@ -3133,16 +3117,6 @@
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
}, },
"node_modules/@types/react-virtualized": {
"version": "9.22.3",
"resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.22.3.tgz",
"integrity": "sha512-UKRWeBIrECaKhE4O//TSFhlgwntMwyiEIOA7WZoVkr52Jahv0dH6YIOorqc358N2V3oKFclsq5XxPmx2PiYB5A==",
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"@types/react": "*"
}
},
"node_modules/@uiw/codemirror-extensions-basic-setup": { "node_modules/@uiw/codemirror-extensions-basic-setup": {
"version": "4.25.4", "version": "4.25.4",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.4.tgz", "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.4.tgz",
@@ -4326,12 +4300,6 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
"license": "MIT"
},
"node_modules/react-remove-scroll": { "node_modules/react-remove-scroll": {
"version": "2.7.2", "version": "2.7.2",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
@@ -4432,33 +4400,6 @@
"react-dom": ">=16.6.0" "react-dom": ">=16.6.0"
} }
}, },
"node_modules/react-virtualized": {
"version": "9.22.6",
"resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.6.tgz",
"integrity": "sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.7.2",
"clsx": "^1.0.4",
"dom-helpers": "^5.1.3",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-lifecycles-compat": "^3.0.4"
},
"peerDependencies": {
"react": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-virtualized/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/recharts": { "node_modules/recharts": {
"version": "2.15.4", "version": "2.15.4",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
-1
View File
@@ -35,7 +35,6 @@
"@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.10",
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"@sinm/react-file-tree": "^1.1.1",
"@uiw/codemirror-theme-vscode": "^4.25.4", "@uiw/codemirror-theme-vscode": "^4.25.4",
"@uiw/react-codemirror": "^4.25.4", "@uiw/react-codemirror": "^4.25.4",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
+60
View File
@@ -0,0 +1,60 @@
"use client";
import { useState } from "react";
import { Folder, FolderMinus, FolderPlus, File } from "lucide-react";
type TreeNode = {
type: "directory" | "file";
uri: string;
children?: TreeNode[];
};
interface FileTreeProps {
tree: TreeNode;
level?: number; // for indentation
}
export const FileTree: React.FC<FileTreeProps> = ({ tree, level = 0 }) => {
const [expanded, setExpanded] = useState(
tree.type !== "directory" || level === 0 ? true : false,
);
const toggleExpand = () => {
if (tree.type === "directory") {
setExpanded(!expanded);
}
};
return (
<div>
{/* Directory/File Row */}
<div
onClick={toggleExpand}
style={{
paddingLeft: level * 20,
display: "flex",
alignItems: "center",
cursor: tree.type === "directory" ? "pointer" : "default",
userSelect: "none",
}}
>
{tree.type === "directory" ? (
expanded ? (
<FolderMinus size={16} className="mr-2" />
) : (
<FolderPlus size={16} className="mr-2" />
)
) : (
<File size={16} className="mr-2" />
)}
{tree.uri}
</div>
{/* Children */}
{expanded &&
tree.children?.map((child) => (
<FileTree key={child.uri} tree={child} level={level + 1} />
))}
</div>
);
};
+12 -4
View File
@@ -1,12 +1,14 @@
"use client"; "use client";
import { FileTree } from "@sinm/react-file-tree";
import CodeMirror, { EditorState } from "@uiw/react-codemirror"; import CodeMirror, { EditorState } from "@uiw/react-codemirror";
import { vscodeDark } from "@uiw/codemirror-theme-vscode"; import { vscodeDark } from "@uiw/codemirror-theme-vscode";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import RunTimer, { RunInfo } from "./components/RunTimer"; import RunTimer, { RunInfo } from "./components/RunTimer";
import RunChat, { ChatMessage } from "./components/RunChat"; import RunChat, { ChatMessage } from "./components/RunChat";
import { useState } from "react";
import { FileTree } from "./components/FileTree";
import { File } from "lucide-react";
export default function Run() { export default function Run() {
const runInfo: RunInfo = { const runInfo: RunInfo = {
@@ -34,16 +36,22 @@ export default function Run() {
tree={{ tree={{
type: "directory", type: "directory",
uri: "my-app", uri: "my-app",
expanded: true, children: [
{ type: "file", uri: "myfile.js" },
{
type: "directory",
uri: "my-app",
children: [{ type: "file", uri: "myfile.js" }], children: [{ type: "file", uri: "myfile.js" }],
},
],
}} }}
/> />
</div> </div>
{/* Code */} {/* Code */}
<div className="flex-1 pl-5 flex flex-col"> <div className="flex-1 pl-5 flex flex-col">
<header className="text-sm font-medium text-muted-foreground"> <header className="text-sm font-medium text-muted-foreground flex items-center gap-1">
myfile.js <File size={16} /> myfile.js
</header> </header>
<div className="flex-1 pt-3 pb-5"> <div className="flex-1 pt-3 pb-5">