Skip to content

Commit e771565

Browse files
committed
WIP
1 parent 26aadd9 commit e771565

File tree

19 files changed

+2176
-367
lines changed

19 files changed

+2176
-367
lines changed

apps/web/client/public/onlook-preload-script.js

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/web/client/src/app/test-fs/page.tsx

Lines changed: 164 additions & 82 deletions
Large diffs are not rendered by default.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import { ChevronRight, ChevronDown, File, Folder } from 'lucide-react';
5+
import { cn } from '@onlook/ui/utils';
6+
import { ScrollArea } from '@onlook/ui/scroll-area';
7+
8+
export interface FileNode {
9+
name: string;
10+
path: string;
11+
type: 'file' | 'directory';
12+
children?: FileNode[];
13+
}
14+
15+
interface FileExplorerProps {
16+
files: FileNode[];
17+
selectedPath: string | null;
18+
onSelectFile: (path: string) => void;
19+
title: string;
20+
emptyMessage?: string;
21+
}
22+
23+
export function FileExplorer({
24+
files,
25+
selectedPath,
26+
onSelectFile,
27+
title,
28+
emptyMessage = 'No files found',
29+
}: FileExplorerProps) {
30+
return (
31+
<div className="h-full flex flex-col">
32+
<div className="px-4 py-3 border-b border-gray-800">
33+
<h3 className="text-sm font-semibold text-gray-100">{title}</h3>
34+
</div>
35+
36+
<ScrollArea className="flex-1">
37+
<div className="p-2">
38+
{files.length === 0 ? (
39+
<p className="text-sm text-gray-500 px-2 py-4">{emptyMessage}</p>
40+
) : (
41+
<FileTree
42+
nodes={files}
43+
selectedPath={selectedPath}
44+
onSelectFile={onSelectFile}
45+
/>
46+
)}
47+
</div>
48+
</ScrollArea>
49+
</div>
50+
);
51+
}
52+
53+
interface FileTreeProps {
54+
nodes: FileNode[];
55+
selectedPath: string | null;
56+
onSelectFile: (path: string) => void;
57+
level?: number;
58+
}
59+
60+
function FileTree({ nodes, selectedPath, onSelectFile, level = 0 }: FileTreeProps) {
61+
return (
62+
<div className="space-y-0.5">
63+
{nodes.map((node) => (
64+
<FileTreeNode
65+
key={node.path}
66+
node={node}
67+
selectedPath={selectedPath}
68+
onSelectFile={onSelectFile}
69+
level={level}
70+
/>
71+
))}
72+
</div>
73+
);
74+
}
75+
76+
interface FileTreeNodeProps {
77+
node: FileNode;
78+
selectedPath: string | null;
79+
onSelectFile: (path: string) => void;
80+
level: number;
81+
}
82+
83+
function FileTreeNode({ node, selectedPath, onSelectFile, level }: FileTreeNodeProps) {
84+
const [isExpanded, setIsExpanded] = useState(level < 2);
85+
const isSelected = selectedPath === node.path;
86+
const isDirectory = node.type === 'directory';
87+
const hasChildren = isDirectory && node.children && node.children.length > 0;
88+
89+
const handleClick = () => {
90+
if (isDirectory) {
91+
setIsExpanded(!isExpanded);
92+
} else {
93+
onSelectFile(node.path);
94+
}
95+
};
96+
97+
return (
98+
<>
99+
<button
100+
onClick={handleClick}
101+
className={cn(
102+
'w-full text-left px-2 py-1 text-sm flex items-center gap-1.5 rounded hover:bg-gray-800/50',
103+
isSelected && 'bg-gray-800 text-white',
104+
!isSelected && 'text-gray-300',
105+
)}
106+
style={{ paddingLeft: `${level * 12 + 8}px` }}
107+
>
108+
{hasChildren && (
109+
<span className="flex-shrink-0">
110+
{isExpanded ? (
111+
<ChevronDown className="w-3 h-3" />
112+
) : (
113+
<ChevronRight className="w-3 h-3" />
114+
)}
115+
</span>
116+
)}
117+
{!hasChildren && isDirectory && (
118+
<span className="w-3" />
119+
)}
120+
121+
{isDirectory ? (
122+
<Folder className="w-4 h-4 flex-shrink-0 text-blue-500" />
123+
) : (
124+
<File className="w-4 h-4 flex-shrink-0 text-gray-400" />
125+
)}
126+
127+
<span className="truncate">{node.name}</span>
128+
</button>
129+
130+
{isDirectory && isExpanded && node.children && (
131+
<FileTree
132+
nodes={node.children}
133+
selectedPath={selectedPath}
134+
onSelectFile={onSelectFile}
135+
level={level + 1}
136+
/>
137+
)}
138+
</>
139+
);
140+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
'use client';
2+
3+
import { ScrollArea } from '@onlook/ui/scroll-area';
4+
import { Badge } from '@onlook/ui/badge';
5+
6+
interface FileViewerProps {
7+
fileName: string | null;
8+
content: string | null;
9+
isLoading?: boolean;
10+
isBinary?: boolean;
11+
}
12+
13+
export function FileViewer({ fileName, content, isLoading, isBinary }: FileViewerProps) {
14+
if (!fileName) {
15+
return (
16+
<div className="h-full flex items-center justify-center text-gray-500">
17+
<p className="text-sm">Select a file to view its contents</p>
18+
</div>
19+
);
20+
}
21+
22+
return (
23+
<div className="h-full flex flex-col">
24+
<div className="px-4 py-3 border-b border-gray-800 flex items-center justify-between">
25+
<h3 className="text-sm font-mono text-gray-100 truncate">{fileName}</h3>
26+
{isBinary && (
27+
<Badge variant="secondary" className="text-xs">
28+
Binary
29+
</Badge>
30+
)}
31+
</div>
32+
33+
<ScrollArea className="flex-1 bg-gray-950">
34+
<div className="p-4">
35+
{isLoading ? (
36+
<div className="space-y-2 animate-pulse">
37+
<div className="h-4 w-full bg-gray-800 rounded" />
38+
<div className="h-4 w-3/4 bg-gray-800 rounded" />
39+
<div className="h-4 w-5/6 bg-gray-800 rounded" />
40+
<div className="h-4 w-2/3 bg-gray-800 rounded" />
41+
<div className="h-4 w-4/5 bg-gray-800 rounded" />
42+
</div>
43+
) : isBinary ? (
44+
<p className="text-sm text-gray-500 italic">Binary file content not displayed</p>
45+
) : (
46+
<pre className="text-xs font-mono text-gray-300 whitespace-pre-wrap">
47+
{content || '(empty file)'}
48+
</pre>
49+
)}
50+
</div>
51+
</ScrollArea>
52+
</div>
53+
);
54+
}
55+
56+
// Optional: Add line numbers
57+
export function FileViewerWithLineNumbers({ fileName, content, isLoading, isBinary }: FileViewerProps) {
58+
const lines = content?.split('\n') || [];
59+
60+
if (!fileName) {
61+
return (
62+
<div className="h-full flex items-center justify-center text-gray-500">
63+
<p className="text-sm">Select a file to view its contents</p>
64+
</div>
65+
);
66+
}
67+
68+
return (
69+
<div className="h-full flex flex-col">
70+
<div className="px-4 py-3 border-b border-gray-800 flex items-center justify-between">
71+
<h3 className="text-sm font-mono text-gray-100 truncate">{fileName}</h3>
72+
<div className="flex items-center gap-2">
73+
{!isBinary && content && (
74+
<Badge variant="secondary" className="text-xs">
75+
{lines.length} lines
76+
</Badge>
77+
)}
78+
{isBinary && (
79+
<Badge variant="secondary" className="text-xs">
80+
Binary
81+
</Badge>
82+
)}
83+
</div>
84+
</div>
85+
86+
<ScrollArea className="flex-1 bg-gray-950">
87+
{isLoading ? (
88+
<div className="p-4 space-y-2 animate-pulse">
89+
<div className="h-4 w-full bg-gray-800 rounded" />
90+
<div className="h-4 w-3/4 bg-gray-800 rounded" />
91+
<div className="h-4 w-5/6 bg-gray-800 rounded" />
92+
<div className="h-4 w-2/3 bg-gray-800 rounded" />
93+
<div className="h-4 w-4/5 bg-gray-800 rounded" />
94+
</div>
95+
) : isBinary ? (
96+
<div className="p-4">
97+
<p className="text-sm text-gray-500 italic">Binary file content not displayed</p>
98+
</div>
99+
) : (
100+
<div className="flex">
101+
<div className="select-none px-4 py-4 text-right border-r border-gray-800">
102+
{lines.map((_, index) => (
103+
<div key={index} className="text-xs text-gray-600 font-mono leading-5">
104+
{index + 1}
105+
</div>
106+
))}
107+
</div>
108+
<div className="flex-1 px-4 py-4">
109+
<pre className="text-xs font-mono text-gray-300 leading-5 whitespace-pre">
110+
{content || '(empty file)'}
111+
</pre>
112+
</div>
113+
</div>
114+
)}
115+
</ScrollArea>
116+
</div>
117+
);
118+
}

0 commit comments

Comments
 (0)