feat: implement EditorPage with file explorer and simulator canvas components

This commit is contained in:
David Montero Crespo 2026-04-30 15:27:58 -03:00
parent 2e4c470f75
commit a9b48a3cc6
3 changed files with 55 additions and 11 deletions

View File

@ -58,6 +58,23 @@ const IcoNewFile = () => (
</svg>
);
const IcoNewWorkspace = () => (
<svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
<line x1="12" y1="11" x2="12" y2="17" />
<line x1="9" y1="14" x2="15" y2="14" />
</svg>
);
const IcoSave = () => (
<svg
width="22"
@ -130,9 +147,10 @@ interface ContextMenu {
interface FileExplorerProps {
onSaveClick: () => void;
onNewClick: () => void;
}
export const FileExplorer: React.FC<FileExplorerProps> = ({ onSaveClick }) => {
export const FileExplorer: React.FC<FileExplorerProps> = ({ onSaveClick, onNewClick }) => {
const {
fileGroups,
activeFileId,
@ -254,6 +272,13 @@ export const FileExplorer: React.FC<FileExplorerProps> = ({ onSaveClick }) => {
<div className="file-explorer-header">
<span className="file-explorer-title">WORKSPACE</span>
<div className="file-explorer-header-actions">
<button
className="file-explorer-new-btn"
title="New workspace (clears boards, components, wires and files)"
onClick={onNewClick}
>
<IcoNewWorkspace />
</button>
<button
className="file-explorer-save-btn"
title="Save project (Ctrl+S)"

View File

@ -958,8 +958,7 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
if (selectedComponentId) {
removeComponent(selectedComponentId);
setSelectedComponentId(null);
} else if (activeBoardId && boards.length > 1) {
// Only allow deleting boards if more than one exists
} else if (activeBoardId) {
setBoardToRemove(activeBoardId);
}
}
@ -967,7 +966,7 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [selectedComponentId, removeComponent, activeBoardId, boards.length]);
}, [selectedComponentId, removeComponent, activeBoardId]);
// Handle component selection from modal
const handleSelectComponent = (metadata: ComponentMetadata) => {
@ -2172,15 +2171,13 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
padding: '7px 14px',
background: 'none',
border: 'none',
color: boards.length <= 1 ? '#555' : '#e06c75',
cursor: boards.length <= 1 ? 'default' : 'pointer',
color: '#e06c75',
cursor: 'pointer',
fontSize: 13,
textAlign: 'left',
}}
disabled={boards.length <= 1}
title={boards.length <= 1 ? 'Cannot remove the last board' : undefined}
onMouseEnter={(e) => {
if (boards.length > 1) e.currentTarget.style.background = '#2a2d2e';
e.currentTarget.style.background = '#2a2d2e';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'none';

View File

@ -24,9 +24,10 @@ import { AppHeader } from '../components/layout/AppHeader';
import { SaveProjectModal } from '../components/layout/SaveProjectModal';
import { LoginPromptModal } from '../components/layout/LoginPromptModal';
import { GitHubStarBanner } from '../components/layout/GitHubStarBanner';
import { useSimulatorStore } from '../store/useSimulatorStore';
import { useSimulatorStore, DEFAULT_BOARD_POSITION } from '../store/useSimulatorStore';
import { useOscilloscopeStore } from '../store/useOscilloscopeStore';
import { useAuthStore } from '../store/useAuthStore';
import { useProjectStore } from '../store/useProjectStore';
import type { CompilationLog } from '../utils/compilationLogger';
import '../App.css';
@ -140,6 +141,27 @@ export const EditorPage: React.FC = () => {
}
}, [user]);
const handleNewClick = useCallback(() => {
if (
!window.confirm(
'Start a new workspace? This clears every board, component, wire and file. This cannot be undone.',
)
) {
return;
}
const sim = useSimulatorStore.getState();
sim.boards.forEach((b) => sim.stopBoard(b.id));
const ids = sim.boards.map((b) => b.id);
ids.forEach((id) => sim.removeBoard(id));
sim.setComponents([]);
sim.setWires([]);
useProjectStore.getState().clearCurrentProject();
const newId = useSimulatorStore
.getState()
.addBoard('arduino-uno', DEFAULT_BOARD_POSITION.x, DEFAULT_BOARD_POSITION.y);
useSimulatorStore.getState().setActiveBoardId(newId);
}, []);
// Track mobile breakpoint
useEffect(() => {
const mq = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`);
@ -357,7 +379,7 @@ export const EditorPage: React.FC = () => {
<div
style={{ width: explorerWidth, flexShrink: 0, display: 'flex', overflow: 'hidden' }}
>
<FileExplorer onSaveClick={handleSaveClick} />
<FileExplorer onSaveClick={handleSaveClick} onNewClick={handleNewClick} />
</div>
{!isMobile && (
<div