Skip to content

Commit bab9d85

Browse files
authored
refactor: Rebuild the sandbox sync to simplify it + support bi-directional sync (#2903)
1 parent bb4390a commit bab9d85

File tree

18 files changed

+3194
-214
lines changed

18 files changed

+3194
-214
lines changed

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

Lines changed: 169 additions & 88 deletions
Large diffs are not rendered by default.

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

Lines changed: 484 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
'use client';
2+
3+
import { useState, useEffect } from 'react';
4+
import { Button } from '@onlook/ui/button';
5+
import { Badge } from '@onlook/ui/badge';
6+
import { Save } from 'lucide-react';
7+
8+
interface FileEditorProps {
9+
fileName: string | null;
10+
content: string | null;
11+
isLoading?: boolean;
12+
isBinary?: boolean;
13+
onSave?: (content: string) => Promise<void>;
14+
}
15+
16+
export function FileEditor({
17+
fileName,
18+
content,
19+
isLoading,
20+
isBinary,
21+
onSave
22+
}: FileEditorProps) {
23+
const [editContent, setEditContent] = useState(content || '');
24+
const [isSaving, setIsSaving] = useState(false);
25+
const [hasChanges, setHasChanges] = useState(false);
26+
27+
useEffect(() => {
28+
setEditContent(content || '');
29+
setHasChanges(false);
30+
}, [content]);
31+
32+
const handleContentChange = (value: string) => {
33+
setEditContent(value);
34+
setHasChanges(value !== content);
35+
};
36+
37+
const handleSave = async () => {
38+
if (!onSave || !hasChanges) return;
39+
40+
setIsSaving(true);
41+
try {
42+
await onSave(editContent);
43+
setHasChanges(false);
44+
} catch (error) {
45+
console.error('Failed to save file:', error);
46+
} finally {
47+
setIsSaving(false);
48+
}
49+
};
50+
51+
// Handle Cmd+S / Ctrl+S
52+
useEffect(() => {
53+
const handleKeyDown = (e: KeyboardEvent) => {
54+
if ((e.metaKey || e.ctrlKey) && e.key === 's' && hasChanges && !isBinary) {
55+
e.preventDefault();
56+
handleSave();
57+
}
58+
};
59+
60+
window.addEventListener('keydown', handleKeyDown);
61+
return () => window.removeEventListener('keydown', handleKeyDown);
62+
}, [hasChanges, editContent]);
63+
64+
if (!fileName) {
65+
return (
66+
<div className="h-full flex items-center justify-center text-gray-500">
67+
<p className="text-sm">Select a file to view its contents</p>
68+
</div>
69+
);
70+
}
71+
72+
const lines = editContent.split('\n');
73+
74+
return (
75+
<div className="h-full flex flex-col">
76+
<div className="px-4 py-3 border-b border-gray-800 flex items-center justify-between">
77+
<h3 className="text-sm font-mono text-gray-100 truncate">{fileName}</h3>
78+
<div className="flex items-center gap-2">
79+
{!isBinary && content !== null && (
80+
<Badge variant="secondary" className="text-xs">
81+
{lines.length} lines
82+
</Badge>
83+
)}
84+
{isBinary && (
85+
<Badge variant="secondary" className="text-xs">
86+
Binary
87+
</Badge>
88+
)}
89+
{!isBinary && hasChanges && (
90+
<Button
91+
onClick={handleSave}
92+
size="sm"
93+
className="h-7 px-2"
94+
disabled={isSaving}
95+
>
96+
<Save className="h-3 w-3 mr-1" />
97+
{isSaving ? 'Saving...' : 'Save'}
98+
</Button>
99+
)}
100+
</div>
101+
</div>
102+
103+
<div className="flex-1 overflow-hidden">
104+
{isLoading ? (
105+
<div className="p-4 space-y-2 animate-pulse">
106+
<div className="h-4 w-full bg-gray-800 rounded" />
107+
<div className="h-4 w-3/4 bg-gray-800 rounded" />
108+
<div className="h-4 w-5/6 bg-gray-800 rounded" />
109+
<div className="h-4 w-2/3 bg-gray-800 rounded" />
110+
<div className="h-4 w-4/5 bg-gray-800 rounded" />
111+
</div>
112+
) : isBinary ? (
113+
<div className="p-4">
114+
<p className="text-sm text-gray-500 italic">Binary file content not displayed</p>
115+
</div>
116+
) : (
117+
<div className="h-full w-full overflow-auto bg-gray-950 p-4">
118+
<textarea
119+
value={editContent}
120+
onChange={(e) => handleContentChange(e.target.value)}
121+
className="min-h-full min-w-full resize-none border-0 bg-transparent font-mono text-xs text-gray-300 outline-none"
122+
placeholder="Enter file content..."
123+
spellCheck={false}
124+
style={{
125+
lineHeight: '1.25rem',
126+
whiteSpace: 'pre',
127+
overflowWrap: 'normal'
128+
}}
129+
/>
130+
</div>
131+
)}
132+
</div>
133+
</div>
134+
);
135+
}

0 commit comments

Comments
 (0)