feat: update component styles and add serial monitor functionality

This commit is contained in:
David Montero Crespo 2026-03-06 10:24:03 -03:00
parent a5c6987aca
commit c1fc653de2
10 changed files with 156 additions and 93 deletions

View File

@ -1,6 +1,6 @@
{ {
"version": "1.0.0", "version": "1.0.0",
"generatedAt": "2026-03-06T02:57:38.515Z", "generatedAt": "2026-03-06T13:12:51.254Z",
"components": [ "components": [
{ {
"id": "arduino-mega", "id": "arduino-mega",

View File

@ -136,7 +136,7 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
className="tb-btn tb-btn-stop" className="tb-btn tb-btn-stop"
title="Stop" title="Stop"
> >
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" stroke="none"> <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" stroke="none">
<rect x="3" y="3" width="18" height="18" rx="2" /> <rect x="3" y="3" width="18" height="18" rx="2" />
</svg> </svg>
</button> </button>
@ -195,7 +195,7 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
className={`tb-btn tb-btn-output${consoleOpen ? ' tb-btn-output-active' : ''}`} className={`tb-btn tb-btn-output${consoleOpen ? ' tb-btn-output-active' : ''}`}
title="Toggle Output Console" title="Toggle Output Console"
> >
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="4 17 10 11 4 5" /> <polyline points="4 17 10 11 4 5" />
<line x1="12" y1="19" x2="20" y2="19" /> <line x1="12" y1="19" x2="20" y2="19" />
</svg> </svg>

View File

@ -14,7 +14,8 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 7px 10px 6px; padding: 0 10px;
height: 46px;
color: #9d9d9d; color: #9d9d9d;
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
@ -23,20 +24,24 @@
flex-shrink: 0; flex-shrink: 0;
} }
.file-explorer-header-actions {
display: flex;
align-items: center;
gap: 2px;
}
.file-explorer-new-btn { .file-explorer-new-btn {
background: none; background: none;
border: none; border: none;
color: #9d9d9d; color: #9d9d9d;
font-size: 18px;
line-height: 1;
cursor: pointer; cursor: pointer;
padding: 0 3px; padding: 2px;
border-radius: 3px; border-radius: 3px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 22px; width: 26px;
height: 22px; height: 26px;
} }
.file-explorer-new-btn:hover { .file-explorer-new-btn:hover {
@ -44,6 +49,25 @@
color: #fff; color: #fff;
} }
.file-explorer-save-btn {
background: none;
border: none;
color: #9d9d9d;
cursor: pointer;
padding: 2px;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
}
.file-explorer-save-btn:hover {
background: rgba(255, 255, 255, 0.08);
color: #4fc3f7;
}
.file-explorer-list { .file-explorer-list {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
@ -92,8 +116,10 @@
} }
.file-explorer-dot { .file-explorer-dot {
color: #e8a87c; width: 7px;
font-size: 9px; height: 7px;
border-radius: 50%;
background: #e8a87c;
flex-shrink: 0; flex-shrink: 0;
} }

View File

@ -2,13 +2,44 @@ import React, { useState, useRef, useEffect, useCallback } from 'react';
import { useEditorStore } from '../../store/useEditorStore'; import { useEditorStore } from '../../store/useEditorStore';
import './FileExplorer.css'; import './FileExplorer.css';
function getFileIcon(name: string): string { // SVG icons — same style as EditorToolbar (stroke-based, 16x16)
const IcoFile = () => (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
</svg>
);
const IcoHeader = () => (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
<line x1="9" y1="13" x2="15" y2="13" />
<line x1="9" y1="17" x2="13" y2="17" />
</svg>
);
const IcoNewFile = () => (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
<line x1="12" y1="18" x2="12" y2="12" />
<line x1="9" y1="15" x2="15" y2="15" />
</svg>
);
const IcoSave = () => (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
<polyline points="17 21 17 13 7 13 7 21" />
<polyline points="7 3 7 8 15 8" />
</svg>
);
function FileIcon({ name }: { name: string }) {
const ext = name.split('.').pop()?.toLowerCase() ?? ''; const ext = name.split('.').pop()?.toLowerCase() ?? '';
if (ext === 'ino') return '⚡'; if (['h', 'hpp'].includes(ext)) return <IcoHeader />;
if (['cpp', 'c', 'cc'].includes(ext)) return '📄'; return <IcoFile />;
if (['h', 'hpp'].includes(ext)) return '📋';
if (ext === 'json') return '{}';
return '📝';
} }
interface ContextMenu { interface ContextMenu {
@ -17,7 +48,11 @@ interface ContextMenu {
y: number; y: number;
} }
export const FileExplorer: React.FC = () => { interface FileExplorerProps {
onSaveClick: () => void;
}
export const FileExplorer: React.FC<FileExplorerProps> = ({ onSaveClick }) => {
const { files, activeFileId, openFile, createFile, deleteFile, renameFile } = const { files, activeFileId, openFile, createFile, deleteFile, renameFile } =
useEditorStore(); useEditorStore();
const [contextMenu, setContextMenu] = useState<ContextMenu | null>(null); const [contextMenu, setContextMenu] = useState<ContextMenu | null>(null);
@ -94,13 +129,22 @@ export const FileExplorer: React.FC = () => {
<div className="file-explorer"> <div className="file-explorer">
<div className="file-explorer-header"> <div className="file-explorer-header">
<span className="file-explorer-title">WORKSPACE</span> <span className="file-explorer-title">WORKSPACE</span>
<button <div className="file-explorer-header-actions">
className="file-explorer-new-btn" <button
title="New File" className="file-explorer-new-btn"
onClick={startCreateFile} title="New File"
> onClick={startCreateFile}
+ >
</button> <IcoNewFile />
</button>
<button
className="file-explorer-save-btn"
title="Save project (Ctrl+S)"
onClick={onSaveClick}
>
<IcoSave />
</button>
</div>
</div> </div>
<div className="file-explorer-list"> <div className="file-explorer-list">
@ -113,7 +157,9 @@ export const FileExplorer: React.FC = () => {
onDoubleClick={() => startRename(file.id)} onDoubleClick={() => startRename(file.id)}
title={`${file.name}${file.modified ? ' (unsaved)' : ''}`} title={`${file.name}${file.modified ? ' (unsaved)' : ''}`}
> >
<span className="file-explorer-icon">{getFileIcon(file.name)}</span> <span className="file-explorer-icon">
<FileIcon name={file.name} />
</span>
{renamingId === file.id ? ( {renamingId === file.id ? (
<input <input
@ -133,16 +179,16 @@ export const FileExplorer: React.FC = () => {
)} )}
{file.modified && ( {file.modified && (
<span className="file-explorer-dot" title="Unsaved changes"> <span className="file-explorer-dot" title="Unsaved changes" />
</span>
)} )}
</div> </div>
))} ))}
{creatingFile && ( {creatingFile && (
<div className="file-explorer-item file-explorer-item-new"> <div className="file-explorer-item file-explorer-item-new">
<span className="file-explorer-icon"></span> <span className="file-explorer-icon">
<IcoFile />
</span>
<input <input
ref={newFileInputRef} ref={newFileInputRef}
className="file-explorer-rename-input" className="file-explorer-rename-input"

View File

@ -43,8 +43,10 @@
} }
.file-tab-modified { .file-tab-modified {
color: #e8a87c; width: 7px;
font-size: 9px; height: 7px;
border-radius: 50%;
background: #e8a87c;
flex-shrink: 0; flex-shrink: 0;
} }

View File

@ -39,7 +39,7 @@ export const FileTabs: React.FC = () => {
onClick={() => setActiveFile(file.id)} onClick={() => setActiveFile(file.id)}
title={file.name} title={file.name}
> >
{file.modified && <span className="file-tab-modified"></span>} {file.modified && <span className="file-tab-modified" title="Unsaved changes" />}
<span className="file-tab-name">{file.name}</span> <span className="file-tab-name">{file.name}</span>
<button <button
className="file-tab-close" className="file-tab-close"

View File

@ -1,15 +1,10 @@
import { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { useAuthStore } from '../../store/useAuthStore'; import { useAuthStore } from '../../store/useAuthStore';
import { useSimulatorStore } from '../../store/useSimulatorStore';
interface AppHeaderProps { interface AppHeaderProps {}
onSaveClick: () => void;
}
export const AppHeader: React.FC<AppHeaderProps> = ({ onSaveClick }) => { export const AppHeader: React.FC<AppHeaderProps> = () => {
const serialMonitorOpen = useSimulatorStore((s) => s.serialMonitorOpen);
const toggleSerialMonitor = useSimulatorStore((s) => s.toggleSerialMonitor);
const user = useAuthStore((s) => s.user); const user = useAuthStore((s) => s.user);
const logout = useAuthStore((s) => s.logout); const logout = useAuthStore((s) => s.logout);
const navigate = useNavigate(); const navigate = useNavigate();
@ -55,56 +50,6 @@ export const AppHeader: React.FC<AppHeaderProps> = ({ onSaveClick }) => {
Examples Examples
</Link> </Link>
<button
onClick={toggleSerialMonitor}
className="serial-monitor-toggle"
title="Toggle Serial Monitor"
style={{
background: serialMonitorOpen ? '#0e639c' : 'transparent',
border: '1px solid #555',
color: '#ccc',
padding: '4px 10px',
borderRadius: 4,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 5,
fontSize: 13,
}}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" />
<path d="M8 21h8M12 17v4" />
</svg>
Serial
</button>
{/* Save button */}
<button
onClick={onSaveClick}
title="Save project (Ctrl+S)"
style={{
background: '#0e639c',
border: 'none',
color: '#fff',
padding: '4px 12px',
borderRadius: 4,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 6,
fontSize: 13,
fontWeight: 500,
}}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
<polyline points="17 21 17 13 7 13 7 21" />
<polyline points="7 3 7 8 15 8" />
</svg>
Save
</button>
{/* Auth UI */} {/* Auth UI */}
{user ? ( {user ? (
<div style={{ position: 'relative' }} ref={dropdownRef}> <div style={{ position: 'relative' }} ref={dropdownRef}>

View File

@ -85,6 +85,35 @@
cursor: not-allowed; cursor: not-allowed;
} }
/* ── Serial Monitor button ───────────────────────── */
.canvas-serial-btn {
display: flex;
align-items: center;
gap: 5px;
padding: 0 10px;
height: 32px;
background: transparent;
border: 1px solid #3a4a5a;
border-radius: 5px;
color: #9d9d9d;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: border-color 0.15s, background 0.15s, color 0.15s;
white-space: nowrap;
}
.canvas-serial-btn:hover {
border-color: #007acc;
color: #ccc;
}
.canvas-serial-btn-active {
background: #0e3a5a;
border-color: #007acc;
color: #4fc3f7;
}
/* ── Component count ─────────────────────────────── */ /* ── Component count ─────────────────────────────── */
.component-count { .component-count {
display: flex; display: flex;

View File

@ -27,6 +27,8 @@ export const SimulatorCanvas = () => {
addComponent, addComponent,
removeComponent, removeComponent,
updateComponent, updateComponent,
serialMonitorOpen,
toggleSerialMonitor,
} = useSimulatorStore(); } = useSimulatorStore();
// Wire management from store // Wire management from store
@ -400,6 +402,19 @@ export const SimulatorCanvas = () => {
<option key={type} value={type}>{label}</option> <option key={type} value={type}>{label}</option>
))} ))}
</select> </select>
{/* Serial Monitor toggle */}
<button
onClick={toggleSerialMonitor}
className={`canvas-serial-btn${serialMonitorOpen ? ' canvas-serial-btn-active' : ''}`}
title="Toggle Serial Monitor"
>
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" />
<path d="M8 21h8M12 17v4" />
</svg>
Serial
</button>
</div> </div>
<div className="canvas-header-right"> <div className="canvas-header-right">
@ -419,7 +434,7 @@ export const SimulatorCanvas = () => {
title="Add Component" title="Add Component"
disabled={running} disabled={running}
> >
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="5" x2="12" y2="19" /> <line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" /> <line x1="5" y1="12" x2="19" y2="12" />
</svg> </svg>

View File

@ -112,7 +112,7 @@ export const EditorPage: React.FC = () => {
return ( return (
<div className="app"> <div className="app">
<AppHeader onSaveClick={handleSaveClick} /> <AppHeader />
<div className="app-container" ref={containerRef}> <div className="app-container" ref={containerRef}>
{/* ── Editor side ── */} {/* ── Editor side ── */}
@ -121,7 +121,7 @@ export const EditorPage: React.FC = () => {
style={{ width: `${editorWidthPct}%`, display: 'flex', flexDirection: 'row' }} style={{ width: `${editorWidthPct}%`, display: 'flex', flexDirection: 'row' }}
> >
{/* File explorer sidebar */} {/* File explorer sidebar */}
{explorerOpen && <FileExplorer />} {explorerOpen && <FileExplorer onSaveClick={handleSaveClick} />}
{/* Editor main area */} {/* Editor main area */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minWidth: 0 }}> <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minWidth: 0 }}>