[Perf] Extract users.statusesById to a separate global store
This commit is contained in:
parent
5563b0b4f4
commit
865ed08d82
@ -47,7 +47,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
...(lastName && { lastName }),
|
...(lastName && { lastName }),
|
||||||
username: mtpUser.username || '',
|
username: mtpUser.username || '',
|
||||||
phoneNumber: mtpUser.phone || '',
|
phoneNumber: mtpUser.phone || '',
|
||||||
status: buildApiUserStatus(mtpUser.status),
|
noStatus: !mtpUser.status,
|
||||||
...(mtpUser.accessHash && { accessHash: String(mtpUser.accessHash) }),
|
...(mtpUser.accessHash && { accessHash: String(mtpUser.accessHash) }),
|
||||||
...(avatarHash && { avatarHash }),
|
...(avatarHash && { avatarHash }),
|
||||||
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
|
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
|
||||||
@ -88,3 +88,23 @@ function buildApiBotCommands(botId: string, botInfo: GramJs.BotInfo) {
|
|||||||
description,
|
description,
|
||||||
})) as ApiBotCommand[];
|
})) as ApiBotCommand[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiUsersAndStatuses(mtpUsers: GramJs.TypeUser[]) {
|
||||||
|
const userStatusesById: Record<string, ApiUserStatus> = {};
|
||||||
|
const users: ApiUser[] = [];
|
||||||
|
|
||||||
|
mtpUsers.forEach((mtpUser) => {
|
||||||
|
const user = buildApiUser(mtpUser);
|
||||||
|
if (!user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
users.push(user);
|
||||||
|
|
||||||
|
if ('status' in mtpUser) {
|
||||||
|
userStatusesById[user.id] = buildApiUserStatus(mtpUser.status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { users, userStatusesById };
|
||||||
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import {
|
|||||||
buildApiChatBotCommands,
|
buildApiChatBotCommands,
|
||||||
} from '../apiBuilders/chats';
|
} from '../apiBuilders/chats';
|
||||||
import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
|
import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
|
||||||
import { buildApiUser } from '../apiBuilders/users';
|
import { buildApiUser, buildApiUsersAndStatuses } from '../apiBuilders/users';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
import localDb from '../localDb';
|
import localDb from '../localDb';
|
||||||
import {
|
import {
|
||||||
@ -146,13 +146,11 @@ export async function fetchChats({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const users = (resultPinned ? resultPinned.users : []).concat(result.users)
|
|
||||||
.map(buildApiUser)
|
|
||||||
.filter<ApiUser>(Boolean as any);
|
|
||||||
const chatIds = chats.map((chat) => chat.id);
|
const chatIds = chats.map((chat) => chat.id);
|
||||||
|
|
||||||
let totalChatCount: number;
|
const { users, userStatusesById } = buildApiUsersAndStatuses((resultPinned?.users || []).concat(result.users));
|
||||||
|
|
||||||
|
let totalChatCount: number;
|
||||||
if (result instanceof GramJs.messages.DialogsSlice) {
|
if (result instanceof GramJs.messages.DialogsSlice) {
|
||||||
totalChatCount = result.count;
|
totalChatCount = result.count;
|
||||||
} else {
|
} else {
|
||||||
@ -163,6 +161,7 @@ export async function fetchChats({
|
|||||||
chatIds,
|
chatIds,
|
||||||
chats,
|
chats,
|
||||||
users,
|
users,
|
||||||
|
userStatusesById,
|
||||||
draftsById,
|
draftsById,
|
||||||
replyingToById,
|
replyingToById,
|
||||||
orderedPinnedIds: withPinned ? orderedPinnedIds : undefined,
|
orderedPinnedIds: withPinned ? orderedPinnedIds : undefined,
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
buildMtpPeerId,
|
buildMtpPeerId,
|
||||||
getEntityTypeById,
|
getEntityTypeById,
|
||||||
} from '../gramjsBuilders';
|
} from '../gramjsBuilders';
|
||||||
import { buildApiUser, buildApiUserFromFull } from '../apiBuilders/users';
|
import { buildApiUser, buildApiUserFromFull, buildApiUsersAndStatuses } from '../apiBuilders/users';
|
||||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import { buildApiPhoto } from '../apiBuilders/common';
|
import { buildApiPhoto } from '../apiBuilders/common';
|
||||||
import localDb from '../localDb';
|
import localDb from '../localDb';
|
||||||
@ -139,7 +139,7 @@ export async function fetchUsers({ users }: { users: ApiUser[] }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.map(buildApiUser).filter<ApiUser>(Boolean as any);
|
return buildApiUsersAndStatuses(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateContact({
|
export function updateContact({
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export interface ApiUser {
|
|||||||
type: ApiUserType;
|
type: ApiUserType;
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
status?: ApiUserStatus;
|
noStatus?: boolean;
|
||||||
username: string;
|
username: string;
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
accessHash?: string;
|
accessHash?: string;
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { MouseEvent as ReactMouseEvent } from 'react';
|
import { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import React, { FC, memo, useCallback } from '../../lib/teact/teact';
|
import React, { FC, memo, useCallback } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import { ApiChat, ApiMediaFormat, ApiUser } from '../../api/types';
|
import {
|
||||||
|
ApiChat, ApiMediaFormat, ApiUser, ApiUserStatus,
|
||||||
|
} from '../../api/types';
|
||||||
|
|
||||||
import { IS_TEST } from '../../config';
|
import { IS_TEST } from '../../config';
|
||||||
import {
|
import {
|
||||||
@ -26,9 +28,9 @@ import './Avatar.scss';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: 'micro' | 'tiny' | 'small' | 'medium' | 'large' | 'jumbo';
|
size?: 'micro' | 'tiny' | 'small' | 'medium' | 'large' | 'jumbo';
|
||||||
withOnlineStatus?: boolean;
|
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
|
userStatus?: ApiUserStatus;
|
||||||
text?: string;
|
text?: string;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
@ -40,8 +42,8 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
size = 'large',
|
size = 'large',
|
||||||
chat,
|
chat,
|
||||||
user,
|
user,
|
||||||
|
userStatus,
|
||||||
text,
|
text,
|
||||||
withOnlineStatus,
|
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
onClick,
|
onClick,
|
||||||
@ -86,7 +88,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
content = getFirstLetters(text, 2);
|
content = getFirstLetters(text, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOnline = !isSavedMessages && user && isUserOnline(user);
|
const isOnline = !isSavedMessages && user && userStatus && isUserOnline(user, userStatus);
|
||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
`Avatar size-${size}`,
|
`Avatar size-${size}`,
|
||||||
className,
|
className,
|
||||||
@ -94,7 +96,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
isSavedMessages && 'saved-messages',
|
isSavedMessages && 'saved-messages',
|
||||||
isDeleted && 'deleted-account',
|
isDeleted && 'deleted-account',
|
||||||
isReplies && 'replies-bot-account',
|
isReplies && 'replies-bot-account',
|
||||||
withOnlineStatus && isOnline && 'online',
|
isOnline && 'online',
|
||||||
onClick && 'interactive',
|
onClick && 'interactive',
|
||||||
(!isSavedMessages && !blobUrl) && 'no-photo',
|
(!isSavedMessages && !blobUrl) && 'no-photo',
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,11 +4,11 @@ import React, {
|
|||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../lib/teact/teactn';
|
import { withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiUser, ApiTypingStatus } from '../../api/types';
|
import { ApiUser, ApiTypingStatus, ApiUserStatus } from '../../api/types';
|
||||||
import { GlobalActions, GlobalState } from '../../global/types';
|
import { GlobalActions, GlobalState } from '../../global/types';
|
||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin } from '../../types';
|
||||||
|
|
||||||
import { selectChatMessages, selectUser } from '../../modules/selectors';
|
import { selectChatMessages, selectUser, selectUserStatus } from '../../modules/selectors';
|
||||||
import { getUserFullName, getUserStatus, isUserOnline } from '../../modules/helpers';
|
import { getUserFullName, getUserStatus, isUserOnline } from '../../modules/helpers';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
import { pick } from '../../util/iteratees';
|
import { pick } from '../../util/iteratees';
|
||||||
@ -34,6 +34,7 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
|
userStatus?: ApiUserStatus;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
areMessagesLoaded: boolean;
|
areMessagesLoaded: boolean;
|
||||||
serverTimeOffset: number;
|
serverTimeOffset: number;
|
||||||
@ -52,6 +53,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
noStatusOrTyping,
|
noStatusOrTyping,
|
||||||
noRtl,
|
noRtl,
|
||||||
user,
|
user,
|
||||||
|
userStatus,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
areMessagesLoaded,
|
areMessagesLoaded,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
@ -106,9 +108,9 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
|
<div className={`status ${isUserOnline(user, userStatus) ? 'online' : ''}`}>
|
||||||
{withUsername && user.username && <span className="handle">{user.username}</span>}
|
{withUsername && user.username && <span className="handle">{user.username}</span>}
|
||||||
<span className="user-status" dir="auto">{getUserStatus(lang, user, serverTimeOffset)}</span>
|
<span className="user-status" dir="auto">{getUserStatus(lang, user, userStatus, serverTimeOffset)}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -143,11 +145,12 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, { userId, forceShowSelf }): StateProps => {
|
(global, { userId, forceShowSelf }): StateProps => {
|
||||||
const { lastSyncTime, serverTimeOffset } = global;
|
const { lastSyncTime, serverTimeOffset } = global;
|
||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
|
const userStatus = selectUserStatus(global, userId);
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||||
const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
|
const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lastSyncTime, user, isSavedMessages, areMessagesLoaded, serverTimeOffset,
|
lastSyncTime, user, userStatus, isSavedMessages, areMessagesLoaded, serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadFullUser', 'openMediaViewer']),
|
(setGlobal, actions): DispatchProps => pick(actions, ['loadFullUser', 'openMediaViewer']),
|
||||||
|
|||||||
@ -3,12 +3,12 @@ import React, {
|
|||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../lib/teact/teactn';
|
import { withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiUser, ApiChat } from '../../api/types';
|
import { ApiUser, ApiChat, ApiUserStatus } from '../../api/types';
|
||||||
import { GlobalActions, GlobalState } from '../../global/types';
|
import { GlobalActions, GlobalState } from '../../global/types';
|
||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin } from '../../types';
|
||||||
|
|
||||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||||
import { selectChat, selectUser } from '../../modules/selectors';
|
import { selectChat, selectUser, selectUserStatus } from '../../modules/selectors';
|
||||||
import {
|
import {
|
||||||
getUserFullName, getUserStatus, isChatChannel, isUserOnline,
|
getUserFullName, getUserStatus, isChatChannel, isUserOnline,
|
||||||
} from '../../modules/helpers';
|
} from '../../modules/helpers';
|
||||||
@ -32,6 +32,7 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
|
userStatus?: ApiUserStatus;
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
animationLevel: 0 | 1 | 2;
|
animationLevel: 0 | 1 | 2;
|
||||||
@ -43,6 +44,7 @@ type DispatchProps = Pick<GlobalActions, 'loadFullUser' | 'openMediaViewer'>;
|
|||||||
const ProfileInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
const ProfileInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
forceShowSelf,
|
forceShowSelf,
|
||||||
user,
|
user,
|
||||||
|
userStatus,
|
||||||
chat,
|
chat,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
connectionState,
|
connectionState,
|
||||||
@ -162,8 +164,8 @@ const ProfileInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
function renderStatus() {
|
function renderStatus() {
|
||||||
if (user) {
|
if (user) {
|
||||||
return (
|
return (
|
||||||
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
|
<div className={`status ${isUserOnline(user, userStatus) ? 'online' : ''}`}>
|
||||||
<span className="user-status" dir="auto">{getUserStatus(lang, user, serverTimeOffset)}</span>
|
<span className="user-status" dir="auto">{getUserStatus(lang, user, userStatus, serverTimeOffset)}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -227,6 +229,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, { userId, forceShowSelf }): StateProps => {
|
(global, { userId, forceShowSelf }): StateProps => {
|
||||||
const { connectionState, serverTimeOffset } = global;
|
const { connectionState, serverTimeOffset } = global;
|
||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
|
const userStatus = selectUserStatus(global, userId);
|
||||||
const chat = selectChat(global, userId);
|
const chat = selectChat(global, userId);
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||||
const { animationLevel } = global.settings.byKey;
|
const { animationLevel } = global.settings.byKey;
|
||||||
@ -234,6 +237,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {
|
return {
|
||||||
connectionState,
|
connectionState,
|
||||||
user,
|
user,
|
||||||
|
userStatus,
|
||||||
chat,
|
chat,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
animationLevel,
|
animationLevel,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import useLang, { LangFn } from '../../../hooks/useLang';
|
|||||||
|
|
||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
import {
|
import {
|
||||||
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus, ApiFormattedText, MAIN_THREAD_ID,
|
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus, ApiFormattedText, MAIN_THREAD_ID, ApiUserStatus,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
|
||||||
import { ANIMATION_END_DELAY } from '../../../config';
|
import { ANIMATION_END_DELAY } from '../../../config';
|
||||||
@ -30,7 +30,7 @@ import {
|
|||||||
} from '../../../modules/helpers';
|
} from '../../../modules/helpers';
|
||||||
import {
|
import {
|
||||||
selectChat, selectUser, selectChatMessage, selectOutgoingStatus, selectDraft, selectCurrentMessageList,
|
selectChat, selectUser, selectChatMessage, selectOutgoingStatus, selectDraft, selectCurrentMessageList,
|
||||||
selectNotifySettings, selectNotifyExceptions,
|
selectNotifySettings, selectNotifyExceptions, selectUserStatus,
|
||||||
} from '../../../modules/selectors';
|
} from '../../../modules/selectors';
|
||||||
import { renderActionMessageText } from '../../common/helpers/renderActionMessageText';
|
import { renderActionMessageText } from '../../common/helpers/renderActionMessageText';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
@ -67,7 +67,8 @@ type OwnProps = {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
privateChatUser?: ApiUser;
|
user?: ApiUser;
|
||||||
|
userStatus?: ApiUserStatus;
|
||||||
actionTargetUserIds?: string[];
|
actionTargetUserIds?: string[];
|
||||||
usersById?: Record<string, ApiUser>;
|
usersById?: Record<string, ApiUser>;
|
||||||
actionTargetMessage?: ApiMessage;
|
actionTargetMessage?: ApiMessage;
|
||||||
@ -94,7 +95,8 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
isPinned,
|
isPinned,
|
||||||
chat,
|
chat,
|
||||||
isMuted,
|
isMuted,
|
||||||
privateChatUser,
|
user,
|
||||||
|
userStatus,
|
||||||
actionTargetUserIds,
|
actionTargetUserIds,
|
||||||
usersById,
|
usersById,
|
||||||
lastMessageSender,
|
lastMessageSender,
|
||||||
@ -196,7 +198,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
const contextActions = useChatContextActions({
|
const contextActions = useChatContextActions({
|
||||||
chat,
|
chat,
|
||||||
privateChatUser,
|
user,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
handleChatFolderChange,
|
handleChatFolderChange,
|
||||||
folderId,
|
folderId,
|
||||||
@ -281,9 +283,9 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
<div className="status">
|
<div className="status">
|
||||||
<Avatar
|
<Avatar
|
||||||
chat={chat}
|
chat={chat}
|
||||||
user={privateChatUser}
|
user={user}
|
||||||
withOnlineStatus
|
userStatus={userStatus}
|
||||||
isSavedMessages={privateChatUser?.isSelf}
|
isSavedMessages={user?.isSelf}
|
||||||
lastSyncTime={lastSyncTime}
|
lastSyncTime={lastSyncTime}
|
||||||
/>
|
/>
|
||||||
{chat.isCallActive && chat.isCallNotEmpty && (
|
{chat.isCallActive && chat.isCallNotEmpty && (
|
||||||
@ -292,7 +294,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
<h3>{renderText(getChatTitle(lang, chat, privateChatUser))}</h3>
|
<h3>{renderText(getChatTitle(lang, chat, user))}</h3>
|
||||||
{chat.isVerified && <VerifiedIcon />}
|
{chat.isVerified && <VerifiedIcon />}
|
||||||
{isMuted && <i className="icon-muted" />}
|
{isMuted && <i className="icon-muted" />}
|
||||||
{chat.lastMessage && (
|
{chat.lastMessage && (
|
||||||
@ -377,7 +379,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canScrollDown: isSelected && messageListType === 'thread',
|
canScrollDown: isSelected && messageListType === 'thread',
|
||||||
lastSyncTime: global.lastSyncTime,
|
lastSyncTime: global.lastSyncTime,
|
||||||
...(isOutgoing && { lastMessageOutgoingStatus: selectOutgoingStatus(global, chat.lastMessage) }),
|
...(isOutgoing && { lastMessageOutgoingStatus: selectOutgoingStatus(global, chat.lastMessage) }),
|
||||||
...(privateChatUserId && { privateChatUser: selectUser(global, privateChatUserId) }),
|
...(privateChatUserId && {
|
||||||
|
user: selectUser(global, privateChatUserId),
|
||||||
|
userStatus: selectUserStatus(global, privateChatUserId),
|
||||||
|
}),
|
||||||
...(actionTargetUserIds && { usersById }),
|
...(actionTargetUserIds && { usersById }),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, {
|
|||||||
import { withGlobal } from '../../../lib/teact/teactn';
|
import { withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
import { ApiUser } from '../../../api/types';
|
import { ApiUser, ApiUserStatus } from '../../../api/types';
|
||||||
|
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
@ -27,6 +27,7 @@ export type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
usersById: Record<string, ApiUser>;
|
usersById: Record<string, ApiUser>;
|
||||||
|
userStatusesById: Record<string, ApiUserStatus>;
|
||||||
contactIds?: string[];
|
contactIds?: string[];
|
||||||
serverTimeOffset: number;
|
serverTimeOffset: number;
|
||||||
};
|
};
|
||||||
@ -36,8 +37,15 @@ type DispatchProps = Pick<GlobalActions, 'loadContactList' | 'openChat'>;
|
|||||||
const runThrottled = throttle((cb) => cb(), 60000, true);
|
const runThrottled = throttle((cb) => cb(), 60000, true);
|
||||||
|
|
||||||
const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
isActive, onReset,
|
isActive,
|
||||||
filter, usersById, contactIds, loadContactList, openChat, serverTimeOffset,
|
filter,
|
||||||
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
|
contactIds,
|
||||||
|
serverTimeOffset,
|
||||||
|
onReset,
|
||||||
|
loadContactList,
|
||||||
|
openChat,
|
||||||
}) => {
|
}) => {
|
||||||
// Due to the parent Transition, this component never gets unmounted,
|
// Due to the parent Transition, this component never gets unmounted,
|
||||||
// that's why we use throttled API call on every update.
|
// that's why we use throttled API call on every update.
|
||||||
@ -67,8 +75,8 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
return fullName && searchWords(fullName, filter);
|
return fullName && searchWords(fullName, filter);
|
||||||
}) : contactIds;
|
}) : contactIds;
|
||||||
|
|
||||||
return sortUserIds(resultIds, usersById, undefined, serverTimeOffset);
|
return sortUserIds(resultIds, usersById, userStatusesById, undefined, serverTimeOffset);
|
||||||
}, [contactIds, filter, usersById, serverTimeOffset]);
|
}, [contactIds, filter, usersById, userStatusesById, serverTimeOffset]);
|
||||||
|
|
||||||
const [viewportIds, getMore] = useInfiniteScroll(undefined, listIds, Boolean(filter));
|
const [viewportIds, getMore] = useInfiniteScroll(undefined, listIds, Boolean(filter));
|
||||||
|
|
||||||
@ -99,10 +107,11 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const { userIds: contactIds } = global.contactList || {};
|
const { userIds: contactIds } = global.contactList || {};
|
||||||
const { byId: usersById } = global.users;
|
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
usersById,
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
contactIds,
|
contactIds,
|
||||||
serverTimeOffset: global.serverTimeOffset,
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -83,7 +83,6 @@ const ChatMessage: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
<Avatar
|
<Avatar
|
||||||
chat={chat}
|
chat={chat}
|
||||||
user={privateChatUser}
|
user={privateChatUser}
|
||||||
withOnlineStatus
|
|
||||||
isSavedMessages={privateChatUser?.isSelf}
|
isSavedMessages={privateChatUser?.isSelf}
|
||||||
lastSyncTime={lastSyncTime}
|
lastSyncTime={lastSyncTime}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -27,7 +27,7 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
privateChatUser?: ApiUser;
|
user?: ApiUser;
|
||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
};
|
};
|
||||||
@ -35,7 +35,7 @@ type StateProps = {
|
|||||||
const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||||
chatId,
|
chatId,
|
||||||
chat,
|
chat,
|
||||||
privateChatUser,
|
user,
|
||||||
isPinned,
|
isPinned,
|
||||||
isMuted,
|
isMuted,
|
||||||
withUsername,
|
withUsername,
|
||||||
@ -46,7 +46,7 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const contextActions = useChatContextActions({
|
const contextActions = useChatContextActions({
|
||||||
chat,
|
chat,
|
||||||
privateChatUser,
|
user,
|
||||||
isPinned,
|
isPinned,
|
||||||
isMuted,
|
isMuted,
|
||||||
handleDelete: openDeleteModal,
|
handleDelete: openDeleteModal,
|
||||||
@ -93,7 +93,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, { chatId }): StateProps => {
|
(global, { chatId }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const privateChatUserId = chat && getPrivateChatUserId(chat);
|
const privateChatUserId = chat && getPrivateChatUserId(chat);
|
||||||
const privateChatUser = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
||||||
const isPinned = selectIsChatPinned(global, chatId);
|
const isPinned = selectIsChatPinned(global, chatId);
|
||||||
const isMuted = chat
|
const isMuted = chat
|
||||||
? selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global))
|
? selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global))
|
||||||
@ -101,7 +101,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
privateChatUser,
|
user,
|
||||||
isPinned,
|
isPinned,
|
||||||
isMuted,
|
isMuted,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import {
|
|||||||
selectChatBot,
|
selectChatBot,
|
||||||
selectChatUser,
|
selectChatUser,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
|
selectUserStatus,
|
||||||
} from '../../../modules/selectors';
|
} from '../../../modules/selectors';
|
||||||
import {
|
import {
|
||||||
getAllowedAttachmentOptions,
|
getAllowedAttachmentOptions,
|
||||||
@ -106,38 +107,40 @@ type OwnProps = {
|
|||||||
onDropHide: NoneToVoidFunction;
|
onDropHide: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps =
|
||||||
editingMessage?: ApiMessage;
|
{
|
||||||
chat?: ApiChat;
|
editingMessage?: ApiMessage;
|
||||||
draft?: ApiFormattedText;
|
chat?: ApiChat;
|
||||||
isChatWithBot?: boolean;
|
draft?: ApiFormattedText;
|
||||||
isChatWithSelf?: boolean;
|
isChatWithBot?: boolean;
|
||||||
isRightColumnShown?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
isSelectModeActive?: boolean;
|
isRightColumnShown?: boolean;
|
||||||
isForwarding?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
isPollModalOpen?: boolean;
|
isForwarding?: boolean;
|
||||||
botKeyboardMessageId?: number;
|
isPollModalOpen?: boolean;
|
||||||
botKeyboardPlaceholder?: string;
|
botKeyboardMessageId?: number;
|
||||||
withScheduledButton?: boolean;
|
botKeyboardPlaceholder?: string;
|
||||||
shouldSchedule?: boolean;
|
withScheduledButton?: boolean;
|
||||||
canScheduleUntilOnline?: boolean;
|
shouldSchedule?: boolean;
|
||||||
stickersForEmoji?: ApiSticker[];
|
canScheduleUntilOnline?: boolean;
|
||||||
groupChatMembers?: ApiChatMember[];
|
stickersForEmoji?: ApiSticker[];
|
||||||
currentUserId?: string;
|
groupChatMembers?: ApiChatMember[];
|
||||||
usersById?: Record<string, ApiUser>;
|
currentUserId?: string;
|
||||||
recentEmojis: string[];
|
usersById?: Record<string, ApiUser>;
|
||||||
lastSyncTime?: number;
|
recentEmojis: string[];
|
||||||
contentToBeScheduled?: GlobalState['messages']['contentToBeScheduled'];
|
lastSyncTime?: number;
|
||||||
shouldSuggestStickers?: boolean;
|
contentToBeScheduled?: GlobalState['messages']['contentToBeScheduled'];
|
||||||
baseEmojiKeywords?: Record<string, string[]>;
|
shouldSuggestStickers?: boolean;
|
||||||
emojiKeywords?: Record<string, string[]>;
|
baseEmojiKeywords?: Record<string, string[]>;
|
||||||
serverTimeOffset: number;
|
emojiKeywords?: Record<string, string[]>;
|
||||||
topInlineBotIds?: string[];
|
serverTimeOffset: number;
|
||||||
isInlineBotLoading: boolean;
|
topInlineBotIds?: string[];
|
||||||
inlineBots?: Record<string, false | InlineBotSettings>;
|
isInlineBotLoading: boolean;
|
||||||
botCommands?: ApiBotCommand[] | false;
|
inlineBots?: Record<string, false | InlineBotSettings>;
|
||||||
chatBotCommands?: ApiBotCommand[];
|
botCommands?: ApiBotCommand[] | false;
|
||||||
} & Pick<GlobalState, 'connectionState'>;
|
chatBotCommands?: ApiBotCommand[];
|
||||||
|
}
|
||||||
|
& Pick<GlobalState, 'connectionState'>;
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, (
|
type DispatchProps = Pick<GlobalActions, (
|
||||||
'sendMessage' | 'editMessage' | 'saveDraft' | 'forwardMessages' |
|
'sendMessage' | 'editMessage' | 'saveDraft' | 'forwardMessages' |
|
||||||
@ -1071,9 +1074,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
chat,
|
chat,
|
||||||
isChatWithBot,
|
isChatWithBot,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
canScheduleUntilOnline: (
|
canScheduleUntilOnline: Boolean(
|
||||||
!isChatWithSelf && !isChatWithBot
|
!isChatWithSelf && !isChatWithBot && chat && chatUser
|
||||||
&& (chat && chatUser && isUserId(chatId) && chatUser.status && Boolean(chatUser.status.wasOnline))
|
&& isUserId(chatId) && selectUserStatus(global, chatId)?.wasOnline,
|
||||||
),
|
),
|
||||||
isRightColumnShown: selectIsRightColumnShown(global),
|
isRightColumnShown: selectIsRightColumnShown(global),
|
||||||
isSelectModeActive: selectIsInSelectMode(global),
|
isSelectModeActive: selectIsInSelectMode(global),
|
||||||
|
|||||||
@ -4,11 +4,12 @@ import React, {
|
|||||||
import { withGlobal } from '../../lib/teact/teactn';
|
import { withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
MAIN_THREAD_ID,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
|
ApiChat,
|
||||||
ApiChatMember,
|
ApiChatMember,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
ApiChat,
|
ApiUserStatus,
|
||||||
MAIN_THREAD_ID,
|
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import { GlobalActions } from '../../global/types';
|
import { GlobalActions } from '../../global/types';
|
||||||
import {
|
import {
|
||||||
@ -87,6 +88,7 @@ type StateProps = {
|
|||||||
commonChatIds?: string[];
|
commonChatIds?: string[];
|
||||||
chatsById: Record<string, ApiChat>;
|
chatsById: Record<string, ApiChat>;
|
||||||
usersById: Record<string, ApiUser>;
|
usersById: Record<string, ApiUser>;
|
||||||
|
userStatusesById: Record<string, ApiUserStatus>;
|
||||||
isRightColumnShown: boolean;
|
isRightColumnShown: boolean;
|
||||||
isRestricted?: boolean;
|
isRestricted?: boolean;
|
||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
@ -129,6 +131,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
commonChatIds,
|
commonChatIds,
|
||||||
members,
|
members,
|
||||||
usersById,
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
chatsById,
|
chatsById,
|
||||||
isRightColumnShown,
|
isRightColumnShown,
|
||||||
isRestricted,
|
isRestricted,
|
||||||
@ -168,7 +171,8 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds(
|
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds(
|
||||||
isRightColumnShown, loadMoreMembers, loadCommonChats, searchMediaMessagesLocal, tabType, mediaSearchType, members,
|
isRightColumnShown, loadMoreMembers, loadCommonChats, searchMediaMessagesLocal, tabType, mediaSearchType, members,
|
||||||
commonChatIds, usersById, chatsById, chatMessages, foundIds, chatId, lastSyncTime, serverTimeOffset,
|
commonChatIds, usersById, userStatusesById, chatsById, chatMessages, foundIds, chatId, lastSyncTime,
|
||||||
|
serverTimeOffset,
|
||||||
);
|
);
|
||||||
const activeKey = tabs.findIndex(({ type }) => type === resultType);
|
const activeKey = tabs.findIndex(({ type }) => type === resultType);
|
||||||
|
|
||||||
@ -487,7 +491,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { currentType: mediaSearchType, resultsByType } = selectCurrentMediaSearch(global) || {};
|
const { currentType: mediaSearchType, resultsByType } = selectCurrentMediaSearch(global) || {};
|
||||||
const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {};
|
const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {};
|
||||||
|
|
||||||
const { byId: usersById } = global.users;
|
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
||||||
const { byId: chatsById } = global.chats;
|
const { byId: chatsById } = global.chats;
|
||||||
|
|
||||||
const isGroup = chat && isChatGroup(chat);
|
const isGroup = chat && isChatGroup(chat);
|
||||||
@ -532,6 +536,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
serverTimeOffset: global.serverTimeOffset,
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
activeDownloadIds,
|
activeDownloadIds,
|
||||||
usersById,
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
chatsById,
|
chatsById,
|
||||||
...(hasMembersTab && members && { members }),
|
...(hasMembersTab && members && { members }),
|
||||||
...(hasCommonChatsTab && user && { commonChatIds: user.commonChats?.ids }),
|
...(hasCommonChatsTab && user && { commonChatIds: user.commonChats?.ids }),
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useMemo, useRef } from '../../../lib/teact/teact';
|
import { useMemo, useRef } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ApiChat, ApiChatMember, ApiMessage, ApiUser,
|
ApiChat, ApiChatMember, ApiMessage, ApiUser, ApiUserStatus,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { ProfileTabType, SharedMediaType } from '../../../types';
|
import { ProfileTabType, SharedMediaType } from '../../../types';
|
||||||
|
|
||||||
@ -20,6 +20,7 @@ export default function useProfileViewportIds(
|
|||||||
groupChatMembers?: ApiChatMember[],
|
groupChatMembers?: ApiChatMember[],
|
||||||
commonChatIds?: string[],
|
commonChatIds?: string[],
|
||||||
usersById?: Record<string, ApiUser>,
|
usersById?: Record<string, ApiUser>,
|
||||||
|
userStatusesById?: Record<string, ApiUserStatus>,
|
||||||
chatsById?: Record<string, ApiChat>,
|
chatsById?: Record<string, ApiChat>,
|
||||||
chatMessages?: Record<number, ApiMessage>,
|
chatMessages?: Record<number, ApiMessage>,
|
||||||
foundIds?: number[],
|
foundIds?: number[],
|
||||||
@ -30,12 +31,18 @@ export default function useProfileViewportIds(
|
|||||||
const resultType = tabType === 'members' || !mediaSearchType ? tabType : mediaSearchType;
|
const resultType = tabType === 'members' || !mediaSearchType ? tabType : mediaSearchType;
|
||||||
|
|
||||||
const memberIds = useMemo(() => {
|
const memberIds = useMemo(() => {
|
||||||
if (!groupChatMembers || !usersById) {
|
if (!groupChatMembers || !usersById || !userStatusesById) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortUserIds(groupChatMembers.map(({ userId }) => userId), usersById, undefined, serverTimeOffset);
|
return sortUserIds(
|
||||||
}, [groupChatMembers, serverTimeOffset, usersById]);
|
groupChatMembers.map(({ userId }) => userId),
|
||||||
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
|
undefined,
|
||||||
|
serverTimeOffset,
|
||||||
|
);
|
||||||
|
}, [groupChatMembers, serverTimeOffset, usersById, userStatusesById]);
|
||||||
|
|
||||||
const chatIds = useMemo(() => {
|
const chatIds = useMemo(() => {
|
||||||
if (!commonChatIds || !chatsById) {
|
if (!commonChatIds || !chatsById) {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, {
|
|||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../lib/teact/teactn';
|
import { withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiChatMember, ApiUser } from '../../../api/types';
|
import { ApiChatMember, ApiUser, ApiUserStatus } from '../../../api/types';
|
||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
import { selectChat } from '../../../modules/selectors';
|
import { selectChat } from '../../../modules/selectors';
|
||||||
import { sortUserIds, isChatChannel } from '../../../modules/helpers';
|
import { sortUserIds, isChatChannel } from '../../../modules/helpers';
|
||||||
@ -22,6 +22,7 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
usersById: Record<string, ApiUser>;
|
usersById: Record<string, ApiUser>;
|
||||||
|
userStatusesById: Record<string, ApiUserStatus>;
|
||||||
members?: ApiChatMember[];
|
members?: ApiChatMember[];
|
||||||
isChannel?: boolean;
|
isChannel?: boolean;
|
||||||
serverTimeOffset: number;
|
serverTimeOffset: number;
|
||||||
@ -32,6 +33,7 @@ type DispatchProps = Pick<GlobalActions, 'openUserInfo'>;
|
|||||||
const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
members,
|
members,
|
||||||
usersById,
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
isChannel,
|
isChannel,
|
||||||
openUserInfo,
|
openUserInfo,
|
||||||
onClose,
|
onClose,
|
||||||
@ -43,8 +45,14 @@ const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortUserIds(members.map(({ userId }) => userId), usersById, undefined, serverTimeOffset);
|
return sortUserIds(
|
||||||
}, [members, serverTimeOffset, usersById]);
|
members.map(({ userId }) => userId),
|
||||||
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
|
undefined,
|
||||||
|
serverTimeOffset,
|
||||||
|
);
|
||||||
|
}, [members, serverTimeOffset, usersById, userStatusesById]);
|
||||||
|
|
||||||
const handleMemberClick = useCallback((id: string) => {
|
const handleMemberClick = useCallback((id: string) => {
|
||||||
openUserInfo({ id });
|
openUserInfo({ id });
|
||||||
@ -83,13 +91,14 @@ const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId }): StateProps => {
|
(global, { chatId }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const { byId: usersById } = global.users;
|
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
||||||
const members = chat?.fullInfo?.members;
|
const members = chat?.fullInfo?.members;
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
members,
|
members,
|
||||||
usersById,
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
isChannel,
|
isChannel,
|
||||||
serverTimeOffset: global.serverTimeOffset,
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, {
|
|||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../lib/teact/teactn';
|
import { withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiChatMember, ApiUser } from '../../../api/types';
|
import { ApiChatMember, ApiUser, ApiUserStatus } from '../../../api/types';
|
||||||
import { ManagementScreens } from '../../../types';
|
import { ManagementScreens } from '../../../types';
|
||||||
|
|
||||||
import { selectChat } from '../../../modules/selectors';
|
import { selectChat } from '../../../modules/selectors';
|
||||||
@ -24,6 +24,7 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
usersById: Record<string, ApiUser>;
|
usersById: Record<string, ApiUser>;
|
||||||
|
userStatusesById: Record<string, ApiUserStatus>;
|
||||||
members?: ApiChatMember[];
|
members?: ApiChatMember[];
|
||||||
isChannel?: boolean;
|
isChannel?: boolean;
|
||||||
serverTimeOffset: number;
|
serverTimeOffset: number;
|
||||||
@ -31,6 +32,7 @@ type StateProps = {
|
|||||||
|
|
||||||
const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
||||||
usersById,
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
members,
|
members,
|
||||||
isChannel,
|
isChannel,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
@ -48,9 +50,12 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return sortUserIds(
|
return sortUserIds(
|
||||||
members.filter((member) => !member.isOwner).map(({ userId }) => userId),
|
members.filter((member) => !member.isOwner).map(({ userId }) => userId),
|
||||||
usersById, undefined, serverTimeOffset,
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
|
undefined,
|
||||||
|
serverTimeOffset,
|
||||||
);
|
);
|
||||||
}, [members, serverTimeOffset, usersById]);
|
}, [members, serverTimeOffset, usersById, userStatusesById]);
|
||||||
|
|
||||||
const handleExceptionMemberClick = useCallback((memberId: string) => {
|
const handleExceptionMemberClick = useCallback((memberId: string) => {
|
||||||
onChatMemberSelect(memberId);
|
onChatMemberSelect(memberId);
|
||||||
@ -88,13 +93,14 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId }): StateProps => {
|
(global, { chatId }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const { byId: usersById } = global.users;
|
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
||||||
const members = chat?.fullInfo?.members;
|
const members = chat?.fullInfo?.members;
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
members,
|
members,
|
||||||
usersById,
|
usersById,
|
||||||
|
userStatusesById,
|
||||||
isChannel,
|
isChannel,
|
||||||
serverTimeOffset: global.serverTimeOffset,
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -190,9 +190,13 @@ function migrateCache(cached: GlobalState, initialState: GlobalState) {
|
|||||||
cached.audioPlayer.playbackRate = DEFAULT_PLAYBACK_RATE;
|
cached.audioPlayer.playbackRate = DEFAULT_PLAYBACK_RATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cached.groupCalls === undefined) {
|
if (!cached.groupCalls) {
|
||||||
cached.groupCalls = initialState.groupCalls;
|
cached.groupCalls = initialState.groupCalls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cached.users.statusesById) {
|
||||||
|
cached.users.statusesById = {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCache() {
|
function updateCache() {
|
||||||
@ -251,7 +255,7 @@ function reduceShowChatInfo(global: GlobalState): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function reduceUsers(global: GlobalState): GlobalState['users'] {
|
function reduceUsers(global: GlobalState): GlobalState['users'] {
|
||||||
const { users: { byId, selectedId } } = global;
|
const { users: { byId, statusesById, selectedId } } = global;
|
||||||
const idsToSave = [
|
const idsToSave = [
|
||||||
...(global.chats.listIds.active || []).slice(0, GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT).filter(isUserId),
|
...(global.chats.listIds.active || []).slice(0, GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT).filter(isUserId),
|
||||||
...Object.keys(byId),
|
...Object.keys(byId),
|
||||||
@ -259,6 +263,7 @@ function reduceUsers(global: GlobalState): GlobalState['users'] {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
byId: pick(byId, idsToSave),
|
byId: pick(byId, idsToSave),
|
||||||
|
statusesById: pick(statusesById, idsToSave),
|
||||||
selectedId: window.innerWidth > MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN ? selectedId : undefined,
|
selectedId: window.innerWidth > MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN ? selectedId : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
|
|
||||||
users: {
|
users: {
|
||||||
byId: {},
|
byId: {},
|
||||||
|
statusesById: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
chats: {
|
chats: {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiThreadInfo,
|
ApiThreadInfo,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
|
ApiUserStatus,
|
||||||
ApiUpdateAuthorizationStateType,
|
ApiUpdateAuthorizationStateType,
|
||||||
ApiUpdateConnectionStateType,
|
ApiUpdateConnectionStateType,
|
||||||
ApiStickerSet,
|
ApiStickerSet,
|
||||||
@ -127,6 +128,7 @@ export type GlobalState = {
|
|||||||
|
|
||||||
users: {
|
users: {
|
||||||
byId: Record<string, ApiUser>;
|
byId: Record<string, ApiUser>;
|
||||||
|
statusesById: Record<string, ApiUserStatus>;
|
||||||
// TODO Remove
|
// TODO Remove
|
||||||
selectedId?: string;
|
selectedId?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import useLang from './useLang';
|
|||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
chat,
|
chat,
|
||||||
privateChatUser,
|
user,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
handleChatFolderChange,
|
handleChatFolderChange,
|
||||||
folderId,
|
folderId,
|
||||||
@ -19,7 +19,7 @@ export default ({
|
|||||||
isMuted,
|
isMuted,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat | undefined;
|
chat: ApiChat | undefined;
|
||||||
privateChatUser: ApiUser | undefined;
|
user: ApiUser | undefined;
|
||||||
handleDelete: () => void;
|
handleDelete: () => void;
|
||||||
handleChatFolderChange: () => void;
|
handleChatFolderChange: () => void;
|
||||||
folderId?: number;
|
folderId?: number;
|
||||||
@ -28,7 +28,7 @@ export default ({
|
|||||||
}, isInSearch = false) => {
|
}, isInSearch = false) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const { isSelf } = privateChatUser || {};
|
const { isSelf } = user || {};
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { callApi } from '../../../api/gramjs';
|
|||||||
import {
|
import {
|
||||||
addChats,
|
addChats,
|
||||||
addUsers,
|
addUsers,
|
||||||
|
addUserStatuses,
|
||||||
replaceThreadParam,
|
replaceThreadParam,
|
||||||
updateChatListIds,
|
updateChatListIds,
|
||||||
updateChats,
|
updateChats,
|
||||||
@ -1007,6 +1008,8 @@ async function loadChats(listType: 'active' | 'archived', offsetId?: string, off
|
|||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
|
global = addUserStatuses(global, result.userStatusesById);
|
||||||
|
|
||||||
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
global = updateChatListIds(global, listType, chatIds);
|
global = updateChatListIds(global, listType, chatIds);
|
||||||
global = updateChatListSecondaryInfo(global, listType, result);
|
global = updateChatListSecondaryInfo(global, listType, result);
|
||||||
|
|||||||
@ -17,7 +17,9 @@ import {
|
|||||||
replaceChatListIds,
|
replaceChatListIds,
|
||||||
replaceChats,
|
replaceChats,
|
||||||
replaceUsers,
|
replaceUsers,
|
||||||
|
replaceUserStatuses,
|
||||||
updateUsers,
|
updateUsers,
|
||||||
|
addUserStatuses,
|
||||||
updateChats,
|
updateChats,
|
||||||
updateChatListSecondaryInfo,
|
updateChatListSecondaryInfo,
|
||||||
updateThreadInfos,
|
updateThreadInfos,
|
||||||
@ -146,16 +148,10 @@ async function loadAndReplaceChats() {
|
|||||||
savedUsers.push(...result.users);
|
savedUsers.push(...result.users);
|
||||||
savedChats.push(...result.chats);
|
savedChats.push(...result.chats);
|
||||||
|
|
||||||
|
global = replaceUserStatuses(global, result.userStatusesById);
|
||||||
|
|
||||||
global = replaceChats(global, buildCollectionByKey(savedChats, 'id'));
|
global = replaceChats(global, buildCollectionByKey(savedChats, 'id'));
|
||||||
global = replaceChatListIds(global, 'active', result.chatIds);
|
global = replaceChatListIds(global, 'active', result.chatIds);
|
||||||
|
|
||||||
global = {
|
|
||||||
...global,
|
|
||||||
chats: {
|
|
||||||
...global.chats,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
global = updateChatListSecondaryInfo(global, 'active', result);
|
global = updateChatListSecondaryInfo(global, 'active', result);
|
||||||
|
|
||||||
Object.keys(result.draftsById).forEach((chatId) => {
|
Object.keys(result.draftsById).forEach((chatId) => {
|
||||||
@ -190,10 +186,14 @@ async function loadAndReplaceArchivedChats() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let global = getGlobal();
|
let global = getGlobal();
|
||||||
|
|
||||||
global = updateUsers(global, buildCollectionByKey(result.users, 'id'));
|
global = updateUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
|
global = addUserStatuses(global, result.userStatusesById);
|
||||||
|
|
||||||
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
global = replaceChatListIds(global, 'archived', result.chatIds);
|
global = replaceChatListIds(global, 'archived', result.chatIds);
|
||||||
global = updateChatListSecondaryInfo(global, 'archived', result);
|
global = updateChatListSecondaryInfo(global, 'archived', result);
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,13 +337,16 @@ async function loadAndUpdateUsers() {
|
|||||||
...(contactIds || []),
|
...(contactIds || []),
|
||||||
].map((id) => selectUser(global, id)).filter<ApiUser>(Boolean as any);
|
].map((id) => selectUser(global, id)).filter<ApiUser>(Boolean as any);
|
||||||
|
|
||||||
const updatedUsers = await callApi('fetchUsers', { users });
|
const result = await callApi('fetchUsers', { users });
|
||||||
if (!updatedUsers) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { users: updatedUsers, userStatusesById } = result;
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateUsers(global, buildCollectionByKey(updatedUsers, 'id'));
|
global = updateUsers(global, buildCollectionByKey(updatedUsers, 'id'));
|
||||||
|
global = addUserStatuses(global, userStatusesById);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { isUserBot, isUserId } from '../../helpers';
|
|||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { selectChat, selectCurrentMessageList, selectUser } from '../../selectors';
|
import { selectChat, selectCurrentMessageList, selectUser } from '../../selectors';
|
||||||
import {
|
import {
|
||||||
addChats, addUsers, updateChat, updateManagementProgress, updateUser, updateUsers,
|
addChats, addUsers, replaceUserStatuses, updateChat, updateManagementProgress, updateUser, updateUsers,
|
||||||
updateUserSearch, updateUserSearchFetchingStatus,
|
updateUserSearch, updateUserSearchFetchingStatus,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
@ -40,13 +40,21 @@ addReducer('loadUser', (global, actions, payload) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const updatedUsers = await callApi('fetchUsers', { users: [user] });
|
const result = await callApi('fetchUsers', { users: [user] });
|
||||||
if (!updatedUsers) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { users, userStatusesById } = result;
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateUsers(global, buildCollectionByKey(updatedUsers, 'id'));
|
|
||||||
|
global = updateUsers(global, buildCollectionByKey(users, 'id'));
|
||||||
|
setGlobal(replaceUserStatuses(global, {
|
||||||
|
...global.users.statusesById,
|
||||||
|
...userStatusesById,
|
||||||
|
}));
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,30 +2,29 @@ import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
|
|||||||
|
|
||||||
import { ApiUpdate, ApiUserStatus } from '../../../api/types';
|
import { ApiUpdate, ApiUserStatus } from '../../../api/types';
|
||||||
|
|
||||||
import { deleteUser, updateUser } from '../../reducers';
|
import { deleteUser, replaceUserStatuses, updateUser } from '../../reducers';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
|
|
||||||
const STATUS_UPDATE_THROTTLE = 3000;
|
const STATUS_UPDATE_THROTTLE = 3000;
|
||||||
|
|
||||||
const flushStatusUpdatesThrottled = throttle(flushStatusUpdates, STATUS_UPDATE_THROTTLE, true);
|
const flushStatusUpdatesThrottled = throttle(flushStatusUpdates, STATUS_UPDATE_THROTTLE, true);
|
||||||
|
|
||||||
let pendingStatusUpdates: [string, ApiUserStatus][] = [];
|
let pendingStatusUpdates: Record<string, ApiUserStatus> = {};
|
||||||
|
|
||||||
function scheduleStatusUpdate(userId: string, statusUpdate: ApiUserStatus) {
|
function scheduleStatusUpdate(userId: string, statusUpdate: ApiUserStatus) {
|
||||||
pendingStatusUpdates.push([userId, statusUpdate]);
|
pendingStatusUpdates[userId] = statusUpdate;
|
||||||
flushStatusUpdatesThrottled();
|
flushStatusUpdatesThrottled();
|
||||||
}
|
}
|
||||||
|
|
||||||
function flushStatusUpdates() {
|
function flushStatusUpdates() {
|
||||||
let global = getGlobal();
|
const global = getGlobal();
|
||||||
pendingStatusUpdates.forEach(([userId, statusUpdate]) => {
|
|
||||||
global = updateUser(global, userId, {
|
|
||||||
status: statusUpdate,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
setGlobal(global);
|
|
||||||
|
|
||||||
pendingStatusUpdates = [];
|
setGlobal(replaceUserStatuses(global, {
|
||||||
|
...global.users.statusesById,
|
||||||
|
...pendingStatusUpdates,
|
||||||
|
}));
|
||||||
|
|
||||||
|
pendingStatusUpdates = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ApiChat, ApiUser } from '../../api/types';
|
import { ApiChat, ApiUser, ApiUserStatus } from '../../api/types';
|
||||||
|
|
||||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
||||||
import { formatFullDate, formatTime } from '../../util/dateFormat';
|
import { formatFullDate, formatTime } from '../../util/dateFormat';
|
||||||
@ -65,7 +65,9 @@ export function getUserFullName(user?: ApiUser) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: number) {
|
export function getUserStatus(
|
||||||
|
lang: LangFn, user: ApiUser, userStatus: ApiUserStatus | undefined, serverTimeOffset: number,
|
||||||
|
) {
|
||||||
if (user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
if (user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
||||||
return lang('ServiceNotifications').toLowerCase();
|
return lang('ServiceNotifications').toLowerCase();
|
||||||
}
|
}
|
||||||
@ -74,11 +76,11 @@ export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: num
|
|||||||
return lang('Bot');
|
return lang('Bot');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.status) {
|
if (!userStatus) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (user.status.type) {
|
switch (userStatus.type) {
|
||||||
case 'userStatusEmpty': {
|
case 'userStatusEmpty': {
|
||||||
return lang('ALongTimeAgo');
|
return lang('ALongTimeAgo');
|
||||||
}
|
}
|
||||||
@ -92,7 +94,7 @@ export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: num
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'userStatusOffline': {
|
case 'userStatusOffline': {
|
||||||
const { wasOnline } = user.status;
|
const { wasOnline } = userStatus;
|
||||||
|
|
||||||
if (!wasOnline) return lang('LastSeen.Offline');
|
if (!wasOnline) return lang('LastSeen.Offline');
|
||||||
|
|
||||||
@ -156,10 +158,10 @@ export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: num
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isUserOnline(user: ApiUser) {
|
export function isUserOnline(user: ApiUser, userStatus?: ApiUserStatus) {
|
||||||
const { id, status, type } = user;
|
const { id, type } = user;
|
||||||
|
|
||||||
if (!status) {
|
if (!userStatus) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,11 +169,11 @@ export function isUserOnline(user: ApiUser) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return status.type === 'userStatusOnline' && type !== 'userTypeBot';
|
return userStatus.type === 'userStatusOnline' && type !== 'userTypeBot';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDeletedUser(user: ApiUser) {
|
export function isDeletedUser(user: ApiUser) {
|
||||||
if (!user.status || user.type === 'userTypeBot' || user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
if (user.noStatus || user.type === 'userTypeBot' || user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +192,7 @@ export function getCanAddContact(user: ApiUser) {
|
|||||||
export function sortUserIds(
|
export function sortUserIds(
|
||||||
userIds: string[],
|
userIds: string[],
|
||||||
usersById: Record<string, ApiUser>,
|
usersById: Record<string, ApiUser>,
|
||||||
|
userStatusesById: Record<string, ApiUserStatus>,
|
||||||
priorityIds?: string[],
|
priorityIds?: string[],
|
||||||
serverTimeOffset = 0,
|
serverTimeOffset = 0,
|
||||||
) {
|
) {
|
||||||
@ -204,17 +207,18 @@ export function sortUserIds(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = usersById[id];
|
const user = usersById[id];
|
||||||
if (!user || !user.status) {
|
const userStatus = userStatusesById[id];
|
||||||
|
if (!user || !userStatus) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.status.type === 'userStatusOnline') {
|
if (userStatus.type === 'userStatusOnline') {
|
||||||
return user.status.expires;
|
return userStatus.expires;
|
||||||
} else if (user.status.type === 'userStatusOffline' && user.status.wasOnline) {
|
} else if (userStatus.type === 'userStatusOffline' && userStatus.wasOnline) {
|
||||||
return user.status.wasOnline;
|
return userStatus.wasOnline;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (user.status.type) {
|
switch (userStatus.type) {
|
||||||
case 'userStatusRecently':
|
case 'userStatusRecently':
|
||||||
return now - 60 * 60 * 24;
|
return now - 60 * 60 * 24;
|
||||||
case 'userStatusLastWeek':
|
case 'userStatusLastWeek':
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { GlobalState } from '../../global/types';
|
|||||||
import { ApiChat, ApiPhoto } from '../../api/types';
|
import { ApiChat, ApiPhoto } from '../../api/types';
|
||||||
|
|
||||||
import { ARCHIVED_FOLDER_ID } from '../../config';
|
import { ARCHIVED_FOLDER_ID } from '../../config';
|
||||||
import { omit } from '../../util/iteratees';
|
import { mapValues, omit } from '../../util/iteratees';
|
||||||
import { selectChatListType } from '../selectors';
|
import { selectChatListType } from '../selectors';
|
||||||
|
|
||||||
export function replaceChatListIds(
|
export function replaceChatListIds(
|
||||||
@ -48,26 +48,6 @@ export function replaceChats(global: GlobalState, newById: Record<string, ApiCha
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// @optimization Don't spread/unspread global for each element, do it in a batch
|
|
||||||
function getUpdatedChat(
|
|
||||||
global: GlobalState, chatId: string, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
|
||||||
): ApiChat {
|
|
||||||
const { byId } = global.chats;
|
|
||||||
const chat = byId[chatId];
|
|
||||||
const shouldOmitMinInfo = chatUpdate.isMin && chat && !chat.isMin;
|
|
||||||
const updatedChat = {
|
|
||||||
...chat,
|
|
||||||
...(shouldOmitMinInfo ? omit(chatUpdate, ['isMin', 'accessHash']) : chatUpdate),
|
|
||||||
...(photo && { photos: [photo, ...(chat.photos || [])] }),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!updatedChat.id || !updatedChat.type) {
|
|
||||||
return updatedChat;
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedChat;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateChat(
|
export function updateChat(
|
||||||
global: GlobalState, chatId: string, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
global: GlobalState, chatId: string, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
||||||
): GlobalState {
|
): GlobalState {
|
||||||
@ -81,52 +61,67 @@ export function updateChat(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateChats(global: GlobalState, updatedById: Record<string, ApiChat>): GlobalState {
|
export function updateChats(global: GlobalState, newById: Record<string, ApiChat>): GlobalState {
|
||||||
const updatedChats = Object.keys(updatedById).reduce<Record<string, ApiChat>>((acc, id) => {
|
const updatedById = mapValues(newById, (chat, id) => {
|
||||||
const updatedChat = getUpdatedChat(global, id, updatedById[id]);
|
return getUpdatedChat(global, id, chat);
|
||||||
if (updatedChat) {
|
});
|
||||||
acc[id] = updatedChat;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
global = replaceChats(global, {
|
global = replaceChats(global, {
|
||||||
...global.chats.byId,
|
...global.chats.byId,
|
||||||
...updatedChats,
|
...updatedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
||||||
export function addChats(global: GlobalState, addedById: Record<string, ApiChat>): GlobalState {
|
export function addChats(global: GlobalState, newById: Record<string, ApiChat>): GlobalState {
|
||||||
const { byId } = global.chats;
|
const { byId } = global.chats;
|
||||||
let isAdded = false;
|
let isAdded = false;
|
||||||
|
|
||||||
const addedChats = Object.keys(addedById).reduce<Record<string, ApiChat>>((acc, id) => {
|
const addedById = Object.keys(newById).reduce<Record<string, ApiChat>>((acc, id) => {
|
||||||
if (!byId[id] || (byId[id].isMin && !addedById[id].isMin)) {
|
if (!byId[id] || (byId[id].isMin && !newById[id].isMin)) {
|
||||||
const updatedChat = getUpdatedChat(global, id, addedById[id]);
|
acc[id] = getUpdatedChat(global, id, newById[id]);
|
||||||
if (updatedChat) {
|
|
||||||
acc[id] = updatedChat;
|
|
||||||
|
|
||||||
if (!isAdded) {
|
if (!isAdded) {
|
||||||
isAdded = true;
|
isAdded = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
if (isAdded) {
|
if (!isAdded) {
|
||||||
global = replaceChats(global, {
|
return global;
|
||||||
...global.chats.byId,
|
|
||||||
...addedChats,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
global = replaceChats(global, {
|
||||||
|
...byId,
|
||||||
|
...addedById,
|
||||||
|
});
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @optimization Don't spread/unspread global for each element, do it in a batch
|
||||||
|
function getUpdatedChat(
|
||||||
|
global: GlobalState, chatId: string, chatUpdate: Partial<ApiChat>, photo?: ApiPhoto,
|
||||||
|
) {
|
||||||
|
const { byId } = global.chats;
|
||||||
|
const chat = byId[chatId];
|
||||||
|
const shouldOmitMinInfo = chatUpdate.isMin && chat && !chat.isMin;
|
||||||
|
const updatedChat: ApiChat = {
|
||||||
|
...chat,
|
||||||
|
...(shouldOmitMinInfo ? omit(chatUpdate, ['isMin', 'accessHash']) : chatUpdate),
|
||||||
|
...(photo && { photos: [photo, ...(chat.photos || [])] }),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!updatedChat.id || !updatedChat.type) {
|
||||||
|
return updatedChat;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedChat;
|
||||||
|
}
|
||||||
|
|
||||||
export function updateChatListType(
|
export function updateChatListType(
|
||||||
global: GlobalState,
|
global: GlobalState,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { GlobalState } from '../../global/types';
|
import { GlobalState } from '../../global/types';
|
||||||
import { ApiUser } from '../../api/types';
|
import { ApiUser, ApiUserStatus } from '../../api/types';
|
||||||
|
|
||||||
import { omit } from '../../util/iteratees';
|
import { mapValues, omit, pick } from '../../util/iteratees';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||||
|
|
||||||
export function replaceUsers(global: GlobalState, newById: Record<string, ApiUser>): GlobalState {
|
export function replaceUsers(global: GlobalState, newById: Record<string, ApiUser>): GlobalState {
|
||||||
@ -14,24 +14,6 @@ export function replaceUsers(global: GlobalState, newById: Record<string, ApiUse
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// @optimization Don't spread/unspread global for each element, do it in a batch
|
|
||||||
function getUpdatedUser(global: GlobalState, userId: string, userUpdate: Partial<ApiUser>): ApiUser {
|
|
||||||
const { byId } = global.users;
|
|
||||||
const user = byId[userId];
|
|
||||||
const shouldOmitMinInfo = userUpdate.isMin && user && !user.isMin;
|
|
||||||
|
|
||||||
const updatedUser = {
|
|
||||||
...user,
|
|
||||||
...(shouldOmitMinInfo ? omit(userUpdate, ['isMin', 'accessHash']) : userUpdate),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!updatedUser.id || !updatedUser.type) {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateContactList(global: GlobalState, updatedUsers: ApiUser[]): GlobalState {
|
function updateContactList(global: GlobalState, updatedUsers: ApiUser[]): GlobalState {
|
||||||
const { userIds: contactUserIds } = global.contactList || {};
|
const { userIds: contactUserIds } = global.contactList || {};
|
||||||
|
|
||||||
@ -67,56 +49,69 @@ export function updateUser(global: GlobalState, userId: string, userUpdate: Part
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateUsers(global: GlobalState, updatedById: Record<string, ApiUser>): GlobalState {
|
export function updateUsers(global: GlobalState, newById: Record<string, ApiUser>): GlobalState {
|
||||||
const updatedUsers = Object.keys(updatedById).reduce<Record<string, ApiUser>>((acc, id) => {
|
const updatedById = mapValues(newById, (user, id) => {
|
||||||
const updatedUser = getUpdatedUser(global, id, updatedById[id]);
|
return getUpdatedUser(global, id, user);
|
||||||
if (updatedUser) {
|
});
|
||||||
acc[id] = updatedUser;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
global = updateContactList(global, Object.values(updatedUsers));
|
|
||||||
|
|
||||||
global = replaceUsers(global, {
|
global = replaceUsers(global, {
|
||||||
...global.users.byId,
|
...global.users.byId,
|
||||||
...updatedUsers,
|
...updatedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
global = updateContactList(global, Object.values(updatedById));
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
||||||
export function addUsers(global: GlobalState, addedById: Record<string, ApiUser>): GlobalState {
|
export function addUsers(global: GlobalState, newById: Record<string, ApiUser>): GlobalState {
|
||||||
const { byId } = global.users;
|
const { byId } = global.users;
|
||||||
let isAdded = false;
|
let isAdded = false;
|
||||||
|
|
||||||
const addedUsers = Object.keys(addedById).reduce<Record<string, ApiUser>>((acc, id) => {
|
const addedById = Object.keys(newById).reduce<Record<string, ApiUser>>((acc, id) => {
|
||||||
if (!byId[id] || (byId[id].isMin && !addedById[id].isMin)) {
|
if (!byId[id] || (byId[id].isMin && !newById[id].isMin)) {
|
||||||
const updatedUser = getUpdatedUser(global, id, addedById[id]);
|
acc[id] = getUpdatedUser(global, id, newById[id]);
|
||||||
if (updatedUser) {
|
|
||||||
acc[id] = updatedUser;
|
|
||||||
|
|
||||||
if (!isAdded) {
|
if (!isAdded) {
|
||||||
isAdded = true;
|
isAdded = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
if (isAdded) {
|
if (!isAdded) {
|
||||||
global = replaceUsers(global, {
|
return global;
|
||||||
...global.users.byId,
|
|
||||||
...addedUsers,
|
|
||||||
});
|
|
||||||
|
|
||||||
global = updateContactList(global, Object.values(addedUsers));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
global = replaceUsers(global, {
|
||||||
|
...byId,
|
||||||
|
...addedById,
|
||||||
|
});
|
||||||
|
|
||||||
|
global = updateContactList(global, Object.values(addedById));
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @optimization Don't spread/unspread global for each element, do it in a batch
|
||||||
|
function getUpdatedUser(global: GlobalState, userId: string, userUpdate: Partial<ApiUser>) {
|
||||||
|
const { byId } = global.users;
|
||||||
|
const user = byId[userId];
|
||||||
|
const shouldOmitMinInfo = userUpdate.isMin && user && !user.isMin;
|
||||||
|
|
||||||
|
const updatedUser = {
|
||||||
|
...user,
|
||||||
|
...(shouldOmitMinInfo ? omit(userUpdate, ['isMin', 'accessHash']) : userUpdate),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!updatedUser.id || !updatedUser.type) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedUser;
|
||||||
|
}
|
||||||
|
|
||||||
export function updateSelectedUserId(global: GlobalState, selectedId?: string): GlobalState {
|
export function updateSelectedUserId(global: GlobalState, selectedId?: string): GlobalState {
|
||||||
if (global.users.selectedId === selectedId) {
|
if (global.users.selectedId === selectedId) {
|
||||||
return global;
|
return global;
|
||||||
@ -182,3 +177,30 @@ export function updateUserBlockedState(global: GlobalState, userId: string, isBl
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function replaceUserStatuses(global: GlobalState, newById: Record<string, ApiUserStatus>): GlobalState {
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
users: {
|
||||||
|
...global.users,
|
||||||
|
statusesById: newById,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// @optimization Allows to avoid redundant updates which cause a lot of renders
|
||||||
|
export function addUserStatuses(global: GlobalState, newById: Record<string, ApiUserStatus>): GlobalState {
|
||||||
|
const { statusesById } = global.users;
|
||||||
|
|
||||||
|
const newKeys = Object.keys(newById).filter((id) => !statusesById[id]);
|
||||||
|
if (!newKeys.length) {
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = replaceUserStatuses(global, {
|
||||||
|
...statusesById,
|
||||||
|
...pick(newById, newKeys),
|
||||||
|
});
|
||||||
|
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|||||||
@ -46,7 +46,11 @@ export function selectChatOnlineCount(global: GlobalState, chat: ApiChat) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return chat.fullInfo.members.reduce((onlineCount, { userId }) => {
|
return chat.fullInfo.members.reduce((onlineCount, { userId }) => {
|
||||||
if (global.users.byId[userId] && isUserOnline(global.users.byId[userId]) && userId !== global.currentUserId) {
|
if (
|
||||||
|
userId !== global.currentUserId
|
||||||
|
&& global.users.byId[userId]
|
||||||
|
&& isUserOnline(global.users.byId[userId], global.users.statusesById[userId])
|
||||||
|
) {
|
||||||
return onlineCount + 1;
|
return onlineCount + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
import { GlobalState } from '../../global/types';
|
import { GlobalState } from '../../global/types';
|
||||||
import { ApiChat, ApiUser } from '../../api/types';
|
import { ApiChat, ApiUser, ApiUserStatus } from '../../api/types';
|
||||||
|
|
||||||
export function selectUser(global: GlobalState, userId: string): ApiUser | undefined {
|
export function selectUser(global: GlobalState, userId: string): ApiUser | undefined {
|
||||||
return global.users.byId[userId];
|
return global.users.byId[userId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectUserStatus(global: GlobalState, userId: string): ApiUserStatus | undefined {
|
||||||
|
return global.users.statusesById[userId];
|
||||||
|
}
|
||||||
|
|
||||||
export function selectIsUserBlocked(global: GlobalState, userId: string) {
|
export function selectIsUserBlocked(global: GlobalState, userId: string) {
|
||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user