Spaces:
Sleeping
Sleeping
Download frontend/src/components/CodeEditor.jsx from chowdary1/ai-interviewer-version-2: direct link, hf CLI and curl.
- Browser
- Download file 15.5 kB
-
https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/components/CodeEditor.jsx
- Command line
-
hf download hf://spaces/chowdary1/ai-interviewer-version-2/frontend/src/components/CodeEditor.jsx
-
curl -L -o CodeEditor.jsx https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/components/CodeEditor.jsx
15.5 kB
| /** | |
| * CodeEditor Component - Lightweight Code Editor for Coding Rounds | |
| * | |
| * Features: | |
| * - Multi-language support (Python, JavaScript, Java, C++, etc.) | |
| * - Syntax highlighting via simple CSS classes | |
| * - Code execution with output display | |
| * - Keyboard shortcuts (Ctrl+Enter to run) | |
| * - Line numbers | |
| * - Auto-indentation | |
| */ | |
| import React, { useState, useRef, useEffect, useCallback } from 'react'; | |
| import { | |
| Play, Square, RotateCcw, Copy, Check, ChevronDown, | |
| Terminal, Code, Loader2, AlertCircle, CheckCircle2, | |
| Maximize2, Minimize2 | |
| } from 'lucide-react'; | |
| import { getToken } from '../services/api'; | |
| import './CodeEditor.css'; | |
| // Supported Languages | |
| const LANGUAGES = [ | |
| { id: 'python', name: 'Python', icon: '🐍', extension: '.py' }, | |
| { id: 'javascript', name: 'JavaScript', icon: '🟨', extension: '.js' }, | |
| { id: 'typescript', name: 'TypeScript', icon: '🔷', extension: '.ts' }, | |
| { id: 'java', name: 'Java', icon: '☕', extension: '.java' }, | |
| { id: 'cpp', name: 'C++', icon: '⚡', extension: '.cpp' }, | |
| { id: 'c', name: 'C', icon: '🔵', extension: '.c' }, | |
| { id: 'go', name: 'Go', icon: '🔹', extension: '.go' }, | |
| { id: 'ruby', name: 'Ruby', icon: '💎', extension: '.rb' }, | |
| { id: 'php', name: 'PHP', icon: '🐘', extension: '.php' }, | |
| ]; | |
| // Default code templates | |
| const DEFAULT_TEMPLATES = { | |
| python: `# Python Solution | |
| def solution(): | |
| # Write your code here | |
| print("Hello, World!") | |
| solution()`, | |
| javascript: `// JavaScript Solution | |
| function solution() { | |
| // Write your code here | |
| console.log("Hello, World!"); | |
| } | |
| solution();`, | |
| typescript: `// TypeScript Solution | |
| function solution(): void { | |
| // Write your code here | |
| console.log("Hello, World!"); | |
| } | |
| solution();`, | |
| java: `// Java Solution | |
| public class Solution { | |
| public static void main(String[] args) { | |
| // Write your code here | |
| System.out.println("Hello, World!"); | |
| } | |
| }`, | |
| cpp: `// C++ Solution | |
| #include <iostream> | |
| using namespace std; | |
| int main() { | |
| // Write your code here | |
| cout << "Hello, World!" << endl; | |
| return 0; | |
| }`, | |
| c: `// C Solution | |
| #include <stdio.h> | |
| int main() { | |
| // Write your code here | |
| printf("Hello, World!\\n"); | |
| return 0; | |
| }`, | |
| go: `// Go Solution | |
| package main | |
| import "fmt" | |
| func main() { | |
| // Write your code here | |
| fmt.Println("Hello, World!") | |
| }`, | |
| ruby: `# Ruby Solution | |
| def solution | |
| # Write your code here | |
| puts "Hello, World!" | |
| end | |
| solution`, | |
| php: `<?php | |
| // PHP Solution | |
| function solution() { | |
| // Write your code here | |
| echo "Hello, World!\\n"; | |
| } | |
| solution(); | |
| ?>`, | |
| }; | |
| const CodeEditor = ({ | |
| initialCode = '', | |
| initialLanguage = 'python', | |
| onCodeChange, | |
| onRun, | |
| readOnly = false, | |
| className = '', | |
| showOutput = true, | |
| apiEndpoint = '/api/compiler/execute', | |
| }) => { | |
| const [code, setCode] = useState(initialCode || DEFAULT_TEMPLATES[initialLanguage] || ''); | |
| const [language, setLanguage] = useState(initialLanguage); | |
| const [output, setOutput] = useState(''); | |
| const [isRunning, setIsRunning] = useState(false); | |
| const [error, setError] = useState(null); | |
| const [executionTime, setExecutionTime] = useState(null); | |
| const [copied, setCopied] = useState(false); | |
| const [isExpanded, setIsExpanded] = useState(false); | |
| const [showLanguageDropdown, setShowLanguageDropdown] = useState(false); | |
| const [stdin, setStdin] = useState(''); | |
| const [showStdin, setShowStdin] = useState(false); | |
| const textareaRef = useRef(null); | |
| const lineNumbersRef = useRef(null); | |
| const dropdownRef = useRef(null); | |
| // Calculate line numbers | |
| const lineCount = code.split('\n').length; | |
| const lineNumbers = Array.from({ length: lineCount }, (_, i) => i + 1); | |
| // Sync scroll between textarea and line numbers | |
| const handleScroll = useCallback((e) => { | |
| if (lineNumbersRef.current) { | |
| lineNumbersRef.current.scrollTop = e.target.scrollTop; | |
| } | |
| }, []); | |
| // Handle code change | |
| const handleCodeChange = (e) => { | |
| const newCode = e.target.value; | |
| setCode(newCode); | |
| onCodeChange?.(newCode, language); | |
| }; | |
| // Handle language change | |
| const handleLanguageChange = (langId) => { | |
| setLanguage(langId); | |
| // If code is empty or default template, switch to new template | |
| if (!code || Object.values(DEFAULT_TEMPLATES).includes(code)) { | |
| setCode(DEFAULT_TEMPLATES[langId] || ''); | |
| } | |
| setShowLanguageDropdown(false); | |
| onCodeChange?.(code, langId); | |
| }; | |
| // Handle Tab key for indentation | |
| const handleKeyDown = (e) => { | |
| if (e.key === 'Tab') { | |
| e.preventDefault(); | |
| const start = e.target.selectionStart; | |
| const end = e.target.selectionEnd; | |
| const newCode = code.substring(0, start) + ' ' + code.substring(end); | |
| setCode(newCode); | |
| // Move cursor after the inserted spaces | |
| setTimeout(() => { | |
| e.target.selectionStart = e.target.selectionEnd = start + 4; | |
| }, 0); | |
| } | |
| // Ctrl/Cmd + Enter to run | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { | |
| e.preventDefault(); | |
| handleRun(); | |
| } | |
| }; | |
| // Run code | |
| const handleRun = async () => { | |
| if (isRunning || !code.trim()) return; | |
| setIsRunning(true); | |
| setError(null); | |
| setOutput(''); | |
| setExecutionTime(null); | |
| const startTime = Date.now(); | |
| try { | |
| const token = getToken(); | |
| const response = await fetch(apiEndpoint, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| ...(token && { Authorization: `Bearer ${token}` }), | |
| }, | |
| body: JSON.stringify({ | |
| code, | |
| language, | |
| stdin: stdin || undefined, | |
| timeout: 10 | |
| }), | |
| }); | |
| const data = await response.json(); | |
| const elapsed = Date.now() - startTime; | |
| setExecutionTime(elapsed); | |
| // Handle HTTP error responses (e.g., 503 when runtime not installed) | |
| if (!response.ok) { | |
| const errorMsg = data.detail || data.error || `Server error (${response.status})`; | |
| setError(errorMsg); | |
| onRun?.({ success: false, error: errorMsg, language, executionTime: elapsed }); | |
| } else if (data.success) { | |
| setOutput(data.output || 'Program executed successfully (no output)'); | |
| onRun?.({ success: true, output: data.output, language, executionTime: elapsed }); | |
| } else { | |
| setError(data.error || data.detail || 'Execution failed'); | |
| setOutput(data.output || ''); | |
| onRun?.({ success: false, error: data.error, language, executionTime: elapsed }); | |
| } | |
| } catch (err) { | |
| const elapsed = Date.now() - startTime; | |
| setExecutionTime(elapsed); | |
| setError(`Network error: ${err.message}`); | |
| onRun?.({ success: false, error: err.message, language, executionTime: elapsed }); | |
| } finally { | |
| setIsRunning(false); | |
| } | |
| }; | |
| // Reset code to template | |
| const handleReset = () => { | |
| setCode(DEFAULT_TEMPLATES[language] || ''); | |
| setOutput(''); | |
| setError(null); | |
| setExecutionTime(null); | |
| }; | |
| // Copy code | |
| const handleCopy = async () => { | |
| try { | |
| await navigator.clipboard.writeText(code); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| } catch (err) { | |
| console.error('Failed to copy:', err); | |
| } | |
| }; | |
| // Close dropdown on outside click | |
| useEffect(() => { | |
| const handleClickOutside = (e) => { | |
| if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { | |
| setShowLanguageDropdown(false); | |
| } | |
| }; | |
| document.addEventListener('mousedown', handleClickOutside); | |
| return () => document.removeEventListener('mousedown', handleClickOutside); | |
| }, []); | |
| const currentLang = LANGUAGES.find(l => l.id === language) || LANGUAGES[0]; | |
| return ( | |
| <div className={`code-editor ${isExpanded ? 'expanded' : ''} ${className}`}> | |
| {/* Toolbar */} | |
| <div className="code-editor-toolbar"> | |
| <div className="toolbar-left"> | |
| {/* Language Selector */} | |
| <div className="language-selector" ref={dropdownRef}> | |
| <button | |
| className="language-btn" | |
| onClick={() => setShowLanguageDropdown(!showLanguageDropdown)} | |
| disabled={readOnly} | |
| > | |
| <span className="lang-icon">{currentLang.icon}</span> | |
| <span className="lang-name">{currentLang.name}</span> | |
| <ChevronDown size={14} className={showLanguageDropdown ? 'rotated' : ''} /> | |
| </button> | |
| {showLanguageDropdown && ( | |
| <div className="language-dropdown"> | |
| {LANGUAGES.map((lang) => ( | |
| <button | |
| key={lang.id} | |
| className={`lang-option ${lang.id === language ? 'active' : ''}`} | |
| onClick={() => handleLanguageChange(lang.id)} | |
| > | |
| <span className="lang-icon">{lang.icon}</span> | |
| <span className="lang-name">{lang.name}</span> | |
| {lang.id === language && <CheckCircle2 size={14} />} | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| <div className="toolbar-right"> | |
| {/* Input Toggle */} | |
| <button | |
| className={`toolbar-btn ${showStdin ? 'active' : ''}`} | |
| onClick={() => setShowStdin(!showStdin)} | |
| title="Standard Input" | |
| > | |
| <Terminal size={14} /> | |
| </button> | |
| {/* Copy */} | |
| <button | |
| className="toolbar-btn" | |
| onClick={handleCopy} | |
| title="Copy Code" | |
| > | |
| {copied ? <Check size={14} /> : <Copy size={14} />} | |
| </button> | |
| {/* Reset */} | |
| <button | |
| className="toolbar-btn" | |
| onClick={handleReset} | |
| title="Reset Code" | |
| disabled={readOnly} | |
| > | |
| <RotateCcw size={14} /> | |
| </button> | |
| {/* Expand */} | |
| <button | |
| className="toolbar-btn" | |
| onClick={() => setIsExpanded(!isExpanded)} | |
| title={isExpanded ? "Minimize" : "Expand"} | |
| > | |
| {isExpanded ? <Minimize2 size={14} /> : <Maximize2 size={14} />} | |
| </button> | |
| {/* Run Button */} | |
| <button | |
| className={`run-btn ${isRunning ? 'running' : ''}`} | |
| onClick={handleRun} | |
| disabled={isRunning || !code.trim() || readOnly} | |
| > | |
| {isRunning ? ( | |
| <> | |
| <Loader2 size={14} className="spin" /> | |
| <span>Running...</span> | |
| </> | |
| ) : ( | |
| <> | |
| <Play size={14} /> | |
| <span>Run</span> | |
| </> | |
| )} | |
| </button> | |
| </div> | |
| </div> | |
| {/* Standard Input (Optional) */} | |
| {showStdin && ( | |
| <div className="stdin-container"> | |
| <label> | |
| <Terminal size={12} /> | |
| <span>Standard Input</span> | |
| </label> | |
| <textarea | |
| value={stdin} | |
| onChange={(e) => setStdin(e.target.value)} | |
| placeholder="Enter input values here (one per line)..." | |
| rows={3} | |
| className="stdin-textarea" | |
| /> | |
| </div> | |
| )} | |
| {/* Editor Area */} | |
| <div className="code-editor-body"> | |
| {/* Line Numbers */} | |
| <div className="line-numbers" ref={lineNumbersRef}> | |
| {lineNumbers.map((num) => ( | |
| <div key={num} className="line-number">{num}</div> | |
| ))} | |
| </div> | |
| {/* Code Area */} | |
| <textarea | |
| ref={textareaRef} | |
| value={code} | |
| onChange={handleCodeChange} | |
| onKeyDown={handleKeyDown} | |
| onScroll={handleScroll} | |
| className={`code-textarea language-${language}`} | |
| placeholder="Write your code here..." | |
| spellCheck={false} | |
| autoCapitalize="off" | |
| autoCorrect="off" | |
| readOnly={readOnly} | |
| /> | |
| </div> | |
| {/* Output Panel */} | |
| {showOutput && (output || error) && ( | |
| <div className={`output-panel ${error ? 'has-error' : 'success'}`}> | |
| <div className="output-header"> | |
| <div className="output-title"> | |
| {error ? ( | |
| <> | |
| <AlertCircle size={14} /> | |
| <span>Error</span> | |
| </> | |
| ) : ( | |
| <> | |
| <CheckCircle2 size={14} /> | |
| <span>Output</span> | |
| </> | |
| )} | |
| </div> | |
| {executionTime && ( | |
| <span className="execution-time">{executionTime}ms</span> | |
| )} | |
| </div> | |
| <pre className="output-content"> | |
| {error && <div className="error-text">{error}</div>} | |
| {output} | |
| </pre> | |
| </div> | |
| )} | |
| {/* Keyboard Shortcut Hint */} | |
| <div className="keyboard-hint"> | |
| Press <kbd>Ctrl</kbd> + <kbd>Enter</kbd> to run | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default CodeEditor; | |