[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,
|
||||
selectCurrentMessageList,
|
||||
selectDraft,
|
||||
selectIsForumPanelClosed,
|
||||
selectNotifyExceptions,
|
||||
selectNotifySettings,
|
||||
selectOutgoingStatus,
|
||||
@ -42,6 +43,7 @@ import buildClassName from '../../../util/buildClassName';
|
||||
import { createLocationHash } from '../../../util/routing';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useSelectorSignal from '../../../hooks/useSelectorSignal';
|
||||
import useChatContextActions from '../../../hooks/useChatContextActions';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useChatListEntry from './hooks/useChatListEntry';
|
||||
@ -58,7 +60,6 @@ import ChatFolderModal from '../ChatFolderModal.async';
|
||||
import MuteChatModal from '../MuteChatModal.async';
|
||||
import ChatCallStatus from './ChatCallStatus';
|
||||
import ChatBadge from './ChatBadge';
|
||||
import AvatarBadge from './AvatarBadge';
|
||||
|
||||
import './Chat.scss';
|
||||
|
||||
@ -157,6 +158,8 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
orderDiff,
|
||||
});
|
||||
|
||||
const getIsForumPanelClosed = useSelectorSignal(selectIsForumPanelClosed);
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
if (isForum) {
|
||||
if (isSelectedForum) {
|
||||
@ -253,7 +256,9 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
userStatus={userStatus}
|
||||
isSavedMessages={user?.isSelf}
|
||||
/>
|
||||
<AvatarBadge chatId={chatId} />
|
||||
<div className="avatar-badge-wrapper">
|
||||
<ChatBadge chat={chat} isMuted={isMuted} shouldShowOnlyMostImportant forceHidden={getIsForumPanelClosed} />
|
||||
</div>
|
||||
{chat.isCallActive && chat.isCallNotEmpty && (
|
||||
<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 { FC } from '../../../lib/teact/teact';
|
||||
|
||||
import type { Signal } from '../../../util/signals';
|
||||
import { isSignal } from '../../../util/signals';
|
||||
import { formatIntegerCompact } from '../../../util/textFormat';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useDerivedState from '../../../hooks/useDerivedState';
|
||||
|
||||
import ShowTransition from '../../ui/ShowTransition';
|
||||
import AnimatedCounter from '../../common/AnimatedCounter';
|
||||
|
||||
@ -18,7 +22,7 @@ type OwnProps = {
|
||||
isPinned?: boolean;
|
||||
isMuted?: boolean;
|
||||
shouldShowOnlyMostImportant?: boolean;
|
||||
forceHidden?: boolean;
|
||||
forceHidden?: boolean | Signal<boolean>;
|
||||
};
|
||||
|
||||
const ChatBadge: FC<OwnProps> = ({
|
||||
@ -51,7 +55,11 @@ const ChatBadge: FC<OwnProps> = ({
|
||||
|
||||
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
|
||||
|| isTopicUnopened,
|
||||
);
|
||||
|
||||
@ -73,10 +73,17 @@ export function selectIsForumPanelOpen<T extends GlobalState>(
|
||||
const tabState = selectTabState(global, tabId);
|
||||
|
||||
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>(
|
||||
global: 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;
|
||||
});
|
||||
|
||||
export type SignalSetter = (newValue: any) => void;
|
||||
|
||||
export function isSignal(obj: any): obj is Signal {
|
||||
return typeof obj === 'function' && SIGNAL_MARK in obj;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user