diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index ce1688c8..454eebfd 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -4,7 +4,7 @@ import { BookConfig } from '@/types/book'; import { FoliateView, wrappedFoliateView } from '@/types/view'; import { useReaderStore } from '@/store/readerStore'; import { useParallelViewStore } from '@/store/parallelViewStore'; -import { useClickEvent } from '../hooks/useClickEvent'; +import { useClickEvent, useTouchEvent } from '../hooks/useClickEvent'; import { useFoliateEvents } from '../hooks/useFoliateEvents'; import { useProgressSync } from '../hooks/useProgressSync'; import { useAutoHideScrollbar } from '../hooks/useAutoHideScrollbar'; @@ -17,6 +17,9 @@ import { handleMouseup, handleClick, handleWheel, + handleTouchStart, + handleTouchMove, + handleTouchEnd, } from '../utils/iframeEventHandlers'; const FoliateViewer: React.FC<{ @@ -42,7 +45,6 @@ const FoliateViewer: React.FC<{ const progressRelocateHandler = (event: Event) => { const detail = (event as CustomEvent).detail; - // console.log('relocate:', detail); setProgress(bookKey, detail.cfi, detail.tocItem, detail.section, detail.location, detail.range); }; @@ -68,6 +70,9 @@ const FoliateViewer: React.FC<{ detail.doc.addEventListener('mouseup', handleMouseup.bind(null, bookKey)); detail.doc.addEventListener('click', handleClick.bind(null, bookKey)); detail.doc.addEventListener('wheel', handleWheel.bind(null, bookKey)); + detail.doc.addEventListener('touchstart', handleTouchStart.bind(null, bookKey)); + detail.doc.addEventListener('touchmove', handleTouchMove.bind(null, bookKey)); + detail.doc.addEventListener('touchend', handleTouchEnd.bind(null, bookKey)); } } }; @@ -88,6 +93,7 @@ const FoliateViewer: React.FC<{ } }; + useTouchEvent(bookKey, viewRef); const { handleTurnPage } = useClickEvent(bookKey, viewRef, containerRef); useFoliateEvents(viewRef.current, { onLoad: docLoadHandler, diff --git a/apps/readest-app/src/app/reader/hooks/useClickEvent.ts b/apps/readest-app/src/app/reader/hooks/useClickEvent.ts index a1931202..7acd0235 100644 --- a/apps/readest-app/src/app/reader/hooks/useClickEvent.ts +++ b/apps/readest-app/src/app/reader/hooks/useClickEvent.ts @@ -31,7 +31,7 @@ export const useClickEvent = ( const centerEndX = rect.left + rect.width * 0.625; if (screenX >= centerStartX && screenX <= centerEndX) { // toggle visibility of the header bar and the footer bar - setHoveredBookKey(hoveredBookKey ? '' : bookKey); + setHoveredBookKey(hoveredBookKey ? null : bookKey); } else if (screenX >= rect.left + rect.width / 2) { viewRef.current?.goRight(); } else if (screenX < rect.left + rect.width / 2) { @@ -73,3 +73,82 @@ export const useClickEvent = ( handleTurnPage, }; }; + +interface IframeTouch { + clientX: number; + clientY: number; + screenX: number; + screenY: number; +} + +interface IframeTouchEvent { + targetTouches: IframeTouch[]; +} + +export const useTouchEvent = ( + bookKey: string, + viewRef: React.MutableRefObject, +) => { + const { hoveredBookKey, setHoveredBookKey } = useReaderStore(); + + let touchStart: IframeTouch | null = null; + let touchEnd: IframeTouch | null = null; + + const onTouchStart = (e: IframeTouchEvent) => { + touchEnd = null; + const touch = e.targetTouches[0]; + if (!touch) return; + touchStart = touch; + }; + + const onTouchMove = (e: IframeTouchEvent) => { + if (!touchStart) return; + const touch = e.targetTouches[0]; + if (touch) { + touchEnd = touch; + } + }; + + const onTouchEnd = (e: IframeTouchEvent) => { + if (!touchStart) return; + + const touch = e.targetTouches[0]; + if (touch) { + touchEnd = touch; + }; + + const windowWidth = window.innerWidth; + if (touchEnd) { + const deltaY = touchEnd.screenY - touchStart.screenY; + const deltaX = touchEnd.screenX - touchStart.screenX; + // also check for deltaX to prevent swipe page turn from triggering the toggle + if (deltaY < -10 && Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaX) < windowWidth * 0.3) { + // swipe up to toggle the header bar and the footer bar + setHoveredBookKey(hoveredBookKey ? null : bookKey); + } + } + + touchStart = null; + touchEnd = null; + }; + + const handleTouch = (msg: MessageEvent) => { + if (msg.data && msg.data.bookKey === bookKey) { + if (msg.data.type === 'iframe-touchstart') { + onTouchStart(msg.data); + } else if (msg.data.type === 'iframe-touchmove') { + onTouchMove(msg.data); + } else if (msg.data.type === 'iframe-touchend') { + onTouchEnd(msg.data); + } + } + }; + + useEffect(() => { + window.addEventListener('message', handleTouch); + return () => { + window.removeEventListener('message', handleTouch); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hoveredBookKey, viewRef]); +} diff --git a/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts b/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts index 4bd672bb..b3852144 100644 --- a/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts +++ b/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts @@ -130,3 +130,36 @@ export const handleClick = (bookKey: string, event: MouseEvent) => { } }, doubleClickThreshold); }; + +const handleTouchEv = (bookKey: string, event: TouchEvent, type: string) => { + const touch = event.targetTouches[0]; + const touches = []; + if (touch) { + touches.push({ + clientX: touch.clientX, + clientY: touch.clientY, + screenX: touch.screenX, + screenY: touch.screenY, + }); + } + window.postMessage( + { + type: type, + bookKey, + targetTouches: touches, + }, + '*', + ); +} + +export const handleTouchStart = (bookKey: string, event: TouchEvent) => { + handleTouchEv(bookKey, event, 'iframe-touchstart'); +}; + +export const handleTouchMove = (bookKey: string, event: TouchEvent) => { + handleTouchEv(bookKey, event, 'iframe-touchmove'); +}; + +export const handleTouchEnd = (bookKey: string, event: TouchEvent) => { + handleTouchEv(bookKey, event, 'iframe-touchend'); +}; diff --git a/apps/readest-app/src/store/readerStore.ts b/apps/readest-app/src/store/readerStore.ts index b06db3a0..07d00abc 100644 --- a/apps/readest-app/src/store/readerStore.ts +++ b/apps/readest-app/src/store/readerStore.ts @@ -30,7 +30,7 @@ interface ReaderStore { bookKeys: string[]; hoveredBookKey: string | null; setBookKeys: (keys: string[]) => void; - setHoveredBookKey: (key: string) => void; + setHoveredBookKey: (key: string | null) => void; setBookmarkRibbonVisibility: (key: string, visible: boolean) => void; setProgress: ( @@ -59,7 +59,7 @@ export const useReaderStore = create((set, get) => ({ bookKeys: [], hoveredBookKey: null, setBookKeys: (keys: string[]) => set({ bookKeys: keys }), - setHoveredBookKey: (key: string) => set({ hoveredBookKey: key }), + setHoveredBookKey: (key: string | null) => set({ hoveredBookKey: key }), getView: (key: string | null) => (key && get().viewStates[key]?.view) || null, setView: (key: string, view) =>