[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, {
memo, useCallback, useState,
} from '../../lib/teact/teact';
import React, { memo, useState } from '../../lib/teact/teact';
import type { OwnProps as AnimatedStickerProps } from './AnimatedSticker';
import buildClassName from '../../util/buildClassName';
import useLastCallback from '../../hooks/useLastCallback';
import useMediaTransition from '../../hooks/useMediaTransition';
import useFlag from '../../hooks/useFlag';
@ -31,20 +31,20 @@ function AnimatedIcon(props: OwnProps) {
const [isAnimationLoaded, markAnimationLoaded] = useFlag(false);
const transitionClassNames = useMediaTransition(noTransition || isAnimationLoaded);
const handleLoad = useCallback(() => {
const handleLoad = useLastCallback(() => {
markAnimationLoaded();
onLoad?.();
}, [markAnimationLoaded, onLoad]);
});
const [playKey, setPlayKey] = useState(String(Math.random()));
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
if (play === true) {
setPlayKey(String(Math.random()));
}
onClick?.();
}, [onClick, play]);
});
return (
<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 buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle';
import useLastCallback from '../../hooks/useLastCallback';
import useMediaTransition from '../../hooks/useMediaTransition';
import useFlag from '../../hooks/useFlag';
@ -26,10 +28,10 @@ function AnimatedIconWithPreview(props: OwnProps) {
const transitionClassNames = useMediaTransition(isPreviewLoaded);
const [isAnimationReady, markAnimationReady] = useFlag(false);
const handlePreviewLoad = useCallback(() => {
const handlePreviewLoad = useLastCallback(() => {
markPreviewLoaded();
loadedPreviewUrls.add(previewUrl);
}, [markPreviewLoaded, previewUrl]);
});
const { size } = props;

View File

@ -5,7 +5,7 @@ import { requestMeasure } from '../../lib/fasterdom/fasterdom';
import { ensureRLottie, getRLottie } from '../../lib/rlottie/RLottie.async';
import React, {
useEffect, useRef, memo, useCallback, useState, useMemo,
useEffect, useRef, memo, useState, useMemo,
} from '../../lib/teact/teact';
import buildClassName from '../../util/buildClassName';
@ -13,6 +13,7 @@ import buildStyle from '../../util/buildStyle';
import generateIdFor from '../../util/generateIdFor';
import { hexToRgb } from '../../util/switchTheme';
import useLastCallback from '../../hooks/useLastCallback';
import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck';
import usePriorityPlaybackCheck, { isPriorityPlaybackActive } from '../../hooks/usePriorityPlaybackCheck';
import useBackgroundMode, { isBackgroundModeActive } from '../../hooks/useBackgroundMode';
@ -108,7 +109,7 @@ const AnimatedSticker: FC<OwnProps> = ({
};
}, []);
const init = useCallback(() => {
const init = useLastCallback(() => {
if (
animationRef.current
|| isUnmountedRef.current
@ -147,10 +148,7 @@ const AnimatedSticker: FC<OwnProps> = ({
setAnimation(newAnimation);
animationRef.current = newAnimation;
}, [
isLowPriority, noLoop, onEnded, onLoad, onLoop, quality,
renderId, sharedCanvas, sharedCanvasCoords, size, speed, tgsUrl, viewId,
]);
});
useEffect(() => {
if (getRLottie()) {
@ -158,7 +156,7 @@ const AnimatedSticker: FC<OwnProps> = ({
} else {
ensureRLottie().then(init);
}
}, [init]);
}, [init, tgsUrl, sharedCanvas, sharedCanvasCoords]);
const throttledInit = useThrottledCallback(init, [init], THROTTLE_MS);
useSharedIntersectionObserver(sharedCanvas, throttledInit);
@ -175,7 +173,7 @@ const AnimatedSticker: FC<OwnProps> = ({
};
}, [viewId]);
const playAnimation = useCallback((shouldRestart = false) => {
const playAnimation = useLastCallback((shouldRestart = false) => {
if (
!animation
|| !(playRef.current || playSegmentRef.current)
@ -189,17 +187,17 @@ const AnimatedSticker: FC<OwnProps> = ({
} else {
animation.play(shouldRestart, viewId);
}
}, [animation, forceOnHeavyAnimation, playRef, playSegmentRef, viewId]);
});
const playAnimationOnRaf = useCallback(() => {
const playAnimationOnRaf = useLastCallback(() => {
requestMeasure(playAnimation);
}, [playAnimation]);
});
const pauseAnimation = useCallback(() => {
const pauseAnimation = useLastCallback(() => {
if (animation?.isPlaying()) {
animation.pause(viewId);
}
}, [animation, viewId]);
});
useEffectWithPrevDeps(([prevNoLoop]) => {
if (prevNoLoop !== undefined && noLoop !== prevNoLoop) {

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState,
memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
} from '../../lib/teact/teact';
import { getActions } from '../../global';
@ -25,6 +25,8 @@ import buildClassName from '../../util/buildClassName';
import { formatMediaDateTime, formatMediaDuration, formatPastTimeShort } from '../../util/dateFormat';
import { decodeWaveform, interpolateArray } from '../../util/waveform';
import { makeTrackId } from '../../util/audioPlayer';
import useLastCallback from '../../hooks/useLastCallback';
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
import useShowTransition from '../../hooks/useShowTransition';
import type { BufferedRange } from '../../hooks/useBuffering';
@ -128,14 +130,14 @@ const Audio: FC<OwnProps> = ({
getMessageMediaFormat(message, 'download'),
);
const handleForcePlay = useCallback(() => {
const handleForcePlay = useLastCallback(() => {
setIsActivated(true);
onPlay(message.id, message.chatId);
}, [message, onPlay]);
});
const handleTrackChange = useCallback(() => {
const handleTrackChange = useLastCallback(() => {
setIsActivated(false);
}, []);
});
const {
isBuffered, bufferedRanges, bufferingHandlers, checkBuffering,
@ -185,7 +187,7 @@ const Audio: FC<OwnProps> = ({
const shouldRenderCross = shouldRenderSpinner && (isLoadingForPlaying || isUploading);
const handleButtonClick = useCallback(() => {
const handleButtonClick = useLastCallback(() => {
if (isUploading) {
onCancelUpload?.();
return;
@ -198,7 +200,7 @@ const Audio: FC<OwnProps> = ({
getActions().setAudioPlayerOrigin({ origin });
setIsActivated(!isActivated);
playPause();
}, [isUploading, isPlaying, isActivated, playPause, onCancelUpload, onPlay, message.id, message.chatId, origin]);
});
useEffect(() => {
if (onReadMedia && isMediaUnread && (isPlaying || isDownloading)) {
@ -206,15 +208,15 @@ const Audio: FC<OwnProps> = ({
}
}, [isPlaying, isMediaUnread, onReadMedia, isDownloading]);
const handleDownloadClick = useCallback(() => {
const handleDownloadClick = useLastCallback(() => {
if (isDownloading) {
cancelMessageMediaDownload({ message });
} else {
downloadMessageMedia({ message });
}
}, [cancelMessageMediaDownload, downloadMessageMedia, isDownloading, message]);
});
const handleSeek = useCallback((e: MouseEvent | TouchEvent) => {
const handleSeek = useLastCallback((e: MouseEvent | TouchEvent) => {
if (isSeeking.current && seekerRef.current) {
const { width, left } = seekerRef.current.getBoundingClientRect();
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
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;
isSeeking.current = true;
handleSeek(e);
}, [handleSeek]);
});
const handleStopSeek = useCallback(() => {
const handleStopSeek = useLastCallback(() => {
isSeeking.current = false;
}, []);
});
const handleDateClick = useCallback(() => {
const handleDateClick = useLastCallback(() => {
onDateClick!(message.id, message.chatId);
}, [onDateClick, message.id, message.chatId]);
});
const handleTranscribe = useCallback(() => {
const handleTranscribe = useLastCallback(() => {
transcribeAudio({ chatId: message.chatId, messageId: message.id });
}, [message.chatId, message.id, transcribeAudio]);
});
useEffect(() => {
if (!seekerRef.current || !withSeekline) return undefined;

View File

@ -1,6 +1,6 @@
import type { MouseEvent as ReactMouseEvent } from 'react';
import React, {
memo, useCallback, useMemo, useRef,
memo, useMemo, useRef,
} 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 './Avatar.scss';
import useLastCallback from '../../hooks/useLastCallback';
const LOOP_COUNT = 3;
@ -111,7 +112,7 @@ const Avatar: FC<OwnProps> = ({
return onlineTransitionClassNames.split(' ').map((c) => (c === 'shown' ? 'online' : `online-${c}`)).join(' ');
}, [onlineTransitionClassNames]);
const handleVideoEnded = useCallback((e) => {
const handleVideoEnded = useLastCallback((e) => {
const video = e.currentTarget;
if (!videoBlobUrl) return;
@ -121,7 +122,7 @@ const Avatar: FC<OwnProps> = ({
if (videoLoopCountRef.current >= LOOP_COUNT) {
video.style.display = 'none';
}
}, [loopIndefinitely, videoBlobUrl]);
});
const lang = useLang();

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useMemo, useState,
memo, useEffect, useMemo, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
@ -34,6 +34,8 @@ import { copyTextToClipboard } from '../../util/clipboard';
import { formatPhoneNumberWithCode } from '../../util/phoneNumber';
import { debounce } from '../../util/schedulers';
import stopEvent from '../../util/stopEvent';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import ListItem from '../ui/ListItem';
@ -123,7 +125,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
: getChatLink(chat) || chatInviteLink;
}, [chat, isTopicInfo, activeChatUsernames, topicId, chatInviteLink]);
const handleNotificationChange = useCallback(() => {
const handleNotificationChange = useLastCallback(() => {
setAreNotificationsEnabled((current) => {
const newAreNotificationsEnabled = !current;
@ -141,7 +143,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
return newAreNotificationsEnabled;
});
}, [chatId, isTopicInfo, topicId, updateChatMutedState, updateTopicMutedState]);
});
if (!chat || chat.isRestricted || (isSelf && !forceShowSelf)) {
return undefined;

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useRef, useCallback, useState, useMemo,
memo, useRef, useState, useMemo,
} from '../../lib/teact/teact';
import { getActions } from '../../global';
@ -10,6 +10,8 @@ import { REM } from './helpers/mediaDimensions';
import { CHAT_HEIGHT_PX } from '../../config';
import renderText from './helpers/renderText';
import { getCanPostInChat, isUserId } from '../../global/helpers';
import useLastCallback from '../../hooks/useLastCallback';
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
import useLang from '../../hooks/useLang';
import useKeyboardListNavigation from '../../hooks/useKeyboardListNavigation';
@ -76,9 +78,9 @@ const ChatOrUserPicker: FC<OwnProps> = ({
const activeKey = forumId ? TOPIC_LIST_SLIDE : CHAT_LIST_SLIDE;
const viewportOffset = chatOrUserIds!.indexOf(viewportIds![0]);
const resetSearch = useCallback(() => {
const resetSearch = useLastCallback(() => {
onSearchChange('');
}, [onSearchChange]);
});
useInputFocusOnOpen(searchRef, isOpen && activeKey === CHAT_LIST_SLIDE, resetSearch);
useInputFocusOnOpen(topicSearchRef, isOpen && activeKey === TOPIC_LIST_SLIDE);
@ -106,18 +108,18 @@ const ChatOrUserPicker: FC<OwnProps> = ({
return [Object.keys(result).map(Number), result];
}, [chatsById, forumId, topicSearch]);
const handleHeaderBackClick = useCallback(() => {
const handleHeaderBackClick = useLastCallback(() => {
setForumId(undefined);
setTopicSearch('');
}, []);
});
const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const handleSearchChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
onSearchChange(e.currentTarget.value);
}, [onSearchChange]);
});
const handleTopicSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const handleTopicSearchChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setTopicSearch(e.currentTarget.value);
}, []);
});
const handleKeyDown = useKeyboardListNavigation(containerRef, isOpen, (index) => {
if (viewportIds && viewportIds.length > 0) {
@ -138,7 +140,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
}
}, '.ListItem-button', true);
const handleClick = useCallback((e: React.MouseEvent, chatId: string) => {
const handleClick = useLastCallback((e: React.MouseEvent, chatId: string) => {
const chat = chatsById?.[chatId];
if (chat?.isForum) {
if (!chat.topics) loadTopics({ chatId });
@ -147,11 +149,11 @@ const ChatOrUserPicker: FC<OwnProps> = ({
} else {
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);
}, [forumId, onSelectChatOrUser]);
});
function renderTopicList() {
return (

View File

@ -1,6 +1,4 @@
import React, {
memo, useCallback, useRef, useState,
} from '../../lib/teact/teact';
import React, { memo, useRef, useState } from '../../lib/teact/teact';
import { getGlobal } from '../../global';
import type { FC, TeactNode } from '../../lib/teact/teact';
@ -11,6 +9,7 @@ import buildClassName from '../../util/buildClassName';
import safePlay from '../../util/safePlay';
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
import useLastCallback from '../../hooks/useLastCallback';
import useCustomEmoji from './hooks/useCustomEmoji';
import useDynamicColorListener from '../../hooks/stickers/useDynamicColorListener';
@ -79,7 +78,7 @@ const CustomEmoji: FC<OwnProps> = ({
const hasCustomColor = customEmoji?.shouldUseTextColor;
const customColor = useDynamicColorListener(containerRef, !hasCustomColor);
const handleVideoEnded = useCallback((e) => {
const handleVideoEnded = useLastCallback((e) => {
if (!loopLimit) return;
loopCountRef.current += 1;
@ -91,9 +90,9 @@ const CustomEmoji: FC<OwnProps> = ({
// Loop manually
safePlay(e.currentTarget);
}
}, [loopLimit]);
});
const handleStickerLoop = useCallback(() => {
const handleStickerLoop = useLastCallback(() => {
if (!loopLimit) return;
loopCountRef.current += 1;
@ -102,7 +101,7 @@ const CustomEmoji: FC<OwnProps> = ({
if (loopCountRef.current >= loopLimit - 1) {
setShouldLoop(false);
}
}, [loopLimit]);
});
const isHq = customEmoji?.stickerSetInfo && selectIsAlwaysHighPriorityEmoji(getGlobal(), customEmoji.stickerSetInfo);

View File

@ -1,5 +1,5 @@
import React, {
useEffect, memo, useRef, useMemo, useCallback,
useEffect, memo, useRef, useMemo,
} from '../../lib/teact/teact';
import { getGlobal, withGlobal } from '../../global';
@ -34,6 +34,7 @@ import {
selectIsCurrentUserPremium,
} from '../../global/selectors';
import useLastCallback from '../../hooks/useLastCallback';
import useAsyncRendering from '../right/hooks/useAsyncRendering';
import useHorizontalScroll from '../../hooks/useHorizontalScroll';
import useLang from '../../hooks/useLang';
@ -276,13 +277,13 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
animateHorizontalScroll(header, newLeft);
}, [areAddedLoaded, activeSetIndex]);
const handleEmojiSelect = useCallback((emoji: ApiSticker) => {
const handleEmojiSelect = useLastCallback((emoji: ApiSticker) => {
onCustomEmojiSelect(emoji);
}, [onCustomEmojiSelect]);
});
const handleReactionSelect = useCallback((reaction: ApiReaction) => {
const handleReactionSelect = useLastCallback((reaction: ApiReaction) => {
onReactionSelect?.(reaction);
}, [onReactionSelect]);
});
function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) {
const firstSticker = stickerSet.stickers?.[0];

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
useCallback, memo, useRef, useEffect, useState,
memo, useRef, useEffect, useState,
} from '../../lib/teact/teact';
import { getActions } from '../../global';
@ -19,6 +19,8 @@ import {
isMessageDocumentVideo,
} from '../../global/helpers';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import useLastCallback from '../../hooks/useLastCallback';
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
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)
);
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
if (isUploading) {
if (onCancelUpload) {
onCancelUpload();
@ -131,13 +133,11 @@ const Document: FC<OwnProps> = ({
} else {
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]);
});
return (
<File

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useRef, useState,
memo, useEffect, useRef, useState,
} from '../../lib/teact/teact';
import type { ApiVideo } from '../../api/types';
@ -26,6 +26,7 @@ import MenuItem from '../ui/MenuItem';
import OptimizedVideo from '../ui/OptimizedVideo';
import './GifButton.scss';
import useLastCallback from '../../hooks/useLastCallback';
type OwnProps = {
gif: ApiVideo;
@ -69,17 +70,9 @@ const GifButton: FC<OwnProps> = ({
handleContextMenuClose, handleContextMenuHide,
} = useContextMenuHandlers(ref);
const getTriggerElement = useCallback(() => ref.current, []);
const getRootElement = useCallback(
() => ref.current!.closest('.custom-scroll, .no-scrollbar'),
[],
);
const getMenuElement = useCallback(
() => ref.current!.querySelector('.gif-context-menu .bubble'),
[],
);
const getTriggerElement = useLastCallback(() => ref.current);
const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar'));
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.gif-context-menu .bubble'));
const {
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
@ -90,42 +83,42 @@ const GifButton: FC<OwnProps> = ({
getMenuElement,
);
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
if (isContextMenuOpen || !onClick) return;
onClick({
...gif,
blobUrl: videoData,
});
}, [isContextMenuOpen, onClick, gif, videoData]);
});
const handleUnsaveClick = useCallback((e: React.MouseEvent) => {
const handleUnsaveClick = useLastCallback((e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
onUnsaveClick!(gif);
}, [onUnsaveClick, gif]);
});
const handleContextDelete = useCallback(() => {
const handleContextDelete = useLastCallback(() => {
onUnsaveClick?.(gif);
}, [gif, onUnsaveClick]);
});
const handleSendQuiet = useCallback(() => {
const handleSendQuiet = useLastCallback(() => {
onClick!({
...gif,
blobUrl: videoData,
}, true);
}, [gif, onClick, videoData]);
});
const handleSendScheduled = useCallback(() => {
const handleSendScheduled = useLastCallback(() => {
onClick!({
...gif,
blobUrl: videoData,
}, undefined, true);
}, [gif, onClick, videoData]);
});
const handleMouseDown = useCallback((e: React.MouseEvent<HTMLElement>) => {
const handleMouseDown = useLastCallback((e: React.MouseEvent<HTMLElement>) => {
preventMessageInputBlurWithBubbling(e);
handleBeforeContextMenu(e);
}, [handleBeforeContextMenu]);
});
useEffect(() => {
if (isDisabled) handleContextMenuClose();

View File

@ -1,8 +1,6 @@
import type { MouseEvent as ReactMouseEvent } from 'react';
import type { FC } from '../../lib/teact/teact';
import React, {
useEffect, useCallback, memo, useMemo,
} from '../../lib/teact/teact';
import React, { useEffect, memo, useMemo } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type {
@ -34,6 +32,7 @@ import TypingStatus from './TypingStatus';
import DotAnimation from './DotAnimation';
import FullNameTitle from './FullNameTitle';
import TopicIcon from './TopicIcon';
import useLastCallback from '../../hooks/useLastCallback';
const TOPIC_ICON_SIZE = 2.5 * REM;
@ -105,16 +104,18 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
}
}, [chatId, isMin, lastSyncTime, withFullInfo, loadFullChat, loadProfilePhotos, isSuperGroup, withMediaViewer]);
const handleAvatarViewerOpen = useCallback((e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
if (chat && hasMedia) {
e.stopPropagation();
openMediaViewer({
avatarOwnerId: chat.id,
mediaId: 0,
origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar,
});
}
}, [chat, avatarSize, openMediaViewer]);
const handleAvatarViewerOpen = useLastCallback(
(e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
if (chat && hasMedia) {
e.stopPropagation();
openMediaViewer({
avatarOwnerId: chat.id,
mediaId: 0,
origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar,
});
}
},
);
const lang = useLang();
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 { ApiMessage } from '../../api/types';
@ -15,6 +15,7 @@ import {
} from '../../global/helpers';
import buildClassName from '../../util/buildClassName';
import useLastCallback from '../../hooks/useLastCallback';
import useMedia from '../../hooks/useMedia';
import useMediaTransition from '../../hooks/useMediaTransition';
import useFlag from '../../hooks/useFlag';
@ -52,10 +53,10 @@ const Media: FC<OwnProps> = ({
const hasSpoiler = getMessageIsSpoiler(message);
const [isSpoilerShown, , hideSpoiler] = useFlag(hasSpoiler);
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
hideSpoiler();
onClick!(message.id, message.chatId);
}, [hideSpoiler, message, onClick]);
});
return (
<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 buildClassName from '../../util/buildClassName';
import useLastCallback from '../../hooks/useLastCallback';
import useCanvasBlur from '../../hooks/useCanvasBlur';
import useShowTransition from '../../hooks/useShowTransition';
@ -36,7 +38,7 @@ const MediaSpoiler: FC<OwnProps> = ({
);
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;
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
@ -44,7 +46,7 @@ const MediaSpoiler: FC<OwnProps> = ({
const shiftX = x - (rect.width / 2);
const shiftY = y - (rect.height / 2);
ref.current.setAttribute('style', `--click-shift-x: ${shiftX}px; --click-shift-y: ${shiftY}px`);
}, []);
});
if (!shouldRender) {
return undefined;

View File

@ -1,5 +1,5 @@
import React, {
useCallback, useRef, useEffect, memo, useMemo,
useRef, useEffect, memo, useMemo,
} from '../../lib/teact/teact';
import { requestMutation } from '../../lib/fasterdom/fasterdom';
@ -8,6 +8,8 @@ import type { FC } from '../../lib/teact/teact';
import { isUserId } from '../../global/helpers';
import buildClassName from '../../util/buildClassName';
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import useLastCallback from '../../hooks/useLastCallback';
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
import useLang from '../../hooks/useLang';
@ -99,7 +101,7 @@ const Picker: FC<OwnProps> = ({
});
}, [itemIds, lockedIdsSet]);
const handleItemClick = useCallback((id: string) => {
const handleItemClick = useLastCallback((id: string) => {
if (lockedIdsSet.has(id)) {
onDisabledClick?.(id);
return;
@ -113,12 +115,12 @@ const Picker: FC<OwnProps> = ({
}
onSelectedIdsChange?.(newSelectedIds);
onFilterChange?.('');
}, [lockedIdsSet, selectedIds, onSelectedIdsChange, onFilterChange, onDisabledClick]);
});
const handleFilterChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const handleFilterChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.currentTarget;
onFilterChange?.(value);
}, [onFilterChange]);
});
const [viewportIds, getMore] = useInfiniteScroll(onLoadMore, sortedItemIds, Boolean(filterValue));

View File

@ -1,6 +1,4 @@
import React, {
useEffect, useCallback, memo, useMemo,
} from '../../lib/teact/teact';
import React, { useEffect, memo, useMemo } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
@ -19,6 +17,7 @@ import { getMainUsername, getUserStatus, isUserOnline } from '../../global/helpe
import buildClassName from '../../util/buildClassName';
import renderText from './helpers/renderText';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import Avatar from './Avatar';
@ -88,16 +87,18 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
}
}, [userId, loadFullUser, loadProfilePhotos, lastSyncTime, withFullInfo, withMediaViewer]);
const handleAvatarViewerOpen = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
if (user && hasMedia) {
e.stopPropagation();
openMediaViewer({
avatarOwnerId: user.id,
mediaId: 0,
origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar,
});
}
}, [user, avatarSize, openMediaViewer]);
const handleAvatarViewerOpen = useLastCallback(
(e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
if (user && hasMedia) {
e.stopPropagation();
openMediaViewer({
avatarOwnerId: user.id,
mediaId: 0,
origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar,
});
}
},
);
const lang = useLang();
const mainUsername = useMemo(() => user && withUsername && getMainUsername(user), [user, withUsername]);

View File

@ -1,7 +1,5 @@
import type { FC } from '../../lib/teact/teact';
import React, {
useEffect, useCallback, memo, useState,
} from '../../lib/teact/teact';
import React, { useEffect, memo, useState } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type {
@ -29,6 +27,7 @@ import { captureEvents, SwipeDirection } from '../../util/captureEvents';
import buildClassName from '../../util/buildClassName';
import renderText from './helpers/renderText';
import useLastCallback from '../../hooks/useLastCallback';
import usePhotosPreload from './hooks/usePhotosPreload';
import useLang from '../../hooks/useLang';
import usePrevious from '../../hooks/usePrevious';
@ -40,6 +39,7 @@ import TopicIcon from './TopicIcon';
import Avatar from './Avatar';
import './ProfileInfo.scss';
import styles from './ProfileInfo.module.scss';
type OwnProps = {
@ -130,35 +130,35 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
usePhotosPreload(user || chat, photos, currentPhotoIndex);
const handleProfilePhotoClick = useCallback(() => {
const handleProfilePhotoClick = useLastCallback(() => {
openMediaViewer({
avatarOwnerId: userId || chatId,
mediaId: currentPhotoIndex,
origin: forceShowSelf ? MediaViewerOrigin.SettingsAvatar : MediaViewerOrigin.ProfileAvatar,
});
}, [openMediaViewer, userId, chatId, currentPhotoIndex, forceShowSelf]);
});
const handleClickPremium = useCallback(() => {
const handleClickPremium = useLastCallback(() => {
if (!user) return;
openPremiumModal({ fromUserId: user.id });
}, [openPremiumModal, user]);
});
const selectPreviousMedia = useCallback(() => {
const selectPreviousMedia = useLastCallback(() => {
if (isFirst) {
return;
}
setHasSlideAnimation(true);
setCurrentPhotoIndex(currentPhotoIndex - 1);
}, [currentPhotoIndex, isFirst]);
});
const selectNextMedia = useCallback(() => {
const selectNextMedia = useLastCallback(() => {
if (isLast) {
return;
}
setHasSlideAnimation(true);
setCurrentPhotoIndex(currentPhotoIndex + 1);
}, [currentPhotoIndex, isLast]);
});
function handleSelectFallbackPhoto() {
if (!isFirst) return;

View File

@ -1,5 +1,5 @@
import React, {
memo, useCallback, useMemo, useRef,
memo, useMemo, useRef,
} 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 { getDocumentMediaHash, isSameReaction } from '../../global/helpers';
import useLastCallback from '../../hooks/useLastCallback';
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
import useMediaTransition from '../../hooks/useMediaTransition';
import useMedia from '../../hooks/useMedia';
@ -54,9 +55,9 @@ const ReactionEmoji: FC<OwnProps> = ({
availableReaction?.selectAnimation ? getDocumentMediaHash(availableReaction.selectAnimation) : undefined,
!animationId,
);
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
onClick(reaction);
}, [onClick, reaction]);
});
const transitionClassNames = useMediaTransition(mediaData);
const fullClassName = buildClassName(

View File

@ -1,13 +1,12 @@
import type { ChangeEvent } from 'react';
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useMemo, useState,
} from '../../lib/teact/teact';
import React, { memo, useMemo, useState } from '../../lib/teact/teact';
import { getActions } from '../../global';
import type { ApiPhoto, ApiReportReason } from '../../api/types';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import Modal from '../ui/Modal';
@ -44,7 +43,7 @@ const ReportModal: FC<OwnProps> = ({
const [selectedReason, setSelectedReason] = useState<ApiReportReason>('spam');
const [description, setDescription] = useState('');
const handleReport = useCallback(() => {
const handleReport = useLastCallback(() => {
switch (subject) {
case 'messages':
reportMessages({ messageIds: messageIds!, reason: selectedReason, description });
@ -60,27 +59,15 @@ const ReportModal: FC<OwnProps> = ({
break;
}
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);
}, []);
});
const handleDescriptionChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const handleDescriptionChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
setDescription(e.target.value);
}, []);
});
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 convertPunycode from '../../lib/punycode';
@ -10,6 +10,7 @@ import {
} from '../../config';
import buildClassName from '../../util/buildClassName';
import { ensureProtocol } from '../../util/ensureProtocol';
import useLastCallback from '../../hooks/useLastCallback';
type OwnProps = {
url?: string;
@ -31,14 +32,14 @@ const SafeLink: FC<OwnProps> = ({
const content = children || 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;
e.preventDefault();
openUrl({ url, shouldSkipModal: isSafe });
return false;
}, [isSafe, openUrl, url]);
});
if (!url) {
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 { selectChatMessage, selectTabState } from '../../global/selectors';
import { formatDateAtTime } from '../../util/dateFormat';
import buildClassName from '../../util/buildClassName';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
@ -47,17 +49,17 @@ function SeenByModal({
return result;
}, [renderingSeenByDates]);
const handleClick = useCallback((userId: string) => {
const handleClick = useLastCallback((userId: string) => {
closeSeenByModal();
setTimeout(() => {
openChat({ id: userId });
}, CLOSE_ANIMATION_DURATION);
}, [closeSeenByModal, openChat]);
});
const handleCloseSeenByModal = useCallback(() => {
const handleCloseSeenByModal = useLastCallback(() => {
closeSeenByModal();
}, [closeSeenByModal]);
});
return (
<Modal

View File

@ -1,6 +1,6 @@
import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
import React, {
memo, useCallback, useEffect, useMemo, useRef,
memo, useEffect, useMemo, useRef,
} from '../../lib/teact/teact';
import { getActions } from '../../global';
@ -12,6 +12,8 @@ import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { getServerTimeOffset } from '../../util/serverTime';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import useLastCallback from '../../hooks/useLastCallback';
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
import useLang from '../../hooks/useLang';
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
@ -113,19 +115,11 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
} = useContextMenuHandlers(ref);
const shouldRenderContextMenu = Boolean(!noContextMenu && contextMenuPosition);
const getTriggerElement = useCallback(() => ref.current, []);
const getRootElement = useCallback(
() => ref.current!.closest('.custom-scroll, .no-scrollbar'),
[],
);
const getMenuElement = useCallback(
() => {
return isStatusPicker ? menuRef.current : ref.current!.querySelector('.sticker-context-menu .bubble');
},
[isStatusPicker],
);
const getTriggerElement = useLastCallback(() => ref.current);
const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar'));
const getMenuElement = useLastCallback(() => {
return isStatusPicker ? menuRef.current : ref.current!.querySelector('.sticker-context-menu .bubble');
});
const getLayout = () => ({ withPortal: isStatusPicker, shouldAvoidNegativePosition: true });
@ -165,38 +159,38 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
handleBeforeContextMenu(e);
};
const handleRemoveClick = useCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
const handleRemoveClick = useLastCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
e.preventDefault();
onRemoveRecentClick!(sticker);
}, [onRemoveRecentClick, sticker]);
});
const handleContextRemoveRecent = useCallback(() => {
const handleContextRemoveRecent = useLastCallback(() => {
onRemoveRecentClick!(sticker);
}, [onRemoveRecentClick, sticker]);
});
const handleContextUnfave = useCallback(() => {
const handleContextUnfave = useLastCallback(() => {
onUnfaveClick!(sticker);
}, [onUnfaveClick, sticker]);
});
const handleContextFave = useCallback(() => {
const handleContextFave = useLastCallback(() => {
onFaveClick!(sticker);
}, [onFaveClick, sticker]);
});
const handleSendQuiet = useCallback(() => {
const handleSendQuiet = useLastCallback(() => {
onClick?.(clickArg, true);
}, [clickArg, onClick]);
});
const handleSendScheduled = useCallback(() => {
const handleSendScheduled = useLastCallback(() => {
onClick?.(clickArg, undefined, true);
}, [clickArg, onClick]);
});
const handleOpenSet = useCallback(() => {
const handleOpenSet = useLastCallback(() => {
openStickerSet({ stickerSetInfo });
}, [openStickerSet, stickerSetInfo]);
});
const handleEmojiStatusExpiresClick = useCallback((e: React.SyntheticEvent, duration = 0) => {
const handleEmojiStatusExpiresClick = useLastCallback((e: React.SyntheticEvent, duration = 0) => {
e.preventDefault();
e.stopPropagation();
@ -206,7 +200,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
emojiStatus: sticker,
expires: Date.now() / 1000 + duration + getServerTimeOffset(),
});
}, [setEmojiStatus, sticker, handleContextMenuClose, onContextMenuClick]);
});
const shouldShowCloseButton = !IS_TOUCH_ENV && onRemoveRecentClick;

