From 6a57908045f3afe4415a8bac80bbf6a61100324a Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Sun, 23 Jul 2023 10:07:54 +0200 Subject: [PATCH] [Perf] Message: Delay some effects while opening message list --- src/components/middle/MessageListContent.tsx | 4 + src/components/middle/message/Message.tsx | 132 ++++++++++-------- .../middle/message/hooks/useOuterHandlers.ts | 6 +- src/hooks/useContextMenuHandlers.ts | 8 +- 4 files changed, 91 insertions(+), 59 deletions(-) diff --git a/src/components/middle/MessageListContent.tsx b/src/components/middle/MessageListContent.tsx index f355706c9..606ce4bd4 100644 --- a/src/components/middle/MessageListContent.tsx +++ b/src/components/middle/MessageListContent.tsx @@ -22,6 +22,7 @@ import { preventMessageInputBlur } from './helpers/preventMessageInputBlur'; import useScrollHooks from './hooks/useScrollHooks'; import useMessageObservers from './hooks/useMessageObservers'; import usePrevious from '../../hooks/usePrevious'; +import useDerivedSignal from '../../hooks/useDerivedSignal'; import Message from './message/Message'; import SponsoredMessage from './message/SponsoredMessage'; @@ -89,6 +90,8 @@ const MessageListContent: FC = ({ }) => { const { openHistoryCalendar } = getActions(); + const getIsReady = useDerivedSignal(isReady); + const { observeIntersectionForReading, observeIntersectionForLoading, @@ -237,6 +240,7 @@ const MessageListContent: FC = ({ isLastInList={position.isLastInList} memoFirstUnreadIdRef={memoFirstUnreadIdRef} onPinnedIntersectionChange={onPinnedIntersectionChange} + getIsMessageListReady={getIsReady} />, message.id === threadTopMessageId && (
diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index 972f03555..2abc3727d 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -1,11 +1,6 @@ import type { FC } from '../../../lib/teact/teact'; import React, { - memo, - useCallback, - useEffect, - useMemo, - useRef, - useState, + memo, useCallback, useEffect, useMemo, useRef, useState, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { @@ -33,6 +28,7 @@ import { AudioOrigin } from '../../../types'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import { useOnIntersect } from '../../../hooks/useIntersectionObserver'; import type { PinnedIntersectionChangedCallback } from '../hooks/usePinnedMessage'; +import type { Signal } from '../../../util/signals'; import { IS_ANDROID, IS_TRANSLATION_SUPPORTED } from '../../../util/windowEnvironment'; import { EMOJI_STATUS_LOOP_LIMIT, GENERAL_TOPIC_ID, IS_ELECTRON } from '../../../config'; @@ -191,6 +187,7 @@ type OwnProps = isJustAdded: boolean; memoFirstUnreadIdRef: { current: number | undefined }; onPinnedIntersectionChange: PinnedIntersectionChangedCallback; + getIsMessageListReady: Signal; } & MessagePositionProperties; @@ -366,6 +363,7 @@ const Message: FC = ({ withStickerEffects, isConnected, onPinnedIntersectionChange, + getIsMessageListReady, }) => { const { toggleMessageSelection, @@ -400,7 +398,13 @@ const Message: FC = ({ handleContextMenu: onContextMenu, handleContextMenuClose, handleContextMenuHide, - } = useContextMenuHandlers(ref, isTouchScreen && isInSelectMode, !IS_ELECTRON, IS_ANDROID); + } = useContextMenuHandlers( + ref, + isTouchScreen && isInSelectMode, + !IS_ELECTRON, + IS_ANDROID, + getIsMessageListReady, + ); useEffect(() => { if (isContextMenuOpen) { @@ -512,6 +516,7 @@ const Message: FC = ({ isContextMenuShown, quickReactionRef, isInDocumentGroupNotLast, + getIsMessageListReady, ); const { @@ -716,11 +721,6 @@ const Message: FC = ({ } }, [hasUnreadReaction, messageId, animateUnreadReaction]); - let style = ''; - let calculatedWidth; - let reactionsMaxWidth; - let contentWidth: number | undefined; - let noMediaCorners = false; const albumLayout = useMemo(() => { return isAlbum ? calculateAlbumLayout(isOwn, Boolean(asForwarded), Boolean(noAvatars), album!, isMobile) @@ -728,56 +728,76 @@ const Message: FC = ({ }, [isAlbum, isOwn, asForwarded, noAvatars, album, isMobile]); const extraPadding = asForwarded ? 28 : 0; - if (!isAlbum && (photo || video || invoice?.extendedMedia)) { - let width: number | undefined; - if (photo) { - width = calculateMediaDimensions(message, asForwarded, noAvatars, isMobile).width; - } else if (video) { - if (video.isRound) { - width = ROUND_VIDEO_DIMENSIONS_PX; - } else { - width = calculateMediaDimensions(message, asForwarded, noAvatars, isMobile).width; - } - } else if (invoice?.extendedMedia && ( - invoice.extendedMedia.width && invoice.extendedMedia.height - )) { - const { width: previewWidth, height: previewHeight } = invoice.extendedMedia; - width = calculateDimensionsForMessageMedia({ - width: previewWidth, - height: previewHeight, - fromOwnMessage: isOwn, - asForwarded, - noAvatars, - isMobile, - }).width; - } - if (width) { - if (width < MIN_MEDIA_WIDTH_WITH_TEXT) { - contentWidth = width; + const sizeCalculations = useMemo(() => { + let calculatedWidth; + let contentWidth: number | undefined; + let noMediaCorners = false; + let style = ''; + let reactionsMaxWidth; + + if (!isAlbum && (photo || video || invoice?.extendedMedia)) { + let width: number | undefined; + if (photo) { + width = calculateMediaDimensions(message, asForwarded, noAvatars, isMobile).width; + } else if (video) { + if (video.isRound) { + width = ROUND_VIDEO_DIMENSIONS_PX; + } else { + width = calculateMediaDimensions(message, asForwarded, noAvatars, isMobile).width; + } + } else if (invoice?.extendedMedia && ( + invoice.extendedMedia.width && invoice.extendedMedia.height + )) { + const { width: previewWidth, height: previewHeight } = invoice.extendedMedia; + width = calculateDimensionsForMessageMedia({ + width: previewWidth, + height: previewHeight, + fromOwnMessage: isOwn, + asForwarded, + noAvatars, + isMobile, + }).width; } - calculatedWidth = Math.max(getMinMediaWidth(text?.text, isMediaWithCommentButton), width); - if (invoice?.extendedMedia && calculatedWidth - width > NO_MEDIA_CORNERS_THRESHOLD) { + + if (width) { + if (width < MIN_MEDIA_WIDTH_WITH_TEXT) { + contentWidth = width; + } + calculatedWidth = Math.max(getMinMediaWidth(text?.text, isMediaWithCommentButton), width); + if (invoice?.extendedMedia && calculatedWidth - width > NO_MEDIA_CORNERS_THRESHOLD) { + noMediaCorners = true; + } + } + } else if (albumLayout) { + calculatedWidth = Math.max( + getMinMediaWidth(text?.text, isMediaWithCommentButton), albumLayout.containerStyle.width, + ); + if (calculatedWidth - albumLayout.containerStyle.width > NO_MEDIA_CORNERS_THRESHOLD) { noMediaCorners = true; } } - } else if (albumLayout) { - calculatedWidth = Math.max( - getMinMediaWidth(text?.text, isMediaWithCommentButton), albumLayout.containerStyle.width, - ); - if (calculatedWidth - albumLayout.containerStyle.width > NO_MEDIA_CORNERS_THRESHOLD) { - noMediaCorners = true; - } - } - if (calculatedWidth) { - style = `width: ${calculatedWidth + extraPadding}px`; - reactionsMaxWidth = calculatedWidth + EXTRA_SPACE_FOR_REACTIONS; - } else if (sticker && !hasSubheader) { - const { width } = getStickerDimensions(sticker, isMobile); - style = `width: ${width + extraPadding}px`; - reactionsMaxWidth = width + EXTRA_SPACE_FOR_REACTIONS; - } + if (calculatedWidth) { + style = `width: ${calculatedWidth + extraPadding}px`; + reactionsMaxWidth = calculatedWidth + EXTRA_SPACE_FOR_REACTIONS; + } else if (sticker && !hasSubheader) { + const { width } = getStickerDimensions(sticker, isMobile); + style = `width: ${width + extraPadding}px`; + reactionsMaxWidth = width + EXTRA_SPACE_FOR_REACTIONS; + } + + return { + contentWidth, noMediaCorners, style, reactionsMaxWidth, + }; + }, [ + albumLayout, asForwarded, extraPadding, hasSubheader, invoice?.extendedMedia, isAlbum, isMediaWithCommentButton, + isMobile, isOwn, message, noAvatars, photo, sticker, text?.text, video, + ]); + + const { + contentWidth, noMediaCorners, style, reactionsMaxWidth, + } = sizeCalculations; function renderAvatar() { const hiddenName = (!avatarPeer && forwardInfo) ? forwardInfo.hiddenUserName : undefined; diff --git a/src/components/middle/message/hooks/useOuterHandlers.ts b/src/components/middle/message/hooks/useOuterHandlers.ts index d5e206537..4ee186971 100644 --- a/src/components/middle/message/hooks/useOuterHandlers.ts +++ b/src/components/middle/message/hooks/useOuterHandlers.ts @@ -1,5 +1,7 @@ import type { RefObject } from 'react'; import type React from '../../../../lib/teact/teact'; +import type { Signal } from '../../../../util/signals'; + import { useEffect, useRef } from '../../../../lib/teact/teact'; import { requestMeasure } from '../../../../lib/fasterdom/fasterdom'; import { getActions } from '../../../../global'; @@ -33,6 +35,7 @@ export default function useOuterHandlers( isContextMenuShown: boolean, quickReactionRef: RefObject, shouldHandleMouseLeave: boolean, + getIsMessageListReady: Signal, ) { const { setReplyingToId, sendDefaultReaction } = getActions(); @@ -142,7 +145,7 @@ export default function useOuterHandlers( } useEffect(() => { - if (!IS_TOUCH_ENV || isInSelectMode || !canReply || isContextMenuShown) { + if (!IS_TOUCH_ENV || isInSelectMode || !canReply || isContextMenuShown || !getIsMessageListReady()) { return undefined; } @@ -176,6 +179,7 @@ export default function useOuterHandlers( }); }, [ containerRef, isInSelectMode, messageId, setReplyingToId, markSwiped, unmarkSwiped, canReply, isContextMenuShown, + getIsMessageListReady, ]); function handleMouseLeave(e: React.MouseEvent) { diff --git a/src/hooks/useContextMenuHandlers.ts b/src/hooks/useContextMenuHandlers.ts index e14f06a60..944a6415e 100644 --- a/src/hooks/useContextMenuHandlers.ts +++ b/src/hooks/useContextMenuHandlers.ts @@ -1,4 +1,5 @@ import type { RefObject } from 'react'; +import type { Signal } from '../util/signals'; import { useState, useEffect } from '../lib/teact/teact'; import { addExtraClass, removeExtraClass } from '../lib/teact/teact-dom'; import { requestMutation } from '../lib/fasterdom/fasterdom'; @@ -23,6 +24,7 @@ const useContextMenuHandlers = ( isMenuDisabled?: boolean, shouldDisableOnLink?: boolean, shouldDisableOnLongTap?: boolean, + getIsReady?: Signal, ) => { const [isContextMenuOpen, setIsContextMenuOpen] = useState(false); const [contextMenuPosition, setContextMenuPosition] = useState(undefined); @@ -65,7 +67,7 @@ const useContextMenuHandlers = ( // Support context menu on touch devices useEffect(() => { - if (isMenuDisabled || !IS_TOUCH_ENV || shouldDisableOnLongTap) { + if (isMenuDisabled || !IS_TOUCH_ENV || shouldDisableOnLongTap || (getIsReady && !getIsReady())) { return undefined; } @@ -149,7 +151,9 @@ const useContextMenuHandlers = ( element.removeEventListener('touchend', clearLongPressTimer, true); element.removeEventListener('touchmove', clearLongPressTimer); }; - }, [contextMenuPosition, isMenuDisabled, shouldDisableOnLongTap, elementRef, shouldDisableOnLink]); + }, [ + contextMenuPosition, isMenuDisabled, shouldDisableOnLongTap, elementRef, shouldDisableOnLink, getIsReady, + ]); return { isContextMenuOpen,