Mehdi commited on
Commit
0758862
·
verified ·
1 Parent(s): 88648a6

Upload components/CodeEditor.jsx with huggingface_hub

Browse files
Files changed (1) hide show
  1. components/CodeEditor.jsx +329 -0
components/CodeEditor.jsx ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef } from 'react'
2
+ import { motion, AnimatePresence } from 'framer-motion'
3
+ import Editor from '@monaco-editor/react'
4
+ import {
5
+ FaPlay, FaSave, FaDownload, FaUpload, FaCopy, FaTerminal,
6
+ FaCode, FaBug, FaCheckCircle, FaExclamationTriangle, FaCog,
7
+ FaFolderOpen, FaFile, FaFileCode, FaJs, FaPython, FaJava,
8
+ FaHtml5, FaCss3Alt, FaReact, FaNodeJs, FaGitAlt, FaDatabase
9
+ } from 'react-icons/fa'
10
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
11
+ import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism'
12
+ import toast from 'react-hot-toast'
13
+
14
+ export default function CodeEditor({ language, theme }) {
15
+ const [code, setCode] = useState(`// ${language === 'fa' ? 'کد خود را اینجا بنویسید' : 'Write your code here'}\nfunction hello() {\n console.log("Hello, World!");\n}`)
16
+ const [output, setOutput] = useState('')
17
+ const [isRunning, setIsRunning] = useState(false)
18
+ const [selectedLanguage, setSelectedLanguage] = useState('javascript')
19
+ const [files, setFiles] = useState([
20
+ { id: 1, name: 'main.js', content: code, language: 'javascript' },
21
+ { id: 2, name: 'utils.py', content: '# Python utilities', language: 'python' }
22
+ ])
23
+ const [activeFile, setActiveFile] = useState(1)
24
+ const [fontSize, setFontSize] = useState(14)
25
+ const [theme, setEditorTheme] = useState('vs-dark')
26
+ const [showTerminal, setShowTerminal] = useState(true)
27
+ const [errors, setErrors] = useState([])
28
+ const editorRef = useRef(null)
29
+
30
+ const languages = [
31
+ { id: 'javascript', name: 'JavaScript', icon: FaJs, color: 'text-yellow-400' },
32
+ { id: 'python', name: 'Python', icon: FaPython, color: 'text-blue-400' },
33
+ { id: 'java', name: 'Java', icon: FaJava, color: 'text-red-400' },
34
+ { id: 'html', name: 'HTML', icon: FaHtml5, color: 'text-orange-500' },
35
+ { id: 'css', name: 'CSS', icon: FaCss3Alt, color: 'text-blue-500' },
36
+ { id: 'typescript', name: 'TypeScript', icon: FaCode, color: 'text-blue-600' },
37
+ { id: 'json', name: 'JSON', icon: FaDatabase, color: 'text-green-500' },
38
+ { id: 'sql', name: 'SQL', icon: FaDatabase, color: 'text-purple-500' }
39
+ ]
40
+
41
+ const handleEditorDidMount = (editor, monaco) => {
42
+ editorRef.current = editor
43
+ editor.focus()
44
+ }
45
+
46
+ const runCode = async () => {
47
+ setIsRunning(true)
48
+ setOutput('')
49
+ setErrors([])
50
+
51
+ try {
52
+ // Simulate code execution
53
+ await new Promise(resolve => setTimeout(resolve, 2000))
54
+
55
+ if (selectedLanguage === 'javascript') {
56
+ // Safe eval simulation
57
+ const result = 'Code executed successfully!\nOutput: Hello, World!'
58
+ setOutput(result)
59
+ toast.success(language === 'fa' ? 'کد با موفقیت اجرا شد' : 'Code executed successfully')
60
+ } else {
61
+ setOutput(`${selectedLanguage.toUpperCase()} code execution simulated`)
62
+ }
63
+ } catch (error) {
64
+ setErrors([error.message])
65
+ toast.error(language === 'fa' ? 'خطا در اجرای کد' : 'Error executing code')
66
+ } finally {
67
+ setIsRunning(false)
68
+ }
69
+ }
70
+
71
+ const saveCode = () => {
72
+ const updatedFiles = files.map(file =>
73
+ file.id === activeFile ? { ...file, content: code } : file
74
+ )
75
+ setFiles(updatedFiles)
76
+ toast.success(language === 'fa' ? 'کد ذخیره شد' : 'Code saved')
77
+ }
78
+
79
+ const downloadCode = () => {
80
+ const file = files.find(f => f.id === activeFile)
81
+ const blob = new Blob([code], { type: 'text/plain' })
82
+ const url = URL.createObjectURL(blob)
83
+ const a = document.createElement('a')
84
+ a.href = url
85
+ a.download = file.name
86
+ a.click()
87
+ toast.success(language === 'fa' ? 'کد دانلود شد' : 'Code downloaded')
88
+ }
89
+
90
+ const uploadFile = (event) => {
91
+ const file = event.target.files[0]
92
+ if (file) {
93
+ const reader = new FileReader()
94
+ reader.onload = (e) => {
95
+ const content = e.target.result
96
+ const newFile = {
97
+ id: Date.now(),
98
+ name: file.name,
99
+ content: content,
100
+ language: selectedLanguage
101
+ }
102
+ setFiles([...files, newFile])
103
+ setActiveFile(newFile.id)
104
+ setCode(content)
105
+ toast.success(language === 'fa' ? 'فایل آپلود شد' : 'File uploaded')
106
+ }
107
+ reader.readAsText(file)
108
+ }
109
+ }
110
+
111
+ const createNewFile = () => {
112
+ const newFile = {
113
+ id: Date.now(),
114
+ name: `untitled.${selectedLanguage === 'javascript' ? 'js' : selectedLanguage}`,
115
+ content: `// New ${selectedLanguage} file`,
116
+ language: selectedLanguage
117
+ }
118
+ setFiles([...files, newFile])
119
+ setActiveFile(newFile.id)
120
+ setCode(newFile.content)
121
+ }
122
+
123
+ const deleteFile = (fileId) => {
124
+ if (files.length > 1) {
125
+ setFiles(files.filter(f => f.id !== fileId))
126
+ if (activeFile === fileId) {
127
+ setActiveFile(files[0].id)
128
+ setCode(files[0].content)
129
+ }
130
+ toast.success(language === 'fa' ? 'فایل حذف شد' : 'File deleted')
131
+ }
132
+ }
133
+
134
+ useEffect(() => {
135
+ const activeFileData = files.find(f => f.id === activeFile)
136
+ if (activeFileData) {
137
+ setCode(activeFileData.content)
138
+ setSelectedLanguage(activeFileData.language)
139
+ }
140
+ }, [activeFile])
141
+
142
+ return (
143
+ <div className="flex flex-col h-full space-y-4">
144
+ {/* Toolbar */}
145
+ <div className="flex items-center justify-between p-4 bg-gray-800 rounded-lg">
146
+ <div className="flex items-center space-x-reverse space-x-3">
147
+ <button
148
+ onClick={runCode}
149
+ disabled={isRunning}
150
+ className="flex items-center space-x-reverse space-x-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50 transition-all"
151
+ >
152
+ <FaPlay className="w-4 h-4" />
153
+ <span>{isRunning ? (language === 'fa' ? 'در حال اجرا...' : 'Running...') : (language === 'fa' ? 'اجرا' : 'Run')}</span>
154
+ </button>
155
+
156
+ <button
157
+ onClick={saveCode}
158
+ className="flex items-center space-x-reverse space-x-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all"
159
+ >
160
+ <FaSave className="w-4 h-4" />
161
+ <span>{language === 'fa' ? 'ذخیره' : 'Save'}</span>
162
+ </button>
163
+
164
+ <button
165
+ onClick={downloadCode}
166
+ className="flex items-center space-x-reverse space-x-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-all"
167
+ >
168
+ <FaDownload className="w-4 h-4" />
169
+ <span>{language === 'fa' ? 'دانلود' : 'Download'}</span>
170
+ </button>
171
+
172
+ <label className="flex items-center space-x-reverse space-x-2 px-4 py-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600 cursor-pointer transition-all">
173
+ <FaUpload className="w-4 h-4" />
174
+ <span>{language === 'fa' ? 'آپلود' : 'Upload'}</span>
175
+ <input type="file" onChange={uploadFile} className="hidden" accept=".js,.py,.java,.html,.css,.json,.sql,.ts,.tsx" />
176
+ </label>
177
+
178
+ <button
179
+ onClick={createNewFile}
180
+ className="flex items-center space-x-reverse space-x-2 px-4 py-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600 transition-all"
181
+ >
182
+ <FaFileCode className="w-4 h-4" />
183
+ <span>{language === 'fa' ? 'فایل جدید' : 'New File'}</span>
184
+ </button>
185
+ </div>
186
+
187
+ <div className="flex items-center space-x-reverse space-x-3">
188
+ <select
189
+ value={selectedLanguage}
190
+ onChange={(e) => setSelectedLanguage(e.target.value)}
191
+ className="px-3 py-2 bg-gray-700 text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
192
+ >
193
+ {languages.map(lang => (
194
+ <option key={lang.id} value={lang.id}>{lang.name}</option>
195
+ ))}
196
+ </select>
197
+
198
+ <button
199
+ onClick={() => setShowTerminal(!showTerminal)}
200
+ className={`p-2 rounded-lg transition-all ${
201
+ showTerminal ? 'bg-primary-600 text-white' : 'bg-gray-700 text-gray-300'
202
+ }`}
203
+ >
204
+ <FaTerminal className="w-4 h-4" />
205
+ </button>
206
+
207
+ <button
208
+ onClick={() => {
209
+ setFontSize(prev => prev === 14 ? 16 : prev === 16 ? 18 : 14)
210
+ toast.success(language === 'fa' ? `اندازه فونت: ${fontSize === 14 ? 16 : fontSize === 16 ? 18 : 14}px` : `Font size: ${fontSize === 14 ? 16 : fontSize === 16 ? 18 : 14}px`)
211
+
212
+ className="p-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600 transition-all"
213
+ >
214
+ <FaCog className="w-4 h-4" />
215
+ </button>
216
+ </div>
217
+ </div>
218
+
219
+ {/* File Tabs */}
220
+ <div className="flex items-center space-x-reverse space-x-1 p-2 bg-gray-800 rounded-lg overflow-x-auto">
221
+ {files.map(file => (
222
+ <button
223
+ key={file.id}
224
+ onClick={() => setActiveFile(file.id)}
225
+ className={`flex items-center space-x-reverse space-x-2 px-3 py-2 rounded-lg transition-all whitespace-nowrap ${
226
+ activeFile === file.id
227
+ ? 'bg-primary-600 text-white'
228
+ : 'bg-gray-700 text-gray-300 hover:bg-gray-600'
229
+ }`}
230
+ >
231
+ <FaFile className="w-3 h-3" />
232
+ <span className="text-sm">{file.name}</span>
233
+ {files.length > 1 && (
234
+ <button
235
+ onClick={(e) => {
236
+ e.stopPropagation()
237
+ deleteFile(file.id)
238
+
239
+ className="opacity-50 hover:opacity-100 transition-opacity"
240
+ >
241
+ ×
242
+ </button>
243
+ )}
244
+ </button>
245
+ ))}
246
+ </div>
247
+
248
+ {/* Editor and Output */}
249
+ <div className="flex-1 flex space-x-reverse space-x-4 min-h-0">
250
+ {/* Code Editor */}
251
+ <div className="flex-1 bg-gray-800 rounded-lg overflow-hidden">
252
+ <Editor
253
+ height="100%"
254
+ language={selectedLanguage}
255
+ value={code}
256
+ onChange={(value) => setCode(value)}
257
+ onMount={handleEditorDidMount}
258
+ theme={theme}
259
+ options={{
260
+ fontSize,
261
+ minimap: { enabled: false },
262
+ scrollBeyondLastLine: false,
263
+ automaticLayout: true,
264
+ tabSize: 2,
265
+ wordWrap: 'on',
266
+ lineNumbers: 'on',
267
+ folding: true,
268
+ renderWhitespace: 'selection',
269
+
270
+ />
271
+ </div>
272
+
273
+ {/* Terminal/Output */}
274
+ <AnimatePresence>
275
+ {showTerminal && (
276
+ <motion.div
277
+ initial={{ width: 0, opacity: 0 }}
278
+ animate={{ width: '400px', opacity: 1 }}
279
+ exit={{ width: 0, opacity: 0 }}
280
+ className="bg-gray-800 rounded-lg overflow-hidden flex flex-col"
281
+ >
282
+ <div className="flex items-center justify-between p-3 bg-gray-900 border-b border-gray-700">
283
+ <div className="flex items-center space-x-reverse space-x-2">
284
+ <FaTerminal className="w-4 h-4 text-green-400" />
285
+ <span className="text-sm font-medium text-white">
286
+ {language === 'fa' ? 'خروجی' : 'Output'}
287
+ </span>
288
+ </div>
289
+ <div className="flex items-center space-x-reverse space-x-2">
290
+ {isRunning && (
291
+ <div className="flex space-x-reverse space-x-1">
292
+ <div className="w-2 h-2 bg-green-400 rounded-full animate-bounce" />
293
+ <div className="w-2 h-2 bg-green-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
294
+ <div className="w-2 h-2 bg-green-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
295
+ </div>
296
+ )}
297
+ {errors.length === 0 && output && (
298
+ <FaCheckCircle className="w-4 h-4 text-green-400" />
299
+ )}
300
+ {errors.length > 0 && (
301
+ <FaExclamationTriangle className="w-4 h-4 text-red-400" />
302
+ )}
303
+ </div>
304
+ </div>
305
+
306
+ <div className="flex-1 p-4 overflow-y-auto font-mono text-sm">
307
+ {errors.length > 0 ? (
308
+ <div className="space-y-2">
309
+ {errors.map((error, index) => (
310
+ <div key={index} className="p-2 bg-red-900 bg-opacity-30 border border-red-600 rounded text-red-400">
311
+ <span className="font-medium">Error:</span> {error}
312
+ </div>
313
+ ))}
314
+ </div>
315
+ ) : output ? (
316
+ <pre className="text-green-400 whitespace-pre-wrap">{output}</pre>
317
+ ) : (
318
+ <div className="text-gray-500">
319
+ {language === 'fa' ? 'کد را اجرا کنید تا خروجی نمایش داده شود' : 'Run the code to see output'}
320
+ </div>
321
+ )}
322
+ </div>
323
+ </motion.div>
324
+ )}
325
+ </AnimatePresence>
326
+ </div>
327
+ </div>
328
+ )
329
+ }