[Refactoring] Introduce useLastCallback

This commit is contained in:
Alexander Zinchuk 2023-06-12 11:44:58 +02:00
parent df70051763
commit b03959e847
168 changed files with 1623 additions and 1630 deletions

View File

@ -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

View File

@ -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;

View File

@ -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) {

View 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, 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;

View File

@ -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();

View 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, 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;

View File

@ -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 (

View File

@ -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);

View File

@ -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];

View File

@ -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

View 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();

View File

@ -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]);

View File

@ -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

View File

@ -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;

View File

@ -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));

View File

@ -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]);

View File

@ -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;

View File

@ -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(

View File

@ -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();

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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(() => {

View File

@ -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;

View File

@ -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(() => {

View File

@ -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]);

View File

@ -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,

View File

@ -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) {

View File

@ -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,

View File

@ -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) {

View File

@ -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,

View 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, 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(() => {

View File

@ -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]);

View File

@ -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,

View 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 { 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;

View 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, 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)

View File

@ -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,

View File

@ -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();

View File

@ -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> = {

View File

@ -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

View File

@ -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);

View 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, 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();

View File

@ -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();

View File

@ -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) {

View 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, 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);

View File

@ -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;

View File

@ -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();

View 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 { 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;

View File

@ -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);

View 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, 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,

View File

@ -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) {

View File

@ -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';

View File

@ -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;

View File

@ -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}>

View File

@ -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();

View File

@ -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);

View File

@ -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,

View File

@ -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,

View 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, 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();

View File

@ -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();

View File

@ -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' });

View File

@ -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);

View File

@ -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;

View File

@ -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);

View 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, 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

View 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, 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' : ''}>

View File

@ -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[] = [];

View File

@ -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

View File

@ -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) => {

View File

@ -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;

View File

@ -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) {

View File

@ -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

View File

@ -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,

View 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, 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

View File

@ -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,

View File

@ -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',

View File

@ -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,

View File

@ -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) {

View File

@ -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',

View File

@ -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];

View File

@ -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,

View File

@ -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);

View File

@ -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

View File

@ -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,

View File

@ -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.

View File

@ -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 (

View File

@ -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,

View File

@ -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];

View File

@ -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();

View File

@ -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,

View File

@ -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();

View 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 { 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) {

View File

@ -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;

View File

@ -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,

View File

@ -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]);

View File

@ -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]);

View File

@ -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);

View File

@ -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();

View File

@ -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,

View File

@ -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