From 9c04a2eaa525747dcea1e7d68d7feb46340b0418 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 2 Feb 2022 22:48:59 +0100 Subject: [PATCH] [Perf] Shared Media, Global Search: Improve media loading --- src/components/common/Media.tsx | 33 ++++++++++++++++----- src/components/common/WebLink.tsx | 10 +++++-- src/components/left/search/FileResults.tsx | 19 ++++++++++-- src/components/left/search/LinkResults.tsx | 17 +++++++++-- src/components/left/search/MediaResults.tsx | 16 ++++++++-- src/components/right/Profile.tsx | 12 +++++++- src/modules/actions/api/localSearch.ts | 10 ++++--- 7 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/components/common/Media.tsx b/src/components/common/Media.tsx index b3254f1a9..10005ad10 100644 --- a/src/components/common/Media.tsx +++ b/src/components/common/Media.tsx @@ -1,4 +1,4 @@ -import React, { FC, memo, useCallback } from '../../lib/teact/teact'; +import React, { FC, memo, useCallback, useRef } from '../../lib/teact/teact'; import { ApiMessage } from '../../api/types'; @@ -12,6 +12,7 @@ import { import buildClassName from '../../util/buildClassName'; import useMedia from '../../hooks/useMedia'; import useMediaTransition from '../../hooks/useMediaTransition'; +import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver'; import './Media.scss'; @@ -19,6 +20,7 @@ type OwnProps = { message: ApiMessage; idPrefix?: string; isProtected?: boolean; + observeIntersection?: ObserveFn; onClick?: (messageId: number, chatId: string) => void; }; @@ -26,26 +28,43 @@ const Media: FC = ({ message, idPrefix = 'shared-media', isProtected, + observeIntersection, onClick, }) => { - const handleClick = useCallback(() => { - onClick!(message.id, message.chatId); - }, [message.id, message.chatId, onClick]); + // eslint-disable-next-line no-null/no-null + const ref = useRef(null); + const isIntersecting = useIsIntersecting(ref, observeIntersection); const thumbDataUri = getMessageMediaThumbDataUri(message); - const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'pictogram')); + const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'pictogram'), !isIntersecting); const transitionClassNames = useMediaTransition(mediaBlobUrl); const video = getMessageVideo(message); + const handleClick = useCallback(() => { + onClick!(message.id, message.chatId); + }, [message.id, message.chatId, onClick]); + return ( -
- +
+ {video && {video.isGif ? 'GIF' : formatMediaDuration(video.duration)}} diff --git a/src/components/common/WebLink.tsx b/src/components/common/WebLink.tsx index bbb3f7524..51cbb0812 100644 --- a/src/components/common/WebLink.tsx +++ b/src/components/common/WebLink.tsx @@ -1,6 +1,7 @@ import React, { FC, memo, useCallback } from '../../lib/teact/teact'; import { ApiMessage, ApiWebPage } from '../../api/types'; +import { ObserveFn } from '../../hooks/useIntersectionObserver'; import { getFirstLinkInMessage, getMessageText, @@ -25,13 +26,16 @@ type OwnProps = { message: ApiMessage; senderTitle?: string; isProtected?: boolean; + observeIntersection?: ObserveFn; onMessageClick: (messageId: number, chatId: string) => void; }; -type ApiWebPageWithFormatted = ApiWebPage & { formattedDescription?: TextPart[] }; +type ApiWebPageWithFormatted = + ApiWebPage + & { formattedDescription?: TextPart[] }; const WebLink: FC = ({ - message, senderTitle, isProtected, onMessageClick, + message, senderTitle, isProtected, observeIntersection, onMessageClick, }) => { const lang = useLang(); @@ -85,7 +89,7 @@ const WebLink: FC = ({ dir={lang.isRtl ? 'rtl' : undefined} > {photo && ( - + )}
diff --git a/src/components/left/search/FileResults.tsx b/src/components/left/search/FileResults.tsx index ff751e63a..6a1fdd019 100644 --- a/src/components/left/search/FileResults.tsx +++ b/src/components/left/search/FileResults.tsx @@ -1,5 +1,5 @@ import React, { - FC, memo, useCallback, useMemo, + FC, memo, useCallback, useMemo, useRef, } from '../../../lib/teact/teact'; import { getDispatch, withGlobal } from '../../../lib/teact/teactn'; @@ -15,6 +15,7 @@ import { throttle } from '../../../util/schedulers'; import { getMessageDocument } from '../../../modules/helpers'; import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import useLang from '../../../hooks/useLang'; +import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import Document from '../../common/Document'; import InfiniteScroll from '../../ui/InfiniteScroll'; @@ -26,6 +27,8 @@ export type OwnProps = { }; const CURRENT_TYPE = 'documents'; +const INTERSECTION_THROTTLE = 500; + const runThrottled = throttle((cb) => cb(), 500, true); const FileResults: FC = ({ @@ -44,7 +47,16 @@ const FileResults: FC = ({ focusMessage, } = getDispatch(); + // eslint-disable-next-line no-null/no-null + const containerRef = useRef(null); + const lang = useLang(); + + const { observe: observeIntersectionForMedia } = useIntersectionObserver({ + rootRef: containerRef, + throttleMs: INTERSECTION_THROTTLE, + }); + const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { if (lastSyncTime && direction === LoadMoreDirection.Backwards) { runThrottled(() => { @@ -93,8 +105,9 @@ const FileResults: FC = ({ smaller sender={getSenderName(lang, message, chatsById, usersById)} className="scroll-item" - onDateClick={handleMessageFocus} isDownloading={activeDownloads[message.chatId]?.includes(message.id)} + observeIntersection={observeIntersectionForMedia} + onDateClick={handleMessageFocus} />
); @@ -104,7 +117,7 @@ const FileResults: FC = ({ const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading; return ( -
+
cb(), 500, true); const LinkResults: FC = ({ @@ -42,7 +45,16 @@ const LinkResults: FC = ({ focusMessage, } = getDispatch(); + // eslint-disable-next-line no-null/no-null + const containerRef = useRef(null); + const lang = useLang(); + + const { observe: observeIntersectionForMedia } = useIntersectionObserver({ + rootRef: containerRef, + throttleMs: INTERSECTION_THROTTLE, + }); + const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { if (lastSyncTime && direction === LoadMoreDirection.Backwards) { runThrottled(() => { @@ -91,6 +103,7 @@ const LinkResults: FC = ({ message={message} senderTitle={getSenderName(lang, message, chatsById, usersById)} isProtected={isChatProtected || message.isProtected} + observeIntersection={observeIntersectionForMedia} onMessageClick={handleMessageFocus} />
@@ -101,7 +114,7 @@ const LinkResults: FC = ({ const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading; return ( -
+
cb(), 500, true); const MediaResults: FC = ({ @@ -40,8 +43,16 @@ const MediaResults: FC = ({ openMediaViewer, } = getDispatch(); + // eslint-disable-next-line no-null/no-null + const containerRef = useRef(null); + const lang = useLang(); + const { observe: observeIntersectionForMedia } = useIntersectionObserver({ + rootRef: containerRef, + throttleMs: INTERSECTION_THROTTLE, + }); + const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { if (lastSyncTime && direction === LoadMoreDirection.Backwards) { runThrottled(() => { @@ -83,6 +94,7 @@ const MediaResults: FC = ({ idPrefix="search-media" message={message} isProtected={isChatProtected || message.isProtected} + observeIntersection={observeIntersectionForMedia} onClick={handleSelectMedia} /> ))} @@ -110,7 +122,7 @@ const MediaResults: FC = ({ ); return ( -
+
= ({ chatId, @@ -179,6 +181,11 @@ const Profile: FC = ({ const [cacheBuster, resetCacheBuster] = useCacheBuster(); + const { observe: observeIntersectionForMedia } = useIntersectionObserver({ + rootRef: containerRef, + throttleMs: INTERSECTION_THROTTLE, + }); + const handleTransitionStop = useCallback(() => { releaseTransitionFix(); resetCacheBuster(); @@ -325,6 +332,7 @@ const Profile: FC = ({ key={id} message={chatMessages[id]} isProtected={isChatProtected || chatMessages[id].isProtected} + observeIntersection={observeIntersectionForMedia} onClick={handleSelectMedia} /> )) @@ -336,8 +344,9 @@ const Profile: FC = ({ withDate smaller className="scroll-item" - onDateClick={handleMessageFocus} isDownloading={activeDownloadIds.includes(id)} + observeIntersection={observeIntersectionForMedia} + onDateClick={handleMessageFocus} /> )) ) : resultType === 'links' ? ( @@ -346,6 +355,7 @@ const Profile: FC = ({ key={id} message={chatMessages[id]} isProtected={isChatProtected || chatMessages[id].isProtected} + observeIntersection={observeIntersectionForMedia} onMessageClick={handleMessageFocus} /> )) diff --git a/src/modules/actions/api/localSearch.ts b/src/modules/actions/api/localSearch.ts index 939a6bb86..e2e6d50cc 100644 --- a/src/modules/actions/api/localSearch.ts +++ b/src/modules/actions/api/localSearch.ts @@ -126,11 +126,12 @@ async function searchSharedMedia( chatOrUser: ApiChat | ApiUser, type: SharedMediaType, offsetId?: number, + isBudgetPreload = false, ) { const result = await callApi('searchMessagesLocal', { chatOrUser, type, - limit: SHARED_MEDIA_SLICE, + limit: SHARED_MEDIA_SLICE * 2, offsetId, }); @@ -156,11 +157,12 @@ async function searchSharedMedia( global = addUsers(global, buildCollectionByKey(users, 'id')); global = updateLocalMediaSearchResults(global, chatOrUser.id, type, newFoundIds, totalCount, nextOffsetId); setGlobal(global); + + if (!isBudgetPreload) { + searchSharedMedia(chatOrUser, type, nextOffsetId, true); + } } -/** - * @param timestamp start of target date in seconds - */ async function searchMessagesByDate(chat: ApiChat, timestamp: number) { const messageId = await callApi('findFirstMessageIdAfterDate', { chat,