| import { useEffect, useState, useRef } from "react"; | |
| import { Check, Copy, FileCode, Save, Play, Code, Eye, RefreshCw, Terminal } from "lucide-react"; | |
| import { toast } from "react-toastify"; | |
| interface CodeEditorProps { | |
| onRun?: (value: string, language: string) => void | Promise<void>; | |
| enablePreview?: boolean; | |
| value?: string; | |
| defaultValue?: string; | |
| language?: string; | |
| readOnly?: boolean; | |
| onChange?: (value: string) => void; | |
| onSave?: (value: string) => void | Promise<void>; | |
| } | |
| export function CodeEditor({ | |
| value, | |
| defaultValue = "", | |
| language: initialLanguage = "typescript", | |
| readOnly = false, | |
| enablePreview = true, | |
| onChange, | |
| onRun, | |
| onSave, | |
| }: CodeEditorProps) { | |
| const [draft, setDraft] = useState(value ?? defaultValue); | |
| const [language, setLanguage] = useState(initialLanguage); | |
| const [copied, setCopied] = useState(false); | |
| const [saved, setSaved] = useState(false); | |
| const [activeTab, setActiveTab] = useState<"code" | "preview">("code"); | |
| const [isRunning, setIsRunning] = useState(false); | |
| const [consoleOutput, setConsoleOutput] = useState<string[]>([]); | |
| const textareaRef = useRef<HTMLTextAreaElement>(null); | |
| useEffect(() => { | |
| if (value !== undefined) setDraft(value); | |
| }, [value]); | |
| const updateDraft = (next: string) => { | |
| setDraft(next); | |
| onChange?.(next); | |
| }; | |
| const copyCode = async () => { | |
| await navigator.clipboard.writeText(draft); | |
| setCopied(true); | |
| toast.success("Code copied to clipboard!"); | |
| window.setTimeout(() => setCopied(false), 1500); | |
| }; | |
| const handleSave = async () => { | |
| if (!onSave) return; | |
| try { | |
| await onSave(draft); | |
| setSaved(true); | |
| toast.success("File saved successfully"); | |
| window.setTimeout(() => setSaved(false), 1500); | |
| } catch (e) { | |
| console.error("Failed to save code:", e); | |
| toast.error("Failed to save file"); | |
| } | |
| }; | |
| const triggerRun = async () => { | |
| if (onRun) { | |
| setIsRunning(true); | |
| try { | |
| await onRun(draft, language); | |
| } catch (err) { | |
| console.error(err); | |
| } finally { | |
| setIsRunning(false); | |
| } | |
| return; | |
| } | |
| // Interactive simulator | |
| setIsRunning(true); | |
| setConsoleOutput(["[Compiler] Initializing workspace build...", `[Compiler] Targeting execution platform (${language})`]); | |
| setTimeout(() => { | |
| setConsoleOutput(prev => [...prev, "[Sandbox] Allocating execution container...", "[Process] Running entrypoint script..."]); | |
| setTimeout(() => { | |
| if (language === "python") { | |
| setConsoleOutput(prev => [ | |
| ...prev, | |
| "-------------------- OUTPUT --------------------", | |
| "Hello, Researcher!", | |
| "Running quantum simulations on local dataset...", | |
| "Completed 500 iterations in 0.084s", | |
| "Process terminated successfully with exit code 0." | |
| ]); | |
| } else if (language === "javascript" || language === "typescript") { | |
| setConsoleOutput(prev => [ | |
| ...prev, | |
| "-------------------- OUTPUT --------------------", | |
| "Starting collaborative sync engine...", | |
| "Websocket established on port 8080.", | |
| "Client sync established. OK.", | |
| "Process terminated successfully with exit code 0." | |
| ]); | |
| } else if (language === "rust") { | |
| setConsoleOutput(prev => [ | |
| ...prev, | |
| " Compiling workspace_lib v0.1.0 (/sandbox)", | |
| " Finished dev [unoptimized + debuginfo] target(s) in 0.42s", | |
| " Running `target/debug/workspace_lib`", | |
| "-------------------- OUTPUT --------------------", | |
| "Rust memory safety audit: PASSED", | |
| "Process terminated successfully with exit code 0." | |
| ]); | |
| } else { | |
| setConsoleOutput(prev => [ | |
| ...prev, | |
| "-------------------- OUTPUT --------------------", | |
| `Executed ${language} script.`, | |
| "Process completed with exit code 0." | |
| ]); | |
| } | |
| setIsRunning(false); | |
| setActiveTab("preview"); | |
| }, 1000); | |
| }, 800); | |
| }; | |
| const lineCount = draft.split("\n").length || 1; | |
| const supportedLanguages = [ | |
| { value: "typescript", label: "TypeScript" }, | |
| { value: "javascript", label: "JavaScript" }, | |
| { value: "python", label: "Python" }, | |
| { value: "html", label: "HTML" }, | |
| { value: "css", label: "CSS" }, | |
| { value: "json", label: "JSON" }, | |
| { value: "java", label: "Java" }, | |
| { value: "rust", label: "Rust" }, | |
| ]; | |
| return ( | |
| <section className="flex h-full min-h-[360px] flex-col bg-slate-950/90 backdrop-blur-md text-slate-100 rounded-b-2xl overflow-hidden border border-white/5 shadow-lg select-text font-sans"> | |
| {/* Editor Header */} | |
| <header className="flex items-center justify-between border-b border-white/10 px-4 py-3 bg-slate-900/40 backdrop-blur-md select-none"> | |
| <div className="flex items-center gap-3"> | |
| <FileCode className="h-4.5 w-4.5 text-teal-400" /> | |
| <select | |
| value={language} | |
| onChange={(e) => setLanguage(e.target.value)} | |
| disabled={readOnly} | |
| className="bg-slate-900/60 text-slate-200 text-xs font-bold uppercase tracking-wider rounded-xl px-2.5 py-1.5 outline-none border border-white/10 cursor-pointer focus:border-teal-500/50" | |
| > | |
| {supportedLanguages.map((lang) => ( | |
| <option key={lang.value} value={lang.value}> | |
| {lang.label} | |
| </option> | |
| ))} | |
| </select> | |
| {enablePreview && ( | |
| <div className="flex bg-slate-900/60 rounded-xl p-1 border border-white/5"> | |
| <button | |
| onClick={() => setActiveTab("code")} | |
| className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-bold rounded-lg transition-all ${activeTab === "code" ? "bg-teal-500/10 text-teal-400" : "text-slate-500 hover:text-slate-300"}`} | |
| > | |
| <Code className="h-3.5 w-3.5" /> Source | |
| </button> | |
| <button | |
| onClick={() => setActiveTab("preview")} | |
| className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-bold rounded-lg transition-all ${activeTab === "preview" ? "bg-teal-500/10 text-teal-400" : "text-slate-500 hover:text-slate-300"}`} | |
| > | |
| <Eye className="h-3.5 w-3.5" /> Console & Live | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| {activeTab === "code" && ( | |
| <button | |
| onClick={triggerRun} | |
| disabled={isRunning} | |
| className="px-3 py-1.5 rounded-xl bg-teal-500/15 hover:bg-teal-500/25 text-teal-450 hover:text-teal-305 transition active:scale-95 flex items-center gap-1.5 disabled:opacity-50 text-[10px] font-bold uppercase tracking-wider" | |
| > | |
| {isRunning ? <RefreshCw className="h-3.5 w-3.5 animate-spin" /> : <Play className="h-3.5 w-3.5" />} | |
| <span>{isRunning ? "Running" : "Run Code"}</span> | |
| </button> | |
| )} | |
| <button | |
| onClick={copyCode} | |
| className="p-2 rounded-xl hover:bg-white/10 text-slate-450 hover:text-white transition-all active:scale-95" | |
| title="Copy code" | |
| > | |
| {copied ? <Check className="h-4 w-4 text-emerald-450" /> : <Copy className="h-4 w-4" />} | |
| </button> | |
| {onSave && ( | |
| <button | |
| onClick={handleSave} | |
| className="p-2 rounded-xl hover:bg-white/10 text-slate-450 hover:text-white transition-all active:scale-95 flex items-center gap-1.5" | |
| title="Save code" | |
| > | |
| <Save className={`h-4 w-4 ${saved ? "text-teal-400" : ""}`} /> | |
| {saved && <span className="text-[10px] text-teal-400 font-bold uppercase tracking-wider">Saved</span>} | |
| </button> | |
| )} | |
| </div> | |
| </header> | |
| {/* Editor Workspace */} | |
| <div className="flex-1 flex min-h-0 relative font-mono text-sm leading-6"> | |
| {activeTab === "preview" ? ( | |
| language === "html" ? ( | |
| <div className="w-full h-full bg-white rounded-b-xl overflow-hidden relative"> | |
| <iframe | |
| title="preview" | |
| srcDoc={draft} | |
| className="w-full h-full border-none" | |
| sandbox="allow-scripts allow-modals" | |
| /> | |
| </div> | |
| ) : ( | |
| <div className="w-full h-full bg-slate-950 p-5 overflow-y-auto text-xs leading-relaxed text-slate-300 font-mono flex flex-col gap-1.5 custom-scrollbar"> | |
| <div className="flex items-center gap-2 border-b border-white/5 pb-2 text-[10px] font-bold text-slate-500 uppercase tracking-widest select-none"> | |
| <Terminal className="w-3.5 h-3.5 text-teal-500" /> | |
| Execution Output Sandbox | |
| </div> | |
| {consoleOutput.length === 0 ? ( | |
| <div className="flex flex-col items-center justify-center flex-1 text-slate-600 select-none py-12"> | |
| <Play className="w-8 h-8 opacity-25 mb-3" /> | |
| <p className="font-bold">No execution logs found</p> | |
| <p className="text-[10px] mt-1 text-slate-550">Click "Run Code" inside the editor source view to execute scripts</p> | |
| </div> | |
| ) : ( | |
| consoleOutput.map((line, idx) => ( | |
| <p | |
| key={idx} | |
| className={ | |
| line.startsWith("[Compiler]") | |
| ? "text-indigo-400" | |
| : line.startsWith("[Process]") || line.startsWith("[Sandbox]") | |
| ? "text-teal-400" | |
| : line.includes("error") || line.includes("failure") | |
| ? "text-rose-400" | |
| : line.startsWith("Hello") || line.startsWith("Rust") || line.includes("terminated") | |
| ? "text-emerald-400" | |
| : "text-slate-350" | |
| } | |
| > | |
| {line} | |
| </p> | |
| )) | |
| )} | |
| </div> | |
| ) | |
| ) : ( | |
| <> | |
| {/* Line Numbers Gutter */} | |
| <div className="w-12 bg-slate-900/30 text-slate-500 dark:text-slate-650 text-right pr-4 pt-4 select-none border-r border-white/5 text-xs font-semibold overflow-hidden"> | |
| {Array.from({ length: lineCount }).map((_, idx) => ( | |
| <div key={idx} className="h-6">{idx + 1}</div> | |
| ))} | |
| </div> | |
| {/* Code TextArea */} | |
| <textarea | |
| ref={textareaRef} | |
| value={draft} | |
| readOnly={readOnly} | |
| onChange={(event) => updateDraft(event.target.value)} | |
| spellCheck={false} | |
| className="min-h-0 flex-1 resize-none bg-transparent p-4 font-mono text-sm leading-6 text-slate-200 outline-none select-text selection:bg-teal-500/20 custom-scrollbar" | |
| placeholder="// Start coding..." | |
| /> | |
| </> | |
| )} | |
| </div> | |
| </section> | |
| ); | |
| } | |
| export default CodeEditor; | |
Xet Storage Details
- Size:
- 11.4 kB
- Xet hash:
- 06304ba1b03f45782723b7f92af80c66c90547f37b2b4cfbbe6bed406b17a885
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.