import { useCallback, useRef } from 'react'; import Editor, { type OnMount } from '@monaco-editor/react'; import { Plus, RotateCcw, X, Download, Lock } from 'lucide-react'; import { LANGUAGE_OPTIONS, DEFAULT_CODE_BY_LANGUAGE } from './useCompiler.js'; import { useSubscription } from '@/contexts/SubscriptionContext'; function getLang(value: string) { return LANGUAGE_OPTIONS.find((l: any) => l.value === value) ?? LANGUAGE_OPTIONS[0]; } export interface EditorFile { id: string; name: string; language: string; code: string; } interface CodeEditorPanelProps { files: EditorFile[]; activeFileId: string; onSelectFile: (id: string) => void; onCloseFile: (id: string) => void; onAddFile: () => void; onCodeChange: (code: string) => void; onLanguageChange: (lang: string) => void; onResetCode: () => void; onCursorChange: (line: number, col: number) => void; onCtrlEnter: () => void; } export default function CodeEditorPanel({ files, activeFileId, onSelectFile, onCloseFile, onAddFile, onCodeChange, onLanguageChange, onResetCode, onCursorChange, onCtrlEnter, }: CodeEditorPanelProps) { const { tier } = useSubscription(); // Free tier: lock C++ (value: 'cpp') const LOCKED_LANGUAGES = tier === 'free' ? new Set(['cpp']) : new Set(); const activeFile = files.find((f) => f.id === activeFileId) ?? files[0]; const activeLang = getLang(activeFile.language); const editorRef = useRef(null); const handleDownload = useCallback(() => { const blob = new Blob([activeFile.code], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = activeFile.name; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }, [activeFile]); const handleMount: OnMount = useCallback( (editor, monaco) => { editorRef.current = editor; // Define RYP dark theme monaco.editor.defineTheme('ryp-dark', { base: 'vs-dark', inherit: true, rules: [ { token: '', foreground: 'e2e2f0', background: '000000' }, { token: 'keyword', foreground: 'c084fc' }, { token: 'string', foreground: '86efac' }, { token: 'number', foreground: 'fbbf24' }, { token: 'comment', foreground: '55556a', fontStyle: 'italic' }, { token: 'type', foreground: '67e8f9' }, { token: 'function', foreground: '60a5fa' }, { token: 'variable', foreground: 'e2e2f0' }, { token: 'operator', foreground: 'a78bfa' }, ], colors: { 'editor.background': '#000000', 'editor.foreground': '#e2e2f0', 'editorLineNumber.foreground': '#333344', 'editorLineNumber.activeForeground': '#8b5cf6', 'editorCursor.foreground': '#8b5cf6', 'editor.selectionBackground': '#8b5cf633', 'editor.inactiveSelectionBackground': '#8b5cf611', 'editor.lineHighlightBackground': '#111111', 'editorGutter.background': '#000000', 'editorWidget.background': '#111111', 'editorWidget.border': '#222233', 'editorBracketMatch.background': '#8b5cf633', 'editorBracketMatch.border': '#8b5cf6', }, }); monaco.editor.setTheme('ryp-dark'); // Track cursor position editor.onDidChangeCursorPosition((e: any) => { onCursorChange(e.position.lineNumber, e.position.column); }); // Ctrl+Enter = run editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => { onCtrlEnter(); }); // Tab inserts 4 spaces editor.getModel()?.updateOptions({ tabSize: 4, insertSpaces: true }); }, [onCursorChange, onCtrlEnter], ); return (
{/* Tabs bar */}
{files.map((file) => ( ))}
{/* Monaco editor */}
onCodeChange(v ?? '')} options={{ minimap: { enabled: false }, fontSize: 14, fontLigatures: true, fontFamily: "'JetBrains Mono', Consolas, monospace", smoothScrolling: true, cursorBlinking: 'smooth', cursorSmoothCaretAnimation: 'on', scrollBeyondLastLine: false, automaticLayout: true, padding: { top: 16, bottom: 16 }, tabSize: 4, insertSpaces: true, renderWhitespace: 'none', bracketPairColorization: { enabled: true }, lineNumbers: 'on', glyphMargin: false, folding: true, lineDecorationsWidth: 12, overviewRulerBorder: false, scrollbar: { verticalScrollbarSize: 6, horizontalScrollbarSize: 6, }, }} />
); }