Swipe Navigation
This commit is contained in:
parent
3d94a298b8
commit
3074d6dd8d
|
|
@ -0,0 +1,65 @@
|
|||
import type { RefObject } from "preact";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
|
||||
const EDGE_THRESHOLD = 30; // px from screen edge to start a swipe
|
||||
const MIN_SWIPE = 50; // minimum horizontal distance to trigger
|
||||
|
||||
export function useSwipeNavigation(
|
||||
elementRef: RefObject<HTMLElement>,
|
||||
onSwipeLeft: () => void,
|
||||
onSwipeRight: () => void,
|
||||
) {
|
||||
// Keep callbacks in a ref so the effect doesn't re-run when they change
|
||||
const callbacksRef = useRef({ onSwipeLeft, onSwipeRight });
|
||||
callbacksRef.current = { onSwipeLeft, onSwipeRight };
|
||||
|
||||
useEffect(() => {
|
||||
const el = elementRef.current;
|
||||
if (!el) return;
|
||||
|
||||
let start: { x: number; y: number } | null = null;
|
||||
|
||||
const onTouchStart = (e: TouchEvent) => {
|
||||
const t = e.touches[0];
|
||||
const fromEdge = t.clientX < EDGE_THRESHOLD
|
||||
|| t.clientX > window.innerWidth - EDGE_THRESHOLD;
|
||||
start = fromEdge ? { x: t.clientX, y: t.clientY } : null;
|
||||
if (fromEdge) {
|
||||
e.preventDefault(); // Prevent scrolling on swipe
|
||||
}
|
||||
};
|
||||
|
||||
const onTouchMove = (e: TouchEvent) => {
|
||||
if (!start) return;
|
||||
const t = e.touches[0];
|
||||
const dx = Math.abs(t.clientX - start.x);
|
||||
const dy = Math.abs(t.clientY - start.y);
|
||||
// Prevent Chrome's back/forward navigation once we're sure it's horizontal
|
||||
if (dx > dy && dx > 10) e.preventDefault();
|
||||
};
|
||||
|
||||
const onTouchEnd = (e: TouchEvent) => {
|
||||
if (!start) return;
|
||||
const t = e.changedTouches[0];
|
||||
const dx = t.clientX - start.x;
|
||||
const dy = t.clientY - start.y;
|
||||
start = null;
|
||||
|
||||
if (Math.abs(dx) < MIN_SWIPE || Math.abs(dx) < Math.abs(dy)) return;
|
||||
|
||||
e.preventDefault(); // prevent Chrome's back/forward navigation
|
||||
if (dx > 0) callbacksRef.current.onSwipeRight();
|
||||
else callbacksRef.current.onSwipeLeft();
|
||||
};
|
||||
|
||||
el.addEventListener('touchstart', onTouchStart, { passive: false });
|
||||
el.addEventListener('touchmove', onTouchMove, { passive: false });
|
||||
el.addEventListener('touchend', onTouchEnd, { passive: false });
|
||||
|
||||
return () => {
|
||||
el.removeEventListener('touchstart', onTouchStart);
|
||||
el.removeEventListener('touchmove', onTouchMove);
|
||||
el.removeEventListener('touchend', onTouchEnd);
|
||||
};
|
||||
}, [elementRef]);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { highlight } from "@common/highlight";
|
||||
import { useMediaQuery } from "@common/hooks/useMediaQuery";
|
||||
import { useSwipeNavigation } from "@common/hooks/useSwipeNavigation";
|
||||
import clsx from "clsx";
|
||||
import { BookMarked, BookOpen, BrainCircuit, Code, FileText, Globe, Layers, List, MapPin, MessageSquare, MessagesSquare, Users, type LucideIcon } from "lucide-preact";
|
||||
import { useEffect, useMemo, useRef } from "preact/hooks";
|
||||
|
|
@ -62,6 +63,7 @@ export const Editor = () => {
|
|||
};
|
||||
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const promptPreview = useMemo(() => {
|
||||
const text = Prompt.formatSystemPrompt(appState);
|
||||
|
|
@ -93,6 +95,20 @@ export const Editor = () => {
|
|||
|
||||
const isMobile = useMediaQuery(`(max-width: ${MOBILE_BREAKPOINT}px)`);
|
||||
|
||||
useSwipeNavigation(
|
||||
editorRef,
|
||||
() => { // swipe left → next tab
|
||||
const idx = tabs.findIndex(t => t.id === currentTab);
|
||||
const next = tabs[idx + 1];
|
||||
if (next) dispatch({ type: 'SET_CURRENT_TAB', tab: next.id });
|
||||
},
|
||||
() => { // swipe right → prev tab
|
||||
const idx = tabs.findIndex(t => t.id === currentTab);
|
||||
const prev = tabs[idx - 1];
|
||||
if (prev) dispatch({ type: 'SET_CURRENT_TAB', tab: prev.id });
|
||||
},
|
||||
);
|
||||
|
||||
const hasSelection = currentWorld !== null;
|
||||
const isChatOnly = currentWorld?.chatOnly ?? false;
|
||||
|
||||
|
|
@ -117,7 +133,7 @@ export const Editor = () => {
|
|||
: null;
|
||||
|
||||
return (
|
||||
<div class={styles.editor}>
|
||||
<div class={styles.editor} ref={editorRef}>
|
||||
{titleBar}
|
||||
<div class={clsx(
|
||||
styles.content,
|
||||
|
|
|
|||
Loading…
Reference in New Issue