View File

@ -1,5 +1,5 @@
import React, {
memo, useCallback, useEffect, useMemo, useRef, useState,
memo, useEffect, useMemo, useRef, useState,
} from '../../lib/teact/teact';
import { getActions, getGlobal } from '../../global';
@ -21,6 +21,7 @@ import buildClassName from '../../util/buildClassName';
import { selectIsAlwaysHighPriorityEmoji, selectIsSetPremium } from '../../global/selectors';
import { getReactionUniqueKey } from '../../global/helpers';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import useFlag from '../../hooks/useFlag';
import useMediaTransition from '../../hooks/useMediaTransition';
@ -140,7 +141,7 @@ const StickerSet: FC<OwnProps> = ({
const isEmoji = stickerSet.isEmoji;
const isPremiumSet = !isRecent && selectIsSetPremium(stickerSet);
const handleClearRecent = useCallback(() => {
const handleClearRecent = useLastCallback(() => {
if (isReactionPicker) {
clearRecentReactions();
} else if (isEmoji) {
@ -149,11 +150,9 @@ const StickerSet: FC<OwnProps> = ({
clearRecentStickers();
}
closeConfirmModal();
}, [
clearRecentCustomEmoji, clearRecentReactions, clearRecentStickers, closeConfirmModal, isEmoji, isReactionPicker,
]);
});
const handleAddClick = useCallback(() => {
const handleAddClick = useLastCallback(() => {
if (isPremiumSet && !isCurrentUserPremium) {
openPremiumModal({
initialSection: 'animated_emoji',
@ -163,9 +162,9 @@ const StickerSet: FC<OwnProps> = ({
stickerSetId: stickerSet.id,
});
}
}, [isCurrentUserPremium, isPremiumSet, openPremiumModal, stickerSet, toggleStickerSet]);
});
const handleDefaultTopicIconClick = useCallback(() => {
const handleDefaultTopicIconClick = useLastCallback(() => {
onStickerSelect?.({
id: DEFAULT_TOPIC_ICON_STICKER_ID,
isLottie: false,
@ -174,9 +173,9 @@ const StickerSet: FC<OwnProps> = ({
shortName: 'dummy',
},
} satisfies ApiSticker);
}, [onStickerSelect]);
});
const handleDefaultStatusIconClick = useCallback(() => {
const handleDefaultStatusIconClick = useLastCallback(() => {
onStickerSelect?.({
id: DEFAULT_STATUS_ICON_ID,
isLottie: false,
@ -185,23 +184,24 @@ const StickerSet: FC<OwnProps> = ({
shortName: 'dummy',
},
} satisfies ApiSticker);
}, [onStickerSelect]);
});
const itemSize = isEmoji ? EMOJI_SIZE_PICKER : STICKER_SIZE_PICKER;
const margin = isEmoji ? emojiMarginPx : stickerMarginPx;
const verticalMargin = isEmoji ? emojiVerticalMarginPx : stickerMarginPx;
const calculateItemsPerRow = useCallback((width: number) => {
const calculateItemsPerRow = useLastCallback((width: number) => {
if (!width) {
return getItemsPerRowFallback(windowWidth);
}
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));
}, [calculateItemsPerRow]);
});
useResizeObserver(ref, handleResize);
useEffect(() => {

View File

@ -1,5 +1,5 @@
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 { TextPart } from '../../types';
@ -13,9 +13,11 @@ import buildClassName from '../../util/buildClassName';
import trimText from '../../util/trimText';
import renderText from './helpers/renderText';
import { formatPastTimeShort } from '../../util/dateFormat';
import useLang from '../../hooks/useLang';
import { renderMessageSummary } from './helpers/renderMessageText';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import Media from './Media';
import Link from '../ui/Link';
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]);
});
if (!linkData) {
return undefined;

View File

@ -1,6 +1,4 @@
import {
useCallback, useEffect, useRef,
} from '../../../lib/teact/teact';
import { useEffect, useRef } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { ActiveEmojiInteraction } from '../../../global/types';
@ -9,6 +7,7 @@ import safePlay from '../../../util/safePlay';
import buildStyle from '../../../util/buildStyle';
import { REM } from '../helpers/mediaDimensions';
import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia';
const SIZE = 7 * REM;
@ -42,7 +41,7 @@ export default function useAnimatedEmoji(
const interactions = useRef<number[] | undefined>(undefined);
const startedInteractions = useRef<number | undefined>(undefined);
const sendInteractionBunch = useCallback(() => {
const sendInteractionBunch = useLastCallback(() => {
const container = ref.current;
if (!container) return;
@ -55,9 +54,9 @@ export default function useAnimatedEmoji(
});
startedInteractions.current = undefined;
interactions.current = undefined;
}, [sendEmojiInteraction, chatId, messageId, emoji]);
});
const play = useCallback(() => {
const play = useLastCallback(() => {
const audio = audioRef.current;
if (soundMediaData) {
if (audio) {
@ -71,9 +70,9 @@ export default function useAnimatedEmoji(
audioRef.current = undefined;
}, { once: true });
}
}, [soundMediaData]);
});
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
play();
const container = ref.current;
@ -101,7 +100,7 @@ export default function useAnimatedEmoji(
interactions.current.push(startedInteractions.current
? (performance.now() - startedInteractions.current) / MS_DIVIDER
: TIME_DEFAULT);
}, [chatId, emoji, interactWithAnimatedEmoji, isOwn, messageId, play, sendInteractionBunch, size]);
});
// Set an end anchor for remote activated interaction
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 type { GlobalState } from '../../../global/types';
@ -7,6 +7,7 @@ import type { ApiSticker } from '../../../api/types';
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
import { addCustomEmojiCallback, removeCustomEmojiCallback } from '../../../util/customEmojiManager';
import useLastCallback from '../../../hooks/useLastCallback';
import useEnsureCustomEmoji from '../../../hooks/useEnsureCustomEmoji';
export default function useCustomEmoji(documentId?: string) {
@ -18,13 +19,13 @@ export default function useCustomEmoji(documentId?: string) {
useEnsureCustomEmoji(documentId);
const handleGlobalChange = useCallback((customEmojis?: GlobalState['customEmojis']) => {
const handleGlobalChange = useLastCallback((customEmojis?: GlobalState['customEmojis']) => {
if (!documentId) return;
const newGlobal = getGlobal();
setCustomEmoji((customEmojis ?? newGlobal.customEmojis).byId[documentId]);
setCanPlay(selectCanPlayAnimatedEmojis(newGlobal));
}, [documentId]);
});
useEffect(handleGlobalChange, [documentId, handleGlobalChange]);

View File

@ -1,12 +1,14 @@
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 animateScroll from '../../../util/animateScroll';
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_MARGIN = 100;
const SLIDE_TRANSITION_DURATION = 350 + ANIMATION_END_DELAY;
@ -85,7 +87,7 @@ export function useStickerPickerObservers(
}
}, [freezeForSet, freezeForShowingItems, isHidden, unfreezeForSet, unfreezeForShowingItems]);
const selectStickerSet = useCallback((index: number) => {
const selectStickerSet = useLastCallback((index: number) => {
setActiveSetIndex((currentIndex) => {
const stickerSetEl = document.getElementById(`${idPrefix}-${index}`)!;
const isClose = Math.abs(currentIndex - index) === 1;
@ -100,7 +102,7 @@ export function useStickerPickerObservers(
return index;
});
}, [containerRef, idPrefix]);
});
return {
activeSetIndex,

View File

@ -1,11 +1,11 @@
import React, {
memo, useCallback, useEffect, useRef,
} from '../../../lib/teact/teact';
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
import type { FC } from '../../../lib/teact/teact';
import { ApiMessageEntityTypes } from '../../../api/types';
import { createClassNameBuilder } from '../../../util/buildClassName';
import useLastCallback from '../../../hooks/useLastCallback';
import useFlag from '../../../hooks/useFlag';
import './Spoiler.scss';
@ -36,7 +36,7 @@ const Spoiler: FC<OwnProps> = ({
const [isRevealed, reveal, conceal] = useFlag();
const getContentLength = useCallback(() => {
const getContentLength = useLastCallback(() => {
if (!contentRef.current) {
return 0;
}
@ -45,9 +45,9 @@ const Spoiler: FC<OwnProps> = ({
const emojiCount = contentRef.current.querySelectorAll('.emoji').length;
// Optimization: ignore alt, assume that viewing emoji takes same time as viewing 4 characters
return textLength + emojiCount * 4;
}, []);
});
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const handleClick = useLastCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
e.preventDefault();
e.stopPropagation();
@ -62,7 +62,7 @@ const Spoiler: FC<OwnProps> = ({
actionsByMessageId.get(messageId!)?.forEach((actions) => actions.conceal());
conceal();
}, timeoutMs);
}, [conceal, messageId]);
});
useEffect(() => {
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 type { FC } from '../../lib/teact/teact';
@ -7,6 +7,8 @@ import type { FolderEditDispatch } from '../../hooks/reducers/useFoldersReducer'
import type { GlobalState } from '../../global/types';
import buildClassName from '../../util/buildClassName';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import useHistoryBack from '../../hooks/useHistoryBack';
import useLeftHeaderButtonRtlForumTransition from './main/hooks/useLeftHeaderButtonRtlForumTransition';
@ -50,9 +52,9 @@ const ArchivedChats: FC<OwnProps> = ({
onBack: onReset,
});
const handleDisplayArchiveInChats = useCallback(() => {
const handleDisplayArchiveInChats = useLastCallback(() => {
updateArchiveSettings({ isHidden: false });
}, [updateArchiveSettings]);
});
const {
shouldDisableDropdownMenuTransitionRef,

View File

@ -1,6 +1,6 @@
import type { RefObject } from 'react';
import React, {
memo, useCallback, useEffect, useState,
memo, useEffect, useState,
} from '../../lib/teact/teact';
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 captureEscKeyListener from '../../util/captureEscKeyListener';
import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors';
import useLastCallback from '../../hooks/useLastCallback';
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
import { useHotkeys } from '../../hooks/useHotkeys';
import useSyncEffect from '../../hooks/useSyncEffect';
@ -114,7 +116,7 @@ function LeftColumn({
break;
}
const handleReset = useCallback((forceReturnToChatList?: true | Event) => {
const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => {
function fullReset() {
setContent(LeftColumnContent.ChatList);
setSettingsScreen(SettingsScreens.Main);
@ -323,12 +325,9 @@ function LeftColumn({
}
fullReset();
}, [
content, isFirstChatFolderActive, setGlobalSearchClosing, resetChatCreation, setGlobalSearchQuery,
setGlobalSearchDate, setGlobalSearchChatId, settingsScreen, hasPasscode,
]);
});
const handleSearchQuery = useCallback((query: string) => {
const handleSearchQuery = useLastCallback((query: string) => {
if (content === LeftColumnContent.Contacts) {
setContactsFilter(query);
return;
@ -339,13 +338,13 @@ function LeftColumn({
if (query !== searchQuery) {
setGlobalSearchQuery({ query });
}
}, [content, searchQuery, setGlobalSearchQuery]);
});
const handleTopicSearch = useCallback(() => {
const handleTopicSearch = useLastCallback(() => {
setContent(LeftColumnContent.GlobalSearch);
setGlobalSearchQuery({ query: '' });
setGlobalSearchChatId({ id: forumPanelChatId });
}, [forumPanelChatId, setGlobalSearchChatId, setGlobalSearchQuery]);
});
useEffect(
() => (content !== LeftColumnContent.ChatList || (isFirstChatFolderActive && !isChatOpen && !isForumPanelOpen)
@ -354,29 +353,29 @@ function LeftColumn({
[isFirstChatFolderActive, content, handleReset, isChatOpen, isForumPanelOpen],
);
const handleHotkeySearch = useCallback((e: KeyboardEvent) => {
const handleHotkeySearch = useLastCallback((e: KeyboardEvent) => {
if (content === LeftColumnContent.GlobalSearch) {
return;
}
e.preventDefault();
setContent(LeftColumnContent.GlobalSearch);
}, [content]);
});
const handleHotkeySavedMessages = useCallback((e: KeyboardEvent) => {
const handleHotkeySavedMessages = useLastCallback((e: KeyboardEvent) => {
e.preventDefault();
openChat({ id: currentUserId, shouldReplaceHistory: true });
}, [currentUserId, openChat]);
});
const handleArchivedChats = useCallback((e: KeyboardEvent) => {
const handleArchivedChats = useLastCallback((e: KeyboardEvent) => {
e.preventDefault();
setContent(LeftColumnContent.Archived);
}, []);
});
const handleHotkeySettings = useCallback((e: KeyboardEvent) => {
const handleHotkeySettings = useLastCallback((e: KeyboardEvent) => {
e.preventDefault();
setContent(LeftColumnContent.Settings);
}, []);
});
useHotkeys({
'Mod+Shift+F': handleHotkeySearch,
@ -408,10 +407,10 @@ function LeftColumn({
}
}, [foldersDispatch, nextFoldersAction, nextSettingsScreen, requestNextSettingsScreen]);
const handleSettingsScreenSelect = useCallback((screen: SettingsScreens) => {
const handleSettingsScreenSelect = useLastCallback((screen: SettingsScreens) => {
setContent(LeftColumnContent.Settings);
setSettingsScreen(screen);
}, []);
});
function renderContent(isActive: boolean) {
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 type { FC } from '../../../lib/teact/teact';
@ -41,6 +41,7 @@ import {
import buildClassName from '../../../util/buildClassName';
import { createLocationHash } from '../../../util/routing';
import useLastCallback from '../../../hooks/useLastCallback';
import useChatContextActions from '../../../hooks/useChatContextActions';
import useFlag from '../../../hooks/useFlag';
import useChatListEntry from './hooks/useChatListEntry';
@ -158,7 +159,7 @@ const Chat: FC<OwnProps & StateProps> = ({
orderDiff,
});
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
if (isForum) {
if (isSelectedForum) {
closeForumPanel(undefined, { forceOnHeavyAnimation: true });
@ -174,32 +175,32 @@ const Chat: FC<OwnProps & StateProps> = ({
if (isSelected && canScrollDown) {
focusLastMessage();
}
}, [isForum, chatId, isSelected, canScrollDown, isSelectedForum]);
});
const handleDragEnter = useCallback((e) => {
const handleDragEnter = useLastCallback((e) => {
e.preventDefault();
onDragEnter?.(chatId);
}, [chatId, onDragEnter]);
});
const handleDelete = useCallback(() => {
const handleDelete = useLastCallback(() => {
markRenderDeleteModal();
openDeleteModal();
}, [markRenderDeleteModal, openDeleteModal]);
});
const handleMute = useCallback(() => {
const handleMute = useLastCallback(() => {
markRenderMuteModal();
openMuteModal();
}, [markRenderMuteModal, openMuteModal]);
});
const handleChatFolderChange = useCallback(() => {
const handleChatFolderChange = useLastCallback(() => {
markRenderChatFolderModal();
openChatFolderModal();
}, [markRenderChatFolderModal, openChatFolderModal]);
});
const handleReport = useCallback(() => {
const handleReport = useLastCallback(() => {
markRenderReportModal();
openReportModal();
}, [markRenderReportModal, openReportModal]);
});
const contextActions = useChatContextActions({
chat,

View File

@ -1,6 +1,6 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useMemo, useRef,
memo, useEffect, useMemo, useRef,
} from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global';
@ -18,6 +18,8 @@ import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { selectCurrentLimit } from '../../../global/selectors/limits';
import { selectCanShareFolder, selectTabState } from '../../../global/selectors';
import useLastCallback from '../../../hooks/useLastCallback';
import useShowTransition from '../../../hooks/useShowTransition';
import useLang from '../../../hooks/useLang';
import useHistoryBack from '../../../hooks/useHistoryBack';
@ -180,9 +182,9 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
});
}, [displayedFolders, folderCountersById, lang, maxFolders, folderInvitesById, maxFolderInvites]);
const handleSwitchTab = useCallback((index: number) => {
const handleSwitchTab = useLastCallback((index: number) => {
setActiveChatFolder({ activeChatFolder: index }, { forceOnHeavyAnimation: true });
}, [setActiveChatFolder]);
});
// Prevent `activeTab` pointing at non-existing folder after update
useEffect(() => {

View File

@ -1,5 +1,5 @@
import React, {
memo, useEffect, useRef, useCallback,
memo, useEffect, useRef,
} from '../../../lib/teact/teact';
import { getActions } from '../../../global';
@ -20,6 +20,7 @@ import { IS_MAC_OS, IS_APP } from '../../../util/windowEnvironment';
import { getPinnedChatsCount, getOrderKey } from '../../../util/folderManager';
import buildClassName from '../../../util/buildClassName';
import useLastCallback from '../../../hooks/useLastCallback';
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
@ -133,18 +134,18 @@ const ChatList: FC<OwnProps> = ({
throttleMs: INTERSECTION_THROTTLE,
});
const handleArchivedClick = useCallback(() => {
const handleArchivedClick = useLastCallback(() => {
onLeftColumnContentChange(LeftColumnContent.Archived);
closeForumPanel();
}, [closeForumPanel, onLeftColumnContentChange]);
});
const handleArchivedDragEnter = useCallback(() => {
const handleArchivedDragEnter = useLastCallback(() => {
if (shouldIgnoreDragRef.current) {
shouldIgnoreDragRef.current = false;
return;
}
handleArchivedClick();
}, [handleArchivedClick]);
});
const handleDragEnter = useDebouncedCallback((chatId: string) => {
if (shouldIgnoreDragRef.current) {
@ -154,13 +155,13 @@ const ChatList: FC<OwnProps> = ({
openChat({ id: chatId, shouldReplaceHistory: 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 x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (x < rect.width || y < rect.y) return;
shouldIgnoreDragRef.current = true;
}, []);
});
function renderChats() {
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);

View File

@ -1,5 +1,5 @@
import React, {
memo, useCallback, useEffect, useMemo, useRef, useState,
memo, useEffect, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import { requestNextMutation } from '../../../lib/fasterdom/fasterdom';
@ -22,6 +22,7 @@ import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners';
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
import { createLocationHash } from '../../../util/routing';
import useLastCallback from '../../../hooks/useLastCallback';
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
import useOrderDiff from './hooks/useOrderDiff';
@ -92,9 +93,9 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
const [isScrolled, setIsScrolled] = useState(false);
const lang = useLang();
const handleClose = useCallback(() => {
const handleClose = useLastCallback(() => {
closeForumPanel();
}, [closeForumPanel]);
});
useEffect(() => {
if (!withInterfaceAnimations && !isOpen) {
@ -102,10 +103,10 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
}
}, [withInterfaceAnimations, isOpen, onCloseAnimationEnd]);
const handleToggleChatInfo = useCallback(() => {
const handleToggleChatInfo = useLastCallback(() => {
if (!chat) return;
openChatWithInfo({ id: chat.id, shouldReplaceHistory: true });
}, [chat, openChatWithInfo]);
});
const { observe } = useIntersectionObserver({
rootRef: containerRef,

View File

@ -1,6 +1,6 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useRef, useState,
memo, useEffect, useRef, useState,
} from '../../../lib/teact/teact';
import { getActions } from '../../../global';
@ -11,6 +11,8 @@ import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReduc
import { IS_ELECTRON } from '../../../config';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import useLastCallback from '../../../hooks/useLastCallback';
import useShowTransition from '../../../hooks/useShowTransition';
import useLang from '../../../hooks/useLang';
import useForumPanelRender from '../../../hooks/useForumPanelRender';
@ -81,15 +83,15 @@ const LeftMain: FC<OwnProps> = ({
const isMouseInside = useRef(false);
const handleMouseEnter = useCallback(() => {
const handleMouseEnter = useLastCallback(() => {
if (content !== LeftColumnContent.ChatList) {
return;
}
isMouseInside.current = true;
setIsNewChatButtonShown(true);
}, [content]);
});
const handleMouseLeave = useCallback(() => {
const handleMouseLeave = useLastCallback(() => {
isMouseInside.current = false;
if (closeTimeout) {
@ -102,36 +104,36 @@ const LeftMain: FC<OwnProps> = ({
setIsNewChatButtonShown(false);
}
}, BUTTON_CLOSE_DELAY_MS);
}, []);
});
const handleSelectSettings = useCallback(() => {
const handleSelectSettings = useLastCallback(() => {
onContentChange(LeftColumnContent.Settings);
}, [onContentChange]);
});
const handleSelectContacts = useCallback(() => {
const handleSelectContacts = useLastCallback(() => {
onContentChange(LeftColumnContent.Contacts);
}, [onContentChange]);
});
const handleSelectArchived = useCallback(() => {
const handleSelectArchived = useLastCallback(() => {
onContentChange(LeftColumnContent.Archived);
closeForumPanel();
}, [closeForumPanel, onContentChange]);
});
const handleUpdateClick = useCallback(() => {
const handleUpdateClick = useLastCallback(() => {
if (IS_ELECTRON) {
window.electron?.installUpdate();
} else {
window.location.reload();
}
}, []);
});
const handleSelectNewChannel = useCallback(() => {
const handleSelectNewChannel = useLastCallback(() => {
onContentChange(LeftColumnContent.NewChannelStep1);
}, [onContentChange]);
});
const handleSelectNewGroup = useCallback(() => {
const handleSelectNewGroup = useLastCallback(() => {
onContentChange(LeftColumnContent.NewGroupStep1);
}, [onContentChange]);
});
useEffect(() => {
let autoCloseTimeout: number | undefined;

View File

@ -1,6 +1,6 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useMemo, useRef,
memo, useEffect, useMemo, useRef,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
@ -40,6 +40,8 @@ import useConnectionStatus from '../../../hooks/useConnectionStatus';
import { useHotkeys } from '../../../hooks/useHotkeys';
import { getPromptInstall } from '../../../util/installPrompt';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import useLastCallback from '../../../hooks/useLastCallback';
import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition';
import { useFullscreenStatus } from '../../../hooks/useFullscreen';
import useElectronDrag from '../../../hooks/useElectronDrag';
@ -152,7 +154,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
lang, connectionState, isSyncing, isMessageListOpen, isConnectionStatusMinimized, !areChatsLoaded,
);
const handleLockScreenHotkey = useCallback((e: KeyboardEvent) => {
const handleLockScreenHotkey = useLastCallback((e: KeyboardEvent) => {
e.preventDefault();
e.stopPropagation();
if (hasPasscode) {
@ -160,7 +162,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
} else {
requestNextSettingsScreen({ screen: SettingsScreens.PasscodeDisabled });
}
}, [hasPasscode]);
});
useHotkeys(canSetPasscode ? {
'Ctrl+Shift+L': handleLockScreenHotkey,
@ -193,29 +195,29 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
);
}, [hasMenu, isMobile, lang, onReset, shouldSkipTransition]);
const handleSearchFocus = useCallback(() => {
const handleSearchFocus = useLastCallback(() => {
if (!searchQuery) {
onSearchQuery('');
}
}, [searchQuery, onSearchQuery]);
});
const toggleConnectionStatus = useCallback(() => {
const toggleConnectionStatus = useLastCallback(() => {
setSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized });
}, [isConnectionStatusMinimized, setSettingOption]);
});
const handleSelectSaved = useCallback(() => {
const handleSelectSaved = useLastCallback(() => {
openChat({ id: currentUserId, shouldReplaceHistory: true });
}, [currentUserId, openChat]);
});
const handleDarkModeToggle = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
const handleDarkModeToggle = useLastCallback((e: React.SyntheticEvent<HTMLElement>) => {
e.stopPropagation();
const newTheme = theme === 'light' ? 'dark' : 'light';
setSettingOption({ theme: newTheme });
setSettingOption({ shouldUseSystemTheme: false });
}, [setSettingOption, theme]);
});
const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
const handleAnimationLevelChange = useLastCallback((e: React.SyntheticEvent<HTMLElement>) => {
e.stopPropagation();
let newLevel = animationLevel + 1;
@ -228,29 +230,29 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
setSettingOption({ animationLevel: newLevel as AnimationLevel });
updatePerformanceSettings(performanceSettings);
}, [animationLevel, setSettingOption]);
});
const handleChangelogClick = useCallback(() => {
const handleChangelogClick = useLastCallback(() => {
window.open(BETA_CHANGELOG_URL, '_blank', 'noopener');
}, []);
});
const handleSwitchToWebK = useCallback(() => {
const handleSwitchToWebK = useLastCallback(() => {
setPermanentWebVersion('K');
clearWebsync();
skipLockOnUnload();
}, [skipLockOnUnload]);
});
const handleOpenTipsChat = useCallback(() => {
const handleOpenTipsChat = useLastCallback(() => {
openChatByUsername({ username: lang('Settings.TipsUsername') });
}, [lang, openChatByUsername]);
});
const handleBugReportClick = useCallback(() => {
const handleBugReportClick = useLastCallback(() => {
openUrl({ url: FEEDBACK_URL });
}, [openUrl]);
});
const handleLockScreen = useCallback(() => {
const handleLockScreen = useLastCallback(() => {
lockScreen();
}, [lockScreen]);
});
const isSearchFocused = (
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 type { FC } from '../../../lib/teact/teact';
@ -28,6 +28,7 @@ import { createLocationHash } from '../../../util/routing';
import renderText from '../../common/helpers/renderText';
import { getMessageAction } from '../../../global/helpers';
import useLastCallback from '../../../hooks/useLastCallback';
import useChatListEntry from './hooks/useChatListEntry';
import useTopicContextActions from './hooks/useTopicContextActions';
import useFlag from '../../../hooks/useFlag';
@ -105,19 +106,19 @@ const Topic: FC<OwnProps & StateProps> = ({
} = topic;
const isMuted = topic.isMuted || (topic.isMuted === undefined && chat.isMuted);
const handleOpenDeleteModal = useCallback(() => {
const handleOpenDeleteModal = useLastCallback(() => {
markRenderDeleteModal();
openDeleteModal();
}, [markRenderDeleteModal, openDeleteModal]);
});
const handleDelete = useCallback(() => {
const handleDelete = useLastCallback(() => {
deleteTopic({ chatId: chat.id, topicId: topic.id });
}, [chat.id, deleteTopic, topic.id]);
});
const handleMute = useCallback(() => {
const handleMute = useLastCallback(() => {
markRenderMuteModal();
openMuteModal();
}, [markRenderMuteModal, openMuteModal]);
});
const { renderSubtitle, ref } = useChatListEntry({
chat,
@ -138,13 +139,13 @@ const Topic: FC<OwnProps & StateProps> = ({
orderDiff,
});
const handleOpenTopic = useCallback(() => {
const handleOpenTopic = useLastCallback(() => {
openChat({ id: chatId, threadId: topic.id, shouldReplaceHistory: true });
if (canScrollDown) {
focusLastMessage();
}
}, [openChat, chatId, topic.id, canScrollDown, focusLastMessage]);
});
const contextActions = useTopicContextActions({
topic,

View File

@ -1,5 +1,5 @@
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 type {
@ -21,6 +21,7 @@ import buildClassName from '../../../util/buildClassName';
import { formatPastTimeShort } from '../../../util/dateFormat';
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia';
import useLang from '../../../hooks/useLang';
import useSelectWithEnter from '../../../hooks/useSelectWithEnter';
@ -61,9 +62,9 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'micro'));
const isRoundVideo = Boolean(getMessageRoundVideo(message));
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
focusMessage({ chatId, messageId: message.id, shouldReplaceHistory: true });
}, [chatId, focusMessage, message.id]);
});
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 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 { selectTabState } from '../../../global/selectors';
import useLastCallback from '../../../hooks/useLastCallback';
import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer';
import Transition from '../../ui/Transition';
@ -145,7 +147,7 @@ const Settings: FC<OwnProps> = ({
const [twoFaState, twoFaDispatch] = useTwoFaReducer();
const [privacyPasscode, setPrivacyPasscode] = useState<string>('');
const handleReset = useCallback((forceReturnToChatList?: true | Event) => {
const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => {
const isFromSettings = selectTabState(getGlobal()).shareFolderScreen?.isFromSettings;
if (currentScreen === SettingsScreens.FoldersShare) {
@ -181,10 +183,7 @@ const Settings: FC<OwnProps> = ({
}
onReset();
}, [
foldersState.mode, foldersDispatch,
currentScreen, onReset, onScreenSelect,
]);
});
function renderCurrentSectionContent(isScreenActive: boolean, screen: SettingsScreens) {
const privacyAllowScreens: Record<number, boolean> = {

View File

@ -1,5 +1,5 @@
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 type { GlobalState, TabState } from '../../global/types';
@ -15,6 +15,7 @@ import {
} from '../../global/helpers';
import { compact } from '../../util/iteratees';
import useLastCallback from '../../hooks/useLastCallback';
import useRunDebounced from '../../hooks/useRunDebounced';
type StateProps = {
@ -34,7 +35,7 @@ const DownloadManager: FC<StateProps> = ({
const runDebounced = useRunDebounced(GLOBAL_UPDATE_DEBOUNCE, true);
const handleMessageDownloaded = useCallback((message: ApiMessage) => {
const handleMessageDownloaded = useLastCallback((message: ApiMessage) => {
downloadedMessages.add(message);
runDebounced(() => {
if (downloadedMessages.size) {
@ -42,7 +43,7 @@ const DownloadManager: FC<StateProps> = ({
downloadedMessages.clear();
}
});
}, [cancelMessagesMediaDownload, runDebounced]);
});
useEffect(() => {
// 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 React, {
useEffect, memo, useCallback, useState, useRef, useLayoutEffect,
useEffect, memo, useState, useRef, useLayoutEffect,
} from '../../lib/teact/teact';
import { addExtraClass } from '../../lib/teact/teact-dom';
import { requestNextMutation } from '../../lib/fasterdom/fasterdom';
@ -44,6 +44,7 @@ import { parseInitialLocationHash, parseLocationHash } from '../../util/routing'
import { Bundles, loadBundle } from '../../util/moduleLoader';
import updateIcon from '../../util/updateIcon';
import useLastCallback from '../../hooks/useLastCallback';
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
import useBackgroundMode from '../../hooks/useBackgroundMode';
import useBeforeUnload from '../../hooks/useBeforeUnload';
@ -487,11 +488,11 @@ const Main: FC<OwnProps & StateProps> = ({
isFullscreen && 'is-fullscreen',
);
const handleBlur = useCallback(() => {
const handleBlur = useLastCallback(() => {
onTabFocusChange({ isBlurred: true });
}, [onTabFocusChange]);
});
const handleFocus = useCallback(() => {
const handleFocus = useLastCallback(() => {
onTabFocusChange({ isBlurred: false });
if (!document.title.includes(INACTIVE_MARKER)) {
@ -499,15 +500,15 @@ const Main: FC<OwnProps & StateProps> = ({
}
updateIcon(false);
}, [onTabFocusChange, updatePageTitle]);
});
const handleStickerSetModalClose = useCallback(() => {
const handleStickerSetModalClose = useLastCallback(() => {
closeStickerSetModal();
}, [closeStickerSetModal]);
});
const handleCustomEmojiSetsModalClose = useCallback(() => {
const handleCustomEmojiSetsModalClose = useLastCallback(() => {
closeCustomEmojiSets();
}, [closeCustomEmojiSets]);
});
// Online status and browser tab indicators
useBackgroundMode(handleBlur, handleFocus);

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useMemo, useRef,
memo, useEffect, useMemo, useRef,
} from '../../lib/teact/teact';
import type {
@ -54,6 +54,7 @@ import MediaViewerSlides from './MediaViewerSlides';
import SenderInfo from './SenderInfo';
import './MediaViewer.scss';
import useLastCallback from '../../hooks/useLastCallback';
type StateProps = {
chatId?: string;
@ -230,10 +231,10 @@ const MediaViewer: FC<StateProps> = ({
bestImageData, prevBestImageData, dimensions, isVideo, hasFooter,
]);
const handleClose = useCallback(() => closeMediaViewer(), [closeMediaViewer]);
const handleClose = useLastCallback(() => closeMediaViewer());
const mediaIdRef = useStateRef(mediaId);
const handleFooterClick = useCallback(() => {
const handleFooterClick = useLastCallback(() => {
handleClose();
const currentMediaId = mediaIdRef.current;
@ -248,16 +249,16 @@ const MediaViewer: FC<StateProps> = ({
} else {
focusMessage({ chatId, threadId, messageId: currentMediaId });
}
}, [handleClose, mediaIdRef, chatId, isMobile, threadId]);
});
const handleForward = useCallback(() => {
const handleForward = useLastCallback(() => {
openForwardMenu({
fromChatId: chatId!,
messageIds: [mediaId!],
});
}, [openForwardMenu, chatId, mediaId]);
});
const selectMedia = useCallback((id?: number) => {
const selectMedia = useLastCallback((id?: number) => {
openMediaViewer({
chatId,
threadId,
@ -267,7 +268,7 @@ const MediaViewer: FC<StateProps> = ({
}, {
forceOnHeavyAnimation: true,
});
}, [avatarOwner?.id, chatId, openMediaViewer, origin, threadId]);
});
useEffect(() => (isOpen ? captureEscKeyListener(() => {
handleClose();
@ -281,7 +282,7 @@ const MediaViewer: FC<StateProps> = ({
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;
const mIds = mediaIdsRef.current;
const index = mIds.indexOf(fromId);
@ -289,9 +290,9 @@ const MediaViewer: FC<StateProps> = ({
return mIds[index + direction];
}
return undefined;
}, [mediaIdsRef]);
});
const handleBeforeDelete = useCallback(() => {
const handleBeforeDelete = useLastCallback(() => {
if (mediaIds.length <= 1) {
handleClose();
return;
@ -300,7 +301,7 @@ const MediaViewer: FC<StateProps> = ({
// Before deleting, select previous media or the first one
index = index > 0 ? index - 1 : 0;
selectMedia(mediaIds[index]);
}, [handleClose, mediaId, mediaIds, selectMedia]);
});
const lang = useLang();

View File

@ -1,8 +1,4 @@
import React, {
memo,
useCallback,
useMemo,
} from '../../lib/teact/teact';
import React, { memo, useMemo } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
@ -21,6 +17,7 @@ import {
} from '../../global/selectors';
import { getMessageMediaFormat, getMessageMediaHash, isUserId } from '../../global/helpers';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
import useFlag from '../../hooks/useFlag';
@ -100,27 +97,27 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
message && getMessageMediaFormat(message, 'download'),
);
const handleDownloadClick = useCallback(() => {
const handleDownloadClick = useLastCallback(() => {
if (isDownloading) {
cancelMessageMediaDownload({ message: message! });
} else {
downloadMessageMedia({ message: message! });
}
}, [cancelMessageMediaDownload, downloadMessageMedia, isDownloading, message]);
});
const handleZoomOut = useCallback(() => {
const handleZoomOut = useLastCallback(() => {
const zoomChange = getZoomChange();
const change = zoomChange < 0 ? zoomChange : 0;
setZoomChange(change - 1);
}, [getZoomChange, setZoomChange]);
});
const handleZoomIn = useCallback(() => {
const handleZoomIn = useLastCallback(() => {
const zoomChange = getZoomChange();
const change = zoomChange > 0 ? zoomChange : 0;
setZoomChange(change + 1);
}, [getZoomChange, setZoomChange]);
});
const handleUpdate = useCallback(() => {
const handleUpdate = useLastCallback(() => {
if (!avatarPhoto || !avatarOwnerId) return;
if (isUserId(avatarOwnerId)) {
updateProfilePhoto({ photo: avatarPhoto });
@ -128,7 +125,7 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
updateChatPhoto({ chatId: avatarOwnerId, photo: avatarPhoto });
}
selectMedia(0);
}, [avatarPhoto, avatarOwnerId, selectMedia, updateProfilePhoto, updateChatPhoto]);
});
const lang = useLang();

View File

@ -1,5 +1,5 @@
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 type {
@ -18,6 +18,8 @@ import { renderMessageText } from '../common/helpers/renderMessageText';
import stopEvent from '../../util/stopEvent';
import buildClassName from '../../util/buildClassName';
import { useMediaProps } from './hooks/useMediaProps';
import useLastCallback from '../../hooks/useLastCallback';
import useAppLayout from '../../hooks/useAppLayout';
import useLang from '../../hooks/useLang';
import useControlsSignal from './hooks/useControlsSignal';
@ -102,9 +104,9 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
const isOpen = Boolean(avatarOwner || mediaId);
const { isMobile } = useAppLayout();
const toggleControlsOnMove = useCallback(() => {
const toggleControlsOnMove = useLastCallback(() => {
toggleControls(true);
}, [toggleControls]);
});
if (avatarOwner || actionPhoto) {
if (!isVideoAvatar) {

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
memo, useEffect, useLayoutEffect, useRef, useState,
} from '../../lib/teact/teact';
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 { debounce } from '../../util/schedulers';
import useLastCallback from '../../hooks/useLastCallback';
import useDebouncedCallback from '../../hooks/useDebouncedCallback';
import useLang from '../../hooks/useLang';
import useTimeout from '../../hooks/useTimeout';
@ -134,13 +135,13 @@ const MediaViewerSlides: FC<OwnProps> = ({
const shouldCloseOnVideo = Boolean(isGif && !IS_IOS);
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;
const isFooter = windowHeight - e.pageY < CLICK_Y_THRESHOLD;
if (!isFooter && e.pageX < clickXThreshold) return;
if (!isFooter && e.pageX > windowWidth - clickXThreshold) return;
setControlsVisible(!getControlsVisible());
}, [clickXThreshold, getControlsVisible, setControlsVisible, windowHeight, windowWidth]);
});
useTimeout(() => setControlsVisible(true), ANIMATION_DURATION);

View File

@ -1,10 +1,11 @@
import React, {
useRef, useState, useCallback, useEffect, memo, useMemo, useLayoutEffect,
useRef, useState, useEffect, memo, useMemo, useLayoutEffect,
} from '../../lib/teact/teact';
import type { BufferedRange } from '../../hooks/useBuffering';
import type { ApiDimensions } from '../../api/types';
import useLastCallback from '../../hooks/useLastCallback';
import useSignal from '../../hooks/useSignal';
import useCurrentTimeSignal from './hooks/currentTimeSignal';
@ -65,11 +66,11 @@ const SeekLine: React.FC<OwnProps> = ({
return getPreviewDimensions(posterSize?.width || 0, posterSize?.height || 0);
}, [posterSize]);
const setPreview = useCallback((time: number) => {
const setPreview = useLastCallback((time: number) => {
time = Math.floor(time);
setPreviewTime(time);
renderVideoPreview(time);
}, [setPreviewTime]);
});
useEffect(() => {
if (isPreviewDisabled || !url || !isReady) return undefined;

View File

@ -1,5 +1,5 @@
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 type { ApiChat, ApiMessage, ApiUser } from '../../api/types';
@ -13,6 +13,8 @@ import {
selectSender,
selectUser,
} from '../../global/selectors';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import useAppLayout from '../../hooks/useAppLayout';
@ -50,7 +52,7 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
const { isMobile } = useAppLayout();
const handleFocusMessage = useCallback(() => {
const handleFocusMessage = useLastCallback(() => {
closeMediaViewer();
if (!chatId || !messageId) return;
@ -63,7 +65,7 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
} else {
focusMessage({ chatId, messageId });
}
}, [chatId, isMobile, focusMessage, toggleChatInfo, messageId, closeMediaViewer]);
});
const lang = useLang();

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useRef, useState,
memo, useEffect, useRef, useState,
} from '../../lib/teact/teact';
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 stopEvent from '../../util/stopEvent';
import { clamp } from '../../util/math';
import useLastCallback from '../../hooks/useLastCallback';
import useBuffering from '../../hooks/useBuffering';
import useFullscreen from '../../hooks/useFullscreen';
import usePictureInPicture from '../../hooks/usePictureInPicture';
@ -83,16 +85,16 @@ const VideoPlayer: FC<OwnProps> = ({
const duration = videoRef.current?.duration || 0;
const isLooped = isGif || duration <= MAX_LOOP_DURATION;
const handleEnterFullscreen = useCallback(() => {
const handleEnterFullscreen = useLastCallback(() => {
// Yandex browser doesn't support PIP when video is hidden
if (IS_YA_BROWSER) return;
setMediaViewerHidden({ isHidden: true });
}, [setMediaViewerHidden]);
});
const handleLeaveFullscreen = useCallback(() => {
const handleLeaveFullscreen = useLastCallback(() => {
if (IS_YA_BROWSER) return;
setMediaViewerHidden({ isHidden: false });
}, [setMediaViewerHidden]);
});
const [
isPictureInPictureSupported,
@ -102,17 +104,17 @@ const VideoPlayer: FC<OwnProps> = ({
const [, toggleControls, lockControls] = useControlsSignal();
const handleVideoMove = useCallback(() => {
const handleVideoMove = useLastCallback(() => {
toggleControls(true);
}, [toggleControls]);
});
const handleVideoLeave = useCallback((e) => {
const handleVideoLeave = useLastCallback((e) => {
const bounds = videoRef.current?.getBoundingClientRect();
if (!bounds) return;
if (e.clientX < bounds.left || e.clientX > bounds.right || e.clientY < bounds.top || e.clientY > bounds.bottom) {
toggleControls(false);
}
}, [toggleControls]);
});
const {
isReady, isBuffered, bufferedRanges, bufferingHandlers, bufferedProgress,
@ -150,7 +152,7 @@ const VideoPlayer: FC<OwnProps> = ({
videoRef.current!.playbackRate = playbackRate;
}, [playbackRate]);
const togglePlayState = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent> | KeyboardEvent) => {
const togglePlayState = useLastCallback((e: React.MouseEvent<HTMLElement, MouseEvent> | KeyboardEvent) => {
e.stopPropagation();
if (isPlaying) {
videoRef.current!.pause();
@ -159,9 +161,9 @@ const VideoPlayer: FC<OwnProps> = ({
safePlay(videoRef.current!);
setIsPlaying(true);
}
}, [isPlaying]);
});
const handleClick = useCallback((e: React.MouseEvent<HTMLVideoElement, MouseEvent>) => {
const handleClick = useLastCallback((e: React.MouseEvent<HTMLVideoElement, MouseEvent>) => {
if (isClickDisabled) {
return;
}
@ -170,12 +172,12 @@ const VideoPlayer: FC<OwnProps> = ({
} else {
togglePlayState(e);
}
}, [onClose, shouldCloseOnClick, togglePlayState, isClickDisabled]);
});
useVideoCleanup(videoRef, []);
const [, setCurrentTime] = useCurrentTimeSignal();
const handleTimeUpdate = useCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
const handleTimeUpdate = useLastCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
const video = e.currentTarget;
if (video.readyState >= MIN_READY_STATE) {
setCurrentTime(video.currentTime);
@ -184,40 +186,40 @@ const VideoPlayer: FC<OwnProps> = ({
setCurrentTime(0);
setIsPlaying(false);
}
}, [isLooped, setCurrentTime]);
});
const handleEnded = useCallback(() => {
const handleEnded = useLastCallback(() => {
if (isLooped) return;
setCurrentTime(0);
setIsPlaying(false);
toggleControls(true);
}, [isLooped, setCurrentTime, toggleControls]);
});
const handleFullscreenChange = useCallback(() => {
const handleFullscreenChange = useLastCallback(() => {
if (isFullscreen && exitFullscreen) {
exitFullscreen();
} else if (!isFullscreen && setFullscreen) {
setFullscreen();
}
}, [exitFullscreen, isFullscreen, setFullscreen]);
});
const handleSeek = useCallback((position: number) => {
const handleSeek = useLastCallback((position: number) => {
videoRef.current!.currentTime = position;
}, []);
});
const handleVolumeChange = useCallback((newVolume: number) => {
const handleVolumeChange = useLastCallback((newVolume: number) => {
setMediaViewerVolume({ volume: newVolume / 100 });
}, [setMediaViewerVolume]);
});
const handleVolumeMuted = useCallback(() => {
const handleVolumeMuted = useLastCallback(() => {
// Browser requires explicit user interaction to keep video playing after unmuting
videoRef.current!.muted = !videoRef.current!.muted;
setMediaViewerMuted({ isMuted: !isMuted });
}, [isMuted, setMediaViewerMuted]);
});
const handlePlaybackRateChange = useCallback((newPlaybackRate: number) => {
const handlePlaybackRateChange = useLastCallback((newPlaybackRate: number) => {
setMediaViewerPlaybackRate({ playbackRate: newPlaybackRate });
}, [setMediaViewerPlaybackRate]);
});
useEffect(() => {
if (!isMediaViewerOpen) return undefined;

View File

@ -1,11 +1,12 @@
import type { FC } from '../../lib/teact/teact';
import React, {
useCallback, memo, useEffect, useMemo, useLayoutEffect,
memo, useEffect, useMemo, useLayoutEffect,
} from '../../lib/teact/teact';
import type { BufferedRange } from '../../hooks/useBuffering';
import type { ApiDimensions } from '../../api/types';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import useFlag from '../../hooks/useFlag';
import useAppLayout from '../../hooks/useAppLayout';
@ -46,7 +47,7 @@ type OwnProps = {
playbackRate: number;
posterSize?: ApiDimensions;
onChangeFullscreen: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
onPictureInPictureChange?: () => void ;
onPictureInPictureChange?: () => void;
onVolumeClick: () => void;
onVolumeChange: (volume: number) => void;
onPlaybackRateChange: (playbackRate: number) => void;
@ -136,14 +137,14 @@ const VideoPlayerControls: FC<OwnProps> = ({
const lang = useLang();
const handleSeek = useCallback((position: number) => {
const handleSeek = useLastCallback((position: number) => {
setIsSeeking(false);
onSeek(position);
}, [onSeek, setIsSeeking]);
});
const handleSeekStart = useCallback(() => {
const handleSeekStart = useLastCallback(() => {
setIsSeeking(true);
}, [setIsSeeking]);
});
const volumeIcon = useMemo(() => {
if (volume === 0 || isMuted) return 'icon-muted';
@ -265,4 +266,5 @@ function renderTime(currentTime: number, duration: number) {
</div>
);
}
export default memo(VideoPlayerControls);

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useMemo, useRef,
memo, useEffect, useMemo, useRef,
} from '../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../global';
@ -24,6 +24,8 @@ import { getMessageHtmlId, isChatChannel } from '../../global/helpers';
import buildClassName from '../../util/buildClassName';
import { renderActionMessageText } from '../common/helpers/renderActionMessageText';
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
import useLastCallback from '../../hooks/useLastCallback';
import useEnsureMessage from '../../hooks/useEnsureMessage';
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
@ -149,7 +151,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
: undefined;
}, [targetUserIds, usersById]);
const renderContent = useCallback(() => {
const renderContent = useLastCallback(() => {
return renderActionMessageText(
lang,
message,
@ -163,11 +165,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
observeIntersectionForLoading,
observeIntersectionForPlaying,
);
},
[
isEmbedded, lang, message, observeIntersectionForLoading, observeIntersectionForPlaying, senderChat,
senderUser, targetChatId, targetMessage, targetUsers, topic,
]);
});
const {
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 type { FC } from '../../lib/teact/teact';
@ -9,6 +9,8 @@ import { ApiMediaFormat, MAIN_THREAD_ID } from '../../api/types';
import { getMessageMediaHash } from '../../global/helpers';
import * as mediaLoader from '../../util/mediaLoader';
import useLastCallback from '../../hooks/useLastCallback';
import useMedia from '../../hooks/useMedia';
import useLang from '../../hooks/useLang';
import useFlag from '../../hooks/useFlag';
@ -38,7 +40,7 @@ const ActionMessageSuggestedAvatar: FC<OwnProps> = ({
const suggestedPhotoUrl = useMedia(getMessageMediaHash(message, 'full'));
const isVideo = message.content.action!.photo?.isVideo;
const showAvatarNotification = useCallback(() => {
const showAvatarNotification = useLastCallback(() => {
showNotification({
title: lang('ApplyAvatarHintTitle'),
message: lang('ApplyAvatarHint'),
@ -50,19 +52,19 @@ const ActionMessageSuggestedAvatar: FC<OwnProps> = ({
},
actionText: lang('Open'),
});
}, [lang, showNotification]);
});
const handleSetSuggestedAvatar = useCallback((file: File) => {
const handleSetSuggestedAvatar = useLastCallback((file: File) => {
setCropModalBlob(undefined);
uploadProfilePhoto({ file });
showAvatarNotification();
}, [showAvatarNotification, uploadProfilePhoto]);
});
const handleCloseCropModal = useCallback(() => {
const handleCloseCropModal = useLastCallback(() => {
setCropModalBlob(undefined);
}, []);
});
const handleSetVideo = useCallback(async () => {
const handleSetVideo = useLastCallback(async () => {
closeVideoModal();
showAvatarNotification();
@ -75,7 +77,7 @@ const ActionMessageSuggestedAvatar: FC<OwnProps> = ({
isVideo: true,
videoTs: photo.videoSizes?.find((l) => l.videoStartTs !== undefined)?.videoStartTs,
});
}, [closeVideoModal, message.content.action, showAvatarNotification, uploadProfilePhoto]);
});
const handleViewSuggestedAvatar = async () => {
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 type { FC } from '../../lib/teact/teact';
@ -33,6 +33,7 @@ import DropdownMenu from '../ui/DropdownMenu';
import MenuItem from '../ui/MenuItem';
import './AudioPlayer.scss';
import useLastCallback from '../../hooks/useLastCallback';
type OwnProps = {
message: ApiMessage;
@ -124,39 +125,39 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
handleContextMenuClose, handleContextMenuHide,
} = useContextMenuHandlers(ref);
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
focusMessage({ chatId: message.chatId, messageId: message.id });
}, [focusMessage, message.chatId, message.id]);
});
const handleClose = useCallback(() => {
const handleClose = useLastCallback(() => {
if (isPlaying) {
playPause();
}
closeAudioPlayer();
clearMediaSession();
stop();
}, [closeAudioPlayer, isPlaying, playPause, stop]);
});
const handleVolumeChange = useCallback((value: number) => {
const handleVolumeChange = useLastCallback((value: number) => {
setAudioPlayerVolume({ volume: value / 100 });
setVolume(value / 100);
}, [setAudioPlayerVolume, setVolume]);
});
const handleVolumeClick = useCallback(() => {
const handleVolumeClick = useLastCallback(() => {
if (IS_TOUCH_ENV && !IS_IOS) return;
toggleMuted();
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 shouldBeActive = newRate !== REGULAR_PLAYBACK_RATE && isActive;
setAudioPlayerPlaybackRate({ playbackRate: rate, isPlaybackRateActive: shouldBeActive });
setPlaybackRate(shouldBeActive ? rate : REGULAR_PLAYBACK_RATE);
}, [setAudioPlayerPlaybackRate, setPlaybackRate]);
});
const handlePlaybackClick = useCallback(() => {
const handlePlaybackClick = useLastCallback(() => {
handleContextMenuClose();
const oldRate = Number(Object.entries(PLAYBACK_RATES).find(([, rate]) => rate === playbackRate)?.[0])
|| REGULAR_PLAYBACK_RATE;
@ -166,9 +167,9 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
newIsActive && oldRate === REGULAR_PLAYBACK_RATE ? DEFAULT_FAST_PLAYBACK_RATE : oldRate,
newIsActive,
);
}, [handleContextMenuClose, isPlaybackRateActive, playbackRate, updatePlaybackRate]);
});
const PlaybackRateButton = useCallback(() => {
const PlaybackRateButton = useLastCallback(() => {
const displayRate = Object.entries(PLAYBACK_RATES).find(([, rate]) => rate === playbackRate)?.[0]
|| REGULAR_PLAYBACK_RATE;
const text = `${playbackRate === REGULAR_PLAYBACK_RATE ? DEFAULT_FAST_PLAYBACK_RATE : displayRate}Х`;
@ -201,10 +202,7 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
</Button>
</div>
);
}, [
handleBeforeContextMenu, handleContextMenu, handleContextMenuClose, handlePlaybackClick, isContextMenuOpen,
isMobile, isPlaybackRateActive, playbackRate,
]);
});
const volumeIcon = useMemo(() => {
if (volume === 0 || isMuted) return 'icon-muted';

View File

@ -1,5 +1,5 @@
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 type { ApiChat, ApiChatSettings, ApiUser } from '../../api/types';
@ -9,6 +9,8 @@ import {
getChatTitle, getUserFirstOrLastName, getUserFullName, isChatBasicGroup, isUserId,
} from '../../global/helpers';
import buildClassName from '../../util/buildClassName';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import useFlag from '../../hooks/useFlag';
@ -55,14 +57,14 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
} = settings || {};
const isBasicGroup = chat && isChatBasicGroup(chat);
const handleAddContact = useCallback(() => {
const handleAddContact = useLastCallback(() => {
openAddContactDialog({ userId: chatId });
if (isAutoArchived) {
toggleChatArchived({ id: chatId });
}
}, [openAddContactDialog, isAutoArchived, toggleChatArchived, chatId]);
});
const handleConfirmBlock = useCallback(() => {
const handleConfirmBlock = useLastCallback(() => {
closeBlockUserModal();
blockContact({ contactId: chatId, accessHash: accessHash! });
if (canReportSpam && shouldReportSpam) {
@ -71,16 +73,13 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
if (shouldDeleteChat) {
deleteChat({ chatId });
}
}, [
accessHash, blockContact, closeBlockUserModal, deleteChat, reportSpam, canReportSpam, shouldDeleteChat,
shouldReportSpam, chatId,
]);
});
const handleCloseReportPanel = useCallback(() => {
const handleCloseReportPanel = useLastCallback(() => {
hideChatReportPanel({ chatId });
}, [chatId, hideChatReportPanel]);
});
const handleChatReportSpam = useCallback(() => {
const handleChatReportSpam = useLastCallback(() => {
closeBlockUserModal();
reportSpam({ chatId });
if (isBasicGroup) {
@ -89,9 +88,7 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
} else {
leaveChannel({ chatId });
}
}, [
chatId, closeBlockUserModal, currentUserId, deleteChatUser, deleteHistory, isBasicGroup, leaveChannel, reportSpam,
]);
});
if (!settings) {
return undefined;

View File

@ -1,16 +1,16 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useRef,
} from '../../lib/teact/teact';
import React, { memo, useEffect, useRef } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { ApiSticker, ApiUpdateConnectionStateType } from '../../api/types';
import { selectChat } from '../../global/selectors';
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
import useLang from '../../hooks/useLang';
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 './ContactGreeting.scss';
@ -61,13 +61,13 @@ const ContactGreeting: FC<OwnProps & StateProps> = ({
}
}, [connectionState, markMessageListRead, lastUnreadMessageId]);
const handleStickerSelect = useCallback((selectedSticker: ApiSticker) => {
const handleStickerSelect = useLastCallback((selectedSticker: ApiSticker) => {
selectedSticker = {
...selectedSticker,
isPreloadedGlobally: true,
};
sendMessage({ sticker: selectedSticker });
}, [sendMessage]);
});
return (
<div className="ContactGreeting" ref={containerRef}>

View File

@ -1,5 +1,5 @@
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 {
@ -13,6 +13,8 @@ import {
isChatSuperGroup,
} from '../../global/helpers';
import renderText from '../common/helpers/renderText';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import usePrevious from '../../hooks/usePrevious';
@ -51,12 +53,12 @@ const DeleteSelectedMessageModal: FC<OwnProps & StateProps> = ({
const prevIsOpen = usePrevious(isOpen);
const handleDeleteMessageForAll = useCallback(() => {
const handleDeleteMessageForAll = useLastCallback(() => {
onClose();
deleteMessages({ messageIds: selectedMessageIds!, shouldDeleteForAll: true });
}, [deleteMessages, selectedMessageIds, onClose]);
});
const handleDeleteMessageForSelf = useCallback(() => {
const handleDeleteMessageForSelf = useLastCallback(() => {
if (isSchedule) {
deleteScheduledMessages({ messageIds: selectedMessageIds! });
} else {
@ -64,7 +66,7 @@ const DeleteSelectedMessageModal: FC<OwnProps & StateProps> = ({
}
onClose();
}, [isSchedule, onClose, deleteScheduledMessages, selectedMessageIds, deleteMessages]);
});
const lang = useLang();

View File

@ -1,20 +1,23 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useLayoutEffect, useRef,
memo, useEffect, useLayoutEffect, useRef,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { ActiveEmojiInteraction } from '../../global/types';
import { IS_ANDROID } from '../../util/windowEnvironment';
import useFlag from '../../hooks/useFlag';
import useMedia from '../../hooks/useMedia';
import buildClassName from '../../util/buildClassName';
import {
selectAnimatedEmojiEffect,
} from '../../global/selectors';
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 './EmojiInteractionAnimation.scss';
@ -41,7 +44,7 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
const [isPlaying, startPlaying] = useFlag(false);
const timeoutRef = useRef<NodeJS.Timeout>();
const stop = useCallback(() => {
const stop = useLastCallback(() => {
startHiding();
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
@ -49,13 +52,13 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
setTimeout(() => {
stopActiveEmojiInteraction({ id: activeEmojiInteraction.id });
}, HIDE_ANIMATION_DURATION);
}, [activeEmojiInteraction.id, startHiding, stopActiveEmojiInteraction]);
});
const handleCancelAnimation = useCallback((e: UIEvent) => {
const handleCancelAnimation = useLastCallback((e: UIEvent) => {
if (!(e.target as HTMLElement)?.closest('.AnimatedEmoji')) {
stop();
}
}, [stop]);
});
useEffect(() => {
document.addEventListener('touchstart', handleCancelAnimation);

View File

@ -1,7 +1,5 @@
import type { FC } from '../../lib/teact/teact';
import React, {
useCallback, memo, useRef, useEffect,
} from '../../lib/teact/teact';
import React, { memo, useRef, useEffect } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { MessageListType } from '../../global/types';
@ -11,6 +9,8 @@ import { selectChat, selectCurrentMessageList } from '../../global/selectors';
import buildClassName from '../../util/buildClassName';
import animateScroll from '../../util/animateScroll';
import useLastCallback from '../../hooks/useLastCallback';
import ScrollDownButton from './ScrollDownButton';
import styles from './FloatingActionButtons.module.scss';
@ -64,7 +64,7 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
}
}, [chatId, fetchUnreadMentions, hasUnreadMentions]);
const handleClick = useCallback(() => {
const handleClick = useLastCallback(() => {
if (!isShown) {
return;
}
@ -81,7 +81,7 @@ const FloatingActionButtons: FC<OwnProps & StateProps> = ({
animateScroll(messagesContainer, lastMessageElement, 'end', FOCUS_MARGIN);
}
}, [isShown, messageListType, focusNextReply]);
});
const fabClassName = buildClassName(
styles.root,

View File

@ -1,10 +1,5 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo,
useRef,
useCallback,
useState,
} from '../../lib/teact/teact';
import React, { memo, useRef, useState } from '../../lib/teact/teact';
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../global';
@ -28,6 +23,8 @@ import {
selectIsRightColumnShown,
selectIsUserBlocked,
} from '../../global/selectors';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import { useHotkeys } from '../../hooks/useHotkeys';
@ -108,42 +105,42 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [menuPosition, setMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
const handleHeaderMenuOpen = useCallback(() => {
const handleHeaderMenuOpen = useLastCallback(() => {
setIsMenuOpen(true);
const rect = menuButtonRef.current!.getBoundingClientRect();
setMenuPosition({ x: rect.right, y: rect.bottom });
}, []);
});
const handleHeaderMenuClose = useCallback(() => {
const handleHeaderMenuClose = useLastCallback(() => {
setIsMenuOpen(false);
}, []);
});
const handleHeaderMenuHide = useCallback(() => {
const handleHeaderMenuHide = useLastCallback(() => {
setMenuPosition(undefined);
}, []);
});
const handleSubscribeClick = useCallback(() => {
const handleSubscribeClick = useLastCallback(() => {
joinChannel({ chatId });
if (shouldSendJoinRequest) {
showNotification({
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
});
}
}, [joinChannel, chatId, shouldSendJoinRequest, showNotification, isChannel, lang]);
});
const handleStartBot = useCallback(() => {
const handleStartBot = useLastCallback(() => {
sendBotCommand({ command: '/start' });
}, [sendBotCommand]);
});
const handleRestartBot = useCallback(() => {
const handleRestartBot = useLastCallback(() => {
restartBot({ chatId });
}, [chatId, restartBot]);
});
const handleJoinRequestsClick = useCallback(() => {
const handleJoinRequestsClick = useLastCallback(() => {
requestNextManagementScreen({ screen: ManagementScreens.JoinRequests });
}, [requestNextManagementScreen]);
});
const handleSearchClick = useCallback(() => {
const handleSearchClick = useLastCallback(() => {
if (withForumActions) {
onTopicSearch?.();
return;
@ -163,24 +160,24 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
} else {
setTimeout(setFocusInSearchInput, SEARCH_FOCUS_DELAY_MS);
}
}, [isMobile, noAnimation, onTopicSearch, openLocalTextSearch, withForumActions]);
});
const handleAsMessagesClick = useCallback(() => {
const handleAsMessagesClick = useLastCallback(() => {
openChat({ id: chatId, threadId: MAIN_THREAD_ID });
}, [chatId, openChat]);
});
function handleRequestCall() {
requestMasterAndRequestCall({ userId: chatId });
}
const handleHotkeySearchClick = useCallback((e: KeyboardEvent) => {
const handleHotkeySearchClick = useLastCallback((e: KeyboardEvent) => {
if (!canSearch || !IS_APP || e.shiftKey) {
return;
}
e.preventDefault();
handleSearchClick();
}, [canSearch, handleSearchClick]);
});
useHotkeys({
'Mod+F': handleHotkeySearchClick,

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useMemo, useState,
memo, useEffect, useMemo, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
@ -33,6 +33,8 @@ import {
isUserRightBanned,
selectIsChatMuted,
} from '../../global/helpers';
import useLastCallback from '../../hooks/useLastCallback';
import useShowTransition from '../../hooks/useShowTransition';
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
import useLang from '../../hooks/useLang';
@ -181,76 +183,76 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
(!isChatInfoShown && isForum) ? true : undefined, CLOSE_MENU_ANIMATION_DURATION,
);
const handleReport = useCallback(() => {
const handleReport = useLastCallback(() => {
setIsMenuOpen(false);
setIsReportModalOpen(true);
}, []);
});
const closeReportModal = useCallback(() => {
const closeReportModal = useLastCallback(() => {
setIsReportModalOpen(false);
onClose();
}, [onClose]);
});
const closeMuteModal = useCallback(() => {
const closeMuteModal = useLastCallback(() => {
setIsMuteModalOpen(false);
onClose();
}, [onClose]);
});
const handleDelete = useCallback(() => {
const handleDelete = useLastCallback(() => {
setIsMenuOpen(false);
setIsDeleteModalOpen(true);
}, []);
});
const closeMenu = useCallback(() => {
const closeMenu = useLastCallback(() => {
setIsMenuOpen(false);
onClose();
}, [onClose]);
});
const handleViewGroupInfo = useCallback(() => {
const handleViewGroupInfo = useLastCallback(() => {
openChatWithInfo({ id: chatId, threadId });
closeMenu();
}, [chatId, closeMenu, openChatWithInfo, threadId]);
});
const closeDeleteModal = useCallback(() => {
const closeDeleteModal = useLastCallback(() => {
setIsDeleteModalOpen(false);
onClose();
}, [onClose]);
});
const handleStartBot = useCallback(() => {
const handleStartBot = useLastCallback(() => {
sendBotCommand({ command: '/start' });
}, [sendBotCommand]);
});
const handleRestartBot = useCallback(() => {
const handleRestartBot = useLastCallback(() => {
restartBot({ chatId });
}, [chatId, restartBot]);
});
const handleUnmuteClick = useCallback(() => {
const handleUnmuteClick = useLastCallback(() => {
updateChatMutedState({ chatId, isMuted: false });
closeMenu();
}, [chatId, closeMenu, updateChatMutedState]);
});
const handleMuteClick = useCallback(() => {
const handleMuteClick = useLastCallback(() => {
markRenderMuteModal();
setIsMuteModalOpen(true);
setIsMenuOpen(false);
}, []);
});
const handleCreateTopicClick = useCallback(() => {
const handleCreateTopicClick = useLastCallback(() => {
openCreateTopicPanel({ chatId });
closeMenu();
}, [openCreateTopicPanel, chatId, closeMenu]);
});
const handleEditTopicClick = useCallback(() => {
const handleEditTopicClick = useLastCallback(() => {
openEditTopicPanel({ chatId, topicId: threadId });
closeMenu();
}, [openEditTopicPanel, chatId, threadId, closeMenu]);
});
const handleViewAsTopicsClick = useCallback(() => {
const handleViewAsTopicsClick = useLastCallback(() => {
openChat({ id: undefined });
closeMenu();
}, [closeMenu, openChat]);
});
const handleEnterVoiceChatClick = useCallback(() => {
const handleEnterVoiceChatClick = useLastCallback(() => {
if (canCreateVoiceChat) {
// TODO Show popup to schedule
createGroupCall({
@ -262,57 +264,57 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
});
}
closeMenu();
}, [closeMenu, canCreateVoiceChat, chatId, requestMasterAndJoinGroupCall, createGroupCall]);
});
const handleLinkedChatClick = useCallback(() => {
const handleLinkedChatClick = useLastCallback(() => {
openLinkedChat({ id: chatId });
closeMenu();
}, [chatId, closeMenu, openLinkedChat]);
});
const handleGiftPremiumClick = useCallback(() => {
const handleGiftPremiumClick = useLastCallback(() => {
openGiftPremiumModal({ forUserId: chatId });
closeMenu();
}, [openGiftPremiumModal, chatId, closeMenu]);
});
const handleAddContactClick = useCallback(() => {
const handleAddContactClick = useLastCallback(() => {
openAddContactDialog({ userId: chatId });
closeMenu();
}, [openAddContactDialog, chatId, closeMenu]);
});
const handleSubscribe = useCallback(() => {
const handleSubscribe = useLastCallback(() => {
onSubscribeChannel();
closeMenu();
}, [closeMenu, onSubscribeChannel]);
});
const handleVideoCall = useCallback(() => {
const handleVideoCall = useLastCallback(() => {
requestMasterAndRequestCall({ userId: chatId, isVideo: true });
closeMenu();
}, [chatId, closeMenu, requestMasterAndRequestCall]);
});
const handleCall = useCallback(() => {
const handleCall = useLastCallback(() => {
requestMasterAndRequestCall({ userId: chatId });
closeMenu();
}, [chatId, closeMenu, requestMasterAndRequestCall]);
});
const handleSearch = useCallback(() => {
const handleSearch = useLastCallback(() => {
onSearchClick();
closeMenu();
}, [closeMenu, onSearchClick]);
});
const handleStatisticsClick = useCallback(() => {
const handleStatisticsClick = useLastCallback(() => {
toggleStatistics();
closeMenu();
}, [closeMenu, toggleStatistics]);
});
const handleSelectMessages = useCallback(() => {
const handleSelectMessages = useLastCallback(() => {
enterMessageSelectMode();
closeMenu();
}, [closeMenu, enterMessageSelectMode]);
});
const handleOpenAsMessages = useCallback(() => {
const handleOpenAsMessages = useLastCallback(() => {
onAsMessagesClick();
closeMenu();
}, [closeMenu, onAsMessagesClick]);
});
useEffect(() => {
disableScrolling();

View File

@ -1,5 +1,5 @@
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 type { ApiMessage } from '../../api/types';
@ -13,6 +13,7 @@ import buildClassName from '../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText';
import useLastCallback from '../../hooks/useLastCallback';
import useMedia from '../../hooks/useMedia';
import useThumbnail from '../../hooks/useThumbnail';
import useFlag from '../../hooks/useFlag';
@ -61,21 +62,21 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
const [isUnpinDialogOpen, openUnpinDialog, closeUnpinDialog] = useFlag();
const handleUnpinMessage = useCallback(() => {
const handleUnpinMessage = useLastCallback(() => {
closeUnpinDialog();
if (onUnpinMessage) {
onUnpinMessage(message.id);
}
}, [closeUnpinDialog, onUnpinMessage, message.id]);
});
const inlineButton = getMessageSingleInlineButton(message);
const handleInlineButtonClick = useCallback(() => {
const handleInlineButtonClick = useLastCallback(() => {
if (inlineButton) {
clickBotInlineButton({ messageId: message.id, button: inlineButton });
}
}, [clickBotInlineButton, inlineButton, message.id]);
});
const [noHoverColor, markNoHoverColor, unmarkNoHoverColor] = useFlag();

View File

@ -1,5 +1,5 @@
import React, {
memo, useCallback, useEffect, useMemo, useState,
memo, useEffect, useMemo, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
@ -10,6 +10,7 @@ import { SUPPORTED_TRANSLATION_LANGUAGES } from '../../config';
import buildClassName from '../../util/buildClassName';
import renderText from '../common/helpers/renderText';
import useLastCallback from '../../hooks/useLastCallback';
import useLang from '../../hooks/useLang';
import Modal from '../ui/Modal';
@ -47,16 +48,16 @@ const MessageLanguageModal: FC<OwnProps & StateProps> = ({
const [search, setSearch] = useState('');
const lang = useLang();
const handleSelect = useCallback((toLanguageCode: string) => {
const handleSelect = useLastCallback((toLanguageCode: string) => {
if (!chatId || !messageId) return;
requestMessageTranslation({ chatId, id: messageId, toLanguageCode });
closeMessageLanguageModal();
}, [chatId, closeMessageLanguageModal, messageId, requestMessageTranslation]);
});
const handleSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const handleSearch = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value);
}, []);
});
const translateLanguages = useMemo(() => SUPPORTED_TRANSLATION_LANGUAGES.map((langCode: string) => {
const translatedNames = new Intl.DisplayNames([currentLanguageCode], { type: 'language' });

View File

@ -1,6 +1,5 @@
import React, {
memo,
useCallback,
useEffect,
useMemo,
useRef,
@ -69,6 +68,7 @@ import resetScroll from '../../util/resetScroll';
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
import renderText from '../common/helpers/renderText';
import useLastCallback from '../../hooks/useLastCallback';
import { useStateRef } from '../../hooks/useStateRef';
import useSyncEffect from '../../hooks/useSyncEffect';
import useStickyDates from './hooks/useStickyDates';
@ -321,7 +321,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
const { isScrolled, updateStickyDates } = useStickyDates();
const handleScroll = useCallback(() => {
const handleScroll = useLastCallback(() => {
if (isScrollTopJustUpdatedRef.current) {
isScrollTopJustUpdatedRef.current = false;
return;
@ -353,9 +353,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
setScrollOffset({ chatId, threadId, scrollOffset: scrollOffsetRef.current });
}
});
}, [
updateStickyDates, hasTools, getForceNextPinnedInHeader, onPinnedIntersectionChange, type, chatId, threadId,
]);
});
const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive);

View File

@ -1,5 +1,5 @@
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 type { MessageListType } from '../../global/types';
@ -16,6 +16,7 @@ import {
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
import buildClassName from '../../util/buildClassName';
import useLastCallback from '../../hooks/useLastCallback';
import useFlag from '../../hooks/useFlag';
import usePrevious from '../../hooks/usePrevious';
import useLang from '../../hooks/useLang';
@ -73,9 +74,9 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
useCopySelectedMessages(isActive);
const handleExitMessageSelectMode = useCallback(() => {
const handleExitMessageSelectMode = useLastCallback(() => {
exitMessageSelectMode();
}, [exitMessageSelectMode]);
});
useEffect(() => {
return isActive && !isDeleteModalOpen && !isReportModalOpen && !isAnyModalOpen
@ -90,18 +91,18 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
canDeleteMessages,
]);
const handleCopy = useCallback(() => {
const handleCopy = useLastCallback(() => {
copySelectedMessages();
showNotification({
message: lang('Share.Link.Copied'),
});
exitMessageSelectMode();
}, [copySelectedMessages, exitMessageSelectMode, lang, showNotification]);
});
const handleDownload = useCallback(() => {
const handleDownload = useLastCallback(() => {
downloadSelectedMessages();
exitMessageSelectMode();
}, [downloadSelectedMessages, exitMessageSelectMode]);
});
const prevSelectedMessagesCount = usePrevious(selectedMessagesCount || undefined, true);
const renderingSelectedMessagesCount = isActive ? selectedMessagesCount : prevSelectedMessagesCount;

View File

@ -1,6 +1,6 @@
import type { RefObject } from 'react';
import React, {
useEffect, useState, memo, useMemo, useCallback,
useEffect, useState, memo, useMemo,
} from '../../lib/teact/teact';
import { requestMutation } from '../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../global';
@ -59,6 +59,8 @@ import {
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
import captureEscKeyListener from '../../util/captureEscKeyListener';
import buildClassName from '../../util/buildClassName';
import useLastCallback from '../../hooks/useLastCallback';
import useCustomBackground from '../../hooks/useCustomBackground';
import useWindowSize from '../../hooks/useWindowSize';
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
@ -307,7 +309,7 @@ function MiddleColumn({
return () => {
visualViewport.removeEventListener('resize', handleResize);
};
}, []);
});
useEffect(() => {
if (isPrivate) {
@ -333,7 +335,7 @@ function MiddleColumn({
leftColumnWidth: n,
}), resetLeftColumnWidth, leftColumnWidth, '--left-column-width');
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
const handleDragEnter = useLastCallback((e: React.DragEvent<HTMLDivElement>) => {
const { items } = e.dataTransfer || {};
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)
@ -343,46 +345,46 @@ function MiddleColumn({
.every(isImage);
setDropAreaState(shouldDrawQuick ? DropAreaState.QuickFile : DropAreaState.Document);
}, []);
});
const handleHideDropArea = useCallback(() => {
const handleHideDropArea = useLastCallback(() => {
setDropAreaState(DropAreaState.None);
}, []);
});
const handleOpenUnpinModal = useCallback(() => {
const handleOpenUnpinModal = useLastCallback(() => {
setIsUnpinModalOpen(true);
}, []);
});
const closeUnpinModal = useCallback(() => {
const closeUnpinModal = useLastCallback(() => {
setIsUnpinModalOpen(false);
}, []);
});
const handleUnpinAllMessages = useCallback(() => {
const handleUnpinAllMessages = useLastCallback(() => {
unpinAllMessages({ chatId: chatId!, threadId: threadId! });
closeUnpinModal();
openPreviousChat();
}, [unpinAllMessages, chatId, threadId, closeUnpinModal, openPreviousChat]);
});
const handleTabletFocus = useCallback(() => {
const handleTabletFocus = useLastCallback(() => {
openChat({ id: chatId });
}, [openChat, chatId]);
});
const handleSubscribeClick = useCallback(() => {
const handleSubscribeClick = useLastCallback(() => {
joinChannel({ chatId: chatId! });
if (renderingShouldSendJoinRequest) {
showNotification({
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
});
}
}, [joinChannel, chatId, renderingShouldSendJoinRequest, showNotification, isChannel, lang]);
});
const handleStartBot = useCallback(() => {
const handleStartBot = useLastCallback(() => {
sendBotCommand({ command: '/start' });
}, [sendBotCommand]);
});
const handleRestartBot = useCallback(() => {
const handleRestartBot = useLastCallback(() => {
restartBot({ chatId: chatId! });
}, [chatId, restartBot]);
});
const customBackgroundValue = useCustomBackground(theme, customBackground);

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useLayoutEffect, useRef,
memo, useEffect, useLayoutEffect, useRef,
} from '../../lib/teact/teact';
import { requestMutation } from '../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../global';
@ -44,6 +44,9 @@ import {
selectThreadTopMessageId,
} from '../../global/selectors';
import cycleRestrict from '../../util/cycleRestrict';
import useLastCallback from '../../hooks/useLastCallback';
import { useFastClick } from '../../hooks/useFastClick';
import useEnsureMessage from '../../hooks/useEnsureMessage';
import useWindowSize from '../../hooks/useWindowSize';
import useShowTransition from '../../hooks/useShowTransition';
@ -68,7 +71,6 @@ import GroupCallTopPane from '../calls/group/GroupCallTopPane';
import ChatReportPanel from './ChatReportPanel';
import './MiddleHeader.scss';
import { useFastClick } from '../../hooks/useFastClick';
const ANIMATION_DURATION = 350;
const BACK_BUTTON_INACTIVE_TIME = 450;
@ -184,11 +186,11 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
openChatWithInfo({ id: chatId, threadId });
});
const handleUnpinMessage = useCallback((messageId: number) => {
const handleUnpinMessage = useLastCallback((messageId: number) => {
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)
? pinnedMessageIds[cycleRestrict(pinnedMessageIds.length, pinnedMessageIds.indexOf(pinnedMessageId!) - 2)]
: pinnedMessageId!;
@ -198,19 +200,19 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
chatId, threadId, messageId, noForumTopicPanel: true,
});
}
}, [pinnedMessageIds, pinnedMessageId, onFocusPinnedMessage, chatId, threadId]);
});
const handleAllPinnedClick = useCallback(() => {
const handleAllPinnedClick = useLastCallback(() => {
openChat({ id: chatId, threadId, type: 'pinned' });
}, [openChat, chatId, threadId]);
});
const setBackButtonActive = useCallback(() => {
const setBackButtonActive = useLastCallback(() => {
setTimeout(() => {
isBackButtonActive.current = true;
}, BACK_BUTTON_INACTIVE_TIME);
}, []);
});
const handleBackClick = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
const handleBackClick = useLastCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
if (!isBackButtonActive.current) return;
// Workaround for missing UI when quickly clicking the Back button
@ -241,10 +243,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
openPreviousChat();
setBackButtonActive();
}, [
isMobile, isSelectModeActive, messageListType, currentTransitionKey, setBackButtonActive, isTablet,
shouldShowCloseButton,
]);
});
const canToolsCollideWithChatInfo = (
windowWidth >= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useRef, useState, useLayoutEffect,
memo, useEffect, useRef, useState, useLayoutEffect,
} from '../../lib/teact/teact';
import { requestMutation } from '../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../global';
@ -17,6 +17,8 @@ import {
} from '../../global/selectors';
import { getDayStartAt } from '../../util/dateFormat';
import useLastCallback from '../../hooks/useLastCallback';
import Button from '../ui/Button';
import SearchInput from '../ui/SearchInput';
@ -123,33 +125,33 @@ const MobileSearchFooter: FC<StateProps> = ({
searchInput.blur();
}, [isHistoryCalendarOpen]);
const handleMessageSearchQueryChange = useCallback((newQuery: string) => {
const handleMessageSearchQueryChange = useLastCallback((newQuery: string) => {
setLocalTextSearchQuery({ query: newQuery });
if (newQuery.length) {
runDebouncedForSearch(searchTextMessagesLocal);
}
}, [searchTextMessagesLocal, setLocalTextSearchQuery]);
});
const handleUp = useCallback(() => {
const handleUp = useLastCallback(() => {
if (chat && foundIds) {
const newFocusIndex = focusedIndex + 1;
focusMessage({ chatId: chat.id, messageId: foundIds[newFocusIndex], threadId });
setFocusedIndex(newFocusIndex);
}
}, [chat, foundIds, focusedIndex, threadId]);
});
const handleDown = useCallback(() => {
const handleDown = useLastCallback(() => {
if (chat && foundIds) {
const newFocusIndex = focusedIndex - 1;
focusMessage({ chatId: chat.id, messageId: foundIds[newFocusIndex], threadId });
setFocusedIndex(newFocusIndex);
}
}, [chat, foundIds, focusedIndex, threadId]);
});
const handleCloseLocalTextSearch = useCallback(() => {
const handleCloseLocalTextSearch = useLastCallback(() => {
closeLocalTextSearch();
}, [closeLocalTextSearch]);
});
return (
<div id="MobileSearch" className={isActive ? 'active' : ''}>

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact';
import React, {
useCallback, memo, useMemo, useEffect, useState, useRef,
memo, useMemo, useEffect, useState, useRef,
} from '../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../global';
@ -32,6 +32,7 @@ import FullNameTitle from '../common/FullNameTitle';
import PrivateChatInfo from '../common/PrivateChatInfo';
import './ReactorListModal.scss';
import useLastCallback from '../../hooks/useLastCallback';
const MIN_REACTIONS_COUNT_FOR_FILTERS = 10;
@ -81,28 +82,28 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
}
}, [isClosing, isOpen, stopClosing]);
const handleCloseAnimationEnd = useCallback(() => {
const handleCloseAnimationEnd = useLastCallback(() => {
if (chatIdRef.current) {
openChat({ id: chatIdRef.current });
}
closeReactorListModal();
}, [closeReactorListModal, openChat]);
});
const handleClose = useCallback(() => {
const handleClose = useLastCallback(() => {
startClosing();
}, [startClosing]);
});
const handleClick = useCallback((userId: string) => {
const handleClick = useLastCallback((userId: string) => {
chatIdRef.current = userId;
handleClose();
}, [handleClose]);
});
const handleLoadMore = useCallback(() => {
const handleLoadMore = useLastCallback(() => {
loadReactors({
chatId: chatId!,
messageId: messageId!,
});
}, [chatId, loadReactors, messageId]);
});
const allReactions = useMemo(() => {
const uniqueReactions: ApiReaction[] = [];

View File

@ -1,12 +1,13 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useMemo, useState,
memo, useMemo, useState,
} from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { IAnchorPosition, ISettings } from '../../../types';
import type { ApiAttachBot } from '../../../api/types';
import useLastCallback from '../../../hooks/useLastCallback';
import useFlag from '../../../hooks/useFlag';
import useLang from '../../../hooks/useLang';
@ -43,29 +44,29 @@ const AttachBotItem: FC<OwnProps> = ({
const [isMenuOpen, openMenu, closeMenu] = useFlag();
const [menuPosition, setMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
const handleContextMenu = useCallback((e: React.UIEvent) => {
const handleContextMenu = useLastCallback((e: React.UIEvent) => {
e.preventDefault();
const rect = e.currentTarget.getBoundingClientRect();
setMenuPosition({ x: rect.right, y: rect.bottom });
onMenuOpened();
openMenu();
}, [onMenuOpened, openMenu]);
});
const handleCloseMenu = useCallback(() => {
const handleCloseMenu = useLastCallback(() => {
closeMenu();
onMenuClosed();
}, [closeMenu, onMenuClosed]);
});
const handleCloseAnimationEnd = useCallback(() => {
const handleCloseAnimationEnd = useLastCallback(() => {
setMenuPosition(undefined);
}, []);
});
const handleRemoveBot = useCallback(() => {
const handleRemoveBot = useLastCallback(() => {
toggleAttachBot({
botId: bot.id,
isEnabled: false,
});
}, [bot.id, toggleAttachBot]);
});
return (
<MenuItem

View File

@ -1,5 +1,5 @@
import React, {
memo, useMemo, useCallback, useEffect,
memo, useMemo, useEffect,
} 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 { validateFiles } from '../../../util/files';
import useLastCallback from '../../../hooks/useLastCallback';
import useMouseInside from '../../../hooks/useMouseInside';
import useLang from '../../../hooks/useLang';
import useFlag from '../../../hooks/useFlag';
@ -75,38 +76,38 @@ const AttachMenu: FC<OwnProps> = ({
}
}, [isAttachMenuOpen, markMouseInside]);
const handleToggleAttachMenu = useCallback(() => {
const handleToggleAttachMenu = useLastCallback(() => {
if (isAttachMenuOpen) {
closeAttachMenu();
} else {
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 validatedFiles = validateFiles(files);
if (validatedFiles?.length) {
onFileSelect(validatedFiles, shouldSuggestCompression);
}
}, [onFileSelect]);
});
const handleQuickSelect = useCallback(() => {
const handleQuickSelect = useLastCallback(() => {
openSystemFilesDialog(
Array.from(canSendVideoAndPhoto ? CONTENT_TYPES_WITH_PREVIEW : (
canSendPhotos ? SUPPORTED_IMAGE_CONTENT_TYPES : SUPPORTED_VIDEO_CONTENT_TYPES
)).join(','),
(e) => handleFileSelect(e, true),
);
}, [canSendPhotos, canSendVideoAndPhoto, handleFileSelect]);
});
const handleDocumentSelect = useCallback(() => {
const handleDocumentSelect = useLastCallback(() => {
openSystemFilesDialog(!canSendDocuments && canSendAudios
? Array.from(SUPPORTED_AUDIO_CONTENT_TYPES).join(',') : (
'*'
), (e) => handleFileSelect(e, false));
}, [canSendAudios, canSendDocuments, handleFileSelect]);
});
const bots = useMemo(() => {
return Object.values(attachBots).filter((bot) => {

View File

@ -1,5 +1,5 @@
import React, {
memo, useCallback, useEffect, useMemo, useRef, useState,
memo, useEffect, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../../global';
@ -30,6 +30,7 @@ import buildClassName from '../../../util/buildClassName';
import { validateFiles } from '../../../util/files';
import { removeAllSelections } from '../../../util/selection';
import useLastCallback from '../../../hooks/useLastCallback';
import usePrevious from '../../../hooks/usePrevious';
import useMentionTooltip from './hooks/useMentionTooltip';
import useEmojiTooltip from './hooks/useEmojiTooltip';
@ -253,7 +254,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
handleContextMenuHide,
} = useContextMenuHandlers(mainButtonRef, !canShowCustomSendMenu || !isOpen);
const sendAttachments = useCallback((isSilent?: boolean, shouldSendScheduled?: boolean) => {
const sendAttachments = useLastCallback((isSilent?: boolean, shouldSendScheduled?: boolean) => {
if (isOpen) {
const send = (shouldSchedule || shouldSendScheduled) ? onSendScheduled
: isSilent ? onSendSilent : onSend;
@ -263,22 +264,19 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
shouldSendGrouped,
});
}
}, [
isOpen, shouldSchedule, onSendScheduled, onSendSilent, onSend, isSendingCompressed, shouldSendGrouped,
updateAttachmentSettings,
]);
});
const handleSendSilent = useCallback(() => {
const handleSendSilent = useLastCallback(() => {
sendAttachments(true);
}, [sendAttachments]);
});
const handleSendClick = useCallback(() => {
const handleSendClick = useLastCallback(() => {
sendAttachments();
}, [sendAttachments]);
});
const handleScheduleClick = useCallback(() => {
const handleScheduleClick = useLastCallback(() => {
sendAttachments(false, true);
}, [sendAttachments]);
});
const handleDragLeave = (e: React.DragEvent<HTMLElement>) => {
const { relatedTarget: toTarget, target: fromTarget } = e;
@ -300,7 +298,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
unmarkHovered();
};
const handleFilesDrop = useCallback(async (e: React.DragEvent<HTMLDivElement>) => {
const handleFilesDrop = useLastCallback(async (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
unmarkHovered();
@ -310,7 +308,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
if (files?.length) {
onFileAppend(files, isEverySpoiler);
}
}, [isEverySpoiler, onFileAppend, unmarkHovered]);
});
function handleDragOver(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
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 validatedFiles = validateFiles(files);
if (validatedFiles?.length) {
onFileAppend(validatedFiles, isEverySpoiler);
}
}, [isEverySpoiler, onFileAppend]);
});
const handleDocumentSelect = useCallback(() => {
const handleDocumentSelect = useLastCallback(() => {
openSystemFilesDialog('*', (e) => handleFileSelect(e));
}, [handleFileSelect]);
});
const handleDelete = useCallback((index: number) => {
const handleDelete = useLastCallback((index: number) => {
onAttachmentsUpdate(attachments.filter((a, i) => i !== index));
}, [attachments, onAttachmentsUpdate]);
});
const handleEnableSpoilers = useCallback(() => {
const handleEnableSpoilers = useLastCallback(() => {
onAttachmentsUpdate(attachments.map((a) => ({
...a,
shouldSendAsSpoiler: a.mimeType !== GIF_MIME_TYPE ? true : undefined,
})));
}, [attachments, onAttachmentsUpdate]);
});
const handleDisableSpoilers = useCallback(() => {
const handleDisableSpoilers = useLastCallback(() => {
onAttachmentsUpdate(attachments.map((a) => ({ ...a, shouldSendAsSpoiler: undefined })));
}, [attachments, onAttachmentsUpdate]);
});
const handleToggleSpoiler = useCallback((index: number) => {
const handleToggleSpoiler = useLastCallback((index: number) => {
onAttachmentsUpdate(attachments.map((attachment, i) => {
if (i === index) {
return {
@ -360,7 +358,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
return attachment;
}));
}, [attachments, onAttachmentsUpdate]);
});
useEffect(() => {
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 { ApiAttachment } from '../../../api/types';
@ -9,6 +9,8 @@ import buildClassName from '../../../util/buildClassName';
import { formatMediaDuration } from '../../../util/dateFormat';
import { REM } from '../../common/helpers/mediaDimensions';
import useLastCallback from '../../../hooks/useLastCallback';
import File from '../../common/File';
import MediaSpoiler from '../../common/MediaSpoiler';
@ -39,9 +41,9 @@ const AttachmentModalItem: FC<OwnProps> = ({
}) => {
const displayType = getDisplayType(attachment, shouldDisplayCompressed);
const handleSpoilerClick = useCallback(() => {
const handleSpoilerClick = useLastCallback(() => {
onToggleSpoiler?.(index);
}, [index, onToggleSpoiler]);
});
const content = useMemo(() => {
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 type { FC } from '../../../lib/teact/teact';
import type { ApiBotCommand } from '../../../api/types';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useLastCallback from '../../../hooks/useLastCallback';
import useMouseInside from '../../../hooks/useMouseInside';
import useAppLayout from '../../../hooks/useAppLayout';
@ -27,12 +29,12 @@ const BotCommandMenu: FC<OwnProps> = ({
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, isMobile);
const handleClick = useCallback((botCommand: ApiBotCommand) => {
const handleClick = useLastCallback((botCommand: ApiBotCommand) => {
sendBotCommand({
command: `/${botCommand.command}`,
});
onClose();
}, [onClose, sendBotCommand]);
});
return (
<Menu

View File

@ -1,7 +1,5 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
useCallback, useEffect, useRef, memo,
} from '../../../lib/teact/teact';
import React, { useEffect, useRef, memo } from '../../../lib/teact/teact';
import { getActions, getGlobal } from '../../../global';
import type { Signal } from '../../../util/signals';
@ -9,6 +7,8 @@ import type { ApiBotCommand } from '../../../api/types';
import buildClassName from '../../../util/buildClassName';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
import useLastCallback from '../../../hooks/useLastCallback';
import useShowTransition from '../../../hooks/useShowTransition';
import usePrevious from '../../../hooks/usePrevious';
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
@ -40,7 +40,7 @@ const BotCommandTooltip: FC<OwnProps> = ({
const containerRef = useRef<HTMLDivElement>(null);
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
const usersById = getGlobal().users.byId;
const bot = usersById[botId];
@ -49,9 +49,9 @@ const BotCommandTooltip: FC<OwnProps> = ({
command: `/${command}${withUsername && bot ? `@${bot.usernames![0].username}` : ''}`,
});
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
if (!botCommand.command.startsWith(getHtml().slice(1))) {
return false;
@ -59,7 +59,7 @@ const BotCommandTooltip: FC<OwnProps> = ({
handleSendCommand(botCommand);
return true;
}, [getHtml, handleSendCommand]);
});
const selectedCommandIndex = useKeyboardNavigation({
isActive: isOpen,

View File

@ -1,6 +1,6 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState,
memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../../global';
@ -89,6 +89,7 @@ import { buildCustomEmojiHtml } from './helpers/customEmoji';
import { processMessageInputForCustomEmoji } from '../../../util/customEmojiManager';
import { getTextWithEntitiesAsHtml } from '../../common/helpers/renderTextWithEntities';
import useLastCallback from '../../../hooks/useLastCallback';
import useSignal from '../../../hooks/useSignal';
import useFlag from '../../../hooks/useFlag';
import usePrevious from '../../../hooks/usePrevious';
@ -335,12 +336,11 @@ const Composer: FC<OwnProps & StateProps> = ({
const customEmojiNotificationNumber = useRef(0);
const handleScheduleCancel = useCallback(() => {
cancelForceShowSymbolMenu();
}, [cancelForceShowSymbolMenu]);
const [requestCalendar, calendar] = useSchedule(canScheduleUntilOnline, handleScheduleCancel);
const [requestCalendar, calendar] = useSchedule(canScheduleUntilOnline, cancelForceShowSymbolMenu);
useTimeout(() => { setIsMounted(true); }, MOUNT_ANIMATION_DURATION);
useTimeout(() => {
setIsMounted(true);
}, MOUNT_ANIMATION_DURATION);
useEffect(() => {
lastMessageSendTimeSeconds.current = undefined;
@ -524,7 +524,7 @@ const Composer: FC<OwnProps & StateProps> = ({
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;
const selection = window.getSelection()!;
let messageInput: HTMLDivElement;
@ -549,22 +549,22 @@ const Composer: FC<OwnProps & StateProps> = ({
requestNextMutation(() => {
focusEditableElement(messageInput);
});
}, [isComposerBlocked, getHtml, setHtml]);
});
const insertFormattedTextAndUpdateCursor = useCallback((
const insertFormattedTextAndUpdateCursor = useLastCallback((
text: ApiFormattedText, inputId: string = EDITABLE_INPUT_ID,
) => {
const newHtml = getTextWithEntitiesAsHtml(text);
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]);
});
useDraft(draft, chatId, threadId, getHtml, setHtml, editingMessage, lastSyncTime);
const resetComposer = useCallback((shouldPreserveInput = false) => {
const resetComposer = useLastCallback((shouldPreserveInput = false) => {
if (!shouldPreserveInput) {
setHtml('');
}
@ -582,10 +582,7 @@ const Composer: FC<OwnProps & StateProps> = ({
} else {
closeSymbolMenu();
}
}, [
setHtml, isMobile, closeStickerTooltip, closeCustomEmojiTooltip, closeMentionTooltip, closeEmojiTooltip,
closeSymbolMenu,
]);
});
const [handleEditComplete, handleEditCancel, shouldForceShowEditing] = useEditing(
getHtml,
@ -613,7 +610,7 @@ const Composer: FC<OwnProps & StateProps> = ({
};
}, [chatId, threadId, resetComposerRef, stopRecordingVoiceRef]);
const showCustomEmojiPremiumNotification = useCallback(() => {
const showCustomEmojiPremiumNotification = useLastCallback(() => {
const notificationNumber = customEmojiNotificationNumber.current;
if (!notificationNumber) {
showNotification({
@ -635,7 +632,7 @@ const Composer: FC<OwnProps & StateProps> = ({
});
}
customEmojiNotificationNumber.current = Number(!notificationNumber);
}, [currentUserId, lang, showNotification]);
});
const mainButtonState = useDerivedState(() => {
if (editingMessage && shouldForceShowEditing) {
@ -672,13 +669,13 @@ const Composer: FC<OwnProps & StateProps> = ({
showCustomEmojiPremiumNotification,
);
const handleEmbeddedClear = useCallback(() => {
const handleEmbeddedClear = useLastCallback(() => {
if (editingMessage) {
handleEditCancel();
}
}, [editingMessage, handleEditCancel]);
});
const validateTextLength = useCallback((text: string, isAttachmentModal?: boolean) => {
const validateTextLength = useLastCallback((text: string, isAttachmentModal?: boolean) => {
const maxLength = isAttachmentModal ? captionLimit : MESSAGE_MAX_LENGTH;
if (text?.length > maxLength) {
const extraLength = text.length - maxLength;
@ -696,9 +693,9 @@ const Composer: FC<OwnProps & StateProps> = ({
return false;
}
return true;
}, [captionLimit, showDialog]);
});
const checkSlowMode = useCallback(() => {
const checkSlowMode = useLastCallback(() => {
if (slowMode && !isAdmin) {
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
@ -728,9 +725,9 @@ const Composer: FC<OwnProps & StateProps> = ({
}
}
return true;
}, [isAdmin, lang, showDialog, slowMode]);
});
const sendAttachments = useCallback(({
const sendAttachments = useLastCallback(({
attachments: attachmentsToSend,
sendCompressed = attachmentSettings.shouldCompress,
sendGrouped = attachmentSettings.shouldSendGrouped,
@ -772,12 +769,9 @@ const Composer: FC<OwnProps & StateProps> = ({
requestMeasure(() => {
resetComposer();
});
}, [
attachmentSettings.shouldCompress, attachmentSettings.shouldSendGrouped, connectionState, getHtml,
validateTextLength, checkSlowMode, sendMessage, clearDraft, chatId, resetComposer, shouldUpdateStickerSetOrder,
]);
});
const handleSendAttachments = useCallback((
const handleSendAttachments = useLastCallback((
sendCompressed: boolean,
sendGrouped: boolean,
isSilent?: boolean,
@ -790,9 +784,9 @@ const Composer: FC<OwnProps & StateProps> = ({
isSilent,
scheduledAt,
});
}, [attachments, sendAttachments]);
});
const handleSend = useCallback(async (isSilent = false, scheduledAt?: number) => {
const handleSend = useLastCallback(async (isSilent = false, scheduledAt?: number) => {
if (connectionState !== 'connectionStateReady') {
return;
}
@ -859,13 +853,9 @@ const Composer: FC<OwnProps & StateProps> = ({
requestMeasure(() => {
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') {
return;
}
@ -873,14 +863,14 @@ const Composer: FC<OwnProps & StateProps> = ({
callAttachBot({
chatId, url: botMenuButton.url, threadId,
});
}, [botMenuButton, callAttachBot, chatId, threadId]);
});
const handleActivateBotCommandMenu = useCallback(() => {
const handleActivateBotCommandMenu = useLastCallback(() => {
closeSymbolMenu();
openBotCommandMenu();
}, [closeSymbolMenu, openBotCommandMenu]);
});
const handleMessageSchedule = useCallback((
const handleMessageSchedule = useLastCallback((
args: ScheduledMessageArgs, scheduledAt: number,
) => {
if (args && 'queryId' in args) {
@ -907,7 +897,7 @@ const Composer: FC<OwnProps & StateProps> = ({
scheduledAt,
});
}
}, [handleSendAttachments, handleSend, sendInlineBotResult, sendMessage]);
});
useEffectWithPrevDeps(([prevContentToBeScheduled]) => {
if (contentToBeScheduled && contentToBeScheduled !== prevContentToBeScheduled) {
@ -936,20 +926,20 @@ const Composer: FC<OwnProps & StateProps> = ({
}
}, [handleFileSelect, requestedDraftFiles, resetOpenChatWithDraft]);
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker, inputId?: string) => {
const handleCustomEmojiSelect = useLastCallback((emoji: ApiSticker, inputId?: string) => {
if (!emoji.isFree && !isCurrentUserPremium && !isChatWithSelf) {
showCustomEmojiPremiumNotification();
return;
}
insertCustomEmojiAndUpdateCursor(emoji, inputId);
}, [insertCustomEmojiAndUpdateCursor, isChatWithSelf, isCurrentUserPremium, showCustomEmojiPremiumNotification]);
});
const handleCustomEmojiSelectAttachmentModal = useCallback((emoji: ApiSticker) => {
const handleCustomEmojiSelectAttachmentModal = useLastCallback((emoji: ApiSticker) => {
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) {
forceShowSymbolMenu();
requestCalendar((scheduledAt) => {
@ -965,12 +955,9 @@ const Composer: FC<OwnProps & StateProps> = ({
resetComposer(true);
});
}
}, [
shouldSchedule, forceShowSymbolMenu, requestCalendar, cancelForceShowSymbolMenu, handleMessageSchedule,
resetComposer, sendMessage,
]);
});
const handleStickerSelect = useCallback((
const handleStickerSelect = useLastCallback((
sticker: ApiSticker,
isSilent?: boolean,
isScheduleRequested?: boolean,
@ -1001,12 +988,9 @@ const Composer: FC<OwnProps & StateProps> = ({
resetComposer(shouldPreserveInput);
});
}
}, [
shouldSchedule, forceShowSymbolMenu, requestCalendar, cancelForceShowSymbolMenu, handleMessageSchedule,
resetComposer, sendMessage, shouldUpdateStickerSetOrder,
]);
});
const handleInlineBotSelect = useCallback((
const handleInlineBotSelect = useLastCallback((
inlineResult: ApiBotInlineResult | ApiBotInlineMediaResult, isSilent?: boolean, isScheduleRequested?: boolean,
) => {
if (connectionState !== 'connectionStateReady') {
@ -1038,19 +1022,16 @@ const Composer: FC<OwnProps & StateProps> = ({
requestMeasure(() => {
resetComposer();
});
}, [
chatId, clearDraft, connectionState, handleMessageSchedule, requestCalendar, resetComposer, sendInlineBotResult,
shouldSchedule,
]);
});
const handleBotCommandSelect = useCallback(() => {
const handleBotCommandSelect = useLastCallback(() => {
clearDraft({ chatId, localOnly: true });
requestMeasure(() => {
resetComposer();
});
}, [chatId, clearDraft, resetComposer]);
});
const handlePollSend = useCallback((poll: ApiNewPoll) => {
const handlePollSend = useLastCallback((poll: ApiNewPoll) => {
if (shouldSchedule) {
requestCalendar((scheduledAt) => {
handleMessageSchedule({ poll }, scheduledAt);
@ -1060,9 +1041,9 @@ const Composer: FC<OwnProps & StateProps> = ({
sendMessage({ poll });
closePollModal();
}
}, [closePollModal, handleMessageSchedule, requestCalendar, sendMessage, shouldSchedule]);
});
const sendSilent = useCallback((additionalArgs?: ScheduledMessageArgs) => {
const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => {
if (shouldSchedule) {
requestCalendar((scheduledAt) => {
handleMessageSchedule({ ...additionalArgs, isSilent: true }, scheduledAt);
@ -1073,9 +1054,9 @@ const Composer: FC<OwnProps & StateProps> = ({
} else {
void handleSend(true);
}
}, [handleMessageSchedule, handleSend, handleSendAttachments, requestCalendar, shouldSchedule]);
});
const handleSendAsMenuOpen = useCallback(() => {
const handleSendAsMenuOpen = useLastCallback(() => {
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
if (!isMobile || messageInput !== document.activeElement) {
@ -1091,14 +1072,14 @@ const Composer: FC<OwnProps & StateProps> = ({
closeSymbolMenu();
openSendAsMenu();
}, 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'])
.join('')
.replace(/\u200b+/g, '\u200b');
insertHtmlAndUpdateCursor(newHtml, inputId);
}, [insertHtmlAndUpdateCursor]);
});
useEffect(() => {
if (!isComposerBlocked) return;
@ -1106,11 +1087,11 @@ const Composer: FC<OwnProps & StateProps> = ({
setHtml('');
}, [isComposerBlocked, setHtml, attachments]);
const insertTextAndUpdateCursorAttachmentModal = useCallback((text: string) => {
const insertTextAndUpdateCursorAttachmentModal = useLastCallback((text: string) => {
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()!;
if (selection.rangeCount) {
@ -1122,17 +1103,17 @@ const Composer: FC<OwnProps & StateProps> = ({
}
setHtml(deleteLastCharacterOutsideSelection(getHtml()));
}, [getHtml, setHtml]);
});
const removeSymbolAttachmentModal = useCallback(() => {
const removeSymbolAttachmentModal = useLastCallback(() => {
removeSymbol(EDITABLE_INPUT_MODAL_ID);
}, [removeSymbol]);
});
const handleAllScheduledClick = useCallback(() => {
const handleAllScheduledClick = useLastCallback(() => {
openChat({
id: chatId, threadId, type: 'scheduled', noForumTopicPanel: true,
});
}, [openChat, chatId, threadId]);
});
useEffect(() => {
if (isRightColumnShown && isMobile) {
@ -1155,7 +1136,7 @@ const Composer: FC<OwnProps & StateProps> = ({
const areVoiceMessagesNotAllowed = mainButtonState === MainButtonState.Record
&& (!canAttachMedia || !canSendVoiceByPrivacy || !canSendVoices);
const mainButtonHandler = useCallback(() => {
const mainButtonHandler = useLastCallback(() => {
switch (mainButtonState) {
case MainButtonState.Send:
void handleSend();
@ -1188,11 +1169,7 @@ const Composer: FC<OwnProps & StateProps> = ({
default:
break;
}
}, [
mainButtonState, handleSend, handleEditComplete, activeVoiceRecording, requestCalendar, areVoiceMessagesNotAllowed,
canSendVoiceByPrivacy, showNotification, lang, chat?.title, startRecordingVoice, pauseRecordingVoice,
handleMessageSchedule, chatId, showAllowedMessageTypesNotification, canSendVoices,
]);
});
const prevEditedMessage = usePrevious(editingMessage, true);
const renderedEditedMessage = editingMessage || prevEditedMessage;
@ -1222,29 +1199,29 @@ const Composer: FC<OwnProps & StateProps> = ({
isMounted && 'mounted',
);
const handleSendScheduled = useCallback(() => {
const handleSendScheduled = useLastCallback(() => {
requestCalendar((scheduledAt) => {
handleMessageSchedule({}, scheduledAt);
});
}, [handleMessageSchedule, requestCalendar]);
});
const handleSendSilent = useCallback(() => {
const handleSendSilent = useLastCallback(() => {
sendSilent();
}, [sendSilent]);
});
const handleSendWhenOnline = useCallback(() => {
handleMessageSchedule({ }, SCHEDULED_WHEN_ONLINE);
}, [handleMessageSchedule]);
const handleSendWhenOnline = useLastCallback(() => {
handleMessageSchedule({}, SCHEDULED_WHEN_ONLINE);
});
const handleSendScheduledAttachments = useCallback((sendCompressed: boolean, sendGrouped: boolean) => {
const handleSendScheduledAttachments = useLastCallback((sendCompressed: boolean, sendGrouped: boolean) => {
requestCalendar((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]);
});
const onSend = mainButtonState === MainButtonState.Edit
? handleEditComplete

View File

@ -1,5 +1,5 @@
import React, {
memo, useCallback, useEffect, useMemo, useRef,
memo, useEffect, useMemo, useRef,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
@ -26,6 +26,7 @@ import captureEscKeyListener from '../../../util/captureEscKeyListener';
import buildClassName from '../../../util/buildClassName';
import { isUserId, stripCustomEmoji } from '../../../global/helpers';
import useLastCallback from '../../../hooks/useLastCallback';
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import useShowTransition from '../../../hooks/useShowTransition';
import useLang from '../../../hooks/useLang';
@ -101,7 +102,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
shouldRender, transitionClassNames,
} = useShowTransition(canAnimate && isShown, undefined, !shouldAnimate, undefined, !shouldAnimate);
const clearEmbedded = useCallback(() => {
const clearEmbedded = useLastCallback(() => {
if (replyingToId && !shouldForceShowEditing) {
setReplyingToId({ messageId: undefined });
} else if (editingId) {
@ -110,35 +111,32 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
exitForwardMode();
}
onClear?.();
}, [
replyingToId, shouldForceShowEditing, editingId, forwardedMessagesCount, onClear, setReplyingToId, setEditingId,
exitForwardMode,
]);
});
useEffect(() => (isShown ? captureEscKeyListener(clearEmbedded) : undefined), [isShown, clearEmbedded]);
const handleMessageClick = useCallback((): void => {
const handleMessageClick = useLastCallback((): void => {
if (isForwarding) return;
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();
clearEmbedded();
}, [clearEmbedded]);
});
const handleChangeRecipientClick = useCallback(() => {
const handleChangeRecipientClick = useLastCallback(() => {
changeForwardRecipient();
}, [changeForwardRecipient]);
});
const {
isContextMenuOpen, contextMenuPosition, handleContextMenu,
handleContextMenuClose, handleContextMenuHide,
} = useContextMenuHandlers(ref);
const getTriggerElement = useCallback(() => ref.current, []);
const getRootElement = useCallback(() => ref.current!, []);
const getMenuElement = useCallback(() => ref.current!.querySelector('.forward-context-menu .bubble'), []);
const getTriggerElement = useLastCallback(() => ref.current);
const getRootElement = useLastCallback(() => ref.current!);
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.forward-context-menu .bubble'));
const {
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 { ApiSticker } from '../../../api/types';
@ -6,6 +6,8 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import buildClassName from '../../../util/buildClassName';
import useLastCallback from '../../../hooks/useLastCallback';
import CustomEmoji from '../../common/CustomEmoji';
import './EmojiButton.scss';
@ -22,12 +24,12 @@ type OwnProps = {
const CustomEmojiButton: FC<OwnProps> = ({
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
e.preventDefault();
onClick?.(emoji);
}, [emoji, onClick]);
});
const className = buildClassName(
'EmojiButton',

View File

@ -1,6 +1,4 @@
import React, {
memo, useCallback, useEffect, useRef,
} from '../../../lib/teact/teact';
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
@ -12,6 +10,7 @@ import { selectIsChatWithSelf, selectIsCurrentUserPremium } from '../../../globa
import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import useLastCallback from '../../../hooks/useLastCallback';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import useShowTransition from '../../../hooks/useShowTransition';
import usePrevious from '../../../hooks/usePrevious';
@ -65,14 +64,14 @@ const CustomEmojiTooltip: FC<OwnProps & StateProps> = ({
useEffect(() => (isOpen ? captureEscKeyListener(onClose) : undefined), [isOpen, onClose]);
const handleCustomEmojiSelect = useCallback((ce: ApiSticker) => {
const handleCustomEmojiSelect = useLastCallback((ce: ApiSticker) => {
if (!isOpen) return;
onCustomEmojiSelect(ce);
addRecentCustomEmoji({
documentId: ce.id,
});
clearCustomEmojiForEmoji();
}, [addRecentCustomEmoji, clearCustomEmojiForEmoji, isOpen, onCustomEmojiSelect]);
});
const className = buildClassName(
styles.root,

View File

@ -1,13 +1,13 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useRef,
} from '../../../lib/teact/teact';
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
import useShowTransition from '../../../hooks/useShowTransition';
import buildClassName from '../../../util/buildClassName';
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import useLastCallback from '../../../hooks/useLastCallback';
import usePrevious from '../../../hooks/usePrevious';
import Portal from '../../ui/Portal';
@ -40,7 +40,7 @@ const DropArea: FC<OwnProps> = ({
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;
let files: File[] = [];
@ -55,18 +55,18 @@ const DropArea: FC<OwnProps> = ({
onHide();
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;
if (dt.files && dt.files.length > 0) {
onHide();
onFileSelect(Array.from(dt.files), true);
}
}, [onFileSelect, onHide]);
});
const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
const handleDragLeave = useLastCallback((e: React.DragEvent<HTMLDivElement>) => {
e.stopPropagation();
const { target: fromTarget, relatedTarget: toTarget } = e;
@ -77,7 +77,7 @@ const DropArea: FC<OwnProps> = ({
onHide();
}, DROP_LEAVE_TIMEOUT_MS);
}
}, [onHide]);
});
const handleDragOver = () => {
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';
@ -7,6 +7,8 @@ import { IS_EMOJI_SUPPORTED } from '../../../util/windowEnvironment';
import { handleEmojiLoad, LOADED_EMOJIS } from '../../../util/emoji';
import buildClassName from '../../../util/buildClassName';
import useLastCallback from '../../../hooks/useLastCallback';
import './EmojiButton.scss';
type OwnProps = {
@ -18,12 +20,12 @@ type OwnProps = {
const EmojiButton: FC<OwnProps> = ({
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
e.preventDefault();
onClick(emoji.native, emoji.id);
}, [emoji, onClick]);
});
const className = buildClassName(
'EmojiButton',

View File

@ -1,5 +1,5 @@
import React, {
useState, useEffect, memo, useRef, useMemo, useCallback,
useState, useEffect, memo, useRef, useMemo,
} from '../../../lib/teact/teact';
import { withGlobal } from '../../../global';
@ -20,6 +20,8 @@ import animateScroll from '../../../util/animateScroll';
import { pick } from '../../../util/iteratees';
import buildClassName from '../../../util/buildClassName';
import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
import useLastCallback from '../../../hooks/useLastCallback';
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
@ -167,16 +169,16 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
}, OPEN_ANIMATION_DELAY);
}, []);
const selectCategory = useCallback((index: number) => {
const selectCategory = useLastCallback((index: number) => {
setActiveCategoryIndex(index);
const categoryEl = containerRef.current!.closest<HTMLElement>('.SymbolMenu-main')!
.querySelector(`#emoji-category-${index}`)! as HTMLElement;
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]);
});
function renderCategoryButton(category: EmojiCategoryData, index: number) {
const icon = ICONS_BY_CATEGORY[category.id];

View File

@ -1,6 +1,4 @@
import React, {
memo, useCallback, useRef,
} from '../../../lib/teact/teact';
import React, { memo, useRef } from '../../../lib/teact/teact';
import type { ApiSticker } from '../../../api/types';
import type { FC } from '../../../lib/teact/teact';
@ -10,6 +8,7 @@ import findInViewport from '../../../util/findInViewport';
import isFullyVisible from '../../../util/isFullyVisible';
import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
import useLastCallback from '../../../hooks/useLastCallback';
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
import useShowTransition from '../../../hooks/useShowTransition';
import usePrevDuringAnimation from '../../../hooks/usePrevDuringAnimation';
@ -91,33 +90,33 @@ const EmojiTooltip: FC<OwnProps> = ({
observe: observeIntersection,
} = useIntersectionObserver({ rootRef: containerRef, throttleMs: INTERSECTION_THROTTLE, isDisabled: !isOpen });
const handleSelectEmoji = useCallback((emoji: Emoji) => {
const handleSelectEmoji = useLastCallback((emoji: Emoji) => {
onEmojiSelect(emoji.native);
addRecentEmoji({ emoji: emoji.id });
}, [addRecentEmoji, onEmojiSelect]);
});
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => {
const handleCustomEmojiSelect = useLastCallback((emoji: ApiSticker) => {
onCustomEmojiSelect(emoji);
addRecentCustomEmoji({ documentId: emoji.id });
}, [addRecentCustomEmoji, onCustomEmojiSelect]);
});
const handleSelect = useCallback((emoji: Emoji | ApiSticker) => {
const handleSelect = useLastCallback((emoji: Emoji | ApiSticker) => {
if ('native' in emoji) {
handleSelectEmoji(emoji);
} else {
handleCustomEmojiSelect(emoji);
}
}, [handleCustomEmojiSelect, handleSelectEmoji]);
});
const handleClick = useCallback((native: string, id: string) => {
const handleClick = useLastCallback((native: string, id: string) => {
onEmojiSelect(native);
addRecentEmoji({ emoji: id });
}, [addRecentEmoji, onEmojiSelect]);
});
const handleCustomEmojiClick = useCallback((emoji: ApiSticker) => {
const handleCustomEmojiClick = useLastCallback((emoji: ApiSticker) => {
onCustomEmojiSelect(emoji);
addRecentCustomEmoji({ documentId: emoji.id });
}, [addRecentCustomEmoji, onCustomEmojiSelect]);
});
const selectedIndex = useKeyboardNavigation({
isActive: isOpen,

View File

@ -1,6 +1,6 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
useEffect, memo, useRef, useCallback,
useEffect, memo, useRef,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
@ -11,6 +11,7 @@ import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { selectCurrentMessageList, selectIsChatWithSelf } from '../../../global/selectors';
import useLastCallback from '../../../hooks/useLastCallback';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
@ -56,9 +57,9 @@ const GifPicker: FC<OwnProps & StateProps> = ({
}
}, [loadAndPlay, loadSavedGifs]);
const handleUnsaveClick = useCallback((gif: ApiVideo) => {
const handleUnsaveClick = useLastCallback((gif: ApiVideo) => {
saveGif({ gif, shouldUnsave: true });
}, [saveGif]);
});
const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION);

View File

@ -1,6 +1,4 @@
import React, {
memo, useCallback, useEffect, useRef,
} from '../../../lib/teact/teact';
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
@ -15,6 +13,7 @@ import buildClassName from '../../../util/buildClassName';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
import { throttle } from '../../../util/schedulers';
import useLastCallback from '../../../hooks/useLastCallback';
import useShowTransition from '../../../hooks/useShowTransition';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import usePrevious from '../../../hooks/usePrevious';
@ -43,7 +42,7 @@ export type OwnProps = {
isSavedMessages?: boolean;
canSendGifs?: boolean;
onSelectResult: (
inlineResult: ApiBotInlineMediaResult | ApiBotInlineResult, isSilent?: boolean, shouldSchedule?: boolean
inlineResult: ApiBotInlineMediaResult | ApiBotInlineResult, isSilent?: boolean, shouldSchedule?: boolean,
) => void;
loadMore: NoneToVoidFunction;
onClose: NoneToVoidFunction;
@ -82,11 +81,11 @@ const InlineBotTooltip: FC<OwnProps> = ({
isDisabled: !isOpen,
});
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
const handleLoadMore = useLastCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (direction === LoadMoreDirection.Backwards) {
runThrottled(loadMore);
}
}, [loadMore]);
});
const selectedIndex = useKeyboardNavigation({
isActive: isOpen,
@ -101,12 +100,12 @@ const InlineBotTooltip: FC<OwnProps> = ({
setTooltipItemVisible('.chat-item-clickable', selectedIndex, containerRef);
}, [selectedIndex]);
const handleSendPm = useCallback(() => {
const handleSendPm = useLastCallback(() => {
openChat({ id: botId });
startBot({ botId: botId!, param: switchPm!.startParam });
}, [botId, openChat, startBot, switchPm]);
});
const handleOpenWebview = useCallback(() => {
const handleOpenWebview = useLastCallback(() => {
const theme = extractCurrentThemeParams();
requestSimpleWebView({
@ -115,7 +114,7 @@ const InlineBotTooltip: FC<OwnProps> = ({
buttonText: switchWebview!.text,
theme,
});
}, [botId, switchWebview]);
});
const prevInlineBotResults = usePrevious(
inlineBotResults?.length

View File

@ -1,13 +1,13 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
useCallback, useEffect, useRef, memo,
} from '../../../lib/teact/teact';
import React, { useEffect, useRef, memo } from '../../../lib/teact/teact';
import { getGlobal } from '../../../global';
import type { ApiUser } from '../../../api/types';
import buildClassName from '../../../util/buildClassName';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
import useLastCallback from '../../../hooks/useLastCallback';
import usePrevious from '../../../hooks/usePrevious';
import useShowTransition from '../../../hooks/useShowTransition';
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
@ -34,7 +34,7 @@ const MentionTooltip: FC<OwnProps> = ({
const containerRef = useRef<HTMLDivElement>(null);
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
const usersById = getGlobal().users.byId;
const user = usersById[userId];
@ -43,17 +43,17 @@ const MentionTooltip: FC<OwnProps> = ({
}
onInsertUserName(user, forceFocus);
}, [onInsertUserName]);
});
const handleClick = useCallback((e: React.MouseEvent, id: string) => {
const handleClick = useLastCallback((e: React.MouseEvent, id: string) => {
e.preventDefault();
handleUserSelect(id);
}, [handleUserSelect]);
});
const handleSelectMention = useCallback((member: ApiUser) => {
const handleSelectMention = useLastCallback((member: ApiUser) => {
handleUserSelect(member.id, true);
}, [handleUserSelect]);
});
const selectedMentionIndex = useKeyboardNavigation({
isActive: isOpen,

View File

@ -1,7 +1,7 @@
import type { RefObject, ChangeEvent } from 'react';
import type { FC } from '../../../lib/teact/teact';
import React, {
useEffect, useRef, memo, useState, useCallback, useLayoutEffect,
useEffect, useRef, memo, useState, useLayoutEffect,
} from '../../../lib/teact/teact';
import { requestMutation, requestForcedReflow } from '../../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../../global';
@ -23,6 +23,7 @@ import parseEmojiOnlyString from '../../../util/parseEmojiOnlyString';
import { isSelectionInsideInput } from './helpers/selection';
import renderText from '../../common/helpers/renderText';
import useLastCallback from '../../../hooks/useLastCallback';
import useFlag from '../../../hooks/useFlag';
import { isHeavyAnimating } from '../../../hooks/useHeavyAnimationCheck';
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 updateInputHeight = useCallback((willSend = false) => {
const updateInputHeight = useLastCallback((willSend = false) => {
requestForcedReflow(() => {
const scroller = inputRef.current!.closest<HTMLDivElement>(`.${SCROLLER_CLASS}`)!;
const currentHeight = Number(scroller.style.height.replace('px', ''));
@ -198,7 +199,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
return exec;
}
});
}, [maxInputHeight]);
});
useLayoutEffect(() => {
if (!isAttachmentModalInput) return;
@ -226,7 +227,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
const chatIdRef = useRef(chatId);
chatIdRef.current = chatId;
const focusInput = useCallback(() => {
const focusInput = useLastCallback(() => {
if (!inputRef.current) {
return;
}
@ -237,12 +238,12 @@ const MessageInput: FC<OwnProps & StateProps> = ({
}
focusEditableElement(inputRef.current!);
}, []);
});
const handleCloseTextFormatter = useCallback(() => {
const handleCloseTextFormatter = useLastCallback(() => {
closeTextFormatter();
clearSelection();
}, [closeTextFormatter]);
});
function checkSelection() {
// Disable the formatter on iOS devices for now.

View File

@ -1,7 +1,7 @@
import type { ChangeEvent, RefObject } from 'react';
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
memo, useEffect, useLayoutEffect, useRef, useState,
} from '../../../lib/teact/teact';
import { requestNextMutation } from '../../../lib/fasterdom/fasterdom';
@ -9,6 +9,8 @@ import type { ApiNewPoll } from '../../../api/types';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import parseMessageInput from '../../../util/parseMessageInput';
import useLastCallback from '../../../hooks/useLastCallback';
import useLang from '../../../hooks/useLang';
import Button from '../../ui/Button';
@ -54,11 +56,11 @@ const PollModal: FC<OwnProps> = ({
const lang = useLang();
const focusInput = useCallback((ref: RefObject<HTMLInputElement>) => {
const focusInput = useLastCallback((ref: RefObject<HTMLInputElement>) => {
if (isOpen && ref.current) {
ref.current.focus();
}
}, [isOpen]);
});
useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]);
useEffect(() => {
@ -84,7 +86,7 @@ const PollModal: FC<OwnProps> = ({
}
}, [solution]);
const addNewOption = useCallback((newOptions: string[] = []) => {
const addNewOption = useLastCallback((newOptions: string[] = []) => {
setOptions([...newOptions, '']);
requestNextMutation(() => {
@ -96,9 +98,9 @@ const PollModal: FC<OwnProps> = ({
list.classList.toggle('overflown', list.scrollHeight > MAX_LIST_HEIGHT);
list.scrollTo({ top: list.scrollHeight, behavior: 'smooth' });
});
}, []);
});
const handleCreate = useCallback(() => {
const handleCreate = useLastCallback(() => {
setHasErrors(false);
if (!isOpen) {
return;
@ -155,20 +157,9 @@ const PollModal: FC<OwnProps> = ({
}
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];
newOptions[index] = text;
if (newOptions[newOptions.length - 1].trim().length && newOptions.length < MAX_OPTIONS_COUNT) {
@ -176,9 +167,9 @@ const PollModal: FC<OwnProps> = ({
} else {
setOptions(newOptions);
}
}, [options, addNewOption]);
});
const removeOption = useCallback((index: number) => {
const removeOption = useLastCallback((index: number) => {
const newOptions = [...options];
newOptions.splice(index, 1);
setOptions(newOptions);
@ -198,49 +189,49 @@ const PollModal: FC<OwnProps> = ({
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]);
});
const handleIsAnonymousChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const handleIsAnonymousChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
setIsAnonymous(e.target.checked);
}, []);
});
const handleMultipleAnswersChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const handleMultipleAnswersChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
setIsMultipleAnswers(e.target.checked);
}, []);
});
const handleQuizModeChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const handleQuizModeChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
setIsQuizMode(e.target.checked);
}, []);
});
const handleKeyPress = useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
const handleKeyPress = useLastCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.keyCode === 13) {
handleCreate();
}
}, [handleCreate]);
});
const handleQuestionChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const handleQuestionChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
setQuestion(e.target.value);
}, []);
});
const getQuestionError = useCallback(() => {
const getQuestionError = useLastCallback(() => {
if (hasErrors && !question.trim().length) {
return lang('lng_polls_choose_question');
}
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);
if (hasErrors && optionsTrimmed.length < 2 && !options[index].trim().length) {
return lang('lng_polls_choose_answers');
}
return undefined;
}, [hasErrors, lang, options]);
});
function renderHeader() {
return (

View File

@ -1,6 +1,4 @@
import React, {
useCallback, useEffect, useRef, memo,
} from '../../../lib/teact/teact';
import React, { useEffect, useRef, memo } from '../../../lib/teact/teact';
import { getActions, getGlobal } from '../../../global';
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 buildClassName from '../../../util/buildClassName';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
import { isUserId } from '../../../global/helpers';
import useLastCallback from '../../../hooks/useLastCallback';
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
import useMouseInside from '../../../hooks/useMouseInside';
import useLang from '../../../hooks/useLang';
@ -56,10 +56,10 @@ const SendAsMenu: FC<OwnProps> = ({
}
}, [isOpen, markMouseInside]);
const handleUserSelect = useCallback((id: string) => {
const handleUserSelect = useLastCallback((id: string) => {
onClose();
saveDefaultSendAs({ chatId: chatId!, sendAsId: id });
}, [chatId, onClose, saveDefaultSendAs]);
});
const selectedSendAsIndex = useKeyboardNavigation({
isActive: isOpen,

View File

@ -1,5 +1,5 @@
import React, {
useEffect, memo, useRef, useMemo, useCallback,
useEffect, memo, useRef, useMemo,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
@ -27,6 +27,7 @@ import {
selectChat, selectChatFullInfo, selectIsChatWithSelf, selectIsCurrentUserPremium, selectShouldLoopStickers,
} from '../../../global/selectors';
import useLastCallback from '../../../hooks/useLastCallback';
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
import useLang from '../../../hooks/useLang';
@ -229,27 +230,27 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
animateHorizontalScroll(header, newLeft);
}, [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);
addRecentSticker({ sticker });
}, [addRecentSticker, onStickerSelect]);
});
const handleStickerUnfave = useCallback((sticker: ApiSticker) => {
const handleStickerUnfave = useLastCallback((sticker: ApiSticker) => {
unfaveSticker({ sticker });
}, [unfaveSticker]);
});
const handleStickerFave = useCallback((sticker: ApiSticker) => {
const handleStickerFave = useLastCallback((sticker: ApiSticker) => {
faveSticker({ sticker });
}, [faveSticker]);
});
const handleMouseMove = useCallback(() => {
const handleMouseMove = useLastCallback(() => {
if (!canSendStickers) return;
sendMessageAction({ type: 'chooseSticker' });
}, [canSendStickers, sendMessageAction]);
});
const handleRemoveRecentSticker = useCallback((sticker: ApiSticker) => {
const handleRemoveRecentSticker = useLastCallback((sticker: ApiSticker) => {
removeRecentSticker({ sticker });
}, [removeRecentSticker]);
});
function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) {
const firstSticker = stickerSet.stickers?.[0];

View File

@ -1,5 +1,5 @@
import React, {
memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
memo, useEffect, useLayoutEffect, useRef, useState,
} from '../../../lib/teact/teact';
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../../global';
@ -12,6 +12,7 @@ import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { selectTabState, selectIsCurrentUserPremium, selectIsContextMenuTranslucent } from '../../../global/selectors';
import useLastCallback from '../../../hooks/useLastCallback';
import useShowTransition from '../../../hooks/useShowTransition';
import useMouseInside from '../../../hooks/useMouseInside';
import useLang from '../../../hooks/useLang';
@ -164,11 +165,11 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
setRecentEmojis([]);
}, [isOpen, addRecentEmoji]);
const handleEmojiSelect = useCallback((emoji: string, name: string) => {
const handleEmojiSelect = useLastCallback((emoji: string, name: string) => {
setRecentEmojis((emojis) => [...emojis, name]);
onEmojiSelect(emoji);
}, [onEmojiSelect]);
});
const recentCustomEmojisRef = useRef(recentCustomEmojis);
recentCustomEmojisRef.current = recentCustomEmojis;
@ -186,22 +187,22 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
setRecentEmojis([]);
}, [isOpen, addRecentCustomEmoji]);
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => {
const handleCustomEmojiSelect = useLastCallback((emoji: ApiSticker) => {
setRecentCustomEmojis((ids) => [...ids, emoji.id]);
onCustomEmojiSelect(emoji);
}, [onCustomEmojiSelect]);
});
const handleSearch = useCallback((type: 'stickers' | 'gifs') => {
const handleSearch = useLastCallback((type: 'stickers' | 'gifs') => {
onClose();
onSearchOpen(type);
}, [onClose, onSearchOpen]);
});
const handleStickerSelect = useCallback((
const handleStickerSelect = useLastCallback((
sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, canUpdateStickerSetsOrder?: boolean,
) => {
onStickerSelect?.(sticker, isSilent, shouldSchedule, true, canUpdateStickerSetsOrder);
}, [onStickerSelect]);
});
const lang = useLang();

View File

@ -1,6 +1,4 @@
import React, {
memo, useCallback, useRef, useState,
} from '../../../lib/teact/teact';
import React, { memo, useRef, useState } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
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 buildClassName from '../../../util/buildClassName';
import useLastCallback from '../../../hooks/useLastCallback';
import useFlag from '../../../hooks/useFlag';
import useMenuPosition from '../../../hooks/useMenuPosition';
@ -35,7 +35,7 @@ type OwnProps = {
isSilent?: boolean,
shouldSchedule?: boolean,
shouldPreserveInput?: boolean,
canUpdateStickerSetsOrder?: boolean
canUpdateStickerSetsOrder?: boolean,
) => void;
onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void;
onRemoveSymbol: VoidFunction;
@ -91,7 +91,7 @@ const SymbolMenuButton: FC<OwnProps> = ({
: (isSymbolMenuOpen && 'is-loading'),
);
const handleActivateSymbolMenu = useCallback(() => {
const handleActivateSymbolMenu = useLastCallback(() => {
closeBotCommandMenu?.();
closeSendAsMenu?.();
openSymbolMenu();
@ -99,9 +99,9 @@ const SymbolMenuButton: FC<OwnProps> = ({
if (!triggerEl) return;
const { x, y } = triggerEl.getBoundingClientRect();
setContextMenuPosition({ x, y });
}, [closeBotCommandMenu, closeSendAsMenu, openSymbolMenu]);
});
const handleSearchOpen = useCallback((type: 'stickers' | 'gifs') => {
const handleSearchOpen = useLastCallback((type: 'stickers' | 'gifs') => {
if (type === 'stickers') {
setStickerSearchQuery({ query: '' });
setGifSearchQuery({ query: undefined });
@ -109,9 +109,9 @@ const SymbolMenuButton: FC<OwnProps> = ({
setGifSearchQuery({ query: '' });
setStickerSearchQuery({ query: undefined });
}
}, [setStickerSearchQuery, setGifSearchQuery]);
});
const handleSymbolMenuOpen = useCallback(() => {
const handleSymbolMenuOpen = useLastCallback(() => {
const messageInput = document.querySelector<HTMLDivElement>(
isAttachmentModal ? EDITABLE_INPUT_MODAL_CSS_SELECTOR : EDITABLE_INPUT_CSS_SELECTOR,
);
@ -126,23 +126,12 @@ const SymbolMenuButton: FC<OwnProps> = ({
closeBotCommandMenu?.();
openSymbolMenu();
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
}, [isAttachmentModal, isMobile, openSymbolMenu, closeBotCommandMenu]);
});
const getTriggerElement = useCallback(() => triggerRef.current, []);
const getRootElement = useCallback(
() => triggerRef.current?.closest('.custom-scroll, .no-scrollbar'),
[],
);
const getMenuElement = useCallback(
() => document.querySelector('#portals .SymbolMenu .bubble'),
[],
);
const getLayout = useCallback(() => ({
withPortal: true,
}), []);
const getTriggerElement = useLastCallback(() => triggerRef.current);
const getRootElement = useLastCallback(() => triggerRef.current?.closest('.custom-scroll, .no-scrollbar'));
const getMenuElement = useLastCallback(() => document.querySelector('#portals .SymbolMenu .bubble'));
const getLayout = useLastCallback(() => ({ withPortal: true }));
const {
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,

View File

@ -1,7 +1,9 @@
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 useLastCallback from '../../../hooks/useLastCallback';
import useLang from '../../../hooks/useLang';
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');
}, [activeTab, onSearchOpen]);
});
function stopPropagation(event: any) {
event.stopPropagation();

View File

@ -1,6 +1,6 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useRef, useState,
memo, useEffect, useRef, useState,
} from '../../../lib/teact/teact';
import type { IAnchorPosition } from '../../../types';
@ -14,6 +14,7 @@ import getKeyFromEvent from '../../../util/getKeyFromEvent';
import { INPUT_CUSTOM_EMOJI_SELECTOR } from './helpers/customEmoji';
import stopEvent from '../../../util/stopEvent';
import useLastCallback from '../../../hooks/useLastCallback';
import useShowTransition from '../../../hooks/useShowTransition';
import useVirtualBackdrop from '../../../hooks/useVirtualBackdrop';
import useFlag from '../../../hooks/useFlag';
@ -114,7 +115,7 @@ const TextFormatter: FC<OwnProps> = ({
setSelectedTextFormats(selectedFormats);
}, [isOpen, selectedRange, openLinkControl]);
const restoreSelection = useCallback(() => {
const restoreSelection = useLastCallback(() => {
if (!selectedRange) {
return;
}
@ -124,16 +125,16 @@ const TextFormatter: FC<OwnProps> = ({
selection.removeAllRanges();
selection.addRange(selectedRange);
}
}, [selectedRange]);
});
const updateSelectedRange = useCallback(() => {
const updateSelectedRange = useLastCallback(() => {
const selection = window.getSelection();
if (selection) {
setSelectedRange(selection.getRangeAt(0));
}
}, [setSelectedRange]);
});
const getSelectedText = useCallback((shouldDropCustomEmoji?: boolean) => {
const getSelectedText = useLastCallback((shouldDropCustomEmoji?: boolean) => {
if (!selectedRange) {
return undefined;
}
@ -144,15 +145,15 @@ const TextFormatter: FC<OwnProps> = ({
});
}
return fragmentEl.innerHTML;
}, [selectedRange]);
});
const getSelectedElement = useCallback(() => {
const getSelectedElement = useLastCallback(() => {
if (!selectedRange) {
return undefined;
}
return selectedRange.commonAncestorContainer.parentElement;
}, [selectedRange]);
});
function updateInputStyles() {
const input = linkUrlInputRef.current;
@ -200,7 +201,7 @@ const TextFormatter: FC<OwnProps> = ({
return undefined;
}
const handleSpoilerText = useCallback(() => {
const handleSpoilerText = useLastCallback(() => {
if (selectedTextFormats.spoiler) {
const element = getSelectedElement();
if (
@ -226,9 +227,9 @@ const TextFormatter: FC<OwnProps> = ({
'insertHTML', false, `<span class="spoiler" data-entity-type="${ApiMessageEntityTypes.Spoiler}">${text}</span>`,
);
onClose();
}, [getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.spoiler]);
});
const handleBoldText = useCallback(() => {
const handleBoldText = useLastCallback(() => {
setSelectedTextFormats((selectedFormats) => {
// Somehow re-applying 'bold' command to already bold text doesn't work
document.execCommand(selectedFormats.bold ? 'removeFormat' : 'bold');
@ -244,27 +245,27 @@ const TextFormatter: FC<OwnProps> = ({
bold: !selectedFormats.bold,
};
});
}, [updateSelectedRange]);
});
const handleItalicText = useCallback(() => {
const handleItalicText = useLastCallback(() => {
document.execCommand('italic');
updateSelectedRange();
setSelectedTextFormats((selectedFormats) => ({
...selectedFormats,
italic: !selectedFormats.italic,
}));
}, [updateSelectedRange]);
});
const handleUnderlineText = useCallback(() => {
const handleUnderlineText = useLastCallback(() => {
document.execCommand('underline');
updateSelectedRange();
setSelectedTextFormats((selectedFormats) => ({
...selectedFormats,
underline: !selectedFormats.underline,
}));
}, [updateSelectedRange]);
});
const handleStrikethroughText = useCallback(() => {
const handleStrikethroughText = useLastCallback(() => {
if (selectedTextFormats.strikethrough) {
const element = getSelectedElement();
if (
@ -288,11 +289,9 @@ const TextFormatter: FC<OwnProps> = ({
const text = getSelectedText();
document.execCommand('insertHTML', false, `<del>${text}</del>`);
onClose();
}, [
getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.strikethrough,
]);
});
const handleMonospaceText = useCallback(() => {
const handleMonospaceText = useLastCallback(() => {
if (selectedTextFormats.monospace) {
const element = getSelectedElement();
if (
@ -316,11 +315,9 @@ const TextFormatter: FC<OwnProps> = ({
const text = getSelectedText(true);
document.execCommand('insertHTML', false, `<code class="text-entity-code" dir="auto">${text}</code>`);
onClose();
}, [
getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.monospace,
]);
});
const handleLinkUrlConfirm = useCallback(() => {
const handleLinkUrlConfirm = useLastCallback(() => {
const formattedLinkUrl = (ensureProtocol(linkUrl) || '').split('%').map(encodeURI).join('%');
if (isEditingLink) {
@ -344,9 +341,9 @@ const TextFormatter: FC<OwnProps> = ({
`<a href=${formattedLinkUrl} class="text-entity-link" dir="auto">${text}</a>`,
);
onClose();
}, [getSelectedElement, getSelectedText, isEditingLink, linkUrl, onClose, restoreSelection]);
});
const handleKeyDown = useCallback((e: KeyboardEvent) => {
const handleKeyDown = useLastCallback((e: KeyboardEvent) => {
const HANDLERS_BY_KEY: Record<string, AnyToVoidFunction> = {
k: openLinkControl,
b: handleBoldText,
@ -370,10 +367,7 @@ const TextFormatter: FC<OwnProps> = ({
e.preventDefault();
e.stopPropagation();
handler();
}, [
openLinkControl, handleBoldText, handleUnderlineText, handleItalicText, handleMonospaceText,
handleStrikethroughText, handleSpoilerText,
]);
});
useEffect(() => {
if (isOpen) {

View File

@ -1,6 +1,4 @@
import React, {
memo, useCallback, useEffect, useRef,
} from '../../../lib/teact/teact';
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
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 buildClassName from '../../../util/buildClassName';
import parseMessageInput from '../../../util/parseMessageInput';
import useLastCallback from '../../../hooks/useLastCallback';
import useSyncEffect from '../../../hooks/useSyncEffect';
import useShowTransition from '../../../hooks/useShowTransition';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
@ -97,9 +97,9 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
const renderingWebPage = useCurrentOrPrev(webPagePreview, true);
const handleClearWebpagePreview = useCallback(() => {
const handleClearWebpagePreview = useLastCallback(() => {
toggleMessageWebPage({ chatId, threadId, noWebPage: true });
}, [chatId, threadId, toggleMessageWebPage]);
});
if (!shouldRender || !renderingWebPage) {
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 type { ApiAttachment } from '../../../../api/types';
@ -11,6 +11,8 @@ import {
SUPPORTED_VIDEO_CONTENT_TYPES,
} from '../../../../config';
import useLastCallback from '../../../../hooks/useLastCallback';
export default function useAttachmentModal({
attachments,
fileSizeLimit,
@ -37,11 +39,11 @@ export default function useAttachmentModal({
const [shouldForceCompression, setShouldForceCompression] = useState<boolean>(false);
const [shouldSuggestCompression, setShouldSuggestCompression] = useState<boolean | undefined>(undefined);
const handleClearAttachments = useCallback(() => {
const handleClearAttachments = useLastCallback(() => {
setAttachments(MEMO_EMPTY_ARRAY);
}, [setAttachments]);
});
const handleSetAttachments = useCallback(
const handleSetAttachments = useLastCallback(
(newValue: ApiAttachment[] | ((current: ApiAttachment[]) => ApiAttachment[])) => {
const newAttachments = typeof newValue === 'function' ? newValue(attachments) : newValue;
if (!newAttachments.length) {
@ -75,25 +77,22 @@ export default function useAttachmentModal({
setShouldForceAsFile(Boolean(shouldForce && 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([
...attachments,
...await Promise.all(files.map((file) => (
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))));
setShouldSuggestCompression(suggestCompression);
}, [handleSetAttachments]);
});
return {
shouldSuggestCompression,

View File

@ -1,5 +1,5 @@
import type { RefObject } from 'react';
import { useCallback, useEffect } from '../../../../lib/teact/teact';
import { useEffect } from '../../../../lib/teact/teact';
import twemojiRegex from '../../../../lib/twemojiRegex';
import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
@ -13,6 +13,7 @@ import { getHtmlBeforeSelection } from '../../../../util/selection';
import focusEditableElement from '../../../../util/focusEditableElement';
import { buildCustomEmojiHtml } from '../helpers/customEmoji';
import useLastCallback from '../../../../hooks/useLastCallback';
import useDerivedState from '../../../../hooks/useDerivedState';
import useFlag from '../../../../hooks/useFlag';
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
@ -68,7 +69,7 @@ export default function useCustomEmojiTooltip(
}
}, [isEnabled, getLastEmoji, hasCustomEmojis, clearCustomEmojiForEmoji, loadCustomEmojiForEmoji]);
const insertCustomEmoji = useCallback((emoji: ApiSticker) => {
const insertCustomEmoji = useLastCallback((emoji: ApiSticker) => {
const lastEmoji = getLastEmoji();
if (!isEnabled || !lastEmoji) return;
@ -89,7 +90,7 @@ export default function useCustomEmojiTooltip(
requestNextMutation(() => {
focusEditableElement(inputEl, true, true);
});
}, [getLastEmoji, isEnabled, inputRef, setHtml]);
});
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 { getActions } from '../../../../global';
@ -12,6 +12,8 @@ import { IS_TOUCH_ENV } from '../../../../util/windowEnvironment';
import focusEditableElement from '../../../../util/focusEditableElement';
import parseMessageInput from '../../../../util/parseMessageInput';
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
import useLastCallback from '../../../../hooks/useLastCallback';
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
import useBeforeUnload from '../../../../hooks/useBeforeUnload';
import { useStateRef } from '../../../../hooks/useStateRef';
@ -41,7 +43,7 @@ const useDraft = (
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;
const html = getHtml();
@ -60,7 +62,7 @@ const useDraft = (
shouldForce,
});
}
}, [chatId, threadId, isEditing, lastSyncTime, getHtml, saveDraft, clearDraft]);
});
const updateDraftRef = useStateRef(updateDraft);
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 { requestMeasure, requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
@ -13,6 +13,8 @@ import parseMessageInput from '../../../../util/parseMessageInput';
import focusEditableElement from '../../../../util/focusEditableElement';
import { hasMessageMedia } from '../../../../global/helpers';
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
import useLastCallback from '../../../../hooks/useLastCallback';
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
import useBeforeUnload from '../../../../hooks/useBeforeUnload';
import { useDebouncedResolver } from '../../../../hooks/useAsyncResolvers';
@ -118,7 +120,7 @@ const useEditing = (
}
}, [editedMessage, chatId, getHtml, threadId, getShouldResetNoWebPageDebounced]);
const restoreNewDraftAfterEditing = useCallback(() => {
const restoreNewDraftAfterEditing = useLastCallback(() => {
if (!draft) return;
// Run one frame after editing draft reset
@ -133,14 +135,14 @@ const useEditing = (
}
});
});
}, [draft, setHtml]);
});
const handleEditCancel = useCallback(() => {
const handleEditCancel = useLastCallback(() => {
resetComposer();
restoreNewDraftAfterEditing();
}, [resetComposer, restoreNewDraftAfterEditing]);
});
const handleEditComplete = useCallback(() => {
const handleEditComplete = useLastCallback(() => {
const { text, entities } = parseMessageInput(getHtml());
if (!editedMessage) {
@ -159,9 +161,9 @@ const useEditing = (
resetComposer();
restoreNewDraftAfterEditing();
}, [editMessage, editedMessage, getHtml, openDeleteModal, resetComposer, restoreNewDraftAfterEditing]);
});
const handleBlur = useCallback(() => {
const handleBlur = useLastCallback(() => {
if (!editedMessage) return;
const edited = parseMessageInput(getHtml());
const update = edited.text.length ? edited : undefined;
@ -169,7 +171,7 @@ const useEditing = (
setEditingDraft({
chatId, threadId, type, text: update,
});
}, [chatId, editedMessage, getHtml, setEditingDraft, threadId, type]);
});
useBackgroundMode(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 { getGlobal } from '../../../../global';
@ -19,6 +19,7 @@ import renderText from '../../../common/helpers/renderText';
import { selectCustomEmojiForEmojis } from '../../../../global/selectors';
import { buildCustomEmojiHtml } from '../helpers/customEmoji';
import useLastCallback from '../../../../hooks/useLastCallback';
import useFlag from '../../../../hooks/useFlag';
import useDerivedSignal from '../../../../hooks/useDerivedSignal';
import { useThrottledResolver } from '../../../../hooks/useAsyncResolvers';
@ -94,7 +95,7 @@ export default function useEmojiTooltip(
detectEmojiCodeThrottled, [detectEmojiCodeThrottled, getHtml], true,
);
const updateFiltered = useCallback((emojis: Emoji[]) => {
const updateFiltered = useLastCallback((emojis: Emoji[]) => {
setFilteredEmojis(emojis);
if (emojis === MEMO_EMPTY_ARRAY) {
@ -108,9 +109,9 @@ export default function useEmojiTooltip(
'id',
);
setFilteredCustomEmojis(customEmojis);
}, []);
});
const insertEmoji = useCallback((emoji: string | ApiSticker, isForce = false) => {
const insertEmoji = useLastCallback((emoji: string | ApiSticker, isForce = false) => {
const html = getHtml();
if (!html) return;
@ -130,7 +131,7 @@ export default function useEmojiTooltip(
}
updateFiltered(MEMO_EMPTY_ARRAY);
}, [getHtml, setHtml, inputId, updateFiltered]);
});
useEffect(() => {
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 { Signal } from '../../../../util/signals';
@ -6,6 +6,7 @@ import type { Signal } from '../../../../util/signals';
import { getActions } from '../../../../global';
import memoized from '../../../../util/memoized';
import useLastCallback from '../../../../hooks/useLastCallback';
import useFlag from '../../../../hooks/useFlag';
import useDerivedState from '../../../../hooks/useDerivedState';
import useSyncEffect from '../../../../hooks/useSyncEffect';
@ -75,13 +76,13 @@ export default function useInlineBotTooltip(
}
}, [isOpen, resetAllInlineBots, username]);
const loadMore = useCallback(() => {
const loadMore = useLastCallback(() => {
if (!usernameLowered) return;
queryInlineBot({
chatId, username: usernameLowered, query, offset,
});
}, [chatId, offset, query, queryInlineBot, usernameLowered]);
});
return {
isOpen,

View File

@ -1,5 +1,5 @@
import {
useCallback, useEffect, useLayoutEffect, useRef,
useEffect, useLayoutEffect, useRef,
} from '../../../../lib/teact/teact';
import { requestMeasure } from '../../../../lib/fasterdom/fasterdom';
import { ensureRLottie } from '../../../../lib/rlottie/RLottie.async';
@ -19,12 +19,12 @@ import { REM } from '../../../common/helpers/mediaDimensions';
import { hexToRgb } from '../../../../util/switchTheme';
import useColorFilter from '../../../../hooks/stickers/useColorFilter';
import useLastCallback from '../../../../hooks/useLastCallback';
import useResizeObserver from '../../../../hooks/useResizeObserver';
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
import useThrottledCallback from '../../../../hooks/useThrottledCallback';
import useDynamicColorListener from '../../../../hooks/stickers/useDynamicColorListener';
import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps';
import { useLastCallback } from '../../../../hooks/useLastCallback';
const SIZE = 1.25 * REM;
const THROTTLE_MS = 300;
@ -51,7 +51,7 @@ export default function useInputCustomEmojis(
const colorFilter = useColorFilter(customColor, true);
const playersById = useRef<Map<string, CustomEmojiPlayer>>(new Map());
const clearPlayers = useCallback((ids: string[]) => {
const clearPlayers = useLastCallback((ids: string[]) => {
ids.forEach((id) => {
const player = playersById.current.get(id);
if (player) {
@ -59,7 +59,7 @@ export default function useInputCustomEmojis(
playersById.current.delete(id);
}
});
}, []);
});
const synchronizeElements = useLastCallback(() => {
if (!isReady || !inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return;
@ -160,13 +160,13 @@ export default function useInputCustomEmojis(
);
useResizeObserver(sharedCanvasRef, throttledSynchronizeElements);
const freezeAnimation = useCallback(() => {
const freezeAnimation = useLastCallback(() => {
playersById.current.forEach((player) => {
player.pause();
});
}, []);
});
const unfreezeAnimation = useCallback(() => {
const unfreezeAnimation = useLastCallback(() => {
if (!canPlayAnimatedEmojis) {
return;
}
@ -174,11 +174,11 @@ export default function useInputCustomEmojis(
playersById.current?.forEach((player) => {
player.play();
});
}, [canPlayAnimatedEmojis]);
});
const unfreezeAnimationOnRaf = useCallback(() => {
const unfreezeAnimationOnRaf = useLastCallback(() => {
requestMeasure(unfreezeAnimation);
}, [unfreezeAnimation]);
});
// Pausing frame may not happen in background,
// 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