Files
readest/apps/readest-app/src/app/reader/hooks/useIframeEvents.ts
T

176 lines
5.3 KiB
TypeScript

import { useEffect } from 'react';
import { useReaderStore } from '@/store/readerStore';
import { useBookDataStore } from '@/store/bookDataStore';
import { debounce } from '@/utils/debounce';
import { ScrollSource } from './usePagination';
export const useMouseEvent = (
bookKey: string,
handlePageFlip: (msg: MessageEvent | React.MouseEvent<HTMLDivElement, MouseEvent>) => void,
handleContinuousScroll: (source: ScrollSource, delta: number, threshold: number) => void,
) => {
const { hoveredBookKey } = useReaderStore();
const debounceScroll = debounce(handleContinuousScroll, 500);
const debounceFlip = debounce(handlePageFlip, 100);
const handleMouseEvent = (msg: MessageEvent | React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (msg instanceof MessageEvent) {
if (msg.data && msg.data.bookKey === bookKey) {
if (msg.data.type === 'iframe-wheel') {
debounceScroll('mouse', -msg.data.deltaY, 0);
}
if (msg.data.type === 'iframe-wheel') {
debounceFlip(msg);
} else {
handlePageFlip(msg);
}
}
} else if (msg.type === 'wheel') {
const event = msg as React.WheelEvent<HTMLDivElement>;
debounceScroll('mouse', -event.deltaY, 0);
} else {
handlePageFlip(msg);
}
};
useEffect(() => {
window.addEventListener('message', handleMouseEvent);
return () => {
window.removeEventListener('message', handleMouseEvent);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bookKey, hoveredBookKey]);
return {
onClick: handlePageFlip,
onWheel: handleMouseEvent,
};
};
interface IframeTouch {
clientX: number;
clientY: number;
screenX: number;
screenY: number;
}
interface IframeTouchEvent {
targetTouches: IframeTouch[];
}
export const useTouchEvent = (
bookKey: string,
handlePageFlip: (msg: CustomEvent) => void,
handleContinuousScroll: (source: ScrollSource, delta: number, threshold: number) => void,
) => {
const { getBookData } = useBookDataStore();
const { hoveredBookKey, setHoveredBookKey, getViewSettings } = useReaderStore();
let touchStart: IframeTouch | null = null;
let touchEnd: IframeTouch | null = null;
const onTouchStart = (e: IframeTouchEvent | React.TouchEvent<HTMLDivElement>) => {
touchEnd = null;
const touch = e.targetTouches[0];
if (!touch) return;
touchStart = touch;
};
const onTouchMove = (e: IframeTouchEvent | React.TouchEvent<HTMLDivElement>) => {
if (!touchStart) return;
const touch = e.targetTouches[0];
if (touch) {
touchEnd = touch;
}
if (hoveredBookKey && touchEnd) {
const viewSettings = getViewSettings(bookKey)!;
const deltaY = touchEnd.screenY - touchStart.screenY;
const deltaX = touchEnd.screenX - touchStart.screenX;
if (!viewSettings!.scrolled && !viewSettings!.vertical) {
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) {
setHoveredBookKey(null);
}
} else {
setHoveredBookKey(null);
}
}
};
const onTouchEnd = (e: IframeTouchEvent | React.TouchEvent<HTMLDivElement>) => {
if (!touchStart) return;
const touch = e.targetTouches[0];
if (touch) {
touchEnd = touch;
}
const windowWidth = window.innerWidth;
if (touchEnd) {
const viewSettings = getViewSettings(bookKey)!;
const bookData = getBookData(bookKey)!;
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, only for horizontal page mode
if (
!viewSettings!.scrolled && // not scrolled
!viewSettings!.vertical && // not vertical
(!bookData.isFixedLayout || viewSettings.zoomLevel <= 100) // for fixed layout, not when zoomed in
) {
setHoveredBookKey(hoveredBookKey ? null : bookKey);
}
} else {
if (hoveredBookKey) {
setHoveredBookKey(null);
}
}
handlePageFlip(
new CustomEvent('touch-swipe', {
detail: {
deltaX,
deltaY,
startX: touchStart.screenX,
startY: touchStart.screenY,
endX: touchEnd.screenX,
endY: touchEnd.screenY,
},
}),
);
handleContinuousScroll('touch', deltaY, 30);
}
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]);
return {
onTouchStart,
onTouchMove,
onTouchEnd,
};
};