tahamajs's picture
download
raw
11.4 kB
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.