From bd5fd18756f6746c03858f2ea5f454ff46ef3457 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Fri, 8 May 2026 12:11:53 -0300 Subject: [PATCH] feat(canvas): keyboard shortcuts + toolbar undo/redo buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI-facing half of the undo/redo feature. Combined with the previous two commits, Ctrl+Z (or the toolbar button) now reverses every canvas mutation: add/remove component, move, rotate, set property, add/remove wire. EditorPage.tsx: - New window-keydown effect for Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z (and the Cmd equivalents). Uses the same input/textarea/contenteditable guard as the existing Ctrl+S handler — Monaco's per-file undo and the AI chat composer keep their own behaviour. SimulatorCanvas.tsx: - Two icon buttons (undo + redo) added to the canvas header, between the board selector and the Serial Monitor toggle. Tooltip surfaces the next command's description ("Undo: Add LED (Ctrl+Z)") so the user knows exactly what's about to revert. Buttons disable when the stack is empty in that direction. - New `canvas-icon-btn` CSS class for square 32×32 icon-only buttons (matches the visual weight of the existing Serial button without the label). - Subscribes to history / historyIndex via store selectors so the buttons re-render reactively as commands are pushed/undone. No new tests — the store-level coverage from 99ed22b already exercises undo/redo round trips. UI affordances are wired pass-through to those store APIs. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/simulator/SimulatorCanvas.css | 30 +++++++++ .../components/simulator/SimulatorCanvas.tsx | 61 +++++++++++++++++++ frontend/src/pages/EditorPage.tsx | 28 +++++++++ 3 files changed, 119 insertions(+) diff --git a/frontend/src/components/simulator/SimulatorCanvas.css b/frontend/src/components/simulator/SimulatorCanvas.css index f2c55206..7f5c6f5c 100644 --- a/frontend/src/components/simulator/SimulatorCanvas.css +++ b/frontend/src/components/simulator/SimulatorCanvas.css @@ -137,6 +137,36 @@ color: #4fc3f7; } +/* ── Canvas icon-only buttons (undo/redo, …) ─────── */ +.canvas-icon-btn { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: transparent; + border: 1px solid #3a4a5a; + border-radius: 5px; + color: #9d9d9d; + cursor: pointer; + transition: + border-color 0.15s, + background 0.15s, + color 0.15s, + opacity 0.15s; + flex-shrink: 0; +} + +.canvas-icon-btn:hover:not(:disabled) { + border-color: #007acc; + color: #ccc; +} + +.canvas-icon-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + /* ── Component count ─────────────────────────────── */ .component-count { display: flex; diff --git a/frontend/src/components/simulator/SimulatorCanvas.tsx b/frontend/src/components/simulator/SimulatorCanvas.tsx index 5e1390a6..240f5219 100644 --- a/frontend/src/components/simulator/SimulatorCanvas.tsx +++ b/frontend/src/components/simulator/SimulatorCanvas.tsx @@ -143,6 +143,12 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => { const recordRotate = useSimulatorStore((s) => s.recordRotate); const recordSetProperty = useSimulatorStore((s) => s.recordSetProperty); const recordRemoveWire = useSimulatorStore((s) => s.recordRemoveWire); + // Subscribe to history shape so the undo/redo buttons reactively + // enable/disable and their tooltips reflect the next command. + const history = useSimulatorStore((s) => s.history); + const historyIndex = useSimulatorStore((s) => s.historyIndex); + const undo = useSimulatorStore((s) => s.undo); + const redo = useSimulatorStore((s) => s.redo); // Oscilloscope const oscilloscopeOpen = useOscilloscopeStore((s) => s.open); @@ -1901,6 +1907,61 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => { )} + {/* Undo / Redo — canvas-scoped, mirrors the Ctrl+Z / Ctrl+Y + handler in EditorPage. Tooltip surfaces the description of + the command that would be applied. Disabled when the stack + is exhausted in that direction. */} + + + {/* Serial Monitor toggle */}