216 lines
4.7 KiB
TypeScript
216 lines
4.7 KiB
TypeScript
import { settings, type ZoomModes } from '$lib/settings';
|
|
import panzoom from 'panzoom';
|
|
import type { PanZoom } from 'panzoom';
|
|
import { get, writable } from 'svelte/store';
|
|
|
|
let pz: PanZoom | undefined;
|
|
let container: HTMLElement | undefined;
|
|
|
|
export const panzoomStore = writable<PanZoom | undefined>(undefined);
|
|
|
|
export function initPanzoom(node: HTMLElement) {
|
|
container = node;
|
|
pz = panzoom(node, {
|
|
bounds: false,
|
|
maxZoom: 10,
|
|
minZoom: 0.1,
|
|
zoomDoubleClickSpeed: 1,
|
|
enableTextSelection: true,
|
|
beforeMouseDown: (e) => {
|
|
const nodeName = (e.target as HTMLElement).nodeName;
|
|
return nodeName === 'P';
|
|
},
|
|
beforeWheel: (e) => e.altKey,
|
|
onTouch: (e) => e.touches.length > 1,
|
|
// Panzoom typing is wrong here
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-expect-error
|
|
filterKey: (e: any) => {
|
|
if (
|
|
e.key === 'ArrowLeft' ||
|
|
e.key === 'ArrowRight' ||
|
|
e.key === 'ArrowUp' ||
|
|
e.key === 'ArrowDown'
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
});
|
|
|
|
panzoomStore.set(pz);
|
|
|
|
|
|
pz.on('pan', () => keepInBounds())
|
|
pz.on('zoom', () => keepInBounds())
|
|
}
|
|
|
|
type PanX = 'left' | 'center' | 'right';
|
|
type PanY = 'top' | 'center' | 'bottom';
|
|
|
|
export function panAlign(alignX: PanX, alignY: PanY) {
|
|
if (!pz || !container) {
|
|
return;
|
|
}
|
|
|
|
const { scale } = pz.getTransform();
|
|
const { innerWidth, innerHeight } = window;
|
|
const { offsetWidth, offsetHeight } = container;
|
|
|
|
let x = 0;
|
|
let y = 0;
|
|
|
|
switch (alignX) {
|
|
case 'left':
|
|
x = 0;
|
|
break;
|
|
case 'center':
|
|
x = (innerWidth - offsetWidth * scale) / 2;
|
|
break;
|
|
case 'right':
|
|
x = innerWidth - offsetWidth * scale;
|
|
break;
|
|
}
|
|
|
|
switch (alignY) {
|
|
case 'top':
|
|
y = 0;
|
|
break;
|
|
case 'center':
|
|
y = (innerHeight - offsetHeight * scale) / 2;
|
|
break;
|
|
case 'bottom':
|
|
y = innerHeight - offsetHeight * scale;
|
|
break;
|
|
}
|
|
|
|
pz.pause();
|
|
pz.moveTo(x, y);
|
|
pz.resume();
|
|
}
|
|
|
|
export function zoomOriginal() {
|
|
pz?.moveTo(0, 0);
|
|
pz?.zoomTo(0, 0, 1 / pz.getTransform().scale);
|
|
panAlign('center', 'center');
|
|
}
|
|
|
|
export function zoomFitToWidth() {
|
|
if (!pz || !container) {
|
|
return;
|
|
}
|
|
const { innerWidth } = window;
|
|
|
|
const scale = (1 / pz.getTransform().scale) * (innerWidth / container.offsetWidth);
|
|
|
|
pz.moveTo(0, 0);
|
|
pz.zoomTo(0, 0, scale);
|
|
panAlign('center', 'top');
|
|
}
|
|
|
|
export function zoomFitToScreen() {
|
|
if (!pz || !container) {
|
|
return;
|
|
}
|
|
const { innerWidth, innerHeight } = window;
|
|
const scaleX = innerWidth / container.offsetWidth;
|
|
const scaleY = innerHeight / container.offsetHeight;
|
|
const scale = (1 / pz.getTransform().scale) * Math.min(scaleX, scaleY);
|
|
pz.moveTo(0, 0);
|
|
pz.zoomTo(0, 0, scale);
|
|
panAlign('center', 'center');
|
|
}
|
|
|
|
export function keepZoomStart() {
|
|
panAlign('center', 'top');
|
|
}
|
|
|
|
export function zoomDefault() {
|
|
const zoomDefault = get(settings).zoomDefault;
|
|
switch (zoomDefault) {
|
|
case 'zoomFitToScreen':
|
|
zoomFitToScreen();
|
|
return;
|
|
case 'zoomFitToWidth':
|
|
zoomFitToWidth();
|
|
return;
|
|
case 'zoomOriginal':
|
|
zoomOriginal();
|
|
return;
|
|
case 'keepZoomStart':
|
|
keepZoomStart();
|
|
return;
|
|
}
|
|
}
|
|
|
|
export function keepInBounds() {
|
|
if (!pz || !container) {
|
|
return
|
|
}
|
|
|
|
const { mobile, bounds } = get(settings)
|
|
|
|
if (!mobile && !bounds) {
|
|
return
|
|
}
|
|
|
|
const transform = pz.getTransform();
|
|
|
|
const { x, y, scale } = transform;
|
|
const { innerWidth, innerHeight } = window
|
|
|
|
const width = container.offsetWidth * scale;
|
|
const height = container.offsetHeight * scale;
|
|
|
|
const marginX = innerWidth * 0.001;
|
|
const marginY = innerHeight * 0.01;
|
|
|
|
let minX = innerWidth - width - marginX;
|
|
let maxX = marginX;
|
|
let minY = innerHeight - height - marginY;
|
|
let maxY = marginY;
|
|
|
|
let forceCenterY = false;
|
|
|
|
if (width + 2 * marginX <= innerWidth) {
|
|
minX = marginX;
|
|
maxX = innerWidth - width - marginX;
|
|
} else {
|
|
minX = innerWidth - width - marginX;
|
|
maxX = marginX;
|
|
}
|
|
|
|
if (height + 2 * marginY <= innerHeight) {
|
|
minY = marginY;
|
|
maxY = innerHeight - height - marginY;
|
|
forceCenterY = true;
|
|
} else {
|
|
minY = innerHeight - height - marginY;
|
|
maxY = marginY;
|
|
}
|
|
|
|
if (x < minX) {
|
|
transform.x = minX;
|
|
}
|
|
if (x > maxX) {
|
|
transform.x = maxX;
|
|
}
|
|
|
|
if (forceCenterY) {
|
|
transform.y = innerHeight / 2 - height / 2;
|
|
} else {
|
|
if (y < minY) {
|
|
transform.y = minY;
|
|
}
|
|
if (y > maxY) {
|
|
transform.y = maxY;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function toggleFullScreen() {
|
|
if (!document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen();
|
|
} else if (document.exitFullscreen) {
|
|
document.exitFullscreen();
|
|
}
|
|
} |