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