2022-01-28 20:59:39 +01:00

226 lines
6.6 KiB
TypeScript

import { RefObject } from 'react';
import React, { useEffect, useRef } from '../../../../lib/teact/teact';
import { getDispatch } from '../../../../lib/teact/teactn';
import { IS_ANDROID, IS_TOUCH_ENV } from '../../../../util/environment';
import windowSize from '../../../../util/windowSize';
import { captureEvents, SwipeDirection } from '../../../../util/captureEvents';
import useFlag from '../../../../hooks/useFlag';
import { preventMessageInputBlur } from '../../helpers/preventMessageInputBlur';
import stopEvent from '../../../../util/stopEvent';
import { REM } from '../../../common/helpers/mediaDimensions';
const ANDROID_KEYBOARD_HIDE_DELAY_MS = 350;
const SWIPE_ANIMATION_DURATION = 150;
const QUICK_REACTION_DOUBLE_TAP_DELAY = 200;
const QUICK_REACTION_AREA_WIDTH = 3 * REM;
const QUICK_REACTION_AREA_HEIGHT = Number(REM);
const GROUP_MESSAGE_HOVER_ATTRIBUTE = 'data-is-document-group-hover';
export default function useOuterHandlers(
selectMessage: (e?: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => void,
containerRef: RefObject<HTMLDivElement>,
messageId: number,
isAlbum: boolean,
isInSelectMode: boolean,
canReply: boolean,
isProtected: boolean,
onContextMenu: (e: React.MouseEvent) => void,
handleBeforeContextMenu: (e: React.MouseEvent) => void,
chatId: string,
isContextMenuShown: boolean,
contentRef: RefObject<HTMLDivElement>,
isOwn: boolean,
shouldHandleMouseLeave: boolean,
) {
const { setReplyingToId, sendDefaultReaction } = getDispatch();
const [isQuickReactionVisible, markQuickReactionVisible, unmarkQuickReactionVisible] = useFlag();
const [isSwiped, markSwiped, unmarkSwiped] = useFlag();
const doubleTapTimeoutRef = useRef<NodeJS.Timeout>();
function handleMouseDown(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
preventMessageInputBlur(e);
handleBeforeContextMenu(e);
}
function handleMouseMove(e: React.MouseEvent) {
const container = contentRef.current;
if (!container) return;
const { clientX, clientY } = e;
const {
x, width, y, height,
} = container.getBoundingClientRect();
const isVisibleX = Math.abs((isOwn ? (clientX - x) : (x + width - clientX))) < QUICK_REACTION_AREA_WIDTH;
const isVisibleY = Math.abs(y + height - clientY) < QUICK_REACTION_AREA_HEIGHT;
if (isVisibleX && isVisibleY) {
markQuickReactionVisible();
} else {
unmarkQuickReactionVisible();
}
}
function handleSendQuickReaction(e: React.MouseEvent) {
const { x, y } = e.currentTarget.getBoundingClientRect();
sendDefaultReaction({
chatId,
messageId,
x,
y,
});
}
function handleTap(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
if (IS_ANDROID) {
const target = e.target as HTMLDivElement;
if (!target.classList.contains('text-content') && !target.classList.contains('Message')) {
return;
}
if (windowSize.getIsKeyboardVisible()) {
setTimeout(() => {
onContextMenu(e);
}, ANDROID_KEYBOARD_HIDE_DELAY_MS);
} else {
onContextMenu(e);
}
}
}
function handleDoubleTap(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
const { pageX: x, pageY: y } = e;
sendDefaultReaction({
chatId,
messageId,
x,
y,
});
}
function handleClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
if (isInSelectMode) {
selectMessage(e);
return;
}
if (!IS_TOUCH_ENV) return;
if (doubleTapTimeoutRef.current) {
clearInterval(doubleTapTimeoutRef.current);
doubleTapTimeoutRef.current = undefined;
handleDoubleTap(e);
return;
}
doubleTapTimeoutRef.current = setTimeout(() => {
doubleTapTimeoutRef.current = undefined;
handleTap(e);
}, QUICK_REACTION_DOUBLE_TAP_DELAY);
}
function handleContextMenu(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
if (IS_ANDROID) {
if ((e.target as HTMLElement).matches('a[href]') || isContextMenuShown) {
return;
}
e.preventDefault();
selectMessage();
} else {
onContextMenu(e);
}
}
function handleContainerDoubleClick() {
if (IS_TOUCH_ENV) return;
setReplyingToId({ messageId });
}
function stopPropagation(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
e.stopPropagation();
}
useEffect(() => {
if (!IS_TOUCH_ENV || isInSelectMode || !canReply || isContextMenuShown) {
return undefined;
}
let startedAt: number | undefined;
return captureEvents(containerRef.current!, {
selectorToPreventScroll: '.MessageList',
onSwipe: ((e, direction) => {
if (direction === SwipeDirection.Left) {
if (!startedAt) {
startedAt = Date.now();
}
markSwiped();
return true;
}
return false;
}),
onRelease: () => {
if (!startedAt) {
return;
}
setReplyingToId({ messageId });
setTimeout(unmarkSwiped, Math.max(0, SWIPE_ANIMATION_DURATION - (Date.now() - startedAt)));
startedAt = undefined;
},
});
}, [
containerRef, isInSelectMode, messageId, setReplyingToId, markSwiped, unmarkSwiped, canReply, isContextMenuShown,
]);
function handleMouseLeave(e: React.MouseEvent<HTMLDivElement>) {
unmarkQuickReactionVisible();
if (shouldHandleMouseLeave) handleDocumentGroupMouseLeave(e);
}
return {
handleMouseDown: !isInSelectMode ? handleMouseDown : undefined,
handleClick,
handleContextMenu: !isInSelectMode ? handleContextMenu : (isProtected ? stopEvent : undefined),
handleDoubleClick: !isInSelectMode ? handleContainerDoubleClick : undefined,
handleContentDoubleClick: !IS_TOUCH_ENV ? stopPropagation : undefined,
handleMouseMove,
handleSendQuickReaction,
handleMouseLeave,
isSwiped,
isQuickReactionVisible,
handleDocumentGroupMouseEnter,
};
}
function handleDocumentGroupMouseEnter(e: React.MouseEvent<HTMLDivElement>) {
const lastGroupElement = getLastElementInDocumentGroup(e.currentTarget);
if (lastGroupElement) {
lastGroupElement.setAttribute(GROUP_MESSAGE_HOVER_ATTRIBUTE, '');
}
}
function handleDocumentGroupMouseLeave(e: React.MouseEvent<HTMLDivElement>) {
const lastGroupElement = getLastElementInDocumentGroup(e.currentTarget);
if (lastGroupElement) {
lastGroupElement.removeAttribute(GROUP_MESSAGE_HOVER_ATTRIBUTE);
}
}
function getLastElementInDocumentGroup(element: Element) {
let current: Element | null = element;
do {
current = current.nextElementSibling;
} while (current && !current.classList.contains('last-in-document-group'));
return current;
}