[Perf] Shared Media, Global Search: Improve media loading

This commit is contained in:
Alexander Zinchuk 2022-02-02 22:48:59 +01:00
parent c95b404085
commit 9c04a2eaa5
7 changed files with 95 additions and 22 deletions

View File

@ -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<OwnProps> = ({
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<HTMLDivElement>(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 (
<div id={`${idPrefix}${message.id}`} className="Media scroll-item" onClick={onClick ? handleClick : undefined}>
<img src={thumbDataUri} alt="" draggable={!isProtected} onContextMenu={isProtected ? stopEvent : undefined} />
<div
ref={ref}
id={`${idPrefix}${message.id}`}
className="Media scroll-item"
onClick={onClick ? handleClick : undefined}
>
<img
src={thumbDataUri}
alt=""
draggable={!isProtected}
decoding="async"
onContextMenu={isProtected ? stopEvent : undefined}
/>
<img
src={mediaBlobUrl}
className={buildClassName('full-media', transitionClassNames)}
alt=""
draggable={!isProtected}
decoding="async"
onContextMenu={isProtected ? stopEvent : undefined}
/>
{video && <span className="video-duration">{video.isGif ? 'GIF' : formatMediaDuration(video.duration)}</span>}

View File

@ -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<OwnProps> = ({
message, senderTitle, isProtected, onMessageClick,
message, senderTitle, isProtected, observeIntersection, onMessageClick,
}) => {
const lang = useLang();
@ -85,7 +89,7 @@ const WebLink: FC<OwnProps> = ({
dir={lang.isRtl ? 'rtl' : undefined}
>
{photo && (
<Media message={message} isProtected={isProtected} />
<Media message={message} isProtected={isProtected} observeIntersection={observeIntersection} />
)}
<div className="content">
<Link isRtl={lang.isRtl} className="site-title" onClick={handleMessageClick}>

View File

@ -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<OwnProps & StateProps> = ({
@ -44,7 +47,16 @@ const FileResults: FC<OwnProps & StateProps> = ({
focusMessage,
} = getDispatch();
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(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<OwnProps & StateProps> = ({
smaller
sender={getSenderName(lang, message, chatsById, usersById)}
className="scroll-item"
onDateClick={handleMessageFocus}
isDownloading={activeDownloads[message.chatId]?.includes(message.id)}
observeIntersection={observeIntersectionForMedia}
onDateClick={handleMessageFocus}
/>
</div>
);
@ -104,7 +117,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
return (
<div className="LeftSearch">
<div ref={containerRef} className="LeftSearch">
<InfiniteScroll
className="search-content documents-list custom-scroll"
items={foundMessages}

View File

@ -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';
@ -13,6 +13,7 @@ import { getSenderName } from './helpers/getSenderName';
import { throttle } from '../../../util/schedulers';
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import useLang from '../../../hooks/useLang';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import InfiniteScroll from '../../ui/InfiniteScroll';
import WebLink from '../../common/WebLink';
@ -24,6 +25,8 @@ export type OwnProps = {
};
const CURRENT_TYPE = 'links';
const INTERSECTION_THROTTLE = 500;
const runThrottled = throttle((cb) => cb(), 500, true);
const LinkResults: FC<OwnProps & StateProps> = ({
@ -42,7 +45,16 @@ const LinkResults: FC<OwnProps & StateProps> = ({
focusMessage,
} = getDispatch();
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(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<OwnProps & StateProps> = ({
message={message}
senderTitle={getSenderName(lang, message, chatsById, usersById)}
isProtected={isChatProtected || message.isProtected}
observeIntersection={observeIntersectionForMedia}
onMessageClick={handleMessageFocus}
/>
</div>
@ -101,7 +114,7 @@ const LinkResults: FC<OwnProps & StateProps> = ({
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
return (
<div className="LeftSearch">
<div ref={containerRef} className="LeftSearch">
<InfiniteScroll
className="search-content documents-list custom-scroll"
items={foundMessages}

View File

@ -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';
@ -12,6 +12,7 @@ import buildClassName from '../../../util/buildClassName';
import { throttle } from '../../../util/schedulers';
import useLang from '../../../hooks/useLang';
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import InfiniteScroll from '../../ui/InfiniteScroll';
import Media from '../../common/Media';
@ -24,6 +25,8 @@ export type OwnProps = {
};
const CURRENT_TYPE = 'media';
const INTERSECTION_THROTTLE = 500;
const runThrottled = throttle((cb) => cb(), 500, true);
const MediaResults: FC<OwnProps & StateProps> = ({
@ -40,8 +43,16 @@ const MediaResults: FC<OwnProps & StateProps> = ({
openMediaViewer,
} = getDispatch();
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(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<OwnProps & StateProps> = ({
idPrefix="search-media"
message={message}
isProtected={isChatProtected || message.isProtected}
observeIntersection={observeIntersectionForMedia}
onClick={handleSelectMedia}
/>
))}
@ -110,7 +122,7 @@ const MediaResults: FC<OwnProps & StateProps> = ({
);
return (
<div className="LeftSearch">
<div ref={containerRef} className="LeftSearch">
<InfiniteScroll
className={classNames}
items={foundMessages}

View File

@ -61,6 +61,7 @@ import DeleteMemberModal from './DeleteMemberModal';
import GroupChatInfo from '../common/GroupChatInfo';
import './Profile.scss';
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
type OwnProps = {
chatId: string;
@ -104,6 +105,7 @@ const TABS = [
];
const HIDDEN_RENDER_DELAY = 1000;
const INTERSECTION_THROTTLE = 500;
const Profile: FC<OwnProps & StateProps> = ({
chatId,
@ -179,6 +181,11 @@ const Profile: FC<OwnProps & StateProps> = ({
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<OwnProps & StateProps> = ({
key={id}
message={chatMessages[id]}
isProtected={isChatProtected || chatMessages[id].isProtected}
observeIntersection={observeIntersectionForMedia}
onClick={handleSelectMedia}
/>
))
@ -336,8 +344,9 @@ const Profile: FC<OwnProps & StateProps> = ({
withDate
smaller
className="scroll-item"
onDateClick={handleMessageFocus}
isDownloading={activeDownloadIds.includes(id)}
observeIntersection={observeIntersectionForMedia}
onDateClick={handleMessageFocus}
/>
))
) : resultType === 'links' ? (
@ -346,6 +355,7 @@ const Profile: FC<OwnProps & StateProps> = ({
key={id}
message={chatMessages[id]}
isProtected={isChatProtected || chatMessages[id].isProtected}
observeIntersection={observeIntersectionForMedia}
onMessageClick={handleMessageFocus}
/>
))

View File

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