Mehdi commited on
Commit
9e5629a
·
verified ·
1 Parent(s): d302672

Upload components/CodeEditor.jsx with huggingface_hub

Browse files
Files changed (1) hide show
  1. components/CodeEditor.jsx +314 -0
components/CodeEditor.jsx ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef } from 'react'
2
+ import { FiPlay, FiSave, FiShare2, FiSettings, FiDownload, FiUpload } from 'react-icons/fi'
3
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
4
+ import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
5
+ import dynamic from 'next/dynamic'
6
+
7
+ const MonacoEditor = dynamic(() => import('@monaco-editor/react'), {
8
+ ssr: false,
9
+ loading: () => <div className="h-full bg-gray-800 rounded-lg animate-pulse" />
10
+ })
11
+
12
+ export default function CodeEditor() {
13
+ const [code, setCode] = useState('')
14
+ const [language, setLanguage] = useState('javascript')
15
+ const [output, setOutput] = useState('')
16
+ const [isRunning, setIsRunning] = useState(false)
17
+ const [error, setError] = useState(null)
18
+ const [theme, setTheme] = useState('vs-dark')
19
+ const [fontSize, setFontSize] = useState(14)
20
+ const [showSettings, setShowSettings] = useState(false)
21
+ const [savedSnippets, setSavedSnippets] = useState([])
22
+ const [snippetName, setSnippetName] = useState('')
23
+ const editorRef = useRef(null)
24
+
25
+ const languages = [
26
+ { value: 'javascript', label: 'JavaScript', icon: '🟡' },
27
+ { value: 'python', label: 'Python', icon: '🐍' },
28
+ { value: 'java', label: 'Java', icon: '☕' },
29
+ { value: 'csharp', label: 'C#', icon: '🔧' },
30
+ { value: 'typescript', label: 'TypeScript', icon: '🔵' },
31
+ { value: 'go', label: 'Go', icon: '🐹' },
32
+ { value: 'rust', label: 'Rust', icon: '🦀' }
33
+ ]
34
+
35
+ const themes = [
36
+ { value: 'vs-dark', label: 'Dark' },
37
+ { value: 'vs-light', label: 'Light' },
38
+ { value: 'hc-black', label: 'High Contrast' }
39
+ ]
40
+
41
+ useEffect(() => {
42
+ const saved = localStorage.getItem('codeSnippets')
43
+ if (saved) {
44
+ setSavedSnippets(JSON.parse(saved))
45
+ }
46
+ }, [])
47
+
48
+ const handleRunCode = async () => {
49
+ if (!code.trim()) return
50
+
51
+ setIsRunning(true)
52
+ setError(null)
53
+ setOutput('')
54
+
55
+ try {
56
+ const response = await fetch('/api/execute', {
57
+ method: 'POST',
58
+ headers: {
59
+ 'Content-Type': 'application/json',
60
+ },
61
+ body: JSON.stringify({ code, language }),
62
+ })
63
+
64
+ const data = await response.json()
65
+
66
+ if (response.ok) {
67
+ setOutput(data.output)
68
+ } else {
69
+ throw new Error(data.error || 'Execution failed')
70
+ }
71
+ } catch (err) {
72
+ setError(err.message)
73
+ } finally {
74
+ setIsRunning(false)
75
+ }
76
+ }
77
+
78
+ const handleSaveSnippet = () => {
79
+ if (!snippetName.trim()) return
80
+
81
+ const newSnippet = {
82
+ id: Date.now(),
83
+ name: snippetName,
84
+ code,
85
+ language,
86
+ createdAt: new Date().toISOString()
87
+ }
88
+
89
+ const updatedSnippets = [...savedSnippets, newSnippet]
90
+ setSavedSnippets(updatedSnippets)
91
+ localStorage.setItem('codeSnippets', JSON.stringify(updatedSnippets))
92
+ setSnippetName('')
93
+ }
94
+
95
+ const handleLoadSnippet = (snippet) => {
96
+ setCode(snippet.code)
97
+ setLanguage(snippet.language)
98
+ }
99
+
100
+ const handleDownloadCode = () => {
101
+ const blob = new Blob([code], { type: 'text/plain' })
102
+ const url = URL.createObjectURL(blob)
103
+ const a = document.createElement('a')
104
+ a.href = url
105
+ a.download = `code.${language === 'javascript' ? 'js' : language === 'python' ? 'py' : 'txt'}`
106
+ document.body.appendChild(a)
107
+ a.click()
108
+ document.body.removeChild(a)
109
+ URL.revokeObjectURL(url)
110
+ }
111
+
112
+ const handleUploadCode = (e) => {
113
+ const file = e.target.files[0]
114
+ if (!file) return
115
+
116
+ const reader = new FileReader()
117
+ reader.onload = (event) => {
118
+ setCode(event.target.result)
119
+ }
120
+ reader.readAsText(file)
121
+ }
122
+
123
+ const handleEditorMount = (editor) => {
124
+ editorRef.current = editor
125
+ }
126
+
127
+ return (
128
+ <div className="space-y-4">
129
+ <div className="flex flex-wrap justify-between items-center gap-4">
130
+ <div className="flex items-center space-x-4">
131
+ <select
132
+ value={language}
133
+ onChange={(e) => setLanguage(e.target.value)}
134
+ className="bg-gray-700 text-white px-3 py-2 rounded-lg"
135
+ >
136
+ {languages.map((lang) => (
137
+ <option key={lang.value} value={lang.value}>
138
+ {lang.icon} {lang.label}
139
+ </option>
140
+ ))}
141
+ </select>
142
+
143
+ <button
144
+ onClick={handleRunCode}
145
+ disabled={isRunning}
146
+ className={`px-4 py-2 rounded-lg flex items-center space-x-2 ${isRunning ? 'bg-gray-600' : 'bg-accent hover:bg-purple-600'} text-white transition-colors`}
147
+ >
148
+ <FiPlay />
149
+ <span>{isRunning ? 'Running...' : 'Run Code'}</span>
150
+ </button>
151
+
152
+ <button
153
+ onClick={() => setShowSettings(!showSettings)}
154
+ className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg flex items-center space-x-2 transition-colors"
155
+ >
156
+ <FiSettings />
157
+ <span>Settings</span>
158
+ </button>
159
+ </div>
160
+
161
+ <div className="flex items-center space-x-2">
162
+ <label className="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg cursor-pointer flex items-center space-x-2 transition-colors">
163
+ <FiUpload />
164
+ <span>Upload</span>
165
+ <input type="file" className="hidden" onChange={handleUploadCode} accept=".js,.py,.java,.cs,.ts,.go,.rs" />
166
+ </label>
167
+
168
+ <button
169
+ onClick={handleDownloadCode}
170
+ className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center space-x-2 transition-colors"
171
+ >
172
+ <FiDownload />
173
+ <span>Download</span>
174
+ </button>
175
+
176
+ <div className="flex items-center space-x-2">
177
+ <input
178
+ type="text"
179
+ placeholder="Snippet name"
180
+ value={snippetName}
181
+ onChange={(e) => setSnippetName(e.target.value)}
182
+ className="px-3 py-2 bg-gray-700 text-white rounded-lg text-sm"
183
+ />
184
+ <button
185
+ onClick={handleSaveSnippet}
186
+ disabled={!snippetName.trim()}
187
+ className="px-4 py-2 bg-yellow-600 hover:bg-yellow-700 text-white rounded-lg flex items-center space-x-2 transition-colors disabled:opacity-50"
188
+ >
189
+ <FiSave />
190
+ <span>Save</span>
191
+ </button>
192
+ </div>
193
+ </div>
194
+ </div>
195
+
196
+ {showSettings && (
197
+ <div className="bg-gray-800 rounded-lg p-4 mb-4">
198
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
199
+ <div>
200
+ <label className="block text-sm font-medium text-gray-300 mb-1">Theme</label>
201
+ <select
202
+ value={theme}
203
+ onChange={(e) => setTheme(e.target.value)}
204
+ className="w-full bg-gray-700 text-white px-3 py-2 rounded-lg"
205
+ >
206
+ {themes.map((t) => (
207
+ <option key={t.value} value={t.value}>{t.label}</option>
208
+ ))}
209
+ </select>
210
+ </div>
211
+ <div>
212
+ <label className="block text-sm font-medium text-gray-300 mb-1">Font Size</label>
213
+ <input
214
+ type="range"
215
+ min="12"
216
+ max="24"
217
+ value={fontSize}
218
+ onChange={(e) => setFontSize(parseInt(e.target.value))}
219
+ className="w-full"
220
+ />
221
+ <div className="text-center text-sm text-gray-300 mt-1">{fontSize}px</div>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ )}
226
+
227
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-[calc(100vh-250px)]">
228
+ <div className="bg-gray-800 rounded-lg overflow-hidden">
229
+ <div className="bg-gray-900 px-4 py-2 flex justify-between items-center">
230
+ <span className="text-sm text-gray-300">Editor</span>
231
+ {savedSnippets.length > 0 && (
232
+ <select
233
+ onChange={(e) => handleLoadSnippet(JSON.parse(e.target.value))}
234
+ className="bg-gray-700 text-white px-2 py-1 rounded text-sm"
235
+ >
236
+ <option value="">Load Snippet</option>
237
+ {savedSnippets.map((snippet) => (
238
+ <option key={snippet.id} value={JSON.stringify(snippet)}>
239
+ {snippet.name} ({snippet.language})
240
+ </option>
241
+ ))}
242
+ </select>
243
+ )}
244
+ </div>
245
+ <div className="h-[calc(100%-40px)]">
246
+ <MonacoEditor
247
+ height="100%"
248
+ language={language}
249
+ theme={theme}
250
+ value={code}
251
+ onChange={setCode}
252
+ onMount={handleEditorMount}
253
+ options={{
254
+ fontSize,
255
+ minimap: { enabled: false },
256
+ wordWrap: 'on',
257
+ scrollBeyondLastLine: false,
258
+ automaticLayout: true
259
+
260
+ />
261
+ </div>
262
+ </div>
263
+
264
+ <div className="bg-gray-100 rounded-lg p-4 overflow-auto">
265
+ <div className="flex justify-between items-center mb-4">
266
+ <h3 className="font-bold text-gray-800">Output Console</h3>
267
+ <button
268
+ onClick={() => setOutput('')}
269
+ className="text-sm text-gray-500 hover:text-gray-700"
270
+ >
271
+ Clear
272
+ </button>
273
+ </div>
274
+ {error ? (
275
+ <div className="text-red-600 p-3 bg-red-50 rounded-lg">
276
+ <strong>Error:</strong> {error}
277
+ </div>
278
+ ) : (
279
+ <div className="bg-white p-3 rounded-lg h-[calc(100%-50px)] overflow-auto">
280
+ {output ? (
281
+ <SyntaxHighlighter language={language} style={atomDark} customStyle={{ background: 'transparent' }}>
282
+ {output}
283
+ </SyntaxHighlighter>
284
+ ) : (
285
+ <p className="text-gray-500">Code execution output will appear here...</p>
286
+ )}
287
+ </div>
288
+ )}
289
+ </div>
290
+ </div>
291
+
292
+ {savedSnippets.length > 0 && (
293
+ <div className="mt-4 bg-gray-800 rounded-lg p-4">
294
+ <h3 className="font-bold text-white mb-3">Saved Snippets</h3>
295
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
296
+ {savedSnippets.map((snippet) => (
297
+ <div key={snippet.id} className="bg-gray-700 rounded-lg p-3 hover:bg-gray-600 transition-colors cursor-pointer" onClick={() => handleLoadSnippet(snippet)}>
298
+ <div className="flex justify-between items-start">
299
+ <div>
300
+ <h4 className="font-medium text-white">{snippet.name}</h4>
301
+ <p className="text-xs text-gray-400">{snippet.language}</p>
302
+ </div>
303
+ <span className="text-xs text-gray-400">
304
+ {new Date(snippet.createdAt).toLocaleDateString()}
305
+ </span>
306
+ </div>
307
+ </div>
308
+ ))}
309
+ </div>
310
+ </div>
311
+ )}
312
+ </div>
313
+ )
314
+ }