[Perf] Shared Media, Global Search: Improve media loading
This commit is contained in:
parent
c95b404085
commit
9c04a2eaa5
@ -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>}
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
))
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user