[Refactoring] Introduce useLastCallback
This commit is contained in:
parent
df70051763
commit
b03959e847
@ -1,10 +1,10 @@
|
|||||||
import React, {
|
import React, { memo, useState } from '../../lib/teact/teact';
|
||||||
memo, useCallback, useState,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import type { OwnProps as AnimatedStickerProps } from './AnimatedSticker';
|
import type { OwnProps as AnimatedStickerProps } from './AnimatedSticker';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
|
||||||
@ -31,20 +31,20 @@ function AnimatedIcon(props: OwnProps) {
|
|||||||
const [isAnimationLoaded, markAnimationLoaded] = useFlag(false);
|
const [isAnimationLoaded, markAnimationLoaded] = useFlag(false);
|
||||||
const transitionClassNames = useMediaTransition(noTransition || isAnimationLoaded);
|
const transitionClassNames = useMediaTransition(noTransition || isAnimationLoaded);
|
||||||
|
|
||||||
const handleLoad = useCallback(() => {
|
const handleLoad = useLastCallback(() => {
|
||||||
markAnimationLoaded();
|
markAnimationLoaded();
|
||||||
onLoad?.();
|
onLoad?.();
|
||||||
}, [markAnimationLoaded, onLoad]);
|
});
|
||||||
|
|
||||||
const [playKey, setPlayKey] = useState(String(Math.random()));
|
const [playKey, setPlayKey] = useState(String(Math.random()));
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (play === true) {
|
if (play === true) {
|
||||||
setPlayKey(String(Math.random()));
|
setPlayKey(String(Math.random()));
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick?.();
|
onClick?.();
|
||||||
}, [onClick, play]);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { OwnProps as AnimatedIconProps } from './AnimatedIcon';
|
import type { OwnProps as AnimatedIconProps } from './AnimatedIcon';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import buildStyle from '../../util/buildStyle';
|
import buildStyle from '../../util/buildStyle';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
|
||||||
@ -26,10 +28,10 @@ function AnimatedIconWithPreview(props: OwnProps) {
|
|||||||
const transitionClassNames = useMediaTransition(isPreviewLoaded);
|
const transitionClassNames = useMediaTransition(isPreviewLoaded);
|
||||||
const [isAnimationReady, markAnimationReady] = useFlag(false);
|
const [isAnimationReady, markAnimationReady] = useFlag(false);
|
||||||
|
|
||||||
const handlePreviewLoad = useCallback(() => {
|
const handlePreviewLoad = useLastCallback(() => {
|
||||||
markPreviewLoaded();
|
markPreviewLoaded();
|
||||||
loadedPreviewUrls.add(previewUrl);
|
loadedPreviewUrls.add(previewUrl);
|
||||||
}, [markPreviewLoaded, previewUrl]);
|
});
|
||||||
|
|
||||||
const { size } = props;
|
const { size } = props;
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
|||||||
import { ensureRLottie, getRLottie } from '../../lib/rlottie/RLottie.async';
|
import { ensureRLottie, getRLottie } from '../../lib/rlottie/RLottie.async';
|
||||||
|
|
||||||
import React, {
|
import React, {
|
||||||
useEffect, useRef, memo, useCallback, useState, useMemo,
|
useEffect, useRef, memo, useState, useMemo,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -13,6 +13,7 @@ import buildStyle from '../../util/buildStyle';
|
|||||||
import generateIdFor from '../../util/generateIdFor';
|
import generateIdFor from '../../util/generateIdFor';
|
||||||
import { hexToRgb } from '../../util/switchTheme';
|
import { hexToRgb } from '../../util/switchTheme';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck';
|
import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck';
|
||||||
import usePriorityPlaybackCheck, { isPriorityPlaybackActive } from '../../hooks/usePriorityPlaybackCheck';
|
import usePriorityPlaybackCheck, { isPriorityPlaybackActive } from '../../hooks/usePriorityPlaybackCheck';
|
||||||
import useBackgroundMode, { isBackgroundModeActive } from '../../hooks/useBackgroundMode';
|
import useBackgroundMode, { isBackgroundModeActive } from '../../hooks/useBackgroundMode';
|
||||||
@ -108,7 +109,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const init = useCallback(() => {
|
const init = useLastCallback(() => {
|
||||||
if (
|
if (
|
||||||
animationRef.current
|
animationRef.current
|
||||||
|| isUnmountedRef.current
|
|| isUnmountedRef.current
|
||||||
@ -147,10 +148,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
|
|
||||||
setAnimation(newAnimation);
|
setAnimation(newAnimation);
|
||||||
animationRef.current = newAnimation;
|
animationRef.current = newAnimation;
|
||||||
}, [
|
});
|
||||||
isLowPriority, noLoop, onEnded, onLoad, onLoop, quality,
|
|
||||||
renderId, sharedCanvas, sharedCanvasCoords, size, speed, tgsUrl, viewId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getRLottie()) {
|
if (getRLottie()) {
|
||||||
@ -158,7 +156,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
ensureRLottie().then(init);
|
ensureRLottie().then(init);
|
||||||
}
|
}
|
||||||
}, [init]);
|
}, [init, tgsUrl, sharedCanvas, sharedCanvasCoords]);
|
||||||
|
|
||||||
const throttledInit = useThrottledCallback(init, [init], THROTTLE_MS);
|
const throttledInit = useThrottledCallback(init, [init], THROTTLE_MS);
|
||||||
useSharedIntersectionObserver(sharedCanvas, throttledInit);
|
useSharedIntersectionObserver(sharedCanvas, throttledInit);
|
||||||
@ -175,7 +173,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
};
|
};
|
||||||
}, [viewId]);
|
}, [viewId]);
|
||||||
|
|
||||||
const playAnimation = useCallback((shouldRestart = false) => {
|
const playAnimation = useLastCallback((shouldRestart = false) => {
|
||||||
if (
|
if (
|
||||||
!animation
|
!animation
|
||||||
|| !(playRef.current || playSegmentRef.current)
|
|| !(playRef.current || playSegmentRef.current)
|
||||||
@ -189,17 +187,17 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
animation.play(shouldRestart, viewId);
|
animation.play(shouldRestart, viewId);
|
||||||
}
|
}
|
||||||
}, [animation, forceOnHeavyAnimation, playRef, playSegmentRef, viewId]);
|
});
|
||||||
|
|
||||||
const playAnimationOnRaf = useCallback(() => {
|
const playAnimationOnRaf = useLastCallback(() => {
|
||||||
requestMeasure(playAnimation);
|
requestMeasure(playAnimation);
|
||||||
}, [playAnimation]);
|
});
|
||||||
|
|
||||||
const pauseAnimation = useCallback(() => {
|
const pauseAnimation = useLastCallback(() => {
|
||||||
if (animation?.isPlaying()) {
|
if (animation?.isPlaying()) {
|
||||||
animation.pause(viewId);
|
animation.pause(viewId);
|
||||||
}
|
}
|
||||||
}, [animation, viewId]);
|
});
|
||||||
|
|
||||||
useEffectWithPrevDeps(([prevNoLoop]) => {
|
useEffectWithPrevDeps(([prevNoLoop]) => {
|
||||||
if (prevNoLoop !== undefined && noLoop !== prevNoLoop) {
|
if (prevNoLoop !== undefined && noLoop !== prevNoLoop) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState,
|
memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
@ -25,6 +25,8 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import { formatMediaDateTime, formatMediaDuration, formatPastTimeShort } from '../../util/dateFormat';
|
import { formatMediaDateTime, formatMediaDuration, formatPastTimeShort } from '../../util/dateFormat';
|
||||||
import { decodeWaveform, interpolateArray } from '../../util/waveform';
|
import { decodeWaveform, interpolateArray } from '../../util/waveform';
|
||||||
import { makeTrackId } from '../../util/audioPlayer';
|
import { makeTrackId } from '../../util/audioPlayer';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
|
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
import type { BufferedRange } from '../../hooks/useBuffering';
|
import type { BufferedRange } from '../../hooks/useBuffering';
|
||||||
@ -128,14 +130,14 @@ const Audio: FC<OwnProps> = ({
|
|||||||
getMessageMediaFormat(message, 'download'),
|
getMessageMediaFormat(message, 'download'),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleForcePlay = useCallback(() => {
|
const handleForcePlay = useLastCallback(() => {
|
||||||
setIsActivated(true);
|
setIsActivated(true);
|
||||||
onPlay(message.id, message.chatId);
|
onPlay(message.id, message.chatId);
|
||||||
}, [message, onPlay]);
|
});
|
||||||
|
|
||||||
const handleTrackChange = useCallback(() => {
|
const handleTrackChange = useLastCallback(() => {
|
||||||
setIsActivated(false);
|
setIsActivated(false);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isBuffered, bufferedRanges, bufferingHandlers, checkBuffering,
|
isBuffered, bufferedRanges, bufferingHandlers, checkBuffering,
|
||||||
@ -185,7 +187,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const shouldRenderCross = shouldRenderSpinner && (isLoadingForPlaying || isUploading);
|
const shouldRenderCross = shouldRenderSpinner && (isLoadingForPlaying || isUploading);
|
||||||
|
|
||||||
const handleButtonClick = useCallback(() => {
|
const handleButtonClick = useLastCallback(() => {
|
||||||
if (isUploading) {
|
if (isUploading) {
|
||||||
onCancelUpload?.();
|
onCancelUpload?.();
|
||||||
return;
|
return;
|
||||||
@ -198,7 +200,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
getActions().setAudioPlayerOrigin({ origin });
|
getActions().setAudioPlayerOrigin({ origin });
|
||||||
setIsActivated(!isActivated);
|
setIsActivated(!isActivated);
|
||||||
playPause();
|
playPause();
|
||||||
}, [isUploading, isPlaying, isActivated, playPause, onCancelUpload, onPlay, message.id, message.chatId, origin]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onReadMedia && isMediaUnread && (isPlaying || isDownloading)) {
|
if (onReadMedia && isMediaUnread && (isPlaying || isDownloading)) {
|
||||||
@ -206,15 +208,15 @@ const Audio: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isPlaying, isMediaUnread, onReadMedia, isDownloading]);
|
}, [isPlaying, isMediaUnread, onReadMedia, isDownloading]);
|
||||||
|
|
||||||
const handleDownloadClick = useCallback(() => {
|
const handleDownloadClick = useLastCallback(() => {
|
||||||
if (isDownloading) {
|
if (isDownloading) {
|
||||||
cancelMessageMediaDownload({ message });
|
cancelMessageMediaDownload({ message });
|
||||||
} else {
|
} else {
|
||||||
downloadMessageMedia({ message });
|
downloadMessageMedia({ message });
|
||||||
}
|
}
|
||||||
}, [cancelMessageMediaDownload, downloadMessageMedia, isDownloading, message]);
|
});
|
||||||
|
|
||||||
const handleSeek = useCallback((e: MouseEvent | TouchEvent) => {
|
const handleSeek = useLastCallback((e: MouseEvent | TouchEvent) => {
|
||||||
if (isSeeking.current && seekerRef.current) {
|
if (isSeeking.current && seekerRef.current) {
|
||||||
const { width, left } = seekerRef.current.getBoundingClientRect();
|
const { width, left } = seekerRef.current.getBoundingClientRect();
|
||||||
const clientX = e instanceof MouseEvent ? e.clientX : e.targetTouches[0].clientX;
|
const clientX = e instanceof MouseEvent ? e.clientX : e.targetTouches[0].clientX;
|
||||||
@ -222,25 +224,25 @@ const Audio: FC<OwnProps> = ({
|
|||||||
// Prevent track skipping while seeking near end
|
// Prevent track skipping while seeking near end
|
||||||
setCurrentTime(Math.max(Math.min(duration * ((clientX - left) / width), duration - 0.1), 0.001));
|
setCurrentTime(Math.max(Math.min(duration * ((clientX - left) / width), duration - 0.1), 0.001));
|
||||||
}
|
}
|
||||||
}, [duration, setCurrentTime]);
|
});
|
||||||
|
|
||||||
const handleStartSeek = useCallback((e: MouseEvent | TouchEvent) => {
|
const handleStartSeek = useLastCallback((e: MouseEvent | TouchEvent) => {
|
||||||
if (e instanceof MouseEvent && e.button === 2) return;
|
if (e instanceof MouseEvent && e.button === 2) return;
|
||||||
isSeeking.current = true;
|
isSeeking.current = true;
|
||||||
handleSeek(e);
|
handleSeek(e);
|
||||||
}, [handleSeek]);
|
});
|
||||||
|
|
||||||
const handleStopSeek = useCallback(() => {
|
const handleStopSeek = useLastCallback(() => {
|
||||||
isSeeking.current = false;
|
isSeeking.current = false;
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleDateClick = useCallback(() => {
|
const handleDateClick = useLastCallback(() => {
|
||||||
onDateClick!(message.id, message.chatId);
|
onDateClick!(message.id, message.chatId);
|
||||||
}, [onDateClick, message.id, message.chatId]);
|
});
|
||||||
|
|
||||||
const handleTranscribe = useCallback(() => {
|
const handleTranscribe = useLastCallback(() => {
|
||||||
transcribeAudio({ chatId: message.chatId, messageId: message.id });
|
transcribeAudio({ chatId: message.chatId, messageId: message.id });
|
||||||
}, [message.chatId, message.id, transcribeAudio]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!seekerRef.current || !withSeekline) return undefined;
|
if (!seekerRef.current || !withSeekline) return undefined;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useMemo, useRef,
|
memo, useMemo, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||||
@ -33,6 +33,7 @@ import { useFastClick } from '../../hooks/useFastClick';
|
|||||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||||
|
|
||||||
import './Avatar.scss';
|
import './Avatar.scss';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
const LOOP_COUNT = 3;
|
const LOOP_COUNT = 3;
|
||||||
|
|
||||||
@ -111,7 +112,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
return onlineTransitionClassNames.split(' ').map((c) => (c === 'shown' ? 'online' : `online-${c}`)).join(' ');
|
return onlineTransitionClassNames.split(' ').map((c) => (c === 'shown' ? 'online' : `online-${c}`)).join(' ');
|
||||||
}, [onlineTransitionClassNames]);
|
}, [onlineTransitionClassNames]);
|
||||||
|
|
||||||
const handleVideoEnded = useCallback((e) => {
|
const handleVideoEnded = useLastCallback((e) => {
|
||||||
const video = e.currentTarget;
|
const video = e.currentTarget;
|
||||||
if (!videoBlobUrl) return;
|
if (!videoBlobUrl) return;
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
if (videoLoopCountRef.current >= LOOP_COUNT) {
|
if (videoLoopCountRef.current >= LOOP_COUNT) {
|
||||||
video.style.display = 'none';
|
video.style.display = 'none';
|
||||||
}
|
}
|
||||||
}, [loopIndefinitely, videoBlobUrl]);
|
});
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useState,
|
memo, useEffect, useMemo, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -34,6 +34,8 @@ import { copyTextToClipboard } from '../../util/clipboard';
|
|||||||
import { formatPhoneNumberWithCode } from '../../util/phoneNumber';
|
import { formatPhoneNumberWithCode } from '../../util/phoneNumber';
|
||||||
import { debounce } from '../../util/schedulers';
|
import { debounce } from '../../util/schedulers';
|
||||||
import stopEvent from '../../util/stopEvent';
|
import stopEvent from '../../util/stopEvent';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import ListItem from '../ui/ListItem';
|
import ListItem from '../ui/ListItem';
|
||||||
@ -123,7 +125,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
: getChatLink(chat) || chatInviteLink;
|
: getChatLink(chat) || chatInviteLink;
|
||||||
}, [chat, isTopicInfo, activeChatUsernames, topicId, chatInviteLink]);
|
}, [chat, isTopicInfo, activeChatUsernames, topicId, chatInviteLink]);
|
||||||
|
|
||||||
const handleNotificationChange = useCallback(() => {
|
const handleNotificationChange = useLastCallback(() => {
|
||||||
setAreNotificationsEnabled((current) => {
|
setAreNotificationsEnabled((current) => {
|
||||||
const newAreNotificationsEnabled = !current;
|
const newAreNotificationsEnabled = !current;
|
||||||
|
|
||||||
@ -141,7 +143,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return newAreNotificationsEnabled;
|
return newAreNotificationsEnabled;
|
||||||
});
|
});
|
||||||
}, [chatId, isTopicInfo, topicId, updateChatMutedState, updateTopicMutedState]);
|
});
|
||||||
|
|
||||||
if (!chat || chat.isRestricted || (isSelf && !forceShowSelf)) {
|
if (!chat || chat.isRestricted || (isSelf && !forceShowSelf)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useRef, useCallback, useState, useMemo,
|
memo, useRef, useState, useMemo,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
@ -10,6 +10,8 @@ import { REM } from './helpers/mediaDimensions';
|
|||||||
import { CHAT_HEIGHT_PX } from '../../config';
|
import { CHAT_HEIGHT_PX } from '../../config';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
import { getCanPostInChat, isUserId } from '../../global/helpers';
|
import { getCanPostInChat, isUserId } from '../../global/helpers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useKeyboardListNavigation from '../../hooks/useKeyboardListNavigation';
|
import useKeyboardListNavigation from '../../hooks/useKeyboardListNavigation';
|
||||||
@ -76,9 +78,9 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
const activeKey = forumId ? TOPIC_LIST_SLIDE : CHAT_LIST_SLIDE;
|
const activeKey = forumId ? TOPIC_LIST_SLIDE : CHAT_LIST_SLIDE;
|
||||||
const viewportOffset = chatOrUserIds!.indexOf(viewportIds![0]);
|
const viewportOffset = chatOrUserIds!.indexOf(viewportIds![0]);
|
||||||
|
|
||||||
const resetSearch = useCallback(() => {
|
const resetSearch = useLastCallback(() => {
|
||||||
onSearchChange('');
|
onSearchChange('');
|
||||||
}, [onSearchChange]);
|
});
|
||||||
useInputFocusOnOpen(searchRef, isOpen && activeKey === CHAT_LIST_SLIDE, resetSearch);
|
useInputFocusOnOpen(searchRef, isOpen && activeKey === CHAT_LIST_SLIDE, resetSearch);
|
||||||
useInputFocusOnOpen(topicSearchRef, isOpen && activeKey === TOPIC_LIST_SLIDE);
|
useInputFocusOnOpen(topicSearchRef, isOpen && activeKey === TOPIC_LIST_SLIDE);
|
||||||
|
|
||||||
@ -106,18 +108,18 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
return [Object.keys(result).map(Number), result];
|
return [Object.keys(result).map(Number), result];
|
||||||
}, [chatsById, forumId, topicSearch]);
|
}, [chatsById, forumId, topicSearch]);
|
||||||
|
|
||||||
const handleHeaderBackClick = useCallback(() => {
|
const handleHeaderBackClick = useLastCallback(() => {
|
||||||
setForumId(undefined);
|
setForumId(undefined);
|
||||||
setTopicSearch('');
|
setTopicSearch('');
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearchChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
onSearchChange(e.currentTarget.value);
|
onSearchChange(e.currentTarget.value);
|
||||||
}, [onSearchChange]);
|
});
|
||||||
|
|
||||||
const handleTopicSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleTopicSearchChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setTopicSearch(e.currentTarget.value);
|
setTopicSearch(e.currentTarget.value);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleKeyDown = useKeyboardListNavigation(containerRef, isOpen, (index) => {
|
const handleKeyDown = useKeyboardListNavigation(containerRef, isOpen, (index) => {
|
||||||
if (viewportIds && viewportIds.length > 0) {
|
if (viewportIds && viewportIds.length > 0) {
|
||||||
@ -138,7 +140,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, '.ListItem-button', true);
|
}, '.ListItem-button', true);
|
||||||
|
|
||||||
const handleClick = useCallback((e: React.MouseEvent, chatId: string) => {
|
const handleClick = useLastCallback((e: React.MouseEvent, chatId: string) => {
|
||||||
const chat = chatsById?.[chatId];
|
const chat = chatsById?.[chatId];
|
||||||
if (chat?.isForum) {
|
if (chat?.isForum) {
|
||||||
if (!chat.topics) loadTopics({ chatId });
|
if (!chat.topics) loadTopics({ chatId });
|
||||||
@ -147,11 +149,11 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
onSelectChatOrUser(chatId);
|
onSelectChatOrUser(chatId);
|
||||||
}
|
}
|
||||||
}, [chatsById, loadTopics, onSelectChatOrUser, resetSearch]);
|
});
|
||||||
|
|
||||||
const handleTopicClick = useCallback((e: React.MouseEvent, topicId: number) => {
|
const handleTopicClick = useLastCallback((e: React.MouseEvent, topicId: number) => {
|
||||||
onSelectChatOrUser(forumId!, topicId);
|
onSelectChatOrUser(forumId!, topicId);
|
||||||
}, [forumId, onSelectChatOrUser]);
|
});
|
||||||
|
|
||||||
function renderTopicList() {
|
function renderTopicList() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import React, {
|
import React, { memo, useRef, useState } from '../../lib/teact/teact';
|
||||||
memo, useCallback, useRef, useState,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
import { getGlobal } from '../../global';
|
import { getGlobal } from '../../global';
|
||||||
|
|
||||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||||
@ -11,6 +9,7 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import safePlay from '../../util/safePlay';
|
import safePlay from '../../util/safePlay';
|
||||||
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
|
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useCustomEmoji from './hooks/useCustomEmoji';
|
import useCustomEmoji from './hooks/useCustomEmoji';
|
||||||
import useDynamicColorListener from '../../hooks/stickers/useDynamicColorListener';
|
import useDynamicColorListener from '../../hooks/stickers/useDynamicColorListener';
|
||||||
|
|
||||||
@ -79,7 +78,7 @@ const CustomEmoji: FC<OwnProps> = ({
|
|||||||
const hasCustomColor = customEmoji?.shouldUseTextColor;
|
const hasCustomColor = customEmoji?.shouldUseTextColor;
|
||||||
const customColor = useDynamicColorListener(containerRef, !hasCustomColor);
|
const customColor = useDynamicColorListener(containerRef, !hasCustomColor);
|
||||||
|
|
||||||
const handleVideoEnded = useCallback((e) => {
|
const handleVideoEnded = useLastCallback((e) => {
|
||||||
if (!loopLimit) return;
|
if (!loopLimit) return;
|
||||||
|
|
||||||
loopCountRef.current += 1;
|
loopCountRef.current += 1;
|
||||||
@ -91,9 +90,9 @@ const CustomEmoji: FC<OwnProps> = ({
|
|||||||
// Loop manually
|
// Loop manually
|
||||||
safePlay(e.currentTarget);
|
safePlay(e.currentTarget);
|
||||||
}
|
}
|
||||||
}, [loopLimit]);
|
});
|
||||||
|
|
||||||
const handleStickerLoop = useCallback(() => {
|
const handleStickerLoop = useLastCallback(() => {
|
||||||
if (!loopLimit) return;
|
if (!loopLimit) return;
|
||||||
|
|
||||||
loopCountRef.current += 1;
|
loopCountRef.current += 1;
|
||||||
@ -102,7 +101,7 @@ const CustomEmoji: FC<OwnProps> = ({
|
|||||||
if (loopCountRef.current >= loopLimit - 1) {
|
if (loopCountRef.current >= loopLimit - 1) {
|
||||||
setShouldLoop(false);
|
setShouldLoop(false);
|
||||||
}
|
}
|
||||||
}, [loopLimit]);
|
});
|
||||||
|
|
||||||
const isHq = customEmoji?.stickerSetInfo && selectIsAlwaysHighPriorityEmoji(getGlobal(), customEmoji.stickerSetInfo);
|
const isHq = customEmoji?.stickerSetInfo && selectIsAlwaysHighPriorityEmoji(getGlobal(), customEmoji.stickerSetInfo);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
useEffect, memo, useRef, useMemo, useCallback,
|
useEffect, memo, useRef, useMemo,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getGlobal, withGlobal } from '../../global';
|
import { getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -34,6 +34,7 @@ import {
|
|||||||
selectIsCurrentUserPremium,
|
selectIsCurrentUserPremium,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useAsyncRendering from '../right/hooks/useAsyncRendering';
|
import useAsyncRendering from '../right/hooks/useAsyncRendering';
|
||||||
import useHorizontalScroll from '../../hooks/useHorizontalScroll';
|
import useHorizontalScroll from '../../hooks/useHorizontalScroll';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -276,13 +277,13 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
|||||||
animateHorizontalScroll(header, newLeft);
|
animateHorizontalScroll(header, newLeft);
|
||||||
}, [areAddedLoaded, activeSetIndex]);
|
}, [areAddedLoaded, activeSetIndex]);
|
||||||
|
|
||||||
const handleEmojiSelect = useCallback((emoji: ApiSticker) => {
|
const handleEmojiSelect = useLastCallback((emoji: ApiSticker) => {
|
||||||
onCustomEmojiSelect(emoji);
|
onCustomEmojiSelect(emoji);
|
||||||
}, [onCustomEmojiSelect]);
|
});
|
||||||
|
|
||||||
const handleReactionSelect = useCallback((reaction: ApiReaction) => {
|
const handleReactionSelect = useLastCallback((reaction: ApiReaction) => {
|
||||||
onReactionSelect?.(reaction);
|
onReactionSelect?.(reaction);
|
||||||
}, [onReactionSelect]);
|
});
|
||||||
|
|
||||||
function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) {
|
function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) {
|
||||||
const firstSticker = stickerSet.stickers?.[0];
|
const firstSticker = stickerSet.stickers?.[0];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback, memo, useRef, useEffect, useState,
|
memo, useRef, useEffect, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
@ -19,6 +19,8 @@ import {
|
|||||||
isMessageDocumentVideo,
|
isMessageDocumentVideo,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||||
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
|
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
@ -108,7 +110,7 @@ const Document: FC<OwnProps> = ({
|
|||||||
SUPPORTED_VIDEO_CONTENT_TYPES.has(document.mimeType) || SUPPORTED_IMAGE_CONTENT_TYPES.has(document.mimeType)
|
SUPPORTED_VIDEO_CONTENT_TYPES.has(document.mimeType) || SUPPORTED_IMAGE_CONTENT_TYPES.has(document.mimeType)
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (isUploading) {
|
if (isUploading) {
|
||||||
if (onCancelUpload) {
|
if (onCancelUpload) {
|
||||||
onCancelUpload();
|
onCancelUpload();
|
||||||
@ -131,13 +133,11 @@ const Document: FC<OwnProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
dispatch.downloadMessageMedia({ message });
|
dispatch.downloadMessageMedia({ message });
|
||||||
}
|
}
|
||||||
}, [
|
});
|
||||||
isUploading, isDownloading, isTransferring, withMediaViewer, onCancelUpload, dispatch, message, onMediaClick,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleDateClick = useCallback(() => {
|
const handleDateClick = useLastCallback(() => {
|
||||||
onDateClick!(message.id, message.chatId);
|
onDateClick!(message.id, message.chatId);
|
||||||
}, [onDateClick, message.id, message.chatId]);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<File
|
<File
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useRef, useState,
|
memo, useEffect, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiVideo } from '../../api/types';
|
import type { ApiVideo } from '../../api/types';
|
||||||
@ -26,6 +26,7 @@ import MenuItem from '../ui/MenuItem';
|
|||||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||||
|
|
||||||
import './GifButton.scss';
|
import './GifButton.scss';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
gif: ApiVideo;
|
gif: ApiVideo;
|
||||||
@ -69,17 +70,9 @@ const GifButton: FC<OwnProps> = ({
|
|||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref);
|
} = useContextMenuHandlers(ref);
|
||||||
|
|
||||||
const getTriggerElement = useCallback(() => ref.current, []);
|
const getTriggerElement = useLastCallback(() => ref.current);
|
||||||
|
const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar'));
|
||||||
const getRootElement = useCallback(
|
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.gif-context-menu .bubble'));
|
||||||
() => ref.current!.closest('.custom-scroll, .no-scrollbar'),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const getMenuElement = useCallback(
|
|
||||||
() => ref.current!.querySelector('.gif-context-menu .bubble'),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
||||||
@ -90,42 +83,42 @@ const GifButton: FC<OwnProps> = ({
|
|||||||
getMenuElement,
|
getMenuElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (isContextMenuOpen || !onClick) return;
|
if (isContextMenuOpen || !onClick) return;
|
||||||
onClick({
|
onClick({
|
||||||
...gif,
|
...gif,
|
||||||
blobUrl: videoData,
|
blobUrl: videoData,
|
||||||
});
|
});
|
||||||
}, [isContextMenuOpen, onClick, gif, videoData]);
|
});
|
||||||
|
|
||||||
const handleUnsaveClick = useCallback((e: React.MouseEvent) => {
|
const handleUnsaveClick = useLastCallback((e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onUnsaveClick!(gif);
|
onUnsaveClick!(gif);
|
||||||
}, [onUnsaveClick, gif]);
|
});
|
||||||
|
|
||||||
const handleContextDelete = useCallback(() => {
|
const handleContextDelete = useLastCallback(() => {
|
||||||
onUnsaveClick?.(gif);
|
onUnsaveClick?.(gif);
|
||||||
}, [gif, onUnsaveClick]);
|
});
|
||||||
|
|
||||||
const handleSendQuiet = useCallback(() => {
|
const handleSendQuiet = useLastCallback(() => {
|
||||||
onClick!({
|
onClick!({
|
||||||
...gif,
|
...gif,
|
||||||
blobUrl: videoData,
|
blobUrl: videoData,
|
||||||
}, true);
|
}, true);
|
||||||
}, [gif, onClick, videoData]);
|
});
|
||||||
|
|
||||||
const handleSendScheduled = useCallback(() => {
|
const handleSendScheduled = useLastCallback(() => {
|
||||||
onClick!({
|
onClick!({
|
||||||
...gif,
|
...gif,
|
||||||
blobUrl: videoData,
|
blobUrl: videoData,
|
||||||
}, undefined, true);
|
}, undefined, true);
|
||||||
}, [gif, onClick, videoData]);
|
});
|
||||||
|
|
||||||
const handleMouseDown = useCallback((e: React.MouseEvent<HTMLElement>) => {
|
const handleMouseDown = useLastCallback((e: React.MouseEvent<HTMLElement>) => {
|
||||||
preventMessageInputBlurWithBubbling(e);
|
preventMessageInputBlurWithBubbling(e);
|
||||||
handleBeforeContextMenu(e);
|
handleBeforeContextMenu(e);
|
||||||
}, [handleBeforeContextMenu]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDisabled) handleContextMenuClose();
|
if (isDisabled) handleContextMenuClose();
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, { useEffect, memo, useMemo } from '../../lib/teact/teact';
|
||||||
useEffect, useCallback, memo, useMemo,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -34,6 +32,7 @@ import TypingStatus from './TypingStatus';
|
|||||||
import DotAnimation from './DotAnimation';
|
import DotAnimation from './DotAnimation';
|
||||||
import FullNameTitle from './FullNameTitle';
|
import FullNameTitle from './FullNameTitle';
|
||||||
import TopicIcon from './TopicIcon';
|
import TopicIcon from './TopicIcon';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
const TOPIC_ICON_SIZE = 2.5 * REM;
|
const TOPIC_ICON_SIZE = 2.5 * REM;
|
||||||
|
|
||||||
@ -105,16 +104,18 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [chatId, isMin, lastSyncTime, withFullInfo, loadFullChat, loadProfilePhotos, isSuperGroup, withMediaViewer]);
|
}, [chatId, isMin, lastSyncTime, withFullInfo, loadFullChat, loadProfilePhotos, isSuperGroup, withMediaViewer]);
|
||||||
|
|
||||||
const handleAvatarViewerOpen = useCallback((e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
|
const handleAvatarViewerOpen = useLastCallback(
|
||||||
if (chat && hasMedia) {
|
(e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
|
||||||
e.stopPropagation();
|
if (chat && hasMedia) {
|
||||||
openMediaViewer({
|
e.stopPropagation();
|
||||||
avatarOwnerId: chat.id,
|
openMediaViewer({
|
||||||
mediaId: 0,
|
avatarOwnerId: chat.id,
|
||||||
origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar,
|
mediaId: 0,
|
||||||
});
|
origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar,
|
||||||
}
|
});
|
||||||
}, [chat, avatarSize, openMediaViewer]);
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const mainUsername = useMemo(() => chat && withUsername && getMainUsername(chat), [chat, withUsername]);
|
const mainUsername = useMemo(() => chat && withUsername && getMainUsername(chat), [chat, withUsername]);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useCallback, useRef } from '../../lib/teact/teact';
|
import React, { memo, useRef } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import type { ApiMessage } from '../../api/types';
|
import type { ApiMessage } from '../../api/types';
|
||||||
@ -15,6 +15,7 @@ import {
|
|||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -52,10 +53,10 @@ const Media: FC<OwnProps> = ({
|
|||||||
const hasSpoiler = getMessageIsSpoiler(message);
|
const hasSpoiler = getMessageIsSpoiler(message);
|
||||||
const [isSpoilerShown, , hideSpoiler] = useFlag(hasSpoiler);
|
const [isSpoilerShown, , hideSpoiler] = useFlag(hasSpoiler);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
hideSpoiler();
|
hideSpoiler();
|
||||||
onClick!(message.id, message.chatId);
|
onClick!(message.id, message.chatId);
|
||||||
}, [hideSpoiler, message, onClick]);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import React, { memo, useCallback, useRef } from '../../lib/teact/teact';
|
import React, { memo, useRef } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useCanvasBlur from '../../hooks/useCanvasBlur';
|
import useCanvasBlur from '../../hooks/useCanvasBlur';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
|
|
||||||
@ -36,7 +38,7 @@ const MediaSpoiler: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
const canvasRef = useCanvasBlur(thumbDataUri, !shouldRender, undefined, BLUR_RADIUS, width, height);
|
const canvasRef = useCanvasBlur(thumbDataUri, !shouldRender, undefined, BLUR_RADIUS, width, height);
|
||||||
|
|
||||||
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
const handleClick = useLastCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
const x = e.clientX - rect.left;
|
const x = e.clientX - rect.left;
|
||||||
@ -44,7 +46,7 @@ const MediaSpoiler: FC<OwnProps> = ({
|
|||||||
const shiftX = x - (rect.width / 2);
|
const shiftX = x - (rect.width / 2);
|
||||||
const shiftY = y - (rect.height / 2);
|
const shiftY = y - (rect.height / 2);
|
||||||
ref.current.setAttribute('style', `--click-shift-x: ${shiftX}px; --click-shift-y: ${shiftY}px`);
|
ref.current.setAttribute('style', `--click-shift-x: ${shiftX}px; --click-shift-y: ${shiftY}px`);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
if (!shouldRender) {
|
if (!shouldRender) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
useCallback, useRef, useEffect, memo, useMemo,
|
useRef, useEffect, memo, useMemo,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
|
|
||||||
@ -8,6 +8,8 @@ import type { FC } from '../../lib/teact/teact';
|
|||||||
import { isUserId } from '../../global/helpers';
|
import { isUserId } from '../../global/helpers';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
@ -99,7 +101,7 @@ const Picker: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
}, [itemIds, lockedIdsSet]);
|
}, [itemIds, lockedIdsSet]);
|
||||||
|
|
||||||
const handleItemClick = useCallback((id: string) => {
|
const handleItemClick = useLastCallback((id: string) => {
|
||||||
if (lockedIdsSet.has(id)) {
|
if (lockedIdsSet.has(id)) {
|
||||||
onDisabledClick?.(id);
|
onDisabledClick?.(id);
|
||||||
return;
|
return;
|
||||||
@ -113,12 +115,12 @@ const Picker: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
onSelectedIdsChange?.(newSelectedIds);
|
onSelectedIdsChange?.(newSelectedIds);
|
||||||
onFilterChange?.('');
|
onFilterChange?.('');
|
||||||
}, [lockedIdsSet, selectedIds, onSelectedIdsChange, onFilterChange, onDisabledClick]);
|
});
|
||||||
|
|
||||||
const handleFilterChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFilterChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { value } = e.currentTarget;
|
const { value } = e.currentTarget;
|
||||||
onFilterChange?.(value);
|
onFilterChange?.(value);
|
||||||
}, [onFilterChange]);
|
});
|
||||||
|
|
||||||
const [viewportIds, getMore] = useInfiniteScroll(onLoadMore, sortedItemIds, Boolean(filterValue));
|
const [viewportIds, getMore] = useInfiniteScroll(onLoadMore, sortedItemIds, Boolean(filterValue));
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import React, {
|
import React, { useEffect, memo, useMemo } from '../../lib/teact/teact';
|
||||||
useEffect, useCallback, memo, useMemo,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
@ -19,6 +17,7 @@ import { getMainUsername, getUserStatus, isUserOnline } from '../../global/helpe
|
|||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
@ -88,16 +87,18 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [userId, loadFullUser, loadProfilePhotos, lastSyncTime, withFullInfo, withMediaViewer]);
|
}, [userId, loadFullUser, loadProfilePhotos, lastSyncTime, withFullInfo, withMediaViewer]);
|
||||||
|
|
||||||
const handleAvatarViewerOpen = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
|
const handleAvatarViewerOpen = useLastCallback(
|
||||||
if (user && hasMedia) {
|
(e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
|
||||||
e.stopPropagation();
|
if (user && hasMedia) {
|
||||||
openMediaViewer({
|
e.stopPropagation();
|
||||||
avatarOwnerId: user.id,
|
openMediaViewer({
|
||||||
mediaId: 0,
|
avatarOwnerId: user.id,
|
||||||
origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar,
|
mediaId: 0,
|
||||||
});
|
origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar,
|
||||||
}
|
});
|
||||||
}, [user, avatarSize, openMediaViewer]);
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const mainUsername = useMemo(() => user && withUsername && getMainUsername(user), [user, withUsername]);
|
const mainUsername = useMemo(() => user && withUsername && getMainUsername(user), [user, withUsername]);
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, { useEffect, memo, useState } from '../../lib/teact/teact';
|
||||||
useEffect, useCallback, memo, useState,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -29,6 +27,7 @@ import { captureEvents, SwipeDirection } from '../../util/captureEvents';
|
|||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import usePhotosPreload from './hooks/usePhotosPreload';
|
import usePhotosPreload from './hooks/usePhotosPreload';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import usePrevious from '../../hooks/usePrevious';
|
import usePrevious from '../../hooks/usePrevious';
|
||||||
@ -40,6 +39,7 @@ import TopicIcon from './TopicIcon';
|
|||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
|
|
||||||
import './ProfileInfo.scss';
|
import './ProfileInfo.scss';
|
||||||
|
|
||||||
import styles from './ProfileInfo.module.scss';
|
import styles from './ProfileInfo.module.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
@ -130,35 +130,35 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
usePhotosPreload(user || chat, photos, currentPhotoIndex);
|
usePhotosPreload(user || chat, photos, currentPhotoIndex);
|
||||||
|
|
||||||
const handleProfilePhotoClick = useCallback(() => {
|
const handleProfilePhotoClick = useLastCallback(() => {
|
||||||
openMediaViewer({
|
openMediaViewer({
|
||||||
avatarOwnerId: userId || chatId,
|
avatarOwnerId: userId || chatId,
|
||||||
mediaId: currentPhotoIndex,
|
mediaId: currentPhotoIndex,
|
||||||
origin: forceShowSelf ? MediaViewerOrigin.SettingsAvatar : MediaViewerOrigin.ProfileAvatar,
|
origin: forceShowSelf ? MediaViewerOrigin.SettingsAvatar : MediaViewerOrigin.ProfileAvatar,
|
||||||
});
|
});
|
||||||
}, [openMediaViewer, userId, chatId, currentPhotoIndex, forceShowSelf]);
|
});
|
||||||
|
|
||||||
const handleClickPremium = useCallback(() => {
|
const handleClickPremium = useLastCallback(() => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
openPremiumModal({ fromUserId: user.id });
|
openPremiumModal({ fromUserId: user.id });
|
||||||
}, [openPremiumModal, user]);
|
});
|
||||||
|
|
||||||
const selectPreviousMedia = useCallback(() => {
|
const selectPreviousMedia = useLastCallback(() => {
|
||||||
if (isFirst) {
|
if (isFirst) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setHasSlideAnimation(true);
|
setHasSlideAnimation(true);
|
||||||
setCurrentPhotoIndex(currentPhotoIndex - 1);
|
setCurrentPhotoIndex(currentPhotoIndex - 1);
|
||||||
}, [currentPhotoIndex, isFirst]);
|
});
|
||||||
|
|
||||||
const selectNextMedia = useCallback(() => {
|
const selectNextMedia = useLastCallback(() => {
|
||||||
if (isLast) {
|
if (isLast) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setHasSlideAnimation(true);
|
setHasSlideAnimation(true);
|
||||||
setCurrentPhotoIndex(currentPhotoIndex + 1);
|
setCurrentPhotoIndex(currentPhotoIndex + 1);
|
||||||
}, [currentPhotoIndex, isLast]);
|
});
|
||||||
|
|
||||||
function handleSelectFallbackPhoto() {
|
function handleSelectFallbackPhoto() {
|
||||||
if (!isFirst) return;
|
if (!isFirst) return;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useMemo, useRef,
|
memo, useMemo, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
@ -10,6 +10,7 @@ import { EMOJI_SIZE_PICKER } from '../../config';
|
|||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { getDocumentMediaHash, isSameReaction } from '../../global/helpers';
|
import { getDocumentMediaHash, isSameReaction } from '../../global/helpers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
|
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
|
||||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
@ -54,9 +55,9 @@ const ReactionEmoji: FC<OwnProps> = ({
|
|||||||
availableReaction?.selectAnimation ? getDocumentMediaHash(availableReaction.selectAnimation) : undefined,
|
availableReaction?.selectAnimation ? getDocumentMediaHash(availableReaction.selectAnimation) : undefined,
|
||||||
!animationId,
|
!animationId,
|
||||||
);
|
);
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
onClick(reaction);
|
onClick(reaction);
|
||||||
}, [onClick, reaction]);
|
});
|
||||||
|
|
||||||
const transitionClassNames = useMediaTransition(mediaData);
|
const transitionClassNames = useMediaTransition(mediaData);
|
||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, { memo, useMemo, useState } from '../../lib/teact/teact';
|
||||||
memo, useCallback, useMemo, useState,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { ApiPhoto, ApiReportReason } from '../../api/types';
|
import type { ApiPhoto, ApiReportReason } from '../../api/types';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import Modal from '../ui/Modal';
|
import Modal from '../ui/Modal';
|
||||||
@ -44,7 +43,7 @@ const ReportModal: FC<OwnProps> = ({
|
|||||||
const [selectedReason, setSelectedReason] = useState<ApiReportReason>('spam');
|
const [selectedReason, setSelectedReason] = useState<ApiReportReason>('spam');
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState('');
|
||||||
|
|
||||||
const handleReport = useCallback(() => {
|
const handleReport = useLastCallback(() => {
|
||||||
switch (subject) {
|
switch (subject) {
|
||||||
case 'messages':
|
case 'messages':
|
||||||
reportMessages({ messageIds: messageIds!, reason: selectedReason, description });
|
reportMessages({ messageIds: messageIds!, reason: selectedReason, description });
|
||||||
@ -60,27 +59,15 @@ const ReportModal: FC<OwnProps> = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
onClose();
|
onClose();
|
||||||
}, [
|
});
|
||||||
description,
|
|
||||||
exitMessageSelectMode,
|
|
||||||
messageIds,
|
|
||||||
photo,
|
|
||||||
onClose,
|
|
||||||
reportMessages,
|
|
||||||
selectedReason,
|
|
||||||
chatId,
|
|
||||||
reportProfilePhoto,
|
|
||||||
reportPeer,
|
|
||||||
subject,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleSelectReason = useCallback((value: string) => {
|
const handleSelectReason = useLastCallback((value: string) => {
|
||||||
setSelectedReason(value as ApiReportReason);
|
setSelectedReason(value as ApiReportReason);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleDescriptionChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
const handleDescriptionChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setDescription(e.target.value);
|
setDescription(e.target.value);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
import convertPunycode from '../../lib/punycode';
|
import convertPunycode from '../../lib/punycode';
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
} from '../../config';
|
} from '../../config';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { ensureProtocol } from '../../util/ensureProtocol';
|
import { ensureProtocol } from '../../util/ensureProtocol';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
url?: string;
|
url?: string;
|
||||||
@ -31,14 +32,14 @@ const SafeLink: FC<OwnProps> = ({
|
|||||||
const content = children || text;
|
const content = children || text;
|
||||||
const isSafe = url === text;
|
const isSafe = url === text;
|
||||||
|
|
||||||
const handleClick = useCallback((e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
const handleClick = useLastCallback((e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||||
if (!url) return true;
|
if (!url) return true;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
openUrl({ url, shouldSkipModal: isSafe });
|
openUrl({ url, shouldSkipModal: isSafe });
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}, [isSafe, openUrl, url]);
|
});
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import React, { useCallback, memo, useMemo } from '../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import { selectChatMessage, selectTabState } from '../../global/selectors';
|
import { selectChatMessage, selectTabState } from '../../global/selectors';
|
||||||
import { formatDateAtTime } from '../../util/dateFormat';
|
import { formatDateAtTime } from '../../util/dateFormat';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||||
|
|
||||||
@ -47,17 +49,17 @@ function SeenByModal({
|
|||||||
return result;
|
return result;
|
||||||
}, [renderingSeenByDates]);
|
}, [renderingSeenByDates]);
|
||||||
|
|
||||||
const handleClick = useCallback((userId: string) => {
|
const handleClick = useLastCallback((userId: string) => {
|
||||||
closeSeenByModal();
|
closeSeenByModal();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
openChat({ id: userId });
|
openChat({ id: userId });
|
||||||
}, CLOSE_ANIMATION_DURATION);
|
}, CLOSE_ANIMATION_DURATION);
|
||||||
}, [closeSeenByModal, openChat]);
|
});
|
||||||
|
|
||||||
const handleCloseSeenByModal = useCallback(() => {
|
const handleCloseSeenByModal = useLastCallback(() => {
|
||||||
closeSeenByModal();
|
closeSeenByModal();
|
||||||
}, [closeSeenByModal]);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef,
|
memo, useEffect, useMemo, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
@ -12,6 +12,8 @@ import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
|||||||
import { getServerTimeOffset } from '../../util/serverTime';
|
import { getServerTimeOffset } from '../../util/serverTime';
|
||||||
|
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
@ -113,19 +115,11 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
|||||||
} = useContextMenuHandlers(ref);
|
} = useContextMenuHandlers(ref);
|
||||||
const shouldRenderContextMenu = Boolean(!noContextMenu && contextMenuPosition);
|
const shouldRenderContextMenu = Boolean(!noContextMenu && contextMenuPosition);
|
||||||
|
|
||||||
const getTriggerElement = useCallback(() => ref.current, []);
|
const getTriggerElement = useLastCallback(() => ref.current);
|
||||||
|
const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar'));
|
||||||
const getRootElement = useCallback(
|
const getMenuElement = useLastCallback(() => {
|
||||||
() => ref.current!.closest('.custom-scroll, .no-scrollbar'),
|
return isStatusPicker ? menuRef.current : ref.current!.querySelector('.sticker-context-menu .bubble');
|
||||||
[],
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const getMenuElement = useCallback(
|
|
||||||
() => {
|
|
||||||
return isStatusPicker ? menuRef.current : ref.current!.querySelector('.sticker-context-menu .bubble');
|
|
||||||
},
|
|
||||||
[isStatusPicker],
|
|
||||||
);
|
|
||||||
|
|
||||||
const getLayout = () => ({ withPortal: isStatusPicker, shouldAvoidNegativePosition: true });
|
const getLayout = () => ({ withPortal: isStatusPicker, shouldAvoidNegativePosition: true });
|
||||||
|
|
||||||
@ -165,38 +159,38 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
|||||||
handleBeforeContextMenu(e);
|
handleBeforeContextMenu(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveClick = useCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const handleRemoveClick = useLastCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
onRemoveRecentClick!(sticker);
|
onRemoveRecentClick!(sticker);
|
||||||
}, [onRemoveRecentClick, sticker]);
|
});
|
||||||
|
|
||||||
const handleContextRemoveRecent = useCallback(() => {
|
const handleContextRemoveRecent = useLastCallback(() => {
|
||||||
onRemoveRecentClick!(sticker);
|
onRemoveRecentClick!(sticker);
|
||||||
}, [onRemoveRecentClick, sticker]);
|
});
|
||||||
|
|
||||||
const handleContextUnfave = useCallback(() => {
|
const handleContextUnfave = useLastCallback(() => {
|
||||||
onUnfaveClick!(sticker);
|
onUnfaveClick!(sticker);
|
||||||
}, [onUnfaveClick, sticker]);
|
});
|
||||||
|
|
||||||
const handleContextFave = useCallback(() => {
|
const handleContextFave = useLastCallback(() => {
|
||||||
onFaveClick!(sticker);
|
onFaveClick!(sticker);
|
||||||
}, [onFaveClick, sticker]);
|
});
|
||||||
|
|
||||||
const handleSendQuiet = useCallback(() => {
|
const handleSendQuiet = useLastCallback(() => {
|
||||||
onClick?.(clickArg, true);
|
onClick?.(clickArg, true);
|
||||||
}, [clickArg, onClick]);
|
});
|
||||||
|
|
||||||
const handleSendScheduled = useCallback(() => {
|
const handleSendScheduled = useLastCallback(() => {
|
||||||
onClick?.(clickArg, undefined, true);
|
onClick?.(clickArg, undefined, true);
|
||||||
}, [clickArg, onClick]);
|
});
|
||||||
|
|
||||||
const handleOpenSet = useCallback(() => {
|
const handleOpenSet = useLastCallback(() => {
|
||||||
openStickerSet({ stickerSetInfo });
|
openStickerSet({ stickerSetInfo });
|
||||||
}, [openStickerSet, stickerSetInfo]);
|
});
|
||||||
|
|
||||||
const handleEmojiStatusExpiresClick = useCallback((e: React.SyntheticEvent, duration = 0) => {
|
const handleEmojiStatusExpiresClick = useLastCallback((e: React.SyntheticEvent, duration = 0) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
@ -206,7 +200,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
|||||||
emojiStatus: sticker,
|
emojiStatus: sticker,
|
||||||
expires: Date.now() / 1000 + duration + getServerTimeOffset(),
|
expires: Date.now() / 1000 + duration + getServerTimeOffset(),
|
||||||
});
|
});
|
||||||
}, [setEmojiStatus, sticker, handleContextMenuClose, onContextMenuClick]);
|
});
|
||||||
|
|
||||||
const shouldShowCloseButton = !IS_TOUCH_ENV && onRemoveRecentClick;
|
const shouldShowCloseButton = !IS_TOUCH_ENV && onRemoveRecentClick;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
memo, useEffect, useMemo, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal } from '../../global';
|
import { getActions, getGlobal } from '../../global';
|
||||||
|
|
||||||
@ -21,6 +21,7 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import { selectIsAlwaysHighPriorityEmoji, selectIsSetPremium } from '../../global/selectors';
|
import { selectIsAlwaysHighPriorityEmoji, selectIsSetPremium } from '../../global/selectors';
|
||||||
import { getReactionUniqueKey } from '../../global/helpers';
|
import { getReactionUniqueKey } from '../../global/helpers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||||
@ -140,7 +141,7 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
const isEmoji = stickerSet.isEmoji;
|
const isEmoji = stickerSet.isEmoji;
|
||||||
const isPremiumSet = !isRecent && selectIsSetPremium(stickerSet);
|
const isPremiumSet = !isRecent && selectIsSetPremium(stickerSet);
|
||||||
|
|
||||||
const handleClearRecent = useCallback(() => {
|
const handleClearRecent = useLastCallback(() => {
|
||||||
if (isReactionPicker) {
|
if (isReactionPicker) {
|
||||||
clearRecentReactions();
|
clearRecentReactions();
|
||||||
} else if (isEmoji) {
|
} else if (isEmoji) {
|
||||||
@ -149,11 +150,9 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
clearRecentStickers();
|
clearRecentStickers();
|
||||||
}
|
}
|
||||||
closeConfirmModal();
|
closeConfirmModal();
|
||||||
}, [
|
});
|
||||||
clearRecentCustomEmoji, clearRecentReactions, clearRecentStickers, closeConfirmModal, isEmoji, isReactionPicker,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleAddClick = useCallback(() => {
|
const handleAddClick = useLastCallback(() => {
|
||||||
if (isPremiumSet && !isCurrentUserPremium) {
|
if (isPremiumSet && !isCurrentUserPremium) {
|
||||||
openPremiumModal({
|
openPremiumModal({
|
||||||
initialSection: 'animated_emoji',
|
initialSection: 'animated_emoji',
|
||||||
@ -163,9 +162,9 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
stickerSetId: stickerSet.id,
|
stickerSetId: stickerSet.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isCurrentUserPremium, isPremiumSet, openPremiumModal, stickerSet, toggleStickerSet]);
|
});
|
||||||
|
|
||||||
const handleDefaultTopicIconClick = useCallback(() => {
|
const handleDefaultTopicIconClick = useLastCallback(() => {
|
||||||
onStickerSelect?.({
|
onStickerSelect?.({
|
||||||
id: DEFAULT_TOPIC_ICON_STICKER_ID,
|
id: DEFAULT_TOPIC_ICON_STICKER_ID,
|
||||||
isLottie: false,
|
isLottie: false,
|
||||||
@ -174,9 +173,9 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
shortName: 'dummy',
|
shortName: 'dummy',
|
||||||
},
|
},
|
||||||
} satisfies ApiSticker);
|
} satisfies ApiSticker);
|
||||||
}, [onStickerSelect]);
|
});
|
||||||
|
|
||||||
const handleDefaultStatusIconClick = useCallback(() => {
|
const handleDefaultStatusIconClick = useLastCallback(() => {
|
||||||
onStickerSelect?.({
|
onStickerSelect?.({
|
||||||
id: DEFAULT_STATUS_ICON_ID,
|
id: DEFAULT_STATUS_ICON_ID,
|
||||||
isLottie: false,
|
isLottie: false,
|
||||||
@ -185,23 +184,24 @@ const StickerSet: FC<OwnProps> = ({
|
|||||||
shortName: 'dummy',
|
shortName: 'dummy',
|
||||||
},
|
},
|
||||||
} satisfies ApiSticker);
|
} satisfies ApiSticker);
|
||||||
}, [onStickerSelect]);
|
});
|
||||||
|
|
||||||
const itemSize = isEmoji ? EMOJI_SIZE_PICKER : STICKER_SIZE_PICKER;
|
const itemSize = isEmoji ? EMOJI_SIZE_PICKER : STICKER_SIZE_PICKER;
|
||||||
const margin = isEmoji ? emojiMarginPx : stickerMarginPx;
|
const margin = isEmoji ? emojiMarginPx : stickerMarginPx;
|
||||||
const verticalMargin = isEmoji ? emojiVerticalMarginPx : stickerMarginPx;
|
const verticalMargin = isEmoji ? emojiVerticalMarginPx : stickerMarginPx;
|
||||||
|
|
||||||
const calculateItemsPerRow = useCallback((width: number) => {
|
const calculateItemsPerRow = useLastCallback((width: number) => {
|
||||||
if (!width) {
|
if (!width) {
|
||||||
return getItemsPerRowFallback(windowWidth);
|
return getItemsPerRowFallback(windowWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.floor((width + margin) / (itemSize + margin));
|
return Math.floor((width + margin) / (itemSize + margin));
|
||||||
}, [itemSize, margin, windowWidth]);
|
});
|
||||||
|
|
||||||
const handleResize = useCallback((entry: ResizeObserverEntry) => {
|
const handleResize = useLastCallback((entry: ResizeObserverEntry) => {
|
||||||
setItemsPerRow(calculateItemsPerRow(entry.contentRect.width));
|
setItemsPerRow(calculateItemsPerRow(entry.contentRect.width));
|
||||||
}, [calculateItemsPerRow]);
|
});
|
||||||
|
|
||||||
useResizeObserver(ref, handleResize);
|
useResizeObserver(ref, handleResize);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiMessage, ApiWebPage } from '../../api/types';
|
import type { ApiMessage, ApiWebPage } from '../../api/types';
|
||||||
import type { TextPart } from '../../types';
|
import type { TextPart } from '../../types';
|
||||||
@ -13,9 +13,11 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import trimText from '../../util/trimText';
|
import trimText from '../../util/trimText';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
import { formatPastTimeShort } from '../../util/dateFormat';
|
import { formatPastTimeShort } from '../../util/dateFormat';
|
||||||
import useLang from '../../hooks/useLang';
|
|
||||||
import { renderMessageSummary } from './helpers/renderMessageText';
|
import { renderMessageSummary } from './helpers/renderMessageText';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import Media from './Media';
|
import Media from './Media';
|
||||||
import Link from '../ui/Link';
|
import Link from '../ui/Link';
|
||||||
import SafeLink from './SafeLink';
|
import SafeLink from './SafeLink';
|
||||||
@ -58,9 +60,9 @@ const WebLink: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMessageClick = useCallback(() => {
|
const handleMessageClick = useLastCallback(() => {
|
||||||
onMessageClick(message.id, message.chatId);
|
onMessageClick(message.id, message.chatId);
|
||||||
}, [onMessageClick, message.id, message.chatId]);
|
});
|
||||||
|
|
||||||
if (!linkData) {
|
if (!linkData) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import {
|
import { useEffect, useRef } from '../../../lib/teact/teact';
|
||||||
useCallback, useEffect, useRef,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { ActiveEmojiInteraction } from '../../../global/types';
|
import type { ActiveEmojiInteraction } from '../../../global/types';
|
||||||
@ -9,6 +7,7 @@ import safePlay from '../../../util/safePlay';
|
|||||||
import buildStyle from '../../../util/buildStyle';
|
import buildStyle from '../../../util/buildStyle';
|
||||||
import { REM } from '../helpers/mediaDimensions';
|
import { REM } from '../helpers/mediaDimensions';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
|
|
||||||
const SIZE = 7 * REM;
|
const SIZE = 7 * REM;
|
||||||
@ -42,7 +41,7 @@ export default function useAnimatedEmoji(
|
|||||||
|
|
||||||
const interactions = useRef<number[] | undefined>(undefined);
|
const interactions = useRef<number[] | undefined>(undefined);
|
||||||
const startedInteractions = useRef<number | undefined>(undefined);
|
const startedInteractions = useRef<number | undefined>(undefined);
|
||||||
const sendInteractionBunch = useCallback(() => {
|
const sendInteractionBunch = useLastCallback(() => {
|
||||||
const container = ref.current;
|
const container = ref.current;
|
||||||
|
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@ -55,9 +54,9 @@ export default function useAnimatedEmoji(
|
|||||||
});
|
});
|
||||||
startedInteractions.current = undefined;
|
startedInteractions.current = undefined;
|
||||||
interactions.current = undefined;
|
interactions.current = undefined;
|
||||||
}, [sendEmojiInteraction, chatId, messageId, emoji]);
|
});
|
||||||
|
|
||||||
const play = useCallback(() => {
|
const play = useLastCallback(() => {
|
||||||
const audio = audioRef.current;
|
const audio = audioRef.current;
|
||||||
if (soundMediaData) {
|
if (soundMediaData) {
|
||||||
if (audio) {
|
if (audio) {
|
||||||
@ -71,9 +70,9 @@ export default function useAnimatedEmoji(
|
|||||||
audioRef.current = undefined;
|
audioRef.current = undefined;
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
}
|
}
|
||||||
}, [soundMediaData]);
|
});
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
play();
|
play();
|
||||||
|
|
||||||
const container = ref.current;
|
const container = ref.current;
|
||||||
@ -101,7 +100,7 @@ export default function useAnimatedEmoji(
|
|||||||
interactions.current.push(startedInteractions.current
|
interactions.current.push(startedInteractions.current
|
||||||
? (performance.now() - startedInteractions.current) / MS_DIVIDER
|
? (performance.now() - startedInteractions.current) / MS_DIVIDER
|
||||||
: TIME_DEFAULT);
|
: TIME_DEFAULT);
|
||||||
}, [chatId, emoji, interactWithAnimatedEmoji, isOwn, messageId, play, sendInteractionBunch, size]);
|
});
|
||||||
|
|
||||||
// Set an end anchor for remote activated interaction
|
// Set an end anchor for remote activated interaction
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState } from '../../../lib/teact/teact';
|
import { useEffect, useState } from '../../../lib/teact/teact';
|
||||||
import { getGlobal } from '../../../global';
|
import { getGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { GlobalState } from '../../../global/types';
|
import type { GlobalState } from '../../../global/types';
|
||||||
@ -7,6 +7,7 @@ import type { ApiSticker } from '../../../api/types';
|
|||||||
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
|
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
|
||||||
import { addCustomEmojiCallback, removeCustomEmojiCallback } from '../../../util/customEmojiManager';
|
import { addCustomEmojiCallback, removeCustomEmojiCallback } from '../../../util/customEmojiManager';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useEnsureCustomEmoji from '../../../hooks/useEnsureCustomEmoji';
|
import useEnsureCustomEmoji from '../../../hooks/useEnsureCustomEmoji';
|
||||||
|
|
||||||
export default function useCustomEmoji(documentId?: string) {
|
export default function useCustomEmoji(documentId?: string) {
|
||||||
@ -18,13 +19,13 @@ export default function useCustomEmoji(documentId?: string) {
|
|||||||
|
|
||||||
useEnsureCustomEmoji(documentId);
|
useEnsureCustomEmoji(documentId);
|
||||||
|
|
||||||
const handleGlobalChange = useCallback((customEmojis?: GlobalState['customEmojis']) => {
|
const handleGlobalChange = useLastCallback((customEmojis?: GlobalState['customEmojis']) => {
|
||||||
if (!documentId) return;
|
if (!documentId) return;
|
||||||
|
|
||||||
const newGlobal = getGlobal();
|
const newGlobal = getGlobal();
|
||||||
setCustomEmoji((customEmojis ?? newGlobal.customEmojis).byId[documentId]);
|
setCustomEmoji((customEmojis ?? newGlobal.customEmojis).byId[documentId]);
|
||||||
setCanPlay(selectCanPlayAnimatedEmojis(newGlobal));
|
setCanPlay(selectCanPlayAnimatedEmojis(newGlobal));
|
||||||
}, [documentId]);
|
});
|
||||||
|
|
||||||
useEffect(handleGlobalChange, [documentId, handleGlobalChange]);
|
useEffect(handleGlobalChange, [documentId, handleGlobalChange]);
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
|
import { useRef, useState } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
|
||||||
import useSyncEffect from '../../../hooks/useSyncEffect';
|
|
||||||
import { useCallback, useRef, useState } from '../../../lib/teact/teact';
|
|
||||||
import { ANIMATION_END_DELAY } from '../../../config';
|
import { ANIMATION_END_DELAY } from '../../../config';
|
||||||
import animateScroll from '../../../util/animateScroll';
|
import animateScroll from '../../../util/animateScroll';
|
||||||
import { REM } from '../helpers/mediaDimensions';
|
import { REM } from '../helpers/mediaDimensions';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
|
import useSyncEffect from '../../../hooks/useSyncEffect';
|
||||||
|
|
||||||
const STICKER_INTERSECTION_THROTTLE = 200;
|
const STICKER_INTERSECTION_THROTTLE = 200;
|
||||||
const STICKER_INTERSECTION_MARGIN = 100;
|
const STICKER_INTERSECTION_MARGIN = 100;
|
||||||
const SLIDE_TRANSITION_DURATION = 350 + ANIMATION_END_DELAY;
|
const SLIDE_TRANSITION_DURATION = 350 + ANIMATION_END_DELAY;
|
||||||
@ -85,7 +87,7 @@ export function useStickerPickerObservers(
|
|||||||
}
|
}
|
||||||
}, [freezeForSet, freezeForShowingItems, isHidden, unfreezeForSet, unfreezeForShowingItems]);
|
}, [freezeForSet, freezeForShowingItems, isHidden, unfreezeForSet, unfreezeForShowingItems]);
|
||||||
|
|
||||||
const selectStickerSet = useCallback((index: number) => {
|
const selectStickerSet = useLastCallback((index: number) => {
|
||||||
setActiveSetIndex((currentIndex) => {
|
setActiveSetIndex((currentIndex) => {
|
||||||
const stickerSetEl = document.getElementById(`${idPrefix}-${index}`)!;
|
const stickerSetEl = document.getElementById(`${idPrefix}-${index}`)!;
|
||||||
const isClose = Math.abs(currentIndex - index) === 1;
|
const isClose = Math.abs(currentIndex - index) === 1;
|
||||||
@ -100,7 +102,7 @@ export function useStickerPickerObservers(
|
|||||||
|
|
||||||
return index;
|
return index;
|
||||||
});
|
});
|
||||||
}, [containerRef, idPrefix]);
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeSetIndex,
|
activeSetIndex,
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import React, {
|
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||||
memo, useCallback, useEffect, useRef,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import { ApiMessageEntityTypes } from '../../../api/types';
|
import { ApiMessageEntityTypes } from '../../../api/types';
|
||||||
|
|
||||||
import { createClassNameBuilder } from '../../../util/buildClassName';
|
import { createClassNameBuilder } from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
|
||||||
import './Spoiler.scss';
|
import './Spoiler.scss';
|
||||||
@ -36,7 +36,7 @@ const Spoiler: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const [isRevealed, reveal, conceal] = useFlag();
|
const [isRevealed, reveal, conceal] = useFlag();
|
||||||
|
|
||||||
const getContentLength = useCallback(() => {
|
const getContentLength = useLastCallback(() => {
|
||||||
if (!contentRef.current) {
|
if (!contentRef.current) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -45,9 +45,9 @@ const Spoiler: FC<OwnProps> = ({
|
|||||||
const emojiCount = contentRef.current.querySelectorAll('.emoji').length;
|
const emojiCount = contentRef.current.querySelectorAll('.emoji').length;
|
||||||
// Optimization: ignore alt, assume that viewing emoji takes same time as viewing 4 characters
|
// Optimization: ignore alt, assume that viewing emoji takes same time as viewing 4 characters
|
||||||
return textLength + emojiCount * 4;
|
return textLength + emojiCount * 4;
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
const handleClick = useLastCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ const Spoiler: FC<OwnProps> = ({
|
|||||||
actionsByMessageId.get(messageId!)?.forEach((actions) => actions.conceal());
|
actionsByMessageId.get(messageId!)?.forEach((actions) => actions.conceal());
|
||||||
conceal();
|
conceal();
|
||||||
}, timeoutMs);
|
}, timeoutMs);
|
||||||
}, [conceal, messageId]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!messageId) {
|
if (!messageId) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
@ -7,6 +7,8 @@ import type { FolderEditDispatch } from '../../hooks/reducers/useFoldersReducer'
|
|||||||
import type { GlobalState } from '../../global/types';
|
import type { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||||
import useLeftHeaderButtonRtlForumTransition from './main/hooks/useLeftHeaderButtonRtlForumTransition';
|
import useLeftHeaderButtonRtlForumTransition from './main/hooks/useLeftHeaderButtonRtlForumTransition';
|
||||||
@ -50,9 +52,9 @@ const ArchivedChats: FC<OwnProps> = ({
|
|||||||
onBack: onReset,
|
onBack: onReset,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDisplayArchiveInChats = useCallback(() => {
|
const handleDisplayArchiveInChats = useLastCallback(() => {
|
||||||
updateArchiveSettings({ isHidden: false });
|
updateArchiveSettings({ isHidden: false });
|
||||||
}, [updateArchiveSettings]);
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
shouldDisableDropdownMenuTransitionRef,
|
shouldDisableDropdownMenuTransitionRef,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useState,
|
memo, useEffect, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -12,6 +12,8 @@ import type { FoldersActions } from '../../hooks/reducers/useFoldersReducer';
|
|||||||
import { IS_MAC_OS, IS_APP, LAYERS_ANIMATION_NAME } from '../../util/windowEnvironment';
|
import { IS_MAC_OS, IS_APP, LAYERS_ANIMATION_NAME } from '../../util/windowEnvironment';
|
||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors';
|
import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
||||||
import { useHotkeys } from '../../hooks/useHotkeys';
|
import { useHotkeys } from '../../hooks/useHotkeys';
|
||||||
import useSyncEffect from '../../hooks/useSyncEffect';
|
import useSyncEffect from '../../hooks/useSyncEffect';
|
||||||
@ -114,7 +116,7 @@ function LeftColumn({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReset = useCallback((forceReturnToChatList?: true | Event) => {
|
const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => {
|
||||||
function fullReset() {
|
function fullReset() {
|
||||||
setContent(LeftColumnContent.ChatList);
|
setContent(LeftColumnContent.ChatList);
|
||||||
setSettingsScreen(SettingsScreens.Main);
|
setSettingsScreen(SettingsScreens.Main);
|
||||||
@ -323,12 +325,9 @@ function LeftColumn({
|
|||||||
}
|
}
|
||||||
|
|
||||||
fullReset();
|
fullReset();
|
||||||
}, [
|
});
|
||||||
content, isFirstChatFolderActive, setGlobalSearchClosing, resetChatCreation, setGlobalSearchQuery,
|
|
||||||
setGlobalSearchDate, setGlobalSearchChatId, settingsScreen, hasPasscode,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleSearchQuery = useCallback((query: string) => {
|
const handleSearchQuery = useLastCallback((query: string) => {
|
||||||
if (content === LeftColumnContent.Contacts) {
|
if (content === LeftColumnContent.Contacts) {
|
||||||
setContactsFilter(query);
|
setContactsFilter(query);
|
||||||
return;
|
return;
|
||||||
@ -339,13 +338,13 @@ function LeftColumn({
|
|||||||
if (query !== searchQuery) {
|
if (query !== searchQuery) {
|
||||||
setGlobalSearchQuery({ query });
|
setGlobalSearchQuery({ query });
|
||||||
}
|
}
|
||||||
}, [content, searchQuery, setGlobalSearchQuery]);
|
});
|
||||||
|
|
||||||
const handleTopicSearch = useCallback(() => {
|
const handleTopicSearch = useLastCallback(() => {
|
||||||
setContent(LeftColumnContent.GlobalSearch);
|
setContent(LeftColumnContent.GlobalSearch);
|
||||||
setGlobalSearchQuery({ query: '' });
|
setGlobalSearchQuery({ query: '' });
|
||||||
setGlobalSearchChatId({ id: forumPanelChatId });
|
setGlobalSearchChatId({ id: forumPanelChatId });
|
||||||
}, [forumPanelChatId, setGlobalSearchChatId, setGlobalSearchQuery]);
|
});
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => (content !== LeftColumnContent.ChatList || (isFirstChatFolderActive && !isChatOpen && !isForumPanelOpen)
|
() => (content !== LeftColumnContent.ChatList || (isFirstChatFolderActive && !isChatOpen && !isForumPanelOpen)
|
||||||
@ -354,29 +353,29 @@ function LeftColumn({
|
|||||||
[isFirstChatFolderActive, content, handleReset, isChatOpen, isForumPanelOpen],
|
[isFirstChatFolderActive, content, handleReset, isChatOpen, isForumPanelOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleHotkeySearch = useCallback((e: KeyboardEvent) => {
|
const handleHotkeySearch = useLastCallback((e: KeyboardEvent) => {
|
||||||
if (content === LeftColumnContent.GlobalSearch) {
|
if (content === LeftColumnContent.GlobalSearch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setContent(LeftColumnContent.GlobalSearch);
|
setContent(LeftColumnContent.GlobalSearch);
|
||||||
}, [content]);
|
});
|
||||||
|
|
||||||
const handleHotkeySavedMessages = useCallback((e: KeyboardEvent) => {
|
const handleHotkeySavedMessages = useLastCallback((e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
openChat({ id: currentUserId, shouldReplaceHistory: true });
|
openChat({ id: currentUserId, shouldReplaceHistory: true });
|
||||||
}, [currentUserId, openChat]);
|
});
|
||||||
|
|
||||||
const handleArchivedChats = useCallback((e: KeyboardEvent) => {
|
const handleArchivedChats = useLastCallback((e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setContent(LeftColumnContent.Archived);
|
setContent(LeftColumnContent.Archived);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleHotkeySettings = useCallback((e: KeyboardEvent) => {
|
const handleHotkeySettings = useLastCallback((e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setContent(LeftColumnContent.Settings);
|
setContent(LeftColumnContent.Settings);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
useHotkeys({
|
useHotkeys({
|
||||||
'Mod+Shift+F': handleHotkeySearch,
|
'Mod+Shift+F': handleHotkeySearch,
|
||||||
@ -408,10 +407,10 @@ function LeftColumn({
|
|||||||
}
|
}
|
||||||
}, [foldersDispatch, nextFoldersAction, nextSettingsScreen, requestNextSettingsScreen]);
|
}, [foldersDispatch, nextFoldersAction, nextSettingsScreen, requestNextSettingsScreen]);
|
||||||
|
|
||||||
const handleSettingsScreenSelect = useCallback((screen: SettingsScreens) => {
|
const handleSettingsScreenSelect = useLastCallback((screen: SettingsScreens) => {
|
||||||
setContent(LeftColumnContent.Settings);
|
setContent(LeftColumnContent.Settings);
|
||||||
setSettingsScreen(screen);
|
setSettingsScreen(screen);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
function renderContent(isActive: boolean) {
|
function renderContent(isActive: boolean) {
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
|
import React, { memo, useEffect } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
@ -41,6 +41,7 @@ import {
|
|||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { createLocationHash } from '../../../util/routing';
|
import { createLocationHash } from '../../../util/routing';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useChatContextActions from '../../../hooks/useChatContextActions';
|
import useChatContextActions from '../../../hooks/useChatContextActions';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useChatListEntry from './hooks/useChatListEntry';
|
import useChatListEntry from './hooks/useChatListEntry';
|
||||||
@ -158,7 +159,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
orderDiff,
|
orderDiff,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (isForum) {
|
if (isForum) {
|
||||||
if (isSelectedForum) {
|
if (isSelectedForum) {
|
||||||
closeForumPanel(undefined, { forceOnHeavyAnimation: true });
|
closeForumPanel(undefined, { forceOnHeavyAnimation: true });
|
||||||
@ -174,32 +175,32 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
if (isSelected && canScrollDown) {
|
if (isSelected && canScrollDown) {
|
||||||
focusLastMessage();
|
focusLastMessage();
|
||||||
}
|
}
|
||||||
}, [isForum, chatId, isSelected, canScrollDown, isSelectedForum]);
|
});
|
||||||
|
|
||||||
const handleDragEnter = useCallback((e) => {
|
const handleDragEnter = useLastCallback((e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onDragEnter?.(chatId);
|
onDragEnter?.(chatId);
|
||||||
}, [chatId, onDragEnter]);
|
});
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useLastCallback(() => {
|
||||||
markRenderDeleteModal();
|
markRenderDeleteModal();
|
||||||
openDeleteModal();
|
openDeleteModal();
|
||||||
}, [markRenderDeleteModal, openDeleteModal]);
|
});
|
||||||
|
|
||||||
const handleMute = useCallback(() => {
|
const handleMute = useLastCallback(() => {
|
||||||
markRenderMuteModal();
|
markRenderMuteModal();
|
||||||
openMuteModal();
|
openMuteModal();
|
||||||
}, [markRenderMuteModal, openMuteModal]);
|
});
|
||||||
|
|
||||||
const handleChatFolderChange = useCallback(() => {
|
const handleChatFolderChange = useLastCallback(() => {
|
||||||
markRenderChatFolderModal();
|
markRenderChatFolderModal();
|
||||||
openChatFolderModal();
|
openChatFolderModal();
|
||||||
}, [markRenderChatFolderModal, openChatFolderModal]);
|
});
|
||||||
|
|
||||||
const handleReport = useCallback(() => {
|
const handleReport = useLastCallback(() => {
|
||||||
markRenderReportModal();
|
markRenderReportModal();
|
||||||
openReportModal();
|
openReportModal();
|
||||||
}, [markRenderReportModal, openReportModal]);
|
});
|
||||||
|
|
||||||
const contextActions = useChatContextActions({
|
const contextActions = useChatContextActions({
|
||||||
chat,
|
chat,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef,
|
memo, useEffect, useMemo, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -18,6 +18,8 @@ import buildClassName from '../../../util/buildClassName';
|
|||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
||||||
import { selectCanShareFolder, selectTabState } from '../../../global/selectors';
|
import { selectCanShareFolder, selectTabState } from '../../../global/selectors';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
@ -180,9 +182,9 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
}, [displayedFolders, folderCountersById, lang, maxFolders, folderInvitesById, maxFolderInvites]);
|
}, [displayedFolders, folderCountersById, lang, maxFolders, folderInvitesById, maxFolderInvites]);
|
||||||
|
|
||||||
const handleSwitchTab = useCallback((index: number) => {
|
const handleSwitchTab = useLastCallback((index: number) => {
|
||||||
setActiveChatFolder({ activeChatFolder: index }, { forceOnHeavyAnimation: true });
|
setActiveChatFolder({ activeChatFolder: index }, { forceOnHeavyAnimation: true });
|
||||||
}, [setActiveChatFolder]);
|
});
|
||||||
|
|
||||||
// Prevent `activeTab` pointing at non-existing folder after update
|
// Prevent `activeTab` pointing at non-existing folder after update
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useEffect, useRef, useCallback,
|
memo, useEffect, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
@ -20,6 +20,7 @@ import { IS_MAC_OS, IS_APP } from '../../../util/windowEnvironment';
|
|||||||
import { getPinnedChatsCount, getOrderKey } from '../../../util/folderManager';
|
import { getPinnedChatsCount, getOrderKey } from '../../../util/folderManager';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||||
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
||||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
@ -133,18 +134,18 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
throttleMs: INTERSECTION_THROTTLE,
|
throttleMs: INTERSECTION_THROTTLE,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleArchivedClick = useCallback(() => {
|
const handleArchivedClick = useLastCallback(() => {
|
||||||
onLeftColumnContentChange(LeftColumnContent.Archived);
|
onLeftColumnContentChange(LeftColumnContent.Archived);
|
||||||
closeForumPanel();
|
closeForumPanel();
|
||||||
}, [closeForumPanel, onLeftColumnContentChange]);
|
});
|
||||||
|
|
||||||
const handleArchivedDragEnter = useCallback(() => {
|
const handleArchivedDragEnter = useLastCallback(() => {
|
||||||
if (shouldIgnoreDragRef.current) {
|
if (shouldIgnoreDragRef.current) {
|
||||||
shouldIgnoreDragRef.current = false;
|
shouldIgnoreDragRef.current = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleArchivedClick();
|
handleArchivedClick();
|
||||||
}, [handleArchivedClick]);
|
});
|
||||||
|
|
||||||
const handleDragEnter = useDebouncedCallback((chatId: string) => {
|
const handleDragEnter = useDebouncedCallback((chatId: string) => {
|
||||||
if (shouldIgnoreDragRef.current) {
|
if (shouldIgnoreDragRef.current) {
|
||||||
@ -154,13 +155,13 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
openChat({ id: chatId, shouldReplaceHistory: true });
|
openChat({ id: chatId, shouldReplaceHistory: true });
|
||||||
}, [openChat], DRAG_ENTER_DEBOUNCE, true);
|
}, [openChat], DRAG_ENTER_DEBOUNCE, true);
|
||||||
|
|
||||||
const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleDragLeave = useLastCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
const x = e.clientX - rect.left;
|
const x = e.clientX - rect.left;
|
||||||
const y = e.clientY - rect.top;
|
const y = e.clientY - rect.top;
|
||||||
if (x < rect.width || y < rect.y) return;
|
if (x < rect.width || y < rect.y) return;
|
||||||
shouldIgnoreDragRef.current = true;
|
shouldIgnoreDragRef.current = true;
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
function renderChats() {
|
function renderChats() {
|
||||||
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
|
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
memo, useEffect, useMemo, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
import { requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
@ -22,6 +22,7 @@ import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners';
|
|||||||
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
|
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
|
||||||
import { createLocationHash } from '../../../util/routing';
|
import { createLocationHash } from '../../../util/routing';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||||
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
import useOrderDiff from './hooks/useOrderDiff';
|
import useOrderDiff from './hooks/useOrderDiff';
|
||||||
@ -92,9 +93,9 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
const [isScrolled, setIsScrolled] = useState(false);
|
const [isScrolled, setIsScrolled] = useState(false);
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useLastCallback(() => {
|
||||||
closeForumPanel();
|
closeForumPanel();
|
||||||
}, [closeForumPanel]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!withInterfaceAnimations && !isOpen) {
|
if (!withInterfaceAnimations && !isOpen) {
|
||||||
@ -102,10 +103,10 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [withInterfaceAnimations, isOpen, onCloseAnimationEnd]);
|
}, [withInterfaceAnimations, isOpen, onCloseAnimationEnd]);
|
||||||
|
|
||||||
const handleToggleChatInfo = useCallback(() => {
|
const handleToggleChatInfo = useLastCallback(() => {
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
openChatWithInfo({ id: chat.id, shouldReplaceHistory: true });
|
openChatWithInfo({ id: chat.id, shouldReplaceHistory: true });
|
||||||
}, [chat, openChatWithInfo]);
|
});
|
||||||
|
|
||||||
const { observe } = useIntersectionObserver({
|
const { observe } = useIntersectionObserver({
|
||||||
rootRef: containerRef,
|
rootRef: containerRef,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useRef, useState,
|
memo, useEffect, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
@ -11,6 +11,8 @@ import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReduc
|
|||||||
import { IS_ELECTRON } from '../../../config';
|
import { IS_ELECTRON } from '../../../config';
|
||||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useForumPanelRender from '../../../hooks/useForumPanelRender';
|
import useForumPanelRender from '../../../hooks/useForumPanelRender';
|
||||||
@ -81,15 +83,15 @@ const LeftMain: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const isMouseInside = useRef(false);
|
const isMouseInside = useRef(false);
|
||||||
|
|
||||||
const handleMouseEnter = useCallback(() => {
|
const handleMouseEnter = useLastCallback(() => {
|
||||||
if (content !== LeftColumnContent.ChatList) {
|
if (content !== LeftColumnContent.ChatList) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isMouseInside.current = true;
|
isMouseInside.current = true;
|
||||||
setIsNewChatButtonShown(true);
|
setIsNewChatButtonShown(true);
|
||||||
}, [content]);
|
});
|
||||||
|
|
||||||
const handleMouseLeave = useCallback(() => {
|
const handleMouseLeave = useLastCallback(() => {
|
||||||
isMouseInside.current = false;
|
isMouseInside.current = false;
|
||||||
|
|
||||||
if (closeTimeout) {
|
if (closeTimeout) {
|
||||||
@ -102,36 +104,36 @@ const LeftMain: FC<OwnProps> = ({
|
|||||||
setIsNewChatButtonShown(false);
|
setIsNewChatButtonShown(false);
|
||||||
}
|
}
|
||||||
}, BUTTON_CLOSE_DELAY_MS);
|
}, BUTTON_CLOSE_DELAY_MS);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleSelectSettings = useCallback(() => {
|
const handleSelectSettings = useLastCallback(() => {
|
||||||
onContentChange(LeftColumnContent.Settings);
|
onContentChange(LeftColumnContent.Settings);
|
||||||
}, [onContentChange]);
|
});
|
||||||
|
|
||||||
const handleSelectContacts = useCallback(() => {
|
const handleSelectContacts = useLastCallback(() => {
|
||||||
onContentChange(LeftColumnContent.Contacts);
|
onContentChange(LeftColumnContent.Contacts);
|
||||||
}, [onContentChange]);
|
});
|
||||||
|
|
||||||
const handleSelectArchived = useCallback(() => {
|
const handleSelectArchived = useLastCallback(() => {
|
||||||
onContentChange(LeftColumnContent.Archived);
|
onContentChange(LeftColumnContent.Archived);
|
||||||
closeForumPanel();
|
closeForumPanel();
|
||||||
}, [closeForumPanel, onContentChange]);
|
});
|
||||||
|
|
||||||
const handleUpdateClick = useCallback(() => {
|
const handleUpdateClick = useLastCallback(() => {
|
||||||
if (IS_ELECTRON) {
|
if (IS_ELECTRON) {
|
||||||
window.electron?.installUpdate();
|
window.electron?.installUpdate();
|
||||||
} else {
|
} else {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleSelectNewChannel = useCallback(() => {
|
const handleSelectNewChannel = useLastCallback(() => {
|
||||||
onContentChange(LeftColumnContent.NewChannelStep1);
|
onContentChange(LeftColumnContent.NewChannelStep1);
|
||||||
}, [onContentChange]);
|
});
|
||||||
|
|
||||||
const handleSelectNewGroup = useCallback(() => {
|
const handleSelectNewGroup = useLastCallback(() => {
|
||||||
onContentChange(LeftColumnContent.NewGroupStep1);
|
onContentChange(LeftColumnContent.NewGroupStep1);
|
||||||
}, [onContentChange]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let autoCloseTimeout: number | undefined;
|
let autoCloseTimeout: number | undefined;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef,
|
memo, useEffect, useMemo, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -40,6 +40,8 @@ import useConnectionStatus from '../../../hooks/useConnectionStatus';
|
|||||||
import { useHotkeys } from '../../../hooks/useHotkeys';
|
import { useHotkeys } from '../../../hooks/useHotkeys';
|
||||||
import { getPromptInstall } from '../../../util/installPrompt';
|
import { getPromptInstall } from '../../../util/installPrompt';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition';
|
import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition';
|
||||||
import { useFullscreenStatus } from '../../../hooks/useFullscreen';
|
import { useFullscreenStatus } from '../../../hooks/useFullscreen';
|
||||||
import useElectronDrag from '../../../hooks/useElectronDrag';
|
import useElectronDrag from '../../../hooks/useElectronDrag';
|
||||||
@ -152,7 +154,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
lang, connectionState, isSyncing, isMessageListOpen, isConnectionStatusMinimized, !areChatsLoaded,
|
lang, connectionState, isSyncing, isMessageListOpen, isConnectionStatusMinimized, !areChatsLoaded,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleLockScreenHotkey = useCallback((e: KeyboardEvent) => {
|
const handleLockScreenHotkey = useLastCallback((e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (hasPasscode) {
|
if (hasPasscode) {
|
||||||
@ -160,7 +162,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
requestNextSettingsScreen({ screen: SettingsScreens.PasscodeDisabled });
|
requestNextSettingsScreen({ screen: SettingsScreens.PasscodeDisabled });
|
||||||
}
|
}
|
||||||
}, [hasPasscode]);
|
});
|
||||||
|
|
||||||
useHotkeys(canSetPasscode ? {
|
useHotkeys(canSetPasscode ? {
|
||||||
'Ctrl+Shift+L': handleLockScreenHotkey,
|
'Ctrl+Shift+L': handleLockScreenHotkey,
|
||||||
@ -193,29 +195,29 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
}, [hasMenu, isMobile, lang, onReset, shouldSkipTransition]);
|
}, [hasMenu, isMobile, lang, onReset, shouldSkipTransition]);
|
||||||
|
|
||||||
const handleSearchFocus = useCallback(() => {
|
const handleSearchFocus = useLastCallback(() => {
|
||||||
if (!searchQuery) {
|
if (!searchQuery) {
|
||||||
onSearchQuery('');
|
onSearchQuery('');
|
||||||
}
|
}
|
||||||
}, [searchQuery, onSearchQuery]);
|
});
|
||||||
|
|
||||||
const toggleConnectionStatus = useCallback(() => {
|
const toggleConnectionStatus = useLastCallback(() => {
|
||||||
setSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized });
|
setSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized });
|
||||||
}, [isConnectionStatusMinimized, setSettingOption]);
|
});
|
||||||
|
|
||||||
const handleSelectSaved = useCallback(() => {
|
const handleSelectSaved = useLastCallback(() => {
|
||||||
openChat({ id: currentUserId, shouldReplaceHistory: true });
|
openChat({ id: currentUserId, shouldReplaceHistory: true });
|
||||||
}, [currentUserId, openChat]);
|
});
|
||||||
|
|
||||||
const handleDarkModeToggle = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
const handleDarkModeToggle = useLastCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const newTheme = theme === 'light' ? 'dark' : 'light';
|
const newTheme = theme === 'light' ? 'dark' : 'light';
|
||||||
|
|
||||||
setSettingOption({ theme: newTheme });
|
setSettingOption({ theme: newTheme });
|
||||||
setSettingOption({ shouldUseSystemTheme: false });
|
setSettingOption({ shouldUseSystemTheme: false });
|
||||||
}, [setSettingOption, theme]);
|
});
|
||||||
|
|
||||||
const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
const handleAnimationLevelChange = useLastCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
let newLevel = animationLevel + 1;
|
let newLevel = animationLevel + 1;
|
||||||
@ -228,29 +230,29 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
setSettingOption({ animationLevel: newLevel as AnimationLevel });
|
setSettingOption({ animationLevel: newLevel as AnimationLevel });
|
||||||
updatePerformanceSettings(performanceSettings);
|
updatePerformanceSettings(performanceSettings);
|
||||||
}, [animationLevel, setSettingOption]);
|
});
|
||||||
|
|
||||||
const handleChangelogClick = useCallback(() => {
|
const handleChangelogClick = useLastCallback(() => {
|
||||||
window.open(BETA_CHANGELOG_URL, '_blank', 'noopener');
|
window.open(BETA_CHANGELOG_URL, '_blank', 'noopener');
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleSwitchToWebK = useCallback(() => {
|
const handleSwitchToWebK = useLastCallback(() => {
|
||||||
setPermanentWebVersion('K');
|
setPermanentWebVersion('K');
|
||||||
clearWebsync();
|
clearWebsync();
|
||||||
skipLockOnUnload();
|
skipLockOnUnload();
|
||||||
}, [skipLockOnUnload]);
|
});
|
||||||
|
|
||||||
const handleOpenTipsChat = useCallback(() => {
|
const handleOpenTipsChat = useLastCallback(() => {
|
||||||
openChatByUsername({ username: lang('Settings.TipsUsername') });
|
openChatByUsername({ username: lang('Settings.TipsUsername') });
|
||||||
}, [lang, openChatByUsername]);
|
});
|
||||||
|
|
||||||
const handleBugReportClick = useCallback(() => {
|
const handleBugReportClick = useLastCallback(() => {
|
||||||
openUrl({ url: FEEDBACK_URL });
|
openUrl({ url: FEEDBACK_URL });
|
||||||
}, [openUrl]);
|
});
|
||||||
|
|
||||||
const handleLockScreen = useCallback(() => {
|
const handleLockScreen = useLastCallback(() => {
|
||||||
lockScreen();
|
lockScreen();
|
||||||
}, [lockScreen]);
|
});
|
||||||
|
|
||||||
const isSearchFocused = (
|
const isSearchFocused = (
|
||||||
Boolean(globalSearchChatId)
|
Boolean(globalSearchChatId)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useCallback } from '../../../lib/teact/teact';
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
@ -28,6 +28,7 @@ import { createLocationHash } from '../../../util/routing';
|
|||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
import { getMessageAction } from '../../../global/helpers';
|
import { getMessageAction } from '../../../global/helpers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useChatListEntry from './hooks/useChatListEntry';
|
import useChatListEntry from './hooks/useChatListEntry';
|
||||||
import useTopicContextActions from './hooks/useTopicContextActions';
|
import useTopicContextActions from './hooks/useTopicContextActions';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
@ -105,19 +106,19 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
} = topic;
|
} = topic;
|
||||||
const isMuted = topic.isMuted || (topic.isMuted === undefined && chat.isMuted);
|
const isMuted = topic.isMuted || (topic.isMuted === undefined && chat.isMuted);
|
||||||
|
|
||||||
const handleOpenDeleteModal = useCallback(() => {
|
const handleOpenDeleteModal = useLastCallback(() => {
|
||||||
markRenderDeleteModal();
|
markRenderDeleteModal();
|
||||||
openDeleteModal();
|
openDeleteModal();
|
||||||
}, [markRenderDeleteModal, openDeleteModal]);
|
});
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useLastCallback(() => {
|
||||||
deleteTopic({ chatId: chat.id, topicId: topic.id });
|
deleteTopic({ chatId: chat.id, topicId: topic.id });
|
||||||
}, [chat.id, deleteTopic, topic.id]);
|
});
|
||||||
|
|
||||||
const handleMute = useCallback(() => {
|
const handleMute = useLastCallback(() => {
|
||||||
markRenderMuteModal();
|
markRenderMuteModal();
|
||||||
openMuteModal();
|
openMuteModal();
|
||||||
}, [markRenderMuteModal, openMuteModal]);
|
});
|
||||||
|
|
||||||
const { renderSubtitle, ref } = useChatListEntry({
|
const { renderSubtitle, ref } = useChatListEntry({
|
||||||
chat,
|
chat,
|
||||||
@ -138,13 +139,13 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
orderDiff,
|
orderDiff,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleOpenTopic = useCallback(() => {
|
const handleOpenTopic = useLastCallback(() => {
|
||||||
openChat({ id: chatId, threadId: topic.id, shouldReplaceHistory: true });
|
openChat({ id: chatId, threadId: topic.id, shouldReplaceHistory: true });
|
||||||
|
|
||||||
if (canScrollDown) {
|
if (canScrollDown) {
|
||||||
focusLastMessage();
|
focusLastMessage();
|
||||||
}
|
}
|
||||||
}, [openChat, chatId, topic.id, canScrollDown, focusLastMessage]);
|
});
|
||||||
|
|
||||||
const contextActions = useTopicContextActions({
|
const contextActions = useTopicContextActions({
|
||||||
topic,
|
topic,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, { memo, useCallback } from '../../../lib/teact/teact';
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -21,6 +21,7 @@ import buildClassName from '../../../util/buildClassName';
|
|||||||
import { formatPastTimeShort } from '../../../util/dateFormat';
|
import { formatPastTimeShort } from '../../../util/dateFormat';
|
||||||
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useSelectWithEnter from '../../../hooks/useSelectWithEnter';
|
import useSelectWithEnter from '../../../hooks/useSelectWithEnter';
|
||||||
@ -61,9 +62,9 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
|
|||||||
const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'micro'));
|
const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'micro'));
|
||||||
const isRoundVideo = Boolean(getMessageRoundVideo(message));
|
const isRoundVideo = Boolean(getMessageRoundVideo(message));
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
focusMessage({ chatId, messageId: message.id, shouldReplaceHistory: true });
|
focusMessage({ chatId, messageId: message.id, shouldReplaceHistory: true });
|
||||||
}, [chatId, focusMessage, message.id]);
|
});
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useCallback, useState } from '../../../lib/teact/teact';
|
import React, { memo, useState } from '../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal } from '../../../global';
|
import { getActions, getGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
@ -7,6 +7,8 @@ import type { FolderEditDispatch, FoldersState } from '../../../hooks/reducers/u
|
|||||||
|
|
||||||
import { LAYERS_ANIMATION_NAME } from '../../../util/windowEnvironment';
|
import { LAYERS_ANIMATION_NAME } from '../../../util/windowEnvironment';
|
||||||
import { selectTabState } from '../../../global/selectors';
|
import { selectTabState } from '../../../global/selectors';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer';
|
import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer';
|
||||||
|
|
||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
@ -145,7 +147,7 @@ const Settings: FC<OwnProps> = ({
|
|||||||
const [twoFaState, twoFaDispatch] = useTwoFaReducer();
|
const [twoFaState, twoFaDispatch] = useTwoFaReducer();
|
||||||
const [privacyPasscode, setPrivacyPasscode] = useState<string>('');
|
const [privacyPasscode, setPrivacyPasscode] = useState<string>('');
|
||||||
|
|
||||||
const handleReset = useCallback((forceReturnToChatList?: true | Event) => {
|
const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => {
|
||||||
const isFromSettings = selectTabState(getGlobal()).shareFolderScreen?.isFromSettings;
|
const isFromSettings = selectTabState(getGlobal()).shareFolderScreen?.isFromSettings;
|
||||||
|
|
||||||
if (currentScreen === SettingsScreens.FoldersShare) {
|
if (currentScreen === SettingsScreens.FoldersShare) {
|
||||||
@ -181,10 +183,7 @@ const Settings: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
onReset();
|
onReset();
|
||||||
}, [
|
});
|
||||||
foldersState.mode, foldersDispatch,
|
|
||||||
currentScreen, onReset, onScreenSelect,
|
|
||||||
]);
|
|
||||||
|
|
||||||
function renderCurrentSectionContent(isScreenActive: boolean, screen: SettingsScreens) {
|
function renderCurrentSectionContent(isScreenActive: boolean, screen: SettingsScreens) {
|
||||||
const privacyAllowScreens: Record<number, boolean> = {
|
const privacyAllowScreens: Record<number, boolean> = {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import { memo, useCallback, useEffect } from '../../lib/teact/teact';
|
import { memo, useEffect } from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { GlobalState, TabState } from '../../global/types';
|
import type { GlobalState, TabState } from '../../global/types';
|
||||||
@ -15,6 +15,7 @@ import {
|
|||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import { compact } from '../../util/iteratees';
|
import { compact } from '../../util/iteratees';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useRunDebounced from '../../hooks/useRunDebounced';
|
import useRunDebounced from '../../hooks/useRunDebounced';
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -34,7 +35,7 @@ const DownloadManager: FC<StateProps> = ({
|
|||||||
|
|
||||||
const runDebounced = useRunDebounced(GLOBAL_UPDATE_DEBOUNCE, true);
|
const runDebounced = useRunDebounced(GLOBAL_UPDATE_DEBOUNCE, true);
|
||||||
|
|
||||||
const handleMessageDownloaded = useCallback((message: ApiMessage) => {
|
const handleMessageDownloaded = useLastCallback((message: ApiMessage) => {
|
||||||
downloadedMessages.add(message);
|
downloadedMessages.add(message);
|
||||||
runDebounced(() => {
|
runDebounced(() => {
|
||||||
if (downloadedMessages.size) {
|
if (downloadedMessages.size) {
|
||||||
@ -42,7 +43,7 @@ const DownloadManager: FC<StateProps> = ({
|
|||||||
downloadedMessages.clear();
|
downloadedMessages.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [cancelMessagesMediaDownload, runDebounced]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// No need for expensive global updates on messages, so we avoid them
|
// No need for expensive global updates on messages, so we avoid them
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useEffect, memo, useCallback, useState, useRef, useLayoutEffect,
|
useEffect, memo, useState, useRef, useLayoutEffect,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { addExtraClass } from '../../lib/teact/teact-dom';
|
import { addExtraClass } from '../../lib/teact/teact-dom';
|
||||||
import { requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
@ -44,6 +44,7 @@ import { parseInitialLocationHash, parseLocationHash } from '../../util/routing'
|
|||||||
import { Bundles, loadBundle } from '../../util/moduleLoader';
|
import { Bundles, loadBundle } from '../../util/moduleLoader';
|
||||||
import updateIcon from '../../util/updateIcon';
|
import updateIcon from '../../util/updateIcon';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||||
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
||||||
import useBeforeUnload from '../../hooks/useBeforeUnload';
|
import useBeforeUnload from '../../hooks/useBeforeUnload';
|
||||||
@ -487,11 +488,11 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
isFullscreen && 'is-fullscreen',
|
isFullscreen && 'is-fullscreen',
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleBlur = useCallback(() => {
|
const handleBlur = useLastCallback(() => {
|
||||||
onTabFocusChange({ isBlurred: true });
|
onTabFocusChange({ isBlurred: true });
|
||||||
}, [onTabFocusChange]);
|
});
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleFocus = useLastCallback(() => {
|
||||||
onTabFocusChange({ isBlurred: false });
|
onTabFocusChange({ isBlurred: false });
|
||||||
|
|
||||||
if (!document.title.includes(INACTIVE_MARKER)) {
|
if (!document.title.includes(INACTIVE_MARKER)) {
|
||||||
@ -499,15 +500,15 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateIcon(false);
|
updateIcon(false);
|
||||||
}, [onTabFocusChange, updatePageTitle]);
|
});
|
||||||
|
|
||||||
const handleStickerSetModalClose = useCallback(() => {
|
const handleStickerSetModalClose = useLastCallback(() => {
|
||||||
closeStickerSetModal();
|
closeStickerSetModal();
|
||||||
}, [closeStickerSetModal]);
|
});
|
||||||
|
|
||||||
const handleCustomEmojiSetsModalClose = useCallback(() => {
|
const handleCustomEmojiSetsModalClose = useLastCallback(() => {
|
||||||
closeCustomEmojiSets();
|
closeCustomEmojiSets();
|
||||||
}, [closeCustomEmojiSets]);
|
});
|
||||||
|
|
||||||
// Online status and browser tab indicators
|
// Online status and browser tab indicators
|
||||||
useBackgroundMode(handleBlur, handleFocus);
|
useBackgroundMode(handleBlur, handleFocus);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef,
|
memo, useEffect, useMemo, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -54,6 +54,7 @@ import MediaViewerSlides from './MediaViewerSlides';
|
|||||||
import SenderInfo from './SenderInfo';
|
import SenderInfo from './SenderInfo';
|
||||||
|
|
||||||
import './MediaViewer.scss';
|
import './MediaViewer.scss';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
@ -230,10 +231,10 @@ const MediaViewer: FC<StateProps> = ({
|
|||||||
bestImageData, prevBestImageData, dimensions, isVideo, hasFooter,
|
bestImageData, prevBestImageData, dimensions, isVideo, hasFooter,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleClose = useCallback(() => closeMediaViewer(), [closeMediaViewer]);
|
const handleClose = useLastCallback(() => closeMediaViewer());
|
||||||
|
|
||||||
const mediaIdRef = useStateRef(mediaId);
|
const mediaIdRef = useStateRef(mediaId);
|
||||||
const handleFooterClick = useCallback(() => {
|
const handleFooterClick = useLastCallback(() => {
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
const currentMediaId = mediaIdRef.current;
|
const currentMediaId = mediaIdRef.current;
|
||||||
@ -248,16 +249,16 @@ const MediaViewer: FC<StateProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
focusMessage({ chatId, threadId, messageId: currentMediaId });
|
focusMessage({ chatId, threadId, messageId: currentMediaId });
|
||||||
}
|
}
|
||||||
}, [handleClose, mediaIdRef, chatId, isMobile, threadId]);
|
});
|
||||||
|
|
||||||
const handleForward = useCallback(() => {
|
const handleForward = useLastCallback(() => {
|
||||||
openForwardMenu({
|
openForwardMenu({
|
||||||
fromChatId: chatId!,
|
fromChatId: chatId!,
|
||||||
messageIds: [mediaId!],
|
messageIds: [mediaId!],
|
||||||
});
|
});
|
||||||
}, [openForwardMenu, chatId, mediaId]);
|
});
|
||||||
|
|
||||||
const selectMedia = useCallback((id?: number) => {
|
const selectMedia = useLastCallback((id?: number) => {
|
||||||
openMediaViewer({
|
openMediaViewer({
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
@ -267,7 +268,7 @@ const MediaViewer: FC<StateProps> = ({
|
|||||||
}, {
|
}, {
|
||||||
forceOnHeavyAnimation: true,
|
forceOnHeavyAnimation: true,
|
||||||
});
|
});
|
||||||
}, [avatarOwner?.id, chatId, openMediaViewer, origin, threadId]);
|
});
|
||||||
|
|
||||||
useEffect(() => (isOpen ? captureEscKeyListener(() => {
|
useEffect(() => (isOpen ? captureEscKeyListener(() => {
|
||||||
handleClose();
|
handleClose();
|
||||||
@ -281,7 +282,7 @@ const MediaViewer: FC<StateProps> = ({
|
|||||||
|
|
||||||
const mediaIdsRef = useStateRef(mediaIds);
|
const mediaIdsRef = useStateRef(mediaIds);
|
||||||
|
|
||||||
const getMediaId = useCallback((fromId?: number, direction?: number): number | undefined => {
|
const getMediaId = useLastCallback((fromId?: number, direction?: number): number | undefined => {
|
||||||
if (fromId === undefined) return undefined;
|
if (fromId === undefined) return undefined;
|
||||||
const mIds = mediaIdsRef.current;
|
const mIds = mediaIdsRef.current;
|
||||||
const index = mIds.indexOf(fromId);
|
const index = mIds.indexOf(fromId);
|
||||||
@ -289,9 +290,9 @@ const MediaViewer: FC<StateProps> = ({
|
|||||||
return mIds[index + direction];
|
return mIds[index + direction];
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [mediaIdsRef]);
|
});
|
||||||
|
|
||||||
const handleBeforeDelete = useCallback(() => {
|
const handleBeforeDelete = useLastCallback(() => {
|
||||||
if (mediaIds.length <= 1) {
|
if (mediaIds.length <= 1) {
|
||||||
handleClose();
|
handleClose();
|
||||||
return;
|
return;
|
||||||
@ -300,7 +301,7 @@ const MediaViewer: FC<StateProps> = ({
|
|||||||
// Before deleting, select previous media or the first one
|
// Before deleting, select previous media or the first one
|
||||||
index = index > 0 ? index - 1 : 0;
|
index = index > 0 ? index - 1 : 0;
|
||||||
selectMedia(mediaIds[index]);
|
selectMedia(mediaIds[index]);
|
||||||
}, [handleClose, mediaId, mediaIds, selectMedia]);
|
});
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import React, {
|
import React, { memo, useMemo } from '../../lib/teact/teact';
|
||||||
memo,
|
|
||||||
useCallback,
|
|
||||||
useMemo,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
@ -21,6 +17,7 @@ import {
|
|||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { getMessageMediaFormat, getMessageMediaHash, isUserId } from '../../global/helpers';
|
import { getMessageMediaFormat, getMessageMediaHash, isUserId } from '../../global/helpers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
|
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -100,27 +97,27 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
|||||||
message && getMessageMediaFormat(message, 'download'),
|
message && getMessageMediaFormat(message, 'download'),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDownloadClick = useCallback(() => {
|
const handleDownloadClick = useLastCallback(() => {
|
||||||
if (isDownloading) {
|
if (isDownloading) {
|
||||||
cancelMessageMediaDownload({ message: message! });
|
cancelMessageMediaDownload({ message: message! });
|
||||||
} else {
|
} else {
|
||||||
downloadMessageMedia({ message: message! });
|
downloadMessageMedia({ message: message! });
|
||||||
}
|
}
|
||||||
}, [cancelMessageMediaDownload, downloadMessageMedia, isDownloading, message]);
|
});
|
||||||
|
|
||||||
const handleZoomOut = useCallback(() => {
|
const handleZoomOut = useLastCallback(() => {
|
||||||
const zoomChange = getZoomChange();
|
const zoomChange = getZoomChange();
|
||||||
const change = zoomChange < 0 ? zoomChange : 0;
|
const change = zoomChange < 0 ? zoomChange : 0;
|
||||||
setZoomChange(change - 1);
|
setZoomChange(change - 1);
|
||||||
}, [getZoomChange, setZoomChange]);
|
});
|
||||||
|
|
||||||
const handleZoomIn = useCallback(() => {
|
const handleZoomIn = useLastCallback(() => {
|
||||||
const zoomChange = getZoomChange();
|
const zoomChange = getZoomChange();
|
||||||
const change = zoomChange > 0 ? zoomChange : 0;
|
const change = zoomChange > 0 ? zoomChange : 0;
|
||||||
setZoomChange(change + 1);
|
setZoomChange(change + 1);
|
||||||
}, [getZoomChange, setZoomChange]);
|
});
|
||||||
|
|
||||||
const handleUpdate = useCallback(() => {
|
const handleUpdate = useLastCallback(() => {
|
||||||
if (!avatarPhoto || !avatarOwnerId) return;
|
if (!avatarPhoto || !avatarOwnerId) return;
|
||||||
if (isUserId(avatarOwnerId)) {
|
if (isUserId(avatarOwnerId)) {
|
||||||
updateProfilePhoto({ photo: avatarPhoto });
|
updateProfilePhoto({ photo: avatarPhoto });
|
||||||
@ -128,7 +125,7 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
|||||||
updateChatPhoto({ chatId: avatarOwnerId, photo: avatarPhoto });
|
updateChatPhoto({ chatId: avatarOwnerId, photo: avatarPhoto });
|
||||||
}
|
}
|
||||||
selectMedia(0);
|
selectMedia(0);
|
||||||
}, [avatarPhoto, avatarOwnerId, selectMedia, updateProfilePhoto, updateChatPhoto]);
|
});
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../global';
|
import { withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -18,6 +18,8 @@ import { renderMessageText } from '../common/helpers/renderMessageText';
|
|||||||
import stopEvent from '../../util/stopEvent';
|
import stopEvent from '../../util/stopEvent';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { useMediaProps } from './hooks/useMediaProps';
|
import { useMediaProps } from './hooks/useMediaProps';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useAppLayout from '../../hooks/useAppLayout';
|
import useAppLayout from '../../hooks/useAppLayout';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useControlsSignal from './hooks/useControlsSignal';
|
import useControlsSignal from './hooks/useControlsSignal';
|
||||||
@ -102,9 +104,9 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
|
|||||||
const isOpen = Boolean(avatarOwner || mediaId);
|
const isOpen = Boolean(avatarOwner || mediaId);
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
|
|
||||||
const toggleControlsOnMove = useCallback(() => {
|
const toggleControlsOnMove = useLastCallback(() => {
|
||||||
toggleControls(true);
|
toggleControls(true);
|
||||||
}, [toggleControls]);
|
});
|
||||||
|
|
||||||
if (avatarOwner || actionPhoto) {
|
if (avatarOwner || actionPhoto) {
|
||||||
if (!isVideoAvatar) {
|
if (!isVideoAvatar) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
|
memo, useEffect, useLayoutEffect, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { MediaViewerOrigin } from '../../types';
|
import type { MediaViewerOrigin } from '../../types';
|
||||||
@ -13,6 +13,7 @@ import { IS_IOS, IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
|||||||
import { clamp, isBetween, round } from '../../util/math';
|
import { clamp, isBetween, round } from '../../util/math';
|
||||||
import { debounce } from '../../util/schedulers';
|
import { debounce } from '../../util/schedulers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useDebouncedCallback from '../../hooks/useDebouncedCallback';
|
import useDebouncedCallback from '../../hooks/useDebouncedCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useTimeout from '../../hooks/useTimeout';
|
import useTimeout from '../../hooks/useTimeout';
|
||||||
@ -134,13 +135,13 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
|||||||
const shouldCloseOnVideo = Boolean(isGif && !IS_IOS);
|
const shouldCloseOnVideo = Boolean(isGif && !IS_IOS);
|
||||||
const clickXThreshold = IS_TOUCH_ENV ? 40 : windowWidth / 10;
|
const clickXThreshold = IS_TOUCH_ENV ? 40 : windowWidth / 10;
|
||||||
|
|
||||||
const handleControlsVisibility = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
const handleControlsVisibility = useLastCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||||
if (!IS_TOUCH_ENV) return;
|
if (!IS_TOUCH_ENV) return;
|
||||||
const isFooter = windowHeight - e.pageY < CLICK_Y_THRESHOLD;
|
const isFooter = windowHeight - e.pageY < CLICK_Y_THRESHOLD;
|
||||||
if (!isFooter && e.pageX < clickXThreshold) return;
|
if (!isFooter && e.pageX < clickXThreshold) return;
|
||||||
if (!isFooter && e.pageX > windowWidth - clickXThreshold) return;
|
if (!isFooter && e.pageX > windowWidth - clickXThreshold) return;
|
||||||
setControlsVisible(!getControlsVisible());
|
setControlsVisible(!getControlsVisible());
|
||||||
}, [clickXThreshold, getControlsVisible, setControlsVisible, windowHeight, windowWidth]);
|
});
|
||||||
|
|
||||||
useTimeout(() => setControlsVisible(true), ANIMATION_DURATION);
|
useTimeout(() => setControlsVisible(true), ANIMATION_DURATION);
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import React, {
|
import React, {
|
||||||
useRef, useState, useCallback, useEffect, memo, useMemo, useLayoutEffect,
|
useRef, useState, useEffect, memo, useMemo, useLayoutEffect,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { BufferedRange } from '../../hooks/useBuffering';
|
import type { BufferedRange } from '../../hooks/useBuffering';
|
||||||
import type { ApiDimensions } from '../../api/types';
|
import type { ApiDimensions } from '../../api/types';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useSignal from '../../hooks/useSignal';
|
import useSignal from '../../hooks/useSignal';
|
||||||
import useCurrentTimeSignal from './hooks/currentTimeSignal';
|
import useCurrentTimeSignal from './hooks/currentTimeSignal';
|
||||||
|
|
||||||
@ -65,11 +66,11 @@ const SeekLine: React.FC<OwnProps> = ({
|
|||||||
return getPreviewDimensions(posterSize?.width || 0, posterSize?.height || 0);
|
return getPreviewDimensions(posterSize?.width || 0, posterSize?.height || 0);
|
||||||
}, [posterSize]);
|
}, [posterSize]);
|
||||||
|
|
||||||
const setPreview = useCallback((time: number) => {
|
const setPreview = useLastCallback((time: number) => {
|
||||||
time = Math.floor(time);
|
time = Math.floor(time);
|
||||||
setPreviewTime(time);
|
setPreviewTime(time);
|
||||||
renderVideoPreview(time);
|
renderVideoPreview(time);
|
||||||
}, [setPreviewTime]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isPreviewDisabled || !url || !isReady) return undefined;
|
if (isPreviewDisabled || !url || !isReady) return undefined;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { useCallback } from '../../lib/teact/teact';
|
import React from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiMessage, ApiUser } from '../../api/types';
|
import type { ApiChat, ApiMessage, ApiUser } from '../../api/types';
|
||||||
@ -13,6 +13,8 @@ import {
|
|||||||
selectSender,
|
selectSender,
|
||||||
selectUser,
|
selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useAppLayout from '../../hooks/useAppLayout';
|
import useAppLayout from '../../hooks/useAppLayout';
|
||||||
|
|
||||||
@ -50,7 +52,7 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
|
|
||||||
const handleFocusMessage = useCallback(() => {
|
const handleFocusMessage = useLastCallback(() => {
|
||||||
closeMediaViewer();
|
closeMediaViewer();
|
||||||
|
|
||||||
if (!chatId || !messageId) return;
|
if (!chatId || !messageId) return;
|
||||||
@ -63,7 +65,7 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
focusMessage({ chatId, messageId });
|
focusMessage({ chatId, messageId });
|
||||||
}
|
}
|
||||||
}, [chatId, isMobile, focusMessage, toggleChatInfo, messageId, closeMediaViewer]);
|
});
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useRef, useState,
|
memo, useEffect, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
@ -10,6 +10,8 @@ import { IS_IOS, IS_TOUCH_ENV, IS_YA_BROWSER } from '../../util/windowEnvironmen
|
|||||||
import safePlay from '../../util/safePlay';
|
import safePlay from '../../util/safePlay';
|
||||||
import stopEvent from '../../util/stopEvent';
|
import stopEvent from '../../util/stopEvent';
|
||||||
import { clamp } from '../../util/math';
|
import { clamp } from '../../util/math';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useBuffering from '../../hooks/useBuffering';
|
import useBuffering from '../../hooks/useBuffering';
|
||||||
import useFullscreen from '../../hooks/useFullscreen';
|
import useFullscreen from '../../hooks/useFullscreen';
|
||||||
import usePictureInPicture from '../../hooks/usePictureInPicture';
|
import usePictureInPicture from '../../hooks/usePictureInPicture';
|
||||||
@ -83,16 +85,16 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
const duration = videoRef.current?.duration || 0;
|
const duration = videoRef.current?.duration || 0;
|
||||||
const isLooped = isGif || duration <= MAX_LOOP_DURATION;
|
const isLooped = isGif || duration <= MAX_LOOP_DURATION;
|
||||||
|
|
||||||
const handleEnterFullscreen = useCallback(() => {
|
const handleEnterFullscreen = useLastCallback(() => {
|
||||||
// Yandex browser doesn't support PIP when video is hidden
|
// Yandex browser doesn't support PIP when video is hidden
|
||||||
if (IS_YA_BROWSER) return;
|
if (IS_YA_BROWSER) return;
|
||||||
setMediaViewerHidden({ isHidden: true });
|
setMediaViewerHidden({ isHidden: true });
|
||||||
}, [setMediaViewerHidden]);
|
});
|
||||||
|
|
||||||
const handleLeaveFullscreen = useCallback(() => {
|
const handleLeaveFullscreen = useLastCallback(() => {
|
||||||
if (IS_YA_BROWSER) return;
|
if (IS_YA_BROWSER) return;
|
||||||
setMediaViewerHidden({ isHidden: false });
|
setMediaViewerHidden({ isHidden: false });
|
||||||
}, [setMediaViewerHidden]);
|
});
|
||||||
|
|
||||||
const [
|
const [
|
||||||
isPictureInPictureSupported,
|
isPictureInPictureSupported,
|
||||||
@ -102,17 +104,17 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const [, toggleControls, lockControls] = useControlsSignal();
|
const [, toggleControls, lockControls] = useControlsSignal();
|
||||||
|
|
||||||
const handleVideoMove = useCallback(() => {
|
const handleVideoMove = useLastCallback(() => {
|
||||||
toggleControls(true);
|
toggleControls(true);
|
||||||
}, [toggleControls]);
|
});
|
||||||
|
|
||||||
const handleVideoLeave = useCallback((e) => {
|
const handleVideoLeave = useLastCallback((e) => {
|
||||||
const bounds = videoRef.current?.getBoundingClientRect();
|
const bounds = videoRef.current?.getBoundingClientRect();
|
||||||
if (!bounds) return;
|
if (!bounds) return;
|
||||||
if (e.clientX < bounds.left || e.clientX > bounds.right || e.clientY < bounds.top || e.clientY > bounds.bottom) {
|
if (e.clientX < bounds.left || e.clientX > bounds.right || e.clientY < bounds.top || e.clientY > bounds.bottom) {
|
||||||
toggleControls(false);
|
toggleControls(false);
|
||||||
}
|
}
|
||||||
}, [toggleControls]);
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isReady, isBuffered, bufferedRanges, bufferingHandlers, bufferedProgress,
|
isReady, isBuffered, bufferedRanges, bufferingHandlers, bufferedProgress,
|
||||||
@ -150,7 +152,7 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
videoRef.current!.playbackRate = playbackRate;
|
videoRef.current!.playbackRate = playbackRate;
|
||||||
}, [playbackRate]);
|
}, [playbackRate]);
|
||||||
|
|
||||||
const togglePlayState = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent> | KeyboardEvent) => {
|
const togglePlayState = useLastCallback((e: React.MouseEvent<HTMLElement, MouseEvent> | KeyboardEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
videoRef.current!.pause();
|
videoRef.current!.pause();
|
||||||
@ -159,9 +161,9 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
safePlay(videoRef.current!);
|
safePlay(videoRef.current!);
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
}
|
}
|
||||||
}, [isPlaying]);
|
});
|
||||||
|
|
||||||
const handleClick = useCallback((e: React.MouseEvent<HTMLVideoElement, MouseEvent>) => {
|
const handleClick = useLastCallback((e: React.MouseEvent<HTMLVideoElement, MouseEvent>) => {
|
||||||
if (isClickDisabled) {
|
if (isClickDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -170,12 +172,12 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
togglePlayState(e);
|
togglePlayState(e);
|
||||||
}
|
}
|
||||||
}, [onClose, shouldCloseOnClick, togglePlayState, isClickDisabled]);
|
});
|
||||||
|
|
||||||
useVideoCleanup(videoRef, []);
|
useVideoCleanup(videoRef, []);
|
||||||
const [, setCurrentTime] = useCurrentTimeSignal();
|
const [, setCurrentTime] = useCurrentTimeSignal();
|
||||||
|
|
||||||
const handleTimeUpdate = useCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
|
const handleTimeUpdate = useLastCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
|
||||||
const video = e.currentTarget;
|
const video = e.currentTarget;
|
||||||
if (video.readyState >= MIN_READY_STATE) {
|
if (video.readyState >= MIN_READY_STATE) {
|
||||||
setCurrentTime(video.currentTime);
|
setCurrentTime(video.currentTime);
|
||||||
@ -184,40 +186,40 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
setCurrentTime(0);
|
setCurrentTime(0);
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
}
|
}
|
||||||
}, [isLooped, setCurrentTime]);
|
});
|
||||||
|
|
||||||
const handleEnded = useCallback(() => {
|
const handleEnded = useLastCallback(() => {
|
||||||
if (isLooped) return;
|
if (isLooped) return;
|
||||||
setCurrentTime(0);
|
setCurrentTime(0);
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
toggleControls(true);
|
toggleControls(true);
|
||||||
}, [isLooped, setCurrentTime, toggleControls]);
|
});
|
||||||
|
|
||||||
const handleFullscreenChange = useCallback(() => {
|
const handleFullscreenChange = useLastCallback(() => {
|
||||||
if (isFullscreen && exitFullscreen) {
|
if (isFullscreen && exitFullscreen) {
|
||||||
exitFullscreen();
|
exitFullscreen();
|
||||||
} else if (!isFullscreen && setFullscreen) {
|
} else if (!isFullscreen && setFullscreen) {
|
||||||
setFullscreen();
|
setFullscreen();
|
||||||
}
|
}
|
||||||
}, [exitFullscreen, isFullscreen, setFullscreen]);
|
});
|
||||||
|
|
||||||
const handleSeek = useCallback((position: number) => {
|
const handleSeek = useLastCallback((position: number) => {
|
||||||
videoRef.current!.currentTime = position;
|
videoRef.current!.currentTime = position;
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleVolumeChange = useCallback((newVolume: number) => {
|
const handleVolumeChange = useLastCallback((newVolume: number) => {
|
||||||
setMediaViewerVolume({ volume: newVolume / 100 });
|
setMediaViewerVolume({ volume: newVolume / 100 });
|
||||||
}, [setMediaViewerVolume]);
|
});
|
||||||
|
|
||||||
const handleVolumeMuted = useCallback(() => {
|
const handleVolumeMuted = useLastCallback(() => {
|
||||||
// Browser requires explicit user interaction to keep video playing after unmuting
|
// Browser requires explicit user interaction to keep video playing after unmuting
|
||||||
videoRef.current!.muted = !videoRef.current!.muted;
|
videoRef.current!.muted = !videoRef.current!.muted;
|
||||||
setMediaViewerMuted({ isMuted: !isMuted });
|
setMediaViewerMuted({ isMuted: !isMuted });
|
||||||
}, [isMuted, setMediaViewerMuted]);
|
});
|
||||||
|
|
||||||
const handlePlaybackRateChange = useCallback((newPlaybackRate: number) => {
|
const handlePlaybackRateChange = useLastCallback((newPlaybackRate: number) => {
|
||||||
setMediaViewerPlaybackRate({ playbackRate: newPlaybackRate });
|
setMediaViewerPlaybackRate({ playbackRate: newPlaybackRate });
|
||||||
}, [setMediaViewerPlaybackRate]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMediaViewerOpen) return undefined;
|
if (!isMediaViewerOpen) return undefined;
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback, memo, useEffect, useMemo, useLayoutEffect,
|
memo, useEffect, useMemo, useLayoutEffect,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { BufferedRange } from '../../hooks/useBuffering';
|
import type { BufferedRange } from '../../hooks/useBuffering';
|
||||||
import type { ApiDimensions } from '../../api/types';
|
import type { ApiDimensions } from '../../api/types';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import useAppLayout from '../../hooks/useAppLayout';
|
import useAppLayout from '../../hooks/useAppLayout';
|
||||||
@ -46,7 +47,7 @@ type OwnProps = {
|
|||||||
playbackRate: number;
|
playbackRate: number;
|
||||||
posterSize?: ApiDimensions;
|
posterSize?: ApiDimensions;
|
||||||
onChangeFullscreen: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
onChangeFullscreen: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||||
onPictureInPictureChange?: () => void ;
|
onPictureInPictureChange?: () => void;
|
||||||
onVolumeClick: () => void;
|
onVolumeClick: () => void;
|
||||||
onVolumeChange: (volume: number) => void;
|
onVolumeChange: (volume: number) => void;
|
||||||
onPlaybackRateChange: (playbackRate: number) => void;
|
onPlaybackRateChange: (playbackRate: number) => void;
|
||||||
@ -136,14 +137,14 @@ const VideoPlayerControls: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const handleSeek = useCallback((position: number) => {
|
const handleSeek = useLastCallback((position: number) => {
|
||||||
setIsSeeking(false);
|
setIsSeeking(false);
|
||||||
onSeek(position);
|
onSeek(position);
|
||||||
}, [onSeek, setIsSeeking]);
|
});
|
||||||
|
|
||||||
const handleSeekStart = useCallback(() => {
|
const handleSeekStart = useLastCallback(() => {
|
||||||
setIsSeeking(true);
|
setIsSeeking(true);
|
||||||
}, [setIsSeeking]);
|
});
|
||||||
|
|
||||||
const volumeIcon = useMemo(() => {
|
const volumeIcon = useMemo(() => {
|
||||||
if (volume === 0 || isMuted) return 'icon-muted';
|
if (volume === 0 || isMuted) return 'icon-muted';
|
||||||
@ -265,4 +266,5 @@ function renderTime(currentTime: number, duration: number) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(VideoPlayerControls);
|
export default memo(VideoPlayerControls);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef,
|
memo, useEffect, useMemo, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -24,6 +24,8 @@ import { getMessageHtmlId, isChatChannel } from '../../global/helpers';
|
|||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { renderActionMessageText } from '../common/helpers/renderActionMessageText';
|
import { renderActionMessageText } from '../common/helpers/renderActionMessageText';
|
||||||
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useEnsureMessage from '../../hooks/useEnsureMessage';
|
import useEnsureMessage from '../../hooks/useEnsureMessage';
|
||||||
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
@ -149,7 +151,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
: undefined;
|
: undefined;
|
||||||
}, [targetUserIds, usersById]);
|
}, [targetUserIds, usersById]);
|
||||||
|
|
||||||
const renderContent = useCallback(() => {
|
const renderContent = useLastCallback(() => {
|
||||||
return renderActionMessageText(
|
return renderActionMessageText(
|
||||||
lang,
|
lang,
|
||||||
message,
|
message,
|
||||||
@ -163,11 +165,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
observeIntersectionForLoading,
|
observeIntersectionForLoading,
|
||||||
observeIntersectionForPlaying,
|
observeIntersectionForPlaying,
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
[
|
|
||||||
isEmbedded, lang, message, observeIntersectionForLoading, observeIntersectionForPlaying, senderChat,
|
|
||||||
senderUser, targetChatId, targetMessage, targetUsers, topic,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition,
|
isContextMenuOpen, contextMenuPosition,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useCallback, useState } from '../../lib/teact/teact';
|
import React, { memo, useState } from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
@ -9,6 +9,8 @@ import { ApiMediaFormat, MAIN_THREAD_ID } from '../../api/types';
|
|||||||
|
|
||||||
import { getMessageMediaHash } from '../../global/helpers';
|
import { getMessageMediaHash } from '../../global/helpers';
|
||||||
import * as mediaLoader from '../../util/mediaLoader';
|
import * as mediaLoader from '../../util/mediaLoader';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -38,7 +40,7 @@ const ActionMessageSuggestedAvatar: FC<OwnProps> = ({
|
|||||||
const suggestedPhotoUrl = useMedia(getMessageMediaHash(message, 'full'));
|
const suggestedPhotoUrl = useMedia(getMessageMediaHash(message, 'full'));
|
||||||
const isVideo = message.content.action!.photo?.isVideo;
|
const isVideo = message.content.action!.photo?.isVideo;
|
||||||
|
|
||||||
const showAvatarNotification = useCallback(() => {
|
const showAvatarNotification = useLastCallback(() => {
|
||||||
showNotification({
|
showNotification({
|
||||||
title: lang('ApplyAvatarHintTitle'),
|
title: lang('ApplyAvatarHintTitle'),
|
||||||
message: lang('ApplyAvatarHint'),
|
message: lang('ApplyAvatarHint'),
|
||||||
@ -50,19 +52,19 @@ const ActionMessageSuggestedAvatar: FC<OwnProps> = ({
|
|||||||
},
|
},
|
||||||
actionText: lang('Open'),
|
actionText: lang('Open'),
|
||||||
});
|
});
|
||||||
}, [lang, showNotification]);
|
});
|
||||||
|
|
||||||
const handleSetSuggestedAvatar = useCallback((file: File) => {
|
const handleSetSuggestedAvatar = useLastCallback((file: File) => {
|
||||||
setCropModalBlob(undefined);
|
setCropModalBlob(undefined);
|
||||||
uploadProfilePhoto({ file });
|
uploadProfilePhoto({ file });
|
||||||
showAvatarNotification();
|
showAvatarNotification();
|
||||||
}, [showAvatarNotification, uploadProfilePhoto]);
|
});
|
||||||
|
|
||||||
const handleCloseCropModal = useCallback(() => {
|
const handleCloseCropModal = useLastCallback(() => {
|
||||||
setCropModalBlob(undefined);
|
setCropModalBlob(undefined);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleSetVideo = useCallback(async () => {
|
const handleSetVideo = useLastCallback(async () => {
|
||||||
closeVideoModal();
|
closeVideoModal();
|
||||||
showAvatarNotification();
|
showAvatarNotification();
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ const ActionMessageSuggestedAvatar: FC<OwnProps> = ({
|
|||||||
isVideo: true,
|
isVideo: true,
|
||||||
videoTs: photo.videoSizes?.find((l) => l.videoStartTs !== undefined)?.videoStartTs,
|
videoTs: photo.videoSizes?.find((l) => l.videoStartTs !== undefined)?.videoStartTs,
|
||||||
});
|
});
|
||||||
}, [closeVideoModal, message.content.action, showAvatarNotification, uploadProfilePhoto]);
|
});
|
||||||
|
|
||||||
const handleViewSuggestedAvatar = async () => {
|
const handleViewSuggestedAvatar = async () => {
|
||||||
if (!isOutgoing && suggestedPhotoUrl) {
|
if (!isOutgoing && suggestedPhotoUrl) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useMemo, useRef } from '../../lib/teact/teact';
|
import React, { useMemo, useRef } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
@ -33,6 +33,7 @@ import DropdownMenu from '../ui/DropdownMenu';
|
|||||||
import MenuItem from '../ui/MenuItem';
|
import MenuItem from '../ui/MenuItem';
|
||||||
|
|
||||||
import './AudioPlayer.scss';
|
import './AudioPlayer.scss';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
@ -124,39 +125,39 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
|
|||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref);
|
} = useContextMenuHandlers(ref);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
focusMessage({ chatId: message.chatId, messageId: message.id });
|
focusMessage({ chatId: message.chatId, messageId: message.id });
|
||||||
}, [focusMessage, message.chatId, message.id]);
|
});
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useLastCallback(() => {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
playPause();
|
playPause();
|
||||||
}
|
}
|
||||||
closeAudioPlayer();
|
closeAudioPlayer();
|
||||||
clearMediaSession();
|
clearMediaSession();
|
||||||
stop();
|
stop();
|
||||||
}, [closeAudioPlayer, isPlaying, playPause, stop]);
|
});
|
||||||
|
|
||||||
const handleVolumeChange = useCallback((value: number) => {
|
const handleVolumeChange = useLastCallback((value: number) => {
|
||||||
setAudioPlayerVolume({ volume: value / 100 });
|
setAudioPlayerVolume({ volume: value / 100 });
|
||||||
|
|
||||||
setVolume(value / 100);
|
setVolume(value / 100);
|
||||||
}, [setAudioPlayerVolume, setVolume]);
|
});
|
||||||
|
|
||||||
const handleVolumeClick = useCallback(() => {
|
const handleVolumeClick = useLastCallback(() => {
|
||||||
if (IS_TOUCH_ENV && !IS_IOS) return;
|
if (IS_TOUCH_ENV && !IS_IOS) return;
|
||||||
toggleMuted();
|
toggleMuted();
|
||||||
setAudioPlayerMuted({ isMuted: !isMuted });
|
setAudioPlayerMuted({ isMuted: !isMuted });
|
||||||
}, [isMuted, setAudioPlayerMuted, toggleMuted]);
|
});
|
||||||
|
|
||||||
const updatePlaybackRate = useCallback((newRate: number, isActive = true) => {
|
const updatePlaybackRate = useLastCallback((newRate: number, isActive = true) => {
|
||||||
const rate = PLAYBACK_RATES[newRate];
|
const rate = PLAYBACK_RATES[newRate];
|
||||||
const shouldBeActive = newRate !== REGULAR_PLAYBACK_RATE && isActive;
|
const shouldBeActive = newRate !== REGULAR_PLAYBACK_RATE && isActive;
|
||||||
setAudioPlayerPlaybackRate({ playbackRate: rate, isPlaybackRateActive: shouldBeActive });
|
setAudioPlayerPlaybackRate({ playbackRate: rate, isPlaybackRateActive: shouldBeActive });
|
||||||
setPlaybackRate(shouldBeActive ? rate : REGULAR_PLAYBACK_RATE);
|
setPlaybackRate(shouldBeActive ? rate : REGULAR_PLAYBACK_RATE);
|
||||||
}, [setAudioPlayerPlaybackRate, setPlaybackRate]);
|
});
|
||||||
|
|
||||||
const handlePlaybackClick = useCallback(() => {
|
const handlePlaybackClick = useLastCallback(() => {
|
||||||
handleContextMenuClose();
|
handleContextMenuClose();
|
||||||
const oldRate = Number(Object.entries(PLAYBACK_RATES).find(([, rate]) => rate === playbackRate)?.[0])
|
const oldRate = Number(Object.entries(PLAYBACK_RATES).find(([, rate]) => rate === playbackRate)?.[0])
|
||||||
|| REGULAR_PLAYBACK_RATE;
|
|| REGULAR_PLAYBACK_RATE;
|
||||||
@ -166,9 +167,9 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
|
|||||||
newIsActive && oldRate === REGULAR_PLAYBACK_RATE ? DEFAULT_FAST_PLAYBACK_RATE : oldRate,
|
newIsActive && oldRate === REGULAR_PLAYBACK_RATE ? DEFAULT_FAST_PLAYBACK_RATE : oldRate,
|
||||||
newIsActive,
|
newIsActive,
|
||||||
);
|
);
|
||||||
}, [handleContextMenuClose, isPlaybackRateActive, playbackRate, updatePlaybackRate]);
|
});
|
||||||
|
|
||||||
const PlaybackRateButton = useCallback(() => {
|
const PlaybackRateButton = useLastCallback(() => {
|
||||||
const displayRate = Object.entries(PLAYBACK_RATES).find(([, rate]) => rate === playbackRate)?.[0]
|
const displayRate = Object.entries(PLAYBACK_RATES).find(([, rate]) => rate === playbackRate)?.[0]
|
||||||
|| REGULAR_PLAYBACK_RATE;
|
|| REGULAR_PLAYBACK_RATE;
|
||||||
const text = `${playbackRate === REGULAR_PLAYBACK_RATE ? DEFAULT_FAST_PLAYBACK_RATE : displayRate}Х`;
|
const text = `${playbackRate === REGULAR_PLAYBACK_RATE ? DEFAULT_FAST_PLAYBACK_RATE : displayRate}Х`;
|
||||||
@ -201,10 +202,7 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [
|
});
|
||||||
handleBeforeContextMenu, handleContextMenu, handleContextMenuClose, handlePlaybackClick, isContextMenuOpen,
|
|
||||||
isMobile, isPlaybackRateActive, playbackRate,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const volumeIcon = useMemo(() => {
|
const volumeIcon = useMemo(() => {
|
||||||
if (volume === 0 || isMuted) return 'icon-muted';
|
if (volume === 0 || isMuted) return 'icon-muted';
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { memo, useCallback, useState } from '../../lib/teact/teact';
|
import React, { memo, useState } from '../../lib/teact/teact';
|
||||||
import { withGlobal, getActions } from '../../global';
|
import { withGlobal, getActions } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiChatSettings, ApiUser } from '../../api/types';
|
import type { ApiChat, ApiChatSettings, ApiUser } from '../../api/types';
|
||||||
@ -9,6 +9,8 @@ import {
|
|||||||
getChatTitle, getUserFirstOrLastName, getUserFullName, isChatBasicGroup, isUserId,
|
getChatTitle, getUserFirstOrLastName, getUserFullName, isChatBasicGroup, isUserId,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
|
||||||
@ -55,14 +57,14 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
|
|||||||
} = settings || {};
|
} = settings || {};
|
||||||
const isBasicGroup = chat && isChatBasicGroup(chat);
|
const isBasicGroup = chat && isChatBasicGroup(chat);
|
||||||
|
|
||||||
const handleAddContact = useCallback(() => {
|
const handleAddContact = useLastCallback(() => {
|
||||||
openAddContactDialog({ userId: chatId });
|
openAddContactDialog({ userId: chatId });
|
||||||
if (isAutoArchived) {
|
if (isAutoArchived) {
|
||||||
toggleChatArchived({ id: chatId });
|
toggleChatArchived({ id: chatId });
|
||||||
}
|
}
|
||||||
}, [openAddContactDialog, isAutoArchived, toggleChatArchived, chatId]);
|
});
|
||||||
|
|
||||||
const handleConfirmBlock = useCallback(() => {
|
const handleConfirmBlock = useLastCallback(() => {
|
||||||
closeBlockUserModal();
|
closeBlockUserModal();
|
||||||
blockContact({ contactId: chatId, accessHash: accessHash! });
|
blockContact({ contactId: chatId, accessHash: accessHash! });
|
||||||
if (canReportSpam && shouldReportSpam) {
|
if (canReportSpam && shouldReportSpam) {
|
||||||
@ -71,16 +73,13 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
|
|||||||
if (shouldDeleteChat) {
|
if (shouldDeleteChat) {
|
||||||
deleteChat({ chatId });
|
deleteChat({ chatId });
|
||||||
}
|
}
|
||||||
}, [
|
});
|
||||||
accessHash, blockContact, closeBlockUserModal, deleteChat, reportSpam, canReportSpam, shouldDeleteChat,
|
|
||||||
shouldReportSpam, chatId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleCloseReportPanel = useCallback(() => {
|
const handleCloseReportPanel = useLastCallback(() => {
|
||||||
hideChatReportPanel({ chatId });
|
hideChatReportPanel({ chatId });
|
||||||
}, [chatId, hideChatReportPanel]);
|
});
|
||||||
|
|
||||||
const handleChatReportSpam = useCallback(() => {
|
const handleChatReportSpam = useLastCallback(() => {
|
||||||
closeBlockUserModal();
|
closeBlockUserModal();
|
||||||
reportSpam({ chatId });
|
reportSpam({ chatId });
|
||||||
if (isBasicGroup) {
|
if (isBasicGroup) {
|
||||||
@ -89,9 +88,7 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
leaveChannel({ chatId });
|
leaveChannel({ chatId });
|
||||||
}
|
}
|
||||||
}, [
|
});
|
||||||
chatId, closeBlockUserModal, currentUserId, deleteChatUser, deleteHistory, isBasicGroup, leaveChannel, reportSpam,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, { memo, useEffect, useRef } from '../../lib/teact/teact';
|
||||||
memo, useCallback, useEffect, useRef,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiSticker, ApiUpdateConnectionStateType } from '../../api/types';
|
import type { ApiSticker, ApiUpdateConnectionStateType } from '../../api/types';
|
||||||
|
|
||||||
import { selectChat } from '../../global/selectors';
|
import { selectChat } from '../../global/selectors';
|
||||||
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
|
|
||||||
import useLang from '../../hooks/useLang';
|
|
||||||
import { getUserIdDividend } from '../../global/helpers';
|
import { getUserIdDividend } from '../../global/helpers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import StickerButton from '../common/StickerButton';
|
import StickerButton from '../common/StickerButton';
|
||||||
|
|
||||||
import './ContactGreeting.scss';
|
import './ContactGreeting.scss';
|
||||||
@ -61,13 +61,13 @@ const ContactGreeting: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [connectionState, markMessageListRead, lastUnreadMessageId]);
|
}, [connectionState, markMessageListRead, lastUnreadMessageId]);
|
||||||
|
|
||||||
const handleStickerSelect = useCallback((selectedSticker: ApiSticker) => {
|
const handleStickerSelect = useLastCallback((selectedSticker: ApiSticker) => {
|
||||||
selectedSticker = {
|
selectedSticker = {
|
||||||
...selectedSticker,
|
...selectedSticker,
|
||||||
isPreloadedGlobally: true,
|
isPreloadedGlobally: true,
|
||||||
};
|
};
|
||||||
sendMessage({ sticker: selectedSticker });
|
sendMessage({ sticker: selectedSticker });
|
||||||
}, [sendMessage]);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ContactGreeting" ref={containerRef}>
|
<div className="ContactGreeting" ref={containerRef}>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { useCallback, memo, useEffect } from '../../lib/teact/teact';
|
import React, { memo, useEffect } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -13,6 +13,8 @@ import {
|
|||||||
isChatSuperGroup,
|
isChatSuperGroup,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import usePrevious from '../../hooks/usePrevious';
|
import usePrevious from '../../hooks/usePrevious';
|
||||||
|
|
||||||
@ -51,12 +53,12 @@ const DeleteSelectedMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const prevIsOpen = usePrevious(isOpen);
|
const prevIsOpen = usePrevious(isOpen);
|
||||||
|
|
||||||
const handleDeleteMessageForAll = useCallback(() => {
|
const handleDeleteMessageForAll = useLastCallback(() => {
|
||||||
onClose();
|
onClose();
|
||||||
deleteMessages({ messageIds: selectedMessageIds!, shouldDeleteForAll: true });
|
deleteMessages({ messageIds: selectedMessageIds!, shouldDeleteForAll: true });
|
||||||
}, [deleteMessages, selectedMessageIds, onClose]);
|
});
|
||||||
|
|
||||||
const handleDeleteMessageForSelf = useCallback(() => {
|
const handleDeleteMessageForSelf = useLastCallback(() => {
|
||||||
if (isSchedule) {
|
if (isSchedule) {
|
||||||
deleteScheduledMessages({ messageIds: selectedMessageIds! });
|
deleteScheduledMessages({ messageIds: selectedMessageIds! });
|
||||||
} else {
|
} else {
|
||||||
@ -64,7 +66,7 @@ const DeleteSelectedMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
}, [isSchedule, onClose, deleteScheduledMessages, selectedMessageIds, deleteMessages]);
|
});
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,23 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useLayoutEffect, useRef,
|
memo, useEffect, useLayoutEffect, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ActiveEmojiInteraction } from '../../global/types';
|
import type { ActiveEmojiInteraction } from '../../global/types';
|
||||||
|
|
||||||
import { IS_ANDROID } from '../../util/windowEnvironment';
|
import { IS_ANDROID } from '../../util/windowEnvironment';
|
||||||
import useFlag from '../../hooks/useFlag';
|
|
||||||
import useMedia from '../../hooks/useMedia';
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
|
||||||
import {
|
import {
|
||||||
selectAnimatedEmojiEffect,
|
selectAnimatedEmojiEffect,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
import useMedia from '../../hooks/useMedia';
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
import AnimatedSticker from '../common/AnimatedSticker';
|
import AnimatedSticker from '../common/AnimatedSticker';
|
||||||
|
|
||||||
import './EmojiInteractionAnimation.scss';
|
import './EmojiInteractionAnimation.scss';
|
||||||
@ -41,7 +44,7 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
|
|||||||
const [isPlaying, startPlaying] = useFlag(false);
|
const [isPlaying, startPlaying] = useFlag(false);
|
||||||
const timeoutRef = useRef<NodeJS.Timeout>();
|
const timeoutRef = useRef<NodeJS.Timeout>();
|
||||||
|
|
||||||
const stop = useCallback(() => {
|
const stop = useLastCallback(() => {
|
||||||
startHiding();
|
startHiding();
|
||||||
if (timeoutRef.current) {
|
if (timeoutRef.current) {
|
||||||
clearTimeout(timeoutRef.current);
|
clearTimeout(timeoutRef.current);
|
||||||
@ -49,13 +52,13 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
stopActiveEmojiInteraction({ id: activeEmojiInteraction.id });
|
stopActiveEmojiInteraction({ id: activeEmojiInteraction.id });
|
||||||
}, HIDE_ANIMATION_DURATION);
|
}, HIDE_ANIMATION_DURATION);
|
||||||
}, [activeEmojiInteraction.id, startHiding, stopActiveEmojiInteraction]);
|
});
|
||||||
|
|
||||||
const handleCancelAnimation = useCallback((e: UIEvent) => {
|
const handleCancelAnimation = useLastCallback((e: UIEvent) => {
|
||||||
if (!(e.target as HTMLElement)?.closest('.AnimatedEmoji')) {
|
if (!(e.target as HTMLElement)?.closest('.AnimatedEmoji')) {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
}, [stop]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('touchstart', handleCancelAnimation);
|
document.addEventListener('touchstart', handleCancelAnimation);
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, { memo, useRef, useEffect } from '../../lib/teact/teact';
|
||||||
useCallback, memo, useRef, useEffect,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { MessageListType } from '../../global/types';
|
import type { MessageListType } from '../../global/types';
|
||||||
@ -11,6 +9,8 @@ import { selectChat, selectCurrentMessageList } from '../../global/selectors';
|
|||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import animateScroll from '../../util/animateScroll';
|
import animateScroll from '../../util/animateScroll';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
import ScrollDownButton from './ScrollDownButton';
|
import ScrollDownButton from './ScrollDownButton';
|
||||||
|
|
||||||
import styles from './FloatingActionButtons.module.scss';
|
import styles from './FloatingActionButtons.module.scss';
|
||||||
@ -64,7 +64,7 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [chatId, fetchUnreadMentions, hasUnreadMentions]);
|
}, [chatId, fetchUnreadMentions, hasUnreadMentions]);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (!isShown) {
|
if (!isShown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
animateScroll(messagesContainer, lastMessageElement, 'end', FOCUS_MARGIN);
|
animateScroll(messagesContainer, lastMessageElement, 'end', FOCUS_MARGIN);
|
||||||
}
|
}
|
||||||
}, [isShown, messageListType, focusNextReply]);
|
});
|
||||||
|
|
||||||
const fabClassName = buildClassName(
|
const fabClassName = buildClassName(
|
||||||
styles.root,
|
styles.root,
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, { memo, useRef, useState } from '../../lib/teact/teact';
|
||||||
memo,
|
|
||||||
useRef,
|
|
||||||
useCallback,
|
|
||||||
useState,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -28,6 +23,8 @@ import {
|
|||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectIsUserBlocked,
|
selectIsUserBlocked,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import { useHotkeys } from '../../hooks/useHotkeys';
|
import { useHotkeys } from '../../hooks/useHotkeys';
|
||||||
|
|
||||||
@ -108,42 +105,42 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const [menuPosition, setMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
|
const [menuPosition, setMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
|
||||||
|
|
||||||
const handleHeaderMenuOpen = useCallback(() => {
|
const handleHeaderMenuOpen = useLastCallback(() => {
|
||||||
setIsMenuOpen(true);
|
setIsMenuOpen(true);
|
||||||
const rect = menuButtonRef.current!.getBoundingClientRect();
|
const rect = menuButtonRef.current!.getBoundingClientRect();
|
||||||
setMenuPosition({ x: rect.right, y: rect.bottom });
|
setMenuPosition({ x: rect.right, y: rect.bottom });
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleHeaderMenuClose = useCallback(() => {
|
const handleHeaderMenuClose = useLastCallback(() => {
|
||||||
setIsMenuOpen(false);
|
setIsMenuOpen(false);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleHeaderMenuHide = useCallback(() => {
|
const handleHeaderMenuHide = useLastCallback(() => {
|
||||||
setMenuPosition(undefined);
|
setMenuPosition(undefined);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleSubscribeClick = useCallback(() => {
|
const handleSubscribeClick = useLastCallback(() => {
|
||||||
joinChannel({ chatId });
|
joinChannel({ chatId });
|
||||||
if (shouldSendJoinRequest) {
|
if (shouldSendJoinRequest) {
|
||||||
showNotification({
|
showNotification({
|
||||||
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
|
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [joinChannel, chatId, shouldSendJoinRequest, showNotification, isChannel, lang]);
|
});
|
||||||
|
|
||||||
const handleStartBot = useCallback(() => {
|
const handleStartBot = useLastCallback(() => {
|
||||||
sendBotCommand({ command: '/start' });
|
sendBotCommand({ command: '/start' });
|
||||||
}, [sendBotCommand]);
|
});
|
||||||
|
|
||||||
const handleRestartBot = useCallback(() => {
|
const handleRestartBot = useLastCallback(() => {
|
||||||
restartBot({ chatId });
|
restartBot({ chatId });
|
||||||
}, [chatId, restartBot]);
|
});
|
||||||
|
|
||||||
const handleJoinRequestsClick = useCallback(() => {
|
const handleJoinRequestsClick = useLastCallback(() => {
|
||||||
requestNextManagementScreen({ screen: ManagementScreens.JoinRequests });
|
requestNextManagementScreen({ screen: ManagementScreens.JoinRequests });
|
||||||
}, [requestNextManagementScreen]);
|
});
|
||||||
|
|
||||||
const handleSearchClick = useCallback(() => {
|
const handleSearchClick = useLastCallback(() => {
|
||||||
if (withForumActions) {
|
if (withForumActions) {
|
||||||
onTopicSearch?.();
|
onTopicSearch?.();
|
||||||
return;
|
return;
|
||||||
@ -163,24 +160,24 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
setTimeout(setFocusInSearchInput, SEARCH_FOCUS_DELAY_MS);
|
setTimeout(setFocusInSearchInput, SEARCH_FOCUS_DELAY_MS);
|
||||||
}
|
}
|
||||||
}, [isMobile, noAnimation, onTopicSearch, openLocalTextSearch, withForumActions]);
|
});
|
||||||
|
|
||||||
const handleAsMessagesClick = useCallback(() => {
|
const handleAsMessagesClick = useLastCallback(() => {
|
||||||
openChat({ id: chatId, threadId: MAIN_THREAD_ID });
|
openChat({ id: chatId, threadId: MAIN_THREAD_ID });
|
||||||
}, [chatId, openChat]);
|
});
|
||||||
|
|
||||||
function handleRequestCall() {
|
function handleRequestCall() {
|
||||||
requestMasterAndRequestCall({ userId: chatId });
|
requestMasterAndRequestCall({ userId: chatId });
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleHotkeySearchClick = useCallback((e: KeyboardEvent) => {
|
const handleHotkeySearchClick = useLastCallback((e: KeyboardEvent) => {
|
||||||
if (!canSearch || !IS_APP || e.shiftKey) {
|
if (!canSearch || !IS_APP || e.shiftKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSearchClick();
|
handleSearchClick();
|
||||||
}, [canSearch, handleSearchClick]);
|
});
|
||||||
|
|
||||||
useHotkeys({
|
useHotkeys({
|
||||||
'Mod+F': handleHotkeySearchClick,
|
'Mod+F': handleHotkeySearchClick,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useState,
|
memo, useEffect, useMemo, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -33,6 +33,8 @@ import {
|
|||||||
isUserRightBanned,
|
isUserRightBanned,
|
||||||
selectIsChatMuted,
|
selectIsChatMuted,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -181,76 +183,76 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
(!isChatInfoShown && isForum) ? true : undefined, CLOSE_MENU_ANIMATION_DURATION,
|
(!isChatInfoShown && isForum) ? true : undefined, CLOSE_MENU_ANIMATION_DURATION,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleReport = useCallback(() => {
|
const handleReport = useLastCallback(() => {
|
||||||
setIsMenuOpen(false);
|
setIsMenuOpen(false);
|
||||||
setIsReportModalOpen(true);
|
setIsReportModalOpen(true);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const closeReportModal = useCallback(() => {
|
const closeReportModal = useLastCallback(() => {
|
||||||
setIsReportModalOpen(false);
|
setIsReportModalOpen(false);
|
||||||
onClose();
|
onClose();
|
||||||
}, [onClose]);
|
});
|
||||||
|
|
||||||
const closeMuteModal = useCallback(() => {
|
const closeMuteModal = useLastCallback(() => {
|
||||||
setIsMuteModalOpen(false);
|
setIsMuteModalOpen(false);
|
||||||
onClose();
|
onClose();
|
||||||
}, [onClose]);
|
});
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useLastCallback(() => {
|
||||||
setIsMenuOpen(false);
|
setIsMenuOpen(false);
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const closeMenu = useCallback(() => {
|
const closeMenu = useLastCallback(() => {
|
||||||
setIsMenuOpen(false);
|
setIsMenuOpen(false);
|
||||||
onClose();
|
onClose();
|
||||||
}, [onClose]);
|
});
|
||||||
|
|
||||||
const handleViewGroupInfo = useCallback(() => {
|
const handleViewGroupInfo = useLastCallback(() => {
|
||||||
openChatWithInfo({ id: chatId, threadId });
|
openChatWithInfo({ id: chatId, threadId });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [chatId, closeMenu, openChatWithInfo, threadId]);
|
});
|
||||||
|
|
||||||
const closeDeleteModal = useCallback(() => {
|
const closeDeleteModal = useLastCallback(() => {
|
||||||
setIsDeleteModalOpen(false);
|
setIsDeleteModalOpen(false);
|
||||||
onClose();
|
onClose();
|
||||||
}, [onClose]);
|
});
|
||||||
|
|
||||||
const handleStartBot = useCallback(() => {
|
const handleStartBot = useLastCallback(() => {
|
||||||
sendBotCommand({ command: '/start' });
|
sendBotCommand({ command: '/start' });
|
||||||
}, [sendBotCommand]);
|
});
|
||||||
|
|
||||||
const handleRestartBot = useCallback(() => {
|
const handleRestartBot = useLastCallback(() => {
|
||||||
restartBot({ chatId });
|
restartBot({ chatId });
|
||||||
}, [chatId, restartBot]);
|
});
|
||||||
|
|
||||||
const handleUnmuteClick = useCallback(() => {
|
const handleUnmuteClick = useLastCallback(() => {
|
||||||
updateChatMutedState({ chatId, isMuted: false });
|
updateChatMutedState({ chatId, isMuted: false });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [chatId, closeMenu, updateChatMutedState]);
|
});
|
||||||
|
|
||||||
const handleMuteClick = useCallback(() => {
|
const handleMuteClick = useLastCallback(() => {
|
||||||
markRenderMuteModal();
|
markRenderMuteModal();
|
||||||
setIsMuteModalOpen(true);
|
setIsMuteModalOpen(true);
|
||||||
setIsMenuOpen(false);
|
setIsMenuOpen(false);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleCreateTopicClick = useCallback(() => {
|
const handleCreateTopicClick = useLastCallback(() => {
|
||||||
openCreateTopicPanel({ chatId });
|
openCreateTopicPanel({ chatId });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [openCreateTopicPanel, chatId, closeMenu]);
|
});
|
||||||
|
|
||||||
const handleEditTopicClick = useCallback(() => {
|
const handleEditTopicClick = useLastCallback(() => {
|
||||||
openEditTopicPanel({ chatId, topicId: threadId });
|
openEditTopicPanel({ chatId, topicId: threadId });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [openEditTopicPanel, chatId, threadId, closeMenu]);
|
});
|
||||||
|
|
||||||
const handleViewAsTopicsClick = useCallback(() => {
|
const handleViewAsTopicsClick = useLastCallback(() => {
|
||||||
openChat({ id: undefined });
|
openChat({ id: undefined });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [closeMenu, openChat]);
|
});
|
||||||
|
|
||||||
const handleEnterVoiceChatClick = useCallback(() => {
|
const handleEnterVoiceChatClick = useLastCallback(() => {
|
||||||
if (canCreateVoiceChat) {
|
if (canCreateVoiceChat) {
|
||||||
// TODO Show popup to schedule
|
// TODO Show popup to schedule
|
||||||
createGroupCall({
|
createGroupCall({
|
||||||
@ -262,57 +264,57 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [closeMenu, canCreateVoiceChat, chatId, requestMasterAndJoinGroupCall, createGroupCall]);
|
});
|
||||||
|
|
||||||
const handleLinkedChatClick = useCallback(() => {
|
const handleLinkedChatClick = useLastCallback(() => {
|
||||||
openLinkedChat({ id: chatId });
|
openLinkedChat({ id: chatId });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [chatId, closeMenu, openLinkedChat]);
|
});
|
||||||
|
|
||||||
const handleGiftPremiumClick = useCallback(() => {
|
const handleGiftPremiumClick = useLastCallback(() => {
|
||||||
openGiftPremiumModal({ forUserId: chatId });
|
openGiftPremiumModal({ forUserId: chatId });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [openGiftPremiumModal, chatId, closeMenu]);
|
});
|
||||||
|
|
||||||
const handleAddContactClick = useCallback(() => {
|
const handleAddContactClick = useLastCallback(() => {
|
||||||
openAddContactDialog({ userId: chatId });
|
openAddContactDialog({ userId: chatId });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [openAddContactDialog, chatId, closeMenu]);
|
});
|
||||||
|
|
||||||
const handleSubscribe = useCallback(() => {
|
const handleSubscribe = useLastCallback(() => {
|
||||||
onSubscribeChannel();
|
onSubscribeChannel();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [closeMenu, onSubscribeChannel]);
|
});
|
||||||
|
|
||||||
const handleVideoCall = useCallback(() => {
|
const handleVideoCall = useLastCallback(() => {
|
||||||
requestMasterAndRequestCall({ userId: chatId, isVideo: true });
|
requestMasterAndRequestCall({ userId: chatId, isVideo: true });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [chatId, closeMenu, requestMasterAndRequestCall]);
|
});
|
||||||
|
|
||||||
const handleCall = useCallback(() => {
|
const handleCall = useLastCallback(() => {
|
||||||
requestMasterAndRequestCall({ userId: chatId });
|
requestMasterAndRequestCall({ userId: chatId });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [chatId, closeMenu, requestMasterAndRequestCall]);
|
});
|
||||||
|
|
||||||
const handleSearch = useCallback(() => {
|
const handleSearch = useLastCallback(() => {
|
||||||
onSearchClick();
|
onSearchClick();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [closeMenu, onSearchClick]);
|
});
|
||||||
|
|
||||||
const handleStatisticsClick = useCallback(() => {
|
const handleStatisticsClick = useLastCallback(() => {
|
||||||
toggleStatistics();
|
toggleStatistics();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [closeMenu, toggleStatistics]);
|
});
|
||||||
|
|
||||||
const handleSelectMessages = useCallback(() => {
|
const handleSelectMessages = useLastCallback(() => {
|
||||||
enterMessageSelectMode();
|
enterMessageSelectMode();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [closeMenu, enterMessageSelectMode]);
|
});
|
||||||
|
|
||||||
const handleOpenAsMessages = useCallback(() => {
|
const handleOpenAsMessages = useLastCallback(() => {
|
||||||
onAsMessagesClick();
|
onAsMessagesClick();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}, [closeMenu, onAsMessagesClick]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
disableScrolling();
|
disableScrolling();
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { ApiMessage } from '../../api/types';
|
import type { ApiMessage } from '../../api/types';
|
||||||
@ -13,6 +13,7 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
import useThumbnail from '../../hooks/useThumbnail';
|
import useThumbnail from '../../hooks/useThumbnail';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -61,21 +62,21 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const [isUnpinDialogOpen, openUnpinDialog, closeUnpinDialog] = useFlag();
|
const [isUnpinDialogOpen, openUnpinDialog, closeUnpinDialog] = useFlag();
|
||||||
|
|
||||||
const handleUnpinMessage = useCallback(() => {
|
const handleUnpinMessage = useLastCallback(() => {
|
||||||
closeUnpinDialog();
|
closeUnpinDialog();
|
||||||
|
|
||||||
if (onUnpinMessage) {
|
if (onUnpinMessage) {
|
||||||
onUnpinMessage(message.id);
|
onUnpinMessage(message.id);
|
||||||
}
|
}
|
||||||
}, [closeUnpinDialog, onUnpinMessage, message.id]);
|
});
|
||||||
|
|
||||||
const inlineButton = getMessageSingleInlineButton(message);
|
const inlineButton = getMessageSingleInlineButton(message);
|
||||||
|
|
||||||
const handleInlineButtonClick = useCallback(() => {
|
const handleInlineButtonClick = useLastCallback(() => {
|
||||||
if (inlineButton) {
|
if (inlineButton) {
|
||||||
clickBotInlineButton({ messageId: message.id, button: inlineButton });
|
clickBotInlineButton({ messageId: message.id, button: inlineButton });
|
||||||
}
|
}
|
||||||
}, [clickBotInlineButton, inlineButton, message.id]);
|
});
|
||||||
|
|
||||||
const [noHoverColor, markNoHoverColor, unmarkNoHoverColor] = useFlag();
|
const [noHoverColor, markNoHoverColor, unmarkNoHoverColor] = useFlag();
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useState,
|
memo, useEffect, useMemo, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ import { SUPPORTED_TRANSLATION_LANGUAGES } from '../../config';
|
|||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import Modal from '../ui/Modal';
|
import Modal from '../ui/Modal';
|
||||||
@ -47,16 +48,16 @@ const MessageLanguageModal: FC<OwnProps & StateProps> = ({
|
|||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const handleSelect = useCallback((toLanguageCode: string) => {
|
const handleSelect = useLastCallback((toLanguageCode: string) => {
|
||||||
if (!chatId || !messageId) return;
|
if (!chatId || !messageId) return;
|
||||||
|
|
||||||
requestMessageTranslation({ chatId, id: messageId, toLanguageCode });
|
requestMessageTranslation({ chatId, id: messageId, toLanguageCode });
|
||||||
closeMessageLanguageModal();
|
closeMessageLanguageModal();
|
||||||
}, [chatId, closeMessageLanguageModal, messageId, requestMessageTranslation]);
|
});
|
||||||
|
|
||||||
const handleSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearch = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearch(e.target.value);
|
setSearch(e.target.value);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const translateLanguages = useMemo(() => SUPPORTED_TRANSLATION_LANGUAGES.map((langCode: string) => {
|
const translateLanguages = useMemo(() => SUPPORTED_TRANSLATION_LANGUAGES.map((langCode: string) => {
|
||||||
const translatedNames = new Intl.DisplayNames([currentLanguageCode], { type: 'language' });
|
const translatedNames = new Intl.DisplayNames([currentLanguageCode], { type: 'language' });
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
@ -69,6 +68,7 @@ import resetScroll from '../../util/resetScroll';
|
|||||||
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
|
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import { useStateRef } from '../../hooks/useStateRef';
|
import { useStateRef } from '../../hooks/useStateRef';
|
||||||
import useSyncEffect from '../../hooks/useSyncEffect';
|
import useSyncEffect from '../../hooks/useSyncEffect';
|
||||||
import useStickyDates from './hooks/useStickyDates';
|
import useStickyDates from './hooks/useStickyDates';
|
||||||
@ -321,7 +321,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const { isScrolled, updateStickyDates } = useStickyDates();
|
const { isScrolled, updateStickyDates } = useStickyDates();
|
||||||
|
|
||||||
const handleScroll = useCallback(() => {
|
const handleScroll = useLastCallback(() => {
|
||||||
if (isScrollTopJustUpdatedRef.current) {
|
if (isScrollTopJustUpdatedRef.current) {
|
||||||
isScrollTopJustUpdatedRef.current = false;
|
isScrollTopJustUpdatedRef.current = false;
|
||||||
return;
|
return;
|
||||||
@ -353,9 +353,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
setScrollOffset({ chatId, threadId, scrollOffset: scrollOffsetRef.current });
|
setScrollOffset({ chatId, threadId, scrollOffset: scrollOffsetRef.current });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [
|
});
|
||||||
updateStickyDates, hasTools, getForceNextPinnedInHeader, onPinnedIntersectionChange, type, chatId, threadId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive);
|
const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { memo, useCallback, useEffect } from '../../lib/teact/teact';
|
import React, { memo, useEffect } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { MessageListType } from '../../global/types';
|
import type { MessageListType } from '../../global/types';
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import usePrevious from '../../hooks/usePrevious';
|
import usePrevious from '../../hooks/usePrevious';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -73,9 +74,9 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useCopySelectedMessages(isActive);
|
useCopySelectedMessages(isActive);
|
||||||
|
|
||||||
const handleExitMessageSelectMode = useCallback(() => {
|
const handleExitMessageSelectMode = useLastCallback(() => {
|
||||||
exitMessageSelectMode();
|
exitMessageSelectMode();
|
||||||
}, [exitMessageSelectMode]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return isActive && !isDeleteModalOpen && !isReportModalOpen && !isAnyModalOpen
|
return isActive && !isDeleteModalOpen && !isReportModalOpen && !isAnyModalOpen
|
||||||
@ -90,18 +91,18 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
|||||||
canDeleteMessages,
|
canDeleteMessages,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useLastCallback(() => {
|
||||||
copySelectedMessages();
|
copySelectedMessages();
|
||||||
showNotification({
|
showNotification({
|
||||||
message: lang('Share.Link.Copied'),
|
message: lang('Share.Link.Copied'),
|
||||||
});
|
});
|
||||||
exitMessageSelectMode();
|
exitMessageSelectMode();
|
||||||
}, [copySelectedMessages, exitMessageSelectMode, lang, showNotification]);
|
});
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useLastCallback(() => {
|
||||||
downloadSelectedMessages();
|
downloadSelectedMessages();
|
||||||
exitMessageSelectMode();
|
exitMessageSelectMode();
|
||||||
}, [downloadSelectedMessages, exitMessageSelectMode]);
|
});
|
||||||
|
|
||||||
const prevSelectedMessagesCount = usePrevious(selectedMessagesCount || undefined, true);
|
const prevSelectedMessagesCount = usePrevious(selectedMessagesCount || undefined, true);
|
||||||
const renderingSelectedMessagesCount = isActive ? selectedMessagesCount : prevSelectedMessagesCount;
|
const renderingSelectedMessagesCount = isActive ? selectedMessagesCount : prevSelectedMessagesCount;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
import React, {
|
import React, {
|
||||||
useEffect, useState, memo, useMemo, useCallback,
|
useEffect, useState, memo, useMemo,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
@ -59,6 +59,8 @@ import {
|
|||||||
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
|
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
|
||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useCustomBackground from '../../hooks/useCustomBackground';
|
import useCustomBackground from '../../hooks/useCustomBackground';
|
||||||
import useWindowSize from '../../hooks/useWindowSize';
|
import useWindowSize from '../../hooks/useWindowSize';
|
||||||
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
||||||
@ -307,7 +309,7 @@ function MiddleColumn({
|
|||||||
return () => {
|
return () => {
|
||||||
visualViewport.removeEventListener('resize', handleResize);
|
visualViewport.removeEventListener('resize', handleResize);
|
||||||
};
|
};
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isPrivate) {
|
if (isPrivate) {
|
||||||
@ -333,7 +335,7 @@ function MiddleColumn({
|
|||||||
leftColumnWidth: n,
|
leftColumnWidth: n,
|
||||||
}), resetLeftColumnWidth, leftColumnWidth, '--left-column-width');
|
}), resetLeftColumnWidth, leftColumnWidth, '--left-column-width');
|
||||||
|
|
||||||
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleDragEnter = useLastCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||||
const { items } = e.dataTransfer || {};
|
const { items } = e.dataTransfer || {};
|
||||||
const shouldDrawQuick = items && items.length > 0 && Array.from(items)
|
const shouldDrawQuick = items && items.length > 0 && Array.from(items)
|
||||||
// Filter unnecessary element for drag and drop images in Firefox (https://github.com/Ajaxy/telegram-tt/issues/49)
|
// Filter unnecessary element for drag and drop images in Firefox (https://github.com/Ajaxy/telegram-tt/issues/49)
|
||||||
@ -343,46 +345,46 @@ function MiddleColumn({
|
|||||||
.every(isImage);
|
.every(isImage);
|
||||||
|
|
||||||
setDropAreaState(shouldDrawQuick ? DropAreaState.QuickFile : DropAreaState.Document);
|
setDropAreaState(shouldDrawQuick ? DropAreaState.QuickFile : DropAreaState.Document);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleHideDropArea = useCallback(() => {
|
const handleHideDropArea = useLastCallback(() => {
|
||||||
setDropAreaState(DropAreaState.None);
|
setDropAreaState(DropAreaState.None);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleOpenUnpinModal = useCallback(() => {
|
const handleOpenUnpinModal = useLastCallback(() => {
|
||||||
setIsUnpinModalOpen(true);
|
setIsUnpinModalOpen(true);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const closeUnpinModal = useCallback(() => {
|
const closeUnpinModal = useLastCallback(() => {
|
||||||
setIsUnpinModalOpen(false);
|
setIsUnpinModalOpen(false);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleUnpinAllMessages = useCallback(() => {
|
const handleUnpinAllMessages = useLastCallback(() => {
|
||||||
unpinAllMessages({ chatId: chatId!, threadId: threadId! });
|
unpinAllMessages({ chatId: chatId!, threadId: threadId! });
|
||||||
closeUnpinModal();
|
closeUnpinModal();
|
||||||
openPreviousChat();
|
openPreviousChat();
|
||||||
}, [unpinAllMessages, chatId, threadId, closeUnpinModal, openPreviousChat]);
|
});
|
||||||
|
|
||||||
const handleTabletFocus = useCallback(() => {
|
const handleTabletFocus = useLastCallback(() => {
|
||||||
openChat({ id: chatId });
|
openChat({ id: chatId });
|
||||||
}, [openChat, chatId]);
|
});
|
||||||
|
|
||||||
const handleSubscribeClick = useCallback(() => {
|
const handleSubscribeClick = useLastCallback(() => {
|
||||||
joinChannel({ chatId: chatId! });
|
joinChannel({ chatId: chatId! });
|
||||||
if (renderingShouldSendJoinRequest) {
|
if (renderingShouldSendJoinRequest) {
|
||||||
showNotification({
|
showNotification({
|
||||||
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
|
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [joinChannel, chatId, renderingShouldSendJoinRequest, showNotification, isChannel, lang]);
|
});
|
||||||
|
|
||||||
const handleStartBot = useCallback(() => {
|
const handleStartBot = useLastCallback(() => {
|
||||||
sendBotCommand({ command: '/start' });
|
sendBotCommand({ command: '/start' });
|
||||||
}, [sendBotCommand]);
|
});
|
||||||
|
|
||||||
const handleRestartBot = useCallback(() => {
|
const handleRestartBot = useLastCallback(() => {
|
||||||
restartBot({ chatId: chatId! });
|
restartBot({ chatId: chatId! });
|
||||||
}, [chatId, restartBot]);
|
});
|
||||||
|
|
||||||
const customBackgroundValue = useCustomBackground(theme, customBackground);
|
const customBackgroundValue = useCustomBackground(theme, customBackground);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useLayoutEffect, useRef,
|
memo, useEffect, useLayoutEffect, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
@ -44,6 +44,9 @@ import {
|
|||||||
selectThreadTopMessageId,
|
selectThreadTopMessageId,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import cycleRestrict from '../../util/cycleRestrict';
|
import cycleRestrict from '../../util/cycleRestrict';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
import { useFastClick } from '../../hooks/useFastClick';
|
||||||
import useEnsureMessage from '../../hooks/useEnsureMessage';
|
import useEnsureMessage from '../../hooks/useEnsureMessage';
|
||||||
import useWindowSize from '../../hooks/useWindowSize';
|
import useWindowSize from '../../hooks/useWindowSize';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
@ -68,7 +71,6 @@ import GroupCallTopPane from '../calls/group/GroupCallTopPane';
|
|||||||
import ChatReportPanel from './ChatReportPanel';
|
import ChatReportPanel from './ChatReportPanel';
|
||||||
|
|
||||||
import './MiddleHeader.scss';
|
import './MiddleHeader.scss';
|
||||||
import { useFastClick } from '../../hooks/useFastClick';
|
|
||||||
|
|
||||||
const ANIMATION_DURATION = 350;
|
const ANIMATION_DURATION = 350;
|
||||||
const BACK_BUTTON_INACTIVE_TIME = 450;
|
const BACK_BUTTON_INACTIVE_TIME = 450;
|
||||||
@ -184,11 +186,11 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
openChatWithInfo({ id: chatId, threadId });
|
openChatWithInfo({ id: chatId, threadId });
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleUnpinMessage = useCallback((messageId: number) => {
|
const handleUnpinMessage = useLastCallback((messageId: number) => {
|
||||||
pinMessage({ messageId, isUnpin: true });
|
pinMessage({ messageId, isUnpin: true });
|
||||||
}, [pinMessage]);
|
});
|
||||||
|
|
||||||
const handlePinnedMessageClick = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>): void => {
|
const handlePinnedMessageClick = useLastCallback((e: React.MouseEvent<HTMLElement, MouseEvent>): void => {
|
||||||
const messageId = e.shiftKey && Array.isArray(pinnedMessageIds)
|
const messageId = e.shiftKey && Array.isArray(pinnedMessageIds)
|
||||||
? pinnedMessageIds[cycleRestrict(pinnedMessageIds.length, pinnedMessageIds.indexOf(pinnedMessageId!) - 2)]
|
? pinnedMessageIds[cycleRestrict(pinnedMessageIds.length, pinnedMessageIds.indexOf(pinnedMessageId!) - 2)]
|
||||||
: pinnedMessageId!;
|
: pinnedMessageId!;
|
||||||
@ -198,19 +200,19 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
chatId, threadId, messageId, noForumTopicPanel: true,
|
chatId, threadId, messageId, noForumTopicPanel: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [pinnedMessageIds, pinnedMessageId, onFocusPinnedMessage, chatId, threadId]);
|
});
|
||||||
|
|
||||||
const handleAllPinnedClick = useCallback(() => {
|
const handleAllPinnedClick = useLastCallback(() => {
|
||||||
openChat({ id: chatId, threadId, type: 'pinned' });
|
openChat({ id: chatId, threadId, type: 'pinned' });
|
||||||
}, [openChat, chatId, threadId]);
|
});
|
||||||
|
|
||||||
const setBackButtonActive = useCallback(() => {
|
const setBackButtonActive = useLastCallback(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isBackButtonActive.current = true;
|
isBackButtonActive.current = true;
|
||||||
}, BACK_BUTTON_INACTIVE_TIME);
|
}, BACK_BUTTON_INACTIVE_TIME);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleBackClick = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
const handleBackClick = useLastCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
if (!isBackButtonActive.current) return;
|
if (!isBackButtonActive.current) return;
|
||||||
|
|
||||||
// Workaround for missing UI when quickly clicking the Back button
|
// Workaround for missing UI when quickly clicking the Back button
|
||||||
@ -241,10 +243,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
openPreviousChat();
|
openPreviousChat();
|
||||||
setBackButtonActive();
|
setBackButtonActive();
|
||||||
}, [
|
});
|
||||||
isMobile, isSelectModeActive, messageListType, currentTransitionKey, setBackButtonActive, isTablet,
|
|
||||||
shouldShowCloseButton,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const canToolsCollideWithChatInfo = (
|
const canToolsCollideWithChatInfo = (
|
||||||
windowWidth >= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN
|
windowWidth >= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useRef, useState, useLayoutEffect,
|
memo, useEffect, useRef, useState, useLayoutEffect,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
@ -17,6 +17,8 @@ import {
|
|||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { getDayStartAt } from '../../util/dateFormat';
|
import { getDayStartAt } from '../../util/dateFormat';
|
||||||
|
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import SearchInput from '../ui/SearchInput';
|
import SearchInput from '../ui/SearchInput';
|
||||||
|
|
||||||
@ -123,33 +125,33 @@ const MobileSearchFooter: FC<StateProps> = ({
|
|||||||
searchInput.blur();
|
searchInput.blur();
|
||||||
}, [isHistoryCalendarOpen]);
|
}, [isHistoryCalendarOpen]);
|
||||||
|
|
||||||
const handleMessageSearchQueryChange = useCallback((newQuery: string) => {
|
const handleMessageSearchQueryChange = useLastCallback((newQuery: string) => {
|
||||||
setLocalTextSearchQuery({ query: newQuery });
|
setLocalTextSearchQuery({ query: newQuery });
|
||||||
|
|
||||||
if (newQuery.length) {
|
if (newQuery.length) {
|
||||||
runDebouncedForSearch(searchTextMessagesLocal);
|
runDebouncedForSearch(searchTextMessagesLocal);
|
||||||
}
|
}
|
||||||
}, [searchTextMessagesLocal, setLocalTextSearchQuery]);
|
});
|
||||||
|
|
||||||
const handleUp = useCallback(() => {
|
const handleUp = useLastCallback(() => {
|
||||||
if (chat && foundIds) {
|
if (chat && foundIds) {
|
||||||
const newFocusIndex = focusedIndex + 1;
|
const newFocusIndex = focusedIndex + 1;
|
||||||
focusMessage({ chatId: chat.id, messageId: foundIds[newFocusIndex], threadId });
|
focusMessage({ chatId: chat.id, messageId: foundIds[newFocusIndex], threadId });
|
||||||
setFocusedIndex(newFocusIndex);
|
setFocusedIndex(newFocusIndex);
|
||||||
}
|
}
|
||||||
}, [chat, foundIds, focusedIndex, threadId]);
|
});
|
||||||
|
|
||||||
const handleDown = useCallback(() => {
|
const handleDown = useLastCallback(() => {
|
||||||
if (chat && foundIds) {
|
if (chat && foundIds) {
|
||||||
const newFocusIndex = focusedIndex - 1;
|
const newFocusIndex = focusedIndex - 1;
|
||||||
focusMessage({ chatId: chat.id, messageId: foundIds[newFocusIndex], threadId });
|
focusMessage({ chatId: chat.id, messageId: foundIds[newFocusIndex], threadId });
|
||||||
setFocusedIndex(newFocusIndex);
|
setFocusedIndex(newFocusIndex);
|
||||||
}
|
}
|
||||||
}, [chat, foundIds, focusedIndex, threadId]);
|
});
|
||||||
|
|
||||||
const handleCloseLocalTextSearch = useCallback(() => {
|
const handleCloseLocalTextSearch = useLastCallback(() => {
|
||||||
closeLocalTextSearch();
|
closeLocalTextSearch();
|
||||||
}, [closeLocalTextSearch]);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="MobileSearch" className={isActive ? 'active' : ''}>
|
<div id="MobileSearch" className={isActive ? 'active' : ''}>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback, memo, useMemo, useEffect, useState, useRef,
|
memo, useMemo, useEffect, useState, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -32,6 +32,7 @@ import FullNameTitle from '../common/FullNameTitle';
|
|||||||
import PrivateChatInfo from '../common/PrivateChatInfo';
|
import PrivateChatInfo from '../common/PrivateChatInfo';
|
||||||
|
|
||||||
import './ReactorListModal.scss';
|
import './ReactorListModal.scss';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
const MIN_REACTIONS_COUNT_FOR_FILTERS = 10;
|
const MIN_REACTIONS_COUNT_FOR_FILTERS = 10;
|
||||||
|
|
||||||
@ -81,28 +82,28 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isClosing, isOpen, stopClosing]);
|
}, [isClosing, isOpen, stopClosing]);
|
||||||
|
|
||||||
const handleCloseAnimationEnd = useCallback(() => {
|
const handleCloseAnimationEnd = useLastCallback(() => {
|
||||||
if (chatIdRef.current) {
|
if (chatIdRef.current) {
|
||||||
openChat({ id: chatIdRef.current });
|
openChat({ id: chatIdRef.current });
|
||||||
}
|
}
|
||||||
closeReactorListModal();
|
closeReactorListModal();
|
||||||
}, [closeReactorListModal, openChat]);
|
});
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useLastCallback(() => {
|
||||||
startClosing();
|
startClosing();
|
||||||
}, [startClosing]);
|
});
|
||||||
|
|
||||||
const handleClick = useCallback((userId: string) => {
|
const handleClick = useLastCallback((userId: string) => {
|
||||||
chatIdRef.current = userId;
|
chatIdRef.current = userId;
|
||||||
handleClose();
|
handleClose();
|
||||||
}, [handleClose]);
|
});
|
||||||
|
|
||||||
const handleLoadMore = useCallback(() => {
|
const handleLoadMore = useLastCallback(() => {
|
||||||
loadReactors({
|
loadReactors({
|
||||||
chatId: chatId!,
|
chatId: chatId!,
|
||||||
messageId: messageId!,
|
messageId: messageId!,
|
||||||
});
|
});
|
||||||
}, [chatId, loadReactors, messageId]);
|
});
|
||||||
|
|
||||||
const allReactions = useMemo(() => {
|
const allReactions = useMemo(() => {
|
||||||
const uniqueReactions: ApiReaction[] = [];
|
const uniqueReactions: ApiReaction[] = [];
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useMemo, useState,
|
memo, useMemo, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { IAnchorPosition, ISettings } from '../../../types';
|
import type { IAnchorPosition, ISettings } from '../../../types';
|
||||||
import type { ApiAttachBot } from '../../../api/types';
|
import type { ApiAttachBot } from '../../../api/types';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
@ -43,29 +44,29 @@ const AttachBotItem: FC<OwnProps> = ({
|
|||||||
const [isMenuOpen, openMenu, closeMenu] = useFlag();
|
const [isMenuOpen, openMenu, closeMenu] = useFlag();
|
||||||
const [menuPosition, setMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
|
const [menuPosition, setMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
|
||||||
|
|
||||||
const handleContextMenu = useCallback((e: React.UIEvent) => {
|
const handleContextMenu = useLastCallback((e: React.UIEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
setMenuPosition({ x: rect.right, y: rect.bottom });
|
setMenuPosition({ x: rect.right, y: rect.bottom });
|
||||||
onMenuOpened();
|
onMenuOpened();
|
||||||
openMenu();
|
openMenu();
|
||||||
}, [onMenuOpened, openMenu]);
|
});
|
||||||
|
|
||||||
const handleCloseMenu = useCallback(() => {
|
const handleCloseMenu = useLastCallback(() => {
|
||||||
closeMenu();
|
closeMenu();
|
||||||
onMenuClosed();
|
onMenuClosed();
|
||||||
}, [closeMenu, onMenuClosed]);
|
});
|
||||||
|
|
||||||
const handleCloseAnimationEnd = useCallback(() => {
|
const handleCloseAnimationEnd = useLastCallback(() => {
|
||||||
setMenuPosition(undefined);
|
setMenuPosition(undefined);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleRemoveBot = useCallback(() => {
|
const handleRemoveBot = useLastCallback(() => {
|
||||||
toggleAttachBot({
|
toggleAttachBot({
|
||||||
botId: bot.id,
|
botId: bot.id,
|
||||||
isEnabled: false,
|
isEnabled: false,
|
||||||
});
|
});
|
||||||
}, [bot.id, toggleAttachBot]);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useMemo, useCallback, useEffect,
|
memo, useMemo, useEffect,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
@ -16,6 +16,7 @@ import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
|||||||
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
||||||
import { validateFiles } from '../../../util/files';
|
import { validateFiles } from '../../../util/files';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMouseInside from '../../../hooks/useMouseInside';
|
import useMouseInside from '../../../hooks/useMouseInside';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
@ -75,38 +76,38 @@ const AttachMenu: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isAttachMenuOpen, markMouseInside]);
|
}, [isAttachMenuOpen, markMouseInside]);
|
||||||
|
|
||||||
const handleToggleAttachMenu = useCallback(() => {
|
const handleToggleAttachMenu = useLastCallback(() => {
|
||||||
if (isAttachMenuOpen) {
|
if (isAttachMenuOpen) {
|
||||||
closeAttachMenu();
|
closeAttachMenu();
|
||||||
} else {
|
} else {
|
||||||
openAttachMenu();
|
openAttachMenu();
|
||||||
}
|
}
|
||||||
}, [isAttachMenuOpen, openAttachMenu, closeAttachMenu]);
|
});
|
||||||
|
|
||||||
const handleFileSelect = useCallback((e: Event, shouldSuggestCompression?: boolean) => {
|
const handleFileSelect = useLastCallback((e: Event, shouldSuggestCompression?: boolean) => {
|
||||||
const { files } = e.target as HTMLInputElement;
|
const { files } = e.target as HTMLInputElement;
|
||||||
const validatedFiles = validateFiles(files);
|
const validatedFiles = validateFiles(files);
|
||||||
|
|
||||||
if (validatedFiles?.length) {
|
if (validatedFiles?.length) {
|
||||||
onFileSelect(validatedFiles, shouldSuggestCompression);
|
onFileSelect(validatedFiles, shouldSuggestCompression);
|
||||||
}
|
}
|
||||||
}, [onFileSelect]);
|
});
|
||||||
|
|
||||||
const handleQuickSelect = useCallback(() => {
|
const handleQuickSelect = useLastCallback(() => {
|
||||||
openSystemFilesDialog(
|
openSystemFilesDialog(
|
||||||
Array.from(canSendVideoAndPhoto ? CONTENT_TYPES_WITH_PREVIEW : (
|
Array.from(canSendVideoAndPhoto ? CONTENT_TYPES_WITH_PREVIEW : (
|
||||||
canSendPhotos ? SUPPORTED_IMAGE_CONTENT_TYPES : SUPPORTED_VIDEO_CONTENT_TYPES
|
canSendPhotos ? SUPPORTED_IMAGE_CONTENT_TYPES : SUPPORTED_VIDEO_CONTENT_TYPES
|
||||||
)).join(','),
|
)).join(','),
|
||||||
(e) => handleFileSelect(e, true),
|
(e) => handleFileSelect(e, true),
|
||||||
);
|
);
|
||||||
}, [canSendPhotos, canSendVideoAndPhoto, handleFileSelect]);
|
});
|
||||||
|
|
||||||
const handleDocumentSelect = useCallback(() => {
|
const handleDocumentSelect = useLastCallback(() => {
|
||||||
openSystemFilesDialog(!canSendDocuments && canSendAudios
|
openSystemFilesDialog(!canSendDocuments && canSendAudios
|
||||||
? Array.from(SUPPORTED_AUDIO_CONTENT_TYPES).join(',') : (
|
? Array.from(SUPPORTED_AUDIO_CONTENT_TYPES).join(',') : (
|
||||||
'*'
|
'*'
|
||||||
), (e) => handleFileSelect(e, false));
|
), (e) => handleFileSelect(e, false));
|
||||||
}, [canSendAudios, canSendDocuments, handleFileSelect]);
|
});
|
||||||
|
|
||||||
const bots = useMemo(() => {
|
const bots = useMemo(() => {
|
||||||
return Object.values(attachBots).filter((bot) => {
|
return Object.values(attachBots).filter((bot) => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
memo, useEffect, useMemo, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
@ -30,6 +30,7 @@ import buildClassName from '../../../util/buildClassName';
|
|||||||
import { validateFiles } from '../../../util/files';
|
import { validateFiles } from '../../../util/files';
|
||||||
import { removeAllSelections } from '../../../util/selection';
|
import { removeAllSelections } from '../../../util/selection';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useMentionTooltip from './hooks/useMentionTooltip';
|
import useMentionTooltip from './hooks/useMentionTooltip';
|
||||||
import useEmojiTooltip from './hooks/useEmojiTooltip';
|
import useEmojiTooltip from './hooks/useEmojiTooltip';
|
||||||
@ -253,7 +254,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
handleContextMenuHide,
|
handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(mainButtonRef, !canShowCustomSendMenu || !isOpen);
|
} = useContextMenuHandlers(mainButtonRef, !canShowCustomSendMenu || !isOpen);
|
||||||
|
|
||||||
const sendAttachments = useCallback((isSilent?: boolean, shouldSendScheduled?: boolean) => {
|
const sendAttachments = useLastCallback((isSilent?: boolean, shouldSendScheduled?: boolean) => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
const send = (shouldSchedule || shouldSendScheduled) ? onSendScheduled
|
const send = (shouldSchedule || shouldSendScheduled) ? onSendScheduled
|
||||||
: isSilent ? onSendSilent : onSend;
|
: isSilent ? onSendSilent : onSend;
|
||||||
@ -263,22 +264,19 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
shouldSendGrouped,
|
shouldSendGrouped,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [
|
});
|
||||||
isOpen, shouldSchedule, onSendScheduled, onSendSilent, onSend, isSendingCompressed, shouldSendGrouped,
|
|
||||||
updateAttachmentSettings,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleSendSilent = useCallback(() => {
|
const handleSendSilent = useLastCallback(() => {
|
||||||
sendAttachments(true);
|
sendAttachments(true);
|
||||||
}, [sendAttachments]);
|
});
|
||||||
|
|
||||||
const handleSendClick = useCallback(() => {
|
const handleSendClick = useLastCallback(() => {
|
||||||
sendAttachments();
|
sendAttachments();
|
||||||
}, [sendAttachments]);
|
});
|
||||||
|
|
||||||
const handleScheduleClick = useCallback(() => {
|
const handleScheduleClick = useLastCallback(() => {
|
||||||
sendAttachments(false, true);
|
sendAttachments(false, true);
|
||||||
}, [sendAttachments]);
|
});
|
||||||
|
|
||||||
const handleDragLeave = (e: React.DragEvent<HTMLElement>) => {
|
const handleDragLeave = (e: React.DragEvent<HTMLElement>) => {
|
||||||
const { relatedTarget: toTarget, target: fromTarget } = e;
|
const { relatedTarget: toTarget, target: fromTarget } = e;
|
||||||
@ -300,7 +298,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
unmarkHovered();
|
unmarkHovered();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilesDrop = useCallback(async (e: React.DragEvent<HTMLDivElement>) => {
|
const handleFilesDrop = useLastCallback(async (e: React.DragEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
unmarkHovered();
|
unmarkHovered();
|
||||||
|
|
||||||
@ -310,7 +308,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
if (files?.length) {
|
if (files?.length) {
|
||||||
onFileAppend(files, isEverySpoiler);
|
onFileAppend(files, isEverySpoiler);
|
||||||
}
|
}
|
||||||
}, [isEverySpoiler, onFileAppend, unmarkHovered]);
|
});
|
||||||
|
|
||||||
function handleDragOver(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
function handleDragOver(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -321,35 +319,35 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFileSelect = useCallback((e: Event) => {
|
const handleFileSelect = useLastCallback((e: Event) => {
|
||||||
const { files } = e.target as HTMLInputElement;
|
const { files } = e.target as HTMLInputElement;
|
||||||
const validatedFiles = validateFiles(files);
|
const validatedFiles = validateFiles(files);
|
||||||
|
|
||||||
if (validatedFiles?.length) {
|
if (validatedFiles?.length) {
|
||||||
onFileAppend(validatedFiles, isEverySpoiler);
|
onFileAppend(validatedFiles, isEverySpoiler);
|
||||||
}
|
}
|
||||||
}, [isEverySpoiler, onFileAppend]);
|
});
|
||||||
|
|
||||||
const handleDocumentSelect = useCallback(() => {
|
const handleDocumentSelect = useLastCallback(() => {
|
||||||
openSystemFilesDialog('*', (e) => handleFileSelect(e));
|
openSystemFilesDialog('*', (e) => handleFileSelect(e));
|
||||||
}, [handleFileSelect]);
|
});
|
||||||
|
|
||||||
const handleDelete = useCallback((index: number) => {
|
const handleDelete = useLastCallback((index: number) => {
|
||||||
onAttachmentsUpdate(attachments.filter((a, i) => i !== index));
|
onAttachmentsUpdate(attachments.filter((a, i) => i !== index));
|
||||||
}, [attachments, onAttachmentsUpdate]);
|
});
|
||||||
|
|
||||||
const handleEnableSpoilers = useCallback(() => {
|
const handleEnableSpoilers = useLastCallback(() => {
|
||||||
onAttachmentsUpdate(attachments.map((a) => ({
|
onAttachmentsUpdate(attachments.map((a) => ({
|
||||||
...a,
|
...a,
|
||||||
shouldSendAsSpoiler: a.mimeType !== GIF_MIME_TYPE ? true : undefined,
|
shouldSendAsSpoiler: a.mimeType !== GIF_MIME_TYPE ? true : undefined,
|
||||||
})));
|
})));
|
||||||
}, [attachments, onAttachmentsUpdate]);
|
});
|
||||||
|
|
||||||
const handleDisableSpoilers = useCallback(() => {
|
const handleDisableSpoilers = useLastCallback(() => {
|
||||||
onAttachmentsUpdate(attachments.map((a) => ({ ...a, shouldSendAsSpoiler: undefined })));
|
onAttachmentsUpdate(attachments.map((a) => ({ ...a, shouldSendAsSpoiler: undefined })));
|
||||||
}, [attachments, onAttachmentsUpdate]);
|
});
|
||||||
|
|
||||||
const handleToggleSpoiler = useCallback((index: number) => {
|
const handleToggleSpoiler = useLastCallback((index: number) => {
|
||||||
onAttachmentsUpdate(attachments.map((attachment, i) => {
|
onAttachmentsUpdate(attachments.map((attachment, i) => {
|
||||||
if (i === index) {
|
if (i === index) {
|
||||||
return {
|
return {
|
||||||
@ -360,7 +358,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return attachment;
|
return attachment;
|
||||||
}));
|
}));
|
||||||
}, [attachments, onAttachmentsUpdate]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const mainButton = mainButtonRef.current;
|
const mainButton = mainButtonRef.current;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import type { ApiAttachment } from '../../../api/types';
|
import type { ApiAttachment } from '../../../api/types';
|
||||||
@ -9,6 +9,8 @@ import buildClassName from '../../../util/buildClassName';
|
|||||||
import { formatMediaDuration } from '../../../util/dateFormat';
|
import { formatMediaDuration } from '../../../util/dateFormat';
|
||||||
import { REM } from '../../common/helpers/mediaDimensions';
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import File from '../../common/File';
|
import File from '../../common/File';
|
||||||
import MediaSpoiler from '../../common/MediaSpoiler';
|
import MediaSpoiler from '../../common/MediaSpoiler';
|
||||||
|
|
||||||
@ -39,9 +41,9 @@ const AttachmentModalItem: FC<OwnProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const displayType = getDisplayType(attachment, shouldDisplayCompressed);
|
const displayType = getDisplayType(attachment, shouldDisplayCompressed);
|
||||||
|
|
||||||
const handleSpoilerClick = useCallback(() => {
|
const handleSpoilerClick = useLastCallback(() => {
|
||||||
onToggleSpoiler?.(index);
|
onToggleSpoiler?.(index);
|
||||||
}, [index, onToggleSpoiler]);
|
});
|
||||||
|
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
switch (displayType) {
|
switch (displayType) {
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import React, { memo, useCallback } from '../../../lib/teact/teact';
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import type { ApiBotCommand } from '../../../api/types';
|
import type { ApiBotCommand } from '../../../api/types';
|
||||||
|
|
||||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMouseInside from '../../../hooks/useMouseInside';
|
import useMouseInside from '../../../hooks/useMouseInside';
|
||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
|
|
||||||
@ -27,12 +29,12 @@ const BotCommandMenu: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, isMobile);
|
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, isMobile);
|
||||||
|
|
||||||
const handleClick = useCallback((botCommand: ApiBotCommand) => {
|
const handleClick = useLastCallback((botCommand: ApiBotCommand) => {
|
||||||
sendBotCommand({
|
sendBotCommand({
|
||||||
command: `/${botCommand.command}`,
|
command: `/${botCommand.command}`,
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
}, [onClose, sendBotCommand]);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, { useEffect, useRef, memo } from '../../../lib/teact/teact';
|
||||||
useCallback, useEffect, useRef, memo,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getActions, getGlobal } from '../../../global';
|
import { getActions, getGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
@ -9,6 +7,8 @@ import type { ApiBotCommand } from '../../../api/types';
|
|||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
||||||
@ -40,7 +40,7 @@ const BotCommandTooltip: FC<OwnProps> = ({
|
|||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
||||||
|
|
||||||
const handleSendCommand = useCallback(({ botId, command }: ApiBotCommand) => {
|
const handleSendCommand = useLastCallback(({ botId, command }: ApiBotCommand) => {
|
||||||
// No need for expensive global updates on users and chats, so we avoid them
|
// No need for expensive global updates on users and chats, so we avoid them
|
||||||
const usersById = getGlobal().users.byId;
|
const usersById = getGlobal().users.byId;
|
||||||
const bot = usersById[botId];
|
const bot = usersById[botId];
|
||||||
@ -49,9 +49,9 @@ const BotCommandTooltip: FC<OwnProps> = ({
|
|||||||
command: `/${command}${withUsername && bot ? `@${bot.usernames![0].username}` : ''}`,
|
command: `/${command}${withUsername && bot ? `@${bot.usernames![0].username}` : ''}`,
|
||||||
});
|
});
|
||||||
onClick();
|
onClick();
|
||||||
}, [onClick, sendBotCommand, withUsername]);
|
});
|
||||||
|
|
||||||
const handleSelect = useCallback((botCommand: ApiBotCommand) => {
|
const handleSelect = useLastCallback((botCommand: ApiBotCommand) => {
|
||||||
// We need an additional check because tooltip is updated with throttling
|
// We need an additional check because tooltip is updated with throttling
|
||||||
if (!botCommand.command.startsWith(getHtml().slice(1))) {
|
if (!botCommand.command.startsWith(getHtml().slice(1))) {
|
||||||
return false;
|
return false;
|
||||||
@ -59,7 +59,7 @@ const BotCommandTooltip: FC<OwnProps> = ({
|
|||||||
|
|
||||||
handleSendCommand(botCommand);
|
handleSendCommand(botCommand);
|
||||||
return true;
|
return true;
|
||||||
}, [getHtml, handleSendCommand]);
|
});
|
||||||
|
|
||||||
const selectedCommandIndex = useKeyboardNavigation({
|
const selectedCommandIndex = useKeyboardNavigation({
|
||||||
isActive: isOpen,
|
isActive: isOpen,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState,
|
memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
@ -89,6 +89,7 @@ import { buildCustomEmojiHtml } from './helpers/customEmoji';
|
|||||||
import { processMessageInputForCustomEmoji } from '../../../util/customEmojiManager';
|
import { processMessageInputForCustomEmoji } from '../../../util/customEmojiManager';
|
||||||
import { getTextWithEntitiesAsHtml } from '../../common/helpers/renderTextWithEntities';
|
import { getTextWithEntitiesAsHtml } from '../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useSignal from '../../../hooks/useSignal';
|
import useSignal from '../../../hooks/useSignal';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
@ -335,12 +336,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const customEmojiNotificationNumber = useRef(0);
|
const customEmojiNotificationNumber = useRef(0);
|
||||||
|
|
||||||
const handleScheduleCancel = useCallback(() => {
|
const [requestCalendar, calendar] = useSchedule(canScheduleUntilOnline, cancelForceShowSymbolMenu);
|
||||||
cancelForceShowSymbolMenu();
|
|
||||||
}, [cancelForceShowSymbolMenu]);
|
|
||||||
const [requestCalendar, calendar] = useSchedule(canScheduleUntilOnline, handleScheduleCancel);
|
|
||||||
|
|
||||||
useTimeout(() => { setIsMounted(true); }, MOUNT_ANIMATION_DURATION);
|
useTimeout(() => {
|
||||||
|
setIsMounted(true);
|
||||||
|
}, MOUNT_ANIMATION_DURATION);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
lastMessageSendTimeSeconds.current = undefined;
|
lastMessageSendTimeSeconds.current = undefined;
|
||||||
@ -524,7 +524,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
chatBotCommands,
|
chatBotCommands,
|
||||||
);
|
);
|
||||||
|
|
||||||
const insertHtmlAndUpdateCursor = useCallback((newHtml: string, inputId: string = EDITABLE_INPUT_ID) => {
|
const insertHtmlAndUpdateCursor = useLastCallback((newHtml: string, inputId: string = EDITABLE_INPUT_ID) => {
|
||||||
if (inputId === EDITABLE_INPUT_ID && isComposerBlocked) return;
|
if (inputId === EDITABLE_INPUT_ID && isComposerBlocked) return;
|
||||||
const selection = window.getSelection()!;
|
const selection = window.getSelection()!;
|
||||||
let messageInput: HTMLDivElement;
|
let messageInput: HTMLDivElement;
|
||||||
@ -549,22 +549,22 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
requestNextMutation(() => {
|
requestNextMutation(() => {
|
||||||
focusEditableElement(messageInput);
|
focusEditableElement(messageInput);
|
||||||
});
|
});
|
||||||
}, [isComposerBlocked, getHtml, setHtml]);
|
});
|
||||||
|
|
||||||
const insertFormattedTextAndUpdateCursor = useCallback((
|
const insertFormattedTextAndUpdateCursor = useLastCallback((
|
||||||
text: ApiFormattedText, inputId: string = EDITABLE_INPUT_ID,
|
text: ApiFormattedText, inputId: string = EDITABLE_INPUT_ID,
|
||||||
) => {
|
) => {
|
||||||
const newHtml = getTextWithEntitiesAsHtml(text);
|
const newHtml = getTextWithEntitiesAsHtml(text);
|
||||||
insertHtmlAndUpdateCursor(newHtml, inputId);
|
insertHtmlAndUpdateCursor(newHtml, inputId);
|
||||||
}, [insertHtmlAndUpdateCursor]);
|
});
|
||||||
|
|
||||||
const insertCustomEmojiAndUpdateCursor = useCallback((emoji: ApiSticker, inputId: string = EDITABLE_INPUT_ID) => {
|
const insertCustomEmojiAndUpdateCursor = useLastCallback((emoji: ApiSticker, inputId: string = EDITABLE_INPUT_ID) => {
|
||||||
insertHtmlAndUpdateCursor(buildCustomEmojiHtml(emoji), inputId);
|
insertHtmlAndUpdateCursor(buildCustomEmojiHtml(emoji), inputId);
|
||||||
}, [insertHtmlAndUpdateCursor]);
|
});
|
||||||
|
|
||||||
useDraft(draft, chatId, threadId, getHtml, setHtml, editingMessage, lastSyncTime);
|
useDraft(draft, chatId, threadId, getHtml, setHtml, editingMessage, lastSyncTime);
|
||||||
|
|
||||||
const resetComposer = useCallback((shouldPreserveInput = false) => {
|
const resetComposer = useLastCallback((shouldPreserveInput = false) => {
|
||||||
if (!shouldPreserveInput) {
|
if (!shouldPreserveInput) {
|
||||||
setHtml('');
|
setHtml('');
|
||||||
}
|
}
|
||||||
@ -582,10 +582,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
closeSymbolMenu();
|
closeSymbolMenu();
|
||||||
}
|
}
|
||||||
}, [
|
});
|
||||||
setHtml, isMobile, closeStickerTooltip, closeCustomEmojiTooltip, closeMentionTooltip, closeEmojiTooltip,
|
|
||||||
closeSymbolMenu,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [handleEditComplete, handleEditCancel, shouldForceShowEditing] = useEditing(
|
const [handleEditComplete, handleEditCancel, shouldForceShowEditing] = useEditing(
|
||||||
getHtml,
|
getHtml,
|
||||||
@ -613,7 +610,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
};
|
};
|
||||||
}, [chatId, threadId, resetComposerRef, stopRecordingVoiceRef]);
|
}, [chatId, threadId, resetComposerRef, stopRecordingVoiceRef]);
|
||||||
|
|
||||||
const showCustomEmojiPremiumNotification = useCallback(() => {
|
const showCustomEmojiPremiumNotification = useLastCallback(() => {
|
||||||
const notificationNumber = customEmojiNotificationNumber.current;
|
const notificationNumber = customEmojiNotificationNumber.current;
|
||||||
if (!notificationNumber) {
|
if (!notificationNumber) {
|
||||||
showNotification({
|
showNotification({
|
||||||
@ -635,7 +632,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
customEmojiNotificationNumber.current = Number(!notificationNumber);
|
customEmojiNotificationNumber.current = Number(!notificationNumber);
|
||||||
}, [currentUserId, lang, showNotification]);
|
});
|
||||||
|
|
||||||
const mainButtonState = useDerivedState(() => {
|
const mainButtonState = useDerivedState(() => {
|
||||||
if (editingMessage && shouldForceShowEditing) {
|
if (editingMessage && shouldForceShowEditing) {
|
||||||
@ -672,13 +669,13 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
showCustomEmojiPremiumNotification,
|
showCustomEmojiPremiumNotification,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleEmbeddedClear = useCallback(() => {
|
const handleEmbeddedClear = useLastCallback(() => {
|
||||||
if (editingMessage) {
|
if (editingMessage) {
|
||||||
handleEditCancel();
|
handleEditCancel();
|
||||||
}
|
}
|
||||||
}, [editingMessage, handleEditCancel]);
|
});
|
||||||
|
|
||||||
const validateTextLength = useCallback((text: string, isAttachmentModal?: boolean) => {
|
const validateTextLength = useLastCallback((text: string, isAttachmentModal?: boolean) => {
|
||||||
const maxLength = isAttachmentModal ? captionLimit : MESSAGE_MAX_LENGTH;
|
const maxLength = isAttachmentModal ? captionLimit : MESSAGE_MAX_LENGTH;
|
||||||
if (text?.length > maxLength) {
|
if (text?.length > maxLength) {
|
||||||
const extraLength = text.length - maxLength;
|
const extraLength = text.length - maxLength;
|
||||||
@ -696,9 +693,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}, [captionLimit, showDialog]);
|
});
|
||||||
|
|
||||||
const checkSlowMode = useCallback(() => {
|
const checkSlowMode = useLastCallback(() => {
|
||||||
if (slowMode && !isAdmin) {
|
if (slowMode && !isAdmin) {
|
||||||
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
||||||
|
|
||||||
@ -728,9 +725,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}, [isAdmin, lang, showDialog, slowMode]);
|
});
|
||||||
|
|
||||||
const sendAttachments = useCallback(({
|
const sendAttachments = useLastCallback(({
|
||||||
attachments: attachmentsToSend,
|
attachments: attachmentsToSend,
|
||||||
sendCompressed = attachmentSettings.shouldCompress,
|
sendCompressed = attachmentSettings.shouldCompress,
|
||||||
sendGrouped = attachmentSettings.shouldSendGrouped,
|
sendGrouped = attachmentSettings.shouldSendGrouped,
|
||||||
@ -772,12 +769,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer();
|
resetComposer();
|
||||||
});
|
});
|
||||||
}, [
|
});
|
||||||
attachmentSettings.shouldCompress, attachmentSettings.shouldSendGrouped, connectionState, getHtml,
|
|
||||||
validateTextLength, checkSlowMode, sendMessage, clearDraft, chatId, resetComposer, shouldUpdateStickerSetOrder,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleSendAttachments = useCallback((
|
const handleSendAttachments = useLastCallback((
|
||||||
sendCompressed: boolean,
|
sendCompressed: boolean,
|
||||||
sendGrouped: boolean,
|
sendGrouped: boolean,
|
||||||
isSilent?: boolean,
|
isSilent?: boolean,
|
||||||
@ -790,9 +784,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
});
|
});
|
||||||
}, [attachments, sendAttachments]);
|
});
|
||||||
|
|
||||||
const handleSend = useCallback(async (isSilent = false, scheduledAt?: number) => {
|
const handleSend = useLastCallback(async (isSilent = false, scheduledAt?: number) => {
|
||||||
if (connectionState !== 'connectionStateReady') {
|
if (connectionState !== 'connectionStateReady') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -859,13 +853,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer();
|
resetComposer();
|
||||||
});
|
});
|
||||||
}, [
|
});
|
||||||
connectionState, attachments, activeVoiceRecording, getHtml, isForwarding, validateTextLength, clearDraft,
|
|
||||||
chatId, stopRecordingVoice, sendAttachments, checkSlowMode, sendMessage, forwardMessages, resetComposer,
|
|
||||||
shouldUpdateStickerSetOrder,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleClickBotMenu = useCallback(() => {
|
const handleClickBotMenu = useLastCallback(() => {
|
||||||
if (botMenuButton?.type !== 'webApp') {
|
if (botMenuButton?.type !== 'webApp') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -873,14 +863,14 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
callAttachBot({
|
callAttachBot({
|
||||||
chatId, url: botMenuButton.url, threadId,
|
chatId, url: botMenuButton.url, threadId,
|
||||||
});
|
});
|
||||||
}, [botMenuButton, callAttachBot, chatId, threadId]);
|
});
|
||||||
|
|
||||||
const handleActivateBotCommandMenu = useCallback(() => {
|
const handleActivateBotCommandMenu = useLastCallback(() => {
|
||||||
closeSymbolMenu();
|
closeSymbolMenu();
|
||||||
openBotCommandMenu();
|
openBotCommandMenu();
|
||||||
}, [closeSymbolMenu, openBotCommandMenu]);
|
});
|
||||||
|
|
||||||
const handleMessageSchedule = useCallback((
|
const handleMessageSchedule = useLastCallback((
|
||||||
args: ScheduledMessageArgs, scheduledAt: number,
|
args: ScheduledMessageArgs, scheduledAt: number,
|
||||||
) => {
|
) => {
|
||||||
if (args && 'queryId' in args) {
|
if (args && 'queryId' in args) {
|
||||||
@ -907,7 +897,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
scheduledAt,
|
scheduledAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [handleSendAttachments, handleSend, sendInlineBotResult, sendMessage]);
|
});
|
||||||
|
|
||||||
useEffectWithPrevDeps(([prevContentToBeScheduled]) => {
|
useEffectWithPrevDeps(([prevContentToBeScheduled]) => {
|
||||||
if (contentToBeScheduled && contentToBeScheduled !== prevContentToBeScheduled) {
|
if (contentToBeScheduled && contentToBeScheduled !== prevContentToBeScheduled) {
|
||||||
@ -936,20 +926,20 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [handleFileSelect, requestedDraftFiles, resetOpenChatWithDraft]);
|
}, [handleFileSelect, requestedDraftFiles, resetOpenChatWithDraft]);
|
||||||
|
|
||||||
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker, inputId?: string) => {
|
const handleCustomEmojiSelect = useLastCallback((emoji: ApiSticker, inputId?: string) => {
|
||||||
if (!emoji.isFree && !isCurrentUserPremium && !isChatWithSelf) {
|
if (!emoji.isFree && !isCurrentUserPremium && !isChatWithSelf) {
|
||||||
showCustomEmojiPremiumNotification();
|
showCustomEmojiPremiumNotification();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertCustomEmojiAndUpdateCursor(emoji, inputId);
|
insertCustomEmojiAndUpdateCursor(emoji, inputId);
|
||||||
}, [insertCustomEmojiAndUpdateCursor, isChatWithSelf, isCurrentUserPremium, showCustomEmojiPremiumNotification]);
|
});
|
||||||
|
|
||||||
const handleCustomEmojiSelectAttachmentModal = useCallback((emoji: ApiSticker) => {
|
const handleCustomEmojiSelectAttachmentModal = useLastCallback((emoji: ApiSticker) => {
|
||||||
handleCustomEmojiSelect(emoji, EDITABLE_INPUT_MODAL_ID);
|
handleCustomEmojiSelect(emoji, EDITABLE_INPUT_MODAL_ID);
|
||||||
}, [handleCustomEmojiSelect]);
|
});
|
||||||
|
|
||||||
const handleGifSelect = useCallback((gif: ApiVideo, isSilent?: boolean, isScheduleRequested?: boolean) => {
|
const handleGifSelect = useLastCallback((gif: ApiVideo, isSilent?: boolean, isScheduleRequested?: boolean) => {
|
||||||
if (shouldSchedule || isScheduleRequested) {
|
if (shouldSchedule || isScheduleRequested) {
|
||||||
forceShowSymbolMenu();
|
forceShowSymbolMenu();
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
@ -965,12 +955,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
resetComposer(true);
|
resetComposer(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [
|
});
|
||||||
shouldSchedule, forceShowSymbolMenu, requestCalendar, cancelForceShowSymbolMenu, handleMessageSchedule,
|
|
||||||
resetComposer, sendMessage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleStickerSelect = useCallback((
|
const handleStickerSelect = useLastCallback((
|
||||||
sticker: ApiSticker,
|
sticker: ApiSticker,
|
||||||
isSilent?: boolean,
|
isSilent?: boolean,
|
||||||
isScheduleRequested?: boolean,
|
isScheduleRequested?: boolean,
|
||||||
@ -1001,12 +988,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
resetComposer(shouldPreserveInput);
|
resetComposer(shouldPreserveInput);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [
|
});
|
||||||
shouldSchedule, forceShowSymbolMenu, requestCalendar, cancelForceShowSymbolMenu, handleMessageSchedule,
|
|
||||||
resetComposer, sendMessage, shouldUpdateStickerSetOrder,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleInlineBotSelect = useCallback((
|
const handleInlineBotSelect = useLastCallback((
|
||||||
inlineResult: ApiBotInlineResult | ApiBotInlineMediaResult, isSilent?: boolean, isScheduleRequested?: boolean,
|
inlineResult: ApiBotInlineResult | ApiBotInlineMediaResult, isSilent?: boolean, isScheduleRequested?: boolean,
|
||||||
) => {
|
) => {
|
||||||
if (connectionState !== 'connectionStateReady') {
|
if (connectionState !== 'connectionStateReady') {
|
||||||
@ -1038,19 +1022,16 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer();
|
resetComposer();
|
||||||
});
|
});
|
||||||
}, [
|
});
|
||||||
chatId, clearDraft, connectionState, handleMessageSchedule, requestCalendar, resetComposer, sendInlineBotResult,
|
|
||||||
shouldSchedule,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleBotCommandSelect = useCallback(() => {
|
const handleBotCommandSelect = useLastCallback(() => {
|
||||||
clearDraft({ chatId, localOnly: true });
|
clearDraft({ chatId, localOnly: true });
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer();
|
resetComposer();
|
||||||
});
|
});
|
||||||
}, [chatId, clearDraft, resetComposer]);
|
});
|
||||||
|
|
||||||
const handlePollSend = useCallback((poll: ApiNewPoll) => {
|
const handlePollSend = useLastCallback((poll: ApiNewPoll) => {
|
||||||
if (shouldSchedule) {
|
if (shouldSchedule) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
handleMessageSchedule({ poll }, scheduledAt);
|
handleMessageSchedule({ poll }, scheduledAt);
|
||||||
@ -1060,9 +1041,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendMessage({ poll });
|
sendMessage({ poll });
|
||||||
closePollModal();
|
closePollModal();
|
||||||
}
|
}
|
||||||
}, [closePollModal, handleMessageSchedule, requestCalendar, sendMessage, shouldSchedule]);
|
});
|
||||||
|
|
||||||
const sendSilent = useCallback((additionalArgs?: ScheduledMessageArgs) => {
|
const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => {
|
||||||
if (shouldSchedule) {
|
if (shouldSchedule) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
handleMessageSchedule({ ...additionalArgs, isSilent: true }, scheduledAt);
|
handleMessageSchedule({ ...additionalArgs, isSilent: true }, scheduledAt);
|
||||||
@ -1073,9 +1054,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
void handleSend(true);
|
void handleSend(true);
|
||||||
}
|
}
|
||||||
}, [handleMessageSchedule, handleSend, handleSendAttachments, requestCalendar, shouldSchedule]);
|
});
|
||||||
|
|
||||||
const handleSendAsMenuOpen = useCallback(() => {
|
const handleSendAsMenuOpen = useLastCallback(() => {
|
||||||
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
||||||
|
|
||||||
if (!isMobile || messageInput !== document.activeElement) {
|
if (!isMobile || messageInput !== document.activeElement) {
|
||||||
@ -1091,14 +1072,14 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
closeSymbolMenu();
|
closeSymbolMenu();
|
||||||
openSendAsMenu();
|
openSendAsMenu();
|
||||||
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
||||||
}, [closeBotCommandMenu, closeSymbolMenu, openSendAsMenu, isMobile]);
|
});
|
||||||
|
|
||||||
const insertTextAndUpdateCursor = useCallback((text: string, inputId: string = EDITABLE_INPUT_ID) => {
|
const insertTextAndUpdateCursor = useLastCallback((text: string, inputId: string = EDITABLE_INPUT_ID) => {
|
||||||
const newHtml = renderText(text, ['escape_html', 'emoji_html', 'br_html'])
|
const newHtml = renderText(text, ['escape_html', 'emoji_html', 'br_html'])
|
||||||
.join('')
|
.join('')
|
||||||
.replace(/\u200b+/g, '\u200b');
|
.replace(/\u200b+/g, '\u200b');
|
||||||
insertHtmlAndUpdateCursor(newHtml, inputId);
|
insertHtmlAndUpdateCursor(newHtml, inputId);
|
||||||
}, [insertHtmlAndUpdateCursor]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isComposerBlocked) return;
|
if (!isComposerBlocked) return;
|
||||||
@ -1106,11 +1087,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
setHtml('');
|
setHtml('');
|
||||||
}, [isComposerBlocked, setHtml, attachments]);
|
}, [isComposerBlocked, setHtml, attachments]);
|
||||||
|
|
||||||
const insertTextAndUpdateCursorAttachmentModal = useCallback((text: string) => {
|
const insertTextAndUpdateCursorAttachmentModal = useLastCallback((text: string) => {
|
||||||
insertTextAndUpdateCursor(text, EDITABLE_INPUT_MODAL_ID);
|
insertTextAndUpdateCursor(text, EDITABLE_INPUT_MODAL_ID);
|
||||||
}, [insertTextAndUpdateCursor]);
|
});
|
||||||
|
|
||||||
const removeSymbol = useCallback((inputId = EDITABLE_INPUT_ID) => {
|
const removeSymbol = useLastCallback((inputId = EDITABLE_INPUT_ID) => {
|
||||||
const selection = window.getSelection()!;
|
const selection = window.getSelection()!;
|
||||||
|
|
||||||
if (selection.rangeCount) {
|
if (selection.rangeCount) {
|
||||||
@ -1122,17 +1103,17 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setHtml(deleteLastCharacterOutsideSelection(getHtml()));
|
setHtml(deleteLastCharacterOutsideSelection(getHtml()));
|
||||||
}, [getHtml, setHtml]);
|
});
|
||||||
|
|
||||||
const removeSymbolAttachmentModal = useCallback(() => {
|
const removeSymbolAttachmentModal = useLastCallback(() => {
|
||||||
removeSymbol(EDITABLE_INPUT_MODAL_ID);
|
removeSymbol(EDITABLE_INPUT_MODAL_ID);
|
||||||
}, [removeSymbol]);
|
});
|
||||||
|
|
||||||
const handleAllScheduledClick = useCallback(() => {
|
const handleAllScheduledClick = useLastCallback(() => {
|
||||||
openChat({
|
openChat({
|
||||||
id: chatId, threadId, type: 'scheduled', noForumTopicPanel: true,
|
id: chatId, threadId, type: 'scheduled', noForumTopicPanel: true,
|
||||||
});
|
});
|
||||||
}, [openChat, chatId, threadId]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isRightColumnShown && isMobile) {
|
if (isRightColumnShown && isMobile) {
|
||||||
@ -1155,7 +1136,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
const areVoiceMessagesNotAllowed = mainButtonState === MainButtonState.Record
|
const areVoiceMessagesNotAllowed = mainButtonState === MainButtonState.Record
|
||||||
&& (!canAttachMedia || !canSendVoiceByPrivacy || !canSendVoices);
|
&& (!canAttachMedia || !canSendVoiceByPrivacy || !canSendVoices);
|
||||||
|
|
||||||
const mainButtonHandler = useCallback(() => {
|
const mainButtonHandler = useLastCallback(() => {
|
||||||
switch (mainButtonState) {
|
switch (mainButtonState) {
|
||||||
case MainButtonState.Send:
|
case MainButtonState.Send:
|
||||||
void handleSend();
|
void handleSend();
|
||||||
@ -1188,11 +1169,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, [
|
});
|
||||||
mainButtonState, handleSend, handleEditComplete, activeVoiceRecording, requestCalendar, areVoiceMessagesNotAllowed,
|
|
||||||
canSendVoiceByPrivacy, showNotification, lang, chat?.title, startRecordingVoice, pauseRecordingVoice,
|
|
||||||
handleMessageSchedule, chatId, showAllowedMessageTypesNotification, canSendVoices,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const prevEditedMessage = usePrevious(editingMessage, true);
|
const prevEditedMessage = usePrevious(editingMessage, true);
|
||||||
const renderedEditedMessage = editingMessage || prevEditedMessage;
|
const renderedEditedMessage = editingMessage || prevEditedMessage;
|
||||||
@ -1222,29 +1199,29 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isMounted && 'mounted',
|
isMounted && 'mounted',
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSendScheduled = useCallback(() => {
|
const handleSendScheduled = useLastCallback(() => {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
handleMessageSchedule({}, scheduledAt);
|
handleMessageSchedule({}, scheduledAt);
|
||||||
});
|
});
|
||||||
}, [handleMessageSchedule, requestCalendar]);
|
});
|
||||||
|
|
||||||
const handleSendSilent = useCallback(() => {
|
const handleSendSilent = useLastCallback(() => {
|
||||||
sendSilent();
|
sendSilent();
|
||||||
}, [sendSilent]);
|
});
|
||||||
|
|
||||||
const handleSendWhenOnline = useCallback(() => {
|
const handleSendWhenOnline = useLastCallback(() => {
|
||||||
handleMessageSchedule({ }, SCHEDULED_WHEN_ONLINE);
|
handleMessageSchedule({}, SCHEDULED_WHEN_ONLINE);
|
||||||
}, [handleMessageSchedule]);
|
});
|
||||||
|
|
||||||
const handleSendScheduledAttachments = useCallback((sendCompressed: boolean, sendGrouped: boolean) => {
|
const handleSendScheduledAttachments = useLastCallback((sendCompressed: boolean, sendGrouped: boolean) => {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
handleMessageSchedule({ sendCompressed, sendGrouped }, scheduledAt);
|
handleMessageSchedule({ sendCompressed, sendGrouped }, scheduledAt);
|
||||||
});
|
});
|
||||||
}, [handleMessageSchedule, requestCalendar]);
|
});
|
||||||
|
|
||||||
const handleSendSilentAttachments = useCallback((sendCompressed: boolean, sendGrouped: boolean) => {
|
const handleSendSilentAttachments = useLastCallback((sendCompressed: boolean, sendGrouped: boolean) => {
|
||||||
sendSilent({ sendCompressed, sendGrouped });
|
sendSilent({ sendCompressed, sendGrouped });
|
||||||
}, [sendSilent]);
|
});
|
||||||
|
|
||||||
const onSend = mainButtonState === MainButtonState.Edit
|
const onSend = mainButtonState === MainButtonState.Edit
|
||||||
? handleEditComplete
|
? handleEditComplete
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef,
|
memo, useEffect, useMemo, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
|||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { isUserId, stripCustomEmoji } from '../../../global/helpers';
|
import { isUserId, stripCustomEmoji } from '../../../global/helpers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -101,7 +102,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
|||||||
shouldRender, transitionClassNames,
|
shouldRender, transitionClassNames,
|
||||||
} = useShowTransition(canAnimate && isShown, undefined, !shouldAnimate, undefined, !shouldAnimate);
|
} = useShowTransition(canAnimate && isShown, undefined, !shouldAnimate, undefined, !shouldAnimate);
|
||||||
|
|
||||||
const clearEmbedded = useCallback(() => {
|
const clearEmbedded = useLastCallback(() => {
|
||||||
if (replyingToId && !shouldForceShowEditing) {
|
if (replyingToId && !shouldForceShowEditing) {
|
||||||
setReplyingToId({ messageId: undefined });
|
setReplyingToId({ messageId: undefined });
|
||||||
} else if (editingId) {
|
} else if (editingId) {
|
||||||
@ -110,35 +111,32 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
|||||||
exitForwardMode();
|
exitForwardMode();
|
||||||
}
|
}
|
||||||
onClear?.();
|
onClear?.();
|
||||||
}, [
|
});
|
||||||
replyingToId, shouldForceShowEditing, editingId, forwardedMessagesCount, onClear, setReplyingToId, setEditingId,
|
|
||||||
exitForwardMode,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => (isShown ? captureEscKeyListener(clearEmbedded) : undefined), [isShown, clearEmbedded]);
|
useEffect(() => (isShown ? captureEscKeyListener(clearEmbedded) : undefined), [isShown, clearEmbedded]);
|
||||||
|
|
||||||
const handleMessageClick = useCallback((): void => {
|
const handleMessageClick = useLastCallback((): void => {
|
||||||
if (isForwarding) return;
|
if (isForwarding) return;
|
||||||
focusMessage({ chatId: message!.chatId, messageId: message!.id, noForumTopicPanel: true });
|
focusMessage({ chatId: message!.chatId, messageId: message!.id, noForumTopicPanel: true });
|
||||||
}, [focusMessage, isForwarding, message]);
|
});
|
||||||
|
|
||||||
const handleClearClick = useCallback((e: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
const handleClearClick = useLastCallback((e: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
clearEmbedded();
|
clearEmbedded();
|
||||||
}, [clearEmbedded]);
|
});
|
||||||
|
|
||||||
const handleChangeRecipientClick = useCallback(() => {
|
const handleChangeRecipientClick = useLastCallback(() => {
|
||||||
changeForwardRecipient();
|
changeForwardRecipient();
|
||||||
}, [changeForwardRecipient]);
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition, handleContextMenu,
|
isContextMenuOpen, contextMenuPosition, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref);
|
} = useContextMenuHandlers(ref);
|
||||||
|
|
||||||
const getTriggerElement = useCallback(() => ref.current, []);
|
const getTriggerElement = useLastCallback(() => ref.current);
|
||||||
const getRootElement = useCallback(() => ref.current!, []);
|
const getRootElement = useLastCallback(() => ref.current!);
|
||||||
const getMenuElement = useCallback(() => ref.current!.querySelector('.forward-context-menu .bubble'), []);
|
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.forward-context-menu .bubble'));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useCallback } from '../../../lib/teact/teact';
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import type { ApiSticker } from '../../../api/types';
|
import type { ApiSticker } from '../../../api/types';
|
||||||
@ -6,6 +6,8 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
|||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import CustomEmoji from '../../common/CustomEmoji';
|
import CustomEmoji from '../../common/CustomEmoji';
|
||||||
|
|
||||||
import './EmojiButton.scss';
|
import './EmojiButton.scss';
|
||||||
@ -22,12 +24,12 @@ type OwnProps = {
|
|||||||
const CustomEmojiButton: FC<OwnProps> = ({
|
const CustomEmojiButton: FC<OwnProps> = ({
|
||||||
emoji, focus, onClick, observeIntersection,
|
emoji, focus, onClick, observeIntersection,
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
const handleClick = useLastCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||||
// Preventing safari from losing focus on Composer MessageInput
|
// Preventing safari from losing focus on Composer MessageInput
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
onClick?.(emoji);
|
onClick?.(emoji);
|
||||||
}, [emoji, onClick]);
|
});
|
||||||
|
|
||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
'EmojiButton',
|
'EmojiButton',
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import React, {
|
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||||
memo, useCallback, useEffect, useRef,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
@ -12,6 +10,7 @@ import { selectIsChatWithSelf, selectIsCurrentUserPremium } from '../../../globa
|
|||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
@ -65,14 +64,14 @@ const CustomEmojiTooltip: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useEffect(() => (isOpen ? captureEscKeyListener(onClose) : undefined), [isOpen, onClose]);
|
useEffect(() => (isOpen ? captureEscKeyListener(onClose) : undefined), [isOpen, onClose]);
|
||||||
|
|
||||||
const handleCustomEmojiSelect = useCallback((ce: ApiSticker) => {
|
const handleCustomEmojiSelect = useLastCallback((ce: ApiSticker) => {
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
onCustomEmojiSelect(ce);
|
onCustomEmojiSelect(ce);
|
||||||
addRecentCustomEmoji({
|
addRecentCustomEmoji({
|
||||||
documentId: ce.id,
|
documentId: ce.id,
|
||||||
});
|
});
|
||||||
clearCustomEmojiForEmoji();
|
clearCustomEmojiForEmoji();
|
||||||
}, [addRecentCustomEmoji, clearCustomEmojiForEmoji, isOpen, onCustomEmojiSelect]);
|
});
|
||||||
|
|
||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
styles.root,
|
styles.root,
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||||
memo, useCallback, useEffect, useRef,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
|
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
|
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
|
||||||
|
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
|
|
||||||
import Portal from '../../ui/Portal';
|
import Portal from '../../ui/Portal';
|
||||||
@ -40,7 +40,7 @@ const DropArea: FC<OwnProps> = ({
|
|||||||
|
|
||||||
useEffect(() => (isOpen ? captureEscKeyListener(onHide) : undefined), [isOpen, onHide]);
|
useEffect(() => (isOpen ? captureEscKeyListener(onHide) : undefined), [isOpen, onHide]);
|
||||||
|
|
||||||
const handleFilesDrop = useCallback(async (e: React.DragEvent<HTMLDivElement>) => {
|
const handleFilesDrop = useLastCallback(async (e: React.DragEvent<HTMLDivElement>) => {
|
||||||
const { dataTransfer: dt } = e;
|
const { dataTransfer: dt } = e;
|
||||||
let files: File[] = [];
|
let files: File[] = [];
|
||||||
|
|
||||||
@ -55,18 +55,18 @@ const DropArea: FC<OwnProps> = ({
|
|||||||
|
|
||||||
onHide();
|
onHide();
|
||||||
onFileSelect(files, withQuick ? false : undefined);
|
onFileSelect(files, withQuick ? false : undefined);
|
||||||
}, [onFileSelect, onHide, withQuick]);
|
});
|
||||||
|
|
||||||
const handleQuickFilesDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleQuickFilesDrop = useLastCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||||
const { dataTransfer: dt } = e;
|
const { dataTransfer: dt } = e;
|
||||||
|
|
||||||
if (dt.files && dt.files.length > 0) {
|
if (dt.files && dt.files.length > 0) {
|
||||||
onHide();
|
onHide();
|
||||||
onFileSelect(Array.from(dt.files), true);
|
onFileSelect(Array.from(dt.files), true);
|
||||||
}
|
}
|
||||||
}, [onFileSelect, onHide]);
|
});
|
||||||
|
|
||||||
const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleDragLeave = useLastCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const { target: fromTarget, relatedTarget: toTarget } = e;
|
const { target: fromTarget, relatedTarget: toTarget } = e;
|
||||||
@ -77,7 +77,7 @@ const DropArea: FC<OwnProps> = ({
|
|||||||
onHide();
|
onHide();
|
||||||
}, DROP_LEAVE_TIMEOUT_MS);
|
}, DROP_LEAVE_TIMEOUT_MS);
|
||||||
}
|
}
|
||||||
}, [onHide]);
|
});
|
||||||
|
|
||||||
const handleDragOver = () => {
|
const handleDragOver = () => {
|
||||||
if (hideTimeoutRef.current) {
|
if (hideTimeoutRef.current) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useCallback } from '../../../lib/teact/teact';
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
@ -7,6 +7,8 @@ import { IS_EMOJI_SUPPORTED } from '../../../util/windowEnvironment';
|
|||||||
import { handleEmojiLoad, LOADED_EMOJIS } from '../../../util/emoji';
|
import { handleEmojiLoad, LOADED_EMOJIS } from '../../../util/emoji';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import './EmojiButton.scss';
|
import './EmojiButton.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
@ -18,12 +20,12 @@ type OwnProps = {
|
|||||||
const EmojiButton: FC<OwnProps> = ({
|
const EmojiButton: FC<OwnProps> = ({
|
||||||
emoji, focus, onClick,
|
emoji, focus, onClick,
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
const handleClick = useLastCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||||
// Preventing safari from losing focus on Composer MessageInput
|
// Preventing safari from losing focus on Composer MessageInput
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
onClick(emoji.native, emoji.id);
|
onClick(emoji.native, emoji.id);
|
||||||
}, [emoji, onClick]);
|
});
|
||||||
|
|
||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
'EmojiButton',
|
'EmojiButton',
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
useState, useEffect, memo, useRef, useMemo, useCallback,
|
useState, useEffect, memo, useRef, useMemo,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../global';
|
import { withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -20,6 +20,8 @@ import animateScroll from '../../../util/animateScroll';
|
|||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
|
import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||||
@ -167,16 +169,16 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
|
|||||||
}, OPEN_ANIMATION_DELAY);
|
}, OPEN_ANIMATION_DELAY);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const selectCategory = useCallback((index: number) => {
|
const selectCategory = useLastCallback((index: number) => {
|
||||||
setActiveCategoryIndex(index);
|
setActiveCategoryIndex(index);
|
||||||
const categoryEl = containerRef.current!.closest<HTMLElement>('.SymbolMenu-main')!
|
const categoryEl = containerRef.current!.closest<HTMLElement>('.SymbolMenu-main')!
|
||||||
.querySelector(`#emoji-category-${index}`)! as HTMLElement;
|
.querySelector(`#emoji-category-${index}`)! as HTMLElement;
|
||||||
animateScroll(containerRef.current!, categoryEl, 'start', FOCUS_MARGIN, SMOOTH_SCROLL_DISTANCE);
|
animateScroll(containerRef.current!, categoryEl, 'start', FOCUS_MARGIN, SMOOTH_SCROLL_DISTANCE);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleEmojiSelect = useCallback((emoji: string, name: string) => {
|
const handleEmojiSelect = useLastCallback((emoji: string, name: string) => {
|
||||||
onEmojiSelect(emoji, name);
|
onEmojiSelect(emoji, name);
|
||||||
}, [onEmojiSelect]);
|
});
|
||||||
|
|
||||||
function renderCategoryButton(category: EmojiCategoryData, index: number) {
|
function renderCategoryButton(category: EmojiCategoryData, index: number) {
|
||||||
const icon = ICONS_BY_CATEGORY[category.id];
|
const icon = ICONS_BY_CATEGORY[category.id];
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import React, {
|
import React, { memo, useRef } from '../../../lib/teact/teact';
|
||||||
memo, useCallback, useRef,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
|
|
||||||
import type { ApiSticker } from '../../../api/types';
|
import type { ApiSticker } from '../../../api/types';
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
@ -10,6 +8,7 @@ import findInViewport from '../../../util/findInViewport';
|
|||||||
import isFullyVisible from '../../../util/isFullyVisible';
|
import isFullyVisible from '../../../util/isFullyVisible';
|
||||||
import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
|
import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import usePrevDuringAnimation from '../../../hooks/usePrevDuringAnimation';
|
import usePrevDuringAnimation from '../../../hooks/usePrevDuringAnimation';
|
||||||
@ -91,33 +90,33 @@ const EmojiTooltip: FC<OwnProps> = ({
|
|||||||
observe: observeIntersection,
|
observe: observeIntersection,
|
||||||
} = useIntersectionObserver({ rootRef: containerRef, throttleMs: INTERSECTION_THROTTLE, isDisabled: !isOpen });
|
} = useIntersectionObserver({ rootRef: containerRef, throttleMs: INTERSECTION_THROTTLE, isDisabled: !isOpen });
|
||||||
|
|
||||||
const handleSelectEmoji = useCallback((emoji: Emoji) => {
|
const handleSelectEmoji = useLastCallback((emoji: Emoji) => {
|
||||||
onEmojiSelect(emoji.native);
|
onEmojiSelect(emoji.native);
|
||||||
addRecentEmoji({ emoji: emoji.id });
|
addRecentEmoji({ emoji: emoji.id });
|
||||||
}, [addRecentEmoji, onEmojiSelect]);
|
});
|
||||||
|
|
||||||
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => {
|
const handleCustomEmojiSelect = useLastCallback((emoji: ApiSticker) => {
|
||||||
onCustomEmojiSelect(emoji);
|
onCustomEmojiSelect(emoji);
|
||||||
addRecentCustomEmoji({ documentId: emoji.id });
|
addRecentCustomEmoji({ documentId: emoji.id });
|
||||||
}, [addRecentCustomEmoji, onCustomEmojiSelect]);
|
});
|
||||||
|
|
||||||
const handleSelect = useCallback((emoji: Emoji | ApiSticker) => {
|
const handleSelect = useLastCallback((emoji: Emoji | ApiSticker) => {
|
||||||
if ('native' in emoji) {
|
if ('native' in emoji) {
|
||||||
handleSelectEmoji(emoji);
|
handleSelectEmoji(emoji);
|
||||||
} else {
|
} else {
|
||||||
handleCustomEmojiSelect(emoji);
|
handleCustomEmojiSelect(emoji);
|
||||||
}
|
}
|
||||||
}, [handleCustomEmojiSelect, handleSelectEmoji]);
|
});
|
||||||
|
|
||||||
const handleClick = useCallback((native: string, id: string) => {
|
const handleClick = useLastCallback((native: string, id: string) => {
|
||||||
onEmojiSelect(native);
|
onEmojiSelect(native);
|
||||||
addRecentEmoji({ emoji: id });
|
addRecentEmoji({ emoji: id });
|
||||||
}, [addRecentEmoji, onEmojiSelect]);
|
});
|
||||||
|
|
||||||
const handleCustomEmojiClick = useCallback((emoji: ApiSticker) => {
|
const handleCustomEmojiClick = useLastCallback((emoji: ApiSticker) => {
|
||||||
onCustomEmojiSelect(emoji);
|
onCustomEmojiSelect(emoji);
|
||||||
addRecentCustomEmoji({ documentId: emoji.id });
|
addRecentCustomEmoji({ documentId: emoji.id });
|
||||||
}, [addRecentCustomEmoji, onCustomEmojiSelect]);
|
});
|
||||||
|
|
||||||
const selectedIndex = useKeyboardNavigation({
|
const selectedIndex = useKeyboardNavigation({
|
||||||
isActive: isOpen,
|
isActive: isOpen,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useEffect, memo, useRef, useCallback,
|
useEffect, memo, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -11,6 +11,7 @@ import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
|||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { selectCurrentMessageList, selectIsChatWithSelf } from '../../../global/selectors';
|
import { selectCurrentMessageList, selectIsChatWithSelf } from '../../../global/selectors';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||||
|
|
||||||
@ -56,9 +57,9 @@ const GifPicker: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [loadAndPlay, loadSavedGifs]);
|
}, [loadAndPlay, loadSavedGifs]);
|
||||||
|
|
||||||
const handleUnsaveClick = useCallback((gif: ApiVideo) => {
|
const handleUnsaveClick = useLastCallback((gif: ApiVideo) => {
|
||||||
saveGif({ gif, shouldUnsave: true });
|
saveGif({ gif, shouldUnsave: true });
|
||||||
}, [saveGif]);
|
});
|
||||||
|
|
||||||
const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION);
|
const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import React, {
|
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||||
memo, useCallback, useEffect, useRef,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
@ -15,6 +13,7 @@ import buildClassName from '../../../util/buildClassName';
|
|||||||
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
@ -43,7 +42,7 @@ export type OwnProps = {
|
|||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
canSendGifs?: boolean;
|
canSendGifs?: boolean;
|
||||||
onSelectResult: (
|
onSelectResult: (
|
||||||
inlineResult: ApiBotInlineMediaResult | ApiBotInlineResult, isSilent?: boolean, shouldSchedule?: boolean
|
inlineResult: ApiBotInlineMediaResult | ApiBotInlineResult, isSilent?: boolean, shouldSchedule?: boolean,
|
||||||
) => void;
|
) => void;
|
||||||
loadMore: NoneToVoidFunction;
|
loadMore: NoneToVoidFunction;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
@ -82,11 +81,11 @@ const InlineBotTooltip: FC<OwnProps> = ({
|
|||||||
isDisabled: !isOpen,
|
isDisabled: !isOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
const handleLoadMore = useLastCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||||
if (direction === LoadMoreDirection.Backwards) {
|
if (direction === LoadMoreDirection.Backwards) {
|
||||||
runThrottled(loadMore);
|
runThrottled(loadMore);
|
||||||
}
|
}
|
||||||
}, [loadMore]);
|
});
|
||||||
|
|
||||||
const selectedIndex = useKeyboardNavigation({
|
const selectedIndex = useKeyboardNavigation({
|
||||||
isActive: isOpen,
|
isActive: isOpen,
|
||||||
@ -101,12 +100,12 @@ const InlineBotTooltip: FC<OwnProps> = ({
|
|||||||
setTooltipItemVisible('.chat-item-clickable', selectedIndex, containerRef);
|
setTooltipItemVisible('.chat-item-clickable', selectedIndex, containerRef);
|
||||||
}, [selectedIndex]);
|
}, [selectedIndex]);
|
||||||
|
|
||||||
const handleSendPm = useCallback(() => {
|
const handleSendPm = useLastCallback(() => {
|
||||||
openChat({ id: botId });
|
openChat({ id: botId });
|
||||||
startBot({ botId: botId!, param: switchPm!.startParam });
|
startBot({ botId: botId!, param: switchPm!.startParam });
|
||||||
}, [botId, openChat, startBot, switchPm]);
|
});
|
||||||
|
|
||||||
const handleOpenWebview = useCallback(() => {
|
const handleOpenWebview = useLastCallback(() => {
|
||||||
const theme = extractCurrentThemeParams();
|
const theme = extractCurrentThemeParams();
|
||||||
|
|
||||||
requestSimpleWebView({
|
requestSimpleWebView({
|
||||||
@ -115,7 +114,7 @@ const InlineBotTooltip: FC<OwnProps> = ({
|
|||||||
buttonText: switchWebview!.text,
|
buttonText: switchWebview!.text,
|
||||||
theme,
|
theme,
|
||||||
});
|
});
|
||||||
}, [botId, switchWebview]);
|
});
|
||||||
|
|
||||||
const prevInlineBotResults = usePrevious(
|
const prevInlineBotResults = usePrevious(
|
||||||
inlineBotResults?.length
|
inlineBotResults?.length
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, { useEffect, useRef, memo } from '../../../lib/teact/teact';
|
||||||
useCallback, useEffect, useRef, memo,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getGlobal } from '../../../global';
|
import { getGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiUser } from '../../../api/types';
|
import type { ApiUser } from '../../../api/types';
|
||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
||||||
@ -34,7 +34,7 @@ const MentionTooltip: FC<OwnProps> = ({
|
|||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
||||||
|
|
||||||
const handleUserSelect = useCallback((userId: string, forceFocus = false) => {
|
const handleUserSelect = useLastCallback((userId: string, forceFocus = false) => {
|
||||||
// No need for expensive global updates on users, so we avoid them
|
// No need for expensive global updates on users, so we avoid them
|
||||||
const usersById = getGlobal().users.byId;
|
const usersById = getGlobal().users.byId;
|
||||||
const user = usersById[userId];
|
const user = usersById[userId];
|
||||||
@ -43,17 +43,17 @@ const MentionTooltip: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
onInsertUserName(user, forceFocus);
|
onInsertUserName(user, forceFocus);
|
||||||
}, [onInsertUserName]);
|
});
|
||||||
|
|
||||||
const handleClick = useCallback((e: React.MouseEvent, id: string) => {
|
const handleClick = useLastCallback((e: React.MouseEvent, id: string) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
handleUserSelect(id);
|
handleUserSelect(id);
|
||||||
}, [handleUserSelect]);
|
});
|
||||||
|
|
||||||
const handleSelectMention = useCallback((member: ApiUser) => {
|
const handleSelectMention = useLastCallback((member: ApiUser) => {
|
||||||
handleUserSelect(member.id, true);
|
handleUserSelect(member.id, true);
|
||||||
}, [handleUserSelect]);
|
});
|
||||||
|
|
||||||
const selectedMentionIndex = useKeyboardNavigation({
|
const selectedMentionIndex = useKeyboardNavigation({
|
||||||
isActive: isOpen,
|
isActive: isOpen,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { RefObject, ChangeEvent } from 'react';
|
import type { RefObject, ChangeEvent } from 'react';
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useEffect, useRef, memo, useState, useCallback, useLayoutEffect,
|
useEffect, useRef, memo, useState, useLayoutEffect,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { requestMutation, requestForcedReflow } from '../../../lib/fasterdom/fasterdom';
|
import { requestMutation, requestForcedReflow } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
@ -23,6 +23,7 @@ import parseEmojiOnlyString from '../../../util/parseEmojiOnlyString';
|
|||||||
import { isSelectionInsideInput } from './helpers/selection';
|
import { isSelectionInsideInput } from './helpers/selection';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import { isHeavyAnimating } from '../../../hooks/useHeavyAnimationCheck';
|
import { isHeavyAnimating } from '../../../hooks/useHeavyAnimationCheck';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -167,7 +168,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const maxInputHeight = isAttachmentModalInput ? MAX_ATTACHMENT_MODAL_INPUT_HEIGHT : (isMobile ? 256 : 416);
|
const maxInputHeight = isAttachmentModalInput ? MAX_ATTACHMENT_MODAL_INPUT_HEIGHT : (isMobile ? 256 : 416);
|
||||||
const updateInputHeight = useCallback((willSend = false) => {
|
const updateInputHeight = useLastCallback((willSend = false) => {
|
||||||
requestForcedReflow(() => {
|
requestForcedReflow(() => {
|
||||||
const scroller = inputRef.current!.closest<HTMLDivElement>(`.${SCROLLER_CLASS}`)!;
|
const scroller = inputRef.current!.closest<HTMLDivElement>(`.${SCROLLER_CLASS}`)!;
|
||||||
const currentHeight = Number(scroller.style.height.replace('px', ''));
|
const currentHeight = Number(scroller.style.height.replace('px', ''));
|
||||||
@ -198,7 +199,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
return exec;
|
return exec;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [maxInputHeight]);
|
});
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!isAttachmentModalInput) return;
|
if (!isAttachmentModalInput) return;
|
||||||
@ -226,7 +227,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const chatIdRef = useRef(chatId);
|
const chatIdRef = useRef(chatId);
|
||||||
chatIdRef.current = chatId;
|
chatIdRef.current = chatId;
|
||||||
const focusInput = useCallback(() => {
|
const focusInput = useLastCallback(() => {
|
||||||
if (!inputRef.current) {
|
if (!inputRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -237,12 +238,12 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
focusEditableElement(inputRef.current!);
|
focusEditableElement(inputRef.current!);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleCloseTextFormatter = useCallback(() => {
|
const handleCloseTextFormatter = useLastCallback(() => {
|
||||||
closeTextFormatter();
|
closeTextFormatter();
|
||||||
clearSelection();
|
clearSelection();
|
||||||
}, [closeTextFormatter]);
|
});
|
||||||
|
|
||||||
function checkSelection() {
|
function checkSelection() {
|
||||||
// Disable the formatter on iOS devices for now.
|
// Disable the formatter on iOS devices for now.
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { ChangeEvent, RefObject } from 'react';
|
import type { ChangeEvent, RefObject } from 'react';
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
|
memo, useEffect, useLayoutEffect, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
|
|
||||||
@ -9,6 +9,8 @@ import type { ApiNewPoll } from '../../../api/types';
|
|||||||
|
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
import parseMessageInput from '../../../util/parseMessageInput';
|
import parseMessageInput from '../../../util/parseMessageInput';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
@ -54,11 +56,11 @@ const PollModal: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const focusInput = useCallback((ref: RefObject<HTMLInputElement>) => {
|
const focusInput = useLastCallback((ref: RefObject<HTMLInputElement>) => {
|
||||||
if (isOpen && ref.current) {
|
if (isOpen && ref.current) {
|
||||||
ref.current.focus();
|
ref.current.focus();
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
});
|
||||||
|
|
||||||
useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]);
|
useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -84,7 +86,7 @@ const PollModal: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [solution]);
|
}, [solution]);
|
||||||
|
|
||||||
const addNewOption = useCallback((newOptions: string[] = []) => {
|
const addNewOption = useLastCallback((newOptions: string[] = []) => {
|
||||||
setOptions([...newOptions, '']);
|
setOptions([...newOptions, '']);
|
||||||
|
|
||||||
requestNextMutation(() => {
|
requestNextMutation(() => {
|
||||||
@ -96,9 +98,9 @@ const PollModal: FC<OwnProps> = ({
|
|||||||
list.classList.toggle('overflown', list.scrollHeight > MAX_LIST_HEIGHT);
|
list.classList.toggle('overflown', list.scrollHeight > MAX_LIST_HEIGHT);
|
||||||
list.scrollTo({ top: list.scrollHeight, behavior: 'smooth' });
|
list.scrollTo({ top: list.scrollHeight, behavior: 'smooth' });
|
||||||
});
|
});
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleCreate = useCallback(() => {
|
const handleCreate = useLastCallback(() => {
|
||||||
setHasErrors(false);
|
setHasErrors(false);
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
@ -155,20 +157,9 @@ const PollModal: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSend(payload);
|
onSend(payload);
|
||||||
}, [
|
});
|
||||||
isOpen,
|
|
||||||
question,
|
|
||||||
options,
|
|
||||||
isQuizMode,
|
|
||||||
correctOption,
|
|
||||||
isAnonymous,
|
|
||||||
isMultipleAnswers,
|
|
||||||
onSend,
|
|
||||||
addNewOption,
|
|
||||||
solution,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const updateOption = useCallback((index: number, text: string) => {
|
const updateOption = useLastCallback((index: number, text: string) => {
|
||||||
const newOptions = [...options];
|
const newOptions = [...options];
|
||||||
newOptions[index] = text;
|
newOptions[index] = text;
|
||||||
if (newOptions[newOptions.length - 1].trim().length && newOptions.length < MAX_OPTIONS_COUNT) {
|
if (newOptions[newOptions.length - 1].trim().length && newOptions.length < MAX_OPTIONS_COUNT) {
|
||||||
@ -176,9 +167,9 @@ const PollModal: FC<OwnProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
setOptions(newOptions);
|
setOptions(newOptions);
|
||||||
}
|
}
|
||||||
}, [options, addNewOption]);
|
});
|
||||||
|
|
||||||
const removeOption = useCallback((index: number) => {
|
const removeOption = useLastCallback((index: number) => {
|
||||||
const newOptions = [...options];
|
const newOptions = [...options];
|
||||||
newOptions.splice(index, 1);
|
newOptions.splice(index, 1);
|
||||||
setOptions(newOptions);
|
setOptions(newOptions);
|
||||||
@ -198,49 +189,49 @@ const PollModal: FC<OwnProps> = ({
|
|||||||
|
|
||||||
optionsListRef.current.classList.toggle('overflown', optionsListRef.current.scrollHeight > MAX_LIST_HEIGHT);
|
optionsListRef.current.classList.toggle('overflown', optionsListRef.current.scrollHeight > MAX_LIST_HEIGHT);
|
||||||
});
|
});
|
||||||
}, [correctOption, options]);
|
});
|
||||||
|
|
||||||
const handleCorrectOptionChange = useCallback((newValue: string) => {
|
const handleCorrectOptionChange = useLastCallback((newValue: string) => {
|
||||||
setCorrectOption(Number(newValue));
|
setCorrectOption(Number(newValue));
|
||||||
}, [setCorrectOption]);
|
});
|
||||||
|
|
||||||
const handleIsAnonymousChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
const handleIsAnonymousChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setIsAnonymous(e.target.checked);
|
setIsAnonymous(e.target.checked);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleMultipleAnswersChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
const handleMultipleAnswersChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setIsMultipleAnswers(e.target.checked);
|
setIsMultipleAnswers(e.target.checked);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleQuizModeChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
const handleQuizModeChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setIsQuizMode(e.target.checked);
|
setIsQuizMode(e.target.checked);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const handleKeyPress = useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
|
const handleKeyPress = useLastCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
handleCreate();
|
handleCreate();
|
||||||
}
|
}
|
||||||
}, [handleCreate]);
|
});
|
||||||
|
|
||||||
const handleQuestionChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
const handleQuestionChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setQuestion(e.target.value);
|
setQuestion(e.target.value);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const getQuestionError = useCallback(() => {
|
const getQuestionError = useLastCallback(() => {
|
||||||
if (hasErrors && !question.trim().length) {
|
if (hasErrors && !question.trim().length) {
|
||||||
return lang('lng_polls_choose_question');
|
return lang('lng_polls_choose_question');
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [hasErrors, lang, question]);
|
});
|
||||||
|
|
||||||
const getOptionsError = useCallback((index: number) => {
|
const getOptionsError = useLastCallback((index: number) => {
|
||||||
const optionsTrimmed = options.map((o) => o.trim()).filter((o) => o.length);
|
const optionsTrimmed = options.map((o) => o.trim()).filter((o) => o.length);
|
||||||
if (hasErrors && optionsTrimmed.length < 2 && !options[index].trim().length) {
|
if (hasErrors && optionsTrimmed.length < 2 && !options[index].trim().length) {
|
||||||
return lang('lng_polls_choose_answers');
|
return lang('lng_polls_choose_answers');
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [hasErrors, lang, options]);
|
});
|
||||||
|
|
||||||
function renderHeader() {
|
function renderHeader() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import React, {
|
import React, { useEffect, useRef, memo } from '../../../lib/teact/teact';
|
||||||
useCallback, useEffect, useRef, memo,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getActions, getGlobal } from '../../../global';
|
import { getActions, getGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
@ -9,8 +7,10 @@ import type { ApiSendAsPeerId } from '../../../api/types';
|
|||||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
||||||
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
|
||||||
import { isUserId } from '../../../global/helpers';
|
import { isUserId } from '../../../global/helpers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
||||||
import useMouseInside from '../../../hooks/useMouseInside';
|
import useMouseInside from '../../../hooks/useMouseInside';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
@ -56,10 +56,10 @@ const SendAsMenu: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isOpen, markMouseInside]);
|
}, [isOpen, markMouseInside]);
|
||||||
|
|
||||||
const handleUserSelect = useCallback((id: string) => {
|
const handleUserSelect = useLastCallback((id: string) => {
|
||||||
onClose();
|
onClose();
|
||||||
saveDefaultSendAs({ chatId: chatId!, sendAsId: id });
|
saveDefaultSendAs({ chatId: chatId!, sendAsId: id });
|
||||||
}, [chatId, onClose, saveDefaultSendAs]);
|
});
|
||||||
|
|
||||||
const selectedSendAsIndex = useKeyboardNavigation({
|
const selectedSendAsIndex = useKeyboardNavigation({
|
||||||
isActive: isOpen,
|
isActive: isOpen,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
useEffect, memo, useRef, useMemo, useCallback,
|
useEffect, memo, useRef, useMemo,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ import {
|
|||||||
selectChat, selectChatFullInfo, selectIsChatWithSelf, selectIsCurrentUserPremium, selectShouldLoopStickers,
|
selectChat, selectChatFullInfo, selectIsChatWithSelf, selectIsCurrentUserPremium, selectShouldLoopStickers,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||||
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -229,27 +230,27 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
|||||||
animateHorizontalScroll(header, newLeft);
|
animateHorizontalScroll(header, newLeft);
|
||||||
}, [areAddedLoaded, activeSetIndex]);
|
}, [areAddedLoaded, activeSetIndex]);
|
||||||
|
|
||||||
const handleStickerSelect = useCallback((sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => {
|
const handleStickerSelect = useLastCallback((sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => {
|
||||||
onStickerSelect(sticker, isSilent, shouldSchedule, true);
|
onStickerSelect(sticker, isSilent, shouldSchedule, true);
|
||||||
addRecentSticker({ sticker });
|
addRecentSticker({ sticker });
|
||||||
}, [addRecentSticker, onStickerSelect]);
|
});
|
||||||
|
|
||||||
const handleStickerUnfave = useCallback((sticker: ApiSticker) => {
|
const handleStickerUnfave = useLastCallback((sticker: ApiSticker) => {
|
||||||
unfaveSticker({ sticker });
|
unfaveSticker({ sticker });
|
||||||
}, [unfaveSticker]);
|
});
|
||||||
|
|
||||||
const handleStickerFave = useCallback((sticker: ApiSticker) => {
|
const handleStickerFave = useLastCallback((sticker: ApiSticker) => {
|
||||||
faveSticker({ sticker });
|
faveSticker({ sticker });
|
||||||
}, [faveSticker]);
|
});
|
||||||
|
|
||||||
const handleMouseMove = useCallback(() => {
|
const handleMouseMove = useLastCallback(() => {
|
||||||
if (!canSendStickers) return;
|
if (!canSendStickers) return;
|
||||||
sendMessageAction({ type: 'chooseSticker' });
|
sendMessageAction({ type: 'chooseSticker' });
|
||||||
}, [canSendStickers, sendMessageAction]);
|
});
|
||||||
|
|
||||||
const handleRemoveRecentSticker = useCallback((sticker: ApiSticker) => {
|
const handleRemoveRecentSticker = useLastCallback((sticker: ApiSticker) => {
|
||||||
removeRecentSticker({ sticker });
|
removeRecentSticker({ sticker });
|
||||||
}, [removeRecentSticker]);
|
});
|
||||||
|
|
||||||
function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) {
|
function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) {
|
||||||
const firstSticker = stickerSet.stickers?.[0];
|
const firstSticker = stickerSet.stickers?.[0];
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
|
memo, useEffect, useLayoutEffect, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
@ -12,6 +12,7 @@ import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
|||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { selectTabState, selectIsCurrentUserPremium, selectIsContextMenuTranslucent } from '../../../global/selectors';
|
import { selectTabState, selectIsCurrentUserPremium, selectIsContextMenuTranslucent } from '../../../global/selectors';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useMouseInside from '../../../hooks/useMouseInside';
|
import useMouseInside from '../../../hooks/useMouseInside';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -164,11 +165,11 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
setRecentEmojis([]);
|
setRecentEmojis([]);
|
||||||
}, [isOpen, addRecentEmoji]);
|
}, [isOpen, addRecentEmoji]);
|
||||||
|
|
||||||
const handleEmojiSelect = useCallback((emoji: string, name: string) => {
|
const handleEmojiSelect = useLastCallback((emoji: string, name: string) => {
|
||||||
setRecentEmojis((emojis) => [...emojis, name]);
|
setRecentEmojis((emojis) => [...emojis, name]);
|
||||||
|
|
||||||
onEmojiSelect(emoji);
|
onEmojiSelect(emoji);
|
||||||
}, [onEmojiSelect]);
|
});
|
||||||
|
|
||||||
const recentCustomEmojisRef = useRef(recentCustomEmojis);
|
const recentCustomEmojisRef = useRef(recentCustomEmojis);
|
||||||
recentCustomEmojisRef.current = recentCustomEmojis;
|
recentCustomEmojisRef.current = recentCustomEmojis;
|
||||||
@ -186,22 +187,22 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
setRecentEmojis([]);
|
setRecentEmojis([]);
|
||||||
}, [isOpen, addRecentCustomEmoji]);
|
}, [isOpen, addRecentCustomEmoji]);
|
||||||
|
|
||||||
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => {
|
const handleCustomEmojiSelect = useLastCallback((emoji: ApiSticker) => {
|
||||||
setRecentCustomEmojis((ids) => [...ids, emoji.id]);
|
setRecentCustomEmojis((ids) => [...ids, emoji.id]);
|
||||||
|
|
||||||
onCustomEmojiSelect(emoji);
|
onCustomEmojiSelect(emoji);
|
||||||
}, [onCustomEmojiSelect]);
|
});
|
||||||
|
|
||||||
const handleSearch = useCallback((type: 'stickers' | 'gifs') => {
|
const handleSearch = useLastCallback((type: 'stickers' | 'gifs') => {
|
||||||
onClose();
|
onClose();
|
||||||
onSearchOpen(type);
|
onSearchOpen(type);
|
||||||
}, [onClose, onSearchOpen]);
|
});
|
||||||
|
|
||||||
const handleStickerSelect = useCallback((
|
const handleStickerSelect = useLastCallback((
|
||||||
sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, canUpdateStickerSetsOrder?: boolean,
|
sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, canUpdateStickerSetsOrder?: boolean,
|
||||||
) => {
|
) => {
|
||||||
onStickerSelect?.(sticker, isSilent, shouldSchedule, true, canUpdateStickerSetsOrder);
|
onStickerSelect?.(sticker, isSilent, shouldSchedule, true, canUpdateStickerSetsOrder);
|
||||||
}, [onStickerSelect]);
|
});
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import React, {
|
import React, { memo, useRef, useState } from '../../../lib/teact/teact';
|
||||||
memo, useCallback, useRef, useState,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
@ -9,6 +7,8 @@ import type { ApiVideo, ApiSticker } from '../../../api/types';
|
|||||||
|
|
||||||
import { EDITABLE_INPUT_CSS_SELECTOR, EDITABLE_INPUT_MODAL_CSS_SELECTOR } from '../../../config';
|
import { EDITABLE_INPUT_CSS_SELECTOR, EDITABLE_INPUT_MODAL_CSS_SELECTOR } from '../../../config';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useMenuPosition from '../../../hooks/useMenuPosition';
|
import useMenuPosition from '../../../hooks/useMenuPosition';
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ type OwnProps = {
|
|||||||
isSilent?: boolean,
|
isSilent?: boolean,
|
||||||
shouldSchedule?: boolean,
|
shouldSchedule?: boolean,
|
||||||
shouldPreserveInput?: boolean,
|
shouldPreserveInput?: boolean,
|
||||||
canUpdateStickerSetsOrder?: boolean
|
canUpdateStickerSetsOrder?: boolean,
|
||||||
) => void;
|
) => void;
|
||||||
onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||||
onRemoveSymbol: VoidFunction;
|
onRemoveSymbol: VoidFunction;
|
||||||
@ -91,7 +91,7 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
|||||||
: (isSymbolMenuOpen && 'is-loading'),
|
: (isSymbolMenuOpen && 'is-loading'),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleActivateSymbolMenu = useCallback(() => {
|
const handleActivateSymbolMenu = useLastCallback(() => {
|
||||||
closeBotCommandMenu?.();
|
closeBotCommandMenu?.();
|
||||||
closeSendAsMenu?.();
|
closeSendAsMenu?.();
|
||||||
openSymbolMenu();
|
openSymbolMenu();
|
||||||
@ -99,9 +99,9 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
|||||||
if (!triggerEl) return;
|
if (!triggerEl) return;
|
||||||
const { x, y } = triggerEl.getBoundingClientRect();
|
const { x, y } = triggerEl.getBoundingClientRect();
|
||||||
setContextMenuPosition({ x, y });
|
setContextMenuPosition({ x, y });
|
||||||
}, [closeBotCommandMenu, closeSendAsMenu, openSymbolMenu]);
|
});
|
||||||
|
|
||||||
const handleSearchOpen = useCallback((type: 'stickers' | 'gifs') => {
|
const handleSearchOpen = useLastCallback((type: 'stickers' | 'gifs') => {
|
||||||
if (type === 'stickers') {
|
if (type === 'stickers') {
|
||||||
setStickerSearchQuery({ query: '' });
|
setStickerSearchQuery({ query: '' });
|
||||||
setGifSearchQuery({ query: undefined });
|
setGifSearchQuery({ query: undefined });
|
||||||
@ -109,9 +109,9 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
|||||||
setGifSearchQuery({ query: '' });
|
setGifSearchQuery({ query: '' });
|
||||||
setStickerSearchQuery({ query: undefined });
|
setStickerSearchQuery({ query: undefined });
|
||||||
}
|
}
|
||||||
}, [setStickerSearchQuery, setGifSearchQuery]);
|
});
|
||||||
|
|
||||||
const handleSymbolMenuOpen = useCallback(() => {
|
const handleSymbolMenuOpen = useLastCallback(() => {
|
||||||
const messageInput = document.querySelector<HTMLDivElement>(
|
const messageInput = document.querySelector<HTMLDivElement>(
|
||||||
isAttachmentModal ? EDITABLE_INPUT_MODAL_CSS_SELECTOR : EDITABLE_INPUT_CSS_SELECTOR,
|
isAttachmentModal ? EDITABLE_INPUT_MODAL_CSS_SELECTOR : EDITABLE_INPUT_CSS_SELECTOR,
|
||||||
);
|
);
|
||||||
@ -126,23 +126,12 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
|||||||
closeBotCommandMenu?.();
|
closeBotCommandMenu?.();
|
||||||
openSymbolMenu();
|
openSymbolMenu();
|
||||||
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
||||||
}, [isAttachmentModal, isMobile, openSymbolMenu, closeBotCommandMenu]);
|
});
|
||||||
|
|
||||||
const getTriggerElement = useCallback(() => triggerRef.current, []);
|
const getTriggerElement = useLastCallback(() => triggerRef.current);
|
||||||
|
const getRootElement = useLastCallback(() => triggerRef.current?.closest('.custom-scroll, .no-scrollbar'));
|
||||||
const getRootElement = useCallback(
|
const getMenuElement = useLastCallback(() => document.querySelector('#portals .SymbolMenu .bubble'));
|
||||||
() => triggerRef.current?.closest('.custom-scroll, .no-scrollbar'),
|
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const getMenuElement = useCallback(
|
|
||||||
() => document.querySelector('#portals .SymbolMenu .bubble'),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const getLayout = useCallback(() => ({
|
|
||||||
withPortal: true,
|
|
||||||
}), []);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, { memo, useCallback } from '../../../lib/teact/teact';
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
@ -58,9 +60,9 @@ const SymbolMenuFooter: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearchOpen = useCallback(() => {
|
const handleSearchOpen = useLastCallback(() => {
|
||||||
onSearchOpen(activeTab === SymbolMenuTabs.Stickers ? 'stickers' : 'gifs');
|
onSearchOpen(activeTab === SymbolMenuTabs.Stickers ? 'stickers' : 'gifs');
|
||||||
}, [activeTab, onSearchOpen]);
|
});
|
||||||
|
|
||||||
function stopPropagation(event: any) {
|
function stopPropagation(event: any) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useRef, useState,
|
memo, useEffect, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { IAnchorPosition } from '../../../types';
|
import type { IAnchorPosition } from '../../../types';
|
||||||
@ -14,6 +14,7 @@ import getKeyFromEvent from '../../../util/getKeyFromEvent';
|
|||||||
import { INPUT_CUSTOM_EMOJI_SELECTOR } from './helpers/customEmoji';
|
import { INPUT_CUSTOM_EMOJI_SELECTOR } from './helpers/customEmoji';
|
||||||
import stopEvent from '../../../util/stopEvent';
|
import stopEvent from '../../../util/stopEvent';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useVirtualBackdrop from '../../../hooks/useVirtualBackdrop';
|
import useVirtualBackdrop from '../../../hooks/useVirtualBackdrop';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
@ -114,7 +115,7 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
setSelectedTextFormats(selectedFormats);
|
setSelectedTextFormats(selectedFormats);
|
||||||
}, [isOpen, selectedRange, openLinkControl]);
|
}, [isOpen, selectedRange, openLinkControl]);
|
||||||
|
|
||||||
const restoreSelection = useCallback(() => {
|
const restoreSelection = useLastCallback(() => {
|
||||||
if (!selectedRange) {
|
if (!selectedRange) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -124,16 +125,16 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
selection.addRange(selectedRange);
|
selection.addRange(selectedRange);
|
||||||
}
|
}
|
||||||
}, [selectedRange]);
|
});
|
||||||
|
|
||||||
const updateSelectedRange = useCallback(() => {
|
const updateSelectedRange = useLastCallback(() => {
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
if (selection) {
|
if (selection) {
|
||||||
setSelectedRange(selection.getRangeAt(0));
|
setSelectedRange(selection.getRangeAt(0));
|
||||||
}
|
}
|
||||||
}, [setSelectedRange]);
|
});
|
||||||
|
|
||||||
const getSelectedText = useCallback((shouldDropCustomEmoji?: boolean) => {
|
const getSelectedText = useLastCallback((shouldDropCustomEmoji?: boolean) => {
|
||||||
if (!selectedRange) {
|
if (!selectedRange) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -144,15 +145,15 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return fragmentEl.innerHTML;
|
return fragmentEl.innerHTML;
|
||||||
}, [selectedRange]);
|
});
|
||||||
|
|
||||||
const getSelectedElement = useCallback(() => {
|
const getSelectedElement = useLastCallback(() => {
|
||||||
if (!selectedRange) {
|
if (!selectedRange) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedRange.commonAncestorContainer.parentElement;
|
return selectedRange.commonAncestorContainer.parentElement;
|
||||||
}, [selectedRange]);
|
});
|
||||||
|
|
||||||
function updateInputStyles() {
|
function updateInputStyles() {
|
||||||
const input = linkUrlInputRef.current;
|
const input = linkUrlInputRef.current;
|
||||||
@ -200,7 +201,7 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSpoilerText = useCallback(() => {
|
const handleSpoilerText = useLastCallback(() => {
|
||||||
if (selectedTextFormats.spoiler) {
|
if (selectedTextFormats.spoiler) {
|
||||||
const element = getSelectedElement();
|
const element = getSelectedElement();
|
||||||
if (
|
if (
|
||||||
@ -226,9 +227,9 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
'insertHTML', false, `<span class="spoiler" data-entity-type="${ApiMessageEntityTypes.Spoiler}">${text}</span>`,
|
'insertHTML', false, `<span class="spoiler" data-entity-type="${ApiMessageEntityTypes.Spoiler}">${text}</span>`,
|
||||||
);
|
);
|
||||||
onClose();
|
onClose();
|
||||||
}, [getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.spoiler]);
|
});
|
||||||
|
|
||||||
const handleBoldText = useCallback(() => {
|
const handleBoldText = useLastCallback(() => {
|
||||||
setSelectedTextFormats((selectedFormats) => {
|
setSelectedTextFormats((selectedFormats) => {
|
||||||
// Somehow re-applying 'bold' command to already bold text doesn't work
|
// Somehow re-applying 'bold' command to already bold text doesn't work
|
||||||
document.execCommand(selectedFormats.bold ? 'removeFormat' : 'bold');
|
document.execCommand(selectedFormats.bold ? 'removeFormat' : 'bold');
|
||||||
@ -244,27 +245,27 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
bold: !selectedFormats.bold,
|
bold: !selectedFormats.bold,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [updateSelectedRange]);
|
});
|
||||||
|
|
||||||
const handleItalicText = useCallback(() => {
|
const handleItalicText = useLastCallback(() => {
|
||||||
document.execCommand('italic');
|
document.execCommand('italic');
|
||||||
updateSelectedRange();
|
updateSelectedRange();
|
||||||
setSelectedTextFormats((selectedFormats) => ({
|
setSelectedTextFormats((selectedFormats) => ({
|
||||||
...selectedFormats,
|
...selectedFormats,
|
||||||
italic: !selectedFormats.italic,
|
italic: !selectedFormats.italic,
|
||||||
}));
|
}));
|
||||||
}, [updateSelectedRange]);
|
});
|
||||||
|
|
||||||
const handleUnderlineText = useCallback(() => {
|
const handleUnderlineText = useLastCallback(() => {
|
||||||
document.execCommand('underline');
|
document.execCommand('underline');
|
||||||
updateSelectedRange();
|
updateSelectedRange();
|
||||||
setSelectedTextFormats((selectedFormats) => ({
|
setSelectedTextFormats((selectedFormats) => ({
|
||||||
...selectedFormats,
|
...selectedFormats,
|
||||||
underline: !selectedFormats.underline,
|
underline: !selectedFormats.underline,
|
||||||
}));
|
}));
|
||||||
}, [updateSelectedRange]);
|
});
|
||||||
|
|
||||||
const handleStrikethroughText = useCallback(() => {
|
const handleStrikethroughText = useLastCallback(() => {
|
||||||
if (selectedTextFormats.strikethrough) {
|
if (selectedTextFormats.strikethrough) {
|
||||||
const element = getSelectedElement();
|
const element = getSelectedElement();
|
||||||
if (
|
if (
|
||||||
@ -288,11 +289,9 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
const text = getSelectedText();
|
const text = getSelectedText();
|
||||||
document.execCommand('insertHTML', false, `<del>${text}</del>`);
|
document.execCommand('insertHTML', false, `<del>${text}</del>`);
|
||||||
onClose();
|
onClose();
|
||||||
}, [
|
});
|
||||||
getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.strikethrough,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleMonospaceText = useCallback(() => {
|
const handleMonospaceText = useLastCallback(() => {
|
||||||
if (selectedTextFormats.monospace) {
|
if (selectedTextFormats.monospace) {
|
||||||
const element = getSelectedElement();
|
const element = getSelectedElement();
|
||||||
if (
|
if (
|
||||||
@ -316,11 +315,9 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
const text = getSelectedText(true);
|
const text = getSelectedText(true);
|
||||||
document.execCommand('insertHTML', false, `<code class="text-entity-code" dir="auto">${text}</code>`);
|
document.execCommand('insertHTML', false, `<code class="text-entity-code" dir="auto">${text}</code>`);
|
||||||
onClose();
|
onClose();
|
||||||
}, [
|
});
|
||||||
getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.monospace,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleLinkUrlConfirm = useCallback(() => {
|
const handleLinkUrlConfirm = useLastCallback(() => {
|
||||||
const formattedLinkUrl = (ensureProtocol(linkUrl) || '').split('%').map(encodeURI).join('%');
|
const formattedLinkUrl = (ensureProtocol(linkUrl) || '').split('%').map(encodeURI).join('%');
|
||||||
|
|
||||||
if (isEditingLink) {
|
if (isEditingLink) {
|
||||||
@ -344,9 +341,9 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
`<a href=${formattedLinkUrl} class="text-entity-link" dir="auto">${text}</a>`,
|
`<a href=${formattedLinkUrl} class="text-entity-link" dir="auto">${text}</a>`,
|
||||||
);
|
);
|
||||||
onClose();
|
onClose();
|
||||||
}, [getSelectedElement, getSelectedText, isEditingLink, linkUrl, onClose, restoreSelection]);
|
});
|
||||||
|
|
||||||
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
const handleKeyDown = useLastCallback((e: KeyboardEvent) => {
|
||||||
const HANDLERS_BY_KEY: Record<string, AnyToVoidFunction> = {
|
const HANDLERS_BY_KEY: Record<string, AnyToVoidFunction> = {
|
||||||
k: openLinkControl,
|
k: openLinkControl,
|
||||||
b: handleBoldText,
|
b: handleBoldText,
|
||||||
@ -370,10 +367,7 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handler();
|
handler();
|
||||||
}, [
|
});
|
||||||
openLinkControl, handleBoldText, handleUnderlineText, handleItalicText, handleMonospaceText,
|
|
||||||
handleStrikethroughText, handleSpoilerText,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import React, {
|
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||||
memo, useCallback, useEffect, useRef,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
@ -15,6 +13,8 @@ import { RE_LINK_TEMPLATE } from '../../../config';
|
|||||||
import { selectTabState, selectNoWebPage, selectTheme } from '../../../global/selectors';
|
import { selectTabState, selectNoWebPage, selectTheme } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import parseMessageInput from '../../../util/parseMessageInput';
|
import parseMessageInput from '../../../util/parseMessageInput';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useSyncEffect from '../../../hooks/useSyncEffect';
|
import useSyncEffect from '../../../hooks/useSyncEffect';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
@ -97,9 +97,9 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const renderingWebPage = useCurrentOrPrev(webPagePreview, true);
|
const renderingWebPage = useCurrentOrPrev(webPagePreview, true);
|
||||||
|
|
||||||
const handleClearWebpagePreview = useCallback(() => {
|
const handleClearWebpagePreview = useLastCallback(() => {
|
||||||
toggleMessageWebPage({ chatId, threadId, noWebPage: true });
|
toggleMessageWebPage({ chatId, threadId, noWebPage: true });
|
||||||
}, [chatId, threadId, toggleMessageWebPage]);
|
});
|
||||||
|
|
||||||
if (!shouldRender || !renderingWebPage) {
|
if (!shouldRender || !renderingWebPage) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useState } from '../../../../lib/teact/teact';
|
import { useState } from '../../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../../global';
|
import { getActions } from '../../../../global';
|
||||||
|
|
||||||
import type { ApiAttachment } from '../../../../api/types';
|
import type { ApiAttachment } from '../../../../api/types';
|
||||||
@ -11,6 +11,8 @@ import {
|
|||||||
SUPPORTED_VIDEO_CONTENT_TYPES,
|
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||||
} from '../../../../config';
|
} from '../../../../config';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
export default function useAttachmentModal({
|
export default function useAttachmentModal({
|
||||||
attachments,
|
attachments,
|
||||||
fileSizeLimit,
|
fileSizeLimit,
|
||||||
@ -37,11 +39,11 @@ export default function useAttachmentModal({
|
|||||||
const [shouldForceCompression, setShouldForceCompression] = useState<boolean>(false);
|
const [shouldForceCompression, setShouldForceCompression] = useState<boolean>(false);
|
||||||
const [shouldSuggestCompression, setShouldSuggestCompression] = useState<boolean | undefined>(undefined);
|
const [shouldSuggestCompression, setShouldSuggestCompression] = useState<boolean | undefined>(undefined);
|
||||||
|
|
||||||
const handleClearAttachments = useCallback(() => {
|
const handleClearAttachments = useLastCallback(() => {
|
||||||
setAttachments(MEMO_EMPTY_ARRAY);
|
setAttachments(MEMO_EMPTY_ARRAY);
|
||||||
}, [setAttachments]);
|
});
|
||||||
|
|
||||||
const handleSetAttachments = useCallback(
|
const handleSetAttachments = useLastCallback(
|
||||||
(newValue: ApiAttachment[] | ((current: ApiAttachment[]) => ApiAttachment[])) => {
|
(newValue: ApiAttachment[] | ((current: ApiAttachment[]) => ApiAttachment[])) => {
|
||||||
const newAttachments = typeof newValue === 'function' ? newValue(attachments) : newValue;
|
const newAttachments = typeof newValue === 'function' ? newValue(attachments) : newValue;
|
||||||
if (!newAttachments.length) {
|
if (!newAttachments.length) {
|
||||||
@ -75,25 +77,22 @@ export default function useAttachmentModal({
|
|||||||
setShouldForceAsFile(Boolean(shouldForce && canSendDocuments));
|
setShouldForceAsFile(Boolean(shouldForce && canSendDocuments));
|
||||||
setShouldForceCompression(!canSendDocuments);
|
setShouldForceCompression(!canSendDocuments);
|
||||||
}
|
}
|
||||||
}, [
|
},
|
||||||
attachments, canSendAudios, canSendDocuments, canSendPhotos, canSendVideos, chatId, fileSizeLimit,
|
|
||||||
handleClearAttachments, openLimitReachedModal, setAttachments, showAllowedMessageTypesNotification,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAppendFiles = useCallback(async (files: File[], isSpoiler?: boolean) => {
|
const handleAppendFiles = useLastCallback(async (files: File[], isSpoiler?: boolean) => {
|
||||||
handleSetAttachments([
|
handleSetAttachments([
|
||||||
...attachments,
|
...attachments,
|
||||||
...await Promise.all(files.map((file) => (
|
...await Promise.all(files.map((file) => (
|
||||||
buildAttachment(file.name, file, { shouldSendAsSpoiler: isSpoiler || undefined })
|
buildAttachment(file.name, file, { shouldSendAsSpoiler: isSpoiler || undefined })
|
||||||
))),
|
))),
|
||||||
]);
|
]);
|
||||||
}, [attachments, handleSetAttachments]);
|
});
|
||||||
|
|
||||||
const handleFileSelect = useCallback(async (files: File[], suggestCompression?: boolean) => {
|
const handleFileSelect = useLastCallback(async (files: File[], suggestCompression?: boolean) => {
|
||||||
handleSetAttachments(await Promise.all(files.map((file) => buildAttachment(file.name, file))));
|
handleSetAttachments(await Promise.all(files.map((file) => buildAttachment(file.name, file))));
|
||||||
setShouldSuggestCompression(suggestCompression);
|
setShouldSuggestCompression(suggestCompression);
|
||||||
}, [handleSetAttachments]);
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shouldSuggestCompression,
|
shouldSuggestCompression,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
import { useCallback, useEffect } from '../../../../lib/teact/teact';
|
import { useEffect } from '../../../../lib/teact/teact';
|
||||||
import twemojiRegex from '../../../../lib/twemojiRegex';
|
import twemojiRegex from '../../../../lib/twemojiRegex';
|
||||||
import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ import { getHtmlBeforeSelection } from '../../../../util/selection';
|
|||||||
import focusEditableElement from '../../../../util/focusEditableElement';
|
import focusEditableElement from '../../../../util/focusEditableElement';
|
||||||
import { buildCustomEmojiHtml } from '../helpers/customEmoji';
|
import { buildCustomEmojiHtml } from '../helpers/customEmoji';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
import useDerivedState from '../../../../hooks/useDerivedState';
|
import useDerivedState from '../../../../hooks/useDerivedState';
|
||||||
import useFlag from '../../../../hooks/useFlag';
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
|
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
|
||||||
@ -68,7 +69,7 @@ export default function useCustomEmojiTooltip(
|
|||||||
}
|
}
|
||||||
}, [isEnabled, getLastEmoji, hasCustomEmojis, clearCustomEmojiForEmoji, loadCustomEmojiForEmoji]);
|
}, [isEnabled, getLastEmoji, hasCustomEmojis, clearCustomEmojiForEmoji, loadCustomEmojiForEmoji]);
|
||||||
|
|
||||||
const insertCustomEmoji = useCallback((emoji: ApiSticker) => {
|
const insertCustomEmoji = useLastCallback((emoji: ApiSticker) => {
|
||||||
const lastEmoji = getLastEmoji();
|
const lastEmoji = getLastEmoji();
|
||||||
if (!isEnabled || !lastEmoji) return;
|
if (!isEnabled || !lastEmoji) return;
|
||||||
|
|
||||||
@ -89,7 +90,7 @@ export default function useCustomEmojiTooltip(
|
|||||||
requestNextMutation(() => {
|
requestNextMutation(() => {
|
||||||
focusEditableElement(inputEl, true, true);
|
focusEditableElement(inputEl, true, true);
|
||||||
});
|
});
|
||||||
}, [getLastEmoji, isEnabled, inputRef, setHtml]);
|
});
|
||||||
|
|
||||||
useEffect(unmarkManuallyClosed, [unmarkManuallyClosed, getHtml]);
|
useEffect(unmarkManuallyClosed, [unmarkManuallyClosed, getHtml]);
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect } from '../../../../lib/teact/teact';
|
import { useEffect } from '../../../../lib/teact/teact';
|
||||||
import { requestMeasure, requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
||||||
import { getActions } from '../../../../global';
|
import { getActions } from '../../../../global';
|
||||||
|
|
||||||
@ -12,6 +12,8 @@ import { IS_TOUCH_ENV } from '../../../../util/windowEnvironment';
|
|||||||
import focusEditableElement from '../../../../util/focusEditableElement';
|
import focusEditableElement from '../../../../util/focusEditableElement';
|
||||||
import parseMessageInput from '../../../../util/parseMessageInput';
|
import parseMessageInput from '../../../../util/parseMessageInput';
|
||||||
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
|
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
||||||
import useBeforeUnload from '../../../../hooks/useBeforeUnload';
|
import useBeforeUnload from '../../../../hooks/useBeforeUnload';
|
||||||
import { useStateRef } from '../../../../hooks/useStateRef';
|
import { useStateRef } from '../../../../hooks/useStateRef';
|
||||||
@ -41,7 +43,7 @@ const useDraft = (
|
|||||||
|
|
||||||
const isEditing = Boolean(editedMessage);
|
const isEditing = Boolean(editedMessage);
|
||||||
|
|
||||||
const updateDraft = useCallback((prevState: { chatId?: string; threadId?: number } = {}, shouldForce = false) => {
|
const updateDraft = useLastCallback((prevState: { chatId?: string; threadId?: number } = {}, shouldForce = false) => {
|
||||||
if (isEditing || !lastSyncTime) return;
|
if (isEditing || !lastSyncTime) return;
|
||||||
|
|
||||||
const html = getHtml();
|
const html = getHtml();
|
||||||
@ -60,7 +62,7 @@ const useDraft = (
|
|||||||
shouldForce,
|
shouldForce,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [chatId, threadId, isEditing, lastSyncTime, getHtml, saveDraft, clearDraft]);
|
});
|
||||||
|
|
||||||
const updateDraftRef = useStateRef(updateDraft);
|
const updateDraftRef = useStateRef(updateDraft);
|
||||||
const runDebouncedForSaveDraft = useRunDebounced(DRAFT_DEBOUNCE, true, undefined, [chatId, threadId]);
|
const runDebouncedForSaveDraft = useRunDebounced(DRAFT_DEBOUNCE, true, undefined, [chatId, threadId]);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState } from '../../../../lib/teact/teact';
|
import { useEffect, useState } from '../../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../../global';
|
import { getActions } from '../../../../global';
|
||||||
import { requestMeasure, requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
||||||
|
|
||||||
@ -13,6 +13,8 @@ import parseMessageInput from '../../../../util/parseMessageInput';
|
|||||||
import focusEditableElement from '../../../../util/focusEditableElement';
|
import focusEditableElement from '../../../../util/focusEditableElement';
|
||||||
import { hasMessageMedia } from '../../../../global/helpers';
|
import { hasMessageMedia } from '../../../../global/helpers';
|
||||||
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
|
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
||||||
import useBeforeUnload from '../../../../hooks/useBeforeUnload';
|
import useBeforeUnload from '../../../../hooks/useBeforeUnload';
|
||||||
import { useDebouncedResolver } from '../../../../hooks/useAsyncResolvers';
|
import { useDebouncedResolver } from '../../../../hooks/useAsyncResolvers';
|
||||||
@ -118,7 +120,7 @@ const useEditing = (
|
|||||||
}
|
}
|
||||||
}, [editedMessage, chatId, getHtml, threadId, getShouldResetNoWebPageDebounced]);
|
}, [editedMessage, chatId, getHtml, threadId, getShouldResetNoWebPageDebounced]);
|
||||||
|
|
||||||
const restoreNewDraftAfterEditing = useCallback(() => {
|
const restoreNewDraftAfterEditing = useLastCallback(() => {
|
||||||
if (!draft) return;
|
if (!draft) return;
|
||||||
|
|
||||||
// Run one frame after editing draft reset
|
// Run one frame after editing draft reset
|
||||||
@ -133,14 +135,14 @@ const useEditing = (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [draft, setHtml]);
|
});
|
||||||
|
|
||||||
const handleEditCancel = useCallback(() => {
|
const handleEditCancel = useLastCallback(() => {
|
||||||
resetComposer();
|
resetComposer();
|
||||||
restoreNewDraftAfterEditing();
|
restoreNewDraftAfterEditing();
|
||||||
}, [resetComposer, restoreNewDraftAfterEditing]);
|
});
|
||||||
|
|
||||||
const handleEditComplete = useCallback(() => {
|
const handleEditComplete = useLastCallback(() => {
|
||||||
const { text, entities } = parseMessageInput(getHtml());
|
const { text, entities } = parseMessageInput(getHtml());
|
||||||
|
|
||||||
if (!editedMessage) {
|
if (!editedMessage) {
|
||||||
@ -159,9 +161,9 @@ const useEditing = (
|
|||||||
|
|
||||||
resetComposer();
|
resetComposer();
|
||||||
restoreNewDraftAfterEditing();
|
restoreNewDraftAfterEditing();
|
||||||
}, [editMessage, editedMessage, getHtml, openDeleteModal, resetComposer, restoreNewDraftAfterEditing]);
|
});
|
||||||
|
|
||||||
const handleBlur = useCallback(() => {
|
const handleBlur = useLastCallback(() => {
|
||||||
if (!editedMessage) return;
|
if (!editedMessage) return;
|
||||||
const edited = parseMessageInput(getHtml());
|
const edited = parseMessageInput(getHtml());
|
||||||
const update = edited.text.length ? edited : undefined;
|
const update = edited.text.length ? edited : undefined;
|
||||||
@ -169,7 +171,7 @@ const useEditing = (
|
|||||||
setEditingDraft({
|
setEditingDraft({
|
||||||
chatId, threadId, type, text: update,
|
chatId, threadId, type, text: update,
|
||||||
});
|
});
|
||||||
}, [chatId, editedMessage, getHtml, setEditingDraft, threadId, type]);
|
});
|
||||||
|
|
||||||
useBackgroundMode(handleBlur);
|
useBackgroundMode(handleBlur);
|
||||||
useBeforeUnload(handleBlur);
|
useBeforeUnload(handleBlur);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState } from '../../../../lib/teact/teact';
|
import { useEffect, useState } from '../../../../lib/teact/teact';
|
||||||
import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
||||||
import { getGlobal } from '../../../../global';
|
import { getGlobal } from '../../../../global';
|
||||||
|
|
||||||
@ -19,6 +19,7 @@ import renderText from '../../../common/helpers/renderText';
|
|||||||
import { selectCustomEmojiForEmojis } from '../../../../global/selectors';
|
import { selectCustomEmojiForEmojis } from '../../../../global/selectors';
|
||||||
import { buildCustomEmojiHtml } from '../helpers/customEmoji';
|
import { buildCustomEmojiHtml } from '../helpers/customEmoji';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
import useFlag from '../../../../hooks/useFlag';
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
|
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
|
||||||
import { useThrottledResolver } from '../../../../hooks/useAsyncResolvers';
|
import { useThrottledResolver } from '../../../../hooks/useAsyncResolvers';
|
||||||
@ -94,7 +95,7 @@ export default function useEmojiTooltip(
|
|||||||
detectEmojiCodeThrottled, [detectEmojiCodeThrottled, getHtml], true,
|
detectEmojiCodeThrottled, [detectEmojiCodeThrottled, getHtml], true,
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateFiltered = useCallback((emojis: Emoji[]) => {
|
const updateFiltered = useLastCallback((emojis: Emoji[]) => {
|
||||||
setFilteredEmojis(emojis);
|
setFilteredEmojis(emojis);
|
||||||
|
|
||||||
if (emojis === MEMO_EMPTY_ARRAY) {
|
if (emojis === MEMO_EMPTY_ARRAY) {
|
||||||
@ -108,9 +109,9 @@ export default function useEmojiTooltip(
|
|||||||
'id',
|
'id',
|
||||||
);
|
);
|
||||||
setFilteredCustomEmojis(customEmojis);
|
setFilteredCustomEmojis(customEmojis);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const insertEmoji = useCallback((emoji: string | ApiSticker, isForce = false) => {
|
const insertEmoji = useLastCallback((emoji: string | ApiSticker, isForce = false) => {
|
||||||
const html = getHtml();
|
const html = getHtml();
|
||||||
if (!html) return;
|
if (!html) return;
|
||||||
|
|
||||||
@ -130,7 +131,7 @@ export default function useEmojiTooltip(
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateFiltered(MEMO_EMPTY_ARRAY);
|
updateFiltered(MEMO_EMPTY_ARRAY);
|
||||||
}, [getHtml, setHtml, inputId, updateFiltered]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const emojiCode = getEmojiCode();
|
const emojiCode = getEmojiCode();
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect } from '../../../../lib/teact/teact';
|
import { useEffect } from '../../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { InlineBotSettings } from '../../../../types';
|
import type { InlineBotSettings } from '../../../../types';
|
||||||
import type { Signal } from '../../../../util/signals';
|
import type { Signal } from '../../../../util/signals';
|
||||||
@ -6,6 +6,7 @@ import type { Signal } from '../../../../util/signals';
|
|||||||
import { getActions } from '../../../../global';
|
import { getActions } from '../../../../global';
|
||||||
import memoized from '../../../../util/memoized';
|
import memoized from '../../../../util/memoized';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
import useFlag from '../../../../hooks/useFlag';
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
import useDerivedState from '../../../../hooks/useDerivedState';
|
import useDerivedState from '../../../../hooks/useDerivedState';
|
||||||
import useSyncEffect from '../../../../hooks/useSyncEffect';
|
import useSyncEffect from '../../../../hooks/useSyncEffect';
|
||||||
@ -75,13 +76,13 @@ export default function useInlineBotTooltip(
|
|||||||
}
|
}
|
||||||
}, [isOpen, resetAllInlineBots, username]);
|
}, [isOpen, resetAllInlineBots, username]);
|
||||||
|
|
||||||
const loadMore = useCallback(() => {
|
const loadMore = useLastCallback(() => {
|
||||||
if (!usernameLowered) return;
|
if (!usernameLowered) return;
|
||||||
|
|
||||||
queryInlineBot({
|
queryInlineBot({
|
||||||
chatId, username: usernameLowered, query, offset,
|
chatId, username: usernameLowered, query, offset,
|
||||||
});
|
});
|
||||||
}, [chatId, offset, query, queryInlineBot, usernameLowered]);
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOpen,
|
isOpen,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
useCallback, useEffect, useLayoutEffect, useRef,
|
useEffect, useLayoutEffect, useRef,
|
||||||
} from '../../../../lib/teact/teact';
|
} from '../../../../lib/teact/teact';
|
||||||
import { requestMeasure } from '../../../../lib/fasterdom/fasterdom';
|
import { requestMeasure } from '../../../../lib/fasterdom/fasterdom';
|
||||||
import { ensureRLottie } from '../../../../lib/rlottie/RLottie.async';
|
import { ensureRLottie } from '../../../../lib/rlottie/RLottie.async';
|
||||||
@ -19,12 +19,12 @@ import { REM } from '../../../common/helpers/mediaDimensions';
|
|||||||
import { hexToRgb } from '../../../../util/switchTheme';
|
import { hexToRgb } from '../../../../util/switchTheme';
|
||||||
import useColorFilter from '../../../../hooks/stickers/useColorFilter';
|
import useColorFilter from '../../../../hooks/stickers/useColorFilter';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
import useResizeObserver from '../../../../hooks/useResizeObserver';
|
import useResizeObserver from '../../../../hooks/useResizeObserver';
|
||||||
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
||||||
import useThrottledCallback from '../../../../hooks/useThrottledCallback';
|
import useThrottledCallback from '../../../../hooks/useThrottledCallback';
|
||||||
import useDynamicColorListener from '../../../../hooks/stickers/useDynamicColorListener';
|
import useDynamicColorListener from '../../../../hooks/stickers/useDynamicColorListener';
|
||||||
import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps';
|
||||||
import { useLastCallback } from '../../../../hooks/useLastCallback';
|
|
||||||
|
|
||||||
const SIZE = 1.25 * REM;
|
const SIZE = 1.25 * REM;
|
||||||
const THROTTLE_MS = 300;
|
const THROTTLE_MS = 300;
|
||||||
@ -51,7 +51,7 @@ export default function useInputCustomEmojis(
|
|||||||
const colorFilter = useColorFilter(customColor, true);
|
const colorFilter = useColorFilter(customColor, true);
|
||||||
const playersById = useRef<Map<string, CustomEmojiPlayer>>(new Map());
|
const playersById = useRef<Map<string, CustomEmojiPlayer>>(new Map());
|
||||||
|
|
||||||
const clearPlayers = useCallback((ids: string[]) => {
|
const clearPlayers = useLastCallback((ids: string[]) => {
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
const player = playersById.current.get(id);
|
const player = playersById.current.get(id);
|
||||||
if (player) {
|
if (player) {
|
||||||
@ -59,7 +59,7 @@ export default function useInputCustomEmojis(
|
|||||||
playersById.current.delete(id);
|
playersById.current.delete(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const synchronizeElements = useLastCallback(() => {
|
const synchronizeElements = useLastCallback(() => {
|
||||||
if (!isReady || !inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return;
|
if (!isReady || !inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return;
|
||||||
@ -160,13 +160,13 @@ export default function useInputCustomEmojis(
|
|||||||
);
|
);
|
||||||
useResizeObserver(sharedCanvasRef, throttledSynchronizeElements);
|
useResizeObserver(sharedCanvasRef, throttledSynchronizeElements);
|
||||||
|
|
||||||
const freezeAnimation = useCallback(() => {
|
const freezeAnimation = useLastCallback(() => {
|
||||||
playersById.current.forEach((player) => {
|
playersById.current.forEach((player) => {
|
||||||
player.pause();
|
player.pause();
|
||||||
});
|
});
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const unfreezeAnimation = useCallback(() => {
|
const unfreezeAnimation = useLastCallback(() => {
|
||||||
if (!canPlayAnimatedEmojis) {
|
if (!canPlayAnimatedEmojis) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -174,11 +174,11 @@ export default function useInputCustomEmojis(
|
|||||||
playersById.current?.forEach((player) => {
|
playersById.current?.forEach((player) => {
|
||||||
player.play();
|
player.play();
|
||||||
});
|
});
|
||||||
}, [canPlayAnimatedEmojis]);
|
});
|
||||||
|
|
||||||
const unfreezeAnimationOnRaf = useCallback(() => {
|
const unfreezeAnimationOnRaf = useLastCallback(() => {
|
||||||
requestMeasure(unfreezeAnimation);
|
requestMeasure(unfreezeAnimation);
|
||||||
}, [unfreezeAnimation]);
|
});
|
||||||
|
|
||||||
// Pausing frame may not happen in background,
|
// Pausing frame may not happen in background,
|
||||||
// so we need to make sure it happens right after focusing,
|
// so we need to make sure it happens right after focusing,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user