feat: implement EditorPage with file explorer and simulator canvas components
This commit is contained in:
parent
2e4c470f75
commit
a9b48a3cc6
|
|
@ -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)"
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue