forked from akai/readest
176 lines
5.3 KiB
TypeScript
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,
|
|
};
|
|
};
|