import React, { useState, useEffect, useRef } from 'react'; import { FileCode, Plus, X, FolderOpen, FileText, Save, Download, Copy, Trash2, Eye, EyeOff, Play, RotateCw } from 'lucide-react'; import * as monaco from 'monaco-editor'; // Import workers import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'; import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'; import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'; import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'; import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'; // Configure Monaco Environment self.MonacoEnvironment = { getWorker(_, label) { if (label === 'json') return new jsonWorker(); if (label === 'css' || label === 'scss' || label === 'less') return new cssWorker(); if (label === 'html' || label === 'handlebars' || label === 'razor') return new htmlWorker(); if (label === 'typescript' || label === 'javascript') return new tsWorker(); return new editorWorker(); } }; const MonacoEditor = () => { const [files, setFiles] = useState(() => { // Load saved files from localStorage on mount try { const saved = localStorage.getItem('monaco-editor-files'); if (saved) { const parsed = JSON.parse(saved); console.log('[Storage] Restored', parsed.length, 'files from localStorage'); return parsed; } } catch (err) { console.error('[Storage] Error loading files:', err); } return []; }); const [activeFileId, setActiveFileId] = useState(() => { // Load active file ID from localStorage try { const saved = localStorage.getItem('monaco-editor-active-file'); if (saved) { console.log('[Storage] Restored active file:', saved); return JSON.parse(saved); } } catch (err) { console.error('[Storage] Error loading active file:', err); } return null; }); const [showPreview, setShowPreview] = useState(false); const [previewContent, setPreviewContent] = useState(''); const [pythonOutput, setPythonOutput] = useState(''); const editorRef = useRef(null); const containerRef = useRef(null); const modelsRef = useRef({}); const iframeRef = useRef(null); const [error, setError] = useState(null); const [selectedPreviewFile, setSelectedPreviewFile] = useState(null); const [filePaths, setFilePaths] = useState({}); // Store file paths: { fileId: realPath } // Add these state variables with your other useState declarations const [showPrompt, setShowPrompt] = useState(false); const [promptValue, setPromptValue] = useState(''); const [promptCallback, setPromptCallback] = useState(null); const [workspaceFolder, setWorkspaceFolder] = useState(null); const [folderContents, setFolderContents] = useState([]); const [expandedFolders, setExpandedFolders] = useState(new Set()); const [showExplorer, setShowExplorer] = useState(true); const [contextMenu, setContextMenu] = useState(null); const [draggedItem, setDraggedItem] = useState(null); // Add this custom prompt function const customPrompt = (message, defaultValue = '') => { return new Promise((resolve) => { setPromptValue(defaultValue); setShowPrompt(true); setPromptCallback(() => resolve); }); }; // Save files to localStorage whenever they change useEffect(() => { if (files.length > 0) { try { localStorage.setItem('monaco-editor-files', JSON.stringify(files)); console.log('[Storage] Saved', files.length, 'files to localStorage'); } catch (err) { console.error('[Storage] Error saving files:', err); } } }, [files]); // Save active file ID whenever it changes useEffect(() => { if (activeFileId !== null) { try { localStorage.setItem('monaco-editor-active-file', JSON.stringify(activeFileId)); console.log('[Storage] Saved active file ID:', activeFileId); } catch (err) { console.error('[Storage] Error saving active file ID:', err); } } }, [activeFileId]); useEffect(() => { if (!containerRef.current || editorRef.current) return; try { const editor = monaco.editor.create(containerRef.current, { value: '// Start typing...', language: 'javascript', theme: 'vs-dark', automaticLayout: true, fontSize: 14, minimap: { enabled: true }, scrollbar: { vertical: 'visible', horizontal: 'visible' } }); editorRef.current = editor; editor.onDidChangeModelContent(() => { if (activeFileId && modelsRef.current[activeFileId]) { const content = editor.getValue(); setFiles(prev => prev.map(f => f.id === activeFileId ? { ...f, content, modified: true } : f )); } }); } catch (err) { setError(`Failed to create editor: ${err.message}`); } return () => { if (editorRef.current) { editorRef.current.dispose(); editorRef.current = null; } Object.values(modelsRef.current).forEach(model => { if (model && !model.isDisposed()) model.dispose(); }); }; }, [files.length]); useEffect(() => { if (!editorRef.current) { console.log('[Monaco] Editor not ready, skipping model update'); return; } const activeFile = files.find(f => f.id === activeFileId); console.log('[Monaco] Switching to file:', activeFile?.name, 'ID:', activeFileId); if (!activeFile) { console.log('[Monaco] No active file'); return; } try { // Create model if it doesn't exist if (!modelsRef.current[activeFile.id]) { console.log('[Monaco] Creating new model for:', activeFile.name); const uri = monaco.Uri.parse(`file:///${activeFile.name}`); // Check if a model with this URI already exists const existingModel = monaco.editor.getModel(uri); if (existingModel) { console.log('[Monaco] Model already exists, reusing it'); modelsRef.current[activeFile.id] = existingModel; } else { modelsRef.current[activeFile.id] = monaco.editor.createModel( activeFile.content || '', activeFile.language, uri ); console.log('[Monaco] Model created successfully'); } } const model = modelsRef.current[activeFile.id]; // Check if model is disposed if (model.isDisposed()) { console.error('[Monaco] Model is disposed, recreating...'); const uri = monaco.Uri.parse(`file:///${activeFile.name}`); modelsRef.current[activeFile.id] = monaco.editor.createModel( activeFile.content || '', activeFile.language, uri ); } console.log('[Monaco] Setting model to editor'); editorRef.current.setModel(modelsRef.current[activeFile.id]); editorRef.current.focus(); console.log('[Monaco] ✅ Model switched successfully'); } catch (err) { console.error('[Monaco] ❌ Error switching model:', err); setError(`Error switching file: ${err.message}`); } }, [activeFileId, files]); // Add keyboard shortcut handler useEffect(() => { const handleKeyDown = (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); saveFile(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [activeFileId, files, filePaths]); const getLanguageFromExtension = (filename) => { const ext = filename.split('.').pop().toLowerCase(); const languageMap = { 'js': 'javascript', 'jsx': 'javascript', 'ts': 'typescript', 'tsx': 'typescript', 'py': 'python', 'java': 'java', 'cpp': 'cpp', 'c': 'c', 'cs': 'csharp', 'php': 'php', 'rb': 'ruby', 'go': 'go', 'rs': 'rust', 'html': 'html', 'css': 'css', 'scss': 'scss', 'json': 'json', 'xml': 'xml', 'md': 'markdown', 'sql': 'sql', 'sh': 'shell', 'yaml': 'yaml', 'yml': 'yaml', 'txt': 'plaintext' }; return languageMap[ext] || 'plaintext'; }; const canPreview = (file) => { return ['html', 'javascript', 'python'].includes(file?.language); }; const buildPreview = () => { console.log('[Preview] Building preview...'); // Get LATEST content from Monaco models, not from state const currentFiles = files.map(f => { const model = modelsRef.current[f.id]; return { ...f, content: model && !model.isDisposed() ? model.getValue() : f.content }; }); console.log('[Preview] Files available:', currentFiles.map(f => `${f.name} (${f.language})`)); // Use selected file or fall back to first HTML file // Use selected file for preview const selectedFile = currentFiles.find(f => f.id === selectedPreviewFile); // If selected file is HTML, use it. If it's JS/JSX, show it as standalone. Otherwise find first HTML. let htmlFile; if (selectedFile?.language === 'html') { htmlFile = selectedFile; } else if (selectedFile?.language === 'javascript') { // For JS/JSX files, create a basic HTML wrapper just for this file htmlFile = null; // Will be handled below } else { htmlFile = currentFiles.find(f => f.language === 'html'); } const jsFiles = currentFiles.filter(f => f.language === 'javascript'); const cssFiles = currentFiles.filter(f => f.language === 'css'); // If viewing a specific JS file, only include that one if (selectedFile?.language === 'javascript') { const jsFilesToUse = [selectedFile]; let html = ''; const jsContent = jsFilesToUse.map(f => f.content).join('\n'); html = html.replace('', `\n`); const timestamp = Date.now(); html = html.replace('', ``); setPreviewContent(html); return; } console.log('[Preview] HTML file:', htmlFile?.name); console.log('[Preview] JS files:', jsFiles.map(f => f.name)); console.log('[Preview] CSS files:', cssFiles.map(f => f.name)); if (!htmlFile && jsFiles.length === 0) { const noContentHtml = '

