[Perf] More performance fixes
This commit is contained in:
parent
83557863b8
commit
cfc71da0c1
@ -3,6 +3,7 @@ import type {
|
||||
ApiEmojiStatus,
|
||||
ApiPremiumGiftOption,
|
||||
ApiUser,
|
||||
ApiUserFullInfo,
|
||||
ApiUserStatus,
|
||||
ApiUserType,
|
||||
} from '../../types';
|
||||
@ -10,7 +11,7 @@ import { buildApiPeerId } from './peers';
|
||||
import { buildApiBotInfo } from './bots';
|
||||
import { buildApiPhoto, buildApiUsernames } from './common';
|
||||
|
||||
export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUser {
|
||||
export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUserFullInfo {
|
||||
const {
|
||||
fullUser: {
|
||||
about, commonChatsCount, pinnedMsgId, botInfo, blocked,
|
||||
@ -20,22 +21,19 @@ export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
users,
|
||||
} = mtpUserFull;
|
||||
|
||||
const user = buildApiUser(users[0])!;
|
||||
const userId = buildApiPeerId(users[0].id, 'user');
|
||||
|
||||
return {
|
||||
...user,
|
||||
fullInfo: {
|
||||
...(profilePhoto instanceof GramJs.Photo && { profilePhoto: buildApiPhoto(profilePhoto) }),
|
||||
...(fallbackPhoto instanceof GramJs.Photo && { fallbackPhoto: buildApiPhoto(fallbackPhoto) }),
|
||||
...(personalPhoto instanceof GramJs.Photo && { personalPhoto: buildApiPhoto(personalPhoto) }),
|
||||
bio: about,
|
||||
commonChatsCount,
|
||||
pinnedMessageId: pinnedMsgId,
|
||||
isBlocked: Boolean(blocked),
|
||||
noVoiceMessages: voiceMessagesForbidden,
|
||||
...(premiumGifts && { premiumGifts: premiumGifts.map((gift) => buildApiPremiumGiftOption(gift)) }),
|
||||
...(botInfo && { botInfo: buildApiBotInfo(botInfo, user.id) }),
|
||||
},
|
||||
bio: about,
|
||||
commonChatsCount,
|
||||
pinnedMessageId: pinnedMsgId,
|
||||
isBlocked: Boolean(blocked),
|
||||
noVoiceMessages: voiceMessagesForbidden,
|
||||
...(profilePhoto instanceof GramJs.Photo && { profilePhoto: buildApiPhoto(profilePhoto) }),
|
||||
...(fallbackPhoto instanceof GramJs.Photo && { fallbackPhoto: buildApiPhoto(fallbackPhoto) }),
|
||||
...(personalPhoto instanceof GramJs.Photo && { personalPhoto: buildApiPhoto(personalPhoto) }),
|
||||
...(premiumGifts && { premiumGifts: premiumGifts.map((gift) => buildApiPremiumGiftOption(gift)) }),
|
||||
...(botInfo && { botInfo: buildApiBotInfo(botInfo, userId) }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import type {
|
||||
ApiUpdateAuthorizationStateType,
|
||||
OnApiUpdate,
|
||||
ApiUser,
|
||||
ApiUserFullInfo,
|
||||
} from '../../types';
|
||||
|
||||
import { DEBUG } from '../../../config';
|
||||
@ -117,10 +118,11 @@ export function onAuthReady() {
|
||||
onUpdate(buildAuthStateUpdate('authorizationStateReady'));
|
||||
}
|
||||
|
||||
export function onCurrentUserUpdate(currentUser: ApiUser) {
|
||||
export function onCurrentUserUpdate(currentUser: ApiUser, currentUserFullInfo: ApiUserFullInfo) {
|
||||
onUpdate({
|
||||
'@type': 'updateCurrentUser',
|
||||
currentUser,
|
||||
currentUserFullInfo,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ import {
|
||||
import { updater } from '../updater';
|
||||
import { setMessageBuilderCurrentUserId } from '../apiBuilders/messages';
|
||||
import downloadMediaWithClient, { parseMediaUrl } from './media';
|
||||
import { buildApiUserFromFull } from '../apiBuilders/users';
|
||||
import { buildApiUser, buildApiUserFullInfo } from '../apiBuilders/users';
|
||||
import localDb, { clearLocalDb } from '../localDb';
|
||||
import { buildApiPeerId } from '../apiBuilders/peers';
|
||||
import { addMessageToLocalDb, log } from '../helpers';
|
||||
@ -336,10 +336,11 @@ export async function fetchCurrentUser() {
|
||||
localDb.photos[user.photo.id.toString()] = user.photo;
|
||||
}
|
||||
localDb.users[buildApiPeerId(user.id, 'user')] = user;
|
||||
const currentUser = buildApiUserFromFull(userFull);
|
||||
const currentUserFullInfo = buildApiUserFullInfo(userFull);
|
||||
const currentUser = buildApiUser(user)!;
|
||||
|
||||
setMessageBuilderCurrentUserId(currentUser.id);
|
||||
onCurrentUserUpdate(currentUser);
|
||||
onCurrentUserUpdate(currentUser, currentUserFullInfo);
|
||||
|
||||
currentUserId = currentUser.id;
|
||||
setIsPremium({ isPremium: Boolean(currentUser.isPremium) });
|
||||
|
||||
@ -14,11 +14,11 @@ import {
|
||||
import { buildApiUser } from '../apiBuilders/users';
|
||||
|
||||
export async function fetchChannelStatistics({
|
||||
chat,
|
||||
}: { chat: ApiChat }) {
|
||||
chat, dcId,
|
||||
}: { chat: ApiChat; dcId?: number }) {
|
||||
const result = await invokeRequest(new GramJs.stats.GetBroadcastStats({
|
||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
}), undefined, undefined, undefined, chat.fullInfo!.statisticsDcId);
|
||||
}), undefined, undefined, undefined, dcId);
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
@ -31,11 +31,11 @@ export async function fetchChannelStatistics({
|
||||
}
|
||||
|
||||
export async function fetchGroupStatistics({
|
||||
chat,
|
||||
}: { chat: ApiChat }) {
|
||||
chat, dcId,
|
||||
}: { chat: ApiChat; dcId?: number }) {
|
||||
const result = await invokeRequest(new GramJs.stats.GetMegagroupStats({
|
||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
}), undefined, undefined, undefined, chat.fullInfo!.statisticsDcId);
|
||||
}), undefined, undefined, undefined, dcId);
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
@ -52,14 +52,16 @@ export async function fetchGroupStatistics({
|
||||
export async function fetchMessageStatistics({
|
||||
chat,
|
||||
messageId,
|
||||
dcId,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
messageId: number;
|
||||
dcId?: number;
|
||||
}): Promise<ApiMessageStatistics | undefined> {
|
||||
const result = await invokeRequest(new GramJs.stats.GetMessageStats({
|
||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
msgId: messageId,
|
||||
}), undefined, undefined, undefined, chat.fullInfo!.statisticsDcId);
|
||||
}), undefined, undefined, undefined, dcId);
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
getEntityTypeById,
|
||||
buildInputEmojiStatus,
|
||||
} from '../gramjsBuilders';
|
||||
import { buildApiUser, buildApiUserFromFull, buildApiUsersAndStatuses } from '../apiBuilders/users';
|
||||
import { buildApiUser, buildApiUserFullInfo, buildApiUsersAndStatuses } from '../apiBuilders/users';
|
||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { buildApiPhoto } from '../apiBuilders/common';
|
||||
import { addEntitiesWithPhotosToLocalDb, addPhotoToLocalDb, addUserToLocalDb } from '../helpers';
|
||||
@ -40,28 +40,28 @@ export async function fetchFullUser({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fullInfo = await invokeRequest(new GramJs.users.GetFullUser({ id: input }));
|
||||
const result = await invokeRequest(new GramJs.users.GetFullUser({ id: input }));
|
||||
|
||||
if (!fullInfo) {
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
updateLocalDb(fullInfo);
|
||||
addUserToLocalDb(fullInfo.users[0], true);
|
||||
updateLocalDb(result);
|
||||
addUserToLocalDb(result.users[0], true);
|
||||
|
||||
if (fullInfo.fullUser.profilePhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[fullInfo.fullUser.profilePhoto.id.toString()] = fullInfo.fullUser.profilePhoto;
|
||||
if (result.fullUser.profilePhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[result.fullUser.profilePhoto.id.toString()] = result.fullUser.profilePhoto;
|
||||
}
|
||||
|
||||
if (fullInfo.fullUser.personalPhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[fullInfo.fullUser.personalPhoto.id.toString()] = fullInfo.fullUser.personalPhoto;
|
||||
if (result.fullUser.personalPhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[result.fullUser.personalPhoto.id.toString()] = result.fullUser.personalPhoto;
|
||||
}
|
||||
|
||||
if (fullInfo.fullUser.fallbackPhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[fullInfo.fullUser.fallbackPhoto.id.toString()] = fullInfo.fullUser.fallbackPhoto;
|
||||
if (result.fullUser.fallbackPhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[result.fullUser.fallbackPhoto.id.toString()] = result.fullUser.fallbackPhoto;
|
||||
}
|
||||
|
||||
const botInfo = fullInfo.fullUser.botInfo;
|
||||
const botInfo = result.fullUser.botInfo;
|
||||
if (botInfo?.descriptionPhoto instanceof GramJs.Photo) {
|
||||
localDb.photos[botInfo.descriptionPhoto.id.toString()] = botInfo.descriptionPhoto;
|
||||
}
|
||||
@ -69,8 +69,8 @@ export async function fetchFullUser({
|
||||
localDb.documents[botInfo.descriptionDocument.id.toString()] = botInfo.descriptionDocument;
|
||||
}
|
||||
|
||||
const userWithFullInfo = buildApiUserFromFull(fullInfo);
|
||||
const user = buildApiUser(fullInfo.users[0]);
|
||||
const fullInfo = buildApiUserFullInfo(result);
|
||||
const user = buildApiUser(result.users[0])!;
|
||||
|
||||
onUpdate({
|
||||
'@type': 'updateUser',
|
||||
@ -78,11 +78,11 @@ export async function fetchFullUser({
|
||||
user: {
|
||||
...user,
|
||||
avatarHash: user?.avatarHash || undefined,
|
||||
fullInfo: userWithFullInfo.fullInfo,
|
||||
},
|
||||
fullInfo,
|
||||
});
|
||||
|
||||
return userWithFullInfo;
|
||||
return { user, fullInfo };
|
||||
}
|
||||
|
||||
export async function fetchCommonChats(id: string, accessHash?: string, maxId?: string) {
|
||||
|
||||
@ -67,8 +67,6 @@ export interface ApiChat {
|
||||
|
||||
// Obtained from GetChatSettings
|
||||
settings?: ApiChatSettings;
|
||||
// Obtained from GetFullChat / GetFullChannel
|
||||
fullInfo?: ApiChatFullInfo;
|
||||
|
||||
joinRequests?: ApiChatInviteImporter[];
|
||||
isJoinToSend?: boolean;
|
||||
|
||||
@ -87,6 +87,7 @@ export type ApiUpdateConnectionState = {
|
||||
export type ApiUpdateCurrentUser = {
|
||||
'@type': 'updateCurrentUser';
|
||||
currentUser: ApiUser;
|
||||
currentUserFullInfo: ApiUserFullInfo;
|
||||
};
|
||||
|
||||
export type ApiUpdateChat = {
|
||||
@ -338,6 +339,7 @@ export type ApiUpdateUser = {
|
||||
'@type': 'updateUser';
|
||||
id: string;
|
||||
user: Partial<ApiUser>;
|
||||
fullInfo?: ApiUserFullInfo;
|
||||
};
|
||||
|
||||
export type ApiUpdateRequestUserUpdate = {
|
||||
|
||||
@ -29,9 +29,6 @@ export interface ApiUser {
|
||||
fakeType?: ApiFakeType;
|
||||
isAttachBot?: boolean;
|
||||
emojiStatus?: ApiEmojiStatus;
|
||||
|
||||
// Obtained from GetFullUser / UserFullInfo
|
||||
fullInfo?: ApiUserFullInfo;
|
||||
}
|
||||
|
||||
export interface ApiUserFullInfo {
|
||||
|
||||
@ -103,7 +103,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
|
||||
@ -98,13 +98,13 @@ const AuthCode: FC<StateProps> = ({
|
||||
<h1>
|
||||
{authPhoneNumber}
|
||||
<div
|
||||
className="auth-number-edit"
|
||||
className="auth-number-edit div-button"
|
||||
onClick={handleReturnToAuthPhoneNumber}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title={lang('WrongNumber')}
|
||||
>
|
||||
<i className="icon-edit" />
|
||||
<i className="icon icon-edit" />
|
||||
</div>
|
||||
</h1>
|
||||
<p className="note">
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
.CountryCodeInput {
|
||||
.input-group {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
z-index: var(--z-country-code-input-group);
|
||||
|
||||
|
||||
@ -84,7 +84,7 @@
|
||||
align-items: center;
|
||||
border-radius: 0.75rem;
|
||||
transition: 0.15s ease-out background-color;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
&:hover {
|
||||
@ -97,7 +97,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon {
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -127,7 +127,7 @@
|
||||
height: 8.75rem;
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
}
|
||||
|
||||
&::before {
|
||||
|
||||
@ -143,7 +143,7 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
||||
onClick={onTrigger}
|
||||
ariaLabel={lang('AccDescrMoreOptions')}
|
||||
>
|
||||
<i className="icon-more" />
|
||||
<i className="icon icon-more" />
|
||||
</Button>
|
||||
);
|
||||
}, [lang]);
|
||||
@ -276,7 +276,7 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
||||
onClick={handleToggleFullscreen}
|
||||
ariaLabel={lang(isFullscreen ? 'AccExitFullscreen' : 'AccSwitchToFullscreen')}
|
||||
>
|
||||
<i className={isFullscreen ? 'icon-smallscreen' : 'icon-fullscreen'} />
|
||||
<i className={buildClassName('icon', isFullscreen ? 'icon-smallscreen' : 'icon-fullscreen')} />
|
||||
</Button>
|
||||
)}
|
||||
{isLandscapeLayout && (
|
||||
@ -286,7 +286,7 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
||||
color="translucent"
|
||||
onClick={handleToggleSidebar}
|
||||
>
|
||||
<i className="icon-sidebar" />
|
||||
<i className="icon icon-sidebar" />
|
||||
</Button>
|
||||
)}
|
||||
{((IS_SCREENSHARE_SUPPORTED && !shouldRaiseHand) || isAdmin) && (
|
||||
@ -319,7 +319,7 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
||||
color="translucent"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<i className="icon-close" />
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -359,7 +359,11 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
onClick={handleClickVideoOrSpeaker}
|
||||
>
|
||||
<i className={shouldRaiseHand ? 'icon-speaker' : (hasVideo ? 'icon-video-stop' : 'icon-video')} />
|
||||
<i className={buildClassName(
|
||||
'icon',
|
||||
shouldRaiseHand ? 'icon-speaker' : (hasVideo ? 'icon-video-stop' : 'icon-video'),
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -372,7 +376,7 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
||||
|
||||
<div className="button-wrapper">
|
||||
<button className="small-button leave" onClick={handleLeaveGroupCall}>
|
||||
<i className="icon-phone-discard" />
|
||||
<i className="icon icon-phone-discard" />
|
||||
</button>
|
||||
|
||||
<div className="button-text">
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.75rem;
|
||||
transition: 0.15s ease-out background-color;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&:hover {
|
||||
background: #2f363e;
|
||||
@ -72,7 +72,7 @@
|
||||
}
|
||||
|
||||
.streams {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,10 +4,10 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChat, ApiUser } from '../../../api/types';
|
||||
import type { ApiChat, ApiPhoto, ApiUser } from '../../../api/types';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { selectChat, selectUser } from '../../../global/selectors';
|
||||
import { selectChat, selectUser, selectUserPhotoFromFullInfo } from '../../../global/selectors';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import { GROUP_CALL_DEFAULT_VOLUME, GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config';
|
||||
|
||||
@ -23,6 +23,7 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
user?: ApiUser;
|
||||
userProfilePhoto?: ApiPhoto;
|
||||
chat?: ApiChat;
|
||||
};
|
||||
|
||||
@ -30,6 +31,7 @@ const GroupCallParticipant: FC<OwnProps & StateProps> = ({
|
||||
openParticipantMenu,
|
||||
participant,
|
||||
user,
|
||||
userProfilePhoto,
|
||||
chat,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
@ -79,7 +81,7 @@ const GroupCallParticipant: FC<OwnProps & StateProps> = ({
|
||||
onClick={handleOnClick}
|
||||
ref={anchorRef}
|
||||
>
|
||||
<Avatar user={user} chat={chat} size="medium" />
|
||||
<Avatar user={user} chat={chat} userProfilePhoto={userProfilePhoto} size="medium" />
|
||||
<div className="info">
|
||||
<span className="name">{name}</span>
|
||||
<span className={buildClassName('about', aboutColor)}>{aboutText}</span>
|
||||
@ -96,6 +98,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
return {
|
||||
user: participant.isUser ? selectUser(global, participant.id) : undefined,
|
||||
chat: !participant.isUser ? selectChat(global, participant.id) : undefined,
|
||||
userProfilePhoto: participant.isUser ? selectUserPhotoFromFullInfo(global, participant.id) : undefined,
|
||||
};
|
||||
},
|
||||
)(GroupCallParticipant));
|
||||
|
||||
@ -53,8 +53,8 @@ const GroupCallParticipantList: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<div className="participants">
|
||||
<div className="invite-btn" onClick={handleCreateGroupCallInviteLink}>
|
||||
<div className="icon">
|
||||
<i className="icon-add-user" />
|
||||
<div className="icon-wrapper">
|
||||
<i className="icon icon-add-user" />
|
||||
</div>
|
||||
<div className="text">{lang('VoipGroupInviteMember')}</div>
|
||||
</div>
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
@mixin thumb-styles() {
|
||||
border: none;
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
width: calc(50% - 0.25rem);
|
||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||
transition: 0.25s ease-out width;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
.thumbnail-avatar {
|
||||
position: absolute;
|
||||
@ -57,7 +57,7 @@
|
||||
gap: 0.25rem;
|
||||
transition: 0.25s ease-out opacity, 0.25s ease-out background-color;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
outline: none !important;
|
||||
|
||||
&:hover {
|
||||
|
||||
@ -4,11 +4,11 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useCallback } from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChat, ApiUser } from '../../../api/types';
|
||||
import type { ApiChat, ApiPhoto, ApiUser } from '../../../api/types';
|
||||
|
||||
import { GROUP_CALL_THUMB_VIDEO_DISABLED } from '../../../config';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { selectChat, selectUser } from '../../../global/selectors';
|
||||
import { selectChat, selectUser, selectUserPhotoFromFullInfo } from '../../../global/selectors';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
@ -25,6 +25,7 @@ type OwnProps = {
|
||||
type StateProps = {
|
||||
user?: ApiUser;
|
||||
chat?: ApiChat;
|
||||
userProfilePhoto?: ApiPhoto;
|
||||
currentUserId?: string;
|
||||
isActive?: boolean;
|
||||
};
|
||||
@ -34,6 +35,7 @@ const GroupCallParticipantVideo: FC<OwnProps & StateProps> = ({
|
||||
onClick,
|
||||
user,
|
||||
chat,
|
||||
userProfilePhoto,
|
||||
isActive,
|
||||
isFullscreen,
|
||||
}) => {
|
||||
@ -56,11 +58,11 @@ const GroupCallParticipantVideo: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
{isFullscreen && (
|
||||
<button className="back-button">
|
||||
<i className="icon-arrow-left" />
|
||||
<i className="icon icon-arrow-left" />
|
||||
{lang('Back')}
|
||||
</button>
|
||||
)}
|
||||
<Avatar user={user} chat={chat} className="thumbnail-avatar" />
|
||||
<Avatar user={user} chat={chat} userProfilePhoto={userProfilePhoto} className="thumbnail-avatar" />
|
||||
{!GROUP_CALL_THUMB_VIDEO_DISABLED && (
|
||||
<div className="thumbnail-wrapper">
|
||||
<video className="thumbnail" muted autoPlay playsInline srcObject={streams?.[type]} />
|
||||
@ -68,9 +70,9 @@ const GroupCallParticipantVideo: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
<video className="video" muted autoPlay playsInline srcObject={streams?.[type]} />
|
||||
<div className="info">
|
||||
<i className="icon-microphone-alt" />
|
||||
<i className="icon icon-microphone-alt" />
|
||||
<span className="name">{user?.firstName || chat?.title}</span>
|
||||
{type === 'presentation' && <i className="last-icon icon-active-sessions" />}
|
||||
{type === 'presentation' && <i className="icon last-icon icon-active-sessions" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -82,6 +84,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
currentUserId: global.currentUserId,
|
||||
user: participant.isUser ? selectUser(global, participant.id) : undefined,
|
||||
chat: !participant.isUser ? selectChat(global, participant.id) : undefined,
|
||||
userProfilePhoto: participant.isUser ? selectUserPhotoFromFullInfo(global, participant.id) : undefined,
|
||||
isActive: (participant.amplitude || 0) > THRESHOLD,
|
||||
};
|
||||
},
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
padding: 0.375rem 0.5rem 0.375rem 0.75rem;
|
||||
background: var(--color-background);
|
||||
z-index: -1;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
|
||||
@ -2,9 +2,9 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useMemo,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChat, ApiGroupCall, ApiUser } from '../../../api/types';
|
||||
import type { ApiGroupCall } from '../../../api/types';
|
||||
import type { AnimationLevel } from '../../../types';
|
||||
|
||||
import { selectChatGroupCall } from '../../../global/selectors/calls';
|
||||
@ -14,6 +14,7 @@ import useLang from '../../../hooks/useLang';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
import Avatar from '../../common/Avatar';
|
||||
import UserAvatar from '../../common/UserAvatar';
|
||||
|
||||
import './GroupCallTopPane.scss';
|
||||
|
||||
@ -26,8 +27,6 @@ type OwnProps = {
|
||||
type StateProps = {
|
||||
groupCall?: ApiGroupCall;
|
||||
isActive: boolean;
|
||||
usersById: Record<string, ApiUser>;
|
||||
chatsById: Record<string, ApiChat>;
|
||||
animationLevel: AnimationLevel;
|
||||
};
|
||||
|
||||
@ -37,8 +36,6 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
|
||||
className,
|
||||
groupCall,
|
||||
hasPinnedOffset,
|
||||
usersById,
|
||||
chatsById,
|
||||
animationLevel,
|
||||
}) => {
|
||||
const {
|
||||
@ -57,22 +54,28 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
|
||||
const participants = groupCall?.participants;
|
||||
|
||||
const fetchedParticipants = useMemo(() => {
|
||||
if (participants) {
|
||||
return Object.values(participants).filter((_, i) => i < 3).map(({ id, isUser }) => {
|
||||
if (isUser) {
|
||||
if (!usersById[id]) {
|
||||
return undefined;
|
||||
}
|
||||
return { user: usersById[id] };
|
||||
} else {
|
||||
if (!chatsById[id]) {
|
||||
return undefined;
|
||||
}
|
||||
return { chat: chatsById[id] };
|
||||
if (!participants) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// No need for expensive global updates on users and chats, so we avoid them
|
||||
const usersById = getGlobal().users.byId;
|
||||
const chatsById = getGlobal().chats.byId;
|
||||
|
||||
return Object.values(participants).filter((_, i) => i < 3).map(({ id, isUser }) => {
|
||||
if (isUser) {
|
||||
if (!usersById[id]) {
|
||||
return undefined;
|
||||
}
|
||||
}).filter(Boolean);
|
||||
} else return [];
|
||||
}, [chatsById, participants, usersById]);
|
||||
return { user: usersById[id] };
|
||||
} else {
|
||||
if (!chatsById[id]) {
|
||||
return undefined;
|
||||
}
|
||||
return { chat: chatsById[id] };
|
||||
}
|
||||
}).filter(Boolean);
|
||||
}, [participants]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!groupCall?.id) return undefined;
|
||||
@ -111,7 +114,7 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
|
||||
{fetchedParticipants.map((p) => {
|
||||
if (!p) return undefined;
|
||||
if (p.user) {
|
||||
return <Avatar key={p.user.id} user={p.user} animationLevel={animationLevel} />;
|
||||
return <UserAvatar key={p.user.id} user={p.user} />;
|
||||
} else {
|
||||
return <Avatar key={p.chat.id} chat={p.chat} animationLevel={animationLevel} />;
|
||||
}
|
||||
@ -125,18 +128,18 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }) => {
|
||||
(global, { chatId }): StateProps => {
|
||||
const chat = selectChat(global, chatId)!;
|
||||
const groupCall = selectChatGroupCall(global, chatId);
|
||||
const activeGroupCallId = selectTabState(global).isMasterTab ? global.groupCalls.activeGroupCallId : undefined;
|
||||
|
||||
return {
|
||||
groupCall,
|
||||
usersById: global.users.byId,
|
||||
chatsById: global.chats.byId,
|
||||
activeGroupCallId: global.groupCalls.activeGroupCallId,
|
||||
isActive: ((!groupCall ? (chat && chat.isCallNotEmpty && chat.isCallActive)
|
||||
: (groupCall.participantsCount > 0 && groupCall.isLoaded)))
|
||||
&& (activeGroupCallId !== groupCall?.id),
|
||||
isActive: activeGroupCallId !== groupCall?.id && Boolean(
|
||||
groupCall
|
||||
? groupCall.participantsCount > 0 && groupCall.isLoaded
|
||||
: chat && chat.isCallNotEmpty && chat.isCallActive,
|
||||
),
|
||||
animationLevel: global.settings.byKey.animationLevel,
|
||||
};
|
||||
},
|
||||
|
||||
@ -82,7 +82,7 @@
|
||||
.emojis {
|
||||
user-select: none;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
margin-top: 1rem;
|
||||
height: 3rem;
|
||||
transition: 0.25s ease-in-out transform;
|
||||
|
||||
@ -5,7 +5,7 @@ import React, {
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
import '../../../global/actions/calls';
|
||||
|
||||
import type { ApiPhoneCall, ApiUser } from '../../../api/types';
|
||||
import type { ApiPhoneCall, ApiPhoto, ApiUser } from '../../../api/types';
|
||||
import type { AnimationLevel } from '../../../types';
|
||||
|
||||
import {
|
||||
@ -14,7 +14,7 @@ import {
|
||||
IS_REQUEST_FULLSCREEN_SUPPORTED,
|
||||
} from '../../../util/windowEnvironment';
|
||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||
import { selectTabState } from '../../../global/selectors';
|
||||
import { selectTabState, selectUserPhotoFromFullInfo } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { selectPhoneCallUser } from '../../../global/selectors/calls';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
@ -39,6 +39,7 @@ import styles from './PhoneCall.module.scss';
|
||||
type StateProps = {
|
||||
user?: ApiUser;
|
||||
phoneCall?: ApiPhoneCall;
|
||||
userProfilePhoto?: ApiPhoto;
|
||||
isOutgoing: boolean;
|
||||
isCallPanelVisible?: boolean;
|
||||
animationLevel: AnimationLevel;
|
||||
@ -46,6 +47,7 @@ type StateProps = {
|
||||
|
||||
const PhoneCall: FC<StateProps> = ({
|
||||
user,
|
||||
userProfilePhoto,
|
||||
isOutgoing,
|
||||
phoneCall,
|
||||
isCallPanelVisible,
|
||||
@ -238,6 +240,7 @@ const PhoneCall: FC<StateProps> = ({
|
||||
>
|
||||
<Avatar
|
||||
user={user}
|
||||
userProfilePhoto={userProfilePhoto}
|
||||
size="jumbo"
|
||||
className={hasVideo || hasPresentation ? styles.blurred : ''}
|
||||
withVideo
|
||||
@ -279,7 +282,7 @@ const PhoneCall: FC<StateProps> = ({
|
||||
onClick={handleToggleFullscreen}
|
||||
ariaLabel={lang(isFullscreen ? 'AccExitFullscreen' : 'AccSwitchToFullscreen')}
|
||||
>
|
||||
<i className={isFullscreen ? 'icon-smallscreen' : 'icon-fullscreen'} />
|
||||
<i className={buildClassName('icon', isFullscreen ? 'icon-smallscreen' : 'icon-fullscreen')} />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@ -290,7 +293,7 @@ const PhoneCall: FC<StateProps> = ({
|
||||
onClick={handleClose}
|
||||
className={styles.closeButton}
|
||||
>
|
||||
<i className="icon-close" />
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
@ -372,10 +375,13 @@ export default memo(withGlobal(
|
||||
(global): StateProps => {
|
||||
const { phoneCall, currentUserId } = global;
|
||||
const { isCallPanelVisible, isMasterTab } = selectTabState(global);
|
||||
const user = selectPhoneCallUser(global);
|
||||
const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined;
|
||||
|
||||
return {
|
||||
isCallPanelVisible: Boolean(isCallPanelVisible),
|
||||
user: selectPhoneCallUser(global),
|
||||
user,
|
||||
userProfilePhoto,
|
||||
isOutgoing: phoneCall?.adminId === currentUserId,
|
||||
phoneCall: isMasterTab ? phoneCall : undefined,
|
||||
animationLevel: global.settings.byKey.animationLevel,
|
||||
|
||||
@ -36,7 +36,7 @@ const PhoneCallButton: FC<OwnProps> = ({
|
||||
onClick={onClick}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{customIcon || <i className={buildClassName(iconClassName, `icon-${icon}`)} />}
|
||||
{customIcon || <i className={buildClassName(iconClassName, 'icon', `icon-${icon}`)} />}
|
||||
</Button>
|
||||
<div className={styles.buttonText}>{label}</div>
|
||||
</div>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
}
|
||||
|
||||
.star {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
&:not(:first-child) {
|
||||
|
||||
@ -56,6 +56,7 @@ const RatePhoneCallModal: FC<OwnProps> = ({
|
||||
return (
|
||||
<i
|
||||
className={buildClassName(
|
||||
'icon',
|
||||
isFilled ? 'icon-favorite-filled' : 'icon-favorite',
|
||||
isFilled && styles.isFilled,
|
||||
styles.star,
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
height: 3rem;
|
||||
margin-inline-end: 0.75rem;
|
||||
|
||||
i {
|
||||
.icon {
|
||||
font-size: 1.625rem;
|
||||
|
||||
&.icon-pause {
|
||||
@ -57,7 +57,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
.icon {
|
||||
position: absolute;
|
||||
|
||||
&.icon-play {
|
||||
@ -94,7 +94,7 @@
|
||||
border: 0.125rem solid var(--background-color);
|
||||
z-index: 1;
|
||||
|
||||
i {
|
||||
.icon {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
}
|
||||
@ -208,7 +208,7 @@
|
||||
}
|
||||
|
||||
.waveform {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
margin-left: 1px;
|
||||
touch-action: none;
|
||||
display: flex;
|
||||
@ -273,7 +273,7 @@
|
||||
height: 1.25rem;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
touch-action: none;
|
||||
|
||||
&::before {
|
||||
@ -295,7 +295,7 @@
|
||||
width: 100%;
|
||||
top: 6px;
|
||||
|
||||
i {
|
||||
&-inner {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
background-color: var(--color-interactive-active);
|
||||
@ -321,7 +321,7 @@
|
||||
top: 7px;
|
||||
left: 0;
|
||||
|
||||
i {
|
||||
&-inner {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
@ -340,7 +340,7 @@ const Audio: FC<OwnProps> = ({
|
||||
<div className={fullClassName} dir={lang.isRtl ? 'rtl' : 'ltr'}>
|
||||
{isSelectable && (
|
||||
<div className="message-select-control">
|
||||
{isSelected && <i className="icon-select" />}
|
||||
{isSelected && <i className="icon icon-select" />}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
@ -354,8 +354,8 @@ const Audio: FC<OwnProps> = ({
|
||||
isRtl={lang.isRtl}
|
||||
backgroundImage={coverBlobUrl}
|
||||
>
|
||||
<i className="icon-play" />
|
||||
<i className="icon-pause" />
|
||||
<i className="icon icon-play" />
|
||||
<i className="icon icon-pause" />
|
||||
</Button>
|
||||
{shouldRenderSpinner && (
|
||||
<div className={buildClassName('media-loading', spinnerClassNames, shouldRenderCross && 'interactive')}>
|
||||
@ -376,7 +376,7 @@ const Audio: FC<OwnProps> = ({
|
||||
ariaLabel={isDownloading ? 'Cancel download' : 'Download'}
|
||||
onClick={handleDownloadClick}
|
||||
>
|
||||
<i className={isDownloading ? 'icon-close' : 'icon-arrow-down'} />
|
||||
<i className={buildClassName('icon', isDownloading ? 'icon-close' : 'icon-arrow-down')} />
|
||||
</Button>
|
||||
)}
|
||||
{origin === AudioOrigin.Search && renderWithTitle()}
|
||||
@ -519,6 +519,7 @@ function renderVoice(
|
||||
>
|
||||
<i className={buildClassName(
|
||||
'transcribe-icon',
|
||||
'icon',
|
||||
(isTranscribed || isTranscriptionError) ? 'icon-down' : 'icon-transcribe',
|
||||
(isTranscribed || isTranscriptionError) && !isTranscriptionHidden && 'transcribe-shown',
|
||||
)}
|
||||
@ -606,11 +607,13 @@ function renderSeekline(
|
||||
))}
|
||||
<span className="seekline-play-progress">
|
||||
<i
|
||||
className="seekline-play-progress-inner"
|
||||
style={`transform: translateX(${playProgress * 100}%)`}
|
||||
/>
|
||||
</span>
|
||||
<span className="seekline-thumb">
|
||||
<i
|
||||
className="seekline-thumb-inner"
|
||||
style={`transform: translateX(${playProgress * 100}%)`}
|
||||
/>
|
||||
</span>
|
||||
|
||||
@ -99,7 +99,7 @@
|
||||
&.size-large {
|
||||
font-size: 1.3125rem;
|
||||
|
||||
i {
|
||||
.icon {
|
||||
font-size: 1.625rem;
|
||||
}
|
||||
|
||||
@ -148,7 +148,7 @@
|
||||
}
|
||||
|
||||
&.interactive {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
}
|
||||
|
||||
.poster {
|
||||
|
||||
@ -49,6 +49,7 @@ type OwnProps = {
|
||||
chat?: ApiChat;
|
||||
user?: ApiUser;
|
||||
photo?: ApiPhoto;
|
||||
userProfilePhoto?: ApiPhoto;
|
||||
userStatus?: ApiUserStatus;
|
||||
text?: string;
|
||||
isSavedMessages?: boolean;
|
||||
@ -69,6 +70,7 @@ const Avatar: FC<OwnProps> = ({
|
||||
chat,
|
||||
user,
|
||||
photo,
|
||||
userProfilePhoto,
|
||||
userStatus,
|
||||
text,
|
||||
isSavedMessages,
|
||||
@ -96,8 +98,7 @@ const Avatar: FC<OwnProps> = ({
|
||||
withVideo && !VIDEO_AVATARS_DISABLED && animationLevel === ANIMATION_LEVEL_MAX
|
||||
&& user?.isPremium && user?.hasVideoAvatar
|
||||
);
|
||||
const profilePhoto = user?.fullInfo?.personalPhoto || user?.fullInfo?.profilePhoto || user?.fullInfo?.fallbackPhoto;
|
||||
const hasProfileVideo = profilePhoto?.isVideo;
|
||||
const hasProfileVideo = userProfilePhoto?.isVideo;
|
||||
const isIntersectingForVideo = useIsIntersecting(
|
||||
ref, canShowVideo && hasProfileVideo ? observeIntersection : undefined,
|
||||
);
|
||||
@ -117,7 +118,7 @@ const Avatar: FC<OwnProps> = ({
|
||||
}
|
||||
|
||||
if (hasProfileVideo) {
|
||||
videoHash = getChatAvatarHash(user!, undefined, 'video');
|
||||
videoHash = getChatAvatarHash(user!, undefined, 'video', undefined, userProfilePhoto);
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,10 +150,10 @@ const Avatar: FC<OwnProps> = ({
|
||||
|
||||
const userId = user?.id;
|
||||
useEffect(() => {
|
||||
if (userId && canShowVideo && !profilePhoto) {
|
||||
if (userId && canShowVideo && !userProfilePhoto) {
|
||||
loadFullUser({ userId });
|
||||
}
|
||||
}, [loadFullUser, profilePhoto, userId, canShowVideo]);
|
||||
}, [loadFullUser, userProfilePhoto, userId, canShowVideo]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
@ -160,11 +161,35 @@ const Avatar: FC<OwnProps> = ({
|
||||
const author = user ? getUserFullName(user) : (chat ? getChatTitle(lang, chat) : text);
|
||||
|
||||
if (isSavedMessages) {
|
||||
content = <i className={buildClassName(cn.icon, 'icon-avatar-saved-messages')} role="img" aria-label={author} />;
|
||||
content = (
|
||||
<i
|
||||
className={buildClassName(cn.icon,
|
||||
'icon',
|
||||
'icon-avatar-saved-messages')}
|
||||
role="img"
|
||||
aria-label={author}
|
||||
/>
|
||||
);
|
||||
} else if (isDeleted) {
|
||||
content = <i className={buildClassName(cn.icon, 'icon-avatar-deleted-account')} role="img" aria-label={author} />;
|
||||
content = (
|
||||
<i
|
||||
className={buildClassName(cn.icon,
|
||||
'icon',
|
||||
'icon-avatar-deleted-account')}
|
||||
role="img"
|
||||
aria-label={author}
|
||||
/>
|
||||
);
|
||||
} else if (isReplies) {
|
||||
content = <i className={buildClassName(cn.icon, 'icon-reply-filled')} role="img" aria-label={author} />;
|
||||
content = (
|
||||
<i
|
||||
className={buildClassName(cn.icon,
|
||||
'icon',
|
||||
'icon-reply-filled')}
|
||||
role="img"
|
||||
aria-label={author}
|
||||
/>
|
||||
);
|
||||
} else if (hasBlobUrl) {
|
||||
content = (
|
||||
<>
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-interactive-element-hover);
|
||||
|
||||
@ -249,7 +249,7 @@ const CalendarModal: FC<OwnProps> = ({
|
||||
color="translucent"
|
||||
onClick={onClose}
|
||||
>
|
||||
<i className="icon-close" />
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
|
||||
<h4>
|
||||
@ -265,7 +265,7 @@ const CalendarModal: FC<OwnProps> = ({
|
||||
disabled={shouldDisablePrevMonth}
|
||||
onClick={!shouldDisablePrevMonth ? handlePrevMonth : undefined}
|
||||
>
|
||||
<i className="icon-previous" />
|
||||
<i className="icon icon-previous" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -275,7 +275,7 @@ const CalendarModal: FC<OwnProps> = ({
|
||||
disabled={shouldDisableNextMonth}
|
||||
onClick={!shouldDisableNextMonth ? handleNextMonth : undefined}
|
||||
>
|
||||
<i className="icon-next" />
|
||||
<i className="icon icon-next" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -297,6 +297,7 @@ const CalendarModal: FC<OwnProps> = ({
|
||||
onClick={() => handleDateSelect(gridDate)}
|
||||
className={buildClassName(
|
||||
'day-button',
|
||||
'div-button',
|
||||
isDisabledDay(
|
||||
currentYear, currentMonth, gridDate, minDate, maxDate,
|
||||
)
|
||||
|
||||
@ -12,10 +12,15 @@ import { MAIN_THREAD_ID } from '../../api/types';
|
||||
|
||||
import { TME_LINK_PREFIX } from '../../config';
|
||||
import {
|
||||
selectChat, selectCurrentMessageList, selectNotifyExceptions, selectNotifySettings, selectUser,
|
||||
selectChat,
|
||||
selectChatFullInfo,
|
||||
selectCurrentMessageList,
|
||||
selectNotifyExceptions,
|
||||
selectNotifySettings,
|
||||
selectUser,
|
||||
selectUserFullInfo,
|
||||
} from '../../global/selectors';
|
||||
import {
|
||||
getChatDescription,
|
||||
getChatLink,
|
||||
getTopicLink,
|
||||
getHasAdminRight,
|
||||
@ -47,6 +52,8 @@ type StateProps =
|
||||
isMuted?: boolean;
|
||||
phoneCodeList: ApiCountryCode[];
|
||||
topicId?: number;
|
||||
description?: string;
|
||||
chatInviteLink?: string;
|
||||
}
|
||||
& Pick<GlobalState, 'lastSyncTime'>;
|
||||
|
||||
@ -61,6 +68,8 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
||||
isMuted,
|
||||
phoneCodeList,
|
||||
topicId,
|
||||
description,
|
||||
chatInviteLink,
|
||||
}) => {
|
||||
const {
|
||||
loadFullUser,
|
||||
@ -71,7 +80,6 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const {
|
||||
id: userId,
|
||||
fullInfo,
|
||||
usernames,
|
||||
phoneNumber,
|
||||
isSelf,
|
||||
@ -108,8 +116,8 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
||||
|
||||
return isTopicInfo
|
||||
? getTopicLink(chat.id, activeChatUsernames?.[0].username, topicId)
|
||||
: getChatLink(chat);
|
||||
}, [chat, isTopicInfo, activeChatUsernames, topicId]);
|
||||
: getChatLink(chat) || chatInviteLink;
|
||||
}, [chat, isTopicInfo, activeChatUsernames, topicId, chatInviteLink]);
|
||||
|
||||
const handleNotificationChange = useCallback(() => {
|
||||
setAreNotificationsEnabled((current) => {
|
||||
@ -141,7 +149,6 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
const formattedNumber = phoneNumber && formatPhoneNumberWithCode(phoneCodeList, phoneNumber);
|
||||
const description = (fullInfo?.bio) || getChatDescription(chat);
|
||||
|
||||
function renderUsernames(usernameList: ApiUsername[], isChat?: boolean) {
|
||||
const [mainUsername, ...otherUsernames] = usernameList;
|
||||
@ -259,6 +266,11 @@ export default memo(withGlobal<OwnProps>(
|
||||
const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global));
|
||||
const { threadId } = selectCurrentMessageList(global) || {};
|
||||
const topicId = isForum ? threadId : undefined;
|
||||
const chatInviteLink = chat ? selectChatFullInfo(global, chat.id)?.inviteLink : undefined;
|
||||
let description = user ? selectUserFullInfo(global, user.id)?.bio : undefined;
|
||||
if (!description && chat) {
|
||||
description = selectChatFullInfo(global, chat.id)?.about;
|
||||
}
|
||||
|
||||
const canInviteUsers = chat && !user && (
|
||||
(!isChatChannel(chat) && !isUserRightBanned(chat, 'inviteUsers'))
|
||||
@ -273,6 +285,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
canInviteUsers,
|
||||
isMuted,
|
||||
topicId,
|
||||
chatInviteLink,
|
||||
description,
|
||||
};
|
||||
},
|
||||
)(ChatExtra));
|
||||
|
||||
@ -158,7 +158,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
||||
<>
|
||||
<div className="modal-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button round color="translucent" size="smaller" ariaLabel={lang('Back')} onClick={handleHeaderBackClick}>
|
||||
<i className="icon-arrow-left" />
|
||||
<i className="icon icon-arrow-left" />
|
||||
</Button>
|
||||
<InputText
|
||||
ref={topicSearchRef}
|
||||
@ -211,7 +211,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
||||
ariaLabel={lang('Close')}
|
||||
onClick={onClose}
|
||||
>
|
||||
<i className="icon-close" />
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
<InputText
|
||||
ref={searchRef}
|
||||
@ -266,7 +266,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
||||
onClose={onClose}
|
||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||
>
|
||||
<Transition activeKey={activeKey} name="slide-fade">
|
||||
<Transition activeKey={activeKey} name="slideFade">
|
||||
{() => {
|
||||
return activeKey === TOPIC_LIST_SLIDE ? renderTopicList() : renderChatList();
|
||||
}}
|
||||
|
||||
@ -320,7 +320,7 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
||||
onClick={() => selectStickerSet(index)}
|
||||
>
|
||||
{(stickerSet.id === RECENT_SYMBOL_SET_ID || stickerSet.id === POPULAR_SYMBOL_SET_ID) ? (
|
||||
<i className="icon-recent" />
|
||||
<i className="icon icon-recent" />
|
||||
) : (
|
||||
<StickerSetCover
|
||||
stickerSet={stickerSet as ApiStickerSet}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
border-radius: var(--border-radius-messages-small);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
direction: ltr;
|
||||
|
||||
@for $i from 1 through 8 {
|
||||
|
||||
@ -115,7 +115,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
</p>
|
||||
<div className="message-title" dir="auto">{renderText(senderTitle || title || NBSP)}</div>
|
||||
</div>
|
||||
{hasContextMenu && <i className="embedded-more icon-more" />}
|
||||
{hasContextMenu && <i className="embedded-more icon icon-more" />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -97,7 +97,7 @@
|
||||
}
|
||||
|
||||
&.interactive .file-icon-container {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&:hover {
|
||||
.file-icon::after {
|
||||
|
||||
@ -95,7 +95,7 @@ const File: FC<OwnProps> = ({
|
||||
<div ref={elementRef} className={fullClassName} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{isSelectable && (
|
||||
<div className="message-select-control">
|
||||
{isSelected && <i className="icon-select" />}
|
||||
{isSelected && <i className="icon icon-select" />}
|
||||
</div>
|
||||
)}
|
||||
<div className="file-icon-container" onClick={isUploading ? undefined : onClick}>
|
||||
@ -135,6 +135,7 @@ const File: FC<OwnProps> = ({
|
||||
<i
|
||||
className={buildClassName(
|
||||
'action-icon',
|
||||
'icon',
|
||||
actionIcon || 'icon-download',
|
||||
shouldSpinnerRender && 'hidden',
|
||||
)}
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
}
|
||||
|
||||
&.interactive {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
|
||||
@ -154,7 +154,7 @@ const GifButton: FC<OwnProps> = ({
|
||||
pill
|
||||
onClick={handleUnsaveClick}
|
||||
>
|
||||
<i className="icon-close gif-unsave-button-icon" />
|
||||
<i className="icon icon-close gif-unsave-button-icon" />
|
||||
</Button>
|
||||
)}
|
||||
{withThumb && (
|
||||
|
||||
@ -165,7 +165,7 @@ const ManageUsernames: FC<OwnProps> = ({
|
||||
>
|
||||
<ListItem
|
||||
key={usernameData.username}
|
||||
className={buildClassName('mb-2 no-icon', styles.item)}
|
||||
className={buildClassName('drag-item mb-2 no-icon', styles.item)}
|
||||
narrow
|
||||
secondaryIcon="more"
|
||||
icon={usernameData.isActive ? 'link' : 'link-broken'}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
padding-bottom: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
.video-duration {
|
||||
position: absolute;
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
line-height: 1;
|
||||
font-size: 1.1875rem;
|
||||
|
||||
i {
|
||||
.icon {
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
|
||||
@ -21,9 +21,9 @@ const MessageOutgoingStatus: FC<OwnProps> = ({ status }) => {
|
||||
<Transition name="reveal" activeKey={Keys[status]}>
|
||||
{status === 'failed' ? (
|
||||
<div className="MessageOutgoingStatus--failed">
|
||||
<i className="icon-message-failed" />
|
||||
<i className="icon icon-message-failed" />
|
||||
</div>
|
||||
) : <i className={`icon-message-${status}`} />}
|
||||
) : <i className={`icon icon-message-${status}`} />}
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -141,13 +141,13 @@ const PasswordForm: FC<OwnProps> = ({
|
||||
/>
|
||||
<label>{error || hint || placeholder}</label>
|
||||
<div
|
||||
className="toggle-password"
|
||||
className="div-button toggle-password"
|
||||
onClick={togglePasswordVisibility}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Toggle password visibility"
|
||||
>
|
||||
<i className={isPasswordVisible ? 'icon-eye' : 'icon-eye-closed'} />
|
||||
<i className={buildClassName('icon', isPasswordVisible ? 'icon-eye' : 'icon-eye-closed')} />
|
||||
</div>
|
||||
</div>
|
||||
{description && <p className="description">{description}</p>}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
margin-bottom: 0.5rem;
|
||||
padding-right: 1rem;
|
||||
border-radius: 1rem;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex-shrink: 1;
|
||||
@ -68,7 +68,7 @@
|
||||
flex-shrink: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
.Avatar__icon, i {
|
||||
.icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
@ -82,7 +82,7 @@
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
|
||||
i {
|
||||
.icon {
|
||||
font-size: 1.25rem;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
|
||||
@ -2,9 +2,9 @@ import React, { memo } from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../global';
|
||||
|
||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||
import type { ApiChat, ApiUser } from '../../api/types';
|
||||
import type { ApiChat, ApiPhoto, ApiUser } from '../../api/types';
|
||||
|
||||
import { selectChat, selectUser } from '../../global/selectors';
|
||||
import { selectChat, selectUser, selectUserPhotoFromFullInfo } from '../../global/selectors';
|
||||
import { getChatTitle, getUserFirstOrLastName, isUserId } from '../../global/helpers';
|
||||
import renderText from './helpers/renderText';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
@ -28,6 +28,7 @@ type OwnProps = {
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
user?: ApiUser;
|
||||
userProfilePhoto?: ApiPhoto;
|
||||
currentUserId?: string;
|
||||
};
|
||||
|
||||
@ -39,6 +40,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
||||
clickArg,
|
||||
chat,
|
||||
user,
|
||||
userProfilePhoto,
|
||||
className,
|
||||
currentUserId,
|
||||
onClick,
|
||||
@ -51,7 +53,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
||||
if (icon && title) {
|
||||
iconElement = (
|
||||
<div className="item-icon">
|
||||
<i className={`icon-${icon}`} />
|
||||
<i className={buildClassName('icon', `icon-${icon}`)} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -61,6 +63,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
||||
<Avatar
|
||||
chat={chat}
|
||||
user={user}
|
||||
userProfilePhoto={userProfilePhoto}
|
||||
size="small"
|
||||
isSavedMessages={user?.isSelf}
|
||||
/>
|
||||
@ -96,7 +99,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
{canClose && (
|
||||
<div className="item-remove">
|
||||
<i className="icon-close" />
|
||||
<i className="icon icon-close" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -111,10 +114,12 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const chat = chatOrUserId ? selectChat(global, chatOrUserId) : undefined;
|
||||
const user = isUserId(chatOrUserId) ? selectUser(global, chatOrUserId) : undefined;
|
||||
const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined;
|
||||
|
||||
return {
|
||||
chat,
|
||||
user,
|
||||
userProfilePhoto,
|
||||
currentUserId: global.currentUserId,
|
||||
};
|
||||
},
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,13 +5,15 @@ import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type {
|
||||
ApiUser, ApiTypingStatus, ApiUserStatus, ApiChatMember,
|
||||
ApiUser, ApiTypingStatus, ApiUserStatus, ApiChatMember, ApiPhoto,
|
||||
} from '../../api/types';
|
||||
import type { GlobalState } from '../../global/types';
|
||||
import type { AnimationLevel } from '../../types';
|
||||
import { MediaViewerOrigin } from '../../types';
|
||||
|
||||
import { selectChatMessages, selectUser, selectUserStatus } from '../../global/selectors';
|
||||
import {
|
||||
selectChatMessages, selectUser, selectUserPhotoFromFullInfo, selectUserStatus,
|
||||
} from '../../global/selectors';
|
||||
import { getMainUsername, getUserStatus, isUserOnline } from '../../global/helpers';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import renderText from './helpers/renderText';
|
||||
@ -45,6 +47,7 @@ type StateProps =
|
||||
{
|
||||
user?: ApiUser;
|
||||
userStatus?: ApiUserStatus;
|
||||
userProfilePhoto?: ApiPhoto;
|
||||
isSavedMessages?: boolean;
|
||||
animationLevel: AnimationLevel;
|
||||
areMessagesLoaded: boolean;
|
||||
@ -66,6 +69,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
||||
noRtl,
|
||||
user,
|
||||
userStatus,
|
||||
userProfilePhoto,
|
||||
isSavedMessages,
|
||||
areMessagesLoaded,
|
||||
animationLevel,
|
||||
@ -171,6 +175,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
||||
key={user.id}
|
||||
size={avatarSize}
|
||||
user={user}
|
||||
userProfilePhoto={userProfilePhoto}
|
||||
isSavedMessages={isSavedMessages}
|
||||
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
|
||||
withVideo={withVideoAvatar}
|
||||
@ -191,11 +196,13 @@ export default memo(withGlobal<OwnProps>(
|
||||
const userStatus = selectUserStatus(global, userId);
|
||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||
const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
|
||||
const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined;
|
||||
|
||||
return {
|
||||
lastSyncTime,
|
||||
user,
|
||||
userStatus,
|
||||
userProfilePhoto,
|
||||
isSavedMessages,
|
||||
areMessagesLoaded,
|
||||
animationLevel: global.settings.byKey.animationLevel,
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-white);
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
user-select: none;
|
||||
align-items: center;
|
||||
height: 1.5rem;
|
||||
@ -112,7 +112,7 @@
|
||||
opacity: 0.25;
|
||||
transition: opacity 0.15s;
|
||||
outline: none !important;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
z-index: 1;
|
||||
|
||||
&:global(:hover),
|
||||
|
||||
@ -46,6 +46,6 @@
|
||||
|
||||
color: var(--color-white);
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import React, {
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import type {
|
||||
ApiUser, ApiChat, ApiUserStatus, ApiTopic,
|
||||
ApiUser, ApiChat, ApiUserStatus, ApiTopic, ApiPhoto,
|
||||
} from '../../api/types';
|
||||
import type { GlobalState } from '../../global/types';
|
||||
import type { AnimationLevel } from '../../types';
|
||||
@ -14,10 +14,18 @@ import { MediaViewerOrigin } from '../../types';
|
||||
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||
import {
|
||||
selectChat,
|
||||
selectChatFullInfo,
|
||||
selectCurrentMessageList,
|
||||
selectTabState,
|
||||
selectChat, selectCurrentMessageList, selectThreadMessagesCount, selectUser, selectUserStatus,
|
||||
selectThreadMessagesCount,
|
||||
selectUser,
|
||||
selectUserFullInfo,
|
||||
selectUserStatus,
|
||||
} from '../../global/selectors';
|
||||
import { getUserStatus, isChatChannel, isUserOnline } from '../../global/helpers';
|
||||
import {
|
||||
getUserStatus, isChatChannel, isUserId, isUserOnline,
|
||||
} from '../../global/helpers';
|
||||
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import renderText from './helpers/renderText';
|
||||
@ -52,6 +60,10 @@ type StateProps =
|
||||
avatarOwnerId?: string;
|
||||
topic?: ApiTopic;
|
||||
messagesCount?: number;
|
||||
userPersonalPhoto?: ApiPhoto;
|
||||
userProfilePhoto?: ApiPhoto;
|
||||
userFallbackPhoto?: ApiPhoto;
|
||||
chatProfilePhoto?: ApiPhoto;
|
||||
}
|
||||
& Pick<GlobalState, 'connectionState'>;
|
||||
|
||||
@ -71,6 +83,10 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
avatarOwnerId,
|
||||
topic,
|
||||
messagesCount,
|
||||
userPersonalPhoto,
|
||||
userProfilePhoto,
|
||||
userFallbackPhoto,
|
||||
chatProfilePhoto,
|
||||
}) => {
|
||||
const {
|
||||
loadFullUser,
|
||||
@ -87,7 +103,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
const prevAvatarOwnerId = usePrevious(avatarOwnerId);
|
||||
const [hasSlideAnimation, setHasSlideAnimation] = useState(true);
|
||||
const slideAnimation = hasSlideAnimation
|
||||
? animationLevel >= 1 ? (lang.isRtl ? 'slide-optimized-rtl' : 'slide-optimized') : 'none'
|
||||
? animationLevel >= 1 ? (lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized') : 'none'
|
||||
: 'none';
|
||||
|
||||
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
|
||||
@ -216,12 +232,14 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
const photo = !isSavedMessages && photos.length > 0
|
||||
? photos[currentPhotoIndex]
|
||||
: undefined;
|
||||
const profilePhoto = photo || userPersonalPhoto || userProfilePhoto || chatProfilePhoto || userFallbackPhoto;
|
||||
|
||||
return (
|
||||
<ProfilePhoto
|
||||
key={currentPhotoIndex}
|
||||
user={user}
|
||||
chat={chat}
|
||||
photo={photo}
|
||||
photo={profilePhoto}
|
||||
isSavedMessages={isSavedMessages}
|
||||
canPlayVideo={Boolean(isActive && canPlayVideo)}
|
||||
onClick={handleProfilePhotoClick}
|
||||
@ -259,18 +277,18 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
<div className={styles.photoWrapper}>
|
||||
{renderPhotoTabs()}
|
||||
{!forceShowSelf && user?.fullInfo?.personalPhoto && (
|
||||
{!forceShowSelf && userPersonalPhoto && (
|
||||
<div className={buildClassName(
|
||||
styles.fallbackPhoto,
|
||||
isFirst && styles.fallbackPhotoVisible,
|
||||
)}
|
||||
>
|
||||
<div className={styles.fallbackPhotoContents}>
|
||||
{lang(user.fullInfo.personalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')}
|
||||
{lang(userPersonalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{forceShowSelf && user?.fullInfo?.fallbackPhoto && (
|
||||
{forceShowSelf && userFallbackPhoto && (
|
||||
<div className={buildClassName(
|
||||
styles.fallbackPhoto,
|
||||
(isFirst || isLast) && styles.fallbackPhotoVisible,
|
||||
@ -279,12 +297,12 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
<div className={styles.fallbackPhotoContents} onClick={handleSelectFallbackPhoto}>
|
||||
{!isLast && (
|
||||
<Avatar
|
||||
photo={user.fullInfo.fallbackPhoto}
|
||||
photo={userFallbackPhoto}
|
||||
className={styles.fallbackPhotoAvatar}
|
||||
size="mini"
|
||||
/>
|
||||
)}
|
||||
{lang(user.fullInfo.fallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')}
|
||||
{lang(userFallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -333,6 +351,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global, { userId, forceShowSelf }): StateProps => {
|
||||
const { connectionState } = global;
|
||||
const user = selectUser(global, userId);
|
||||
const isPrivate = isUserId(userId);
|
||||
const userStatus = selectUserStatus(global, userId);
|
||||
const chat = selectChat(global, userId);
|
||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||
@ -341,12 +360,18 @@ export default memo(withGlobal<OwnProps>(
|
||||
const isForum = chat?.isForum;
|
||||
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
||||
const topic = isForum && currentTopicId ? chat?.topics?.[currentTopicId] : undefined;
|
||||
const userFullInfo = isPrivate ? selectUserFullInfo(global, userId) : undefined;
|
||||
const chatFullInfo = !isPrivate ? selectChatFullInfo(global, userId) : undefined;
|
||||
|
||||
return {
|
||||
connectionState,
|
||||
user,
|
||||
userStatus,
|
||||
chat,
|
||||
userPersonalPhoto: userFullInfo?.personalPhoto,
|
||||
userProfilePhoto: userFullInfo?.profilePhoto,
|
||||
userFallbackPhoto: userFullInfo?.fallbackPhoto,
|
||||
chatProfilePhoto: chatFullInfo?.profilePhoto,
|
||||
isSavedMessages,
|
||||
animationLevel,
|
||||
mediaId,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.ProfilePhoto {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
position: relative;
|
||||
|
||||
.avatar-media {
|
||||
@ -24,7 +24,7 @@
|
||||
justify-content: center;
|
||||
color: var(--color-white);
|
||||
background: linear-gradient(var(--color-white) -125%, var(--color-user));
|
||||
cursor: default;
|
||||
cursor: var(--custom-cursor, default);
|
||||
}
|
||||
|
||||
&.no-photo {
|
||||
|
||||
@ -56,31 +56,27 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
const isDeleted = user && isDeletedUser(user);
|
||||
const isRepliesChat = chat && isChatWithRepliesBot(chat.id);
|
||||
const userOrChat = user || chat;
|
||||
const currentPhoto = photo
|
||||
|| user?.fullInfo?.personalPhoto
|
||||
|| userOrChat?.fullInfo?.profilePhoto
|
||||
|| user?.fullInfo?.fallbackPhoto;
|
||||
const canHaveMedia = userOrChat && !isSavedMessages && !isDeleted && !isRepliesChat;
|
||||
const { isVideo } = currentPhoto || {};
|
||||
const { isVideo } = photo || {};
|
||||
|
||||
const avatarHash = canHaveMedia && getChatAvatarHash(userOrChat, 'normal', 'photo');
|
||||
const avatarBlobUrl = useMedia(avatarHash, undefined, undefined, lastSyncTime);
|
||||
|
||||
const photoHash = canHaveMedia && currentPhoto && !isVideo && `photo${currentPhoto.id}?size=c`;
|
||||
const photoHash = canHaveMedia && photo && !isVideo && `photo${photo.id}?size=c`;
|
||||
const photoBlobUrl = useMedia(photoHash, undefined, undefined, lastSyncTime);
|
||||
|
||||
const videoHash = canHaveMedia && currentPhoto && isVideo && getVideoAvatarMediaHash(currentPhoto);
|
||||
const videoHash = canHaveMedia && photo && isVideo && getVideoAvatarMediaHash(photo);
|
||||
const videoBlobUrl = useMedia(videoHash, undefined, undefined, lastSyncTime);
|
||||
|
||||
const fullMediaData = videoBlobUrl || photoBlobUrl;
|
||||
const [isVideoReady, markVideoReady] = useFlag();
|
||||
const isFullMediaReady = Boolean(fullMediaData && (!isVideo || isVideoReady));
|
||||
const transitionClassNames = useMediaTransition(isFullMediaReady);
|
||||
const isBlurredThumb = canHaveMedia && !isFullMediaReady && !avatarBlobUrl && currentPhoto?.thumbnail?.dataUri;
|
||||
const isBlurredThumb = canHaveMedia && !isFullMediaReady && !avatarBlobUrl && photo?.thumbnail?.dataUri;
|
||||
const blurredThumbCanvasRef = useCanvasBlur(
|
||||
currentPhoto?.thumbnail?.dataUri, !isBlurredThumb, isMobile && !IS_CANVAS_FILTER_SUPPORTED,
|
||||
photo?.thumbnail?.dataUri, !isBlurredThumb, isMobile && !IS_CANVAS_FILTER_SUPPORTED,
|
||||
);
|
||||
const hasMedia = currentPhoto || avatarBlobUrl || isBlurredThumb;
|
||||
const hasMedia = photo || avatarBlobUrl || isBlurredThumb;
|
||||
|
||||
useEffect(() => {
|
||||
if (videoRef.current && !canPlayVideo) {
|
||||
@ -91,11 +87,11 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
let content: TeactNode | undefined;
|
||||
|
||||
if (isSavedMessages) {
|
||||
content = <i className="icon-avatar-saved-messages" />;
|
||||
content = <i className="icon icon-avatar-saved-messages" />;
|
||||
} else if (isDeleted) {
|
||||
content = <i className="icon-avatar-deleted-account" />;
|
||||
content = <i className="icon icon-avatar-deleted-account" />;
|
||||
} else if (isRepliesChat) {
|
||||
content = <i className="icon-reply-filled" />;
|
||||
content = <i className="icon icon-reply-filled" />;
|
||||
} else if (hasMedia) {
|
||||
content = (
|
||||
<>
|
||||
@ -104,7 +100,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
) : (
|
||||
<img src={avatarBlobUrl} className="thumb" alt="" />
|
||||
)}
|
||||
{currentPhoto && (
|
||||
{photo && (
|
||||
isVideo ? (
|
||||
<OptimizedVideo
|
||||
canPlay={canPlayVideo}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.root {
|
||||
--custom-emoji-size: 2.5rem;
|
||||
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
display: inline-block;
|
||||
width: var(--custom-emoji-size);
|
||||
height: var(--custom-emoji-size);
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
|
||||
.dot {
|
||||
flex: none;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #DFE1E5;
|
||||
|
||||
@ -45,7 +45,7 @@ const SliderDots: FC<OwnProps> = ({
|
||||
round
|
||||
onClick={handleGoBack}
|
||||
>
|
||||
<i className="icon-previous" />
|
||||
<i className="icon icon-previous" />
|
||||
</Button>
|
||||
)}
|
||||
<div className={styles.root} style={`--start-from: ${startFrom}; --length: ${length}; --count: ${count};`}>
|
||||
@ -80,7 +80,7 @@ const SliderDots: FC<OwnProps> = ({
|
||||
disabled={active === length - 1}
|
||||
onClick={handleGoForward}
|
||||
>
|
||||
<i className="icon-next" />
|
||||
<i className="icon icon-next" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
}
|
||||
|
||||
&.interactive {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-interactive-element-hover);
|
||||
@ -124,7 +124,7 @@
|
||||
height: 1.25rem;
|
||||
padding: 0.125rem;
|
||||
|
||||
i {
|
||||
.icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@ -134,7 +134,7 @@
|
||||
|
||||
.sticker-context-menu {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
cursor: var(--custom-cursor, default);
|
||||
z-index: var(--z-header-menu);
|
||||
|
||||
.bubble {
|
||||
|
||||
@ -310,12 +310,12 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
<div
|
||||
className="sticker-locked"
|
||||
>
|
||||
<i className="icon-lock-badge" />
|
||||
<i className="icon icon-lock-badge" />
|
||||
</div>
|
||||
)}
|
||||
{!noShowPremium && isPremium && !isLocked && (
|
||||
<div className="sticker-premium">
|
||||
<i className="icon-premium" />
|
||||
<i className="icon icon-premium" />
|
||||
</div>
|
||||
)}
|
||||
{shouldShowCloseButton && (
|
||||
@ -325,7 +325,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
round
|
||||
onClick={handleRemoveClick}
|
||||
>
|
||||
<i className="icon-close" />
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
)}
|
||||
{Boolean(contextMenuItems.length) && (
|
||||
|
||||
@ -244,11 +244,11 @@ const StickerSet: FC<OwnProps> = ({
|
||||
{!shouldHideHeader && (
|
||||
<div className="symbol-set-header">
|
||||
<p className="symbol-set-name">
|
||||
{isLocked && <i className="symbol-set-locked-icon icon-lock-badge" />}
|
||||
{isLocked && <i className="symbol-set-locked-icon icon icon-lock-badge" />}
|
||||
{stickerSet.title}
|
||||
</p>
|
||||
{isRecent && (
|
||||
<i className="symbol-set-remove icon-close" onClick={openConfirmModal} />
|
||||
<i className="symbol-set-remove icon icon-close" onClick={openConfirmModal} />
|
||||
)}
|
||||
{!isRecent && isEmoji && !isInstalled && !isPopular && (
|
||||
<Button
|
||||
@ -291,7 +291,7 @@ const StickerSet: FC<OwnProps> = ({
|
||||
onClick={handleDefaultStatusIconClick}
|
||||
key="default-status-icon"
|
||||
>
|
||||
<i className="icon-premium" />
|
||||
<i className="icon icon-premium" />
|
||||
</Button>
|
||||
)}
|
||||
{shouldRender && stickerSet.reactions?.map((reaction) => {
|
||||
|
||||
@ -161,7 +161,7 @@ const StickerSetModal: FC<OwnProps & StateProps> = ({
|
||||
onClick={onTrigger}
|
||||
ariaLabel="More actions"
|
||||
>
|
||||
<i className="icon-more" />
|
||||
<i className="icon icon-more" />
|
||||
</Button>
|
||||
);
|
||||
}, [isMobile]);
|
||||
@ -170,7 +170,7 @@ const StickerSetModal: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<div className="modal-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button round color="translucent" size="smaller" ariaLabel={lang('Close')} onClick={onClose}>
|
||||
<i className="icon-close" />
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
<div className="modal-title">
|
||||
{renderingStickerSet ? renderText(renderingStickerSet.title, ['emoji', 'links']) : lang('AccDescrStickerSet')}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
background-color: var(--background-color);
|
||||
border-radius: var(--border-radius-messages);
|
||||
color: var(--topic-button-accent-color);
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
|
||||
@ -39,8 +39,8 @@ const TopicChip: FC<OwnProps> = ({
|
||||
? <TopicIcon topic={topic} size={TOPIC_ICON_SIZE} />
|
||||
: <img src={blankSrc} alt="" />}
|
||||
{topic?.title ? renderText(topic.title) : lang('Loading')}
|
||||
{topic?.isClosed && <i className="icon-lock" />}
|
||||
<i className="icon-next" />
|
||||
{topic?.isClosed && <i className="icon icon-lock" />}
|
||||
<i className="icon icon-next" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -31,7 +31,10 @@ const TopicDefaultIcon: FC<OwnProps> = ({
|
||||
|
||||
if (topicId === GENERAL_TOPIC_ID) {
|
||||
return (
|
||||
<i className={buildClassName(styles.root, className, 'icon-hashtag', 'general-forum-icon')} onClick={onClick} />
|
||||
<i
|
||||
className={buildClassName(styles.root, className, 'icon', 'icon-hashtag', 'general-forum-icon')}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
51
src/components/common/UserAvatar.tsx
Normal file
51
src/components/common/UserAvatar.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../global';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { ApiPhoto, ApiUser } from '../../api/types';
|
||||
import type { AnimationLevel } from '../../types';
|
||||
|
||||
import { selectUserPhotoFromFullInfo } from '../../global/selectors';
|
||||
|
||||
import Avatar from './Avatar';
|
||||
|
||||
type OwnProps = {
|
||||
user?: ApiUser;
|
||||
withVideo?: boolean;
|
||||
className?: string;
|
||||
size?: 'micro' | 'tiny' | 'small' | 'medium' | 'large';
|
||||
};
|
||||
|
||||
interface StateProps {
|
||||
profilePhoto?: ApiPhoto;
|
||||
animationLevel?: AnimationLevel;
|
||||
}
|
||||
|
||||
const UserAvatar: FC<OwnProps & StateProps> = ({
|
||||
user,
|
||||
profilePhoto,
|
||||
className,
|
||||
animationLevel,
|
||||
withVideo,
|
||||
size,
|
||||
}) => {
|
||||
return (
|
||||
<Avatar
|
||||
user={user}
|
||||
className={className}
|
||||
userProfilePhoto={profilePhoto}
|
||||
animationLevel={animationLevel}
|
||||
withVideo={withVideo}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { user }): StateProps => {
|
||||
return {
|
||||
profilePhoto: user ? selectUserPhotoFromFullInfo(global, user.id) : undefined,
|
||||
animationLevel: global.settings.byKey.animationLevel,
|
||||
};
|
||||
},
|
||||
)(UserAvatar));
|
||||
@ -33,7 +33,7 @@
|
||||
top: 0;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
cursor: default !important;
|
||||
cursor: var(--custom-cursor, default) !important;
|
||||
padding-bottom: unset !important;
|
||||
border-radius: var(--border-radius-messages-small) !important;
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
border-radius: 0.125rem;
|
||||
margin: 0.125rem;
|
||||
transition: background-color 0.15s ease-in-out;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&:hover, &.wrapOn {
|
||||
background-color: var(--color-background-compact-menu-hover);
|
||||
|
||||
@ -63,12 +63,12 @@ const CodeOverlay: FC<OwnProps> = ({
|
||||
<div className={contentClass}>
|
||||
{withWordWrapButton && (
|
||||
<div className={wrapClass} onClick={handleWordWrapClick} title="Word Wrap">
|
||||
<i className="icon-word-wrap" />
|
||||
<i className="icon icon-word-wrap" />
|
||||
</div>
|
||||
)}
|
||||
{!noCopy && (
|
||||
<div className={styles.copy} onClick={handleCopy} title={lang('Copy')}>
|
||||
<i className="icon-copy" />
|
||||
<i className="icon icon-copy" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
.Spoiler {
|
||||
&--concealed {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
background-image: url('../../../assets/spoiler-dots-black.png');
|
||||
background-size: auto min(100%, 1.125rem);
|
||||
border-radius: 0.5rem;
|
||||
|
||||
@ -87,7 +87,7 @@ const ArchivedChats: FC<OwnProps> = ({
|
||||
)}
|
||||
onTransitionEnd={handleDropdownMenuTransitionEnd}
|
||||
>
|
||||
<i className="icon-arrow-left" />
|
||||
<i className="icon icon-arrow-left" />
|
||||
</Button>
|
||||
{shouldRenderTitle && <h3 className={titleClassNames}>{lang('ArchivedChats')}</h3>}
|
||||
{archiveSettings.isHidden && (
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
border-radius: var(--border-radius-default);
|
||||
|
||||
&.interactive {
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
}
|
||||
|
||||
> .Spinner {
|
||||
@ -57,7 +57,7 @@
|
||||
// https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
|
||||
min-width: 0;
|
||||
|
||||
> .Transition__slide {
|
||||
> .Transition_slide {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
@ -27,7 +27,7 @@ const ConnectionStatusOverlay: FC<OwnProps> = ({
|
||||
<div id="ConnectionStatusOverlay" dir={lang.isRtl ? 'rtl' : undefined} onClick={onClick}>
|
||||
<Spinner color="black" />
|
||||
<div className="state-text">
|
||||
<Transition activeKey={connectionStatus} name="slide-fade">
|
||||
<Transition activeKey={connectionStatus} name="slideFade">
|
||||
{connectionStatusText}
|
||||
</Transition>
|
||||
</div>
|
||||
@ -36,7 +36,7 @@ const ConnectionStatusOverlay: FC<OwnProps> = ({
|
||||
size="tiny"
|
||||
color="translucent-black"
|
||||
>
|
||||
<span className="icon-close" />
|
||||
<span className="icon icon-close" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -67,8 +67,8 @@ const NewChatButton: FC<OwnProps> = ({
|
||||
ariaLabel={lang(isMenuOpen ? 'Close' : 'NewMessageTitle')}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<i className="icon-new-chat-filled" />
|
||||
<i className="icon-close" />
|
||||
<i className="icon icon-new-chat-filled" />
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
<Menu
|
||||
isOpen={isMenuOpen}
|
||||
|
||||
@ -103,7 +103,7 @@ const Archive: FC<OwnProps> = ({
|
||||
<div className="info-row">
|
||||
<div className={buildClassName('title', styles.title)}>
|
||||
<h3 dir="auto" className={buildClassName(styles.name, 'fullName')}>
|
||||
<i className={buildClassName(styles.icon, 'icon-archive-filled')} />
|
||||
<i className={buildClassName(styles.icon, 'icon', 'icon-archive-filled')} />
|
||||
{lang('ArchivedChats')}
|
||||
</h3>
|
||||
</div>
|
||||
@ -122,7 +122,7 @@ const Archive: FC<OwnProps> = ({
|
||||
<>
|
||||
<div className={buildClassName('status', styles.avatarWrapper)}>
|
||||
<div className={buildClassName('Avatar', styles.avatar)}>
|
||||
<i className="icon-archive-filled" />
|
||||
<i className="icon icon-archive-filled" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={buildClassName(styles.info, 'info')}>
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
width: 1.5rem;
|
||||
padding: 0;
|
||||
|
||||
i {
|
||||
.icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
@ -83,7 +83,7 @@
|
||||
width: 1.5rem;
|
||||
padding: 0.25rem;
|
||||
|
||||
i {
|
||||
.icon {
|
||||
font-size: 1rem;
|
||||
vertical-align: super;
|
||||
}
|
||||
@ -92,7 +92,7 @@
|
||||
width: 1.375rem;
|
||||
padding: 0.25rem;
|
||||
|
||||
i {
|
||||
.icon {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,13 +67,13 @@ const Badge: FC<OwnProps> = ({
|
||||
function renderContent() {
|
||||
const unreadReactionsElement = unreadReactionsCount && (
|
||||
<div className={buildClassName('Badge reaction', shouldBeMuted && 'muted')}>
|
||||
<i className="icon-heart" />
|
||||
<i className="icon icon-heart" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const unreadMentionsElement = unreadMentionsCount && (
|
||||
<div className="Badge mention">
|
||||
<i className="icon-mention" />
|
||||
<i className="icon icon-mention" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -89,7 +89,7 @@ const Badge: FC<OwnProps> = ({
|
||||
|
||||
const pinnedElement = isPinned && !unreadCountElement && !unreadMentionsElement && !unreadReactionsElement && (
|
||||
<div className={className}>
|
||||
<i className="icon-pinned-chat" />
|
||||
<i className="icon icon-pinned-chat" />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@ -5,13 +5,14 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import type {
|
||||
ApiChat,
|
||||
ApiUser,
|
||||
ApiFormattedText,
|
||||
ApiMessage,
|
||||
ApiMessageOutgoingStatus,
|
||||
ApiFormattedText,
|
||||
ApiUserStatus,
|
||||
ApiPhoto,
|
||||
ApiTopic,
|
||||
ApiTypingStatus,
|
||||
ApiUser,
|
||||
ApiUserStatus,
|
||||
} from '../../../api/types';
|
||||
import type { AnimationLevel } from '../../../types';
|
||||
import type { ChatAnimationTypes } from './hooks';
|
||||
@ -19,23 +20,25 @@ import type { ChatAnimationTypes } from './hooks';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment';
|
||||
import {
|
||||
isUserId,
|
||||
getPrivateChatUserId,
|
||||
getMessageAction,
|
||||
getPrivateChatUserId,
|
||||
isUserId,
|
||||
selectIsChatMuted,
|
||||
} from '../../../global/helpers';
|
||||
import {
|
||||
selectChat,
|
||||
selectUser,
|
||||
selectChatMessage,
|
||||
selectOutgoingStatus,
|
||||
selectDraft,
|
||||
selectCurrentMessageList,
|
||||
selectNotifySettings,
|
||||
selectDraft,
|
||||
selectNotifyExceptions,
|
||||
selectUserStatus,
|
||||
selectNotifySettings,
|
||||
selectOutgoingStatus,
|
||||
selectTabState,
|
||||
selectThreadParam,
|
||||
selectTopicFromMessage,
|
||||
selectThreadParam, selectTabState,
|
||||
selectUser,
|
||||
selectUserPhotoFromFullInfo,
|
||||
selectUserStatus,
|
||||
} from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { createLocationHash } from '../../../util/routing';
|
||||
@ -75,6 +78,7 @@ type StateProps = {
|
||||
isMuted?: boolean;
|
||||
user?: ApiUser;
|
||||
userStatus?: ApiUserStatus;
|
||||
userProfilePhoto?: ApiPhoto;
|
||||
actionTargetUserIds?: string[];
|
||||
actionTargetMessage?: ApiMessage;
|
||||
actionTargetChatId?: string;
|
||||
@ -102,6 +106,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
isMuted,
|
||||
user,
|
||||
userStatus,
|
||||
userProfilePhoto,
|
||||
actionTargetUserIds,
|
||||
lastMessageSender,
|
||||
lastMessageOutgoingStatus,
|
||||
@ -235,6 +240,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
<Avatar
|
||||
chat={chat}
|
||||
user={user}
|
||||
userProfilePhoto={userProfilePhoto}
|
||||
userStatus={userStatus}
|
||||
isSavedMessages={user?.isSelf}
|
||||
lastSyncTime={lastSyncTime}
|
||||
@ -255,7 +261,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
isSavedMessages={chatId === user?.id && user?.isSelf}
|
||||
observeIntersection={observeIntersection}
|
||||
/>
|
||||
{isMuted && <i className="icon-muted" />}
|
||||
{isMuted && <i className="icon icon-muted" />}
|
||||
<div className="separator" />
|
||||
{chat.lastMessage && (
|
||||
<LastMessageMeta
|
||||
@ -324,6 +330,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
||||
const userStatus = privateChatUserId ? selectUserStatus(global, privateChatUserId) : undefined;
|
||||
const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined;
|
||||
const lastMessageTopic = chat.lastMessage && selectTopicFromMessage(global, chat.lastMessage);
|
||||
|
||||
const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus');
|
||||
@ -347,6 +354,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
}),
|
||||
user,
|
||||
userStatus,
|
||||
userProfilePhoto,
|
||||
lastMessageTopic,
|
||||
typingStatus,
|
||||
};
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
@keyframes bar-animation-transform-1 {
|
||||
0% { transform: scaleY(0.33); }
|
||||
12.5% { transform: scaleY(1.66); }
|
||||
@ -24,7 +23,7 @@
|
||||
}
|
||||
|
||||
|
||||
.ChatCallStatus {
|
||||
.root {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
bottom: 0;
|
||||
@ -35,47 +34,51 @@
|
||||
border: 2px solid var(--color-background);
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.indicator {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
& > div {
|
||||
width: 2px;
|
||||
height: 6px;
|
||||
background: var(--color-background);
|
||||
border-radius: 1px;
|
||||
margin: 1px;
|
||||
will-change: transform;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
& > div:nth-child(odd) {
|
||||
transform: scaleY(0.8);
|
||||
}
|
||||
& > div:nth-child(even) {
|
||||
transform: scaleY(1.33);
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: var(--color-white);
|
||||
border-color: var(--color-chat-active);
|
||||
.indicator div{
|
||||
background-color: var(--color-chat-active);
|
||||
}
|
||||
}
|
||||
|
||||
&.active .indicator {
|
||||
div:nth-child(odd) {
|
||||
animation: bar-animation-transform-2 3.2s normal infinite;
|
||||
}
|
||||
|
||||
div:nth-child(even) {
|
||||
animation: bar-animation-transform-1 3.2s normal infinite;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.indicator {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.indicatorInner {
|
||||
width: 2px;
|
||||
height: 6px;
|
||||
background: var(--color-background);
|
||||
border-radius: 1px;
|
||||
margin: 1px;
|
||||
will-change: transform;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.indicatorInner:nth-child(odd) {
|
||||
transform: scaleY(0.8);
|
||||
}
|
||||
|
||||
.indicatorInner:nth-child(even) {
|
||||
transform: scaleY(1.33);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--color-white);
|
||||
border-color: var(--color-chat-active);
|
||||
|
||||
.indicatorInner {
|
||||
background-color: var(--color-chat-active);
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
.indicatorInner:nth-child(odd) {
|
||||
animation: bar-animation-transform-2 3.2s normal infinite;
|
||||
}
|
||||
|
||||
.indicatorInner:nth-child(even) {
|
||||
animation: bar-animation-transform-1 3.2s normal infinite;
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../lib/teact/teact';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import './ChatCallStatus.scss';
|
||||
import styles from './ChatCallStatus.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
isSelected?: boolean;
|
||||
@ -17,15 +17,15 @@ const ChatCallStatus: FC<OwnProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<div className={buildClassName(
|
||||
'ChatCallStatus',
|
||||
isActive && 'active',
|
||||
isSelected && !isMobile && 'selected',
|
||||
styles.root,
|
||||
isActive && styles.active,
|
||||
isSelected && !isMobile && styles.selected,
|
||||
)}
|
||||
>
|
||||
<div className="indicator">
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
<div className={styles.indicator}>
|
||||
<div className={styles.indicatorInner} />
|
||||
<div className={styles.indicatorInner} />
|
||||
<div className={styles.indicatorInner} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -242,7 +242,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
) : undefined}
|
||||
<Transition
|
||||
ref={transitionRef}
|
||||
name={shouldSkipHistoryAnimations ? 'none' : lang.isRtl ? 'slide-optimized-rtl' : 'slide-optimized'}
|
||||
name={shouldSkipHistoryAnimations ? 'none' : lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'}
|
||||
activeKey={activeChatFolder}
|
||||
renderCount={shouldRenderFolders ? folderTabs.length : undefined}
|
||||
>
|
||||
|
||||
@ -92,7 +92,7 @@ const ContactList: FC<OwnProps & StateProps> = ({
|
||||
onClick={openNewContactDialog}
|
||||
ariaLabel={lang('CreateNewContact')}
|
||||
>
|
||||
<i className="icon-add-user-filled" />
|
||||
<i className="icon icon-add-user-filled" />
|
||||
</FloatingActionButton>
|
||||
</InfiniteScroll>
|
||||
);
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
i {
|
||||
:global(.icon) {
|
||||
margin-inline-end: 0.625rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ const EmptyFolder: FC<OwnProps & StateProps> = ({
|
||||
size="smaller"
|
||||
isRtl={lang.isRtl}
|
||||
>
|
||||
<i className="icon-settings" />
|
||||
<i className="icon icon-settings" />
|
||||
<div className={styles.buttonText}>
|
||||
{lang('ChatList.EmptyChatListEditFilter')}
|
||||
</div>
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
i {
|
||||
:global(.icon) {
|
||||
margin-inline-end: 0.625rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
margin-left: 0.4375rem;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
:global(.info) {
|
||||
display: flex;
|
||||
|
||||
@ -224,7 +224,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
||||
onClick={handleClose}
|
||||
ariaLabel={lang('Close')}
|
||||
>
|
||||
<i className="icon-close" />
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
|
||||
{chat && (
|
||||
|
||||
@ -167,7 +167,7 @@ const LeftMain: FC<OwnProps> = ({
|
||||
isClosingSearch={isClosingSearch}
|
||||
/>
|
||||
<Transition
|
||||
name={shouldSkipTransition ? 'none' : 'zoom-fade'}
|
||||
name={shouldSkipTransition ? 'none' : 'zoomFade'}
|
||||
renderCount={TRANSITION_RENDER_COUNT}
|
||||
activeKey={content}
|
||||
shouldCleanup
|
||||
|
||||
@ -5,13 +5,12 @@ import React, {
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { AnimationLevel, ISettings } from '../../../types';
|
||||
import { LeftColumnContent, SettingsScreens } from '../../../types';
|
||||
import type { ApiChat } from '../../../api/types';
|
||||
import type { TabState, GlobalState } from '../../../global/types';
|
||||
|
||||
import { LeftColumnContent, SettingsScreens } from '../../../types';
|
||||
|
||||
import {
|
||||
ANIMATION_LEVEL_MAX,
|
||||
APP_NAME, APP_VERSION,
|
||||
APP_NAME, APP_VERSION, ARCHIVED_FOLDER_ID,
|
||||
BETA_CHANGELOG_URL,
|
||||
DEBUG,
|
||||
FEEDBACK_URL,
|
||||
@ -22,13 +21,11 @@ import {
|
||||
import { IS_PWA } from '../../../util/windowEnvironment';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatDateToString } from '../../../util/dateFormat';
|
||||
import switchTheme from '../../../util/switchTheme';
|
||||
import { setPermanentWebVersion } from '../../../util/permanentWebVersion';
|
||||
import { clearWebsync } from '../../../util/websync';
|
||||
import {
|
||||
selectCurrentMessageList, selectIsCurrentUserPremium, selectTabState, selectTheme,
|
||||
} from '../../../global/selectors';
|
||||
import { isChatArchived } from '../../../global/helpers';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useConnectionStatus from '../../../hooks/useConnectionStatus';
|
||||
import { useHotkeys } from '../../../hooks/useHotkeys';
|
||||
@ -48,6 +45,7 @@ import ConnectionStatusOverlay from '../ConnectionStatusOverlay';
|
||||
import StatusButton from './StatusButton';
|
||||
|
||||
import './LeftMainHeader.scss';
|
||||
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
|
||||
|
||||
type OwnProps = {
|
||||
shouldHideSearch?: boolean;
|
||||
@ -71,7 +69,6 @@ type StateProps =
|
||||
searchDate?: number;
|
||||
theme: ISettings['theme'];
|
||||
animationLevel: AnimationLevel;
|
||||
chatsById?: Record<string, ApiChat>;
|
||||
isMessageListOpen: boolean;
|
||||
isCurrentUserPremium?: boolean;
|
||||
isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized'];
|
||||
@ -79,7 +76,8 @@ type StateProps =
|
||||
hasPasscode?: boolean;
|
||||
isAuthRememberMe?: boolean;
|
||||
}
|
||||
& Pick<GlobalState, 'connectionState' | 'isSyncing' | 'archiveSettings'> & Pick<TabState, 'canInstall'>;
|
||||
& Pick<GlobalState, 'connectionState' | 'isSyncing' | 'archiveSettings'>
|
||||
& Pick<TabState, 'canInstall'>;
|
||||
|
||||
const ANIMATION_LEVEL_OPTIONS = [0, 1, 2];
|
||||
const WEBK_VERSION_URL = 'https://web.telegram.org/k/';
|
||||
@ -103,7 +101,6 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
searchDate,
|
||||
theme,
|
||||
animationLevel,
|
||||
chatsById,
|
||||
connectionState,
|
||||
isSyncing,
|
||||
isMessageListOpen,
|
||||
@ -136,19 +133,8 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
? formatDateToString(new Date(searchDate * 1000))
|
||||
: undefined;
|
||||
}, [searchDate]);
|
||||
const archivedUnreadChatsCount = useMemo(() => {
|
||||
if (!hasMenu || !chatsById) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Object.values(chatsById).reduce((total, chat) => {
|
||||
if (!isChatArchived(chat)) {
|
||||
return total;
|
||||
}
|
||||
|
||||
return chat.unreadCount ? total + 1 : total;
|
||||
}, 0);
|
||||
}, [hasMenu, chatsById]);
|
||||
const archivedUnreadChatsCount = useFolderManagerForUnreadCounters()[ARCHIVED_FOLDER_ID]?.chatsCount || 0;
|
||||
|
||||
const { connectionStatus, connectionStatusText, connectionStatusPosition } = useConnectionStatus(
|
||||
lang, connectionState, isSyncing, isMessageListOpen, isConnectionStatusMinimized, !areChatsLoaded,
|
||||
@ -215,8 +201,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
|
||||
setSettingOption({ theme: newTheme });
|
||||
setSettingOption({ shouldUseSystemTheme: false });
|
||||
switchTheme(newTheme, animationLevel === ANIMATION_LEVEL_MAX);
|
||||
}, [animationLevel, setSettingOption, theme]);
|
||||
}, [setSettingOption, theme]);
|
||||
|
||||
const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
@ -347,7 +332,8 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
{withOtherVersions && (
|
||||
<MenuItem
|
||||
icon="char-K"
|
||||
icon="K"
|
||||
isCharIcon
|
||||
href={WEBK_VERSION_URL}
|
||||
onClick={handleSwitchToWebK}
|
||||
>
|
||||
@ -437,7 +423,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
onClick={handleLockScreen}
|
||||
className={buildClassName(!isCurrentUserPremium && 'extra-spacing')}
|
||||
>
|
||||
<i className="icon-lock" />
|
||||
<i className="icon icon-lock" />
|
||||
</Button>
|
||||
)}
|
||||
<ShowTransition
|
||||
@ -465,14 +451,12 @@ export default memo(withGlobal<OwnProps>(
|
||||
const {
|
||||
currentUserId, connectionState, isSyncing, archiveSettings,
|
||||
} = global;
|
||||
const { byId: chatsById } = global.chats;
|
||||
const { isConnectionStatusMinimized, animationLevel } = global.settings.byKey;
|
||||
|
||||
return {
|
||||
searchQuery,
|
||||
isLoading: fetchingStatus ? Boolean(fetchingStatus.chats || fetchingStatus.messages) : false,
|
||||
currentUserId,
|
||||
chatsById,
|
||||
globalSearchChatId: chatId,
|
||||
searchDate: date,
|
||||
theme: selectTheme(global),
|
||||
|
||||
@ -156,10 +156,11 @@ const Topic: FC<OwnProps & StateProps> = ({
|
||||
<TopicIcon topic={topic} className={styles.topicIcon} />
|
||||
<h3 dir="auto" className="fullName">{renderText(topic.title)}</h3>
|
||||
</div>
|
||||
{topic.isMuted && <i className="icon-muted" />}
|
||||
{topic.isMuted && <i className="icon icon-muted" />}
|
||||
<div className="separator" />
|
||||
{isClosed && (
|
||||
<i className={buildClassName(
|
||||
'icon',
|
||||
'icon-lock-badge',
|
||||
styles.closedIcon,
|
||||
)}
|
||||
|
||||
@ -226,7 +226,7 @@ function renderSummary(
|
||||
buildClassName('media-preview--image', isRoundVideo && 'round', isSpoiler && 'media-preview-spoiler')
|
||||
}
|
||||
/>
|
||||
{getMessageVideo(message) && <i className="icon-play" />}
|
||||
{getMessageVideo(message) && <i className="icon icon-play" />}
|
||||
{messageSummary}
|
||||
</span>
|
||||
);
|
||||
|
||||
@ -102,7 +102,7 @@ const NewChatStep1: FC<OwnProps & StateProps> = ({
|
||||
onClick={onReset}
|
||||
ariaLabel="Return to Chat List"
|
||||
>
|
||||
<i className="icon-arrow-left" />
|
||||
<i className="icon icon-arrow-left" />
|
||||
</Button>
|
||||
<h3>{lang('GroupAddMembers')}</h3>
|
||||
</div>
|
||||
@ -123,7 +123,7 @@ const NewChatStep1: FC<OwnProps & StateProps> = ({
|
||||
onClick={handleNextStep}
|
||||
ariaLabel={isChannel ? 'Continue To Channel Info' : 'Continue To Group Info'}
|
||||
>
|
||||
<i className="icon-arrow-right" />
|
||||
<i className="icon icon-arrow-right" />
|
||||
</FloatingActionButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -133,7 +133,7 @@ const NewChatStep2: FC<OwnProps & StateProps > = ({
|
||||
onClick={() => onReset()}
|
||||
ariaLabel="Return to member selection"
|
||||
>
|
||||
<i className="icon-arrow-left" />
|
||||
<i className="icon icon-arrow-left" />
|
||||
</Button>
|
||||
<h3>{lang(isChannel ? 'NewChannel' : 'NewGroup')}</h3>
|
||||
</div>
|
||||
@ -187,7 +187,7 @@ const NewChatStep2: FC<OwnProps & StateProps > = ({
|
||||
{isLoading ? (
|
||||
<Spinner color="white" />
|
||||
) : (
|
||||
<i className="icon-arrow-right" />
|
||||
<i className="icon icon-arrow-right" />
|
||||
)}
|
||||
</FloatingActionButton>
|
||||
</div>
|
||||
|
||||
@ -3,7 +3,7 @@ import React, { memo, useCallback } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus,
|
||||
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus, ApiPhoto,
|
||||
} from '../../../api/types';
|
||||
import type { AnimationLevel } from '../../../types';
|
||||
import type { LangFn } from '../../../hooks/useLang';
|
||||
@ -17,7 +17,7 @@ import {
|
||||
getMessageSticker,
|
||||
getMessageIsSpoiler,
|
||||
} from '../../../global/helpers';
|
||||
import { selectChat, selectUser } from '../../../global/selectors';
|
||||
import { selectChat, selectUser, selectUserPhotoFromFullInfo } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatPastTimeShort } from '../../../util/dateFormat';
|
||||
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
||||
@ -43,6 +43,7 @@ type OwnProps = {
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
privateChatUser?: ApiUser;
|
||||
userProfilePhoto?: ApiPhoto;
|
||||
lastMessageOutgoingStatus?: ApiMessageOutgoingStatus;
|
||||
lastSyncTime?: number;
|
||||
animationLevel?: AnimationLevel;
|
||||
@ -54,6 +55,7 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
chat,
|
||||
privateChatUser,
|
||||
userProfilePhoto,
|
||||
animationLevel,
|
||||
lastSyncTime,
|
||||
}) => {
|
||||
@ -86,6 +88,7 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
|
||||
<Avatar
|
||||
chat={chat}
|
||||
user={privateChatUser}
|
||||
userProfilePhoto={userProfilePhoto}
|
||||
isSavedMessages={privateChatUser?.isSelf}
|
||||
lastSyncTime={lastSyncTime}
|
||||
withVideo
|
||||
@ -133,7 +136,7 @@ function renderSummary(
|
||||
buildClassName('media-preview--image', isRoundVideo && 'round', isSpoiler && 'media-preview-spoiler')
|
||||
}
|
||||
/>
|
||||
{getMessageVideo(message) && <i className="icon-play" />}
|
||||
{getMessageVideo(message) && <i className="icon icon-play" />}
|
||||
{renderMessageSummary(lang, message, true, searchQuery)}
|
||||
</span>
|
||||
);
|
||||
@ -147,12 +150,17 @@ export default memo(withGlobal<OwnProps>(
|
||||
}
|
||||
|
||||
const privateChatUserId = getPrivateChatUserId(chat);
|
||||
const privateChatUser = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
||||
const userProfilePhoto = privateChatUser ? selectUserPhotoFromFullInfo(global, privateChatUser.id) : undefined;
|
||||
|
||||
return {
|
||||
chat,
|
||||
lastSyncTime: global.lastSyncTime,
|
||||
animationLevel: global.settings.byKey.animationLevel,
|
||||
...(privateChatUserId && { privateChatUser: selectUser(global, privateChatUserId) }),
|
||||
...(privateChatUserId && {
|
||||
privateChatUser,
|
||||
userProfilePhoto,
|
||||
}),
|
||||
};
|
||||
},
|
||||
)(ChatMessage));
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
flex: 1 1 auto;
|
||||
min-width: 8rem;
|
||||
margin-top: 0.375rem;
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
@ -24,7 +24,7 @@ const DateSuggest: FC<OwnProps> = ({
|
||||
className="date-item"
|
||||
key={text}
|
||||
>
|
||||
<i className="icon-calendar" />
|
||||
<i className="icon icon-calendar" />
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -91,7 +91,7 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
|
||||
<div className="LeftSearch" ref={containerRef} onKeyDown={handleKeyDown}>
|
||||
<TabList activeTab={activeTab} tabs={chatId ? CHAT_TABS : TABS} onSwitchTab={handleSwitchTab} />
|
||||
<Transition
|
||||
name={lang.isRtl ? 'slide-optimized-rtl' : 'slide-optimized'}
|
||||
name={lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'}
|
||||
renderCount={TRANSITION_RENDER_COUNT}
|
||||
activeKey={currentContent}
|
||||
>
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
padding: 0.625rem 0.25rem;
|
||||
margin-left: 0.5rem;
|
||||
border-radius: var(--border-radius-default);
|
||||
cursor: pointer;
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ import React, {
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiUser } from '../../../api/types';
|
||||
import type { AnimationLevel } from '../../../types';
|
||||
|
||||
import { getUserFirstOrLastName } from '../../../global/helpers';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
@ -13,9 +12,9 @@ import { throttle } from '../../../util/schedulers';
|
||||
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import Button from '../../ui/Button';
|
||||
import LeftSearchResultChat from './LeftSearchResultChat';
|
||||
import UserAvatar from '../../common/UserAvatar';
|
||||
|
||||
import './RecentContacts.scss';
|
||||
|
||||
@ -27,7 +26,6 @@ type StateProps = {
|
||||
topUserIds?: string[];
|
||||
usersById: Record<string, ApiUser>;
|
||||
recentlyFoundChatIds?: string[];
|
||||
animationLevel: AnimationLevel;
|
||||
};
|
||||
|
||||
const SEARCH_CLOSE_TIMEOUT_MS = 250;
|
||||
@ -39,7 +37,6 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
|
||||
topUserIds,
|
||||
usersById,
|
||||
recentlyFoundChatIds,
|
||||
animationLevel,
|
||||
onReset,
|
||||
}) => {
|
||||
const {
|
||||
@ -80,8 +77,13 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
|
||||
<div className="top-peers-section" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<div ref={topUsersRef} className="top-peers no-selection">
|
||||
{topUserIds.map((userId) => (
|
||||
<div className="top-peer-item" onClick={() => handleClick(userId)} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Avatar user={usersById[userId]} animationLevel={animationLevel} withVideo />
|
||||
<div
|
||||
key={userId}
|
||||
className="top-peer-item"
|
||||
onClick={() => handleClick(userId)}
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
>
|
||||
<UserAvatar user={usersById[userId]} withVideo />
|
||||
<div className="top-peer-name">{renderText(getUserFirstOrLastName(usersById[userId]) || NBSP)}</div>
|
||||
</div>
|
||||
))}
|
||||
@ -101,7 +103,7 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
|
||||
onClick={handleClearRecentlyFoundChats}
|
||||
isRtl={lang.isRtl}
|
||||
>
|
||||
<i className="icon-close" />
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
</h3>
|
||||
{recentlyFoundChatIds.map((id) => (
|
||||
@ -121,13 +123,11 @@ export default memo(withGlobal<OwnProps>(
|
||||
const { userIds: topUserIds } = global.topPeers;
|
||||
const usersById = global.users.byId;
|
||||
const { recentlyFoundChatIds } = global;
|
||||
const { animationLevel } = global.settings.byKey;
|
||||
|
||||
return {
|
||||
topUserIds,
|
||||
usersById,
|
||||
recentlyFoundChatIds,
|
||||
animationLevel,
|
||||
};
|
||||
},
|
||||
)(RecentContacts));
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#Settings {
|
||||
height: 100%;
|
||||
|
||||
> .Transition__slide {
|
||||
> .Transition_slide {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@ -98,7 +98,7 @@
|
||||
.settings-content-icon {
|
||||
margin-bottom: 2.5rem;
|
||||
|
||||
&.opacity-transition:not(.shown) {
|
||||
&.opacity-transition.not-shown {
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
}
|
||||
@ -242,7 +242,7 @@
|
||||
.ListItem-button {
|
||||
color: var(--color-error);
|
||||
|
||||
i {
|
||||
.icon {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user