anycoder-f86f051e / components /CodeEditor.jsx
Mehdi
Upload components/CodeEditor.jsx with huggingface_hub
9e5629a verified
Raw
History Blame Contribute Delete
11 kB
import { useState, useEffect, useRef } from 'react'
import { FiPlay, FiSave, FiShare2, FiSettings, FiDownload, FiUpload } from 'react-icons/fi'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
import dynamic from 'next/dynamic'
const MonacoEditor = dynamic(() => import('@monaco-editor/react'), {
ssr: false,
loading: () => <div className="h-full bg-gray-800 rounded-lg animate-pulse" />
})
export default function CodeEditor() {
const [code, setCode] = useState('')
const [language, setLanguage] = useState('javascript')
const [output, setOutput] = useState('')
const [isRunning, setIsRunning] = useState(false)
const [error, setError] = useState(null)
const [theme, setTheme] = useState('vs-dark')
const [fontSize, setFontSize] = useState(14)
const [showSettings, setShowSettings] = useState(false)
const [savedSnippets, setSavedSnippets] = useState([])
const [snippetName, setSnippetName] = useState('')
const editorRef = useRef(null)
const languages = [
{ value: 'javascript', label: 'JavaScript', icon: '🟡' },
{ value: 'python', label: 'Python', icon: '🐍' },
{ value: 'java', label: 'Java', icon: '☕' },
{ value: 'csharp', label: 'C#', icon: '🔧' },
{ value: 'typescript', label: 'TypeScript', icon: '🔵' },
{ value: 'go', label: 'Go', icon: '🐹' },
{ value: 'rust', label: 'Rust', icon: '🦀' }
]
const themes = [
{ value: 'vs-dark', label: 'Dark' },
{ value: 'vs-light', label: 'Light' },
{ value: 'hc-black', label: 'High Contrast' }
]
useEffect(() => {
const saved = localStorage.getItem('codeSnippets')
if (saved) {
setSavedSnippets(JSON.parse(saved))
}
}, [])
const handleRunCode = async () => {
if (!code.trim()) return
setIsRunning(true)
setError(null)
setOutput('')
try {
const response = await fetch('/api/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ code, language }),
})
const data = await response.json()
if (response.ok) {
setOutput(data.output)
} else {
throw new Error(data.error || 'Execution failed')
}
} catch (err) {
setError(err.message)
} finally {
setIsRunning(false)
}
}
const handleSaveSnippet = () => {
if (!snippetName.trim()) return
const newSnippet = {
id: Date.now(),
name: snippetName,
code,
language,
createdAt: new Date().toISOString()
}
const updatedSnippets = [...savedSnippets, newSnippet]
setSavedSnippets(updatedSnippets)
localStorage.setItem('codeSnippets', JSON.stringify(updatedSnippets))
setSnippetName('')
}
const handleLoadSnippet = (snippet) => {
setCode(snippet.code)
setLanguage(snippet.language)
}
const handleDownloadCode = () => {
const blob = new Blob([code], { type: 'text/plain' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `code.${language === 'javascript' ? 'js' : language === 'python' ? 'py' : 'txt'}`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
}
const handleUploadCode = (e) => {
const file = e.target.files[0]
if (!file) return
const reader = new FileReader()
reader.onload = (event) => {
setCode(event.target.result)
}
reader.readAsText(file)
}
const handleEditorMount = (editor) => {
editorRef.current = editor
}
return (
<div className="space-y-4">
<div className="flex flex-wrap justify-between items-center gap-4">
<div className="flex items-center space-x-4">
<select
value={language}
onChange={(e) => setLanguage(e.target.value)}
className="bg-gray-700 text-white px-3 py-2 rounded-lg"
>
{languages.map((lang) => (
<option key={lang.value} value={lang.value}>
{lang.icon} {lang.label}
</option>
))}
</select>
<button
onClick={handleRunCode}
disabled={isRunning}
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`}
>
<FiPlay />
<span>{isRunning ? 'Running...' : 'Run Code'}</span>
</button>
<button
onClick={() => setShowSettings(!showSettings)}
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg flex items-center space-x-2 transition-colors"
>
<FiSettings />
<span>Settings</span>
</button>
</div>
<div className="flex items-center space-x-2">
<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">
<FiUpload />
<span>Upload</span>
<input type="file" className="hidden" onChange={handleUploadCode} accept=".js,.py,.java,.cs,.ts,.go,.rs" />
</label>
<button
onClick={handleDownloadCode}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center space-x-2 transition-colors"
>
<FiDownload />
<span>Download</span>
</button>
<div className="flex items-center space-x-2">
<input
type="text"
placeholder="Snippet name"
value={snippetName}
onChange={(e) => setSnippetName(e.target.value)}
className="px-3 py-2 bg-gray-700 text-white rounded-lg text-sm"
/>
<button
onClick={handleSaveSnippet}
disabled={!snippetName.trim()}
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"
>
<FiSave />
<span>Save</span>
</button>
</div>
</div>
</div>
{showSettings && (
<div className="bg-gray-800 rounded-lg p-4 mb-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Theme</label>
<select
value={theme}
onChange={(e) => setTheme(e.target.value)}
className="w-full bg-gray-700 text-white px-3 py-2 rounded-lg"
>
{themes.map((t) => (
<option key={t.value} value={t.value}>{t.label}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Font Size</label>
<input
type="range"
min="12"
max="24"
value={fontSize}
onChange={(e) => setFontSize(parseInt(e.target.value))}
className="w-full"
/>
<div className="text-center text-sm text-gray-300 mt-1">{fontSize}px</div>
</div>
</div>
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-[calc(100vh-250px)]">
<div className="bg-gray-800 rounded-lg overflow-hidden">
<div className="bg-gray-900 px-4 py-2 flex justify-between items-center">
<span className="text-sm text-gray-300">Editor</span>
{savedSnippets.length > 0 && (
<select
onChange={(e) => handleLoadSnippet(JSON.parse(e.target.value))}
className="bg-gray-700 text-white px-2 py-1 rounded text-sm"
>
<option value="">Load Snippet</option>
{savedSnippets.map((snippet) => (
<option key={snippet.id} value={JSON.stringify(snippet)}>
{snippet.name} ({snippet.language})
</option>
))}
</select>
)}
</div>
<div className="h-[calc(100%-40px)]">
<MonacoEditor
height="100%"
language={language}
theme={theme}
value={code}
onChange={setCode}
onMount={handleEditorMount}
options={{
fontSize,
minimap: { enabled: false },
wordWrap: 'on',
scrollBeyondLastLine: false,
automaticLayout: true
/>
</div>
</div>
<div className="bg-gray-100 rounded-lg p-4 overflow-auto">
<div className="flex justify-between items-center mb-4">
<h3 className="font-bold text-gray-800">Output Console</h3>
<button
onClick={() => setOutput('')}
className="text-sm text-gray-500 hover:text-gray-700"
>
Clear
</button>
</div>
{error ? (
<div className="text-red-600 p-3 bg-red-50 rounded-lg">
<strong>Error:</strong> {error}
</div>
) : (
<div className="bg-white p-3 rounded-lg h-[calc(100%-50px)] overflow-auto">
{output ? (
<SyntaxHighlighter language={language} style={atomDark} customStyle={{ background: 'transparent' }}>
{output}
</SyntaxHighlighter>
) : (
<p className="text-gray-500">Code execution output will appear here...</p>
)}
</div>
)}
</div>
</div>
{savedSnippets.length > 0 && (
<div className="mt-4 bg-gray-800 rounded-lg p-4">
<h3 className="font-bold text-white mb-3">Saved Snippets</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{savedSnippets.map((snippet) => (
<div key={snippet.id} className="bg-gray-700 rounded-lg p-3 hover:bg-gray-600 transition-colors cursor-pointer" onClick={() => handleLoadSnippet(snippet)}>
<div className="flex justify-between items-start">
<div>
<h4 className="font-medium text-white">{snippet.name}</h4>
<p className="text-xs text-gray-400">{snippet.language}</p>
</div>
<span className="text-xs text-gray-400">
{new Date(snippet.createdAt).toLocaleDateString()}
</span>
</div>
</div>
))}
</div>
</div>
)}
</div>
)
}