[Perf] Reduce amount of global containers (DeleteChatModal, Transition, SafeLink, Audio)

This commit is contained in:
Alexander Zinchuk 2021-06-24 14:46:12 +03:00
parent 094a235373
commit 3f5c8edceb
11 changed files with 80 additions and 60 deletions

View File

@ -1,7 +1,6 @@
import React, { import React, {
FC, memo, useCallback, useEffect, useMemo, useRef, useState, FC, memo, useCallback, useEffect, useMemo, useRef, useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { withGlobal } from '../../lib/teact/teactn';
import { import {
ApiAudio, ApiMessage, ApiVoice, ApiAudio, ApiMessage, ApiVoice,
@ -24,7 +23,6 @@ import { renderWaveformToDataUri } from './helpers/waveform';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import renderText from './helpers/renderText'; import renderText from './helpers/renderText';
import { decodeWaveform, interpolateArray } from '../../util/waveform'; import { decodeWaveform, interpolateArray } from '../../util/waveform';
import { selectTheme } from '../../modules/selectors';
import useMediaWithDownloadProgress from '../../hooks/useMediaWithDownloadProgress'; import useMediaWithDownloadProgress from '../../hooks/useMediaWithDownloadProgress';
import useShowTransition from '../../hooks/useShowTransition'; import useShowTransition from '../../hooks/useShowTransition';
import useBuffering from '../../hooks/useBuffering'; import useBuffering from '../../hooks/useBuffering';
@ -39,10 +37,11 @@ import Link from '../ui/Link';
import './Audio.scss'; import './Audio.scss';
type OwnProps = { type OwnProps = {
theme: ISettings['theme'];
message: ApiMessage; message: ApiMessage;
senderTitle?: string; senderTitle?: string;
uploadProgress?: number; uploadProgress?: number;
renderingFor?: 'searchResult' | 'sharedMedia'; target?: 'searchResult' | 'sharedMedia';
date?: number; date?: number;
lastSyncTime?: number; lastSyncTime?: number;
className?: string; className?: string;
@ -54,10 +53,6 @@ type OwnProps = {
onDateClick?: (messageId: number, chatId: number) => void; onDateClick?: (messageId: number, chatId: number) => void;
}; };
type StateProps = {
theme: ISettings['theme'];
};
interface ISeekMethods { interface ISeekMethods {
handleStartSeek: (e: React.MouseEvent<HTMLElement>) => void; handleStartSeek: (e: React.MouseEvent<HTMLElement>) => void;
handleSeek: (e: React.MouseEvent<HTMLElement>) => void; handleSeek: (e: React.MouseEvent<HTMLElement>) => void;
@ -70,12 +65,12 @@ const MAX_SPIKES = IS_SINGLE_COLUMN_LAYOUT ? 50 : 75;
// This is needed for browsers requiring user interaction before playing. // This is needed for browsers requiring user interaction before playing.
const PRELOAD = true; const PRELOAD = true;
const Audio: FC<OwnProps & StateProps> = ({ const Audio: FC<OwnProps> = ({
theme, theme,
message, message,
senderTitle, senderTitle,
uploadProgress, uploadProgress,
renderingFor, target,
date, date,
lastSyncTime, lastSyncTime,
className, className,
@ -229,8 +224,8 @@ const Audio: FC<OwnProps & StateProps> = ({
const fullClassName = buildClassName( const fullClassName = buildClassName(
'Audio media-inner', 'Audio media-inner',
className, className,
isOwn && !renderingFor && 'own', isOwn && !target && 'own',
renderingFor && 'bigger', target && 'bigger',
isSelected && 'audio-is-selected', isSelected && 'audio-is-selected',
); );
@ -287,7 +282,7 @@ const Audio: FC<OwnProps & StateProps> = ({
<Button <Button
round round
ripple={!IS_SINGLE_COLUMN_LAYOUT} ripple={!IS_SINGLE_COLUMN_LAYOUT}
size={renderingFor ? 'smaller' : 'tiny'} size={target ? 'smaller' : 'tiny'}
className={buttonClassNames.join(' ')} className={buttonClassNames.join(' ')}
ariaLabel={isPlaying ? 'Pause audio' : 'Play audio'} ariaLabel={isPlaying ? 'Pause audio' : 'Play audio'}
onClick={handleButtonClick} onClick={handleButtonClick}
@ -301,7 +296,7 @@ const Audio: FC<OwnProps & StateProps> = ({
<ProgressSpinner <ProgressSpinner
progress={transferProgress} progress={transferProgress}
transparent transparent
size={renderingFor ? 'm' : 's'} size={target ? 'm' : 's'}
onClick={isLoadingForPlaying ? handleButtonClick : undefined} onClick={isLoadingForPlaying ? handleButtonClick : undefined}
noCross={!isLoadingForPlaying} noCross={!isLoadingForPlaying}
/> />
@ -318,12 +313,12 @@ const Audio: FC<OwnProps & StateProps> = ({
<i className={isDownloadStarted ? 'icon-close' : 'icon-arrow-down'} /> <i className={isDownloadStarted ? 'icon-close' : 'icon-arrow-down'} />
</Button> </Button>
)} )}
{renderingFor === 'searchResult' && renderSearchResult()} {target === 'searchResult' && renderSearchResult()}
{renderingFor !== 'searchResult' && audio && renderAudio( {target !== 'searchResult' && audio && renderAudio(
lang, audio, isPlaying, playProgress, bufferedProgress, seekHandlers, date, lang, audio, isPlaying, playProgress, bufferedProgress, seekHandlers, date,
onDateClick ? handleDateClick : undefined, onDateClick ? handleDateClick : undefined,
)} )}
{renderingFor !== 'searchResult' && voice && renderVoice(voice, renderedWaveform, isMediaUnread)} {target !== 'searchResult' && voice && renderVoice(voice, renderedWaveform, isMediaUnread)}
</div> </div>
); );
}; };
@ -457,6 +452,4 @@ function renderSeekline(
); );
} }
export default memo(withGlobal<OwnProps>((global) => ({ export default memo(Audio);
theme: selectTheme(global),
}))(Audio));

View File

@ -28,6 +28,7 @@ export type OwnProps = {
isOpen: boolean; isOpen: boolean;
chat: ApiChat; chat: ApiChat;
onClose: () => void; onClose: () => void;
onCloseAnimationEnd?: () => void;
}; };
type StateProps = { type StateProps = {
@ -53,6 +54,7 @@ const DeleteChatModal: FC<OwnProps & StateProps & DispatchProps> = ({
canDeleteForAll, canDeleteForAll,
contactName, contactName,
onClose, onClose,
onCloseAnimationEnd,
leaveChannel, leaveChannel,
deleteHistory, deleteHistory,
deleteChannel, deleteChannel,
@ -147,9 +149,10 @@ const DeleteChatModal: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
onClose={onClose}
className="DeleteChatModal" className="DeleteChatModal"
header={renderHeader()} header={renderHeader()}
onClose={onClose}
onCloseAnimationEnd={onCloseAnimationEnd}
> >
{renderMessage()} {renderMessage()}
{canDeleteForAll && ( {canDeleteForAll && (

View File

@ -1,10 +1,8 @@
import React, { FC, memo, useCallback } from '../../lib/teact/teact'; import React, { FC, memo, useCallback } from '../../lib/teact/teact';
import { withGlobal } from '../../lib/teact/teactn'; import { getDispatch } from '../../lib/teact/teactn';
import convertPunycode from '../../lib/punycode'; import convertPunycode from '../../lib/punycode';
import { GlobalActions } from '../../global/types';
import { DEBUG, RE_TME_INVITE_LINK, RE_TME_LINK } from '../../config'; import { DEBUG, RE_TME_INVITE_LINK, RE_TME_LINK } from '../../config';
import { pick } from '../../util/iteratees';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
type OwnProps = { type OwnProps = {
@ -15,17 +13,15 @@ type OwnProps = {
isRtl?: boolean; isRtl?: boolean;
}; };
type DispatchProps = Pick<GlobalActions, 'toggleSafeLinkModal' | 'openTelegramLink'>; const SafeLink: FC<OwnProps> = ({
const SafeLink: FC<OwnProps & DispatchProps> = ({
url, url,
text, text,
className, className,
children, children,
isRtl, isRtl,
toggleSafeLinkModal,
openTelegramLink,
}) => { }) => {
const { toggleSafeLinkModal, openTelegramLink } = getDispatch();
const content = children || text; const content = children || text;
const isNotSafe = url !== content; const isNotSafe = url !== content;
@ -113,9 +109,4 @@ function getDomain(url?: string) {
return undefined; return undefined;
} }
export default memo(withGlobal<OwnProps>( export default memo(SafeLink);
undefined,
(setGlobal, actions): DispatchProps => pick(actions, [
'toggleSafeLinkModal', 'openTelegramLink',
]),
)(SafeLink));

View File

@ -109,6 +109,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag(); const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
const [shouldRenderDeleteModal, markRenderDeleteModal, unmarkRenderDeleteModal] = useFlag();
const { lastMessage, typingStatus } = chat || {}; const { lastMessage, typingStatus } = chat || {};
const isAction = lastMessage && isActionMessage(lastMessage); const isAction = lastMessage && isActionMessage(lastMessage);
@ -171,10 +172,15 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
focusLastMessage, focusLastMessage,
]); ]);
function handleDelete() {
markRenderDeleteModal();
openDeleteModal();
}
const contextActions = useChatContextActions({ const contextActions = useChatContextActions({
chat, chat,
privateChatUser, privateChatUser,
handleDelete: openDeleteModal, handleDelete,
folderId, folderId,
isPinned, isPinned,
isMuted, isMuted,
@ -277,11 +283,14 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
<Badge chat={chat} isPinned={isPinned} isMuted={isMuted} /> <Badge chat={chat} isPinned={isPinned} isMuted={isMuted} />
</div> </div>
</div> </div>
<DeleteChatModal {shouldRenderDeleteModal && (
isOpen={isDeleteModalOpen} <DeleteChatModal
onClose={closeDeleteModal} isOpen={isDeleteModalOpen}
chat={chat} onClose={closeDeleteModal}
/> onCloseAnimationEnd={unmarkRenderDeleteModal}
chat={chat}
/>
)}
</ListItem> </ListItem>
); );
}; };

View File

@ -31,6 +31,7 @@ type DispatchProps = Pick<GlobalActions, ('searchMessagesGlobal' | 'focusMessage
const runThrottled = throttle((cb) => cb(), 500, true); const runThrottled = throttle((cb) => cb(), 500, true);
const AudioResults: FC<OwnProps & StateProps & DispatchProps> = ({ const AudioResults: FC<OwnProps & StateProps & DispatchProps> = ({
theme,
isVoice, isVoice,
searchQuery, searchQuery,
searchChatId, searchChatId,
@ -94,8 +95,9 @@ const AudioResults: FC<OwnProps & StateProps & DispatchProps> = ({
)} )}
<Audio <Audio
key={message.id} key={message.id}
theme={theme}
message={message} message={message}
renderingFor="searchResult" target="searchResult"
senderTitle={getSenderName(lang, message, chatsById, usersById)} senderTitle={getSenderName(lang, message, chatsById, usersById)}
date={message.date} date={message.date}
lastSyncTime={lastSyncTime} lastSyncTime={lastSyncTime}

View File

@ -2,8 +2,12 @@ import { GlobalState } from '../../../../global/types';
import { import {
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiUser, ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiUser,
} from '../../../../api/types'; } from '../../../../api/types';
import { ISettings } from '../../../../types';
import { selectTheme } from '../../../../modules/selectors';
export type StateProps = { export type StateProps = {
theme: ISettings['theme'];
isLoading?: boolean; isLoading?: boolean;
chatsById: Record<number, ApiChat>; chatsById: Record<number, ApiChat>;
usersById: Record<number, ApiUser>; usersById: Record<number, ApiUser>;
@ -30,6 +34,7 @@ export function createMapStateToProps(type: ApiGlobalMessageSearchType) {
const { foundIds } = (resultsByType && resultsByType[currentType]) || {}; const { foundIds } = (resultsByType && resultsByType[currentType]) || {};
return { return {
theme: selectTheme(global),
isLoading: foundIds === undefined isLoading: foundIds === undefined
|| (fetchingStatus ? Boolean(fetchingStatus.chats || fetchingStatus.messages) : false), || (fetchingStatus ? Boolean(fetchingStatus.chats || fetchingStatus.messages) : false),
chatsById, chatsById,

View File

@ -19,7 +19,9 @@ import {
ApiSticker, ApiSticker,
MAIN_THREAD_ID, MAIN_THREAD_ID,
} from '../../../api/types'; } from '../../../api/types';
import { FocusDirection, IAlbum, MediaViewerOrigin } from '../../../types'; import {
FocusDirection, IAlbum, ISettings, MediaViewerOrigin,
} from '../../../types';
import { IS_ANDROID } from '../../../util/environment'; import { IS_ANDROID } from '../../../util/environment';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
@ -40,7 +42,9 @@ import {
selectForwardedSender, selectForwardedSender,
selectThreadTopMessageId, selectThreadTopMessageId,
selectShouldAutoLoadMedia, selectShouldAutoLoadMedia,
selectShouldAutoPlayMedia, selectShouldLoopStickers, selectShouldAutoPlayMedia,
selectShouldLoopStickers,
selectTheme,
} from '../../../modules/selectors'; } from '../../../modules/selectors';
import { import {
getMessageContent, getMessageContent,
@ -117,6 +121,7 @@ type OwnProps = {
} & MessagePositionProperties; } & MessagePositionProperties;
type StateProps = { type StateProps = {
theme: ISettings['theme'];
forceSenderName?: boolean; forceSenderName?: boolean;
sender?: ApiUser | ApiChat; sender?: ApiUser | ApiChat;
originSender?: ApiUser | ApiChat; originSender?: ApiUser | ApiChat;
@ -179,6 +184,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
isFirstInDocumentGroup, isFirstInDocumentGroup,
isLastInDocumentGroup, isLastInDocumentGroup,
isLastInList, isLastInList,
theme,
forceSenderName, forceSenderName,
sender, sender,
originSender, originSender,
@ -601,6 +607,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
)} )}
{(audio || voice) && ( {(audio || voice) && (
<Audio <Audio
theme={theme}
message={message} message={message}
uploadProgress={uploadProgress} uploadProgress={uploadProgress}
lastSyncTime={lastSyncTime} lastSyncTime={lastSyncTime}
@ -907,6 +914,7 @@ export default memo(withGlobal<OwnProps>(
} }
return { return {
theme: selectTheme(global),
forceSenderName, forceSenderName,
sender, sender,
originSender, originSender,

View File

@ -28,8 +28,8 @@ type StateProps = {
type DispatchProps = Pick<GlobalActions, ('toggleMessageSelection')>; type DispatchProps = Pick<GlobalActions, ('toggleMessageSelection')>;
export default function withSelectControl(WrapedComponent: FC) { export default function withSelectControl(WrappedComponent: FC) {
const Component: FC<OwnProps & StateProps & DispatchProps> = (props) => { const ComponentWithSelectControl: FC<OwnProps & StateProps & DispatchProps> = (props) => {
const { const {
isInSelectMode, isInSelectMode,
isSelected, isSelected,
@ -77,7 +77,7 @@ export default function withSelectControl(WrapedComponent: FC) {
</div> </div>
)} )}
{/* eslint-disable-next-line react/jsx-props-no-spreading */} {/* eslint-disable-next-line react/jsx-props-no-spreading */}
<WrapedComponent {...newProps} /> <WrappedComponent {...newProps} />
</div> </div>
); );
}; };
@ -93,5 +93,5 @@ export default function withSelectControl(WrapedComponent: FC) {
(setGlobal, actions) => pick(actions, [ (setGlobal, actions) => pick(actions, [
'toggleMessageSelection', 'toggleMessageSelection',
]), ]),
)(Component)); )(ComponentWithSelectControl));
} }

View File

@ -11,6 +11,7 @@ import {
} from '../../api/types'; } from '../../api/types';
import { GlobalActions } from '../../global/types'; import { GlobalActions } from '../../global/types';
import { import {
ISettings,
MediaViewerOrigin, ProfileState, ProfileTabType, SharedMediaType, MediaViewerOrigin, ProfileState, ProfileTabType, SharedMediaType,
} from '../../types'; } from '../../types';
@ -29,6 +30,7 @@ import {
selectChat, selectChat,
selectCurrentMediaSearch, selectCurrentMediaSearch,
selectIsRightColumnShown, selectIsRightColumnShown,
selectTheme,
} from '../../modules/selectors'; } from '../../modules/selectors';
import { pick } from '../../util/iteratees'; import { pick } from '../../util/iteratees';
import { captureEvents, SwipeDirection } from '../../util/captureEvents'; import { captureEvents, SwipeDirection } from '../../util/captureEvents';
@ -63,6 +65,7 @@ type OwnProps = {
}; };
type StateProps = { type StateProps = {
theme: ISettings['theme'];
isChannel?: boolean; isChannel?: boolean;
resolvedUserId?: number; resolvedUserId?: number;
chatMessages?: Record<number, ApiMessage>; chatMessages?: Record<number, ApiMessage>;
@ -96,6 +99,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
chatId, chatId,
profileState, profileState,
onProfileStateChange, onProfileStateChange,
theme,
isChannel, isChannel,
resolvedUserId, resolvedUserId,
chatMessages, chatMessages,
@ -287,8 +291,9 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
viewportIds!.map((id) => chatMessages[id] && ( viewportIds!.map((id) => chatMessages[id] && (
<Audio <Audio
key={id} key={id}
renderingFor="sharedMedia" theme={theme}
message={chatMessages[id]} message={chatMessages[id]}
target="sharedMedia"
date={chatMessages[id].date} date={chatMessages[id].date}
lastSyncTime={lastSyncTime} lastSyncTime={lastSyncTime}
className="scroll-item" className="scroll-item"
@ -394,6 +399,7 @@ export default memo(withGlobal<OwnProps>(
} }
return { return {
theme: selectTheme(global),
isChannel, isChannel,
resolvedUserId, resolvedUserId,
chatMessages, chatMessages,

View File

@ -2,7 +2,7 @@ import { RefObject } from 'react';
import React, { import React, {
FC, useLayoutEffect, useRef, FC, useLayoutEffect, useRef,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { withGlobal } from '../../lib/teact/teactn'; import { getGlobal } from '../../lib/teact/teactn';
import { ANIMATION_END_DELAY } from '../../config'; import { ANIMATION_END_DELAY } from '../../config';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
@ -32,10 +32,6 @@ type OwnProps = {
children: ChildrenFn; children: ChildrenFn;
}; };
type StateProps = {
animationLevel: number;
};
const ANIMATION_DURATION = { const ANIMATION_DURATION = {
slide: 450, slide: 450,
'slide-reversed': 450, 'slide-reversed': 450,
@ -51,7 +47,7 @@ const ANIMATION_DURATION = {
const CLEANED_UP = Symbol('CLEANED_UP'); const CLEANED_UP = Symbol('CLEANED_UP');
const Transition: FC<OwnProps & StateProps> = ({ const Transition: FC<OwnProps> = ({
ref, ref,
activeKey, activeKey,
name, name,
@ -64,8 +60,10 @@ const Transition: FC<OwnProps & StateProps> = ({
onStart, onStart,
onStop, onStop,
children, children,
animationLevel,
}) => { }) => {
// No need for a container to update on change
const { animationLevel } = getGlobal().settings.byKey;
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
let containerRef = useRef<HTMLDivElement>(null); let containerRef = useRef<HTMLDivElement>(null);
if (ref) { if (ref) {
@ -255,7 +253,4 @@ const Transition: FC<OwnProps & StateProps> = ({
); );
}; };
export default withGlobal<OwnProps>((global) => { export default Transition;
const { animationLevel } = global.settings.byKey;
return { animationLevel };
})(Transition);

View File

@ -218,6 +218,14 @@ if (DEBUG) {
document.addEventListener('dblclick', () => { document.addEventListener('dblclick', () => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('GLOBAL CONTAINERS', orderBy(Object.values(containers), 'DEBUG_updates', 'desc')); console.log(
'GLOBAL CONTAINERS',
orderBy(
Array.from(containers.values())
.map(({ DEBUG_componentName, DEBUG_updates }) => ({ DEBUG_componentName, DEBUG_updates })),
'DEBUG_updates',
'desc',
),
);
}); });
} }