import { useState, useRef, useEffect, useCallback } from 'react'; import Editor from '@monaco-editor/react'; import { motion, AnimatePresence } from 'motion/react'; import { Play, RotateCcw, Copy, Download, Terminal, CheckCircle2, XCircle, Loader2, Code2, FileCode2, Cpu, Activity, Braces, ChevronDown, ChevronUp, Clock, Maximize2, Minimize2, Trash2, type LucideIcon, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { getCodeEditorFontSize } from '@/lib/preferences'; /* ────────────────────────────── Language Config ────────────────────────────── */ interface LanguageDef { id: string; label: string; monacoId: string; icon: string; extension: string; accent: string; glow: string; dot: string; template: string; } const LANGUAGES: LanguageDef[] = [ { id: 'python', label: 'Python', monacoId: 'python', icon: 'PY', extension: 'py', accent: 'from-emerald-300 to-teal-300', glow: 'shadow-emerald-500/25', dot: 'bg-emerald-300', template: `# RYP Online Compiler - Python # Write your code below and click Run name = input("Enter your name: ") print(f"Hello, {name}! Welcome to RYP.") # Try more: nums = [int(x) for x in input("Enter numbers (space-separated): ").split()] print(f"Sum = {sum(nums)}") print(f"Max = {max(nums)}") print(f"Min = {min(nums)}") `, }, { id: 'cpp', label: 'C++', monacoId: 'cpp', icon: 'C++', extension: 'cpp', accent: 'from-sky-300 to-blue-400', glow: 'shadow-sky-500/25', dot: 'bg-sky-300', template: `// RYP Online Compiler - C++ #include #include #include using namespace std; int main() { string name; cout << "Enter your name: "; getline(cin, name); cout << "Hello, " << name << "! Welcome to RYP." << endl; int n; cout << "How many numbers? "; cin >> n; vector nums(n); cout << "Enter " << n << " numbers: "; for (int i = 0; i < n; i++) { cin >> nums[i]; } int total = 0; for (int x : nums) total += x; cout << "Sum = " << total << endl; cout << "Max = " << *max_element(nums.begin(), nums.end()) << endl; cout << "Min = " << *min_element(nums.begin(), nums.end()) << endl; return 0; } `, }, { id: 'java', label: 'Java', monacoId: 'java', icon: 'JV', extension: 'java', accent: 'from-amber-300 to-orange-400', glow: 'shadow-amber-500/25', dot: 'bg-amber-300', template: `// RYP Online Compiler - Java import java.util.Scanner; import java.util.Arrays; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.print("Enter your name: "); String name = sc.nextLine(); System.out.println("Hello, " + name + "! Welcome to RYP."); System.out.print("How many numbers? "); int n = sc.nextInt(); int[] nums = new int[n]; System.out.print("Enter " + n + " numbers: "); for (int i = 0; i < n; i++) { nums[i] = sc.nextInt(); } int sum = Arrays.stream(nums).sum(); int max = Arrays.stream(nums).max().orElse(0); int min = Arrays.stream(nums).min().orElse(0); System.out.println("Sum = " + sum); System.out.println("Max = " + max); System.out.println("Min = " + min); } } `, }, { id: 'javascript', label: 'JavaScript', monacoId: 'javascript', icon: 'JS', extension: 'js', accent: 'from-yellow-200 to-amber-300', glow: 'shadow-yellow-500/20', dot: 'bg-yellow-200', template: `// RYP Online Compiler - JavaScript (Node.js) // Note: Uses readline for stdin const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); function ask(question) { return new Promise(resolve => rl.question(question, resolve)); } (async () => { const name = await ask("Enter your name: "); console.log(\`Hello, \${name}! Welcome to RYP.\`); const input = await ask("Enter numbers (space-separated): "); const nums = input.split(" ").map(Number); console.log(\`Sum = \${nums.reduce((a, b) => a + b, 0)}\`); console.log(\`Max = \${Math.max(...nums)}\`); console.log(\`Min = \${Math.min(...nums)}\`); rl.close(); })(); `, }, { id: 'go', label: 'Go', monacoId: 'go', icon: 'GO', extension: 'go', accent: 'from-cyan-200 to-sky-300', glow: 'shadow-cyan-500/25', dot: 'bg-cyan-200', template: `// RYP Online Compiler - Go package main import ( \t"bufio" \t"fmt" \t"os" ) func main() { \treader := bufio.NewReader(os.Stdin) \tfmt.Print("Enter your name: ") \tname, _ := reader.ReadString('\\n') \tfmt.Printf("Hello, %s! Welcome to RYP.\\n", name[:len(name)-1]) \tvar n int \tfmt.Print("How many numbers? ") \tfmt.Scan(&n) \tnums := make([]int, n) \tfmt.Printf("Enter %d numbers: ", n) \tfor i := 0; i < n; i++ { \t\tfmt.Scan(&nums[i]) \t} \tsum, mx, mn := 0, nums[0], nums[0] \tfor _, v := range nums { \t\tsum += v \t\tif v > mx { mx = v } \t\tif v < mn { mn = v } \t} \tfmt.Printf("Sum = %d\\nMax = %d\\nMin = %d\\n", sum, mx, mn) } `, }, { id: 'typescript', label: 'TypeScript', monacoId: 'typescript', icon: 'TS', extension: 'ts', accent: 'from-blue-300 to-indigo-300', glow: 'shadow-blue-500/25', dot: 'bg-blue-300', template: `// RYP Online Compiler - TypeScript (Node.js) // Note: Uses readline for stdin const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); function ask(question: string): Promise { return new Promise(resolve => rl.question(question, resolve)); } (async () => { const name = await ask("Enter your name: "); console.log(\`Hello, \${name}! Welcome to RYP.\`); const input = await ask("Enter numbers (space-separated): "); const nums = input.split(" ").map(Number); console.log(\`Sum = \${nums.reduce((a: number, b: number) => a + b, 0)}\`); console.log(\`Max = \${Math.max(...nums)}\`); console.log(\`Min = \${Math.min(...nums)}\`); rl.close(); })(); `, }, { id: 'c', label: 'C', monacoId: 'c', icon: 'C', extension: 'c', accent: 'from-rose-300 to-pink-300', glow: 'shadow-rose-500/20', dot: 'bg-rose-300', template: `// RYP Online Compiler - C #include int main() { char name[100]; printf("Enter your name: "); fgets(name, sizeof(name), stdin); // Remove newline for (int i = 0; name[i]; i++) { if (name[i] == '\\n') { name[i] = '\\0'; break; } } printf("Hello, %s! Welcome to RYP.\\n", name); int n; printf("How many numbers? "); scanf("%d", &n); int nums[100], sum = 0, mx, mn; printf("Enter %d numbers: ", n); for (int i = 0; i < n; i++) { scanf("%d", &nums[i]); sum += nums[i]; if (i == 0) { mx = mn = nums[0]; } else { if (nums[i] > mx) mx = nums[i]; if (nums[i] < mn) mn = nums[i]; } } printf("Sum = %d\\nMax = %d\\nMin = %d\\n", sum, mx, mn); return 0; } `, }, ]; /* ────────────────────────────── Execution API ─────────────────────────────── */ interface ExecResult { success: boolean; stdout: string; stderr: string; output: string; error: string; exitCode: number; executionTime: number; durationMs: number; } async function executeCode( language: string, code: string, input: string, ): Promise { const res = await fetch('/api/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ language, code, input }), }); if (!res.ok) { const text = await res.text(); let errMsg = 'Execution failed'; try { const j = JSON.parse(text); errMsg = j.error || errMsg; } catch { errMsg = text || errMsg; } return { success: false, stdout: '', stderr: errMsg, output: '', error: errMsg, exitCode: -1, executionTime: 0, durationMs: 0, }; } return res.json(); } /* ────────────────────────────── Component ─────────────────────────────────── */ export default function CodingPage({ fontSize = 'medium' }: { fontSize?: 'small' | 'medium' | 'large' }) { const [language, setLanguage] = useState('python'); const [code, setCode] = useState(LANGUAGES[0].template); const [userInput, setUserInput] = useState(''); const [isRunning, setIsRunning] = useState(false); const [result, setResult] = useState(null); const [showInput, setShowInput] = useState(true); const [isFullscreen, setIsFullscreen] = useState(false); const [copied, setCopied] = useState(false); const [filename, setFilename] = useState('main'); const editorRef = useRef(null); const containerRef = useRef(null); const currentLang = LANGUAGES.find((l) => l.id === language) ?? LANGUAGES[0]; const codeLineCount = code.trim() ? code.split(/\r\n|\r|\n/).length : 0; const inputLineCount = userInput.trim() ? userInput.split(/\r\n|\r|\n/).length : 0; const errorText = result?.error || result?.stderr || ''; const visibleOutput = result?.stdout || errorText || ''; const outputLineCount = visibleOutput ? visibleOutput.split(/\r\n|\r|\n/).length : 0; const executionDuration = result?.executionTime || result?.durationMs || 0; const runStatus = isRunning ? 'Executing' : result ? result.success ? 'Last run passed' : 'Review output' : 'Ready'; // Handle language change const handleLanguageChange = useCallback( (langId: string) => { const lang = LANGUAGES.find((l) => l.id === langId); if (!lang) return; setLanguage(langId); setCode(lang.template); setResult(null); }, [], ); // Reset code to template const handleReset = useCallback(() => { setCode(currentLang.template); setResult(null); setUserInput(''); setFilename('main'); }, [currentLang]); // Run code const handleRun = useCallback(async () => { setIsRunning(true); setResult(null); try { const res = await executeCode(language, code, userInput); setResult(res); } catch (err: any) { setResult({ success: false, stdout: '', stderr: err.message || 'Unknown error', output: '', error: err.message || 'Unknown error', exitCode: -1, executionTime: 0, durationMs: 0, }); } finally { setIsRunning(false); } }, [language, code, userInput]); // Keyboard shortcut: Ctrl+Enter to run useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); if (!isRunning) handleRun(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [handleRun, isRunning]); // Copy code const handleCopy = useCallback(() => { navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 2000); }, [code]); // Download code const handleDownload = useCallback(() => { const blob = new Blob([code], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${filename}.${currentLang.extension}`; a.click(); URL.revokeObjectURL(url); }, [code, currentLang, filename]); // Toggle fullscreen const toggleFullscreen = useCallback(() => { if (!containerRef.current) return; if (!document.fullscreenElement) { containerRef.current.requestFullscreen().catch(() => {}); setIsFullscreen(true); } else { document.exitFullscreen().catch(() => {}); setIsFullscreen(false); } }, []); useEffect(() => { const handler = () => { setIsFullscreen(!!document.fullscreenElement); // Layout AFTER the fullscreen transition finishes so dimensions are final requestAnimationFrame(() => editorRef.current?.layout()); const t = setTimeout(() => editorRef.current?.layout(), 300); const t2 = setTimeout(() => editorRef.current?.layout(), 600); return () => { clearTimeout(t); clearTimeout(t2); }; }; document.addEventListener('fullscreenchange', handler); return () => document.removeEventListener('fullscreenchange', handler); }, []); // Layout the editor on mount, input toggle, and window resize useEffect(() => { const layout = () => { requestAnimationFrame(() => editorRef.current?.layout()); }; window.addEventListener('resize', layout); return () => window.removeEventListener('resize', layout); }, []); useEffect(() => { requestAnimationFrame(() => editorRef.current?.layout()); const t = setTimeout(() => editorRef.current?.layout(), 250); return () => clearTimeout(t); }, [showInput]); return (
{/* ── Header Bar ─────────────────────────────────────────────────── */}

Online Compiler

{currentLang.icon}
{runStatus}
{/* ── Main Content ───────────────────────────────────────────────── */}
{/* ── Editor Panel ─────────────────────────────────────────── */}
{/* Editor file tab */}
{currentLang.icon} setFilename(e.target.value)} onBlur={(e) => { if (!e.target.value.trim()) setFilename('main'); }} onKeyDown={(e) => { if (e.key === 'Enter') (e.target as HTMLInputElement).blur(); }} className="w-32 border-b border-transparent bg-transparent text-xs font-semibold text-slate-300 outline-none transition-colors focus:border-cyan-300/40 focus:text-white" spellCheck={false} /> .{currentLang.extension} {currentLang.label}
{/* Monaco Editor */}
{ monaco.editor.defineTheme('ryp-lab-dark', { base: 'vs-dark', inherit: true, rules: [ { token: 'comment', foreground: '64748b', fontStyle: 'italic' }, { token: 'keyword', foreground: '67e8f9' }, { token: 'string', foreground: '86efac' }, { token: 'number', foreground: 'fbbf24' }, { token: 'type', foreground: 'c4b5fd' }, ], colors: { 'editor.background': '#080d16', 'editor.foreground': '#dbeafe', 'editor.lineHighlightBackground': '#ffffff08', 'editorLineNumber.foreground': '#334155', 'editorLineNumber.activeForeground': '#a7f3d0', 'editorCursor.foreground': '#5eead4', 'editor.selectionBackground': '#155e7555', 'editor.inactiveSelectionBackground': '#1e293b80', 'editorIndentGuide.background1': '#1e293b', 'editorIndentGuide.activeBackground1': '#38bdf8', }, }); }} onChange={(value) => setCode(value || '')} onMount={(editorInstance) => { editorRef.current = editorInstance; editorInstance.layout(); }} options={{ fontSize: getCodeEditorFontSize(fontSize), lineHeight: 21, fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace", fontLigatures: true, minimap: { enabled: true, maxColumn: 80 }, scrollBeyondLastLine: false, automaticLayout: true, padding: { top: 16, bottom: 16 }, smoothScrolling: true, cursorSmoothCaretAnimation: 'on', cursorBlinking: 'smooth', renderLineHighlight: 'all', renderWhitespace: 'selection', stickyScroll: { enabled: true }, scrollbar: { vertical: 'auto', horizontal: 'auto', verticalScrollbarSize: 14, horizontalScrollbarSize: 14, useShadows: false, }, lineNumbers: 'on', lineNumbersMinChars: 3, wordWrap: 'on', wordWrapColumn: 80, wrappingIndent: 'same', folding: true, foldingHighlight: true, foldingStrategy: 'auto', showFoldingControls: 'always', bracketPairColorization: { enabled: true }, guides: { bracketPairs: true, indentation: true, highlightActiveIndentation: true, }, hover: { enabled: true }, suggest: { showKeywords: true, showSnippets: true, showFunctions: true, showConstructors: true, showFields: true, showVariables: true, showClasses: true, showStructs: true, showInterfaces: true, showModules: true, showProperties: true, showEvents: true, showOperators: true, showUnits: true, showValues: true, showConstants: true, showEnums: true, showEnumMembers: true, showColors: true, showFiles: true, showReferences: true, showFolders: true, showTypeParameters: true, showUsers: true, showIssues: true, }, quickSuggestions: { other: true, comments: false, strings: true, }, acceptSuggestionOnCommitCharacter: true, acceptSuggestionOnEnter: 'on', tabCompletion: 'on', parameterHints: { enabled: true, cycle: true, }, autoClosingBrackets: 'always', autoClosingQuotes: 'always', autoSurround: 'languageDefined', formatOnPaste: true, formatOnType: true, autoIndent: 'full', matchBrackets: 'always', autoClosingDelete: 'auto', trimAutoWhitespace: true, renderControlCharacters: false, contextmenu: true, mouseWheelZoom: true, multiCursorModifier: 'ctrlCmd', selectionHighlight: true, occurrencesHighlight: 'multiFile', }} />
{/* ── Input / Output Panel ─────────────────────────────────── */}
{/* ── Input Section ──────────────────────────────────────── */}
{showInput && (