From 37907ef0981407e44df874a27f6f921733e2b0fc Mon Sep 17 00:00:00 2001 From: Rob Colbert Date: Tue, 12 May 2026 19:57:27 -0400 Subject: [PATCH] fix --- .../frontend/src/components/EditorPanel.tsx | 266 ++++++++++-------- 1 file changed, 151 insertions(+), 115 deletions(-) diff --git a/gadget-code/frontend/src/components/EditorPanel.tsx b/gadget-code/frontend/src/components/EditorPanel.tsx index cde0fd6..8ec37c2 100644 --- a/gadget-code/frontend/src/components/EditorPanel.tsx +++ b/gadget-code/frontend/src/components/EditorPanel.tsx @@ -1,6 +1,6 @@ -import { useState, useCallback, useEffect } from 'react'; -import Ace from 'react-ace'; -import ace from 'ace-builds'; +import { useState, useCallback, useEffect } from "react"; +import Ace from "react-ace"; +import ace from "ace-builds"; // ── Vite ?url imports for ACE modes ────────────────────────────────────── // These resolve at build time to asset URLs. We register them with ACE's @@ -10,44 +10,44 @@ import ace from 'ace-builds'; // See: https://github.com/ajaxorg/ace/issues/4597 // ───────────────────────────────────────────────────────────────────────── -import modeJavascriptUrl from 'ace-builds/src-noconflict/mode-javascript?url'; -import modeTypescriptUrl from 'ace-builds/src-noconflict/mode-typescript?url'; -import modePythonUrl from 'ace-builds/src-noconflict/mode-python?url'; -import modeJsonUrl from 'ace-builds/src-noconflict/mode-json?url'; -import modeHtmlUrl from 'ace-builds/src-noconflict/mode-html?url'; -import modeCssUrl from 'ace-builds/src-noconflict/mode-css?url'; -import modeYamlUrl from 'ace-builds/src-noconflict/mode-yaml?url'; -import modeMarkdownUrl from 'ace-builds/src-noconflict/mode-markdown?url'; -import modeShUrl from 'ace-builds/src-noconflict/mode-sh?url'; -import modeSqlUrl from 'ace-builds/src-noconflict/mode-sql?url'; -import modeJavaUrl from 'ace-builds/src-noconflict/mode-java?url'; -import modeGolangUrl from 'ace-builds/src-noconflict/mode-golang?url'; -import modeRustUrl from 'ace-builds/src-noconflict/mode-rust?url'; -import modeCsharpUrl from 'ace-builds/src-noconflict/mode-csharp?url'; -import modePhpUrl from 'ace-builds/src-noconflict/mode-php?url'; -import modeRubyUrl from 'ace-builds/src-noconflict/mode-ruby?url'; -import modeCcppUrl from 'ace-builds/src-noconflict/mode-c_cpp?url'; -import modeScssUrl from 'ace-builds/src-noconflict/mode-scss?url'; -import modeLessUrl from 'ace-builds/src-noconflict/mode-less?url'; -import modeXmlUrl from 'ace-builds/src-noconflict/mode-xml?url'; -import modeDockerfileUrl from 'ace-builds/src-noconflict/mode-dockerfile?url'; -import modeMakefileUrl from 'ace-builds/src-noconflict/mode-makefile?url'; -import modeSassUrl from 'ace-builds/src-noconflict/mode-sass?url'; -import modeTextUrl from 'ace-builds/src-noconflict/mode-text?url'; +import modeJavascriptUrl from "ace-builds/src-noconflict/mode-javascript?url"; +import modeTypescriptUrl from "ace-builds/src-noconflict/mode-typescript?url"; +import modePythonUrl from "ace-builds/src-noconflict/mode-python?url"; +import modeJsonUrl from "ace-builds/src-noconflict/mode-json?url"; +import modeHtmlUrl from "ace-builds/src-noconflict/mode-html?url"; +import modeCssUrl from "ace-builds/src-noconflict/mode-css?url"; +import modeYamlUrl from "ace-builds/src-noconflict/mode-yaml?url"; +import modeMarkdownUrl from "ace-builds/src-noconflict/mode-markdown?url"; +import modeShUrl from "ace-builds/src-noconflict/mode-sh?url"; +import modeSqlUrl from "ace-builds/src-noconflict/mode-sql?url"; +import modeJavaUrl from "ace-builds/src-noconflict/mode-java?url"; +import modeGolangUrl from "ace-builds/src-noconflict/mode-golang?url"; +import modeRustUrl from "ace-builds/src-noconflict/mode-rust?url"; +import modeCsharpUrl from "ace-builds/src-noconflict/mode-csharp?url"; +import modePhpUrl from "ace-builds/src-noconflict/mode-php?url"; +import modeRubyUrl from "ace-builds/src-noconflict/mode-ruby?url"; +import modeCcppUrl from "ace-builds/src-noconflict/mode-c_cpp?url"; +import modeScssUrl from "ace-builds/src-noconflict/mode-scss?url"; +import modeLessUrl from "ace-builds/src-noconflict/mode-less?url"; +import modeXmlUrl from "ace-builds/src-noconflict/mode-xml?url"; +import modeDockerfileUrl from "ace-builds/src-noconflict/mode-dockerfile?url"; +import modeMakefileUrl from "ace-builds/src-noconflict/mode-makefile?url"; +import modeSassUrl from "ace-builds/src-noconflict/mode-sass?url"; +import modeTextUrl from "ace-builds/src-noconflict/mode-text?url"; // Workers (for syntax validation — currently disabled via useWorker:false, // but registered in case we want to enable them later) -import workerJavascriptUrl from 'ace-builds/src-noconflict/worker-javascript?url'; -import workerJsonUrl from 'ace-builds/src-noconflict/worker-json?url'; -import workerCssUrl from 'ace-builds/src-noconflict/worker-css?url'; -import workerHtmlUrl from 'ace-builds/src-noconflict/worker-html?url'; +import workerJavascriptUrl from "ace-builds/src-noconflict/worker-javascript?url"; +import workerJsonUrl from "ace-builds/src-noconflict/worker-json?url"; +import workerCssUrl from "ace-builds/src-noconflict/worker-css?url"; +import workerHtmlUrl from "ace-builds/src-noconflict/worker-html?url"; // Theme -import themeTomorrowUrl from 'ace-builds/src-noconflict/theme-tomorrow?url'; +import themeTomorrowUrl from "ace-builds/src-noconflict/theme-tomorrow?url"; // Extensions -import extLanguageToolsUrl from 'ace-builds/src-noconflict/ext-language_tools?url'; -import extSearchboxUrl from 'ace-builds/src-noconflict/ext-searchbox?url'; +import extLanguageToolsUrl from "ace-builds/src-noconflict/ext-language_tools?url"; +import extSearchboxUrl from "ace-builds/src-noconflict/ext-searchbox?url"; // ── Register all modules with ACE ──────────────────────────────────────── @@ -80,7 +80,7 @@ const MODE_URLS: Record = { const WORKER_URLS: Record = { javascript: workerJavascriptUrl, - typescript: workerJavascriptUrl, // TS mode shares the JS worker + typescript: workerJavascriptUrl, // TS mode shares the JS worker json: workerJsonUrl, css: workerCssUrl, html: workerHtmlUrl, @@ -97,14 +97,14 @@ for (const [mode, url] of Object.entries(WORKER_URLS)) { } // Register theme and extensions -ace.config.setModuleUrl('ace/theme/tomorrow', themeTomorrowUrl); -ace.config.setModuleUrl('ace/ext/language_tools', extLanguageToolsUrl); -ace.config.setModuleUrl('ace/ext/searchbox', extSearchboxUrl); +ace.config.setModuleUrl("ace/theme/tomorrow", themeTomorrowUrl); +ace.config.setModuleUrl("ace/ext/language_tools", extLanguageToolsUrl); +ace.config.setModuleUrl("ace/ext/searchbox", extSearchboxUrl); // ── Component ──────────────────────────────────────────────────────────── -import { WorkspaceMode } from '../lib/types'; -import { socketClient } from '../lib/socket'; +import { WorkspaceMode } from "../lib/types"; +import { socketClient } from "../lib/socket"; interface EditorPanelProps { workspaceMode: WorkspaceMode; @@ -125,51 +125,55 @@ interface EditorState { // Map file extensions to ACE language modes function detectLanguage(filePath: string): string { - const ext = filePath.split('.').pop()?.toLowerCase(); + const ext = filePath.split(".").pop()?.toLowerCase(); const languageMap: Record = { - 'js': 'javascript', - 'jsx': 'javascript', - 'ts': 'typescript', - 'tsx': 'typescript', - 'py': 'python', - 'rb': 'ruby', - 'java': 'java', - 'c': 'c_cpp', - 'cpp': 'c_cpp', - 'h': 'c_cpp', - 'hpp': 'c_cpp', - 'cs': 'csharp', - 'go': 'golang', - 'rs': 'rust', - 'php': 'php', - 'html': 'html', - 'htm': 'html', - 'css': 'css', - 'scss': 'scss', - 'sass': 'sass', - 'less': 'less', - 'json': 'json', - 'xml': 'xml', - 'yaml': 'yaml', - 'yml': 'yaml', - 'md': 'markdown', - 'sql': 'sql', - 'sh': 'sh', - 'bash': 'sh', - 'dockerfile': 'dockerfile', - 'makefile': 'makefile', + js: "javascript", + jsx: "javascript", + ts: "typescript", + tsx: "typescript", + py: "python", + rb: "ruby", + java: "java", + c: "c_cpp", + cpp: "c_cpp", + h: "c_cpp", + hpp: "c_cpp", + cs: "csharp", + go: "golang", + rs: "rust", + php: "php", + html: "html", + htm: "html", + css: "css", + scss: "scss", + sass: "sass", + less: "less", + json: "json", + xml: "xml", + yaml: "yaml", + yml: "yaml", + md: "markdown", + sql: "sql", + sh: "sh", + bash: "sh", + dockerfile: "dockerfile", + makefile: "makefile", }; - return languageMap[ext || ''] || 'text'; + return languageMap[ext || ""] || "text"; } -export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: EditorPanelProps) { +export default function EditorPanel({ + workspaceMode, + filePath, + onCloseFile, +}: EditorPanelProps) { const [state, setState] = useState({ - content: '', - originalContent: '', + content: "", + originalContent: "", isDirty: false, isLoading: false, isSaving: false, - language: 'text', + language: "text", }); const isReadOnly = workspaceMode === WorkspaceMode.Agent; @@ -182,18 +186,18 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed } else { // Clear editor when no file is selected setState({ - content: '', - originalContent: '', + content: "", + originalContent: "", isDirty: false, isLoading: false, isSaving: false, - language: 'text', + language: "text", }); } }, [filePath]); const loadFile = useCallback(async (path: string) => { - setState(prev => ({ ...prev, isLoading: true, error: undefined })); + setState((prev) => ({ ...prev, isLoading: true, error: undefined })); try { const result = await socketClient.requestFileRead({ path }); @@ -201,7 +205,6 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed if (result.success && result.content !== undefined) { const language = detectLanguage(path); // No dynamic import needed — all modes are registered via setModuleUrl at module load time - setState({ content: result.content, originalContent: result.content, @@ -211,17 +214,17 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed language, }); } else { - setState(prev => ({ + setState((prev) => ({ ...prev, isLoading: false, - error: result.error || 'Failed to load file', + error: result.error || "Failed to load file", })); } } catch (error) { - setState(prev => ({ + setState((prev) => ({ ...prev, isLoading: false, - error: error instanceof Error ? error.message : 'Failed to load file', + error: error instanceof Error ? error.message : "Failed to load file", })); } }, []); @@ -229,7 +232,7 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed const saveFile = useCallback(async () => { if (!filePath || isReadOnly) return; - setState(prev => ({ ...prev, isSaving: true, error: undefined })); + setState((prev) => ({ ...prev, isSaving: true, error: undefined })); try { const result = await socketClient.requestFileWrite({ @@ -238,56 +241,59 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed }); if (result.success) { - setState(prev => ({ + setState((prev) => ({ ...prev, isSaving: false, originalContent: prev.content, isDirty: false, - successMessage: 'File saved successfully', + successMessage: "File saved successfully", })); // Clear success message after 3 seconds setTimeout(() => { - setState(prev => ({ ...prev, successMessage: undefined })); + setState((prev) => ({ ...prev, successMessage: undefined })); }, 3000); } else { - setState(prev => ({ + setState((prev) => ({ ...prev, isSaving: false, - error: result.error || 'Failed to save file', + error: result.error || "Failed to save file", })); } } catch (error) { - setState(prev => ({ + setState((prev) => ({ ...prev, isSaving: false, - error: error instanceof Error ? error.message : 'Failed to save file', + error: error instanceof Error ? error.message : "Failed to save file", })); } }, [filePath, state.content, isReadOnly]); const handleContentChange = useCallback((newValue: string) => { - setState(prev => ({ + setState((prev) => ({ ...prev, content: newValue, isDirty: newValue !== prev.originalContent, })); }, []); - const handleKeyDown = useCallback((e: KeyboardEvent) => { - // Ctrl+S or Cmd+S to save - if ((e.ctrlKey || e.metaKey) && e.key === 's') { - e.preventDefault(); - if (!isReadOnly && state.isDirty) { - saveFile(); + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + // Ctrl+S or Cmd+S to save + if ((e.ctrlKey || e.metaKey) && e.key === "s") { + e.preventDefault(); + if (!isReadOnly && state.isDirty) { + saveFile(); + } } - } - }, [saveFile, isReadOnly, state.isDirty]); + }, + [saveFile, isReadOnly, state.isDirty], + ); // Add keyboard listener useEffect(() => { - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); }, [handleKeyDown]); // If no file is selected, show placeholder @@ -295,8 +301,18 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed return (
- - + +

No file open

Select a file from the tree to edit

@@ -332,17 +348,17 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed Read-only (Agent mode) )} - + {onCloseFile && ( @@ -351,8 +367,18 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed className="p-1.5 text-text-muted hover:text-text-primary hover:bg-bg-secondary rounded transition-colors" title="Close file" > - - + + )} @@ -363,8 +389,18 @@ export default function EditorPanel({ workspaceMode, filePath, onCloseFile }: Ed {state.error && (
- - + +

{state.error}