From 9b8747349f770cccb9766ee92493bc287b319aac Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Thu, 5 Mar 2026 21:07:10 -0300 Subject: [PATCH] feat: enhance RP2040 and AVR simulators with serial baud rate handling; update editor toolbar and library manager modal for improved state management and UI --- backend/app/services/arduino_cli.py | 7 ++ .../src/components/editor/EditorToolbar.tsx | 30 +++---- .../simulator/LibraryManagerModal.css | 6 ++ .../simulator/LibraryManagerModal.tsx | 48 ++++++----- .../components/simulator/SerialMonitor.tsx | 13 +++ frontend/src/pages/EditorPage.tsx | 83 +++++++++++++++++-- frontend/src/simulation/AVRSimulator.ts | 12 +++ frontend/src/simulation/RP2040Simulator.ts | 9 +- frontend/src/simulation/parts/ComplexParts.ts | 10 ++- frontend/src/store/useSimulatorStore.ts | 19 ++++- 10 files changed, 181 insertions(+), 56 deletions(-) diff --git a/backend/app/services/arduino_cli.py b/backend/app/services/arduino_cli.py index e50fefc6..c8e0f202 100644 --- a/backend/app/services/arduino_cli.py +++ b/backend/app/services/arduino_cli.py @@ -224,6 +224,13 @@ class ArduinoCLIService: # arduino-cli requires sketch name to match directory name sketch_file = sketch_dir / "sketch.ino" + + # For RP2040 boards, redirect Serial (USB CDC) to Serial1 (UART0) + # The emulator captures UART0 output, but the arduino-pico core + # defaults Serial to USB CDC which isn't emulated. + if "rp2040" in board_fqbn: + code = "#define Serial Serial1\n" + code + sketch_file.write_text(code) print(f"Created sketch file: {sketch_file}") diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx index 04925449..ce6f59a7 100644 --- a/frontend/src/components/editor/EditorToolbar.tsx +++ b/frontend/src/components/editor/EditorToolbar.tsx @@ -3,12 +3,18 @@ import { useEditorStore } from '../../store/useEditorStore'; import { useSimulatorStore, BOARD_FQBN, BOARD_LABELS } from '../../store/useSimulatorStore'; import { compileCode } from '../../services/compilation'; import { LibraryManagerModal } from '../simulator/LibraryManagerModal'; -import { CompilationConsole } from './CompilationConsole'; import { parseCompileResult } from '../../utils/compilationLogger'; import type { CompilationLog } from '../../utils/compilationLogger'; import './EditorToolbar.css'; -export const EditorToolbar = () => { +interface EditorToolbarProps { + consoleOpen: boolean; + setConsoleOpen: (open: boolean | ((v: boolean) => boolean)) => void; + compileLogs: CompilationLog[]; + setCompileLogs: (logs: CompilationLog[] | ((prev: CompilationLog[]) => CompilationLog[])) => void; +} + +export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compileLogs, setCompileLogs }: EditorToolbarProps) => { const { code } = useEditorStore(); const { boardType, @@ -23,12 +29,10 @@ export const EditorToolbar = () => { const [compiling, setCompiling] = useState(false); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const [libManagerOpen, setLibManagerOpen] = useState(false); - const [consoleOpen, setConsoleOpen] = useState(false); - const [compileLogs, setCompileLogs] = useState([]); const addLog = useCallback((log: CompilationLog) => { - setCompileLogs((prev) => [...prev, log]); - }, []); + setCompileLogs((prev: CompilationLog[]) => [...prev, log]); + }, [setCompileLogs]); const handleCompile = async () => { setCompiling(true); @@ -45,7 +49,7 @@ export const EditorToolbar = () => { // Parse the full result into log entries const resultLogs = parseCompileResult(result, boardLabel); - setCompileLogs((prev) => [...prev, ...resultLogs]); + setCompileLogs((prev: CompilationLog[]) => [...prev, ...resultLogs]); if (result.success) { if (result.hex_content) { @@ -203,18 +207,6 @@ export const EditorToolbar = () => {
{message.text}
)} - {/* Compilation Console */} - {consoleOpen && ( -
- setConsoleOpen(false)} - logs={compileLogs} - onClear={() => setCompileLogs([])} - /> -
- )} - setLibManagerOpen(false)} /> ); diff --git a/frontend/src/components/simulator/LibraryManagerModal.css b/frontend/src/components/simulator/LibraryManagerModal.css index dacb8545..879be3b2 100644 --- a/frontend/src/components/simulator/LibraryManagerModal.css +++ b/frontend/src/components/simulator/LibraryManagerModal.css @@ -191,6 +191,12 @@ animation: spin 0.8s linear infinite; } +.lib-spinner-center { + width: 32px; + height: 32px; + margin-bottom: 8px; +} + @keyframes spin { from { transform: rotate(0deg); diff --git a/frontend/src/components/simulator/LibraryManagerModal.tsx b/frontend/src/components/simulator/LibraryManagerModal.tsx index 2e436fbb..5ac7f003 100644 --- a/frontend/src/components/simulator/LibraryManagerModal.tsx +++ b/frontend/src/components/simulator/LibraryManagerModal.tsx @@ -33,26 +33,29 @@ export const LibraryManagerModal: React.FC = ({ isOpen } }, []); - // Fetch installed list when modal opens (to cross-reference in search tab) + // Reset state when modal closes useEffect(() => { - if (isOpen) { - fetchInstalled(); + if (!isOpen) { + setSearchQuery(''); + setSearchResults([]); + setStatusMsg(null); } - }, [isOpen, fetchInstalled]); + }, [isOpen]); - // Also refresh when switching to the installed tab + // Fetch installed list when modal opens or switching to installed tab useEffect(() => { - if (isOpen && activeTab === 'installed') { - fetchInstalled(); - } + if (isOpen && activeTab === 'installed') fetchInstalled(); }, [isOpen, activeTab, fetchInstalled]); useEffect(() => { - if (!searchQuery.trim()) { - setSearchResults([]); - return; - } + if (isOpen) fetchInstalled(); + }, [isOpen, fetchInstalled]); + + // Search: immediate on open (empty query), debounced when typing + useEffect(() => { + if (!isOpen) return; if (debounceRef.current) clearTimeout(debounceRef.current); + const delay = searchQuery ? 400 : 0; debounceRef.current = setTimeout(async () => { setLoadingSearch(true); setStatusMsg(null); @@ -65,10 +68,10 @@ export const LibraryManagerModal: React.FC = ({ isOpen } finally { setLoadingSearch(false); } - }, 500); + }, delay); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; - }, [searchQuery]); + }, [searchQuery, isOpen]); const handleInstall = async (libName: string) => { setInstallingLib(libName); @@ -89,9 +92,6 @@ export const LibraryManagerModal: React.FC = ({ isOpen }; const handleClose = () => { - setStatusMsg(null); - setSearchQuery(''); - setSearchResults([]); onClose(); }; @@ -193,18 +193,20 @@ export const LibraryManagerModal: React.FC = ({ isOpen
- {!searchQuery.trim() && ( + {loadingSearch && (
-

Type a library name to search

-

e.g. "ArduinoJson", "Servo", "LiquidCrystal"

+ + + +

{searchQuery ? `Searching "${searchQuery}"…` : 'Loading libraries…'}

)} - {searchQuery.trim() && searchResults.length === 0 && !loadingSearch && ( + {!loadingSearch && searchResults.length === 0 && (
-

No libraries found for "{searchQuery}"

+

{searchQuery ? `No libraries found for "${searchQuery}"` : 'No libraries available'}

)} - {searchResults.map((lib, i) => ( + {!loadingSearch && searchResults.map((lib, i) => (
diff --git a/frontend/src/components/simulator/SerialMonitor.tsx b/frontend/src/components/simulator/SerialMonitor.tsx index b76e1d5d..1467c21a 100644 --- a/frontend/src/components/simulator/SerialMonitor.tsx +++ b/frontend/src/components/simulator/SerialMonitor.tsx @@ -8,6 +8,7 @@ import { useSimulatorStore } from '../../store/useSimulatorStore'; export const SerialMonitor: React.FC = () => { const serialOutput = useSimulatorStore((s) => s.serialOutput); + const serialBaudRate = useSimulatorStore((s) => s.serialBaudRate); const running = useSimulatorStore((s) => s.running); const serialWrite = useSimulatorStore((s) => s.serialWrite); const clearSerialOutput = useSimulatorStore((s) => s.clearSerialOutput); @@ -49,6 +50,9 @@ export const SerialMonitor: React.FC = () => {
Serial Monitor
+ {serialBaudRate > 0 && ( + {serialBaudRate.toLocaleString()} baud + )}