diff --git a/src/Components/ZoomableImage.tsx b/src/Components/ZoomableImage.tsx index c8326c0..49cd35b 100644 --- a/src/Components/ZoomableImage.tsx +++ b/src/Components/ZoomableImage.tsx @@ -331,9 +331,12 @@ const ZoomableImage: Component<{ } } - function ifEnabled(func: (event: T) => void): (event: T) => void { + function ifEnabled( + func: (event: T) => void, + ): (event: T) => void { return (event) => { if (enabled) { + event.stopPropagation(); func(event); } }; @@ -390,13 +393,17 @@ const ZoomableImage: Component<{ }; } - function onClick() { - if (movedDuringClick) { - return; + function handleKeyDown(event: KeyboardEvent) { + if (event.key === "Escape") { + clickDeactivate(); } + } + function onClick() { if (enabled) { - clickDeactivate(); + if (!movedDuringClick) { + clickDeactivate(); + } } else { clickActivate(); } @@ -409,6 +416,7 @@ const ZoomableImage: Component<{ updateNeutralImageDimensions(); window.addEventListener("resize", updateNeutralImageDimensions); + window.addEventListener("keydown", ifEnabled(handleKeyDown)); // When image is in disabled state container is exactly the size of // image and catches propagated events. When enabled container @@ -438,6 +446,7 @@ const ZoomableImage: Component<{ onCleanup(() => { window.removeEventListener("resize", updateNeutralImageDimensions); + window.removeEventListener("keydown", handleKeyDown); }); return ( diff --git a/src/Pages/PhotoDetailed.tsx b/src/Pages/PhotoDetailed.tsx index 9c4094c..af3f492 100644 --- a/src/Pages/PhotoDetailed.tsx +++ b/src/Pages/PhotoDetailed.tsx @@ -8,7 +8,7 @@ import { createEffect, on, } from "solid-js"; -import { useLocation, useParams } from "@solidjs/router"; +import { useLocation, useNavigate, useParams } from "@solidjs/router"; import style from "./PhotoDetailed.module.css"; import getDB from "../Data/Database"; @@ -30,6 +30,7 @@ import Card from "../Components/Card"; import classList from "../Util/Classes"; import Tag from "../Components/Tag"; import Arrays from "../Util/Arrays"; +import swipe from "../Util/Swipe"; const Toolbar: Component<{ selfId: string; @@ -108,10 +109,12 @@ const Tags: Component<{ class: string; tags?: string[] }> = (props) => { const PhotoDetailed: Component = () => { const params = useParams(); + const location = useLocation(); + const navigate = useNavigate(); const searchParams: { search?: string; tags?: string | string[]; - } = useLocation().query; + } = location.query; const [info, { refetch }] = createResource(async () => { const db = await getDB(); @@ -170,6 +173,13 @@ const PhotoDetailed: Component = () => { ), ); + swipe((dir) => { + if (dir === "right" && info()?.prevURL !== undefined) + navigate(`/photo/${info()!.prevURL}${location.search}`); + else if (dir === "left" && info()?.nextURL !== undefined) + navigate(`/photo/${info()!.nextURL}${location.search}`); + }); + return (
void; + +// number: startX of active single-finger gesture +// "scrolled": gesture was invalidated by a scroll +// undefined: no active gesture +type SwipeState = number | "scrolled" | undefined; + +const THRESHOLD = 50; + +function swipe(onSwipe: SwipeHandler) { + let state: SwipeState; + + function onScroll() { + state = "scrolled"; + } + + function onTouchStart(e: TouchEvent) { + if (e.touches.length !== 1) { + state = undefined; + return; + } + state = e.touches[0].clientX; + // detect if scroll happened during swipe + window.addEventListener("scroll", onScroll, { once: true }); + } + + function onTouchEnd(e: TouchEvent) { + window.removeEventListener("scroll", onScroll); + + if (typeof state !== "number") { + return; + } + + const deltaX = e.changedTouches[0].clientX - state; + state = undefined; + + if (deltaX > THRESHOLD) { + onSwipe("right"); + } else if (deltaX < -THRESHOLD) { + onSwipe("left"); + } + } + + document.addEventListener("touchstart", onTouchStart); + document.addEventListener("touchend", onTouchEnd); + + onCleanup(() => { + console.log("onCleanup"); + document.removeEventListener("touchstart", onTouchStart); + document.removeEventListener("touchend", onTouchEnd); + window.removeEventListener("scroll", onScroll); + }); +} + +export default swipe;