Add magnifier, interaction mode toggle, and zoom percentage display

Magnifier (fixed-layout.js):
- Rewrite magnifier to use CSS background-image for comics (blob URLs
  from iframe img elements) and canvas drawImage for PDFs. Replaces
  broken iframe.cloneNode approach that created empty sandboxed clones.
- Detect which iframe is under the cursor by checking bounding rects,
  so magnifier works on both left and right panes.
- Forward mousemove events from iframe documents to handleMagnifierMove
  so the magnifier follows the cursor inside iframes.
- Add background-repeat: no-repeat to lens styles.
- Add zoomMagnifierEnabled getter for external state checks.

Interaction mode toggle (PDF only):
- Add 'interaction-mode' observed attribute with 'select'/'pan' values.
- In select mode (default for PDFs): mouse events pass through for text
  selection; Shift+drag enables panning as a fallback.
- In pan mode: drag-to-pan works as normal.
- Comic books always use pan mode (no text to select).
- Auto-detect PDF via #isPDF flag set in #showSpread from onZoom presence.
- Add isPDF getter so reader.js can show/hide the toggle button.
- Add toggle button in reader.html, hidden by default, shown for PDFs
  via the view 'load' event.
- Escape key dismisses the magnifier.

Zoom percentage display (reader.js):
- Dispatch 'zoom' CustomEvent from #zoomByRatio with the new scale.
- Listener in reader.js updates the percentage display on scroll zoom.

Also includes minor formatting (line wrapping) in #goLeft/#goRight.
This commit is contained in:
2026-04-18 00:39:15 -04:00
parent 3173cf038d
commit d5b2e3deda
3 changed files with 171 additions and 19 deletions
+28
View File
@@ -221,6 +221,9 @@ class Reader {
$("#magnifier-toggle").addEventListener("click", () => {
console.log("[Reader Debug] 🔍 Magnifier toggle button clicked");
const renderer = this.view.renderer;
renderer.addEventListener("zoom", (e) => {
$("#zoom-reset").textContent = `${Math.round(e.detail.scale * 100)}%`;
});
console.log(
"[Reader Debug] Is FixedLayout?",
renderer instanceof FixedLayout,
@@ -232,6 +235,26 @@ class Reader {
console.log("[Reader Debug] ❌ Not a FixedLayout renderer, ignoring");
}
});
const modeToggle = $("#interaction-mode-toggle");
modeToggle.addEventListener("click", () => {
const renderer = this.view.renderer;
if (!(renderer instanceof FixedLayout)) return;
const current = renderer.getAttribute("interaction-mode") || "select";
const next = current === "select" ? "pan" : "select";
renderer.setAttribute("interaction-mode", next);
modeToggle.title = next === "select" ? "Select Mode" : "Pan Mode";
const svg = modeToggle.querySelector("svg");
svg.innerHTML =
next === "select"
? '<path d="M 5 3 v 12 M 5 15 l -2 2 M 5 15 l 2 2 M 5 3 l 3 3" />'
: '<path d="M 5 9 h 10 M 10 5 v 10" />';
});
this.view.addEventListener("load", () => {
const renderer = this.view.renderer;
if (renderer instanceof FixedLayout && renderer.isPDF) {
modeToggle.style.display = "";
}
});
console.log("[Reader Debug] ✅ Zoom control listeners configured");
const title = formatLanguageMap(book.metadata?.title) || "Untitled Book";
@@ -328,6 +351,11 @@ class Reader {
$("#zoom-reset").textContent = "100%";
console.log("[Reader Debug] ✅ Keyboard zoom reset");
}
} else if (k === "Escape") {
const renderer = this.view.renderer;
if (renderer instanceof FixedLayout && renderer.zoomMagnifierEnabled) {
renderer.toggleMagnifier();
}
}
}
#onLoad({ detail: { doc } }) {