import { createEffect, Show, type JSX } from "solid-js"; import texts from "../../i18n"; import { READER_BUTTON_CLASS } from "./Toolbar"; const MIN_SCALE_PERCENT = 10; const MAX_SCALE_PERCENT = 500; export type ViewportCanvasCallbacks = { onApply: () => void; onApplyAll: () => void; onClose: () => void; onFit: () => void; onOneToOne: () => void; onScaleChange: (scale: number) => void; }; export function ViewportCanvas(props: { adjusting: boolean; callbacks: ViewportCanvasCallbacks; children: JSX.Element; scaleMode: "custom" | "fit" | "one-to-one"; scalePercent: number | null; }) { const pointers = new Map(); let pinchStart: { distance: number; scale: number } | null = null; let interactionLayer!: HTMLDivElement; const sliderPercent = () => Math.min( MAX_SCALE_PERCENT, Math.max(MIN_SCALE_PERCENT, props.scalePercent ?? 100), ); const clampScale = (scale: number) => Math.min( MAX_SCALE_PERCENT / 100, Math.max(MIN_SCALE_PERCENT / 100, scale), ); const stopInteraction = (event: Event) => { event.preventDefault(); event.stopPropagation(); }; const endPointer = (event: PointerEvent) => { pointers.delete(event.pointerId); if (pointers.size < 2) { pinchStart = null; } }; createEffect(() => { if (!props.adjusting) { pointers.clear(); pinchStart = null; } }); return (
{props.children}
{ stopInteraction(event); const deltaPixels = event.deltaY * ( event.deltaMode === WheelEvent.DOM_DELTA_LINE ? 16 : event.deltaMode === WheelEvent.DOM_DELTA_PAGE ? interactionLayer.clientHeight : 1 ); props.callbacks.onScaleChange(clampScale( (props.scalePercent ?? 100) / 100 * Math.exp(-deltaPixels * 0.0015), )); }} onPointerDown={(event: PointerEvent) => { stopInteraction(event); pointers.set(event.pointerId, { x: event.clientX, y: event.clientY }); interactionLayer.setPointerCapture(event.pointerId); if (pointers.size !== 2) { return; } const [first, second] = Array.from(pointers.values()); if (!first || !second) { return; } pinchStart = { distance: Math.max(1, Math.hypot(second.x - first.x, second.y - first.y)), scale: (props.scalePercent ?? 100) / 100, }; }} onPointerMove={(event: PointerEvent) => { if (!pointers.has(event.pointerId)) { return; } pointers.set(event.pointerId, { x: event.clientX, y: event.clientY }); if (!pinchStart || pointers.size < 2) { return; } const [first, second] = Array.from(pointers.values()); if (!first || !second) { return; } const distance = Math.hypot(second.x - first.x, second.y - first.y); props.callbacks.onScaleChange(clampScale(pinchStart.scale * distance / pinchStart.distance)); }} onPointerUp={endPointer} onPointerCancel={endPointer} />
); }