Skip to content

Commit 1bd39c8

Browse files
authored
Merge pull request #60 from anAcc22/dev
Support "Ctrl+Z" behavior for annotations
2 parents b19cffa + cec557a commit 1bd39c8

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ function App() {
208208
rounded-lg bg-block -left-2 top-8 w-100 invisible
209209
group-hover:visible max-h-28 no-scrollbar overflow-scroll"
210210
>
211+
<p>22 Jan 2026</p>
212+
<ul className="list-disc list-inside">
213+
<li>Support `Ctrl+Z` for annotations</li>
214+
</ul>
215+
<hr className="border-dashed border-border" />
211216
<p>22 Dec 2025</p>
212217
<ul className="list-disc list-inside">
213218
<li>Add edge physics</li>

src/components/GraphCanvas.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,12 @@ export function GraphCanvas({
318318
resizeCanvasIndicator();
319319
}, [settings.expandedCanvas]);
320320

321+
const loseInputFocus = () => {
322+
if (document.activeElement instanceof HTMLTextAreaElement) {
323+
document.activeElement.blur();
324+
}
325+
};
326+
321327
return (
322328
<div className="flex h-screen">
323329
<div
@@ -568,7 +574,7 @@ export function GraphCanvas({
568574
</button>
569575
</div>
570576
</div>
571-
<div className="h-full w-full relative">
577+
<div onMouseEnter={loseInputFocus} className="h-full w-full relative">
572578
<canvas
573579
ref={refMain}
574580
className="active:cursor-pointer border-2 border-border

src/components/animateGraph.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,13 @@ const FILL_COLORS_DARK = [
178178

179179
const FILL_COLORS_LENGTH = 20;
180180

181+
const MAX_UNDO = 15; // WARN: tweak later if needed (should be ok for now)
182+
183+
let undoStates: ImageData[] = [];
184+
185+
let saveMS = performance.now();
186+
let undoMS = performance.now();
187+
181188
let prevMS = performance.now();
182189
let latestColorChangeMS = performance.now();
183190

@@ -1278,6 +1285,24 @@ export function animateGraph(
12781285
annotationSecondLastPos = mousePos;
12791286
annotationLastPos = mousePos;
12801287

1288+
const curMS = performance.now();
1289+
1290+
if (curMS - saveMS >= 250) {
1291+
const savedState = ctxAnnotation.getImageData(
1292+
0,
1293+
0,
1294+
canvasWidth,
1295+
canvasHeight,
1296+
);
1297+
undoStates.push(savedState);
1298+
1299+
if (undoStates.length > MAX_UNDO) {
1300+
undoStates.shift();
1301+
}
1302+
1303+
saveMS = curMS;
1304+
}
1305+
12811306
if (settings.drawMode === "pen") {
12821307
inAnnotation = true;
12831308
inErase = false;
@@ -1317,6 +1342,33 @@ export function animateGraph(
13171342
inErase = false;
13181343
});
13191344

1345+
const undo = () => {
1346+
if (undoStates.length === 0) return;
1347+
1348+
const lastState = undoStates.pop();
1349+
1350+
ctxAnnotation.putImageData(lastState as ImageData, 0, 0);
1351+
};
1352+
1353+
window.addEventListener("keydown", (event: KeyboardEvent) => {
1354+
const isCtrl = event.ctrlKey || event.metaKey;
1355+
const isZ = event.key.toLowerCase() === "z";
1356+
const inInput = document.activeElement instanceof HTMLTextAreaElement;
1357+
1358+
if (isCtrl && isZ) {
1359+
if (inInput) return;
1360+
1361+
event.preventDefault();
1362+
const curMS = performance.now();
1363+
1364+
if (curMS - undoMS <= 100) return;
1365+
1366+
undo();
1367+
1368+
undoMS = curMS;
1369+
}
1370+
});
1371+
13201372
canvas.addEventListener("pointerdown", (event) => {
13211373
event.preventDefault();
13221374

0 commit comments

Comments
 (0)