No HTML or JS files to preview

'; console.log('[Preview] No files to preview'); setPreviewContent(noContentHtml); return; } let html = htmlFile?.content || ''; // Inject CSS if (cssFiles.length > 0) { const cssContent = cssFiles.map(f => f.content).join('\n'); if (html.includes('')) { html = html.replace('', `\n`); } else { html = html.replace('', ``); } } // Inject JS if (jsFiles.length > 0) { const jsContent = jsFiles.map(f => f.content).join('\n'); if (html.includes('')) { html = html.replace('', `\n`); } else { html += ``; } } const timestamp = Date.now(); html = html.replace('', ``); setPreviewContent(html); }; const runPython = () => { const selectedFile = files.find(f => f.id === selectedPreviewFile); const pythonFile = selectedFile?.language === 'python' ? selectedFile : files.find(f => f.language === 'python'); if (!pythonFile) { setPythonOutput('No Python file found'); return; } try { setPythonOutput('Note: This is a basic simulation. Use a real Python environment for actual execution.\n\n'); const code = pythonFile.content; const printMatches = code.matchAll(/print\s*\((.*?)\)/g); let output = ''; for (const match of printMatches) { const content = match[1].trim(); const value = content.replace(/^["']|["']$/g, ''); output += value + '\n'; } setPythonOutput(output || 'No print statements found'); } catch (err) { setPythonOutput(`Error: ${err.message}`); } }; const runPreview = () => { console.log('[Preview] Run preview clicked'); // Set active file as default if none selected if (!selectedPreviewFile) { setSelectedPreviewFile(activeFileId); } const activeFile = files.find(f => f.id === activeFileId); if (activeFile?.language === 'python') { runPython(); } else { buildPreview(); } setShowPreview(true); }; const refreshPreview = () => { console.log('[Preview] Refresh clicked'); const fileToPreview = files.find(f => f.id === selectedPreviewFile); if (fileToPreview?.language === 'python') { runPython(); } else { setPreviewContent(''); setTimeout(() => { buildPreview(); }, 50); } }; const promptForFilename = async () => { const filename = await customPrompt('Enter filename (e.g., script.js, index.html, styles.css):', 'untitled.txt'); if (filename) { const newFile = { id: Date.now(), name: filename, content: '', language: getLanguageFromExtension(filename), modified: false }; setFiles(prev => [...prev, newFile]); setActiveFileId(newFile.id); } }; // Replace saveFile function const saveFile = async () => { const activeFile = files.find(f => f.id === activeFileId); if (!activeFile) return; if (!window.electronAPI) { // Browser fallback const blob = new Blob([activeFile.content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = activeFile.name; a.click(); URL.revokeObjectURL(url); setFiles(prev => prev.map(f => f.id === activeFileId ? { ...f, modified: false } : f)); return; } // Electron save const filePath = filePaths[activeFileId]; if (filePath) { // Save to existing file const result = await window.electronAPI.writeFile(filePath, activeFile.content); if (result.success) { setFiles(prev => prev.map(f => f.id === activeFileId ? { ...f, modified: false } : f)); console.log('File saved:', filePath); } } else { // Save As - no existing path saveFileAs(); } }; // Add new saveFileAs function const saveFileAs = async () => { const activeFile = files.find(f => f.id === activeFileId); if (!activeFile) return; if (!window.electronAPI) { saveFile(); return; } const result = await window.electronAPI.saveFileDialog(activeFile.name); if (result.success) { const writeResult = await window.electronAPI.writeFile(result.filePath, activeFile.content); if (writeResult.success) { setFilePaths(prev => ({ ...prev, [activeFileId]: result.filePath })); setFiles(prev => prev.map(f => f.id === activeFileId ? { ...f, modified: false } : f)); console.log('File saved as:', result.filePath); } } }; const saveAllFiles = () => { files.forEach(file => { const blob = new Blob([file.content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = file.name; a.click(); URL.revokeObjectURL(url); }); setFiles(prev => prev.map(f => ({ ...f, modified: false }))); }; const copyContent = () => { const activeFile = files.find(f => f.id === activeFileId); if (activeFile) { navigator.clipboard.writeText(activeFile.content); alert('Content copied to clipboard!'); } }; const closeFile = (id, e) => { e.stopPropagation(); const fileToClose = files.find(f => f.id === id); if (fileToClose?.modified) { const confirm = window.confirm(`${fileToClose.name} has unsaved changes. Close anyway?`); if (!confirm) return; } console.log('[App] Closing file:', id); // Dispose the model safely if (modelsRef.current[id]) { try { if (!modelsRef.current[id].isDisposed()) { modelsRef.current[id].dispose(); console.log('[App] Model disposed for:', id); } delete modelsRef.current[id]; } catch (err) { console.error('[App] Error disposing model:', err); } } setFiles(prev => { const newFiles = prev.filter(f => f.id !== id); if (activeFileId === id && newFiles.length > 0) { // Switch to the first available file const newActiveId = newFiles[0].id; console.log('[App] Switching to file:', newActiveId); setActiveFileId(newActiveId); } else if (newFiles.length === 0) { setActiveFileId(null); } return newFiles; }); }; const closeAllFiles = () => { const hasModified = files.some(f => f.modified); if (hasModified) { const confirm = window.confirm('Some files have unsaved changes. Close all anyway?'); if (!confirm) return; } console.log('[App] Closing all files'); // Dispose all models safely Object.keys(modelsRef.current).forEach(id => { try { const model = modelsRef.current[id]; if (model && !model.isDisposed()) { model.dispose(); } } catch (err) { console.error('[App] Error disposing model:', err); } }); modelsRef.current = {}; setFiles([]); setActiveFileId(null); setShowPreview(false); // Clear localStorage try { localStorage.removeItem('monaco-editor-files'); localStorage.removeItem('monaco-editor-active-file'); console.log('[Storage] Cleared localStorage'); } catch (err) { console.error('[Storage] Error clearing localStorage:', err); } }; // Replace openFileDialog function const openFileDialog = async () => { if (!window.electronAPI) { // Fallback to browser file picker const input = document.createElement('input'); input.type = 'file'; input.multiple = true; input.onchange = (e) => { Array.from(e.target.files).forEach(file => { const reader = new FileReader(); reader.onload = (event) => { const newFile = { id: Date.now() + Math.random(), name: file.name, content: event.target.result, language: getLanguageFromExtension(file.name), modified: false }; setFiles(prev => [...prev, newFile]); setActiveFileId(newFile.id); }; reader.readAsText(file); }); }; input.click(); return; } // Electron file picker const result = await window.electronAPI.openFileDialog(); if (result.success) { for (const filePath of result.filePaths) { const readResult = await window.electronAPI.readFile(filePath); if (readResult.success) { const fileName = filePath.split(/[\\/]/).pop(); const newFile = { id: Date.now() + Math.random(), name: fileName, content: readResult.content, language: getLanguageFromExtension(fileName), modified: false }; setFiles(prev => [...prev, newFile]); setFilePaths(prev => ({ ...prev, [newFile.id]: filePath })); setActiveFileId(newFile.id); } } } }; const openFolder = async () => { const result = await window.electronAPI.openFolderDialog(); if (result.success) { setWorkspaceFolder(result.folderPath); loadFolder(result.folderPath); } }; const loadFolder = async (folderPath) => { const result = await window.electronAPI.readDirectory(folderPath); if (result.success) { // Completely replace folder contents setFolderContents(result.files); } }; const refreshFolder = async () => { if (workspaceFolder) { // Clear expanded folders except root setExpandedFolders(new Set()); // Do a fresh load await loadFolder(workspaceFolder); } }; const toggleFolder = async (folderPath) => { const newExpanded = new Set(expandedFolders); if (newExpanded.has(folderPath)) { newExpanded.delete(folderPath); setExpandedFolders(newExpanded); } else { newExpanded.add(folderPath); setExpandedFolders(newExpanded); const result = await window.electronAPI.readDirectory(folderPath); if (result.success) { setFolderContents(prev => { const existing = new Map(prev.map(item => [item.path, item])); result.files.forEach(file => { if (!existing.has(file.path)) { existing.set(file.path, file); } }); return Array.from(existing.values()); }); } } }; const openFileFromExplorer = async (filePath) => { // Check if this file is already open const existingFile = Object.entries(filePaths).find(([id, path]) => path === filePath); if (existingFile) { // File is already open, just switch to it setActiveFileId(Number(existingFile[0])); return; } const result = await window.electronAPI.readFile(filePath); if (result.success) { const fileName = filePath.split(/[\\/]/).pop(); const newFile = { id: Date.now() + Math.random(), name: fileName, content: result.content, language: getLanguageFromExtension(fileName), modified: false }; setFiles(prev => [...prev, newFile]); setFilePaths(prev => ({ ...prev, [newFile.id]: filePath })); setActiveFileId(newFile.id); } }; const createNewFileInFolder = async (folderPath) => { const filename = await customPrompt('Enter filename:', 'newfile.txt'); if (filename) { const separator = folderPath.includes('\\') ? '\\' : '/'; const filePath = `${folderPath}${separator}${filename}`; const result = await window.electronAPI.createFile(filePath); if (result.success) { await loadFolder(workspaceFolder); setExpandedFolders(prev => new Set([...prev, folderPath])); openFileFromExplorer(filePath); } else { alert(`Failed to create file: ${result.error || 'Unknown error'}`); } } }; const createNewFolderInFolder = async (folderPath) => { const foldername = await customPrompt('Enter folder name:', 'newfolder'); if (foldername) { const separator = folderPath.includes('\\') ? '\\' : '/'; const newFolderPath = `${folderPath}${separator}${foldername}`; const result = await window.electronAPI.createFolder(newFolderPath); if (result.success) { await loadFolder(workspaceFolder); setExpandedFolders(prev => new Set([...prev, newFolderPath])); } } }; const handleContextMenu = (e, item) => { e.preventDefault(); setContextMenu({ x: e.clientX, y: e.clientY, item: item }); }; const openCommandPalette = () => { if (editorRef.current) { editorRef.current.focus(); editorRef.current.trigger('keyboard', 'editor.action.quickCommand', null); } }; const PromptModal = () => { if (!showPrompt) return null; const handleSubmit = (e) => { e.preventDefault(); if (promptCallback) { const callback = promptCallback; setShowPrompt(false); setPromptCallback(null); callback(promptValue || null); } }; const handleCancel = () => { if (promptCallback) { const callback = promptCallback; setShowPrompt(false); setPromptCallback(null); callback(null); } }; return (

Enter filename

setPromptValue(e.target.value)} placeholder="e.g., script.js, index.html, styles.css" autoFocus style={{ width: '100%', padding: '8px 12px', backgroundColor: '#3c3c3c', color: '#cccccc', border: '1px solid #454545', borderRadius: '4px', fontSize: '14px', outline: 'none', boxSizing: 'border-box' }} />
); }; const FileExplorer = () => { const renderTree = (items, parentPath = workspaceFolder) => { return items .filter(item => { if (!item.path) return false; const itemParent = item.path.substring(0, item.path.lastIndexOf(item.path.includes('\\') ? '\\' : '/')); return itemParent === parentPath; }) .sort((a, b) => { if (a.isDirectory === b.isDirectory) return a.name.localeCompare(b.name); return a.isDirectory ? -1 : 1; }) .map(item => (
{ if (!item.isDirectory) { setDraggedItem(item); e.dataTransfer.effectAllowed = 'move'; } }} onDragOver={(e) => { if (item.isDirectory && draggedItem && draggedItem.path !== item.path) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; } }} onDrop={async (e) => { if (item.isDirectory && draggedItem) { e.preventDefault(); e.stopPropagation(); const separator = draggedItem.path.includes('\\') ? '\\' : '/'; const newPath = `${item.path}${separator}${draggedItem.name}`; // Check if file is open in editor const openFile = files.find(f => filePaths[f.id] === draggedItem.path); const result = await window.electronAPI.renameFile(draggedItem.path, newPath); if (result.success) { // Update file paths if the file is open if (openFile) { setFilePaths(prev => ({ ...prev, [openFile.id]: newPath })); } // Refresh the entire folder structure setDraggedItem(null); await loadFolder(workspaceFolder); // Full reload instead of refresh setExpandedFolders(prev => new Set([...prev, item.path])); } else { alert(`Failed to move file: ${result.error || 'Unknown error'}`); } setDraggedItem(null); } }} onDragEnd={() => setDraggedItem(null)} onClick={() => item.isDirectory ? toggleFolder(item.path) : openFileFromExplorer(item.path)} onContextMenu={(e) => handleContextMenu(e, item)} style={{ padding: '4px 8px', cursor: item.isDirectory ? 'pointer' : 'grab', display: 'flex', alignItems: 'center', gap: '6px', color: '#cccccc', fontSize: '13px', backgroundColor: 'transparent', transition: 'background-color 0.1s', paddingLeft: `${(item.path.split(/[\\/]/).length - workspaceFolder.split(/[\\/]/).length) * 12 + 8}px`, opacity: draggedItem?.path === item.path ? 0.5 : 1 }} onMouseOver={(e) => e.currentTarget.style.backgroundColor = '#2a2d2e'} onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'transparent'} > {item.isDirectory ? ( expandedFolders.has(item.path) ? '▼' : '▶' ) : ( )} {item.name}
{item.isDirectory && expandedFolders.has(item.path) && renderTree(folderContents, item.path)}
)); }; return (
Explorer
{workspaceFolder && ( <> )}
{!workspaceFolder ? (
) : (
handleContextMenu(e, { path: workspaceFolder, isDirectory: true, name: workspaceFolder.split(/[\\/]/).pop() })} >
{workspaceFolder.split(/[\\/]/).pop()}
{renderTree(folderContents)}
)}
); }; const ContextMenu = () => { if (!contextMenu) return null; const handleNewFile = async () => { setContextMenu(null); await createNewFileInFolder(contextMenu.item.path); }; const handleNewFolder = async () => { setContextMenu(null); await createNewFolderInFolder(contextMenu.item.path); }; const handleDelete = async () => { // Only allow deleting files, NOT folders if (contextMenu.item.isDirectory) { setContextMenu(null); alert('Deleting folders is disabled for safety. Please delete files individually or use your file manager.'); return; } if (window.confirm(`Delete file "${contextMenu.item.name}"?`)) { setContextMenu(null); const result = await window.electronAPI.deleteFile(contextMenu.item.path); if (result.success) { // Close the file if it's open const openFile = files.find(f => filePaths[f.id] === contextMenu.item.path); if (openFile) { // Close the file tab if (modelsRef.current[openFile.id]) { try { if (!modelsRef.current[openFile.id].isDisposed()) { modelsRef.current[openFile.id].dispose(); } delete modelsRef.current[openFile.id]; } catch (err) { console.error('[App] Error disposing model:', err); } } setFiles(prev => { const newFiles = prev.filter(f => f.id !== openFile.id); if (activeFileId === openFile.id && newFiles.length > 0) { setActiveFileId(newFiles[0].id); } else if (newFiles.length === 0) { setActiveFileId(null); } return newFiles; }); setFilePaths(prev => { const newPaths = { ...prev }; delete newPaths[openFile.id]; return newPaths; }); } await loadFolder(workspaceFolder); } else { alert(`Failed to delete: ${result.error || 'Unknown error'}`); } } else { setContextMenu(null); } }; const handleRename = async () => { // Only allow renaming files, NOT folders if (contextMenu.item.isDirectory) { setContextMenu(null); alert('Renaming folders is disabled for safety. Please use your file manager.'); return; } setContextMenu(null); const newName = await customPrompt('Enter new name:', contextMenu.item.name); if (newName) { const separator = contextMenu.item.path.includes('\\') ? '\\' : '/'; const parentPath = contextMenu.item.path.substring(0, contextMenu.item.path.lastIndexOf(separator)); const newPath = `${parentPath}${separator}${newName}`; const result = await window.electronAPI.renameFile(contextMenu.item.path, newPath); if (result.success) { // Update file paths if the file is open const openFile = files.find(f => filePaths[f.id] === contextMenu.item.path); if (openFile) { setFilePaths(prev => ({ ...prev, [openFile.id]: newPath })); setFiles(prev => prev.map(f => f.id === openFile.id ? { ...f, name: newName } : f )); } await loadFolder(workspaceFolder); } else { alert(`Failed to rename: ${result.error || 'Unknown error'}`); } } }; return ( <>
setContextMenu(null)} />
{contextMenu.item.isDirectory && ( <>
e.currentTarget.style.backgroundColor = '#094771'} onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'transparent'} > New File
e.currentTarget.style.backgroundColor = '#094771'} onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'transparent'} > New Folder
)} {!contextMenu.item.isDirectory && ( <>
e.currentTarget.style.backgroundColor = '#094771'} onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'transparent'} > Rename
e.currentTarget.style.backgroundColor = '#094771'} onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'transparent'} > Delete
)}
); }; const WelcomeScreen = () => (

Welcome

{error && (
Error: {error}
)}

Supported: HTML, CSS, JS, Python, TypeScript, and more

Press F1 for Command Palette

); const activeFile = files.find(f => f.id === activeFileId); const editorReady = editorRef.current !== null; return (
NeoEditor {editorReady ? '● Ready' : '○ No files open'}
{!showExplorer && ( )} {activeFile && ( <> )} {files.length > 0 && ( <> {canPreview(activeFile) && ( <> {/* comment here */} {showPreview && ( <> )} )} )}
{files.map(file => (
setActiveFileId(file.id)} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '8px 16px', cursor: 'pointer', borderRight: '1px solid #252526', backgroundColor: activeFileId === file.id ? '#1e1e1e' : '#2d2d30', color: activeFileId === file.id ? '#ffffff' : '#969696', transition: 'background-color 0.2s' }} onMouseOver={(e) => { if (activeFileId !== file.id) e.currentTarget.style.backgroundColor = '#343437'; }} onMouseOut={(e) => { if (activeFileId !== file.id) e.currentTarget.style.backgroundColor = '#2d2d30'; }}> {file.name}{file.modified && ' •'}
))}
{showExplorer && }
{files.length === 0 ? ( ) : (
)}
{showPreview && (
Preview
{files.find(f => f.id === selectedPreviewFile)?.language === 'python' ? (
                  {pythonOutput}
                
) : ( previewContent ? (