Files
readest/apps/readest-app/src/app/reader/utils/iframeEventHandlers.ts
T

270 lines
6.2 KiB
TypeScript

import { DOUBLE_CLICK_INTERVAL_THRESHOLD_MS, LONG_HOLD_THRESHOLD } from '@/services/constants';
import { eventDispatcher } from '@/utils/event';
let lastClickTime = 0;
let longHoldTimeout: ReturnType<typeof setTimeout> | null = null;
let keyboardState = {
key: '',
code: '',
ctrlKey: false,
shiftKey: false,
altKey: false,
metaKey: false,
};
const getKeyStatus = (event?: MouseEvent | WheelEvent | TouchEvent) => {
if (event && 'ctrlKey' in event) {
return {
key: keyboardState.key,
code: keyboardState.code,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey,
};
}
return {
...keyboardState,
};
};
export const handleKeydown = (bookKey: string, event: KeyboardEvent) => {
keyboardState = {
key: event.key,
code: event.code,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey,
};
if (['Backspace'].includes(event.key)) {
event.preventDefault();
}
if (event.ctrlKey && event.key.toLowerCase() === 'f') {
event.preventDefault();
}
window.postMessage(
{
type: 'iframe-keydown',
bookKey,
key: event.key,
code: event.code,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey,
},
'*',
);
};
export const handleKeyup = (bookKey: string, event: KeyboardEvent) => {
keyboardState = {
key: '',
code: '',
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey,
};
window.postMessage(
{
type: 'iframe-keyup',
bookKey,
key: event.key,
code: event.code,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey,
},
'*',
);
};
export const handleMousedown = (bookKey: string, event: MouseEvent) => {
longHoldTimeout = setTimeout(() => {
longHoldTimeout = null;
}, LONG_HOLD_THRESHOLD);
window.postMessage(
{
type: 'iframe-mousedown',
bookKey,
button: event.button,
screenX: event.screenX,
screenY: event.screenY,
clientX: event.clientX,
clientY: event.clientY,
offsetX: event.offsetX,
offsetY: event.offsetY,
...getKeyStatus(event),
},
'*',
);
};
export const handleMouseup = (bookKey: string, event: MouseEvent) => {
// we will handle mouse back and forward buttons ourselves
if ([3, 4].includes(event.button)) {
event.preventDefault();
}
window.postMessage(
{
type: 'iframe-mouseup',
bookKey,
button: event.button,
screenX: event.screenX,
screenY: event.screenY,
clientX: event.clientX,
clientY: event.clientY,
offsetX: event.offsetX,
offsetY: event.offsetY,
...getKeyStatus(event),
},
'*',
);
};
export const handleWheel = (bookKey: string, event: WheelEvent) => {
window.postMessage(
{
type: 'iframe-wheel',
bookKey,
deltaMode: event.deltaMode,
deltaX: event.deltaX,
deltaY: event.deltaY,
deltaZ: event.deltaZ,
screenX: event.screenX,
screenY: event.screenY,
clientX: event.clientX,
clientY: event.clientY,
offsetX: event.offsetX,
offsetY: event.offsetY,
...getKeyStatus(event),
},
'*',
);
};
export const handleClick = (
bookKey: string,
doubleClickDisabled: React.MutableRefObject<boolean>,
event: MouseEvent,
) => {
const now = Date.now();
if (!doubleClickDisabled.current && now - lastClickTime < DOUBLE_CLICK_INTERVAL_THRESHOLD_MS) {
lastClickTime = now;
window.postMessage(
{
type: 'iframe-double-click',
bookKey,
screenX: event.screenX,
screenY: event.screenY,
clientX: event.clientX,
clientY: event.clientY,
offsetX: event.offsetX,
offsetY: event.offsetY,
...getKeyStatus(event),
},
'*',
);
return;
}
lastClickTime = now;
const postSingleClick = () => {
const element = event.target as HTMLElement | null;
if (
element?.closest('sup, a, audio, video') &&
!element?.closest('a.duokan-footnote:not([href])')
) {
return;
}
const footnote = element?.closest('.js_readerFooterNote, .zhangyue-footnote, .duokan-footnote');
if (footnote) {
eventDispatcher.dispatch('footnote-popup', {
bookKey,
element: footnote,
footnote:
footnote.getAttribute('data-wr-footernote') ||
footnote.getAttribute('zy-footnote') ||
footnote.getAttribute('alt') ||
element?.getAttribute('alt') ||
'',
});
return;
}
// if long hold is detected, we don't want to send single click event
if (!longHoldTimeout) {
return;
}
window.postMessage(
{
type: 'iframe-single-click',
bookKey,
screenX: event.screenX,
screenY: event.screenY,
clientX: event.clientX,
clientY: event.clientY,
offsetX: event.offsetX,
offsetY: event.offsetY,
...getKeyStatus(event),
},
'*',
);
};
if (!doubleClickDisabled.current) {
setTimeout(() => {
if (Date.now() - lastClickTime >= DOUBLE_CLICK_INTERVAL_THRESHOLD_MS) {
postSingleClick();
}
}, DOUBLE_CLICK_INTERVAL_THRESHOLD_MS);
} else {
postSingleClick();
}
};
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,
timeStamp: Date.now(),
targetTouches: touches,
...getKeyStatus(event),
},
'*',
);
};
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');
};