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