[Refactoring, Perf] Chat: Get rid of AvatarBadge container
This commit is contained in:
parent
eddb8016ea
commit
4c9815a394
@ -1,52 +0,0 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
|
||||||
import React, { memo } from '../../../lib/teact/teact';
|
|
||||||
import { withGlobal } from '../../../global';
|
|
||||||
|
|
||||||
import type { ApiChat } from '../../../api/types';
|
|
||||||
|
|
||||||
import { selectIsChatMuted } from '../../../global/helpers';
|
|
||||||
import {
|
|
||||||
selectChat,
|
|
||||||
selectNotifySettings,
|
|
||||||
selectNotifyExceptions,
|
|
||||||
selectIsForumPanelOpen,
|
|
||||||
} from '../../../global/selectors';
|
|
||||||
|
|
||||||
import ChatBadge from './ChatBadge';
|
|
||||||
|
|
||||||
type OwnProps = {
|
|
||||||
chatId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StateProps = {
|
|
||||||
chat?: ApiChat;
|
|
||||||
isMuted?: boolean;
|
|
||||||
isForumPanelActive?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AvatarBadge: FC<OwnProps & StateProps> = ({
|
|
||||||
chat,
|
|
||||||
isMuted,
|
|
||||||
isForumPanelActive,
|
|
||||||
}) => {
|
|
||||||
return chat && (
|
|
||||||
<div className="avatar-badge-wrapper">
|
|
||||||
<ChatBadge chat={chat} isMuted={isMuted} shouldShowOnlyMostImportant forceHidden={!isForumPanelActive} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
|
||||||
(global, { chatId }): StateProps => {
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
if (!chat) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
chat,
|
|
||||||
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
|
||||||
isForumPanelActive: selectIsForumPanelOpen(global),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
)(AvatarBadge));
|
|
||||||
@ -29,6 +29,7 @@ import {
|
|||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectDraft,
|
selectDraft,
|
||||||
|
selectIsForumPanelClosed,
|
||||||
selectNotifyExceptions,
|
selectNotifyExceptions,
|
||||||
selectNotifySettings,
|
selectNotifySettings,
|
||||||
selectOutgoingStatus,
|
selectOutgoingStatus,
|
||||||
@ -42,6 +43,7 @@ import buildClassName from '../../../util/buildClassName';
|
|||||||
import { createLocationHash } from '../../../util/routing';
|
import { createLocationHash } from '../../../util/routing';
|
||||||
|
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useSelectorSignal from '../../../hooks/useSelectorSignal';
|
||||||
import useChatContextActions from '../../../hooks/useChatContextActions';
|
import useChatContextActions from '../../../hooks/useChatContextActions';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useChatListEntry from './hooks/useChatListEntry';
|
import useChatListEntry from './hooks/useChatListEntry';
|
||||||
@ -58,7 +60,6 @@ import ChatFolderModal from '../ChatFolderModal.async';
|
|||||||
import MuteChatModal from '../MuteChatModal.async';
|
import MuteChatModal from '../MuteChatModal.async';
|
||||||
import ChatCallStatus from './ChatCallStatus';
|
import ChatCallStatus from './ChatCallStatus';
|
||||||
import ChatBadge from './ChatBadge';
|
import ChatBadge from './ChatBadge';
|
||||||
import AvatarBadge from './AvatarBadge';
|
|
||||||
|
|
||||||
import './Chat.scss';
|
import './Chat.scss';
|
||||||
|
|
||||||
@ -157,6 +158,8 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
orderDiff,
|
orderDiff,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getIsForumPanelClosed = useSelectorSignal(selectIsForumPanelClosed);
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (isForum) {
|
if (isForum) {
|
||||||
if (isSelectedForum) {
|
if (isSelectedForum) {
|
||||||
@ -253,7 +256,9 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
userStatus={userStatus}
|
userStatus={userStatus}
|
||||||
isSavedMessages={user?.isSelf}
|
isSavedMessages={user?.isSelf}
|
||||||
/>
|
/>
|
||||||
<AvatarBadge chatId={chatId} />
|
<div className="avatar-badge-wrapper">
|
||||||
|
<ChatBadge chat={chat} isMuted={isMuted} shouldShowOnlyMostImportant forceHidden={getIsForumPanelClosed} />
|
||||||
|
</div>
|
||||||
{chat.isCallActive && chat.isCallNotEmpty && (
|
{chat.isCallActive && chat.isCallNotEmpty && (
|
||||||
<ChatCallStatus isMobile={isMobile} isSelected={isSelected} isActive={withInterfaceAnimations} />
|
<ChatCallStatus isMobile={isMobile} isSelected={isSelected} isActive={withInterfaceAnimations} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -3,9 +3,13 @@ import React, { memo, useMemo } from '../../../lib/teact/teact';
|
|||||||
import type { ApiChat, ApiTopic } from '../../../api/types';
|
import type { ApiChat, ApiTopic } from '../../../api/types';
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { Signal } from '../../../util/signals';
|
||||||
|
import { isSignal } from '../../../util/signals';
|
||||||
import { formatIntegerCompact } from '../../../util/textFormat';
|
import { formatIntegerCompact } from '../../../util/textFormat';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useDerivedState from '../../../hooks/useDerivedState';
|
||||||
|
|
||||||
import ShowTransition from '../../ui/ShowTransition';
|
import ShowTransition from '../../ui/ShowTransition';
|
||||||
import AnimatedCounter from '../../common/AnimatedCounter';
|
import AnimatedCounter from '../../common/AnimatedCounter';
|
||||||
|
|
||||||
@ -18,7 +22,7 @@ type OwnProps = {
|
|||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
shouldShowOnlyMostImportant?: boolean;
|
shouldShowOnlyMostImportant?: boolean;
|
||||||
forceHidden?: boolean;
|
forceHidden?: boolean | Signal<boolean>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ChatBadge: FC<OwnProps> = ({
|
const ChatBadge: FC<OwnProps> = ({
|
||||||
@ -51,7 +55,11 @@ const ChatBadge: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const hasUnreadMark = topic ? false : chat.hasUnreadMark;
|
const hasUnreadMark = topic ? false : chat.hasUnreadMark;
|
||||||
|
|
||||||
const isShown = !forceHidden && Boolean(
|
const resolvedForceHidden = useDerivedState(
|
||||||
|
() => (isSignal(forceHidden) ? forceHidden() : forceHidden),
|
||||||
|
[forceHidden],
|
||||||
|
);
|
||||||
|
const isShown = !resolvedForceHidden && Boolean(
|
||||||
unreadCount || unreadMentionsCount || hasUnreadMark || isPinned || unreadReactionsCount
|
unreadCount || unreadMentionsCount || hasUnreadMark || isPinned || unreadReactionsCount
|
||||||
|| isTopicUnopened,
|
|| isTopicUnopened,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -73,10 +73,17 @@ export function selectIsForumPanelOpen<T extends GlobalState>(
|
|||||||
const tabState = selectTabState(global, tabId);
|
const tabState = selectTabState(global, tabId);
|
||||||
|
|
||||||
return Boolean(tabState.forumPanelChatId) && (
|
return Boolean(tabState.forumPanelChatId) && (
|
||||||
tabState.globalSearch.query === undefined || tabState.globalSearch.isClosing
|
tabState.globalSearch.query === undefined || Boolean(tabState.globalSearch.isClosing)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectIsForumPanelClosed<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
|
) {
|
||||||
|
return !selectIsForumPanelOpen(global, tabId);
|
||||||
|
}
|
||||||
|
|
||||||
export function selectIsReactionPickerOpen<T extends GlobalState>(
|
export function selectIsReactionPickerOpen<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
|
|||||||
56
src/hooks/useSelectorSignal.ts
Normal file
56
src/hooks/useSelectorSignal.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import type { GlobalState } from '../global/types';
|
||||||
|
import type { Signal, SignalSetter } from '../util/signals';
|
||||||
|
|
||||||
|
import { getGlobal } from '../global';
|
||||||
|
import { createSignal } from '../util/signals';
|
||||||
|
|
||||||
|
import useEffectOnce from './useEffectOnce';
|
||||||
|
import { addCallback } from '../lib/teact/teactn';
|
||||||
|
|
||||||
|
/*
|
||||||
|
This hook is a more performant variation of the standard React `useSelector` hook. It allows to:
|
||||||
|
a) Avoid multiple subscriptions to global updates by leveraging a single selector reference.
|
||||||
|
b) Return a signal instead of forcing a component update right away.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Selector<T extends any> = (global: GlobalState) => T;
|
||||||
|
|
||||||
|
interface State<T extends any> {
|
||||||
|
clientsCount: number;
|
||||||
|
getter: Signal<T>;
|
||||||
|
setter: SignalSetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bySelector = new Map<Selector<any>, State<any>>();
|
||||||
|
|
||||||
|
addCallback((global: GlobalState) => {
|
||||||
|
for (const [selector, { setter }] of bySelector) {
|
||||||
|
setter(selector(global));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function useSelectorSignal<T extends any>(selector: Selector<T>): Signal<T> {
|
||||||
|
let state = bySelector.get(selector);
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
const [getter, setter] = createSignal(selector(getGlobal()));
|
||||||
|
state = { clientsCount: 0, getter, setter };
|
||||||
|
bySelector.set(selector, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffectOnce(() => {
|
||||||
|
state!.clientsCount++;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
state!.clientsCount--;
|
||||||
|
|
||||||
|
if (!state!.clientsCount) {
|
||||||
|
bySelector.delete(selector);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return state.getter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSelectorSignal;
|
||||||
@ -13,6 +13,8 @@ export type Signal<T = any> = ((() => T) & {
|
|||||||
subscribe: (cb: AnyToVoidFunction) => NoneToVoidFunction;
|
subscribe: (cb: AnyToVoidFunction) => NoneToVoidFunction;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type SignalSetter = (newValue: any) => void;
|
||||||
|
|
||||||
export function isSignal(obj: any): obj is Signal {
|
export function isSignal(obj: any): obj is Signal {
|
||||||
return typeof obj === 'function' && SIGNAL_MARK in obj;
|
return typeof obj === 'function' && SIGNAL_MARK in obj;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user