[Refactoring] Introduce useLastCallback
This commit is contained in:
parent
df70051763
commit
b03959e847
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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]);
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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> = {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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' });
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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' : ''}>
|
||||
|
||||
@ -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[] = [];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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]);
|
||||
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user