feat: drag handles for resizing redaction bounding boxes

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Alex-GilbertandClaude Opus 4.6 committed 2026-02-20 12:49:52 -06:00
1 parent 9b9618d9fa
commit 43e7f80bbe
2 files changed
+92 -1

No files matched your search

+21
View File
@@ -70,6 +70,23 @@ function measureStyledText(text, fontName, fontSize) {
return w;
}
/**
* Draw small square handles at the four edges of a redaction for resizing.
* @param {import('./types.js').Redaction} r
*/
function drawResizeHandles(r) {
const sz = 6;
ctx.fillStyle = "rgba(0, 120, 255, 0.8)";
// Left edge
ctx.fillRect(r.x - sz/2, r.y + r.h/2 - sz/2, sz, sz);
// Right edge
ctx.fillRect(r.x + r.w - sz/2, r.y + r.h/2 - sz/2, sz, sz);
// Top edge
ctx.fillRect(r.x + r.w/2 - sz/2, r.y - sz/2, sz, sz);
// Bottom edge
ctx.fillRect(r.x + r.w/2 - sz/2, r.y + r.h - sz/2, sz, sz);
}
export function renderCanvas() {
if (!docImage.naturalWidth || !state.fontsReady) return;
@@ -94,6 +111,10 @@ export function renderCanvas() {
} else {
drawRedactionUnanalyzed(r, isActive);
}
if (r.id === state.activeRedaction) {
drawResizeHandles(r);
}
}
}
+71 -1
View File
@@ -12,6 +12,7 @@ import { renderCanvas } from './canvas.js';
import { applyTransform, screenToDoc, hitTestRedaction, initViewport } from './viewport.js';
import { openPopover, closePopover, setOnPopoverClose, updatePosDisplay, initPopover } from './popover.js';
import { stopSolve, acceptSolution, initSolver } from './solver.js';
import { showInlineEdit, hideInlineEdit, syncInlineEdit } from './inline-edit.js';
// ── Font loading ──
@@ -325,6 +326,7 @@ function activateRedaction(id) {
if (r.status === "analyzed" || r.status === "solved") {
openPopover(id);
showInlineEdit(id);
}
}
@@ -443,6 +445,74 @@ window.addEventListener("mouseup", () => {
if (modDrag) modDrag = null;
});
// ── Drag handles for resizing redaction bounding boxes ──
let resizeDrag = null;
canvas.addEventListener("mousedown", (e) => {
if (e.button !== 0 || e.ctrlKey || e.shiftKey) return;
const r = state.redactions[state.activeRedaction];
if (!r) return;
const rect = rightPanel.getBoundingClientRect();
const doc = screenToDoc(e.clientX - rect.left, e.clientY - rect.top);
const threshold = 8 / state.zoom;
// Check if near an edge handle
let edge = null;
if (Math.abs(doc.x - r.x) < threshold && Math.abs(doc.y - (r.y + r.h/2)) < threshold) edge = "left";
else if (Math.abs(doc.x - (r.x + r.w)) < threshold && Math.abs(doc.y - (r.y + r.h/2)) < threshold) edge = "right";
else if (Math.abs(doc.y - r.y) < threshold && Math.abs(doc.x - (r.x + r.w/2)) < threshold) edge = "top";
else if (Math.abs(doc.y - (r.y + r.h)) < threshold && Math.abs(doc.x - (r.x + r.w/2)) < threshold) edge = "bottom";
if (!edge) return;
resizeDrag = {
edge,
startX: e.clientX,
startY: e.clientY,
origX: r.x,
origY: r.y,
origW: r.w,
origH: r.h,
};
e.stopPropagation();
e.preventDefault();
}, { capture: true });
window.addEventListener("mousemove", (e) => {
if (!resizeDrag) return;
const r = state.redactions[state.activeRedaction];
if (!r) return;
const dx = (e.clientX - resizeDrag.startX) / state.zoom;
const dy = (e.clientY - resizeDrag.startY) / state.zoom;
if (resizeDrag.edge === "left") {
r.x = Math.round(resizeDrag.origX + dx);
r.w = Math.max(10, Math.round(resizeDrag.origW - dx));
} else if (resizeDrag.edge === "right") {
r.w = Math.max(10, Math.round(resizeDrag.origW + dx));
} else if (resizeDrag.edge === "top") {
r.y = Math.round(resizeDrag.origY + dy);
r.h = Math.max(10, Math.round(resizeDrag.origH - dy));
} else if (resizeDrag.edge === "bottom") {
r.h = Math.max(10, Math.round(resizeDrag.origH + dy));
}
// Update gap width in overrides to match box width changes
if (r.overrides && (resizeDrag.edge === "left" || resizeDrag.edge === "right")) {
r.overrides.gapWidth = r.w;
gapValue.textContent = String(Math.round(r.w));
}
renderCanvas();
});
window.addEventListener("mouseup", () => {
if (resizeDrag) resizeDrag = null;
});
// ── Accept solution (wired here to avoid circular dep solver↔main) ──
solveAccept.addEventListener("click", () => {
@@ -515,7 +585,7 @@ document.addEventListener("keydown", (e) => {
// ── Initialize all modules ──
setOnPopoverClose(stopSolve);
setOnPopoverClose(() => { stopSolve(); hideInlineEdit(); });
initViewport();
initPopover();
initSolver();