reworked zooming

This commit is contained in:
John Lancaster
2026-06-29 18:00:22 -05:00
parent e35a8ec060
commit 67b0980664
@@ -60,16 +60,16 @@ def _register_panzoom_assets() -> None:
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
align-items: center; align-items: flex-start;
justify-content: center; justify-content: flex-start;
} }
.document-panzoom-media { .document-panzoom-media {
width: auto; width: auto;
height: auto; height: auto;
display: block; display: block;
max-width: none; max-width: 100%;
max-height: none; max-height: 100%;
user-select: none; user-select: none;
-webkit-user-drag: none; -webkit-user-drag: none;
} }
@@ -99,79 +99,76 @@ def _attach_panzoom(host_id: str) -> None:
const media = host.querySelector('[data-panzoom-media]'); const media = host.querySelector('[data-panzoom-media]');
if (!target) return; if (!target) return;
const cleanup = () => {{
const existing = window.__transcriptionPanzoom[{host_id!r}];
if (existing?.resizeObserver) existing.resizeObserver.disconnect();
if (existing?.wheelHandler) host.removeEventListener('wheel', existing.wheelHandler);
if (existing?.instance) existing.instance.destroy();
}};
const computeFitScale = () => {{ const computeFitScale = () => {{
const hostRect = host.getBoundingClientRect(); const hostRect = host.getBoundingClientRect();
if (hostRect.width <= 0 || hostRect.height <= 0) return 1; if (hostRect.width <= 0 || hostRect.height <= 0) return null;
if (media && media.tagName === 'IMG') {{
if (media.naturalWidth <= 0 || media.naturalHeight <= 0) return 1;
return Math.min(
hostRect.width / media.naturalWidth,
hostRect.height / media.naturalHeight
);
}}
if (target.tagName === 'IFRAME') {{
const targetRect = target.getBoundingClientRect();
if (targetRect.width <= 0 || targetRect.height <= 0) return 1;
return hostRect.height / targetRect.height;
}}
return 1; return 1;
}}; }};
const initPanzoom = () => {{ const buildInstance = () => {{
if (window.__transcriptionPanzoom[{host_id!r}]) {{ cleanup();
window.__transcriptionPanzoom[{host_id!r}].destroy();
}}
if (host.__transcriptionWheelHandler) {{ const fitScale = computeFitScale();
host.removeEventListener('wheel', host.__transcriptionWheelHandler); if (fitScale === null) return false;
host.__transcriptionWheelHandler = null;
}}
let fitScale = computeFitScale(); const minScale = Math.min(fitScale, 0.01);
if (!Number.isFinite(fitScale) || fitScale <= 0) {{
fitScale = 1;
}}
const startScale = fitScale;
const minScale = 0.01;
const instance = Panzoom(target, {{ const instance = Panzoom(target, {{
maxScale: 32, startX: 0,
startY: 0,
startScale: fitScale,
minScale: minScale, minScale: minScale,
startScale: startScale, maxScale: 256,
step: 0.18, step: 0.2,
contain: 'inside', roundPixels: false,
roundPixels: true, panOnlyWhenZoomed: true,
overflow: 'hidden', overflow: 'hidden',
}}); }});
window.__transcriptionPanzoom[{host_id!r}] = instance; const wheelHandler = (event) => instance.zoomWithWheel(event);
host.addEventListener('wheel', wheelHandler, {{ passive: false }});
requestAnimationFrame(() => {{ requestAnimationFrame(() => {{
instance.reset({{ animate: false, force: true }}); instance.reset({{ animate: false }});
instance.setOptions({{ contain: undefined }});
}}); }});
const wheelHandler = (event) => instance.zoomWithWheel(event); const resizeObserver = new ResizeObserver(() => {{
host.__transcriptionWheelHandler = wheelHandler; const nextFitScale = computeFitScale();
host.addEventListener('wheel', wheelHandler, {{ passive: false }}); if (nextFitScale === null) return;
instance.setOptions({{
startScale: nextFitScale,
minScale: Math.min(nextFitScale, 0.01),
}});
instance.reset({{ animate: false }});
}});
resizeObserver.observe(host);
window.__transcriptionPanzoom[{host_id!r}] = {{
instance,
wheelHandler,
resizeObserver,
}};
return true;
}};
const initWhenReady = (retries = 15) => {{
if (buildInstance()) return;
if (retries <= 0) return;
requestAnimationFrame(() => initWhenReady(retries - 1));
}}; }};
if (media && media.tagName === 'IMG' && !media.complete) {{ if (media && media.tagName === 'IMG' && !media.complete) {{
media.addEventListener('load', initPanzoom, {{ once: true }}); media.addEventListener('load', () => initWhenReady(), {{ once: true }});
return; return;
}} }}
if ((media && media.tagName === 'IMG' && (host.clientWidth <= 0 || host.clientHeight <= 0)) || initWhenReady();
(target.tagName === 'IFRAME' && (host.clientWidth <= 0 || host.clientHeight <= 0))) {{
requestAnimationFrame(initPanzoom);
return;
}}
initPanzoom();
}})(); }})();
""" """
) )
@@ -186,10 +183,11 @@ def render_document_panzoom(*, document: Document, height: str = "640px") -> Non
document_kind = _document_kind(document) document_kind = _document_kind(document)
with ui.card().classes("w-full q-pa-md"): with ui.card().classes("w-full q-pa-md"):
with ui.column().classes("q-gutter-none"): with ui.row().classes("w-full items-center justify-between no-wrap"):
with ui.row().classes("w-full items-center justify-between q-gutter-sm"):
ui.label("Document preview").classes("text-subtitle1 text-weight-medium") ui.label("Document preview").classes("text-subtitle1 text-weight-medium")
ui.label(document.filename).classes("text-caption text-grey-4") ui.label(document.filename).classes("text-caption text-grey-4 ellipsis").style(
"max-width: 60%; text-align: right;"
)
with ( with (
ui.element("div") ui.element("div")