anycoder-6d5b65da / components /CodeEditor.jsx
Mehdi
Upload components/CodeEditor.jsx with huggingface_hub
0758862 verified
Raw
History Blame
13.3 kB
import { useState, useEffect, useRef } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import Editor from '@monaco-editor/react'
import {
FaPlay, FaSave, FaDownload, FaUpload, FaCopy, FaTerminal,
FaCode, FaBug, FaCheckCircle, FaExclamationTriangle, FaCog,
FaFolderOpen, FaFile, FaFileCode, FaJs, FaPython, FaJava,
FaHtml5, FaCss3Alt, FaReact, FaNodeJs, FaGitAlt, FaDatabase
} from 'react-icons/fa'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism'
import toast from 'react-hot-toast'
export default function CodeEditor({ language, theme }) {
const [code, setCode] = useState(`// ${language === 'fa' ? 'کد خود را اینجا بنویسید' : 'Write your code here'}\nfunction hello() {\n console.log("Hello, World!");\n}`)
const [output, setOutput] = useState('')
const [isRunning, setIsRunning] = useState(false)
const [selectedLanguage, setSelectedLanguage] = useState('javascript')
const [files, setFiles] = useState([
{ id: 1, name: 'main.js', content: code, language: 'javascript' },
{ id: 2, name: 'utils.py', content: '# Python utilities', language: 'python' }
])
const [activeFile, setActiveFile] = useState(1)
const [fontSize, setFontSize] = useState(14)
const [theme, setEditorTheme] = useState('vs-dark')
const [showTerminal, setShowTerminal] = useState(true)
const [errors, setErrors] = useState([])
const editorRef = useRef(null)
const languages = [
{ id: 'javascript', name: 'JavaScript', icon: FaJs, color: 'text-yellow-400' },
{ id: 'python', name: 'Python', icon: FaPython, color: 'text-blue-400' },
{ id: 'java', name: 'Java', icon: FaJava, color: 'text-red-400' },
{ id: 'html', name: 'HTML', icon: FaHtml5, color: 'text-orange-500' },
{ id: 'css', name: 'CSS', icon: FaCss3Alt, color: 'text-blue-500' },
{ id: 'typescript', name: 'TypeScript', icon: FaCode, color: 'text-blue-600' },
{ id: 'json', name: 'JSON', icon: FaDatabase, color: 'text-green-500' },
{ id: 'sql', name: 'SQL', icon: FaDatabase, color: 'text-purple-500' }
]
const handleEditorDidMount = (editor, monaco) => {
editorRef.current = editor
editor.focus()
}
const runCode = async () => {
setIsRunning(true)
setOutput('')
setErrors([])
try {
// Simulate code execution
await new Promise(resolve => setTimeout(resolve, 2000))
if (selectedLanguage === 'javascript') {
// Safe eval simulation
const result = 'Code executed successfully!\nOutput: Hello, World!'
setOutput(result)
toast.success(language === 'fa' ? 'کد با موفقیت اجرا شد' : 'Code executed successfully')
} else {
setOutput(`${selectedLanguage.toUpperCase()} code execution simulated`)
}
} catch (error) {
setErrors([error.message])
toast.error(language === 'fa' ? 'خطا در اجرای کد' : 'Error executing code')
} finally {
setIsRunning(false)
}
}
const saveCode = () => {
const updatedFiles = files.map(file =>
file.id === activeFile ? { ...file, content: code } : file
)
setFiles(updatedFiles)
toast.success(language === 'fa' ? 'کد ذخیره شد' : 'Code saved')
}
const downloadCode = () => {
const file = files.find(f => f.id === activeFile)
const blob = new Blob([code], { type: 'text/plain' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = file.name
a.click()
toast.success(language === 'fa' ? 'کد دانلود شد' : 'Code downloaded')
}
const uploadFile = (event) => {
const file = event.target.files[0]
if (file) {
const reader = new FileReader()
reader.onload = (e) => {
const content = e.target.result
const newFile = {
id: Date.now(),
name: file.name,
content: content,
language: selectedLanguage
}
setFiles([...files, newFile])
setActiveFile(newFile.id)
setCode(content)
toast.success(language === 'fa' ? 'فایل آپلود شد' : 'File uploaded')
}
reader.readAsText(file)
}
}
const createNewFile = () => {
const newFile = {
id: Date.now(),
name: `untitled.${selectedLanguage === 'javascript' ? 'js' : selectedLanguage}`,
content: `// New ${selectedLanguage} file`,
language: selectedLanguage
}
setFiles([...files, newFile])
setActiveFile(newFile.id)
setCode(newFile.content)
}
const deleteFile = (fileId) => {
if (files.length > 1) {
setFiles(files.filter(f => f.id !== fileId))
if (activeFile === fileId) {
setActiveFile(files[0].id)
setCode(files[0].content)
}
toast.success(language === 'fa' ? 'فایل حذف شد' : 'File deleted')
}
}
useEffect(() => {
const activeFileData = files.find(f => f.id === activeFile)
if (activeFileData) {
setCode(activeFileData.content)
setSelectedLanguage(activeFileData.language)
}
}, [activeFile])
return (
<div className="flex flex-col h-full space-y-4">
{/* Toolbar */}
<div className="flex items-center justify-between p-4 bg-gray-800 rounded-lg">
<div className="flex items-center space-x-reverse space-x-3">
<button
onClick={runCode}
disabled={isRunning}
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"
>
<FaPlay className="w-4 h-4" />
<span>{isRunning ? (language === 'fa' ? 'در حال اجرا...' : 'Running...') : (language === 'fa' ? 'اجرا' : 'Run')}</span>
</button>
<button
onClick={saveCode}
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"
>
<FaSave className="w-4 h-4" />
<span>{language === 'fa' ? 'ذخیره' : 'Save'}</span>
</button>
<button
onClick={downloadCode}
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"
>
<FaDownload className="w-4 h-4" />
<span>{language === 'fa' ? 'دانلود' : 'Download'}</span>
</button>
<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">
<FaUpload className="w-4 h-4" />
<span>{language === 'fa' ? 'آپلود' : 'Upload'}</span>
<input type="file" onChange={uploadFile} className="hidden" accept=".js,.py,.java,.html,.css,.json,.sql,.ts,.tsx" />
</label>
<button
onClick={createNewFile}
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"
>
<FaFileCode className="w-4 h-4" />
<span>{language === 'fa' ? 'فایل جدید' : 'New File'}</span>
</button>
</div>
<div className="flex items-center space-x-reverse space-x-3">
<select
value={selectedLanguage}
onChange={(e) => setSelectedLanguage(e.target.value)}
className="px-3 py-2 bg-gray-700 text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
{languages.map(lang => (
<option key={lang.id} value={lang.id}>{lang.name}</option>
))}
</select>
<button
onClick={() => setShowTerminal(!showTerminal)}
className={`p-2 rounded-lg transition-all ${
showTerminal ? 'bg-primary-600 text-white' : 'bg-gray-700 text-gray-300'
}`}
>
<FaTerminal className="w-4 h-4" />
</button>
<button
onClick={() => {
setFontSize(prev => prev === 14 ? 16 : prev === 16 ? 18 : 14)
toast.success(language === 'fa' ? `اندازه فونت: ${fontSize === 14 ? 16 : fontSize === 16 ? 18 : 14}px` : `Font size: ${fontSize === 14 ? 16 : fontSize === 16 ? 18 : 14}px`)
className="p-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600 transition-all"
>
<FaCog className="w-4 h-4" />
</button>
</div>
</div>
{/* File Tabs */}
<div className="flex items-center space-x-reverse space-x-1 p-2 bg-gray-800 rounded-lg overflow-x-auto">
{files.map(file => (
<button
key={file.id}
onClick={() => setActiveFile(file.id)}
className={`flex items-center space-x-reverse space-x-2 px-3 py-2 rounded-lg transition-all whitespace-nowrap ${
activeFile === file.id
? 'bg-primary-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
<FaFile className="w-3 h-3" />
<span className="text-sm">{file.name}</span>
{files.length > 1 && (
<button
onClick={(e) => {
e.stopPropagation()
deleteFile(file.id)
className="opacity-50 hover:opacity-100 transition-opacity"
>
×
</button>
)}
</button>
))}
</div>
{/* Editor and Output */}
<div className="flex-1 flex space-x-reverse space-x-4 min-h-0">
{/* Code Editor */}
<div className="flex-1 bg-gray-800 rounded-lg overflow-hidden">
<Editor
height="100%"
language={selectedLanguage}
value={code}
onChange={(value) => setCode(value)}
onMount={handleEditorDidMount}
theme={theme}
options={{
fontSize,
minimap: { enabled: false },
scrollBeyondLastLine: false,
automaticLayout: true,
tabSize: 2,
wordWrap: 'on',
lineNumbers: 'on',
folding: true,
renderWhitespace: 'selection',
/>
</div>
{/* Terminal/Output */}
<AnimatePresence>
{showTerminal && (
<motion.div
initial={{ width: 0, opacity: 0 }}
animate={{ width: '400px', opacity: 1 }}
exit={{ width: 0, opacity: 0 }}
className="bg-gray-800 rounded-lg overflow-hidden flex flex-col"
>
<div className="flex items-center justify-between p-3 bg-gray-900 border-b border-gray-700">
<div className="flex items-center space-x-reverse space-x-2">
<FaTerminal className="w-4 h-4 text-green-400" />
<span className="text-sm font-medium text-white">
{language === 'fa' ? 'خروجی' : 'Output'}
</span>
</div>
<div className="flex items-center space-x-reverse space-x-2">
{isRunning && (
<div className="flex space-x-reverse space-x-1">
<div className="w-2 h-2 bg-green-400 rounded-full animate-bounce" />
<div className="w-2 h-2 bg-green-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
<div className="w-2 h-2 bg-green-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
</div>
)}
{errors.length === 0 && output && (
<FaCheckCircle className="w-4 h-4 text-green-400" />
)}
{errors.length > 0 && (
<FaExclamationTriangle className="w-4 h-4 text-red-400" />
)}
</div>
</div>
<div className="flex-1 p-4 overflow-y-auto font-mono text-sm">
{errors.length > 0 ? (
<div className="space-y-2">
{errors.map((error, index) => (
<div key={index} className="p-2 bg-red-900 bg-opacity-30 border border-red-600 rounded text-red-400">
<span className="font-medium">Error:</span> {error}
</div>
))}
</div>
) : output ? (
<pre className="text-green-400 whitespace-pre-wrap">{output}</pre>
) : (
<div className="text-gray-500">
{language === 'fa' ? 'کد را اجرا کنید تا خروجی نمایش داده شود' : 'Run the code to see output'}
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
)
}