From 1e55035f09fcfd2caea524db09604e628ca85ec4 Mon Sep 17 00:00:00 2001 From: McElwain Date: Mon, 25 May 2026 23:51:30 -0500 Subject: [PATCH] Add layout review multi-select controls --- app/templates/documents/detail.html | 34 ++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/templates/documents/detail.html b/app/templates/documents/detail.html index de782ca..d3339f4 100644 --- a/app/templates/documents/detail.html +++ b/app/templates/documents/detail.html @@ -5,6 +5,10 @@ box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.18) !important; font-weight: 800 !important; } + +body.layout-multi-select-active #layout-word-popover { + display: none !important; +} {% extends "base.html" %} @@ -1926,6 +1930,9 @@ document.addEventListener("DOMContentLoaded", () => {
+ + + @@ -2504,6 +2511,8 @@ const HANDLE_SIZE_PX = 14; const page = pages[0]; let words = JSON.parse(JSON.stringify(page.words || [])); let selectedId = null; + let selectedIds = new Set(); + let multiSelectMode = false; let tool = "select"; let zoom = 1; let dragState = null; @@ -3070,7 +3079,7 @@ function refreshSelectionUI(opts = {}) { const y2 = bbox[3] * scaleY; const w = Math.max(1, x2 - x1); const h = Math.max(1, y2 - y1); - const selected = String(word.id) === String(selectedId); + const selected = selectedIds.has(String(word.id)) || String(word.id) === String(selectedId); if (showText) { drawReplicaText(word, bbox, x1, y1, x2, y2, w, h, scaleX, scaleY); @@ -3558,6 +3567,7 @@ function refreshSelectionUI(opts = {}) { if (popoverEl) popoverEl.style.display = "none"; canvas.style.cursor = "default"; refreshSelectionUI({ forceText: true }); + if (multiSelectMode) hidePopover(); renderCanvas(); setStatus("No selection"); return; @@ -3727,6 +3737,28 @@ function refreshSelectionUI(opts = {}) { document.getElementById("layout-nudge-down")?.addEventListener("click", () => nudge(0, 1)); document.getElementById("layout-tool-select")?.addEventListener("click", () => setTool("select")); + document.getElementById("layout-select-all")?.addEventListener("click", () => { + selectedIds = new Set(words.map(w => String(w.id))); + selectedId = Array.from(selectedIds).at(-1) || null; + refreshSelectionUI({ forceText: true }); + if (multiSelectMode) hidePopover(); + renderCanvas(); + setStatus("Selected all: " + selectedIds.size); + }); + document.getElementById("layout-clear-selection")?.addEventListener("click", () => { + selectedIds.clear(); + selectedId = null; + refreshSelectionUI({ forceText: true }); + renderCanvas(); + setStatus("Selection cleared"); + }); + document.getElementById("layout-multi-select")?.addEventListener("click", () => { + multiSelectMode = !multiSelectMode; + document.getElementById("layout-multi-select")?.classList.toggle("active", multiSelectMode); + document.body.classList.toggle("layout-multi-select-active", multiSelectMode); + if (multiSelectMode) hidePopover(); + setStatus(multiSelectMode ? "Multi-select on" : "Multi-select off"); + }); document.getElementById("layout-tool-pan")?.addEventListener("click", () => setTool("pan")); document.getElementById("layout-tool-add")?.addEventListener("click", () => setTool("add"));