diff --git a/src/api/gramjs/apiBuilders/users.ts b/src/api/gramjs/apiBuilders/users.ts index bacfd8fa0..d624f9982 100644 --- a/src/api/gramjs/apiBuilders/users.ts +++ b/src/api/gramjs/apiBuilders/users.ts @@ -3,6 +3,7 @@ import type { ApiEmojiStatus, ApiPremiumGiftOption, ApiUser, + ApiUserFullInfo, ApiUserStatus, ApiUserType, } from '../../types'; @@ -10,7 +11,7 @@ import { buildApiPeerId } from './peers'; import { buildApiBotInfo } from './bots'; import { buildApiPhoto, buildApiUsernames } from './common'; -export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUser { +export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUserFullInfo { const { fullUser: { about, commonChatsCount, pinnedMsgId, botInfo, blocked, @@ -20,22 +21,19 @@ export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUse users, } = mtpUserFull; - const user = buildApiUser(users[0])!; + const userId = buildApiPeerId(users[0].id, 'user'); return { - ...user, - fullInfo: { - ...(profilePhoto instanceof GramJs.Photo && { profilePhoto: buildApiPhoto(profilePhoto) }), - ...(fallbackPhoto instanceof GramJs.Photo && { fallbackPhoto: buildApiPhoto(fallbackPhoto) }), - ...(personalPhoto instanceof GramJs.Photo && { personalPhoto: buildApiPhoto(personalPhoto) }), - bio: about, - commonChatsCount, - pinnedMessageId: pinnedMsgId, - isBlocked: Boolean(blocked), - noVoiceMessages: voiceMessagesForbidden, - ...(premiumGifts && { premiumGifts: premiumGifts.map((gift) => buildApiPremiumGiftOption(gift)) }), - ...(botInfo && { botInfo: buildApiBotInfo(botInfo, user.id) }), - }, + bio: about, + commonChatsCount, + pinnedMessageId: pinnedMsgId, + isBlocked: Boolean(blocked), + noVoiceMessages: voiceMessagesForbidden, + ...(profilePhoto instanceof GramJs.Photo && { profilePhoto: buildApiPhoto(profilePhoto) }), + ...(fallbackPhoto instanceof GramJs.Photo && { fallbackPhoto: buildApiPhoto(fallbackPhoto) }), + ...(personalPhoto instanceof GramJs.Photo && { personalPhoto: buildApiPhoto(personalPhoto) }), + ...(premiumGifts && { premiumGifts: premiumGifts.map((gift) => buildApiPremiumGiftOption(gift)) }), + ...(botInfo && { botInfo: buildApiBotInfo(botInfo, userId) }), }; } diff --git a/src/api/gramjs/methods/auth.ts b/src/api/gramjs/methods/auth.ts index 9f1b6847f..347efc3e6 100644 --- a/src/api/gramjs/methods/auth.ts +++ b/src/api/gramjs/methods/auth.ts @@ -4,6 +4,7 @@ import type { ApiUpdateAuthorizationStateType, OnApiUpdate, ApiUser, + ApiUserFullInfo, } from '../../types'; import { DEBUG } from '../../../config'; @@ -117,10 +118,11 @@ export function onAuthReady() { onUpdate(buildAuthStateUpdate('authorizationStateReady')); } -export function onCurrentUserUpdate(currentUser: ApiUser) { +export function onCurrentUserUpdate(currentUser: ApiUser, currentUserFullInfo: ApiUserFullInfo) { onUpdate({ '@type': 'updateCurrentUser', currentUser, + currentUserFullInfo, }); } diff --git a/src/api/gramjs/methods/client.ts b/src/api/gramjs/methods/client.ts index f526e386d..914adeca1 100644 --- a/src/api/gramjs/methods/client.ts +++ b/src/api/gramjs/methods/client.ts @@ -24,7 +24,7 @@ import { import { updater } from '../updater'; import { setMessageBuilderCurrentUserId } from '../apiBuilders/messages'; import downloadMediaWithClient, { parseMediaUrl } from './media'; -import { buildApiUserFromFull } from '../apiBuilders/users'; +import { buildApiUser, buildApiUserFullInfo } from '../apiBuilders/users'; import localDb, { clearLocalDb } from '../localDb'; import { buildApiPeerId } from '../apiBuilders/peers'; import { addMessageToLocalDb, log } from '../helpers'; @@ -336,10 +336,11 @@ export async function fetchCurrentUser() { localDb.photos[user.photo.id.toString()] = user.photo; } localDb.users[buildApiPeerId(user.id, 'user')] = user; - const currentUser = buildApiUserFromFull(userFull); + const currentUserFullInfo = buildApiUserFullInfo(userFull); + const currentUser = buildApiUser(user)!; setMessageBuilderCurrentUserId(currentUser.id); - onCurrentUserUpdate(currentUser); + onCurrentUserUpdate(currentUser, currentUserFullInfo); currentUserId = currentUser.id; setIsPremium({ isPremium: Boolean(currentUser.isPremium) }); diff --git a/src/api/gramjs/methods/statistics.ts b/src/api/gramjs/methods/statistics.ts index 20eb53c5f..da485d00a 100644 --- a/src/api/gramjs/methods/statistics.ts +++ b/src/api/gramjs/methods/statistics.ts @@ -14,11 +14,11 @@ import { import { buildApiUser } from '../apiBuilders/users'; export async function fetchChannelStatistics({ - chat, -}: { chat: ApiChat }) { + chat, dcId, +}: { chat: ApiChat; dcId?: number }) { const result = await invokeRequest(new GramJs.stats.GetBroadcastStats({ channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, - }), undefined, undefined, undefined, chat.fullInfo!.statisticsDcId); + }), undefined, undefined, undefined, dcId); if (!result) { return undefined; @@ -31,11 +31,11 @@ export async function fetchChannelStatistics({ } export async function fetchGroupStatistics({ - chat, -}: { chat: ApiChat }) { + chat, dcId, +}: { chat: ApiChat; dcId?: number }) { const result = await invokeRequest(new GramJs.stats.GetMegagroupStats({ channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, - }), undefined, undefined, undefined, chat.fullInfo!.statisticsDcId); + }), undefined, undefined, undefined, dcId); if (!result) { return undefined; @@ -52,14 +52,16 @@ export async function fetchGroupStatistics({ export async function fetchMessageStatistics({ chat, messageId, + dcId, }: { chat: ApiChat; messageId: number; + dcId?: number; }): Promise { const result = await invokeRequest(new GramJs.stats.GetMessageStats({ channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, msgId: messageId, - }), undefined, undefined, undefined, chat.fullInfo!.statisticsDcId); + }), undefined, undefined, undefined, dcId); if (!result) { return undefined; diff --git a/src/api/gramjs/methods/users.ts b/src/api/gramjs/methods/users.ts index 70f689477..39a9736aa 100644 --- a/src/api/gramjs/methods/users.ts +++ b/src/api/gramjs/methods/users.ts @@ -15,7 +15,7 @@ import { getEntityTypeById, buildInputEmojiStatus, } from '../gramjsBuilders'; -import { buildApiUser, buildApiUserFromFull, buildApiUsersAndStatuses } from '../apiBuilders/users'; +import { buildApiUser, buildApiUserFullInfo, buildApiUsersAndStatuses } from '../apiBuilders/users'; import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiPhoto } from '../apiBuilders/common'; import { addEntitiesWithPhotosToLocalDb, addPhotoToLocalDb, addUserToLocalDb } from '../helpers'; @@ -40,28 +40,28 @@ export async function fetchFullUser({ return undefined; } - const fullInfo = await invokeRequest(new GramJs.users.GetFullUser({ id: input })); + const result = await invokeRequest(new GramJs.users.GetFullUser({ id: input })); - if (!fullInfo) { + if (!result) { return undefined; } - updateLocalDb(fullInfo); - addUserToLocalDb(fullInfo.users[0], true); + updateLocalDb(result); + addUserToLocalDb(result.users[0], true); - if (fullInfo.fullUser.profilePhoto instanceof GramJs.Photo) { - localDb.photos[fullInfo.fullUser.profilePhoto.id.toString()] = fullInfo.fullUser.profilePhoto; + if (result.fullUser.profilePhoto instanceof GramJs.Photo) { + localDb.photos[result.fullUser.profilePhoto.id.toString()] = result.fullUser.profilePhoto; } - if (fullInfo.fullUser.personalPhoto instanceof GramJs.Photo) { - localDb.photos[fullInfo.fullUser.personalPhoto.id.toString()] = fullInfo.fullUser.personalPhoto; + if (result.fullUser.personalPhoto instanceof GramJs.Photo) { + localDb.photos[result.fullUser.personalPhoto.id.toString()] = result.fullUser.personalPhoto; } - if (fullInfo.fullUser.fallbackPhoto instanceof GramJs.Photo) { - localDb.photos[fullInfo.fullUser.fallbackPhoto.id.toString()] = fullInfo.fullUser.fallbackPhoto; + if (result.fullUser.fallbackPhoto instanceof GramJs.Photo) { + localDb.photos[result.fullUser.fallbackPhoto.id.toString()] = result.fullUser.fallbackPhoto; } - const botInfo = fullInfo.fullUser.botInfo; + const botInfo = result.fullUser.botInfo; if (botInfo?.descriptionPhoto instanceof GramJs.Photo) { localDb.photos[botInfo.descriptionPhoto.id.toString()] = botInfo.descriptionPhoto; } @@ -69,8 +69,8 @@ export async function fetchFullUser({ localDb.documents[botInfo.descriptionDocument.id.toString()] = botInfo.descriptionDocument; } - const userWithFullInfo = buildApiUserFromFull(fullInfo); - const user = buildApiUser(fullInfo.users[0]); + const fullInfo = buildApiUserFullInfo(result); + const user = buildApiUser(result.users[0])!; onUpdate({ '@type': 'updateUser', @@ -78,11 +78,11 @@ export async function fetchFullUser({ user: { ...user, avatarHash: user?.avatarHash || undefined, - fullInfo: userWithFullInfo.fullInfo, }, + fullInfo, }); - return userWithFullInfo; + return { user, fullInfo }; } export async function fetchCommonChats(id: string, accessHash?: string, maxId?: string) { diff --git a/src/api/types/chats.ts b/src/api/types/chats.ts index 3a69b57f9..05e9efe2f 100644 --- a/src/api/types/chats.ts +++ b/src/api/types/chats.ts @@ -67,8 +67,6 @@ export interface ApiChat { // Obtained from GetChatSettings settings?: ApiChatSettings; - // Obtained from GetFullChat / GetFullChannel - fullInfo?: ApiChatFullInfo; joinRequests?: ApiChatInviteImporter[]; isJoinToSend?: boolean; diff --git a/src/api/types/updates.ts b/src/api/types/updates.ts index 2d22fa195..dc005ba9a 100644 --- a/src/api/types/updates.ts +++ b/src/api/types/updates.ts @@ -87,6 +87,7 @@ export type ApiUpdateConnectionState = { export type ApiUpdateCurrentUser = { '@type': 'updateCurrentUser'; currentUser: ApiUser; + currentUserFullInfo: ApiUserFullInfo; }; export type ApiUpdateChat = { @@ -338,6 +339,7 @@ export type ApiUpdateUser = { '@type': 'updateUser'; id: string; user: Partial; + fullInfo?: ApiUserFullInfo; }; export type ApiUpdateRequestUserUpdate = { diff --git a/src/api/types/users.ts b/src/api/types/users.ts index c125e6478..a272ec23f 100644 --- a/src/api/types/users.ts +++ b/src/api/types/users.ts @@ -29,9 +29,6 @@ export interface ApiUser { fakeType?: ApiFakeType; isAttachBot?: boolean; emojiStatus?: ApiEmojiStatus; - - // Obtained from GetFullUser / UserFullInfo - fullInfo?: ApiUserFullInfo; } export interface ApiUserFullInfo { diff --git a/src/components/auth/Auth.scss b/src/components/auth/Auth.scss index 52c684f31..3c5137c6b 100644 --- a/src/components/auth/Auth.scss +++ b/src/components/auth/Auth.scss @@ -103,7 +103,7 @@ display: flex; align-items: center; justify-content: center; - cursor: pointer; + cursor: var(--custom-cursor, pointer); &:hover, &:focus { diff --git a/src/components/auth/AuthCode.tsx b/src/components/auth/AuthCode.tsx index ec55f772a..bdb1fc6ea 100644 --- a/src/components/auth/AuthCode.tsx +++ b/src/components/auth/AuthCode.tsx @@ -98,13 +98,13 @@ const AuthCode: FC = ({

{authPhoneNumber}
- +

diff --git a/src/components/auth/CountryCodeInput.scss b/src/components/auth/CountryCodeInput.scss index 587d9a882..aa2bc7e2d 100644 --- a/src/components/auth/CountryCodeInput.scss +++ b/src/components/auth/CountryCodeInput.scss @@ -1,6 +1,6 @@ .CountryCodeInput { .input-group { - cursor: pointer; + cursor: var(--custom-cursor, pointer); z-index: var(--z-country-code-input-group); diff --git a/src/components/calls/group/GroupCall.scss b/src/components/calls/group/GroupCall.scss index 13f9ac2c1..f2735ffd3 100644 --- a/src/components/calls/group/GroupCall.scss +++ b/src/components/calls/group/GroupCall.scss @@ -84,7 +84,7 @@ align-items: center; border-radius: 0.75rem; transition: 0.15s ease-out background-color; - cursor: pointer; + cursor: var(--custom-cursor, pointer); color: var(--color-text-secondary); &:hover { @@ -97,7 +97,7 @@ overflow: hidden; } - .icon { + .icon-wrapper { display: flex; justify-content: center; align-items: center; @@ -127,7 +127,7 @@ height: 8.75rem; button { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } &::before { diff --git a/src/components/calls/group/GroupCall.tsx b/src/components/calls/group/GroupCall.tsx index 3672a43de..71abc5d43 100644 --- a/src/components/calls/group/GroupCall.tsx +++ b/src/components/calls/group/GroupCall.tsx @@ -143,7 +143,7 @@ const GroupCall: FC = ({ onClick={onTrigger} ariaLabel={lang('AccDescrMoreOptions')} > - + ); }, [lang]); @@ -276,7 +276,7 @@ const GroupCall: FC = ({ onClick={handleToggleFullscreen} ariaLabel={lang(isFullscreen ? 'AccExitFullscreen' : 'AccSwitchToFullscreen')} > - + )} {isLandscapeLayout && ( @@ -286,7 +286,7 @@ const GroupCall: FC = ({ color="translucent" onClick={handleToggleSidebar} > - + )} {((IS_SCREENSHARE_SUPPORTED && !shouldRaiseHand) || isAdmin) && ( @@ -319,7 +319,7 @@ const GroupCall: FC = ({ color="translucent" onClick={handleClose} > - + @@ -359,7 +359,11 @@ const GroupCall: FC = ({ )} onClick={handleClickVideoOrSpeaker} > - + @@ -372,7 +376,7 @@ const GroupCall: FC = ({

diff --git a/src/components/calls/group/GroupCallParticipant.scss b/src/components/calls/group/GroupCallParticipant.scss index f5e7dc1da..318aa0df5 100644 --- a/src/components/calls/group/GroupCallParticipant.scss +++ b/src/components/calls/group/GroupCallParticipant.scss @@ -7,7 +7,7 @@ padding: 0.5rem 0.75rem; border-radius: 0.75rem; transition: 0.15s ease-out background-color; - cursor: pointer; + cursor: var(--custom-cursor, pointer); &:hover { background: #2f363e; @@ -72,7 +72,7 @@ } .streams { - cursor: pointer; + cursor: var(--custom-cursor, pointer); display: flex; } } diff --git a/src/components/calls/group/GroupCallParticipant.tsx b/src/components/calls/group/GroupCallParticipant.tsx index d1b10c89c..56c42bd55 100644 --- a/src/components/calls/group/GroupCallParticipant.tsx +++ b/src/components/calls/group/GroupCallParticipant.tsx @@ -4,10 +4,10 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, useMemo, useRef } from '../../../lib/teact/teact'; import { withGlobal } from '../../../global'; -import type { ApiChat, ApiUser } from '../../../api/types'; +import type { ApiChat, ApiPhoto, ApiUser } from '../../../api/types'; import buildClassName from '../../../util/buildClassName'; -import { selectChat, selectUser } from '../../../global/selectors'; +import { selectChat, selectUser, selectUserPhotoFromFullInfo } from '../../../global/selectors'; import useLang from '../../../hooks/useLang'; import { GROUP_CALL_DEFAULT_VOLUME, GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config'; @@ -23,6 +23,7 @@ type OwnProps = { type StateProps = { user?: ApiUser; + userProfilePhoto?: ApiPhoto; chat?: ApiChat; }; @@ -30,6 +31,7 @@ const GroupCallParticipant: FC = ({ openParticipantMenu, participant, user, + userProfilePhoto, chat, }) => { // eslint-disable-next-line no-null/no-null @@ -79,7 +81,7 @@ const GroupCallParticipant: FC = ({ onClick={handleOnClick} ref={anchorRef} > - +
{name} {aboutText} @@ -96,6 +98,7 @@ export default memo(withGlobal( return { user: participant.isUser ? selectUser(global, participant.id) : undefined, chat: !participant.isUser ? selectChat(global, participant.id) : undefined, + userProfilePhoto: participant.isUser ? selectUserPhotoFromFullInfo(global, participant.id) : undefined, }; }, )(GroupCallParticipant)); diff --git a/src/components/calls/group/GroupCallParticipantList.tsx b/src/components/calls/group/GroupCallParticipantList.tsx index 6b3453b75..21681ae6d 100644 --- a/src/components/calls/group/GroupCallParticipantList.tsx +++ b/src/components/calls/group/GroupCallParticipantList.tsx @@ -53,8 +53,8 @@ const GroupCallParticipantList: FC = ({ return (
-
- +
+
{lang('VoipGroupInviteMember')}
diff --git a/src/components/calls/group/GroupCallParticipantMenu.scss b/src/components/calls/group/GroupCallParticipantMenu.scss index ed1ce1f86..e43265a9c 100644 --- a/src/components/calls/group/GroupCallParticipantMenu.scss +++ b/src/components/calls/group/GroupCallParticipantMenu.scss @@ -66,7 +66,7 @@ position: relative; overflow: hidden; - cursor: pointer; + cursor: var(--custom-cursor, pointer); @mixin thumb-styles() { border: none; diff --git a/src/components/calls/group/GroupCallParticipantVideo.scss b/src/components/calls/group/GroupCallParticipantVideo.scss index ccfa93b2b..2918592e5 100644 --- a/src/components/calls/group/GroupCallParticipantVideo.scss +++ b/src/components/calls/group/GroupCallParticipantVideo.scss @@ -6,7 +6,7 @@ width: calc(50% - 0.25rem); /* stylelint-disable-next-line plugin/no-low-performance-animation-properties */ transition: 0.25s ease-out width; - cursor: pointer; + cursor: var(--custom-cursor, pointer); .thumbnail-avatar { position: absolute; @@ -57,7 +57,7 @@ gap: 0.25rem; transition: 0.25s ease-out opacity, 0.25s ease-out background-color; opacity: 0; - cursor: pointer; + cursor: var(--custom-cursor, pointer); outline: none !important; &:hover { diff --git a/src/components/calls/group/GroupCallParticipantVideo.tsx b/src/components/calls/group/GroupCallParticipantVideo.tsx index f8415480c..3a8859dd6 100644 --- a/src/components/calls/group/GroupCallParticipantVideo.tsx +++ b/src/components/calls/group/GroupCallParticipantVideo.tsx @@ -4,11 +4,11 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, useCallback } from '../../../lib/teact/teact'; import { withGlobal } from '../../../global'; -import type { ApiChat, ApiUser } from '../../../api/types'; +import type { ApiChat, ApiPhoto, ApiUser } from '../../../api/types'; import { GROUP_CALL_THUMB_VIDEO_DISABLED } from '../../../config'; import buildClassName from '../../../util/buildClassName'; -import { selectChat, selectUser } from '../../../global/selectors'; +import { selectChat, selectUser, selectUserPhotoFromFullInfo } from '../../../global/selectors'; import useLang from '../../../hooks/useLang'; import Avatar from '../../common/Avatar'; @@ -25,6 +25,7 @@ type OwnProps = { type StateProps = { user?: ApiUser; chat?: ApiChat; + userProfilePhoto?: ApiPhoto; currentUserId?: string; isActive?: boolean; }; @@ -34,6 +35,7 @@ const GroupCallParticipantVideo: FC = ({ onClick, user, chat, + userProfilePhoto, isActive, isFullscreen, }) => { @@ -56,11 +58,11 @@ const GroupCallParticipantVideo: FC = ({ > {isFullscreen && ( )} - + {!GROUP_CALL_THUMB_VIDEO_DISABLED && (
); @@ -82,6 +84,7 @@ export default memo(withGlobal( currentUserId: global.currentUserId, user: participant.isUser ? selectUser(global, participant.id) : undefined, chat: !participant.isUser ? selectChat(global, participant.id) : undefined, + userProfilePhoto: participant.isUser ? selectUserPhotoFromFullInfo(global, participant.id) : undefined, isActive: (participant.amplitude || 0) > THRESHOLD, }; }, diff --git a/src/components/calls/group/GroupCallTopPane.scss b/src/components/calls/group/GroupCallTopPane.scss index 182866904..65616fedd 100644 --- a/src/components/calls/group/GroupCallTopPane.scss +++ b/src/components/calls/group/GroupCallTopPane.scss @@ -13,7 +13,7 @@ padding: 0.375rem 0.5rem 0.375rem 0.75rem; background: var(--color-background); z-index: -1; - cursor: pointer; + cursor: var(--custom-cursor, pointer); &::before { content: ""; diff --git a/src/components/calls/group/GroupCallTopPane.tsx b/src/components/calls/group/GroupCallTopPane.tsx index 39c53cfc7..d00de1c7d 100644 --- a/src/components/calls/group/GroupCallTopPane.tsx +++ b/src/components/calls/group/GroupCallTopPane.tsx @@ -2,9 +2,9 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, useCallback, useEffect, useMemo, } from '../../../lib/teact/teact'; -import { getActions, withGlobal } from '../../../global'; +import { getActions, getGlobal, withGlobal } from '../../../global'; -import type { ApiChat, ApiGroupCall, ApiUser } from '../../../api/types'; +import type { ApiGroupCall } from '../../../api/types'; import type { AnimationLevel } from '../../../types'; import { selectChatGroupCall } from '../../../global/selectors/calls'; @@ -14,6 +14,7 @@ import useLang from '../../../hooks/useLang'; import Button from '../../ui/Button'; import Avatar from '../../common/Avatar'; +import UserAvatar from '../../common/UserAvatar'; import './GroupCallTopPane.scss'; @@ -26,8 +27,6 @@ type OwnProps = { type StateProps = { groupCall?: ApiGroupCall; isActive: boolean; - usersById: Record; - chatsById: Record; animationLevel: AnimationLevel; }; @@ -37,8 +36,6 @@ const GroupCallTopPane: FC = ({ className, groupCall, hasPinnedOffset, - usersById, - chatsById, animationLevel, }) => { const { @@ -57,22 +54,28 @@ const GroupCallTopPane: FC = ({ const participants = groupCall?.participants; const fetchedParticipants = useMemo(() => { - if (participants) { - return Object.values(participants).filter((_, i) => i < 3).map(({ id, isUser }) => { - if (isUser) { - if (!usersById[id]) { - return undefined; - } - return { user: usersById[id] }; - } else { - if (!chatsById[id]) { - return undefined; - } - return { chat: chatsById[id] }; + if (!participants) { + return []; + } + + // No need for expensive global updates on users and chats, so we avoid them + const usersById = getGlobal().users.byId; + const chatsById = getGlobal().chats.byId; + + return Object.values(participants).filter((_, i) => i < 3).map(({ id, isUser }) => { + if (isUser) { + if (!usersById[id]) { + return undefined; } - }).filter(Boolean); - } else return []; - }, [chatsById, participants, usersById]); + return { user: usersById[id] }; + } else { + if (!chatsById[id]) { + return undefined; + } + return { chat: chatsById[id] }; + } + }).filter(Boolean); + }, [participants]); useEffect(() => { if (!groupCall?.id) return undefined; @@ -111,7 +114,7 @@ const GroupCallTopPane: FC = ({ {fetchedParticipants.map((p) => { if (!p) return undefined; if (p.user) { - return ; + return ; } else { return ; } @@ -125,18 +128,18 @@ const GroupCallTopPane: FC = ({ }; export default memo(withGlobal( - (global, { chatId }) => { + (global, { chatId }): StateProps => { const chat = selectChat(global, chatId)!; const groupCall = selectChatGroupCall(global, chatId); const activeGroupCallId = selectTabState(global).isMasterTab ? global.groupCalls.activeGroupCallId : undefined; + return { groupCall, - usersById: global.users.byId, - chatsById: global.chats.byId, - activeGroupCallId: global.groupCalls.activeGroupCallId, - isActive: ((!groupCall ? (chat && chat.isCallNotEmpty && chat.isCallActive) - : (groupCall.participantsCount > 0 && groupCall.isLoaded))) - && (activeGroupCallId !== groupCall?.id), + isActive: activeGroupCallId !== groupCall?.id && Boolean( + groupCall + ? groupCall.participantsCount > 0 && groupCall.isLoaded + : chat && chat.isCallNotEmpty && chat.isCallActive, + ), animationLevel: global.settings.byKey.animationLevel, }; }, diff --git a/src/components/calls/phone/PhoneCall.module.scss b/src/components/calls/phone/PhoneCall.module.scss index 52e7c6517..3902a0902 100644 --- a/src/components/calls/phone/PhoneCall.module.scss +++ b/src/components/calls/phone/PhoneCall.module.scss @@ -82,7 +82,7 @@ .emojis { user-select: none; pointer-events: auto; - cursor: pointer; + cursor: var(--custom-cursor, pointer); margin-top: 1rem; height: 3rem; transition: 0.25s ease-in-out transform; diff --git a/src/components/calls/phone/PhoneCall.tsx b/src/components/calls/phone/PhoneCall.tsx index eeb8789fb..6b3fa56f7 100644 --- a/src/components/calls/phone/PhoneCall.tsx +++ b/src/components/calls/phone/PhoneCall.tsx @@ -5,7 +5,7 @@ import React, { import { getActions, withGlobal } from '../../../global'; import '../../../global/actions/calls'; -import type { ApiPhoneCall, ApiUser } from '../../../api/types'; +import type { ApiPhoneCall, ApiPhoto, ApiUser } from '../../../api/types'; import type { AnimationLevel } from '../../../types'; import { @@ -14,7 +14,7 @@ import { IS_REQUEST_FULLSCREEN_SUPPORTED, } from '../../../util/windowEnvironment'; import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; -import { selectTabState } from '../../../global/selectors'; +import { selectTabState, selectUserPhotoFromFullInfo } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import { selectPhoneCallUser } from '../../../global/selectors/calls'; import useLang from '../../../hooks/useLang'; @@ -39,6 +39,7 @@ import styles from './PhoneCall.module.scss'; type StateProps = { user?: ApiUser; phoneCall?: ApiPhoneCall; + userProfilePhoto?: ApiPhoto; isOutgoing: boolean; isCallPanelVisible?: boolean; animationLevel: AnimationLevel; @@ -46,6 +47,7 @@ type StateProps = { const PhoneCall: FC = ({ user, + userProfilePhoto, isOutgoing, phoneCall, isCallPanelVisible, @@ -238,6 +240,7 @@ const PhoneCall: FC = ({ > = ({ onClick={handleToggleFullscreen} ariaLabel={lang(isFullscreen ? 'AccExitFullscreen' : 'AccSwitchToFullscreen')} > - + )} @@ -290,7 +293,7 @@ const PhoneCall: FC = ({ onClick={handleClose} className={styles.closeButton} > - +
{ const { phoneCall, currentUserId } = global; const { isCallPanelVisible, isMasterTab } = selectTabState(global); + const user = selectPhoneCallUser(global); + const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined; return { isCallPanelVisible: Boolean(isCallPanelVisible), - user: selectPhoneCallUser(global), + user, + userProfilePhoto, isOutgoing: phoneCall?.adminId === currentUserId, phoneCall: isMasterTab ? phoneCall : undefined, animationLevel: global.settings.byKey.animationLevel, diff --git a/src/components/calls/phone/PhoneCallButton.tsx b/src/components/calls/phone/PhoneCallButton.tsx index dc71d42e8..d3f66f3a1 100644 --- a/src/components/calls/phone/PhoneCallButton.tsx +++ b/src/components/calls/phone/PhoneCallButton.tsx @@ -36,7 +36,7 @@ const PhoneCallButton: FC = ({ onClick={onClick} disabled={isDisabled} > - {customIcon || } + {customIcon || }
{label}
diff --git a/src/components/calls/phone/RatePhoneCallModal.module.scss b/src/components/calls/phone/RatePhoneCallModal.module.scss index 191fc7e12..b98f1ff6d 100644 --- a/src/components/calls/phone/RatePhoneCallModal.module.scss +++ b/src/components/calls/phone/RatePhoneCallModal.module.scss @@ -6,7 +6,7 @@ } .star { - cursor: pointer; + cursor: var(--custom-cursor, pointer); color: var(--color-text-secondary); &:not(:first-child) { diff --git a/src/components/calls/phone/RatePhoneCallModal.tsx b/src/components/calls/phone/RatePhoneCallModal.tsx index 5c8483f0b..56776f627 100644 --- a/src/components/calls/phone/RatePhoneCallModal.tsx +++ b/src/components/calls/phone/RatePhoneCallModal.tsx @@ -56,6 +56,7 @@ const RatePhoneCallModal: FC = ({ return ( = ({
{isSelectable && (
- {isSelected && } + {isSelected && }
)} {shouldRenderSpinner && (
@@ -376,7 +376,7 @@ const Audio: FC = ({ ariaLabel={isDownloading ? 'Cancel download' : 'Download'} onClick={handleDownloadClick} > - + )} {origin === AudioOrigin.Search && renderWithTitle()} @@ -519,6 +519,7 @@ function renderVoice( > diff --git a/src/components/common/Avatar.scss b/src/components/common/Avatar.scss index 7973ac292..819816773 100644 --- a/src/components/common/Avatar.scss +++ b/src/components/common/Avatar.scss @@ -99,7 +99,7 @@ &.size-large { font-size: 1.3125rem; - i { + .icon { font-size: 1.625rem; } @@ -148,7 +148,7 @@ } &.interactive { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } .poster { diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx index 3728ecc1c..7cb354974 100644 --- a/src/components/common/Avatar.tsx +++ b/src/components/common/Avatar.tsx @@ -49,6 +49,7 @@ type OwnProps = { chat?: ApiChat; user?: ApiUser; photo?: ApiPhoto; + userProfilePhoto?: ApiPhoto; userStatus?: ApiUserStatus; text?: string; isSavedMessages?: boolean; @@ -69,6 +70,7 @@ const Avatar: FC = ({ chat, user, photo, + userProfilePhoto, userStatus, text, isSavedMessages, @@ -96,8 +98,7 @@ const Avatar: FC = ({ withVideo && !VIDEO_AVATARS_DISABLED && animationLevel === ANIMATION_LEVEL_MAX && user?.isPremium && user?.hasVideoAvatar ); - const profilePhoto = user?.fullInfo?.personalPhoto || user?.fullInfo?.profilePhoto || user?.fullInfo?.fallbackPhoto; - const hasProfileVideo = profilePhoto?.isVideo; + const hasProfileVideo = userProfilePhoto?.isVideo; const isIntersectingForVideo = useIsIntersecting( ref, canShowVideo && hasProfileVideo ? observeIntersection : undefined, ); @@ -117,7 +118,7 @@ const Avatar: FC = ({ } if (hasProfileVideo) { - videoHash = getChatAvatarHash(user!, undefined, 'video'); + videoHash = getChatAvatarHash(user!, undefined, 'video', undefined, userProfilePhoto); } } @@ -149,10 +150,10 @@ const Avatar: FC = ({ const userId = user?.id; useEffect(() => { - if (userId && canShowVideo && !profilePhoto) { + if (userId && canShowVideo && !userProfilePhoto) { loadFullUser({ userId }); } - }, [loadFullUser, profilePhoto, userId, canShowVideo]); + }, [loadFullUser, userProfilePhoto, userId, canShowVideo]); const lang = useLang(); @@ -160,11 +161,35 @@ const Avatar: FC = ({ const author = user ? getUserFullName(user) : (chat ? getChatTitle(lang, chat) : text); if (isSavedMessages) { - content = ; + content = ( + + ); } else if (isDeleted) { - content = ; + content = ( + + ); } else if (isReplies) { - content = ; + content = ( + + ); } else if (hasBlobUrl) { content = ( <> diff --git a/src/components/common/CalendarModal.scss b/src/components/common/CalendarModal.scss index 7151cb761..d3185eb00 100644 --- a/src/components/common/CalendarModal.scss +++ b/src/components/common/CalendarModal.scss @@ -80,7 +80,7 @@ } &.clickable { - cursor: pointer; + cursor: var(--custom-cursor, pointer); &:hover { background-color: var(--color-interactive-element-hover); diff --git a/src/components/common/CalendarModal.tsx b/src/components/common/CalendarModal.tsx index 8c9a7260a..5d8c5496f 100644 --- a/src/components/common/CalendarModal.tsx +++ b/src/components/common/CalendarModal.tsx @@ -249,7 +249,7 @@ const CalendarModal: FC = ({ color="translucent" onClick={onClose} > - +

@@ -265,7 +265,7 @@ const CalendarModal: FC = ({ disabled={shouldDisablePrevMonth} onClick={!shouldDisablePrevMonth ? handlePrevMonth : undefined} > - +

@@ -297,6 +297,7 @@ const CalendarModal: FC = ({ onClick={() => handleDateSelect(gridDate)} className={buildClassName( 'day-button', + 'div-button', isDisabledDay( currentYear, currentMonth, gridDate, minDate, maxDate, ) diff --git a/src/components/common/ChatExtra.tsx b/src/components/common/ChatExtra.tsx index 218d17201..19930a85f 100644 --- a/src/components/common/ChatExtra.tsx +++ b/src/components/common/ChatExtra.tsx @@ -12,10 +12,15 @@ import { MAIN_THREAD_ID } from '../../api/types'; import { TME_LINK_PREFIX } from '../../config'; import { - selectChat, selectCurrentMessageList, selectNotifyExceptions, selectNotifySettings, selectUser, + selectChat, + selectChatFullInfo, + selectCurrentMessageList, + selectNotifyExceptions, + selectNotifySettings, + selectUser, + selectUserFullInfo, } from '../../global/selectors'; import { - getChatDescription, getChatLink, getTopicLink, getHasAdminRight, @@ -47,6 +52,8 @@ type StateProps = isMuted?: boolean; phoneCodeList: ApiCountryCode[]; topicId?: number; + description?: string; + chatInviteLink?: string; } & Pick; @@ -61,6 +68,8 @@ const ChatExtra: FC = ({ isMuted, phoneCodeList, topicId, + description, + chatInviteLink, }) => { const { loadFullUser, @@ -71,7 +80,6 @@ const ChatExtra: FC = ({ const { id: userId, - fullInfo, usernames, phoneNumber, isSelf, @@ -108,8 +116,8 @@ const ChatExtra: FC = ({ return isTopicInfo ? getTopicLink(chat.id, activeChatUsernames?.[0].username, topicId) - : getChatLink(chat); - }, [chat, isTopicInfo, activeChatUsernames, topicId]); + : getChatLink(chat) || chatInviteLink; + }, [chat, isTopicInfo, activeChatUsernames, topicId, chatInviteLink]); const handleNotificationChange = useCallback(() => { setAreNotificationsEnabled((current) => { @@ -141,7 +149,6 @@ const ChatExtra: FC = ({ } const formattedNumber = phoneNumber && formatPhoneNumberWithCode(phoneCodeList, phoneNumber); - const description = (fullInfo?.bio) || getChatDescription(chat); function renderUsernames(usernameList: ApiUsername[], isChat?: boolean) { const [mainUsername, ...otherUsernames] = usernameList; @@ -259,6 +266,11 @@ export default memo(withGlobal( const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)); const { threadId } = selectCurrentMessageList(global) || {}; const topicId = isForum ? threadId : undefined; + const chatInviteLink = chat ? selectChatFullInfo(global, chat.id)?.inviteLink : undefined; + let description = user ? selectUserFullInfo(global, user.id)?.bio : undefined; + if (!description && chat) { + description = selectChatFullInfo(global, chat.id)?.about; + } const canInviteUsers = chat && !user && ( (!isChatChannel(chat) && !isUserRightBanned(chat, 'inviteUsers')) @@ -273,6 +285,8 @@ export default memo(withGlobal( canInviteUsers, isMuted, topicId, + chatInviteLink, + description, }; }, )(ChatExtra)); diff --git a/src/components/common/ChatOrUserPicker.tsx b/src/components/common/ChatOrUserPicker.tsx index 94bdf01e3..0ea375d3a 100644 --- a/src/components/common/ChatOrUserPicker.tsx +++ b/src/components/common/ChatOrUserPicker.tsx @@ -158,7 +158,7 @@ const ChatOrUserPicker: FC = ({ <>
= ({ ariaLabel={lang('Close')} onClick={onClose} > - + = ({ onClose={onClose} onCloseAnimationEnd={onCloseAnimationEnd} > - + {() => { return activeKey === TOPIC_LIST_SLIDE ? renderTopicList() : renderChatList(); }} diff --git a/src/components/common/CustomEmojiPicker.tsx b/src/components/common/CustomEmojiPicker.tsx index 7075dce97..9be2e6fb1 100644 --- a/src/components/common/CustomEmojiPicker.tsx +++ b/src/components/common/CustomEmojiPicker.tsx @@ -320,7 +320,7 @@ const CustomEmojiPicker: FC = ({ onClick={() => selectStickerSet(index)} > {(stickerSet.id === RECENT_SYMBOL_SET_ID || stickerSet.id === POPULAR_SYMBOL_SET_ID) ? ( - + ) : ( = ({

{renderText(senderTitle || title || NBSP)}
- {hasContextMenu && } + {hasContextMenu && }
); }; diff --git a/src/components/common/File.scss b/src/components/common/File.scss index 480944eae..2d551f34d 100644 --- a/src/components/common/File.scss +++ b/src/components/common/File.scss @@ -97,7 +97,7 @@ } &.interactive .file-icon-container { - cursor: pointer; + cursor: var(--custom-cursor, pointer); &:hover { .file-icon::after { diff --git a/src/components/common/File.tsx b/src/components/common/File.tsx index 36d00e2e7..f0d755fe9 100644 --- a/src/components/common/File.tsx +++ b/src/components/common/File.tsx @@ -95,7 +95,7 @@ const File: FC = ({
{isSelectable && (
- {isSelected && } + {isSelected && }
)}
@@ -135,6 +135,7 @@ const File: FC = ({ = ({ pill onClick={handleUnsaveClick} > - + )} {withThumb && ( diff --git a/src/components/common/ManageUsernames.tsx b/src/components/common/ManageUsernames.tsx index f9daba165..95470bd94 100644 --- a/src/components/common/ManageUsernames.tsx +++ b/src/components/common/ManageUsernames.tsx @@ -165,7 +165,7 @@ const ManageUsernames: FC = ({ > = ({ status }) => { {status === 'failed' ? (
- +
- ) : } + ) : }
); diff --git a/src/components/common/PasswordForm.tsx b/src/components/common/PasswordForm.tsx index 272907320..f40a67f55 100644 --- a/src/components/common/PasswordForm.tsx +++ b/src/components/common/PasswordForm.tsx @@ -141,13 +141,13 @@ const PasswordForm: FC = ({ />
- +
{description &&

{description}

} diff --git a/src/components/common/PickerSelectedItem.scss b/src/components/common/PickerSelectedItem.scss index 91867087e..ecb344821 100644 --- a/src/components/common/PickerSelectedItem.scss +++ b/src/components/common/PickerSelectedItem.scss @@ -8,7 +8,7 @@ margin-bottom: 0.5rem; padding-right: 1rem; border-radius: 1rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); position: relative; overflow: hidden; flex-shrink: 1; @@ -68,7 +68,7 @@ flex-shrink: 0; transition: opacity 0.15s ease; - .Avatar__icon, i { + .icon { font-size: 1rem; } } @@ -82,7 +82,7 @@ background-color: var(--color-primary); color: white; - i { + .icon { font-size: 1.25rem; position: relative; top: -1px; diff --git a/src/components/common/PickerSelectedItem.tsx b/src/components/common/PickerSelectedItem.tsx index 9058bcce3..a783d9e8a 100644 --- a/src/components/common/PickerSelectedItem.tsx +++ b/src/components/common/PickerSelectedItem.tsx @@ -2,9 +2,9 @@ import React, { memo } from '../../lib/teact/teact'; import { withGlobal } from '../../global'; import type { FC, TeactNode } from '../../lib/teact/teact'; -import type { ApiChat, ApiUser } from '../../api/types'; +import type { ApiChat, ApiPhoto, ApiUser } from '../../api/types'; -import { selectChat, selectUser } from '../../global/selectors'; +import { selectChat, selectUser, selectUserPhotoFromFullInfo } from '../../global/selectors'; import { getChatTitle, getUserFirstOrLastName, isUserId } from '../../global/helpers'; import renderText from './helpers/renderText'; import buildClassName from '../../util/buildClassName'; @@ -28,6 +28,7 @@ type OwnProps = { type StateProps = { chat?: ApiChat; user?: ApiUser; + userProfilePhoto?: ApiPhoto; currentUserId?: string; }; @@ -39,6 +40,7 @@ const PickerSelectedItem: FC = ({ clickArg, chat, user, + userProfilePhoto, className, currentUserId, onClick, @@ -51,7 +53,7 @@ const PickerSelectedItem: FC = ({ if (icon && title) { iconElement = (
- +
); @@ -61,6 +63,7 @@ const PickerSelectedItem: FC = ({ @@ -96,7 +99,7 @@ const PickerSelectedItem: FC = ({ )} {canClose && (
- +
)}
@@ -111,10 +114,12 @@ export default memo(withGlobal( const chat = chatOrUserId ? selectChat(global, chatOrUserId) : undefined; const user = isUserId(chatOrUserId) ? selectUser(global, chatOrUserId) : undefined; + const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined; return { chat, user, + userProfilePhoto, currentUserId: global.currentUserId, }; }, diff --git a/src/components/common/PremiumIcon.scss b/src/components/common/PremiumIcon.scss index 354c7c42e..059604f77 100644 --- a/src/components/common/PremiumIcon.scss +++ b/src/components/common/PremiumIcon.scss @@ -17,7 +17,7 @@ } &.clickable { - cursor: pointer; + cursor: var(--custom-cursor, pointer); pointer-events: auto; } } diff --git a/src/components/common/PrivateChatInfo.tsx b/src/components/common/PrivateChatInfo.tsx index 3cfd727a1..223af895e 100644 --- a/src/components/common/PrivateChatInfo.tsx +++ b/src/components/common/PrivateChatInfo.tsx @@ -5,13 +5,15 @@ import { getActions, withGlobal } from '../../global'; import type { FC } from '../../lib/teact/teact'; import type { - ApiUser, ApiTypingStatus, ApiUserStatus, ApiChatMember, + ApiUser, ApiTypingStatus, ApiUserStatus, ApiChatMember, ApiPhoto, } from '../../api/types'; import type { GlobalState } from '../../global/types'; import type { AnimationLevel } from '../../types'; import { MediaViewerOrigin } from '../../types'; -import { selectChatMessages, selectUser, selectUserStatus } from '../../global/selectors'; +import { + selectChatMessages, selectUser, selectUserPhotoFromFullInfo, selectUserStatus, +} from '../../global/selectors'; import { getMainUsername, getUserStatus, isUserOnline } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; import renderText from './helpers/renderText'; @@ -45,6 +47,7 @@ type StateProps = { user?: ApiUser; userStatus?: ApiUserStatus; + userProfilePhoto?: ApiPhoto; isSavedMessages?: boolean; animationLevel: AnimationLevel; areMessagesLoaded: boolean; @@ -66,6 +69,7 @@ const PrivateChatInfo: FC = ({ noRtl, user, userStatus, + userProfilePhoto, isSavedMessages, areMessagesLoaded, animationLevel, @@ -171,6 +175,7 @@ const PrivateChatInfo: FC = ({ key={user.id} size={avatarSize} user={user} + userProfilePhoto={userProfilePhoto} isSavedMessages={isSavedMessages} onClick={withMediaViewer ? handleAvatarViewerOpen : undefined} withVideo={withVideoAvatar} @@ -191,11 +196,13 @@ export default memo(withGlobal( const userStatus = selectUserStatus(global, userId); const isSavedMessages = !forceShowSelf && user && user.isSelf; const areMessagesLoaded = Boolean(selectChatMessages(global, userId)); + const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined; return { lastSyncTime, user, userStatus, + userProfilePhoto, isSavedMessages, areMessagesLoaded, animationLevel: global.settings.byKey.animationLevel, diff --git a/src/components/common/ProfileInfo.module.scss b/src/components/common/ProfileInfo.module.scss index 40640ce1f..5c902d80a 100644 --- a/src/components/common/ProfileInfo.module.scss +++ b/src/components/common/ProfileInfo.module.scss @@ -35,7 +35,7 @@ font-size: 0.75rem; color: var(--color-white); opacity: 0.5; - cursor: pointer; + cursor: var(--custom-cursor, pointer); user-select: none; align-items: center; height: 1.5rem; @@ -112,7 +112,7 @@ opacity: 0.25; transition: opacity 0.15s; outline: none !important; - cursor: pointer; + cursor: var(--custom-cursor, pointer); z-index: 1; &:global(:hover), diff --git a/src/components/common/ProfileInfo.scss b/src/components/common/ProfileInfo.scss index 647b168ba..c5f62f036 100644 --- a/src/components/common/ProfileInfo.scss +++ b/src/components/common/ProfileInfo.scss @@ -46,6 +46,6 @@ color: var(--color-white); pointer-events: auto; - cursor: pointer; + cursor: var(--custom-cursor, pointer); } } diff --git a/src/components/common/ProfileInfo.tsx b/src/components/common/ProfileInfo.tsx index a536d7e6e..7323912b3 100644 --- a/src/components/common/ProfileInfo.tsx +++ b/src/components/common/ProfileInfo.tsx @@ -5,7 +5,7 @@ import React, { import { getActions, withGlobal } from '../../global'; import type { - ApiUser, ApiChat, ApiUserStatus, ApiTopic, + ApiUser, ApiChat, ApiUserStatus, ApiTopic, ApiPhoto, } from '../../api/types'; import type { GlobalState } from '../../global/types'; import type { AnimationLevel } from '../../types'; @@ -14,10 +14,18 @@ import { MediaViewerOrigin } from '../../types'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { MEMO_EMPTY_ARRAY } from '../../util/memo'; import { + selectChat, + selectChatFullInfo, + selectCurrentMessageList, selectTabState, - selectChat, selectCurrentMessageList, selectThreadMessagesCount, selectUser, selectUserStatus, + selectThreadMessagesCount, + selectUser, + selectUserFullInfo, + selectUserStatus, } from '../../global/selectors'; -import { getUserStatus, isChatChannel, isUserOnline } from '../../global/helpers'; +import { + getUserStatus, isChatChannel, isUserId, isUserOnline, +} from '../../global/helpers'; import { captureEvents, SwipeDirection } from '../../util/captureEvents'; import buildClassName from '../../util/buildClassName'; import renderText from './helpers/renderText'; @@ -52,6 +60,10 @@ type StateProps = avatarOwnerId?: string; topic?: ApiTopic; messagesCount?: number; + userPersonalPhoto?: ApiPhoto; + userProfilePhoto?: ApiPhoto; + userFallbackPhoto?: ApiPhoto; + chatProfilePhoto?: ApiPhoto; } & Pick; @@ -71,6 +83,10 @@ const ProfileInfo: FC = ({ avatarOwnerId, topic, messagesCount, + userPersonalPhoto, + userProfilePhoto, + userFallbackPhoto, + chatProfilePhoto, }) => { const { loadFullUser, @@ -87,7 +103,7 @@ const ProfileInfo: FC = ({ const prevAvatarOwnerId = usePrevious(avatarOwnerId); const [hasSlideAnimation, setHasSlideAnimation] = useState(true); const slideAnimation = hasSlideAnimation - ? animationLevel >= 1 ? (lang.isRtl ? 'slide-optimized-rtl' : 'slide-optimized') : 'none' + ? animationLevel >= 1 ? (lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized') : 'none' : 'none'; const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0); @@ -216,12 +232,14 @@ const ProfileInfo: FC = ({ const photo = !isSavedMessages && photos.length > 0 ? photos[currentPhotoIndex] : undefined; + const profilePhoto = photo || userPersonalPhoto || userProfilePhoto || chatProfilePhoto || userFallbackPhoto; + return ( = ({ >
{renderPhotoTabs()} - {!forceShowSelf && user?.fullInfo?.personalPhoto && ( + {!forceShowSelf && userPersonalPhoto && (
- {lang(user.fullInfo.personalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')} + {lang(userPersonalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')}
)} - {forceShowSelf && user?.fullInfo?.fallbackPhoto && ( + {forceShowSelf && userFallbackPhoto && (
= ({
{!isLast && ( )} - {lang(user.fullInfo.fallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')} + {lang(userFallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')}
)} @@ -333,6 +351,7 @@ export default memo(withGlobal( (global, { userId, forceShowSelf }): StateProps => { const { connectionState } = global; const user = selectUser(global, userId); + const isPrivate = isUserId(userId); const userStatus = selectUserStatus(global, userId); const chat = selectChat(global, userId); const isSavedMessages = !forceShowSelf && user && user.isSelf; @@ -341,12 +360,18 @@ export default memo(withGlobal( const isForum = chat?.isForum; const { threadId: currentTopicId } = selectCurrentMessageList(global) || {}; const topic = isForum && currentTopicId ? chat?.topics?.[currentTopicId] : undefined; + const userFullInfo = isPrivate ? selectUserFullInfo(global, userId) : undefined; + const chatFullInfo = !isPrivate ? selectChatFullInfo(global, userId) : undefined; return { connectionState, user, userStatus, chat, + userPersonalPhoto: userFullInfo?.personalPhoto, + userProfilePhoto: userFullInfo?.profilePhoto, + userFallbackPhoto: userFullInfo?.fallbackPhoto, + chatProfilePhoto: chatFullInfo?.profilePhoto, isSavedMessages, animationLevel, mediaId, diff --git a/src/components/common/ProfilePhoto.scss b/src/components/common/ProfilePhoto.scss index ddbd89b9a..db95e4b1c 100644 --- a/src/components/common/ProfilePhoto.scss +++ b/src/components/common/ProfilePhoto.scss @@ -1,7 +1,7 @@ .ProfilePhoto { width: 100%; height: 100%; - cursor: pointer; + cursor: var(--custom-cursor, pointer); position: relative; .avatar-media { @@ -24,7 +24,7 @@ justify-content: center; color: var(--color-white); background: linear-gradient(var(--color-white) -125%, var(--color-user)); - cursor: default; + cursor: var(--custom-cursor, default); } &.no-photo { diff --git a/src/components/common/ProfilePhoto.tsx b/src/components/common/ProfilePhoto.tsx index f95dd8c5a..ba00504b4 100644 --- a/src/components/common/ProfilePhoto.tsx +++ b/src/components/common/ProfilePhoto.tsx @@ -56,31 +56,27 @@ const ProfilePhoto: FC = ({ const isDeleted = user && isDeletedUser(user); const isRepliesChat = chat && isChatWithRepliesBot(chat.id); const userOrChat = user || chat; - const currentPhoto = photo - || user?.fullInfo?.personalPhoto - || userOrChat?.fullInfo?.profilePhoto - || user?.fullInfo?.fallbackPhoto; const canHaveMedia = userOrChat && !isSavedMessages && !isDeleted && !isRepliesChat; - const { isVideo } = currentPhoto || {}; + const { isVideo } = photo || {}; const avatarHash = canHaveMedia && getChatAvatarHash(userOrChat, 'normal', 'photo'); const avatarBlobUrl = useMedia(avatarHash, undefined, undefined, lastSyncTime); - const photoHash = canHaveMedia && currentPhoto && !isVideo && `photo${currentPhoto.id}?size=c`; + const photoHash = canHaveMedia && photo && !isVideo && `photo${photo.id}?size=c`; const photoBlobUrl = useMedia(photoHash, undefined, undefined, lastSyncTime); - const videoHash = canHaveMedia && currentPhoto && isVideo && getVideoAvatarMediaHash(currentPhoto); + const videoHash = canHaveMedia && photo && isVideo && getVideoAvatarMediaHash(photo); const videoBlobUrl = useMedia(videoHash, undefined, undefined, lastSyncTime); const fullMediaData = videoBlobUrl || photoBlobUrl; const [isVideoReady, markVideoReady] = useFlag(); const isFullMediaReady = Boolean(fullMediaData && (!isVideo || isVideoReady)); const transitionClassNames = useMediaTransition(isFullMediaReady); - const isBlurredThumb = canHaveMedia && !isFullMediaReady && !avatarBlobUrl && currentPhoto?.thumbnail?.dataUri; + const isBlurredThumb = canHaveMedia && !isFullMediaReady && !avatarBlobUrl && photo?.thumbnail?.dataUri; const blurredThumbCanvasRef = useCanvasBlur( - currentPhoto?.thumbnail?.dataUri, !isBlurredThumb, isMobile && !IS_CANVAS_FILTER_SUPPORTED, + photo?.thumbnail?.dataUri, !isBlurredThumb, isMobile && !IS_CANVAS_FILTER_SUPPORTED, ); - const hasMedia = currentPhoto || avatarBlobUrl || isBlurredThumb; + const hasMedia = photo || avatarBlobUrl || isBlurredThumb; useEffect(() => { if (videoRef.current && !canPlayVideo) { @@ -91,11 +87,11 @@ const ProfilePhoto: FC = ({ let content: TeactNode | undefined; if (isSavedMessages) { - content = ; + content = ; } else if (isDeleted) { - content = ; + content = ; } else if (isRepliesChat) { - content = ; + content = ; } else if (hasMedia) { content = ( <> @@ -104,7 +100,7 @@ const ProfilePhoto: FC = ({ ) : ( )} - {currentPhoto && ( + {photo && ( isVideo ? ( = ({ round onClick={handleGoBack} > - + )}
@@ -80,7 +80,7 @@ const SliderDots: FC = ({ disabled={active === length - 1} onClick={handleGoForward} > - + )}
diff --git a/src/components/common/StickerButton.scss b/src/components/common/StickerButton.scss index 969b1ee85..0a5797de3 100644 --- a/src/components/common/StickerButton.scss +++ b/src/components/common/StickerButton.scss @@ -70,7 +70,7 @@ } &.interactive { - cursor: pointer; + cursor: var(--custom-cursor, pointer); &:hover { background-color: var(--color-interactive-element-hover); @@ -124,7 +124,7 @@ height: 1.25rem; padding: 0.125rem; - i { + .icon { font-size: 1rem; } @@ -134,7 +134,7 @@ .sticker-context-menu { position: absolute; - cursor: default; + cursor: var(--custom-cursor, default); z-index: var(--z-header-menu); .bubble { diff --git a/src/components/common/StickerButton.tsx b/src/components/common/StickerButton.tsx index d033ae493..520ad9cbd 100644 --- a/src/components/common/StickerButton.tsx +++ b/src/components/common/StickerButton.tsx @@ -310,12 +310,12 @@ const StickerButton = - +
)} {!noShowPremium && isPremium && !isLocked && (
- +
)} {shouldShowCloseButton && ( @@ -325,7 +325,7 @@ const StickerButton = - + )} {Boolean(contextMenuItems.length) && ( diff --git a/src/components/common/StickerSet.tsx b/src/components/common/StickerSet.tsx index 3e752d322..dec120566 100644 --- a/src/components/common/StickerSet.tsx +++ b/src/components/common/StickerSet.tsx @@ -244,11 +244,11 @@ const StickerSet: FC = ({ {!shouldHideHeader && (

- {isLocked && } + {isLocked && } {stickerSet.title}

{isRecent && ( - + )} {!isRecent && isEmoji && !isInstalled && !isPopular && ( )} {shouldRender && stickerSet.reactions?.map((reaction) => { diff --git a/src/components/common/StickerSetModal.tsx b/src/components/common/StickerSetModal.tsx index b850453ff..f3e036237 100644 --- a/src/components/common/StickerSetModal.tsx +++ b/src/components/common/StickerSetModal.tsx @@ -161,7 +161,7 @@ const StickerSetModal: FC = ({ onClick={onTrigger} ariaLabel="More actions" > - + ); }, [isMobile]); @@ -170,7 +170,7 @@ const StickerSetModal: FC = ({ return (
{renderingStickerSet ? renderText(renderingStickerSet.title, ['emoji', 'links']) : lang('AccDescrStickerSet')} diff --git a/src/components/common/TopicChip.module.scss b/src/components/common/TopicChip.module.scss index 40c45b6f3..4e3578045 100644 --- a/src/components/common/TopicChip.module.scss +++ b/src/components/common/TopicChip.module.scss @@ -10,7 +10,7 @@ background-color: var(--background-color); border-radius: var(--border-radius-messages); color: var(--topic-button-accent-color); - cursor: pointer; + cursor: var(--custom-cursor, pointer); &::before { content: ""; diff --git a/src/components/common/TopicChip.tsx b/src/components/common/TopicChip.tsx index a0e676e77..d71bcfbc6 100644 --- a/src/components/common/TopicChip.tsx +++ b/src/components/common/TopicChip.tsx @@ -39,8 +39,8 @@ const TopicChip: FC = ({ ? : } {topic?.title ? renderText(topic.title) : lang('Loading')} - {topic?.isClosed && } - + {topic?.isClosed && } +
); }; diff --git a/src/components/common/TopicDefaultIcon.tsx b/src/components/common/TopicDefaultIcon.tsx index 42a5d4508..a1df7065e 100644 --- a/src/components/common/TopicDefaultIcon.tsx +++ b/src/components/common/TopicDefaultIcon.tsx @@ -31,7 +31,10 @@ const TopicDefaultIcon: FC = ({ if (topicId === GENERAL_TOPIC_ID) { return ( - + ); } return ( diff --git a/src/components/common/UserAvatar.tsx b/src/components/common/UserAvatar.tsx new file mode 100644 index 000000000..9335c14bd --- /dev/null +++ b/src/components/common/UserAvatar.tsx @@ -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 = ({ + user, + profilePhoto, + className, + animationLevel, + withVideo, + size, +}) => { + return ( + + ); +}; + +export default memo(withGlobal( + (global, { user }): StateProps => { + return { + profilePhoto: user ? selectUserPhotoFromFullInfo(global, user.id) : undefined, + animationLevel: global.settings.byKey.animationLevel, + }; + }, +)(UserAvatar)); diff --git a/src/components/common/WebLink.scss b/src/components/common/WebLink.scss index 0467ef31b..1cf27a380 100644 --- a/src/components/common/WebLink.scss +++ b/src/components/common/WebLink.scss @@ -33,7 +33,7 @@ top: 0; width: 3rem; height: 3rem; - cursor: default !important; + cursor: var(--custom-cursor, default) !important; padding-bottom: unset !important; border-radius: var(--border-radius-messages-small) !important; } diff --git a/src/components/common/code/CodeOverlay.module.scss b/src/components/common/code/CodeOverlay.module.scss index fac902516..2222e3280 100644 --- a/src/components/common/code/CodeOverlay.module.scss +++ b/src/components/common/code/CodeOverlay.module.scss @@ -16,7 +16,7 @@ border-radius: 0.125rem; margin: 0.125rem; transition: background-color 0.15s ease-in-out; - cursor: pointer; + cursor: var(--custom-cursor, pointer); &:hover, &.wrapOn { background-color: var(--color-background-compact-menu-hover); diff --git a/src/components/common/code/CodeOverlay.tsx b/src/components/common/code/CodeOverlay.tsx index 04cfbb722..26910a1a2 100644 --- a/src/components/common/code/CodeOverlay.tsx +++ b/src/components/common/code/CodeOverlay.tsx @@ -63,12 +63,12 @@ const CodeOverlay: FC = ({
{withWordWrapButton && (
- +
)} {!noCopy && (
- +
)}
diff --git a/src/components/common/spoiler/Spoiler.scss b/src/components/common/spoiler/Spoiler.scss index 65e680344..13d7d8a1d 100644 --- a/src/components/common/spoiler/Spoiler.scss +++ b/src/components/common/spoiler/Spoiler.scss @@ -1,6 +1,6 @@ .Spoiler { &--concealed { - cursor: pointer; + cursor: var(--custom-cursor, pointer); background-image: url('../../../assets/spoiler-dots-black.png'); background-size: auto min(100%, 1.125rem); border-radius: 0.5rem; diff --git a/src/components/left/ArchivedChats.tsx b/src/components/left/ArchivedChats.tsx index 342d5533c..f7004bb17 100644 --- a/src/components/left/ArchivedChats.tsx +++ b/src/components/left/ArchivedChats.tsx @@ -87,7 +87,7 @@ const ArchivedChats: FC = ({ )} onTransitionEnd={handleDropdownMenuTransitionEnd} > - + {shouldRenderTitle &&

{lang('ArchivedChats')}

} {archiveSettings.isHidden && ( diff --git a/src/components/left/ConnectionStatusOverlay.scss b/src/components/left/ConnectionStatusOverlay.scss index 62db6c3a1..52afd22c8 100644 --- a/src/components/left/ConnectionStatusOverlay.scss +++ b/src/components/left/ConnectionStatusOverlay.scss @@ -28,7 +28,7 @@ border-radius: var(--border-radius-default); &.interactive { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } > .Spinner { @@ -57,7 +57,7 @@ // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size min-width: 0; - > .Transition__slide { + > .Transition_slide { display: flex; align-items: center; width: 100%; diff --git a/src/components/left/ConnectionStatusOverlay.tsx b/src/components/left/ConnectionStatusOverlay.tsx index 871c610fe..e62141b53 100644 --- a/src/components/left/ConnectionStatusOverlay.tsx +++ b/src/components/left/ConnectionStatusOverlay.tsx @@ -27,7 +27,7 @@ const ConnectionStatusOverlay: FC = ({
- + {connectionStatusText}
@@ -36,7 +36,7 @@ const ConnectionStatusOverlay: FC = ({ size="tiny" color="translucent-black" > - +
); diff --git a/src/components/left/NewChatButton.tsx b/src/components/left/NewChatButton.tsx index 95abdca43..c33f81cb5 100644 --- a/src/components/left/NewChatButton.tsx +++ b/src/components/left/NewChatButton.tsx @@ -67,8 +67,8 @@ const NewChatButton: FC = ({ ariaLabel={lang(isMenuOpen ? 'Close' : 'NewMessageTitle')} tabIndex={-1} > - - + + = ({

- + {lang('ArchivedChats')}

@@ -122,7 +122,7 @@ const Archive: FC = ({ <>
- +
diff --git a/src/components/left/main/Badge.scss b/src/components/left/main/Badge.scss index 23c21f52d..fb2de27fa 100644 --- a/src/components/left/main/Badge.scss +++ b/src/components/left/main/Badge.scss @@ -70,7 +70,7 @@ width: 1.5rem; padding: 0; - i { + .icon { font-size: 1.5rem; } } @@ -83,7 +83,7 @@ width: 1.5rem; padding: 0.25rem; - i { + .icon { font-size: 1rem; vertical-align: super; } @@ -92,7 +92,7 @@ width: 1.375rem; padding: 0.25rem; - i { + .icon { font-size: 0.875rem; } } diff --git a/src/components/left/main/Badge.tsx b/src/components/left/main/Badge.tsx index e56781c01..523356b75 100644 --- a/src/components/left/main/Badge.tsx +++ b/src/components/left/main/Badge.tsx @@ -67,13 +67,13 @@ const Badge: FC = ({ function renderContent() { const unreadReactionsElement = unreadReactionsCount && (
- +
); const unreadMentionsElement = unreadMentionsCount && (
- +
); @@ -89,7 +89,7 @@ const Badge: FC = ({ const pinnedElement = isPinned && !unreadCountElement && !unreadMentionsElement && !unreadReactionsElement && (
- +
); diff --git a/src/components/left/main/Chat.tsx b/src/components/left/main/Chat.tsx index cc288028f..a97b639cf 100644 --- a/src/components/left/main/Chat.tsx +++ b/src/components/left/main/Chat.tsx @@ -5,13 +5,14 @@ import type { FC } from '../../../lib/teact/teact'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ApiChat, - ApiUser, + ApiFormattedText, ApiMessage, ApiMessageOutgoingStatus, - ApiFormattedText, - ApiUserStatus, + ApiPhoto, ApiTopic, ApiTypingStatus, + ApiUser, + ApiUserStatus, } from '../../../api/types'; import type { AnimationLevel } from '../../../types'; import type { ChatAnimationTypes } from './hooks'; @@ -19,23 +20,25 @@ import type { ChatAnimationTypes } from './hooks'; import { MAIN_THREAD_ID } from '../../../api/types'; import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment'; import { - isUserId, - getPrivateChatUserId, getMessageAction, + getPrivateChatUserId, + isUserId, selectIsChatMuted, } from '../../../global/helpers'; import { selectChat, - selectUser, selectChatMessage, - selectOutgoingStatus, - selectDraft, selectCurrentMessageList, - selectNotifySettings, + selectDraft, selectNotifyExceptions, - selectUserStatus, + selectNotifySettings, + selectOutgoingStatus, + selectTabState, + selectThreadParam, selectTopicFromMessage, - selectThreadParam, selectTabState, + selectUser, + selectUserPhotoFromFullInfo, + selectUserStatus, } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import { createLocationHash } from '../../../util/routing'; @@ -75,6 +78,7 @@ type StateProps = { isMuted?: boolean; user?: ApiUser; userStatus?: ApiUserStatus; + userProfilePhoto?: ApiPhoto; actionTargetUserIds?: string[]; actionTargetMessage?: ApiMessage; actionTargetChatId?: string; @@ -102,6 +106,7 @@ const Chat: FC = ({ isMuted, user, userStatus, + userProfilePhoto, actionTargetUserIds, lastMessageSender, lastMessageOutgoingStatus, @@ -235,6 +240,7 @@ const Chat: FC = ({ = ({ isSavedMessages={chatId === user?.id && user?.isSelf} observeIntersection={observeIntersection} /> - {isMuted && } + {isMuted && }
{chat.lastMessage && ( ( const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined; const userStatus = privateChatUserId ? selectUserStatus(global, privateChatUserId) : undefined; + const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined; const lastMessageTopic = chat.lastMessage && selectTopicFromMessage(global, chat.lastMessage); const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus'); @@ -347,6 +354,7 @@ export default memo(withGlobal( }), user, userStatus, + userProfilePhoto, lastMessageTopic, typingStatus, }; diff --git a/src/components/left/main/ChatCallStatus.scss b/src/components/left/main/ChatCallStatus.module.scss similarity index 52% rename from src/components/left/main/ChatCallStatus.scss rename to src/components/left/main/ChatCallStatus.module.scss index c93f279f6..c16bf3029 100644 --- a/src/components/left/main/ChatCallStatus.scss +++ b/src/components/left/main/ChatCallStatus.module.scss @@ -1,4 +1,3 @@ - @keyframes bar-animation-transform-1 { 0% { transform: scaleY(0.33); } 12.5% { transform: scaleY(1.66); } @@ -24,7 +23,7 @@ } -.ChatCallStatus { +.root { position: absolute; right: 6px; bottom: 0; @@ -35,47 +34,51 @@ border: 2px solid var(--color-background); overflow: hidden; z-index: 1; - - .indicator { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - & > div { - width: 2px; - height: 6px; - background: var(--color-background); - border-radius: 1px; - margin: 1px; - will-change: transform; - transform: translateZ(0); - } - & > div:nth-child(odd) { - transform: scaleY(0.8); - } - & > div:nth-child(even) { - transform: scaleY(1.33); - } - } - - &.selected { - background-color: var(--color-white); - border-color: var(--color-chat-active); - .indicator div{ - background-color: var(--color-chat-active); - } - } - - &.active .indicator { - div:nth-child(odd) { - animation: bar-animation-transform-2 3.2s normal infinite; - } - - div:nth-child(even) { - animation: bar-animation-transform-1 3.2s normal infinite; - } - } - +} + + +.indicator { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; +} + +.indicatorInner { + width: 2px; + height: 6px; + background: var(--color-background); + border-radius: 1px; + margin: 1px; + will-change: transform; + transform: translateZ(0); +} + +.indicatorInner:nth-child(odd) { + transform: scaleY(0.8); +} + +.indicatorInner:nth-child(even) { + transform: scaleY(1.33); +} + +.selected { + background-color: var(--color-white); + border-color: var(--color-chat-active); + + .indicatorInner { + background-color: var(--color-chat-active); + } +} + +.active { + .indicatorInner:nth-child(odd) { + animation: bar-animation-transform-2 3.2s normal infinite; + } + + .indicatorInner:nth-child(even) { + animation: bar-animation-transform-1 3.2s normal infinite; + } } diff --git a/src/components/left/main/ChatCallStatus.tsx b/src/components/left/main/ChatCallStatus.tsx index 176b04144..aab71f96a 100644 --- a/src/components/left/main/ChatCallStatus.tsx +++ b/src/components/left/main/ChatCallStatus.tsx @@ -2,7 +2,7 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo } from '../../../lib/teact/teact'; import buildClassName from '../../../util/buildClassName'; -import './ChatCallStatus.scss'; +import styles from './ChatCallStatus.module.scss'; type OwnProps = { isSelected?: boolean; @@ -17,15 +17,15 @@ const ChatCallStatus: FC = ({ }) => { return (
-
-
-
-
+
+
+
+
); diff --git a/src/components/left/main/ChatFolders.tsx b/src/components/left/main/ChatFolders.tsx index a5ed269d4..b7aa14452 100644 --- a/src/components/left/main/ChatFolders.tsx +++ b/src/components/left/main/ChatFolders.tsx @@ -242,7 +242,7 @@ const ChatFolders: FC = ({ ) : undefined} diff --git a/src/components/left/main/ContactList.tsx b/src/components/left/main/ContactList.tsx index 18a5eb195..e9c984826 100644 --- a/src/components/left/main/ContactList.tsx +++ b/src/components/left/main/ContactList.tsx @@ -92,7 +92,7 @@ const ContactList: FC = ({ onClick={openNewContactDialog} ariaLabel={lang('CreateNewContact')} > - + ); diff --git a/src/components/left/main/EmptyFolder.module.scss b/src/components/left/main/EmptyFolder.module.scss index f15821377..7df717f6c 100644 --- a/src/components/left/main/EmptyFolder.module.scss +++ b/src/components/left/main/EmptyFolder.module.scss @@ -48,7 +48,7 @@ white-space: nowrap; } - i { + :global(.icon) { margin-inline-end: 0.625rem; font-size: 1.5rem; } diff --git a/src/components/left/main/EmptyFolder.tsx b/src/components/left/main/EmptyFolder.tsx index d13d07766..6a12c3d81 100644 --- a/src/components/left/main/EmptyFolder.tsx +++ b/src/components/left/main/EmptyFolder.tsx @@ -58,7 +58,7 @@ const EmptyFolder: FC = ({ size="smaller" isRtl={lang.isRtl} > - +
{lang('ChatList.EmptyChatListEditFilter')}
diff --git a/src/components/left/main/EmptyForum.module.scss b/src/components/left/main/EmptyForum.module.scss index c264f801c..791ce8405 100644 --- a/src/components/left/main/EmptyForum.module.scss +++ b/src/components/left/main/EmptyForum.module.scss @@ -25,7 +25,7 @@ white-space: nowrap; } - i { + :global(.icon) { margin-inline-end: 0.625rem; font-size: 1.5rem; } diff --git a/src/components/left/main/ForumPanel.module.scss b/src/components/left/main/ForumPanel.module.scss index 3db5fc95f..a458d6a63 100644 --- a/src/components/left/main/ForumPanel.module.scss +++ b/src/components/left/main/ForumPanel.module.scss @@ -72,7 +72,7 @@ margin-left: 0.4375rem; min-width: 0; width: 100%; - cursor: pointer; + cursor: var(--custom-cursor, pointer); :global(.info) { display: flex; diff --git a/src/components/left/main/ForumPanel.tsx b/src/components/left/main/ForumPanel.tsx index 9c110bf79..913a339b2 100644 --- a/src/components/left/main/ForumPanel.tsx +++ b/src/components/left/main/ForumPanel.tsx @@ -224,7 +224,7 @@ const ForumPanel: FC = ({ onClick={handleClose} ariaLabel={lang('Close')} > - + {chat && ( diff --git a/src/components/left/main/LeftMain.tsx b/src/components/left/main/LeftMain.tsx index fa3db0375..bc145e497 100644 --- a/src/components/left/main/LeftMain.tsx +++ b/src/components/left/main/LeftMain.tsx @@ -167,7 +167,7 @@ const LeftMain: FC = ({ isClosingSearch={isClosingSearch} /> ; isMessageListOpen: boolean; isCurrentUserPremium?: boolean; isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized']; @@ -79,7 +76,8 @@ type StateProps = hasPasscode?: boolean; isAuthRememberMe?: boolean; } - & Pick & Pick; + & Pick + & Pick; const ANIMATION_LEVEL_OPTIONS = [0, 1, 2]; const WEBK_VERSION_URL = 'https://web.telegram.org/k/'; @@ -103,7 +101,6 @@ const LeftMainHeader: FC = ({ searchDate, theme, animationLevel, - chatsById, connectionState, isSyncing, isMessageListOpen, @@ -136,19 +133,8 @@ const LeftMainHeader: FC = ({ ? formatDateToString(new Date(searchDate * 1000)) : undefined; }, [searchDate]); - const archivedUnreadChatsCount = useMemo(() => { - if (!hasMenu || !chatsById) { - return 0; - } - return Object.values(chatsById).reduce((total, chat) => { - if (!isChatArchived(chat)) { - return total; - } - - return chat.unreadCount ? total + 1 : total; - }, 0); - }, [hasMenu, chatsById]); + const archivedUnreadChatsCount = useFolderManagerForUnreadCounters()[ARCHIVED_FOLDER_ID]?.chatsCount || 0; const { connectionStatus, connectionStatusText, connectionStatusPosition } = useConnectionStatus( lang, connectionState, isSyncing, isMessageListOpen, isConnectionStatusMinimized, !areChatsLoaded, @@ -215,8 +201,7 @@ const LeftMainHeader: FC = ({ setSettingOption({ theme: newTheme }); setSettingOption({ shouldUseSystemTheme: false }); - switchTheme(newTheme, animationLevel === ANIMATION_LEVEL_MAX); - }, [animationLevel, setSettingOption, theme]); + }, [setSettingOption, theme]); const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent) => { e.stopPropagation(); @@ -347,7 +332,8 @@ const LeftMainHeader: FC = ({ )} {withOtherVersions && ( @@ -437,7 +423,7 @@ const LeftMainHeader: FC = ({ onClick={handleLockScreen} className={buildClassName(!isCurrentUserPremium && 'extra-spacing')} > - + )} ( const { currentUserId, connectionState, isSyncing, archiveSettings, } = global; - const { byId: chatsById } = global.chats; const { isConnectionStatusMinimized, animationLevel } = global.settings.byKey; return { searchQuery, isLoading: fetchingStatus ? Boolean(fetchingStatus.chats || fetchingStatus.messages) : false, currentUserId, - chatsById, globalSearchChatId: chatId, searchDate: date, theme: selectTheme(global), diff --git a/src/components/left/main/Topic.tsx b/src/components/left/main/Topic.tsx index f2e3a68cd..47b935425 100644 --- a/src/components/left/main/Topic.tsx +++ b/src/components/left/main/Topic.tsx @@ -156,10 +156,11 @@ const Topic: FC = ({

{renderText(topic.title)}

- {topic.isMuted && } + {topic.isMuted && }
{isClosed && ( - {getMessageVideo(message) && } + {getMessageVideo(message) && } {messageSummary} ); diff --git a/src/components/left/newChat/NewChatStep1.tsx b/src/components/left/newChat/NewChatStep1.tsx index 364799b60..b12da0f35 100644 --- a/src/components/left/newChat/NewChatStep1.tsx +++ b/src/components/left/newChat/NewChatStep1.tsx @@ -102,7 +102,7 @@ const NewChatStep1: FC = ({ onClick={onReset} ariaLabel="Return to Chat List" > - +

{lang('GroupAddMembers')}

@@ -123,7 +123,7 @@ const NewChatStep1: FC = ({ onClick={handleNextStep} ariaLabel={isChannel ? 'Continue To Channel Info' : 'Continue To Group Info'} > - +
diff --git a/src/components/left/newChat/NewChatStep2.tsx b/src/components/left/newChat/NewChatStep2.tsx index 204cd9bb4..f376e281b 100644 --- a/src/components/left/newChat/NewChatStep2.tsx +++ b/src/components/left/newChat/NewChatStep2.tsx @@ -133,7 +133,7 @@ const NewChatStep2: FC = ({ onClick={() => onReset()} ariaLabel="Return to member selection" > - +

{lang(isChannel ? 'NewChannel' : 'NewGroup')}

@@ -187,7 +187,7 @@ const NewChatStep2: FC = ({ {isLoading ? ( ) : ( - + )}
diff --git a/src/components/left/search/ChatMessage.tsx b/src/components/left/search/ChatMessage.tsx index 701f4ac2b..de86abd04 100644 --- a/src/components/left/search/ChatMessage.tsx +++ b/src/components/left/search/ChatMessage.tsx @@ -3,7 +3,7 @@ import React, { memo, useCallback } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { - ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus, + ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus, ApiPhoto, } from '../../../api/types'; import type { AnimationLevel } from '../../../types'; import type { LangFn } from '../../../hooks/useLang'; @@ -17,7 +17,7 @@ import { getMessageSticker, getMessageIsSpoiler, } from '../../../global/helpers'; -import { selectChat, selectUser } from '../../../global/selectors'; +import { selectChat, selectUser, selectUserPhotoFromFullInfo } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import { formatPastTimeShort } from '../../../util/dateFormat'; import { renderMessageSummary } from '../../common/helpers/renderMessageText'; @@ -43,6 +43,7 @@ type OwnProps = { type StateProps = { chat?: ApiChat; privateChatUser?: ApiUser; + userProfilePhoto?: ApiPhoto; lastMessageOutgoingStatus?: ApiMessageOutgoingStatus; lastSyncTime?: number; animationLevel?: AnimationLevel; @@ -54,6 +55,7 @@ const ChatMessage: FC = ({ chatId, chat, privateChatUser, + userProfilePhoto, animationLevel, lastSyncTime, }) => { @@ -86,6 +88,7 @@ const ChatMessage: FC = ({ - {getMessageVideo(message) && } + {getMessageVideo(message) && } {renderMessageSummary(lang, message, true, searchQuery)} ); @@ -147,12 +150,17 @@ export default memo(withGlobal( } const privateChatUserId = getPrivateChatUserId(chat); + const privateChatUser = privateChatUserId ? selectUser(global, privateChatUserId) : undefined; + const userProfilePhoto = privateChatUser ? selectUserPhotoFromFullInfo(global, privateChatUser.id) : undefined; return { chat, lastSyncTime: global.lastSyncTime, animationLevel: global.settings.byKey.animationLevel, - ...(privateChatUserId && { privateChatUser: selectUser(global, privateChatUserId) }), + ...(privateChatUserId && { + privateChatUser, + userProfilePhoto, + }), }; }, )(ChatMessage)); diff --git a/src/components/left/search/DateSuggest.scss b/src/components/left/search/DateSuggest.scss index 2c3ca7295..ce5d30c52 100644 --- a/src/components/left/search/DateSuggest.scss +++ b/src/components/left/search/DateSuggest.scss @@ -11,7 +11,7 @@ flex: 1 1 auto; min-width: 8rem; margin-top: 0.375rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); font-size: 0.875rem; font-weight: 500; color: var(--color-text-secondary); diff --git a/src/components/left/search/DateSuggest.tsx b/src/components/left/search/DateSuggest.tsx index 936c84d89..2be4092ec 100644 --- a/src/components/left/search/DateSuggest.tsx +++ b/src/components/left/search/DateSuggest.tsx @@ -24,7 +24,7 @@ const DateSuggest: FC = ({ className="date-item" key={text} > - + {text}
); diff --git a/src/components/left/search/LeftSearch.tsx b/src/components/left/search/LeftSearch.tsx index d6b22d698..7c5879f94 100644 --- a/src/components/left/search/LeftSearch.tsx +++ b/src/components/left/search/LeftSearch.tsx @@ -91,7 +91,7 @@ const LeftSearch: FC = ({
diff --git a/src/components/left/search/RecentContacts.scss b/src/components/left/search/RecentContacts.scss index 224df0f50..0c43ff2e5 100644 --- a/src/components/left/search/RecentContacts.scss +++ b/src/components/left/search/RecentContacts.scss @@ -33,7 +33,7 @@ padding: 0.625rem 0.25rem; margin-left: 0.5rem; border-radius: var(--border-radius-default); - cursor: pointer; + cursor: var(--custom-cursor, pointer); position: relative; overflow: hidden; diff --git a/src/components/left/search/RecentContacts.tsx b/src/components/left/search/RecentContacts.tsx index 643b9cdae..5666f311e 100644 --- a/src/components/left/search/RecentContacts.tsx +++ b/src/components/left/search/RecentContacts.tsx @@ -5,7 +5,6 @@ import React, { import { getActions, withGlobal } from '../../../global'; import type { ApiUser } from '../../../api/types'; -import type { AnimationLevel } from '../../../types'; import { getUserFirstOrLastName } from '../../../global/helpers'; import renderText from '../../common/helpers/renderText'; @@ -13,9 +12,9 @@ import { throttle } from '../../../util/schedulers'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useLang from '../../../hooks/useLang'; -import Avatar from '../../common/Avatar'; import Button from '../../ui/Button'; import LeftSearchResultChat from './LeftSearchResultChat'; +import UserAvatar from '../../common/UserAvatar'; import './RecentContacts.scss'; @@ -27,7 +26,6 @@ type StateProps = { topUserIds?: string[]; usersById: Record; recentlyFoundChatIds?: string[]; - animationLevel: AnimationLevel; }; const SEARCH_CLOSE_TIMEOUT_MS = 250; @@ -39,7 +37,6 @@ const RecentContacts: FC = ({ topUserIds, usersById, recentlyFoundChatIds, - animationLevel, onReset, }) => { const { @@ -80,8 +77,13 @@ const RecentContacts: FC = ({
{topUserIds.map((userId) => ( -
handleClick(userId)} dir={lang.isRtl ? 'rtl' : undefined}> - +
handleClick(userId)} + dir={lang.isRtl ? 'rtl' : undefined} + > +
{renderText(getUserFirstOrLastName(usersById[userId]) || NBSP)}
))} @@ -101,7 +103,7 @@ const RecentContacts: FC = ({ onClick={handleClearRecentlyFoundChats} isRtl={lang.isRtl} > - + {recentlyFoundChatIds.map((id) => ( @@ -121,13 +123,11 @@ export default memo(withGlobal( const { userIds: topUserIds } = global.topPeers; const usersById = global.users.byId; const { recentlyFoundChatIds } = global; - const { animationLevel } = global.settings.byKey; return { topUserIds, usersById, recentlyFoundChatIds, - animationLevel, }; }, )(RecentContacts)); diff --git a/src/components/left/settings/Settings.scss b/src/components/left/settings/Settings.scss index 55e49c581..4c895ac94 100644 --- a/src/components/left/settings/Settings.scss +++ b/src/components/left/settings/Settings.scss @@ -3,7 +3,7 @@ #Settings { height: 100%; - > .Transition__slide { + > .Transition_slide { display: flex; flex-direction: column; overflow: hidden; @@ -98,7 +98,7 @@ .settings-content-icon { margin-bottom: 2.5rem; - &.opacity-transition:not(.shown) { + &.opacity-transition.not-shown { display: block; visibility: hidden; } @@ -242,7 +242,7 @@ .ListItem-button { color: var(--color-error); - i { + .icon { color: inherit; } } diff --git a/src/components/left/settings/SettingsActiveSession.tsx b/src/components/left/settings/SettingsActiveSession.tsx index 42e2f379e..7b4597ba5 100644 --- a/src/components/left/settings/SettingsActiveSession.tsx +++ b/src/components/left/settings/SettingsActiveSession.tsx @@ -62,7 +62,7 @@ const SettingsActiveSession: FC = ({ return (
{lang('SessionPreview.Title')}
{lang('WebSessionsTitle')}
); }, [isMobile]); @@ -237,7 +237,7 @@ const SettingsHeader: FC = ({ onClick={onSaveFilter} ariaLabel={lang('AutoDeleteConfirm')} > - +
); @@ -259,7 +259,7 @@ const SettingsHeader: FC = ({ onClick={() => onScreenSelect(SettingsScreens.EditProfile)} ariaLabel={lang('lng_settings_information')} > - + = ({ onClick={onReset} ariaLabel={lang('AccDescrGoBack')} > - + {renderHeaderContent()} = ({ narrow disabled={!isCurrentUserPremium} allowDisabledClick - rightElement={!isCurrentUserPremium && } + rightElement={!isCurrentUserPremium && } className="no-icon" onClick={handleVoiceMessagesClick} > diff --git a/src/components/left/settings/SettingsPrivacyBlockedUsers.tsx b/src/components/left/settings/SettingsPrivacyBlockedUsers.tsx index 6b482d3cd..48e718224 100644 --- a/src/components/left/settings/SettingsPrivacyBlockedUsers.tsx +++ b/src/components/left/settings/SettingsPrivacyBlockedUsers.tsx @@ -1,12 +1,13 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; -import { getActions, withGlobal } from '../../../global'; +import { getActions, getGlobal, withGlobal } from '../../../global'; import type { ApiChat, ApiCountryCode, ApiUser } from '../../../api/types'; import { CHAT_HEIGHT_PX } from '../../../config'; import { formatPhoneNumberWithCode } from '../../../util/phoneNumber'; import { getMainUsername, isUserId } from '../../../global/helpers'; +import { selectUserPhotoFromFullInfo } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import useLang from '../../../hooks/useLang'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -81,7 +82,7 @@ const SettingsPrivacyBlockedUsers: FC = ({ return ( = ({ }]} style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`} > - +
{userOrChat && } {user?.phoneNumber && ( @@ -133,7 +139,7 @@ const SettingsPrivacyBlockedUsers: FC = ({ onClick={openBlockUserModal} ariaLabel={lang('BlockContact')} > - + = ({ currentUser, + hasCurrentUserFullInfo, + currentUserFallbackPhoto, }) => { const { loadFullUser, uploadProfilePhoto, deleteProfilePhoto, showNotification, @@ -29,17 +33,16 @@ const SettingsPrivacyPublicProfilePhoto: FC = ({ const lang = useLang(); - const fallbackPhoto = currentUser.fullInfo?.fallbackPhoto; const [isDeleteFallbackPhotoModalOpen, openDeleteFallbackPhotoModal, closeDeleteFallbackPhotoModal] = useFlag(false); // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); useEffect(() => { - if (!currentUser.fullInfo) { + if (!hasCurrentUserFullInfo) { loadFullUser({ userId: currentUser.id }); } - }, [currentUser.fullInfo, currentUser.id, loadFullUser]); + }, [hasCurrentUserFullInfo, currentUser.id, loadFullUser]); const handleSelectFile = useCallback((file: File) => { uploadProfilePhoto({ @@ -53,8 +56,8 @@ const SettingsPrivacyPublicProfilePhoto: FC = ({ const handleConfirmDelete = useCallback(() => { closeDeleteFallbackPhotoModal(); - deleteProfilePhoto({ photo: fallbackPhoto! }); - }, [closeDeleteFallbackPhotoModal, deleteProfilePhoto, fallbackPhoto]); + deleteProfilePhoto({ photo: currentUserFallbackPhoto! }); + }, [closeDeleteFallbackPhotoModal, deleteProfilePhoto, currentUserFallbackPhoto]); const handleOpenFileSelector = useCallback(() => { inputRef.current?.click(); @@ -70,15 +73,17 @@ const SettingsPrivacyPublicProfilePhoto: FC = ({ onChange={handleSelectFile} inputRef={inputRef} /> - {lang(fallbackPhoto ? 'Privacy.ProfilePhoto.UpdatePublicPhoto' : 'Privacy.ProfilePhoto.SetPublicPhoto')} + {lang(currentUserFallbackPhoto + ? 'Privacy.ProfilePhoto.UpdatePublicPhoto' + : 'Privacy.ProfilePhoto.SetPublicPhoto')} - {fallbackPhoto && ( + {currentUserFallbackPhoto && ( } + leftElement={} onClick={openDeleteFallbackPhotoModal} destructive > - {lang(fallbackPhoto.isVideo + {lang(currentUserFallbackPhoto.isVideo ? 'Privacy.ProfilePhoto.RemovePublicVideo' : 'Privacy.ProfilePhoto.RemovePublicPhoto')} ; usersById?: Record; currentUser: ApiUser; + hasCurrentUserFullInfo?: boolean; + currentUserFallbackPhoto?: ApiPhoto; }; const SettingsPrivacyVisibility: FC = ({ @@ -41,6 +43,8 @@ const SettingsPrivacyVisibility: FC = ({ blockChatIds, chatsById, currentUser, + hasCurrentUserFullInfo, + currentUserFallbackPhoto, }) => { const { setPrivacyVisibility } = getActions(); @@ -231,7 +235,11 @@ const SettingsPrivacyVisibility: FC = ({
{screen === SettingsScreens.PrivacyProfilePhoto && exceptionLists.shouldShowAllowed && ( - + )}
); @@ -247,6 +255,7 @@ export default memo(withGlobal( } = global; const currentUser = selectUser(global, global.currentUserId!)!; + const currentUserFullInfo = selectUserFullInfo(global, global.currentUserId!); switch (screen) { case SettingsScreens.PrivacyPhoneNumber: @@ -285,6 +294,8 @@ export default memo(withGlobal( if (!privacySettings) { return { currentUser, + hasCurrentUserFullInfo: Boolean(currentUserFullInfo), + currentUserFallbackPhoto: currentUserFullInfo?.fallbackPhoto, }; } @@ -292,6 +303,8 @@ export default memo(withGlobal( ...privacySettings, chatsById, currentUser, + hasCurrentUserFullInfo: Boolean(currentUserFullInfo), + currentUserFallbackPhoto: currentUserFullInfo?.fallbackPhoto, }; }, )(SettingsPrivacyVisibility)); diff --git a/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx b/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx index cb8c1a42f..a3137ddcb 100644 --- a/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx +++ b/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx @@ -116,7 +116,7 @@ const SettingsPrivacyVisibilityExceptionList: FC = ({ onClick={handleSubmit} ariaLabel={isAllowList ? lang('AlwaysAllow') : lang('NeverAllow')} > - +
); diff --git a/src/components/left/settings/WallpaperTile.scss b/src/components/left/settings/WallpaperTile.scss index 7ded53f00..a971a7e00 100644 --- a/src/components/left/settings/WallpaperTile.scss +++ b/src/components/left/settings/WallpaperTile.scss @@ -1,7 +1,7 @@ .WallpaperTile { height: 0; padding-bottom: 100%; - cursor: pointer; + cursor: var(--custom-cursor, pointer); position: relative; .media-inner, diff --git a/src/components/left/settings/folders/SettingsFolders.scss b/src/components/left/settings/folders/SettingsFolders.scss index 47f991eaa..259d0a523 100644 --- a/src/components/left/settings/folders/SettingsFolders.scss +++ b/src/components/left/settings/folders/SettingsFolders.scss @@ -50,7 +50,7 @@ } .ListItem-button { - i { + .icon { opacity: 0.9; } } @@ -58,7 +58,7 @@ &.color-primary .ListItem-button { color: var(--color-primary); - i { + .icon { opacity: 1; color: inherit; } diff --git a/src/components/left/settings/folders/SettingsFoldersChatsPicker.tsx b/src/components/left/settings/folders/SettingsFoldersChatsPicker.tsx index fa6a03f01..336f4dc30 100644 --- a/src/components/left/settings/folders/SettingsFoldersChatsPicker.tsx +++ b/src/components/left/settings/folders/SettingsFoldersChatsPicker.tsx @@ -11,6 +11,7 @@ import { INCLUDED_CHAT_TYPES, EXCLUDED_CHAT_TYPES, } from '../../../../hooks/reducers/useFoldersReducer'; +import buildClassName from '../../../../util/buildClassName'; import useInfiniteScroll from '../../../../hooks/useInfiniteScroll'; import useLang from '../../../../hooks/useLang'; import { selectCurrentLimit } from '../../../../global/selectors/limits'; @@ -133,7 +134,7 @@ const SettingsFoldersChatsPicker: FC = ({ onClick={() => handleChatTypeClick(type.key)} ripple > - +

{lang(type.title)}

= ({ {state.isLoading ? ( ) : ( - + )}
diff --git a/src/components/left/settings/folders/SettingsFoldersMain.tsx b/src/components/left/settings/folders/SettingsFoldersMain.tsx index 47a5fcbef..7f11c60e3 100644 --- a/src/components/left/settings/folders/SettingsFoldersMain.tsx +++ b/src/components/left/settings/folders/SettingsFoldersMain.tsx @@ -212,7 +212,7 @@ const SettingsFoldersMain: FC = ({ onClick={handleCreateFolder} isRtl={lang.isRtl} > - + {lang('CreateNewFilter')} )} @@ -241,7 +241,7 @@ const SettingsFoldersMain: FC = ({ > = ({ isDisabled={isBlocked || !isActive} > = ({ > {renderText(folder.title, ['emoji'])} - {isBlocked && } + {isBlocked && } {folder.subtitle} diff --git a/src/components/main/Dialogs.tsx b/src/components/main/Dialogs.tsx index 37a5b8fa9..f056aa466 100644 --- a/src/components/main/Dialogs.tsx +++ b/src/components/main/Dialogs.tsx @@ -52,7 +52,7 @@ const Dialogs: FC = ({ dialogs, animationLevel }) => { {renderText(title)}
); diff --git a/src/components/main/DownloadManager.tsx b/src/components/main/DownloadManager.tsx index 51ba1415a..c37d6b675 100644 --- a/src/components/main/DownloadManager.tsx +++ b/src/components/main/DownloadManager.tsx @@ -2,7 +2,7 @@ import type { FC } from '../../lib/teact/teact'; import { memo, useCallback, useEffect } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; -import type { Thread } from '../../global/types'; +import type { GlobalState, TabState } from '../../global/types'; import type { ApiMessage } from '../../api/types'; import { ApiMediaFormat } from '../../api/types'; @@ -17,11 +17,8 @@ import { import useRunDebounced from '../../hooks/useRunDebounced'; type StateProps = { - activeDownloads: Record; - messages: Record; - threadsById: Record; - }>; + activeDownloads: TabState['activeDownloads']['byChatId']; + messages?: GlobalState['messages']['byChatId']; }; const GLOBAL_UPDATE_DEBOUNCE = 1000; @@ -49,7 +46,7 @@ const DownloadManager: FC = ({ useEffect(() => { const activeMessages = Object.entries(activeDownloads).map(([chatId, messageIds]) => ( - messageIds.map((id) => messages[chatId].byId[id]) + messageIds.map((id) => messages![chatId].byId[id]) )).flat(); if (!activeMessages.length) { @@ -115,10 +112,11 @@ const DownloadManager: FC = ({ export default memo(withGlobal( (global): StateProps => { const activeDownloads = selectTabState(global).activeDownloads.byChatId; - const messages = global.messages.byChatId; + const hasActiveDownloads = Object.values(activeDownloads).some((messageIds) => messageIds.length); + return { activeDownloads, - messages, + messages: hasActiveDownloads ? global.messages.byChatId : undefined, }; }, )(DownloadManager)); diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 8bbe7d8d0..a06a624c9 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -374,7 +374,7 @@ const Main: FC = ({ useLayoutEffect(() => { const container = containerRef.current!; if (container.parentNode!.childElementCount === 1) { - addExtraClass(container, 'Transition__slide--active'); + addExtraClass(container, 'Transition_slide-active'); } }, []); diff --git a/src/components/main/NewContactModal.tsx b/src/components/main/NewContactModal.tsx index 9417b0c0f..bf5d219b5 100644 --- a/src/components/main/NewContactModal.tsx +++ b/src/components/main/NewContactModal.tsx @@ -4,11 +4,13 @@ import React, { } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; -import type { ApiCountryCode, ApiUser, ApiUserStatus } from '../../api/types'; +import type { + ApiCountryCode, ApiPhoto, ApiUser, ApiUserStatus, +} from '../../api/types'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { getUserStatus } from '../../global/helpers'; -import { selectUser, selectUserStatus } from '../../global/selectors'; +import { selectUser, selectUserPhotoFromFullInfo, selectUserStatus } from '../../global/selectors'; import renderText from '../common/helpers/renderText'; import { formatPhoneNumberWithCode } from '../../util/phoneNumber'; import useLang from '../../hooks/useLang'; @@ -34,6 +36,7 @@ export type OwnProps = { type StateProps = { user?: ApiUser; userStatus?: ApiUserStatus; + userProfilePhoto?: ApiPhoto; phoneCodeList: ApiCountryCode[]; }; @@ -43,12 +46,14 @@ const NewContactModal: FC = ({ isByPhoneNumber, user, userStatus, + userProfilePhoto, phoneCodeList, }) => { const { updateContact, importContact, closeNewContactDialog } = getActions(); const lang = useLang(); const renderingUser = useCurrentOrPrev(user); + const renderingUserProfilePhoto = useCurrentOrPrev(userProfilePhoto); const renderingIsByPhoneNumber = useCurrentOrPrev(isByPhoneNumber); // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); @@ -120,7 +125,12 @@ const NewContactModal: FC = ({ return ( <>
- +

{renderingUser?.phoneNumber @@ -227,9 +237,11 @@ const NewContactModal: FC = ({ export default memo(withGlobal( (global, { userId }): StateProps => { + const user = userId ? selectUser(global, userId) : undefined; return { - user: userId ? selectUser(global, userId) : undefined, + user, userStatus: userId ? selectUserStatus(global, userId) : undefined, + userProfilePhoto: user ? selectUserPhotoFromFullInfo(global, user.id) : undefined, phoneCodeList: global.countryList.phoneCodes, }; }, diff --git a/src/components/main/WebAppModal.tsx b/src/components/main/WebAppModal.tsx index 77f866ab5..d614b3dcd 100644 --- a/src/components/main/WebAppModal.tsx +++ b/src/components/main/WebAppModal.tsx @@ -331,7 +331,7 @@ const WebAppModal: FC = ({ onClick={onTrigger} ariaLabel="More actions" > - + ); }, [isMobile]); diff --git a/src/components/main/premium/GiftOption.module.scss b/src/components/main/premium/GiftOption.module.scss index e29731157..ac70a1067 100644 --- a/src/components/main/premium/GiftOption.module.scss +++ b/src/components/main/premium/GiftOption.module.scss @@ -7,7 +7,7 @@ background: var(--color-background); box-shadow: 0 0 0 0.0625rem var(--color-borders-input); - cursor: pointer; + cursor: var(--custom-cursor, pointer); line-height: 1.5rem; } diff --git a/src/components/main/premium/GiftPremiumModal.tsx b/src/components/main/premium/GiftPremiumModal.tsx index 2eaea1ce3..60534bdd1 100644 --- a/src/components/main/premium/GiftPremiumModal.tsx +++ b/src/components/main/premium/GiftPremiumModal.tsx @@ -4,13 +4,18 @@ import React, { import { getActions, withGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; -import type { ApiPremiumGiftOption, ApiUser } from '../../../api/types'; +import type { ApiPhoto, ApiPremiumGiftOption, ApiUser } from '../../../api/types'; import type { AnimationLevel } from '../../../types'; import { formatCurrency } from '../../../util/formatCurrency'; import renderText from '../../common/helpers/renderText'; import { getUserFirstOrLastName } from '../../../global/helpers'; -import { selectTabState, selectUser } from '../../../global/selectors'; +import { + selectTabState, + selectUser, + selectUserFullInfo, + selectUserPhotoFromFullInfo, +} from '../../../global/selectors'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; import useLang from '../../../hooks/useLang'; @@ -29,6 +34,7 @@ export type OwnProps = { type StateProps = { user?: ApiUser; + userProfilePhoto?: ApiPhoto; gifts?: ApiPremiumGiftOption[]; monthlyCurrency?: string; monthlyAmount?: number; @@ -38,6 +44,7 @@ type StateProps = { const GiftPremiumModal: FC = ({ isOpen, user, + userProfilePhoto, gifts, monthlyCurrency, monthlyAmount, @@ -47,6 +54,7 @@ const GiftPremiumModal: FC = ({ const lang = useLang(); const renderedUser = useCurrentOrPrev(user, true); + const renderedUserProfilePhoto = useCurrentOrPrev(userProfilePhoto, true); const renderedGifts = useCurrentOrPrev(gifts, true); const [selectedOption, setSelectedOption] = useState(); const firstGift = renderedGifts?.[0]; @@ -121,9 +129,16 @@ const GiftPremiumModal: FC = ({ onClick={() => closeGiftPremiumModal()} ariaLabel={lang('Close')} > - + - +

{lang('GiftTelegramPremiumTitle')}

@@ -162,10 +177,12 @@ const GiftPremiumModal: FC = ({ export default memo(withGlobal((global): StateProps => { const { forUserId, monthlyCurrency, monthlyAmount } = selectTabState(global).giftPremiumModal || {}; const user = forUserId ? selectUser(global, forUserId) : undefined; - const gifts = user ? user.fullInfo?.premiumGifts : undefined; + const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined; + const gifts = user ? selectUserFullInfo(global, user.id)?.premiumGifts : undefined; return { user, + userProfilePhoto, gifts, monthlyCurrency, monthlyAmount: monthlyAmount ? Number(monthlyAmount) : undefined, diff --git a/src/components/main/premium/PremiumFeatureModal.tsx b/src/components/main/premium/PremiumFeatureModal.tsx index 35ea977f2..e24ee70f6 100644 --- a/src/components/main/premium/PremiumFeatureModal.tsx +++ b/src/components/main/premium/PremiumFeatureModal.tsx @@ -213,7 +213,7 @@ const PremiumFeatureModal: FC = ({ onClick={onBack} ariaLabel={lang('Back')} > - +
diff --git a/src/components/main/premium/PremiumMainModal.tsx b/src/components/main/premium/PremiumMainModal.tsx index f913ab7e3..b3870fbdc 100644 --- a/src/components/main/premium/PremiumMainModal.tsx +++ b/src/components/main/premium/PremiumMainModal.tsx @@ -245,7 +245,7 @@ const PremiumMainModal: FC = ({ onClick={() => closePremiumModal()} ariaLabel={lang('Close')} > - +

diff --git a/src/components/main/premium/common/PremiumLimitReachedModal.tsx b/src/components/main/premium/common/PremiumLimitReachedModal.tsx index fa27eefeb..27db1bda0 100644 --- a/src/components/main/premium/common/PremiumLimitReachedModal.tsx +++ b/src/components/main/premium/common/PremiumLimitReachedModal.tsx @@ -154,7 +154,7 @@ const PremiumLimitReachedModal: FC = ({ > {!canUpgrade && (
- +
{valueFormatter?.( lang, isPremium ? premiumValue : defaultValue, ) || (isPremium ? premiumValue : defaultValue)} @@ -193,7 +193,7 @@ const PremiumLimitReachedModal: FC = ({ color="primary" > {lang('IncreaseLimit')} - + )}
diff --git a/src/components/main/premium/common/PremiumLimitsCompare.tsx b/src/components/main/premium/common/PremiumLimitsCompare.tsx index 7f6921d2e..f90d9bb9a 100644 --- a/src/components/main/premium/common/PremiumLimitsCompare.tsx +++ b/src/components/main/premium/common/PremiumLimitsCompare.tsx @@ -30,7 +30,7 @@ const PremiumLimitsCompare: FC = ({
{floatingBadgeIcon && (
- +
{leftValue}
diff --git a/src/components/mediaViewer/MediaViewer.scss b/src/components/mediaViewer/MediaViewer.scss index ce2d01ea7..d6daad2c4 100644 --- a/src/components/mediaViewer/MediaViewer.scss +++ b/src/components/mediaViewer/MediaViewer.scss @@ -130,7 +130,7 @@ opacity: 0; transition: opacity 0.15s; outline: none; - cursor: pointer; + cursor: var(--custom-cursor, pointer); z-index: 1; @media (max-width: 600px) { diff --git a/src/components/mediaViewer/MediaViewer.tsx b/src/components/mediaViewer/MediaViewer.tsx index 8e2ff7204..c0e4073c5 100644 --- a/src/components/mediaViewer/MediaViewer.tsx +++ b/src/components/mediaViewer/MediaViewer.tsx @@ -3,7 +3,9 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, } from '../../lib/teact/teact'; -import type { ApiChat, ApiMessage, ApiUser } from '../../api/types'; +import type { + ApiChat, ApiMessage, ApiPhoto, ApiUser, +} from '../../api/types'; import type { AnimationLevel } from '../../types'; import { MediaViewerOrigin } from '../../types'; @@ -18,7 +20,9 @@ import { selectIsChatWithSelf, selectListedIds, selectScheduledMessage, - selectUser, selectOutlyingListByMessageId, + selectUser, + selectOutlyingListByMessageId, + selectUserFullInfo, } from '../../global/selectors'; import { stopCurrentAudio } from '../../util/audioPlayer'; import captureEscKeyListener from '../../util/captureEscKeyListener'; @@ -59,6 +63,7 @@ type StateProps = { canUpdateMedia?: boolean; origin?: MediaViewerOrigin; avatarOwner?: ApiChat | ApiUser; + avatarOwnerFallbackPhoto?: ApiPhoto; message?: ApiMessage; chatMessages?: Record; collectionIds?: number[]; @@ -78,6 +83,7 @@ const MediaViewer: FC = ({ canUpdateMedia, origin, avatarOwner, + avatarOwnerFallbackPhoto, message, chatMessages, collectionIds, @@ -99,7 +105,7 @@ const MediaViewer: FC = ({ /* Animation */ const animationKey = useRef(); const prevSenderId = usePrevious(senderId); - const headerAnimation = animationLevel === 2 ? 'slide-fade' : 'none'; + const headerAnimation = animationLevel === 2 ? 'slideFade' : 'none'; const isGhostAnimation = animationLevel === 2 && !shouldSkipHistoryAnimations; /* Controls */ @@ -296,7 +302,7 @@ const MediaViewer: FC = ({ chatId={avatarOwner.id} isAvatar isFallbackAvatar={isUserId(avatarOwner.id) - && (avatarOwner as ApiUser).photos?.[mediaId!].id === (avatarOwner as ApiUser).fullInfo?.fallbackPhoto?.id} + && (avatarOwner as ApiUser).photos?.[mediaId!].id === avatarOwnerFallbackPhoto?.id} /> ) : ( = ({ ariaLabel={lang('Close')} onClick={handleClose} > - + )} @@ -433,6 +439,7 @@ export default memo(withGlobal( mediaId, senderId: avatarOwnerId, avatarOwner: user || chat, + avatarOwnerFallbackPhoto: user ? selectUserFullInfo(global, avatarOwnerId)?.fallbackPhoto : undefined, isChatWithSelf, canUpdateMedia, animationLevel, diff --git a/src/components/mediaViewer/MediaViewerActions.tsx b/src/components/mediaViewer/MediaViewerActions.tsx index 8f6340fe2..d1acdec20 100644 --- a/src/components/mediaViewer/MediaViewerActions.tsx +++ b/src/components/mediaViewer/MediaViewerActions.tsx @@ -142,7 +142,7 @@ const MediaViewerActions: FC = ({ onClick={onTrigger} ariaLabel="More actions" > - + ); }, []); @@ -185,7 +185,7 @@ const MediaViewerActions: FC = ({ {isDownloading ? ( ) : ( - + )} ) : ( @@ -197,7 +197,7 @@ const MediaViewerActions: FC = ({ color="translucent-white" ariaLabel={lang('AccActionDownload')} > - + ); } @@ -292,7 +292,7 @@ const MediaViewerActions: FC = ({ ariaLabel={lang('Forward')} onClick={onForward} > - + )} {renderDownloadButton()} @@ -303,7 +303,7 @@ const MediaViewerActions: FC = ({ ariaLabel={lang('MediaZoomOut')} onClick={handleZoomOut} > - + {canReport && ( )} {canUpdate && ( @@ -333,7 +333,7 @@ const MediaViewerActions: FC = ({ ariaLabel={lang('ProfilePhoto.SetMainPhoto')} onClick={handleUpdate} > - + )} {canDelete && ( @@ -344,7 +344,7 @@ const MediaViewerActions: FC = ({ ariaLabel={lang('Delete')} onClick={openDeleteModal} > - + )} {canDelete && renderDeleteModals()}
diff --git a/src/components/mediaViewer/MediaViewerFooter.scss b/src/components/mediaViewer/MediaViewerFooter.scss index ece0e37ae..a370476b8 100644 --- a/src/components/mediaViewer/MediaViewerFooter.scss +++ b/src/components/mediaViewer/MediaViewerFooter.scss @@ -5,10 +5,6 @@ width: 100%; transition: opacity 0.15s; - #MediaViewer.zoomed & { - display: none; - } - @media (max-height: 640px) { padding: 0.5rem 0 0; } @@ -44,7 +40,7 @@ position: relative; max-width: var(--messages-container-width); margin: auto; - cursor: pointer; + cursor: var(--custom-cursor, pointer); opacity: 0.5; transition: opacity 0.15s; diff --git a/src/components/mediaViewer/MediaViewerSlides.scss b/src/components/mediaViewer/MediaViewerSlides.scss index e2fe01e33..ac55625d0 100644 --- a/src/components/mediaViewer/MediaViewerSlides.scss +++ b/src/components/mediaViewer/MediaViewerSlides.scss @@ -14,10 +14,6 @@ left: 0; right: 0; bottom: 0; - - & * { - -ms-scroll-chaining: none; - } } .MediaViewerSlide { @@ -37,6 +33,6 @@ } &--moving { - cursor: move; + cursor: var(--custom-cursor, move); } } diff --git a/src/components/mediaViewer/SenderInfo.scss b/src/components/mediaViewer/SenderInfo.scss index f1e0fb04e..0ecf850d0 100644 --- a/src/components/mediaViewer/SenderInfo.scss +++ b/src/components/mediaViewer/SenderInfo.scss @@ -1,7 +1,7 @@ .SenderInfo { display: flex; align-content: center; - cursor: pointer; + cursor: var(--custom-cursor, pointer); color: rgba(255, 255, 255, 0.5); transition: 0.15s color; diff --git a/src/components/mediaViewer/SenderInfo.tsx b/src/components/mediaViewer/SenderInfo.tsx index b2fc17cd8..ae8c5d627 100644 --- a/src/components/mediaViewer/SenderInfo.tsx +++ b/src/components/mediaViewer/SenderInfo.tsx @@ -18,6 +18,7 @@ import useLang from '../../hooks/useLang'; import useAppLayout from '../../hooks/useAppLayout'; import Avatar from '../common/Avatar'; +import UserAvatar from '../common/UserAvatar'; import './SenderInfo.scss'; @@ -79,7 +80,7 @@ const SenderInfo: FC = ({ return (
{isUserId(sender.id) ? ( - + ) : ( )} diff --git a/src/components/mediaViewer/VideoPlayer.scss b/src/components/mediaViewer/VideoPlayer.scss index df24549db..ee94377f4 100644 --- a/src/components/mediaViewer/VideoPlayer.scss +++ b/src/components/mediaViewer/VideoPlayer.scss @@ -4,21 +4,6 @@ flex-direction: column; overflow: hidden; - #MediaViewer.zoomed & { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - > div { - width: 100% !important; - height: 100% !important; - } - video { - max-height: none !important; - } - } - @media (min-width: 601px) { // Safari: custom controls are not displayed after exiting full screen mode. z-index: 1; @@ -100,7 +85,7 @@ } .ProgressSpinner { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } } } diff --git a/src/components/mediaViewer/VideoPlayer.tsx b/src/components/mediaViewer/VideoPlayer.tsx index e3eff248c..cfa63b9ac 100644 --- a/src/components/mediaViewer/VideoPlayer.tsx +++ b/src/components/mediaViewer/VideoPlayer.tsx @@ -299,7 +299,7 @@ const VideoPlayer: FC = ({
{shouldRenderPlayButton && ( )} {shouldRenderSpinner && ( diff --git a/src/components/mediaViewer/VideoPlayerControls.scss b/src/components/mediaViewer/VideoPlayerControls.scss index 141454f8b..ff7feaf0d 100644 --- a/src/components/mediaViewer/VideoPlayerControls.scss +++ b/src/components/mediaViewer/VideoPlayerControls.scss @@ -14,10 +14,6 @@ opacity: 0; pointer-events: none; - #MediaViewer.zoomed & { - display: none; - } - &.mobile { position: fixed; padding: 2.25rem 0.5rem 0.75rem; @@ -112,7 +108,7 @@ top: 1rem; height: 1rem; touch-action: none; - cursor: pointer; + cursor: var(--custom-cursor, pointer); &-track { position: absolute; diff --git a/src/components/mediaViewer/VideoPlayerControls.tsx b/src/components/mediaViewer/VideoPlayerControls.tsx index 851e69e18..ee0184c74 100644 --- a/src/components/mediaViewer/VideoPlayerControls.tsx +++ b/src/components/mediaViewer/VideoPlayerControls.tsx @@ -180,7 +180,7 @@ const VideoPlayerControls: FC = ({ round onClick={onPlayPause} > - + {!IS_IOS && ( @@ -221,7 +221,7 @@ const VideoPlayerControls: FC = ({ round onClick={onPictureInPictureChange} > - + )} {isFullscreenSupported && ( @@ -233,7 +233,7 @@ const VideoPlayerControls: FC = ({ round onClick={onChangeFullscreen} > - + )}
diff --git a/src/components/mediaViewer/helpers/ghostAnimation.ts b/src/components/mediaViewer/helpers/ghostAnimation.ts index 1aac7dcec..051695608 100644 --- a/src/components/mediaViewer/helpers/ghostAnimation.ts +++ b/src/components/mediaViewer/helpers/ghostAnimation.ts @@ -264,7 +264,7 @@ function uncover(realWidth: number, realHeight: number, top: number, left: numbe } function isMessageImageFullyVisible(container: HTMLElement, imageEl: HTMLElement) { - const messageListElement = document.querySelector('.Transition__slide--active > .MessageList')!; + const messageListElement = document.querySelector('.Transition_slide-active > .MessageList')!; let imgOffsetTop = container.offsetTop + imageEl.closest('.content-inner, .WebPage')!.offsetTop; if (container.id.includes('album-media-')) { imgOffsetTop += container.parentElement!.offsetTop + container.closest('.Message')!.offsetTop; @@ -295,7 +295,7 @@ function getNodes(origin: MediaViewerOrigin, message?: ApiMessage) { switch (origin) { case MediaViewerOrigin.Album: case MediaViewerOrigin.ScheduledAlbum: - containerSelector = `.Transition__slide--active > .MessageList #album-media-${getMessageHtmlId(message!.id)}`; + containerSelector = `.Transition_slide-active > .MessageList #album-media-${getMessageHtmlId(message!.id)}`; mediaSelector = '.full-media'; break; @@ -310,29 +310,29 @@ function getNodes(origin: MediaViewerOrigin, message?: ApiMessage) { break; case MediaViewerOrigin.MiddleHeaderAvatar: - containerSelector = '.MiddleHeader .Transition__slide--active .ChatInfo .Avatar'; + containerSelector = '.MiddleHeader .Transition_slide-active .ChatInfo .Avatar'; mediaSelector = '.avatar-media'; break; case MediaViewerOrigin.SettingsAvatar: - containerSelector = '#Settings .ProfileInfo .Transition__slide--active .ProfilePhoto'; + containerSelector = '#Settings .ProfileInfo .Transition_slide-active .ProfilePhoto'; mediaSelector = '.avatar-media'; break; case MediaViewerOrigin.ProfileAvatar: - containerSelector = '#RightColumn .ProfileInfo .Transition__slide--active .ProfilePhoto'; + containerSelector = '#RightColumn .ProfileInfo .Transition_slide-active .ProfilePhoto'; mediaSelector = '.avatar-media'; break; case MediaViewerOrigin.SuggestedAvatar: - containerSelector = `.Transition__slide--active > .MessageList #${getMessageHtmlId(message!.id)}`; + containerSelector = `.Transition_slide-active > .MessageList #${getMessageHtmlId(message!.id)}`; mediaSelector = '.Avatar img'; break; case MediaViewerOrigin.ScheduledInline: case MediaViewerOrigin.Inline: default: - containerSelector = `.Transition__slide--active > .MessageList #${getMessageHtmlId(message!.id)}`; + containerSelector = `.Transition_slide-active > .MessageList #${getMessageHtmlId(message!.id)}`; mediaSelector = `${MESSAGE_CONTENT_SELECTOR} .full-media,${MESSAGE_CONTENT_SELECTOR} .thumbnail:not(.blurred-bg)`; } diff --git a/src/components/middle/ActionMessage.tsx b/src/components/middle/ActionMessage.tsx index df9f91ee4..df82c878a 100644 --- a/src/components/middle/ActionMessage.tsx +++ b/src/components/middle/ActionMessage.tsx @@ -2,7 +2,7 @@ import type { FC } from '../../lib/teact/teact'; import React, { memo, useEffect, useMemo, useRef, } from '../../lib/teact/teact'; -import { getActions, withGlobal } from '../../global'; +import { getActions, getGlobal, withGlobal } from '../../global'; import type { ApiUser, ApiMessage, ApiChat, ApiSticker, ApiTopic, @@ -53,7 +53,6 @@ type OwnProps = { }; type StateProps = { - usersById: Record; senderUser?: ApiUser; senderChat?: ApiChat; targetUserIds?: string[]; @@ -74,7 +73,6 @@ const ActionMessage: FC = ({ appearanceOrder = 0, isJustAdded, isLastInList, - usersById, senderUser, senderChat, targetUserIds, @@ -140,6 +138,8 @@ const ActionMessage: FC = ({ const { transitionClassNames } = useShowTransition(isShown, undefined, noAppearanceAnimation, false); + // No need for expensive global updates on users and chats, so we avoid them + const usersById = getGlobal().users.byId; const targetUsers = useMemo(() => { return targetUserIds ? targetUserIds.map((userId) => usersById?.[userId]).filter(Boolean) @@ -256,7 +256,6 @@ export default memo(withGlobal( chatId, senderId, replyToMessageId, content, } = message; - const { byId: usersById } = global.users; const userId = senderId; const { targetUserIds, targetChatId } = content.action || {}; const targetMessageId = replyToMessageId; @@ -278,7 +277,6 @@ export default memo(withGlobal( const topic = selectTopicFromMessage(global, message); return { - usersById, senderUser, senderChat, targetChatId, diff --git a/src/components/middle/AudioPlayer.scss b/src/components/middle/AudioPlayer.scss index febdc92c4..95980907b 100644 --- a/src/components/middle/AudioPlayer.scss +++ b/src/components/middle/AudioPlayer.scss @@ -21,12 +21,12 @@ margin: 0.125rem; - &.smaller i { + &.smaller .icon { font-size: 1.625rem; margin-top: -0.0625rem; } - i { + .icon { position: absolute; font-size: 1.9375rem; margin-top: -0.0625rem; @@ -70,7 +70,7 @@ bottom: 0; height: 1rem; width: 8rem; - cursor: default; + cursor: var(--custom-cursor, default); visibility: hidden; } @@ -91,7 +91,7 @@ width: 8rem; padding: 0.75rem; border-radius: 0.5rem; - cursor: default; + cursor: var(--custom-cursor, default); box-shadow: 0 1px 2px var(--color-default-shadow); .RangeSlider { @@ -159,7 +159,7 @@ padding: 0 0.5rem; position: relative; overflow: hidden; - cursor: pointer; + cursor: var(--custom-cursor, pointer); border-radius: var(--border-radius-messages-small); &:hover { diff --git a/src/components/middle/AudioPlayer.tsx b/src/components/middle/AudioPlayer.tsx index 92533062e..3d2d97908 100644 --- a/src/components/middle/AudioPlayer.tsx +++ b/src/components/middle/AudioPlayer.tsx @@ -234,7 +234,7 @@ const AudioPlayer: FC = ({ onClick={requestPreviousTrack} ariaLabel="Previous track" > - +
@@ -271,7 +271,7 @@ const AudioPlayer: FC = ({ onClick={handleVolumeClick} ripple={!isMobile} > - + {!IS_IOS && ( @@ -309,7 +309,7 @@ const AudioPlayer: FC = ({ onClick={handleClose} ariaLabel="Close player" > - +
); @@ -349,7 +349,7 @@ function renderPlaybackRateMenuItem( // eslint-disable-next-line react/jsx-no-bind onClick={() => onClick(rate)} icon={isSelected ? 'check' : undefined} - customIcon={!isSelected ? : undefined} + customIcon={!isSelected ? : undefined} > {rate}X diff --git a/src/components/middle/ChatReportPanel.tsx b/src/components/middle/ChatReportPanel.tsx index a0f439747..cccfebd26 100644 --- a/src/components/middle/ChatReportPanel.tsx +++ b/src/components/middle/ChatReportPanel.tsx @@ -142,7 +142,7 @@ const ChatReportPanel: FC = ({ onClick={handleCloseReportPanel} ariaLabel={lang('Close')} > - + = ({ onClick={handleSearchClick} ariaLabel="Search in this chat" > - + )} {canCall && ( @@ -250,7 +251,7 @@ const HeaderActions: FC = ({ onClick={handleRequestCall} ariaLabel="Call" > - + )} @@ -265,7 +266,7 @@ const HeaderActions: FC = ({ onClick={handleJoinRequestsClick} ariaLabel={isChannel ? lang('SubscribeRequests') : lang('MemberRequests')} > - +
{pendingJoinRequests}
)} @@ -280,7 +281,7 @@ const HeaderActions: FC = ({ ariaLabel="More actions" onClick={handleHeaderMenuOpen} > - + {menuPosition && ( ( } const bot = selectChatBot(global, chatId); + const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined; const isChatWithSelf = selectIsChatWithSelf(global, chatId); const isMainThread = messageListType === 'thread' && threadId === MAIN_THREAD_ID; const isDiscussionThread = messageListType === 'thread' && threadId !== MAIN_THREAD_ID; @@ -345,8 +347,8 @@ export default memo(withGlobal( const canEnterVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && chat.isCallActive; const canCreateVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && !chat.isCallActive && (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat))); - const canViewStatistics = isMainThread && chat.fullInfo?.canViewStatistics; - const pendingJoinRequests = isMainThread ? chat.fullInfo?.requestsPending : undefined; + const canViewStatistics = isMainThread && chatFullInfo?.canViewStatistics; + const pendingJoinRequests = isMainThread ? chatFullInfo?.requestsPending : undefined; const shouldJoinToSend = Boolean(chat?.isNotJoined && chat.isJoinToSend); const shouldSendJoinRequest = Boolean(chat?.isNotJoined && chat.isJoinRequest); const noAnimation = global.settings.byKey.animationLevel === ANIMATION_LEVEL_MIN; diff --git a/src/components/middle/HeaderMenuContainer.tsx b/src/components/middle/HeaderMenuContainer.tsx index 6cebf5714..2dd9d625c 100644 --- a/src/components/middle/HeaderMenuContainer.tsx +++ b/src/components/middle/HeaderMenuContainer.tsx @@ -12,23 +12,26 @@ import { REPLIES_USER_ID } from '../../config'; import { disableScrolling, enableScrolling } from '../../util/scrollLock'; import { selectChat, - selectNotifySettings, - selectNotifyExceptions, - selectUser, selectChatBot, + selectChatFullInfo, + selectCurrentMessageList, selectIsPremiumPurchaseBlocked, - selectCurrentMessageList, selectTabState, + selectNotifyExceptions, + selectNotifySettings, + selectTabState, + selectUser, + selectUserFullInfo, } from '../../global/selectors'; import { - isUserId, - getCanDeleteChat, - selectIsChatMuted, getCanAddContact, + getCanDeleteChat, + getCanManageTopic, + getHasAdminRight, isChatChannel, isChatGroup, - getCanManageTopic, + isUserId, isUserRightBanned, - getHasAdminRight, + selectIsChatMuted, } from '../../global/helpers'; import useShowTransition from '../../hooks/useShowTransition'; import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation'; @@ -549,9 +552,11 @@ export default memo(withGlobal( const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {}; const chatBot = chatId !== REPLIES_USER_ID ? selectChatBot(global, chatId) : undefined; + const userFullInfo = isPrivate ? selectUserFullInfo(global, chatId) : undefined; + const chatFullInfo = !isPrivate ? selectChatFullInfo(global, chatId) : undefined; const canGiftPremium = Boolean( global.lastSyncTime - && user?.fullInfo?.premiumGifts?.length + && userFullInfo?.premiumGifts?.length && !selectIsPremiumPurchaseBlocked(global), ); @@ -571,8 +576,8 @@ export default memo(withGlobal( canReportChat, canDeleteChat: getCanDeleteChat(chat), canGiftPremium, - hasLinkedChat: Boolean(chat?.fullInfo?.linkedChatId), - botCommands: chatBot?.fullInfo?.botInfo?.commands, + hasLinkedChat: Boolean(chatFullInfo?.linkedChatId), + botCommands: chatBot ? userFullInfo?.botInfo?.commands : undefined, isChatInfoShown: selectTabState(global).isChatInfoShown && currentChatId === chatId && currentThreadId === threadId, canCreateTopic, diff --git a/src/components/middle/HeaderPinnedMessage.module.scss b/src/components/middle/HeaderPinnedMessage.module.scss index eebb04d1e..13d301ee9 100644 --- a/src/components/middle/HeaderPinnedMessage.module.scss +++ b/src/components/middle/HeaderPinnedMessage.module.scss @@ -4,7 +4,7 @@ display: flex; align-items: center; margin-left: auto; - cursor: default; + cursor: var(--custom-cursor, default); flex-direction: row-reverse; background: var(--color-background); @@ -108,7 +108,7 @@ border-radius: var(--border-radius-messages-small); position: relative; overflow: hidden; - cursor: pointer; + cursor: var(--custom-cursor, pointer); align-items: center; &:hover:not(.no-hover) { diff --git a/src/components/middle/HeaderPinnedMessage.tsx b/src/components/middle/HeaderPinnedMessage.tsx index 3141336cc..33ddb0d04 100644 --- a/src/components/middle/HeaderPinnedMessage.tsx +++ b/src/components/middle/HeaderPinnedMessage.tsx @@ -123,7 +123,7 @@ const HeaderPinnedMessage: FC = ({ )} @@ -136,7 +136,7 @@ const HeaderPinnedMessage: FC = ({ ariaLabel={lang('UnpinMessageAlertTitle')} onClick={openUnpinDialog} > - + )} = ({ count={count} index={index} /> - + {renderPictogram( mediaThumbnail, mediaBlobUrl, @@ -171,7 +171,7 @@ const HeaderPinnedMessage: FC = ({ {customTitle && renderText(customTitle)}
- +

diff --git a/src/components/middle/MessageList.scss b/src/components/middle/MessageList.scss index be7b6cea3..e323bffa3 100644 --- a/src/components/middle/MessageList.scss +++ b/src/components/middle/MessageList.scss @@ -311,7 +311,7 @@ line-height: 1rem !important; padding-bottom: 0.75rem !important; margin-top: 0.5rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); outline: none; } @@ -323,7 +323,7 @@ line-height: 1rem !important; padding-bottom: 0.75rem !important; margin-top: 0.5rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); outline: none; .Avatar { @@ -367,7 +367,7 @@ } &.interactive { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } span { @@ -405,7 +405,7 @@ .ActionMessage { .action-link { - cursor: pointer; + cursor: var(--custom-cursor, pointer); &:hover { text-decoration: underline; @@ -463,15 +463,16 @@ } } - .Transition.animating > .Transition__slide > & { + .Transition_slide-from > &, + .Transition_slide-to > & { &::-webkit-scrollbar { - width: 0; + width: 0 !important; } /* Styles for Firefox */ @supports (scrollbar-width: none) { - padding-right: 0.6875rem; - scrollbar-width: none; + padding-right: 0.6875rem !important; + scrollbar-width: none !important; } } } diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 6f59cda9c..ada7894e8 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -41,6 +41,7 @@ import { selectLastScrollOffset, selectThreadInfo, selectTabState, + selectUserFullInfo, selectChatFullInfo, } from '../../global/selectors'; import { isChatChannel, @@ -693,14 +694,16 @@ export default memo(withGlobal( let isLoadingBotInfo = false; let botInfo; if (selectIsChatBotNotStarted(global, chatId)) { - if (chatBot.fullInfo) { - botInfo = chatBot.fullInfo.botInfo; + const chatBotFullInfo = selectUserFullInfo(global, chatBot.id); + if (chatBotFullInfo) { + botInfo = chatBotFullInfo.botInfo; } else { isLoadingBotInfo = true; } } const topic = chat.topics?.[threadId]; + const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined; return { isCurrentUserPremium: selectIsCurrentUserPremium(global), @@ -724,9 +727,7 @@ export default memo(withGlobal( isLoadingBotInfo, botInfo, threadTopMessageId, - hasLinkedChat: chat.fullInfo && ('linkedChatId' in chat.fullInfo) - ? Boolean(chat.fullInfo.linkedChatId) - : undefined, + hasLinkedChat: Boolean(chatFullInfo?.linkedChatId), lastSyncTime: global.lastSyncTime, topic, ...(withLastMessageWhenPreloading && { lastMessage }), diff --git a/src/components/middle/MessageSelectToolbar.scss b/src/components/middle/MessageSelectToolbar.scss index 2130dcc9a..c921369ae 100644 --- a/src/components/middle/MessageSelectToolbar.scss +++ b/src/components/middle/MessageSelectToolbar.scss @@ -135,27 +135,27 @@ white-space: nowrap; color: var(--color-text); --ripple-color: rgba(0, 0, 0, 0.08); - cursor: pointer; + cursor: var(--custom-cursor, pointer); unicode-bidi: plaintext; padding: 0.6875rem; border-radius: 50%; - i { + .icon { font-size: 1.5rem; color: var(--color-text-secondary); } &.destructive { color: var(--color-error); - i { + .icon { color: inherit; } } &.disabled { opacity: 0.5; - cursor: default; + cursor: var(--custom-cursor, default); pointer-events: none; } diff --git a/src/components/middle/MessageSelectToolbar.tsx b/src/components/middle/MessageSelectToolbar.tsx index 59dd68abe..3e1463c47 100644 --- a/src/components/middle/MessageSelectToolbar.tsx +++ b/src/components/middle/MessageSelectToolbar.tsx @@ -122,13 +122,14 @@ const MessageSelectToolbar: FC = ({ role="button" tabIndex={0} className={buildClassName( + 'div-button', 'item', destructive && 'destructive', )} onClick={onClick} title={label} > - +

); }; @@ -142,7 +143,7 @@ const MessageSelectToolbar: FC = ({ onClick={handleExitMessageSelectMode} ariaLabel="Exit select mode" > - + {formattedMessagesCount} diff --git a/src/components/middle/MiddleColumn.scss b/src/components/middle/MiddleColumn.scss index 3ae7da38d..99a32b32f 100644 --- a/src/components/middle/MiddleColumn.scss +++ b/src/components/middle/MiddleColumn.scss @@ -26,7 +26,7 @@ height: 100%; overflow: hidden; - > .Transition__slide { + > .Transition_slide { display: flex; flex-direction: column; align-items: center; diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index 3ea0b2391..a42c42226 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -33,8 +33,9 @@ import { DropAreaState } from './composer/DropArea'; import { selectChat, selectChatBot, + selectChatFullInfo, + selectChatMessage, selectCurrentMessageList, - selectTabState, selectCurrentTextSearch, selectIsChatBotNotStarted, selectIsInSelectMode, @@ -42,9 +43,9 @@ import { selectIsUserBlocked, selectPinnedIds, selectReplyingToId, + selectTabState, selectTheme, selectThreadInfo, - selectChatMessage, } from '../../global/selectors'; import { getCanPostInChat, @@ -514,7 +515,7 @@ const MiddleColumn: FC = ({ className="unpin-all-button" onClick={handleOpenUnpinModal} > - + {lang('Chat.Pinned.UnpinAll', pinnedMessagesCount, 'i')}
@@ -686,7 +687,9 @@ export default memo(withGlobal( const shouldSendJoinRequest = Boolean(chat?.isNotJoined && chat.isJoinRequest); const canRestartBot = Boolean(bot && selectIsUserBlocked(global, bot.id)); const canStartBot = !canRestartBot && isBotNotStarted; - const shouldLoadFullChat = Boolean(chat && isChatGroup(chat) && !chat.fullInfo && lastSyncTime); + const shouldLoadFullChat = Boolean( + chat && isChatGroup(chat) && !selectChatFullInfo(global, chat.id) && lastSyncTime, + ); const replyingToId = selectReplyingToId(global, chatId, threadId); const shouldBlockSendInForum = chat?.isForum ? threadId === MAIN_THREAD_ID && !replyingToId && (chat.topics?.[GENERAL_TOPIC_ID]?.isClosed) diff --git a/src/components/middle/MiddleHeader.scss b/src/components/middle/MiddleHeader.scss index f23e93930..c2aea50bc 100644 --- a/src/components/middle/MiddleHeader.scss +++ b/src/components/middle/MiddleHeader.scss @@ -60,7 +60,7 @@ // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size min-width: 0; - > .Transition__slide { + > .Transition_slide { display: flex; align-items: center; width: 100%; @@ -204,7 +204,7 @@ } .ChatInfo { - cursor: pointer; + cursor: var(--custom-cursor, pointer); display: flex; align-items: center; diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index 981937cb2..4430a3983 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -409,7 +409,7 @@ const MiddleHeader: FC = ({ return (
( return state; } - Object.assign(state, { messagesById }); - if (threadId !== MAIN_THREAD_ID && !chat?.isForum) { const pinnedMessageId = selectThreadTopMessageId(global, chatId, threadId); const message = pinnedMessageId ? selectChatMessage(global, chatId, pinnedMessageId) : undefined; @@ -542,6 +540,7 @@ export default memo(withGlobal( return { ...state, pinnedMessageIds: pinnedMessageId, + messagesById, canUnpin: false, topMessageSender, isComments: Boolean(threadInfo?.originChannelId), @@ -558,6 +557,7 @@ export default memo(withGlobal( return { ...state, pinnedMessageIds, + messagesById, canUnpin, }; } diff --git a/src/components/middle/MobileSearch.tsx b/src/components/middle/MobileSearch.tsx index 88db4f1df..49a1e3740 100644 --- a/src/components/middle/MobileSearch.tsx +++ b/src/components/middle/MobileSearch.tsx @@ -160,7 +160,7 @@ const MobileSearchFooter: FC = ({ color="translucent" onClick={handleCloseLocalTextSearch} > - + = ({ onClick={() => openHistoryCalendar({ selectedAt: getDayStartAt(Date.now()) })} ariaLabel="Search messages by date" > - + )}
@@ -198,7 +198,7 @@ const MobileSearchFooter: FC = ({ onClick={handleUp} disabled={!foundIds || !foundIds.length || focusedIndex === foundIds.length - 1} > - +
diff --git a/src/components/middle/NoMessages.scss b/src/components/middle/NoMessages.scss index 3eba8c145..558509fc9 100644 --- a/src/components/middle/NoMessages.scss +++ b/src/components/middle/NoMessages.scss @@ -5,7 +5,7 @@ align-items: center; justify-content: center; - .icon { + .no-messages-icon { font-size: 5rem; margin: 0 auto 1rem; } diff --git a/src/components/middle/NoMessages.tsx b/src/components/middle/NoMessages.tsx index 525d9e240..0b4e864c3 100644 --- a/src/components/middle/NoMessages.tsx +++ b/src/components/middle/NoMessages.tsx @@ -56,7 +56,7 @@ function renderTopic(lang: LangFn, topic: ApiTopic) { return (
- +

{lang('Chat.EmptyTopicPlaceholder.Title')}

{renderText(lang('Chat.EmptyTopicPlaceholder.Text'), ['br'])}

@@ -74,7 +74,7 @@ function renderSavedMessages(lang: LangFn) { return (
- +

{lang('Conversation.CloudStorageInfo.Title')}

  • {lang('Conversation.ClousStorageInfo.Description1')}
  • diff --git a/src/components/middle/ReactorListModal.tsx b/src/components/middle/ReactorListModal.tsx index fc5ef0cac..c9b09018f 100644 --- a/src/components/middle/ReactorListModal.tsx +++ b/src/components/middle/ReactorListModal.tsx @@ -5,7 +5,6 @@ import React, { import { getActions, getGlobal, withGlobal } from '../../global'; import type { ApiAvailableReaction, ApiMessage, ApiReaction } from '../../api/types'; -import type { AnimationLevel } from '../../types'; import { LoadMoreDirection } from '../../types'; import { selectChatMessage, selectTabState } from '../../global/selectors'; @@ -21,7 +20,7 @@ import useFlag from '../../hooks/useFlag'; import InfiniteScroll from '../ui/InfiniteScroll'; import Modal from '../ui/Modal'; import Button from '../ui/Button'; -import Avatar from '../common/Avatar'; +import UserAvatar from '../common/UserAvatar'; import ListItem from '../ui/ListItem'; import ReactionStaticEmoji from '../common/ReactionStaticEmoji'; import Loading from '../ui/Loading'; @@ -38,7 +37,6 @@ export type OwnProps = { export type StateProps = Pick & { chatId?: string; messageId?: number; - animationLevel: AnimationLevel; availableReactions?: ApiAvailableReaction[]; }; @@ -49,7 +47,6 @@ const ReactorListModal: FC = ({ chatId, messageId, seenByUserIds, - animationLevel, availableReactions, }) => { const { @@ -146,7 +143,7 @@ const ReactorListModal: FC = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => setChosenTab(undefined)} > - + {Boolean(reactors?.count) && formatIntegerCompact(reactors.count)} {allReactions.map((reaction) => { @@ -194,7 +191,7 @@ const ReactorListModal: FC = ({ // eslint-disable-next-line react/jsx-no-bind onClick={() => handleClick(userId)} > - + {r.reaction && ( ( reactions: message?.reactions, reactors: message?.reactors, seenByUserIds: message?.seenByUserIds, - animationLevel: global.settings.byKey.animationLevel, availableReactions: global.availableReactions, }; }, diff --git a/src/components/middle/ScrollDownButton.tsx b/src/components/middle/ScrollDownButton.tsx index d9fa9cc46..803f96e33 100644 --- a/src/components/middle/ScrollDownButton.tsx +++ b/src/components/middle/ScrollDownButton.tsx @@ -50,7 +50,7 @@ const ScrollDownButton: FC = ({ onContextMenu={handleContextMenu} ariaLabel={lang(ariaLabelLang)} > - + {Boolean(unreadCount) &&
    {formatIntegerCompact(unreadCount)}
    } {onReadAll && ( diff --git a/src/components/middle/composer/AttachBotIcon.tsx b/src/components/middle/composer/AttachBotIcon.tsx index 755431bc4..7cba832d8 100644 --- a/src/components/middle/composer/AttachBotIcon.tsx +++ b/src/components/middle/composer/AttachBotIcon.tsx @@ -43,7 +43,7 @@ const AttachBotIcon: FC = ({ }, [mediaData, theme]); return ( - + ); diff --git a/src/components/middle/composer/AttachMenu.tsx b/src/components/middle/composer/AttachMenu.tsx index 872f06b7f..36d3521d9 100644 --- a/src/components/middle/composer/AttachMenu.tsx +++ b/src/components/middle/composer/AttachMenu.tsx @@ -136,7 +136,7 @@ const AttachMenu: FC = ({ ariaControls="attach-menu-controls" hasPopup > - + = ({ onClick={onTrigger} ariaLabel="More actions" > - + ); }, [isMobile]); @@ -431,7 +432,7 @@ const AttachmentModal: FC = ({ return (
    {title}
    ( attachmentSettings, } = global; - const chat = selectChat(global, chatId); + const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined; const isChatWithSelf = selectIsChatWithSelf(global, chatId); const { language, shouldSuggestCustomEmoji } = global.settings.byKey; const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG]; @@ -644,7 +645,7 @@ export default memo(withGlobal( return { isChatWithSelf, currentUserId, - groupChatMembers: chat?.fullInfo?.members, + groupChatMembers: chatFullInfo?.members, recentEmojis, baseEmojiKeywords: baseEmojiKeywords?.keywords, emojiKeywords: emojiKeywords?.keywords, diff --git a/src/components/middle/composer/AttachmentModalItem.module.scss b/src/components/middle/composer/AttachmentModalItem.module.scss index 29981af8c..d939f633b 100644 --- a/src/components/middle/composer/AttachmentModalItem.module.scss +++ b/src/components/middle/composer/AttachmentModalItem.module.scss @@ -68,7 +68,7 @@ transition: 0.2s background-color ease-in-out; - cursor: pointer; + cursor: var(--custom-cursor, pointer); &:hover { background-color: rgba(0, 0, 0, 0.15); diff --git a/src/components/middle/composer/AttachmentModalItem.tsx b/src/components/middle/composer/AttachmentModalItem.tsx index aac036f68..86f2b80f7 100644 --- a/src/components/middle/composer/AttachmentModalItem.tsx +++ b/src/components/middle/composer/AttachmentModalItem.tsx @@ -83,7 +83,7 @@ const AttachmentModalItem: FC = ({ /> {onDelete && ( onDelete(index)} /> )} @@ -116,6 +116,7 @@ const AttachmentModalItem: FC = ({ {canDisplaySpoilerButton && ( = ({ /> )} {onDelete && ( - onDelete(index)} /> + onDelete(index)} + /> )}
    )} diff --git a/src/components/middle/composer/BotCommandTooltip.tsx b/src/components/middle/composer/BotCommandTooltip.tsx index 8d00320f7..ccc6c23d9 100644 --- a/src/components/middle/composer/BotCommandTooltip.tsx +++ b/src/components/middle/composer/BotCommandTooltip.tsx @@ -2,10 +2,10 @@ import type { FC } from '../../../lib/teact/teact'; import React, { useCallback, useEffect, useRef, memo, } from '../../../lib/teact/teact'; -import { getActions, withGlobal } from '../../../global'; +import { getActions, getGlobal } from '../../../global'; import type { Signal } from '../../../util/signals'; -import type { ApiBotCommand, ApiUser } from '../../../api/types'; +import type { ApiBotCommand } from '../../../api/types'; import buildClassName from '../../../util/buildClassName'; import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; @@ -26,12 +26,7 @@ export type OwnProps = { onClose: NoneToVoidFunction; }; -type StateProps = { - usersById: Record; -}; - -const BotCommandTooltip: FC = ({ - usersById, +const BotCommandTooltip: FC = ({ isOpen, withUsername, botCommands, @@ -46,12 +41,15 @@ const BotCommandTooltip: FC = ({ const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false); const handleSendCommand = useCallback(({ botId, command }: ApiBotCommand) => { + // No need for expensive global updates on users and chats, so we avoid them + const usersById = getGlobal().users.byId; const bot = usersById[botId]; + sendBotCommand({ command: `/${command}${withUsername && bot ? `@${bot.usernames![0].username}` : ''}`, }); onClick(); - }, [onClick, sendBotCommand, usersById, withUsername]); + }, [onClick, sendBotCommand, withUsername]); const handleSelect = useCallback((botCommand: ApiBotCommand) => { // We need an additional check because tooltip is updated with throttling @@ -98,7 +96,8 @@ const BotCommandTooltip: FC = ({ = ({ ); }; -export default memo(withGlobal( - (global): StateProps => ({ - usersById: global.users.byId, - }), -)(BotCommandTooltip)); +export default memo(BotCommandTooltip); diff --git a/src/components/middle/composer/BotMenuButton.tsx b/src/components/middle/composer/BotMenuButton.tsx index c7713b9e9..6d66588c3 100644 --- a/src/components/middle/composer/BotMenuButton.tsx +++ b/src/components/middle/composer/BotMenuButton.tsx @@ -51,7 +51,7 @@ const BotMenuButton: FC = ({ onClick={onClick} ariaLabel="Open bot command keyboard" > - + {text} ); diff --git a/src/components/middle/composer/Composer.scss b/src/components/middle/composer/Composer.scss index 2025cb98f..7947715f0 100644 --- a/src/components/middle/composer/Composer.scss +++ b/src/components/middle/composer/Composer.scss @@ -152,7 +152,7 @@ } } - &.not-ready > i { + &.not-ready > .icon { animation-duration: 0ms !important; } @@ -201,7 +201,7 @@ animation: hide-icon 0.4s forwards ease-out; } - &.not-ready > i { + &.not-ready > .icon { animation-duration: 0ms !important; } @@ -356,7 +356,7 @@ } } - &.scheduled-button i::after { + &.scheduled-button .icon::after { content: ""; position: absolute; top: 0.75rem; @@ -549,7 +549,7 @@ .text-entity-link { color: var(--color-links) !important; - cursor: default; + cursor: var(--custom-cursor, default); text-decoration: none; &:hover, diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx index 872605746..c0d4aafdf 100644 --- a/src/components/middle/composer/Composer.tsx +++ b/src/components/middle/composer/Composer.tsx @@ -23,6 +23,8 @@ import type { ApiBotCommand, ApiBotMenuButton, ApiAttachMenuPeerType, + ApiChatFullInfo, + ApiPhoto, } from '../../../api/types'; import type { InlineBotSettings, ISettings } from '../../../types'; @@ -32,40 +34,42 @@ import { REPLIES_USER_ID, SEND_MESSAGE_ACTION_INTERVAL, EDITABLE_INPUT_CSS_SELECTOR, - MAX_UPLOAD_FILEPART_SIZE, EDITABLE_INPUT_MODAL_ID, + MAX_UPLOAD_FILEPART_SIZE, + EDITABLE_INPUT_MODAL_ID, } from '../../../config'; import { IS_VOICE_RECORDING_SUPPORTED, IS_IOS } from '../../../util/windowEnvironment'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { - selectChat, - selectIsRightColumnShown, - selectIsInSelectMode, - selectNewestMessageWithBotKeyboardButtons, - selectDraft, - selectScheduledIds, - selectEditingMessage, - selectIsChatWithSelf, - selectChatBot, - selectChatMessage, - selectUser, selectCanScheduleUntilOnline, - selectEditingScheduledDraft, - selectEditingDraft, - selectRequestedDraftText, - selectTheme, - selectCurrentMessageList, - selectIsCurrentUserPremium, + selectChat, + selectChatBot, + selectChatFullInfo, + selectChatMessage, selectChatType, - selectRequestedDraftFiles, - selectTabState, + selectCurrentMessageList, + selectDraft, + selectEditingDraft, + selectEditingMessage, + selectEditingScheduledDraft, + selectIsChatWithSelf, + selectIsCurrentUserPremium, + selectIsInSelectMode, + selectIsRightColumnShown, + selectNewestMessageWithBotKeyboardButtons, selectReplyingToId, + selectRequestedDraftFiles, + selectRequestedDraftText, + selectScheduledIds, + selectTabState, + selectTheme, + selectUser, + selectUserFullInfo, selectUserPhotoFromFullInfo, } from '../../../global/selectors'; import { getAllowedAttachmentOptions, - getChatSlowModeOptions, isChatAdmin, - isChatSuperGroup, isChatChannel, + isChatSuperGroup, isUserId, } from '../../../global/helpers'; import { formatMediaDuration, formatVoiceRecordDuration } from '../../../util/dateFormat'; @@ -186,6 +190,7 @@ type StateProps = chatBotCommands?: ApiBotCommand[]; sendAsUser?: ApiUser; sendAsChat?: ApiChat; + sendAsUserProfilePhoto?: ApiPhoto; sendAsId?: string; editingDraft?: ApiFormattedText; requestedDraftText?: string; @@ -198,6 +203,7 @@ type StateProps = isCurrentUserPremium?: boolean; canSendVoiceByPrivacy?: boolean; attachmentSettings: GlobalState['attachmentSettings']; + slowMode?: ApiChatFullInfo['slowMode']; } & Pick; @@ -271,6 +277,7 @@ const Composer: FC = ({ chatBotCommands, sendAsUser, sendAsChat, + sendAsUserProfilePhoto, sendAsId, editingDraft, replyingToId, @@ -281,6 +288,7 @@ const Composer: FC = ({ attachMenuPeerType, attachmentSettings, theme, + slowMode, }) => { const { sendMessage, @@ -423,7 +431,6 @@ const Composer: FC = ({ }, [getHtml, isEditingRef, sendMessageAction]); const isAdmin = chat && isChatAdmin(chat); - const slowMode = getChatSlowModeOptions(chat); const { isEmojiTooltipOpen, @@ -917,7 +924,7 @@ const Composer: FC = ({ useEffect(() => { if (requestedDraftFiles?.length) { - handleFileSelect(requestedDraftFiles); + void handleFileSelect(requestedDraftFiles); resetOpenChatWithDraft(); } }, [handleFileSelect, requestedDraftFiles, resetOpenChatWithDraft]); @@ -1140,7 +1147,7 @@ const Composer: FC = ({ const mainButtonHandler = useCallback(() => { switch (mainButtonState) { case MainButtonState.Send: - handleSend(); + void handleSend(); break; case MainButtonState.Record: { if (areVoiceMessagesNotAllowed) { @@ -1152,7 +1159,7 @@ const Composer: FC = ({ showAllowedMessageTypesNotification({ chatId }); } } else { - startRecordingVoice(); + void startRecordingVoice(); } break; } @@ -1350,7 +1357,7 @@ const Composer: FC = ({ onActivate={handleActivateBotCommandMenu} ariaLabel="Open bot command keyboard" > - + )} {canShowSendAs && (sendAsUser || sendAsChat) && ( @@ -1364,6 +1371,7 @@ const Composer: FC = ({ @@ -1427,7 +1435,7 @@ const Composer: FC = ({ onClick={handleAllScheduledClick} ariaLabel="Open scheduled messages" > - + )} {Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && ( @@ -1438,7 +1446,7 @@ const Composer: FC = ({ onActivate={openBotKeyboard} ariaLabel="Open bot command keyboard" > - + )} {activeVoiceRecording && Boolean(currentRecordTime) && ( @@ -1511,7 +1519,7 @@ const Composer: FC = ({ onClick={stopRecordingVoice} ariaLabel="Cancel voice recording" > - + )} {canShowCustomSendMenu && ( ( const isChatWithBot = Boolean(chatBot); const isChatWithSelf = selectIsChatWithSelf(global, chatId); const isChatWithUser = isUserId(chatId); + const chatBotFullInfo = isChatWithBot ? selectUserFullInfo(global, chatBot.id) : undefined; + const chatFullInfo = !isChatWithUser ? selectChatFullInfo(global, chatId) : undefined; const messageWithActualBotKeyboard = (isChatWithBot || !isChatWithUser) && selectNewestMessageWithBotKeyboardButtons(global, chatId, threadId); const scheduledIds = selectScheduledIds(global, chatId, threadId); @@ -1566,7 +1576,7 @@ export default memo(withGlobal( const botKeyboardMessageId = messageWithActualBotKeyboard ? messageWithActualBotKeyboard.id : undefined; const keyboardMessage = botKeyboardMessageId ? selectChatMessage(global, chatId, botKeyboardMessageId) : undefined; const { currentUserId } = global; - const defaultSendAsId = chat?.fullInfo ? chat?.fullInfo?.sendAsId || currentUserId : undefined; + const defaultSendAsId = chatFullInfo ? chatFullInfo?.sendAsId || currentUserId : undefined; const sendAsId = chat?.sendAsPeerIds && defaultSendAsId && ( chat.sendAsPeerIds.some((peer) => peer.id === defaultSendAsId) ? defaultSendAsId @@ -1574,6 +1584,7 @@ export default memo(withGlobal( ); const sendAsUser = sendAsId ? selectUser(global, sendAsId) : undefined; const sendAsChat = !sendAsUser && sendAsId ? selectChat(global, sendAsId) : undefined; + const sendAsUserProfilePhoto = sendAsUser ? selectUserPhotoFromFullInfo(global, sendAsUser.id) : undefined; const requestedDraftText = selectRequestedDraftText(global, chatId); const requestedDraftFiles = selectRequestedDraftFiles(global, chatId); const currentMessageList = selectCurrentMessageList(global); @@ -1581,7 +1592,8 @@ export default memo(withGlobal( && threadId === currentMessageList?.threadId && messageListType === currentMessageList?.type; const user = selectUser(global, chatId); - const canSendVoiceByPrivacy = (user && !user.fullInfo?.noVoiceMessages) ?? true; + const canSendVoiceByPrivacy = (user && !selectUserFullInfo(global, user.id)?.noVoiceMessages) ?? true; + const slowMode = chatFullInfo?.slowMode; const editingDraft = messageListType === 'scheduled' ? selectEditingScheduledDraft(global, chatId) @@ -1616,7 +1628,7 @@ export default memo(withGlobal( pollModal: tabState.pollModal, stickersForEmoji: global.stickers.forEmoji.stickers, customEmojiForEmoji: global.customEmojis.forEmoji.stickers, - groupChatMembers: chat?.fullInfo?.members, + groupChatMembers: chatFullInfo?.members, topInlineBotIds: global.topInlineBots?.userIds, currentUserId, lastSyncTime: global.lastSyncTime, @@ -1628,11 +1640,12 @@ export default memo(withGlobal( emojiKeywords: emojiKeywords?.keywords, inlineBots: tabState.inlineBots.byUsername, isInlineBotLoading: tabState.inlineBots.isLoading, - chatBotCommands: chat?.fullInfo && chat.fullInfo.botCommands, - botCommands: chatBot?.fullInfo ? (chatBot.fullInfo.botInfo?.commands || false) : undefined, - botMenuButton: chatBot?.fullInfo?.botInfo?.menuButton, + chatBotCommands: chatFullInfo?.botCommands, + botCommands: chatBotFullInfo ? (chatBotFullInfo.botInfo?.commands || false) : undefined, + botMenuButton: chatBotFullInfo?.botInfo?.menuButton, sendAsUser, sendAsChat, + sendAsUserProfilePhoto, sendAsId, editingDraft, requestedDraftText, @@ -1645,6 +1658,7 @@ export default memo(withGlobal( isCurrentUserPremium: selectIsCurrentUserPremium(global), canSendVoiceByPrivacy, attachmentSettings: global.attachmentSettings, + slowMode, }; }, )(Composer)); diff --git a/src/components/middle/composer/ComposerEmbeddedMessage.scss b/src/components/middle/composer/ComposerEmbeddedMessage.scss index 8867a5da7..78e936ffe 100644 --- a/src/components/middle/composer/ComposerEmbeddedMessage.scss +++ b/src/components/middle/composer/ComposerEmbeddedMessage.scss @@ -15,7 +15,7 @@ transition: none !important; } - & > div { + &_inner { display: flex; align-items: center; padding-right: 0.625rem; diff --git a/src/components/middle/composer/ComposerEmbeddedMessage.tsx b/src/components/middle/composer/ComposerEmbeddedMessage.tsx index fe9a4b357..9614940e3 100644 --- a/src/components/middle/composer/ComposerEmbeddedMessage.tsx +++ b/src/components/middle/composer/ComposerEmbeddedMessage.tsx @@ -187,9 +187,9 @@ const ComposerEmbeddedMessage: FC = ({ return (
    -
    +
    - +
    = ({ ariaLabel={lang('Cancel')} onClick={handleClearClick} > - + {isForwarding && ( = ({ > : undefined} + customIcon={noAuthors ? : undefined} // eslint-disable-next-line react/jsx-no-bind onClick={() => setForwardNoAuthors({ noAuthors: false, @@ -234,7 +234,7 @@ const ComposerEmbeddedMessage: FC = ({ : undefined} + customIcon={!noAuthors ? : undefined} // eslint-disable-next-line react/jsx-no-bind onClick={() => setForwardNoAuthors({ noAuthors: true, @@ -247,7 +247,7 @@ const ComposerEmbeddedMessage: FC = ({ : undefined} + customIcon={noCaptions ? : undefined} // eslint-disable-next-line react/jsx-no-bind onClick={() => setForwardNoCaptions({ noCaptions: false, @@ -257,7 +257,7 @@ const ComposerEmbeddedMessage: FC = ({ : undefined} + customIcon={!noCaptions ? : undefined} // eslint-disable-next-line react/jsx-no-bind onClick={() => setForwardNoCaptions({ noCaptions: true, diff --git a/src/components/middle/composer/EmojiButton.scss b/src/components/middle/composer/EmojiButton.scss index 484fca644..d05dd9b50 100644 --- a/src/components/middle/composer/EmojiButton.scss +++ b/src/components/middle/composer/EmojiButton.scss @@ -6,7 +6,7 @@ height: 2.5rem; margin: 0.3125rem; border-radius: var(--border-radius-messages-small); - cursor: pointer; + cursor: var(--custom-cursor, pointer); font-size: 1.75rem; line-height: 2.5rem; diff --git a/src/components/middle/composer/EmojiPicker.tsx b/src/components/middle/composer/EmojiPicker.tsx index ef7bb3252..de780c0b0 100644 --- a/src/components/middle/composer/EmojiPicker.tsx +++ b/src/components/middle/composer/EmojiPicker.tsx @@ -185,7 +185,7 @@ const EmojiPicker: FC = ({ onClick={() => selectCategory(index)} ariaLabel={category.name} > - + ); } diff --git a/src/components/middle/composer/InlineBotTooltip.tsx b/src/components/middle/composer/InlineBotTooltip.tsx index 8b0f670d9..fdb0b7a5a 100644 --- a/src/components/middle/composer/InlineBotTooltip.tsx +++ b/src/components/middle/composer/InlineBotTooltip.tsx @@ -176,6 +176,7 @@ const InlineBotTooltip: FC = ({ ); case 'article': case 'audio': + case 'voice': return ( = ({ )} dir="auto" > - {!isAttachmentModalInput && !canSendPlainText && } + {!isAttachmentModalInput && !canSendPlainText + && } {placeholder} )} diff --git a/src/components/middle/composer/PollModal.tsx b/src/components/middle/composer/PollModal.tsx index c7efdd594..7e6787aca 100644 --- a/src/components/middle/composer/PollModal.tsx +++ b/src/components/middle/composer/PollModal.tsx @@ -246,7 +246,7 @@ const PollModal: FC = ({ return (
    {lang('NewPoll')}
    )}
    diff --git a/src/components/middle/composer/SendAsMenu.tsx b/src/components/middle/composer/SendAsMenu.tsx index eb5246d63..649fb78e9 100644 --- a/src/components/middle/composer/SendAsMenu.tsx +++ b/src/components/middle/composer/SendAsMenu.tsx @@ -1,21 +1,22 @@ import React, { useCallback, useEffect, useRef, memo, } from '../../../lib/teact/teact'; +import { getActions, getGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; import type { ApiSendAsPeerId } from '../../../api/types'; +import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; +import buildClassName from '../../../util/buildClassName'; import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; -import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import { isUserId } from '../../../global/helpers'; import useMouseInside from '../../../hooks/useMouseInside'; import useLang from '../../../hooks/useLang'; -import buildClassName from '../../../util/buildClassName'; -import { getActions, getGlobal } from '../../../global'; import ListItem from '../../ui/ListItem'; import Avatar from '../../common/Avatar'; +import UserAvatar from '../../common/UserAvatar'; import Menu from '../../ui/Menu'; import FullNameTitle from '../../common/FullNameTitle'; @@ -114,6 +115,8 @@ const SendAsMenu: FC = ({ } }; + const avatarClassName = buildClassName(selectedSendAsId === id && 'selected'); + return ( = ({ // eslint-disable-next-line react/jsx-no-bind onClick={handleClick} focus={selectedSendAsIndex === index} - rightElement={!isCurrentUserPremium && isPremium && } + rightElement={!isCurrentUserPremium && isPremium + && } > - + {user ? ( + + ) : ( + + )}
    {userOrChat && } {user diff --git a/src/components/middle/composer/StickerPicker.tsx b/src/components/middle/composer/StickerPicker.tsx index 8a35cb5e9..e5e1135e9 100644 --- a/src/components/middle/composer/StickerPicker.tsx +++ b/src/components/middle/composer/StickerPicker.tsx @@ -12,16 +12,20 @@ import { FAVORITE_SYMBOL_SET_ID, PREMIUM_STICKER_SET_ID, RECENT_SYMBOL_SET_ID, - SLIDE_TRANSITION_DURATION, STICKER_PICKER_MAX_SHARED_COVERS, + SLIDE_TRANSITION_DURATION, + STICKER_PICKER_MAX_SHARED_COVERS, STICKER_SIZE_PICKER_HEADER, } from '../../../config'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; +import { isUserId } from '../../../global/helpers'; import animateScroll from '../../../util/animateScroll'; import buildClassName from '../../../util/buildClassName'; import animateHorizontalScroll from '../../../util/animateHorizontalScroll'; import { pickTruthy, uniqueByField } from '../../../util/iteratees'; -import { selectChat, selectIsChatWithSelf, selectIsCurrentUserPremium } from '../../../global/selectors'; +import { + selectChat, selectChatFullInfo, selectIsChatWithSelf, selectIsCurrentUserPremium, +} from '../../../global/selectors'; import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; @@ -57,6 +61,7 @@ type StateProps = { favoriteStickers: ApiSticker[]; premiumStickers: ApiSticker[]; stickerSetsById: Record; + chatStickerSetId?: string; addedSetIds?: string[]; canAnimate?: boolean; isSavedMessages?: boolean; @@ -78,6 +83,7 @@ const StickerPicker: FC = ({ premiumStickers, addedSetIds, stickerSetsById, + chatStickerSetId, canAnimate, isSavedMessages, isCurrentUserPremium, @@ -161,8 +167,8 @@ const StickerPicker: FC = ({ } } - if (chat?.fullInfo?.stickerSet) { - const fullSet = stickerSetsById[chat.fullInfo.stickerSet.id]; + if (chatStickerSetId) { + const fullSet = stickerSetsById[chatStickerSetId]; if (fullSet) { defaultSets.push({ id: CHAT_STICKER_SET_ID, @@ -179,7 +185,8 @@ const StickerPicker: FC = ({ ...existingAddedSetIds, ]; }, [ - addedSetIds, stickerSetsById, favoriteStickers, recentStickers, isCurrentUserPremium, chat, lang, premiumStickers, + addedSetIds, stickerSetsById, favoriteStickers, recentStickers, isCurrentUserPremium, chatStickerSetId, lang, + premiumStickers, ]); const noPopulatedSets = useMemo(() => ( @@ -273,9 +280,9 @@ const StickerPicker: FC = ({ {stickerSet.id === PREMIUM_STICKER_SET_ID ? ( ) : stickerSet.id === RECENT_SYMBOL_SET_ID ? ( - + ) : stickerSet.id === FAVORITE_SYMBOL_SET_ID ? ( - + ) : stickerSet.id === CHAT_STICKER_SET_ID ? ( ) : ( @@ -376,6 +383,7 @@ export default memo(withGlobal( const isSavedMessages = selectIsChatWithSelf(global, chatId); const chat = selectChat(global, chatId); + const chatStickerSetId = !isUserId(chatId) ? selectChatFullInfo(global, chatId)?.stickerSet?.id : undefined; return { chat, @@ -387,6 +395,7 @@ export default memo(withGlobal( canAnimate: global.settings.byKey.shouldLoopStickers, isSavedMessages, isCurrentUserPremium: selectIsCurrentUserPremium(global), + chatStickerSetId, }; }, )(StickerPicker)); diff --git a/src/components/middle/composer/SymbolMenu.scss b/src/components/middle/composer/SymbolMenu.scss index 7fcb6e707..51f372206 100644 --- a/src/components/middle/composer/SymbolMenu.scss +++ b/src/components/middle/composer/SymbolMenu.scss @@ -233,7 +233,7 @@ right: 0; position: absolute; font-size: 1rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); border-radius: 50%; padding: 0.25rem; transition: background-color 0.15s; diff --git a/src/components/middle/composer/SymbolMenu.tsx b/src/components/middle/composer/SymbolMenu.tsx index 2c0cf217e..dc3b8bc75 100644 --- a/src/components/middle/composer/SymbolMenu.tsx +++ b/src/components/middle/composer/SymbolMenu.tsx @@ -271,7 +271,7 @@ const SymbolMenu: FC = ({ size="tiny" onClick={onClose} > - + )} = ({ onClick={isSymbolMenuOpen ? closeSymbolMenu : handleSymbolMenuOpen} ariaLabel="Choose emoji, sticker or GIF" > - - + + {isSymbolMenuOpen && !isSymbolMenuLoaded && } ) : ( @@ -177,7 +177,7 @@ const SymbolMenuButton: FC = ({ ariaLabel="Choose emoji, sticker or GIF" >
    - + )} diff --git a/src/components/middle/composer/SymbolMenuFooter.tsx b/src/components/middle/composer/SymbolMenuFooter.tsx index fee3afe73..dae1a5eb3 100644 --- a/src/components/middle/composer/SymbolMenuFooter.tsx +++ b/src/components/middle/composer/SymbolMenuFooter.tsx @@ -1,6 +1,7 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, useCallback } from '../../../lib/teact/teact'; +import buildClassName from '../../../util/buildClassName'; import useLang from '../../../hooks/useLang'; import Button from '../../ui/Button'; @@ -52,7 +53,7 @@ const SymbolMenuFooter: FC = ({ faded color="translucent" > - + ); } @@ -76,7 +77,7 @@ const SymbolMenuFooter: FC = ({ color="translucent" onClick={handleSearchOpen} > - + )} @@ -94,7 +95,7 @@ const SymbolMenuFooter: FC = ({ faded color="translucent" > - + )}
    diff --git a/src/components/middle/composer/TextFormatter.scss b/src/components/middle/composer/TextFormatter.scss index addc47a6e..257a34471 100644 --- a/src/components/middle/composer/TextFormatter.scss +++ b/src/components/middle/composer/TextFormatter.scss @@ -119,7 +119,7 @@ margin: 0 0.125rem; border-radius: var(--border-radius-messages-small); flex-shrink: 0; - cursor: pointer; + cursor: var(--custom-cursor, pointer); &.active { background-color: var(--color-primary) !important; diff --git a/src/components/middle/composer/TextFormatter.tsx b/src/components/middle/composer/TextFormatter.tsx index 147f93310..f80c69dba 100644 --- a/src/components/middle/composer/TextFormatter.tsx +++ b/src/components/middle/composer/TextFormatter.tsx @@ -427,7 +427,7 @@ const TextFormatter: FC = ({ className={getFormatButtonClassName('spoiler')} onClick={handleSpoilerText} > - +
    @@ -508,7 +508,7 @@ const TextFormatter: FC = ({ className="color-primary" onClick={handleLinkUrlConfirm} > - +
    diff --git a/src/components/middle/composer/WebPagePreview.scss b/src/components/middle/composer/WebPagePreview.scss index 220a05d6c..9edf5647e 100644 --- a/src/components/middle/composer/WebPagePreview.scss +++ b/src/components/middle/composer/WebPagePreview.scss @@ -16,7 +16,7 @@ } // TODO Remove duplication with `.ComposerEmbeddedMessage` - & > div { + &_inner { display: flex; align-items: center; padding-right: 0.625rem; diff --git a/src/components/middle/composer/WebPagePreview.tsx b/src/components/middle/composer/WebPagePreview.tsx index 923fc92a6..0cefda73f 100644 --- a/src/components/middle/composer/WebPagePreview.tsx +++ b/src/components/middle/composer/WebPagePreview.tsx @@ -106,9 +106,9 @@ const WebPagePreview: FC = ({ return (
    -
    +
    - +
    diff --git a/src/components/middle/composer/inlineResults/MediaResult.scss b/src/components/middle/composer/inlineResults/MediaResult.scss index 36714f7e7..3a8d6b4e7 100644 --- a/src/components/middle/composer/inlineResults/MediaResult.scss +++ b/src/components/middle/composer/inlineResults/MediaResult.scss @@ -3,7 +3,7 @@ padding-bottom: 100%; overflow: hidden; position: relative; - cursor: pointer; + cursor: var(--custom-cursor, pointer); img { position: absolute; diff --git a/src/components/middle/composer/inlineResults/StickerResult.scss b/src/components/middle/composer/inlineResults/StickerResult.scss index 3e74f8dbe..ff994fb98 100644 --- a/src/components/middle/composer/inlineResults/StickerResult.scss +++ b/src/components/middle/composer/inlineResults/StickerResult.scss @@ -4,7 +4,7 @@ overflow: hidden; background: transparent no-repeat center; background-size: contain; - cursor: pointer; + cursor: var(--custom-cursor, pointer); transition: background-color 0.15s ease, opacity 0.3s ease !important; position: relative; diff --git a/src/components/middle/message/CommentButton.scss b/src/components/middle/message/CommentButton.scss index 470766c95..c10cc7199 100644 --- a/src/components/middle/message/CommentButton.scss +++ b/src/components/middle/message/CommentButton.scss @@ -16,7 +16,7 @@ line-height: 2rem; color: var(--accent-color); white-space: nowrap; - cursor: pointer; + cursor: var(--custom-cursor, pointer); transition: background-color 0.15s, color 0.15s; user-select: none; @@ -213,7 +213,7 @@ } &.disabled { - cursor: default; + cursor: var(--custom-cursor, default); pointer-events: none; } } diff --git a/src/components/middle/message/CommentButton.tsx b/src/components/middle/message/CommentButton.tsx index e465c8ef8..0b6d5dac2 100644 --- a/src/components/middle/message/CommentButton.tsx +++ b/src/components/middle/message/CommentButton.tsx @@ -9,6 +9,7 @@ import type { import { isUserId } from '../../../global/helpers'; import { formatIntegerCompact } from '../../../util/textFormat'; import buildClassName from '../../../util/buildClassName'; +import { selectUserPhotoFromFullInfo } from '../../../global/selectors'; import useLang from '../../../hooks/useLang'; import Avatar from '../../common/Avatar'; @@ -62,6 +63,7 @@ const CommentButton: FC = ({ key={user.id} size="small" user={isUserId(user.id) ? user as ApiUser : undefined} + userProfilePhoto={isUserId(user.id) ? selectUserPhotoFromFullInfo(getGlobal(), user.id) : undefined} chat={!isUserId(user.id) ? user as ApiChat : undefined} /> ))} @@ -86,13 +88,13 @@ const CommentButton: FC = ({ dir={lang.isRtl ? 'rtl' : 'ltr'} onClick={handleClick} > - - {(!recentRepliers || recentRepliers.length === 0) && } + + {(!recentRepliers || recentRepliers.length === 0) && } {renderRecentRepliers()}
    {messagesCount ? commentsText : lang('LeaveAComment')}
    - +
    ); }; diff --git a/src/components/middle/message/Contact.scss b/src/components/middle/message/Contact.scss index 17948e65b..a57e0f815 100644 --- a/src/components/middle/message/Contact.scss +++ b/src/components/middle/message/Contact.scss @@ -4,7 +4,7 @@ padding: 0.25rem; &.interactive { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } .Avatar { diff --git a/src/components/middle/message/Contact.tsx b/src/components/middle/message/Contact.tsx index 513f08d72..847e610d1 100644 --- a/src/components/middle/message/Contact.tsx +++ b/src/components/middle/message/Contact.tsx @@ -2,10 +2,12 @@ import type { FC } from '../../../lib/teact/teact'; import React, { useCallback } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import type { ApiUser, ApiContact, ApiCountryCode } from '../../../api/types'; +import type { + ApiUser, ApiContact, ApiCountryCode, ApiPhoto, +} from '../../../api/types'; import type { AnimationLevel } from '../../../types'; -import { selectUser } from '../../../global/selectors'; +import { selectUser, selectUserPhotoFromFullInfo } from '../../../global/selectors'; import { formatPhoneNumberWithCode } from '../../../util/phoneNumber'; import buildClassName from '../../../util/buildClassName'; @@ -19,6 +21,7 @@ type OwnProps = { type StateProps = { user?: ApiUser; + userProfilePhoto?: ApiPhoto; phoneCodeList: ApiCountryCode[]; animationLevel: AnimationLevel; }; @@ -26,7 +29,7 @@ type StateProps = { const UNREGISTERED_CONTACT_ID = '0'; const Contact: FC = ({ - contact, user, phoneCodeList, animationLevel, + contact, user, userProfilePhoto, phoneCodeList, animationLevel, }) => { const { openChat } = getActions(); @@ -47,7 +50,14 @@ const Contact: FC = ({ className={buildClassName('Contact', isRegistered && 'interactive')} onClick={isRegistered ? handleClick : undefined} > - +
    {firstName} {lastName}
    {formatPhoneNumberWithCode(phoneCodeList, phoneNumber)}
    @@ -59,8 +69,12 @@ const Contact: FC = ({ export default withGlobal( (global, { contact }): StateProps => { const { countryList: { phoneCodes: phoneCodeList } } = global; + const user = selectUser(global, contact.userId); + const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined; + return { - user: selectUser(global, contact.userId), + user, + userProfilePhoto, phoneCodeList, animationLevel: global.settings.byKey.animationLevel, }; diff --git a/src/components/middle/message/ContextMenuContainer.tsx b/src/components/middle/message/ContextMenuContainer.tsx index 2cd0a01cd..557a0941e 100644 --- a/src/components/middle/message/ContextMenuContainer.tsx +++ b/src/components/middle/message/ContextMenuContainer.tsx @@ -15,6 +15,7 @@ import { selectAllowedMessageActions, selectCanScheduleUntilOnline, selectChat, + selectChatFullInfo, selectCurrentMessageList, selectIsCurrentUserPremium, selectIsMessageProtected, @@ -592,6 +593,7 @@ export default memo(withGlobal( canClosePoll, } = (threadId && selectAllowedMessageActions(global, message, threadId)) || {}; + const isPrivate = chat && isUserId(chat.id); const isOwn = isOwnMessage(message); const isPinned = messageListType === 'pinned'; const isScheduled = messageListType === 'scheduled'; @@ -607,8 +609,8 @@ export default memo(withGlobal( && chat.membersCount && chat.membersCount <= seenByMaxChatMembers && message.date > Date.now() / 1000 - seenByExpiresAt); - const isPrivate = chat && isUserId(chat.id); const isAction = isActionMessage(message); + const chatFullInfo = !isPrivate ? selectChatFullInfo(global, message.chatId) : undefined; const canShowReactionsCount = !isLocal && !isChannel && !isScheduled && !isAction && !isPrivate && message.reactions && !areReactionsEmpty(message.reactions) && message.reactions.canSeeList; const isProtected = selectIsMessageProtected(global, message); @@ -656,11 +658,11 @@ export default memo(withGlobal( canClosePoll: !isScheduled && canClosePoll, activeDownloads, canShowSeenBy, - enabledReactions: chat?.isForbidden ? undefined : chat?.fullInfo?.enabledReactions, + enabledReactions: chat?.isForbidden ? undefined : chatFullInfo?.enabledReactions, maxUniqueReactions, isPrivate, isCurrentUserPremium, - hasFullInfo: Boolean(chat?.fullInfo), + hasFullInfo: Boolean(chatFullInfo), canShowReactionsCount, canShowReactionList: !isLocal && !isAction && !isScheduled && chat?.id !== SERVICE_NOTIFICATIONS_USER_ID, canBuyPremium: !isCurrentUserPremium && !selectIsPremiumPurchaseBlocked(global), diff --git a/src/components/middle/message/Game.scss b/src/components/middle/message/Game.scss index fbe626cae..a811743e5 100644 --- a/src/components/middle/message/Game.scss +++ b/src/components/middle/message/Game.scss @@ -13,7 +13,7 @@ max-width: 100%; margin-bottom: 0.25rem; margin-top: 0.25rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); } .preview-content { diff --git a/src/components/middle/message/InlineButtons.scss b/src/components/middle/message/InlineButtons.scss index c7dfab1b5..710897967 100644 --- a/src/components/middle/message/InlineButtons.scss +++ b/src/components/middle/message/InlineButtons.scss @@ -37,7 +37,7 @@ margin-right: 0; } - i { + .icon { font-size: 0.875rem; position: absolute; right: 0.1875rem; diff --git a/src/components/middle/message/InlineButtons.tsx b/src/components/middle/message/InlineButtons.tsx index 342e18df6..acd643109 100644 --- a/src/components/middle/message/InlineButtons.tsx +++ b/src/components/middle/message/InlineButtons.tsx @@ -24,20 +24,20 @@ const InlineButtons: FC = ({ message, onClick }) => { switch (type) { case 'url': { if (!RE_TME_LINK.test(button.url)) { - return ; + return ; } break; } case 'urlAuth': - return ; + return ; case 'buy': case 'receipt': - return ; + return ; case 'switchBotInline': - return ; + return ; case 'webView': case 'simpleWebView': - return ; + return ; } return undefined; }; diff --git a/src/components/middle/message/InvoiceMediaPreview.module.scss b/src/components/middle/message/InvoiceMediaPreview.module.scss index 856fe85f6..463e71c0d 100644 --- a/src/components/middle/message/InvoiceMediaPreview.module.scss +++ b/src/components/middle/message/InvoiceMediaPreview.module.scss @@ -3,7 +3,7 @@ z-index: 0; overflow: hidden; - cursor: pointer; + cursor: var(--custom-cursor, pointer); } .duration { diff --git a/src/components/middle/message/InvoiceMediaPreview.tsx b/src/components/middle/message/InvoiceMediaPreview.tsx index 60ec3e554..4950c86a0 100644 --- a/src/components/middle/message/InvoiceMediaPreview.tsx +++ b/src/components/middle/message/InvoiceMediaPreview.tsx @@ -71,7 +71,7 @@ const InvoiceMediaPreview: FC = ({ /> {Boolean(duration) &&
    {formatMediaDuration(duration)}
    }
    - + {lang('Checkout.PayPrice', formatCurrency(amount, currency))}
    diff --git a/src/components/middle/message/Location.tsx b/src/components/middle/message/Location.tsx index 8ced4fd01..257cb90c3 100644 --- a/src/components/middle/message/Location.tsx +++ b/src/components/middle/message/Location.tsx @@ -5,7 +5,9 @@ import { requestMutation } from '../../../lib/fasterdom/fasterdom'; import { getActions } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; -import type { ApiChat, ApiMessage, ApiUser } from '../../../api/types'; +import type { + ApiChat, ApiMessage, ApiPhoto, ApiUser, +} from '../../../api/types'; import type { ISettings } from '../../../types'; import { CUSTOM_APPENDIX_ATTRIBUTE, MESSAGE_CONTENT_SELECTOR } from '../../../config'; @@ -53,6 +55,7 @@ const SVG_PIN = { __html: ' + {location.heading !== undefined && (
    )} diff --git a/src/components/middle/message/Message.scss b/src/components/middle/message/Message.scss index ed4ef440f..7173ea941 100644 --- a/src/components/middle/message/Message.scss +++ b/src/components/middle/message/Message.scss @@ -69,7 +69,7 @@ .quick-reaction { --custom-emoji-size: 1.75rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); position: absolute; right: -0.875rem; bottom: -0.5rem; @@ -267,7 +267,7 @@ } .select-mode-active & { - cursor: pointer; + cursor: var(--custom-cursor, pointer); @media (min-width: 600px) { user-select: none; diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index 38bbb80f5..51b9d3c79 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -24,6 +24,7 @@ import type { ApiTopic, ApiReaction, ApiStickerSet, + ApiPhoto, } from '../../../api/types'; import type { AnimationLevel, FocusDirection, IAlbum, ISettings, @@ -68,6 +69,8 @@ import { selectTabState, selectChatTranslations, selectRequestedTranslationLanguage, + selectChatFullInfo, + selectUserPhotoFromFullInfo, } from '../../../global/selectors'; import { getMessageContent, @@ -200,6 +203,8 @@ type StateProps = { canShowSender: boolean; originSender?: ApiUser | ApiChat; botSender?: ApiUser; + senderUserProfilePhoto?: ApiPhoto; + originSenderUserProfilePhoto?: ApiPhoto; isThreadTop?: boolean; shouldHideReply?: boolean; replyMessage?: ApiMessage; @@ -308,6 +313,8 @@ const Message: FC = ({ canShowSender, originSender, botSender, + senderUserProfilePhoto, + originSenderUserProfilePhoto, isThreadTop, shouldHideReply, replyMessage, @@ -477,8 +484,10 @@ const Message: FC = ({ const messageSender = canShowSender ? sender : undefined; const withVoiceTranscription = Boolean(!isTranscriptionHidden && (isTranscriptionError || transcribedText)); - const avatarPeer = forwardInfo && (isChatWithSelf || isRepliesChat || !messageSender) ? originSender : messageSender; + const shouldPreferOriginSender = forwardInfo && (isChatWithSelf || isRepliesChat || !messageSender); + const avatarPeer = shouldPreferOriginSender ? originSender : messageSender; const senderPeer = forwardInfo ? originSender : messageSender; + const avatarUserProfilePhoto = shouldPreferOriginSender ? originSenderUserProfilePhoto : senderUserProfilePhoto; const { handleMouseDown, @@ -781,6 +790,7 @@ const Message: FC = ({ user={avatarUser} chat={avatarChat} text={hiddenName} + userProfilePhoto={avatarUserProfilePhoto} lastSyncTime={lastSyncTime} onClick={(avatarUser || avatarChat) ? handleAvatarClick : undefined} observeIntersection={observeIntersectionForLoading} @@ -1127,6 +1137,7 @@ const Message: FC = ({ isSelected={isSelected} theme={theme} peer={sender} + peerProfilePhoto={senderUserProfilePhoto} /> )}
    @@ -1237,7 +1248,7 @@ const Message: FC = ({ /> {!isInDocumentGroup && (
    - {isSelected && } + {isSelected && }
    )} {isLastInDocumentGroup && ( @@ -1246,7 +1257,7 @@ const Message: FC = ({ onClick={handleDocumentGroupSelectAll} > {isGroupSelected && ( - + )}
    )} @@ -1276,7 +1287,7 @@ const Message: FC = ({ ariaLabel={lang('lng_context_forward_msg')} onClick={isLastInDocumentGroup ? handleGroupForward : handleForward} > - + ) : canShowActionButton && canFocus ? ( ) : undefined} {withCommentButton && } @@ -1351,6 +1362,7 @@ export default memo(withGlobal( const isChannel = chat && isChatChannel(chat); const isGroup = chat && isChatGroup(chat); const chatUsernames = chat?.usernames; + const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined; const isForwarding = forwardMessages.messageIds && forwardMessages.messageIds.includes(id); const forceSenderName = !isChatWithSelf && isAnonymousOwnMessage(message); @@ -1359,8 +1371,12 @@ export default memo(withGlobal( const originSender = selectForwardedSender(global, message); const botSender = viaBotId ? selectUser(global, viaBotId) : undefined; const senderAdminMember = sender?.id && isGroup - ? chat.fullInfo?.adminMembersById?.[sender?.id] + ? chatFullInfo?.adminMembersById?.[sender?.id] : undefined; + const senderUserProfilePhoto = canShowSender && sender && isUserId(sender.id) + ? selectUserPhotoFromFullInfo(global, sender.id) : undefined; + const originSenderUserProfilePhoto = originSender && isUserId(originSender.id) + ? selectUserPhotoFromFullInfo(global, originSender.id) : undefined; const threadTopMessageId = threadId ? selectThreadTopMessageId(global, chatId, threadId) : undefined; const isThreadTop = message.id === threadTopMessageId; @@ -1427,6 +1443,8 @@ export default memo(withGlobal( canShowSender, originSender, botSender, + senderUserProfilePhoto, + originSenderUserProfilePhoto, shouldHideReply: shouldHideReply || isReplyToTopicStart, isThreadTop, replyMessage, @@ -1478,7 +1496,7 @@ export default memo(withGlobal( chatTranslations, areTranslationsEnabled: global.settings.byKey.canTranslate, requestedTranslationLanguage, - hasLinkedChat: Boolean(chat?.fullInfo?.linkedChatId), + hasLinkedChat: Boolean(chatFullInfo?.linkedChatId), ...((canShowSender || isLocation) && { sender }), ...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }), ...(typeof uploadProgress === 'number' && { uploadProgress }), diff --git a/src/components/middle/message/MessageContextMenu.tsx b/src/components/middle/message/MessageContextMenu.tsx index 05c39ab1e..9c5b3d4e2 100644 --- a/src/components/middle/message/MessageContextMenu.tsx +++ b/src/components/middle/message/MessageContextMenu.tsx @@ -1,7 +1,7 @@ import React, { memo, useCallback, useEffect, useRef, } from '../../../lib/teact/teact'; -import { getActions } from '../../../global'; +import { getActions, getGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; import type { @@ -21,6 +21,7 @@ import { getMessageCopyOptions } from './helpers/copyOptions'; import { disableScrolling, enableScrolling } from '../../../util/scrollLock'; import { getUserFullName } from '../../../global/helpers'; import buildClassName from '../../../util/buildClassName'; +import { selectUserPhotoFromFullInfo } from '../../../global/selectors'; import renderText from '../../common/helpers/renderText'; import useFlag from '../../../hooks/useFlag'; @@ -236,12 +237,12 @@ const MessageContextMenu: FC = ({ const getTriggerElement = useCallback(() => { return isSponsoredMessage - ? document.querySelector('.Transition__slide--active > .MessageList .SponsoredMessage') - : document.querySelector(`.Transition__slide--active > .MessageList div[data-message-id="${messageId}"]`); + ? document.querySelector('.Transition_slide-active > .MessageList .SponsoredMessage') + : document.querySelector(`.Transition_slide-active > .MessageList div[data-message-id="${messageId}"]`); }, [isSponsoredMessage, messageId]); const getRootElement = useCallback( - () => document.querySelector('.Transition__slide--active > .MessageList'), + () => document.querySelector('.Transition_slide-active > .MessageList'), [], ); @@ -394,6 +395,7 @@ const MessageContextMenu: FC = ({ ))}
    diff --git a/src/components/middle/message/MessageMeta.scss b/src/components/middle/message/MessageMeta.scss index 3c8267544..d7df9f6c6 100644 --- a/src/components/middle/message/MessageMeta.scss +++ b/src/components/middle/message/MessageMeta.scss @@ -9,7 +9,7 @@ border-radius: 0.625rem; padding: 0 0.25rem; color: white; - cursor: pointer; + cursor: var(--custom-cursor, pointer); max-width: 100%; user-select: none; @@ -95,7 +95,7 @@ height: 1.125rem; padding: 0 0.3125rem 0 0.375rem; - .MessageOutgoingStatus i { + .MessageOutgoingStatus .icon { background: transparent; } } diff --git a/src/components/middle/message/MessageMeta.tsx b/src/components/middle/message/MessageMeta.tsx index 0a8546c0e..19ba1ba51 100644 --- a/src/components/middle/message/MessageMeta.tsx +++ b/src/components/middle/message/MessageMeta.tsx @@ -103,14 +103,14 @@ const MessageMeta: FC = ({ data-ignore-on-paste > {isTranslated && ( - + )} {Boolean(message.views) && ( <> {formatIntegerCompact(message.views!)} - + )} {!noReplies && Boolean(repliesThreadInfo?.messagesCount) && ( @@ -118,11 +118,11 @@ const MessageMeta: FC = ({ - +
    )} {isPinned && ( - + )} {signature && ( {renderText(signature)} diff --git a/src/components/middle/message/MessagePhoneCall.tsx b/src/components/middle/message/MessagePhoneCall.tsx index 704ec699e..673872b6b 100644 --- a/src/components/middle/message/MessagePhoneCall.tsx +++ b/src/components/middle/message/MessagePhoneCall.tsx @@ -66,14 +66,18 @@ const MessagePhoneCall: FC = ({ disabled={!ARE_CALLS_SUPPORTED} ariaLabel={lang(isOutgoing ? 'CallAgain' : 'CallBack')} > - +
    {lang(reasonText)}
    diff --git a/src/components/middle/message/Photo.tsx b/src/components/middle/message/Photo.tsx index 7064de192..fc6a4f19d 100644 --- a/src/components/middle/message/Photo.tsx +++ b/src/components/middle/message/Photo.tsx @@ -208,7 +208,8 @@ const Photo: FC = ({
    )} - {shouldRenderDownloadButton && } + {shouldRenderDownloadButton + && } div { + .Spinner .Spinner__inner { // gray spinner background-image: var(--spinner-gray-data); diff --git a/src/components/middle/message/Poll.tsx b/src/components/middle/message/Poll.tsx index 9d1bad9b3..0dbbcbe54 100644 --- a/src/components/middle/message/Poll.tsx +++ b/src/components/middle/message/Poll.tsx @@ -7,7 +7,7 @@ import React, { useMemo, useRef, } from '../../../lib/teact/teact'; -import { getActions, withGlobal } from '../../../global'; +import { getActions, getGlobal, withGlobal } from '../../../global'; import type { ApiMessage, ApiPoll, ApiUser, ApiPollAnswer, @@ -19,6 +19,7 @@ import { formatMediaDuration } from '../../../util/dateFormat'; import type { LangFn } from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang'; import { getServerTimeOffset } from '../../../util/serverTime'; +import { selectUserPhotoFromFullInfo } from '../../../global/selectors'; import CheckboxGroup from '../../ui/CheckboxGroup'; import RadioGroup from '../../ui/RadioGroup'; @@ -231,8 +232,10 @@ const Poll: FC = ({
    {recentVoters.map((user) => ( ))}
    @@ -271,7 +274,7 @@ const Poll: FC = ({ onClick={handleSolutionShow} ariaLabel="Show Solution" > - + )}
    diff --git a/src/components/middle/message/PollOption.scss b/src/components/middle/message/PollOption.scss index 3cd7448b2..8358c7cf4 100644 --- a/src/components/middle/message/PollOption.scss +++ b/src/components/middle/message/PollOption.scss @@ -44,7 +44,7 @@ background: var(--color-error); } - .is-forwarded & > i { + .is-forwarded & > .icon { margin-left: 1px; } diff --git a/src/components/middle/message/PollOption.tsx b/src/components/middle/message/PollOption.tsx index 5a5a81f3e..61c264fac 100644 --- a/src/components/middle/message/PollOption.tsx +++ b/src/components/middle/message/PollOption.tsx @@ -78,7 +78,7 @@ const PollOption: FC = ({ shouldAnimate && 'animate', )} > - + )}
    diff --git a/src/components/middle/message/ReactionButton.tsx b/src/components/middle/message/ReactionButton.tsx index ff76c527e..c464f6b6c 100644 --- a/src/components/middle/message/ReactionButton.tsx +++ b/src/components/middle/message/ReactionButton.tsx @@ -11,6 +11,7 @@ import type { ActiveReaction } from '../../../global/types'; import buildClassName from '../../../util/buildClassName'; import { formatIntegerCompact } from '../../../util/textFormat'; import { isSameReaction, isReactionChosen } from '../../../global/helpers'; +import { selectUserPhotoFromFullInfo } from '../../../global/selectors'; import Button from '../../ui/Button'; import Avatar from '../../common/Avatar'; @@ -76,7 +77,14 @@ const ReactionButton: FC<{ /> {recentReactors?.length ? (
    - {recentReactors.map((user) => )} + {recentReactors.map((user) => ( + + ))}
    ) : } diff --git a/src/components/middle/message/ReactionPicker.tsx b/src/components/middle/message/ReactionPicker.tsx index 6e3c16ec8..e666e854a 100644 --- a/src/components/middle/message/ReactionPicker.tsx +++ b/src/components/middle/message/ReactionPicker.tsx @@ -11,7 +11,9 @@ import type { IAnchorPosition } from '../../../types'; import buildClassName from '../../../util/buildClassName'; import { isUserId } from '../../../global/helpers'; -import { selectChat, selectChatMessage, selectTabState } from '../../../global/selectors'; +import { + selectChat, selectChatFullInfo, selectChatMessage, selectTabState, +} from '../../../global/selectors'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; import useMenuPosition from '../../../hooks/useMenuPosition'; @@ -167,11 +169,12 @@ export default memo(withGlobal((global): StateProps => { const state = selectTabState(global); const { chatId, messageId, position } = state.reactionPicker || {}; const chat = chatId ? selectChat(global, chatId) : undefined; + const chatFullInfo = chatId ? selectChatFullInfo(global, chatId) : undefined; const message = chatId && messageId ? selectChatMessage(global, chatId, messageId) : undefined; const isPrivateChat = chatId ? isUserId(chatId) : false; - const areSomeReactionsAllowed = chat?.fullInfo?.enabledReactions?.type === 'some'; - const areCustomReactionsAllowed = chat?.fullInfo?.enabledReactions?.type === 'all' - && chat?.fullInfo?.enabledReactions?.areCustomAllowed; + const areSomeReactionsAllowed = chatFullInfo?.enabledReactions?.type === 'some'; + const areCustomReactionsAllowed = chatFullInfo?.enabledReactions?.type === 'all' + && chatFullInfo?.enabledReactions?.areCustomAllowed; return { message, diff --git a/src/components/middle/message/ReactionPickerLimited.tsx b/src/components/middle/message/ReactionPickerLimited.tsx index 77fd12ddc..f7b7558eb 100644 --- a/src/components/middle/message/ReactionPickerLimited.tsx +++ b/src/components/middle/message/ReactionPickerLimited.tsx @@ -8,7 +8,7 @@ import type { ApiReaction, ApiAvailableReaction, ApiChatReactions } from '../../ import { REM } from '../../common/helpers/mediaDimensions'; import buildClassName from '../../../util/buildClassName'; import { getReactionUniqueKey, sortReactions } from '../../../global/helpers'; -import { selectChat } from '../../../global/selectors'; +import { selectChatFullInfo } from '../../../global/selectors'; import useWindowSize from '../../../hooks/useWindowSize'; import useAppLayout from '../../../hooks/useAppLayout'; @@ -122,9 +122,8 @@ const ReactionPickerLimited: FC = ({ export default memo(withGlobal( (global, { chatId }): StateProps => { - const chat = selectChat(global, chatId); const { availableReactions, topReactions } = global; - const { enabledReactions } = chat?.fullInfo || {}; + const { enabledReactions } = selectChatFullInfo(global, chatId) || {}; return { enabledReactions, diff --git a/src/components/middle/message/ReactionSelector.scss b/src/components/middle/message/ReactionSelector.scss index ad8998136..7c192b610 100644 --- a/src/components/middle/message/ReactionSelector.scss +++ b/src/components/middle/message/ReactionSelector.scss @@ -105,7 +105,7 @@ width: 100%; height: 100%; display: flex; - cursor: pointer; + cursor: var(--custom-cursor, pointer); align-items: center; border-radius: 3rem; } diff --git a/src/components/middle/message/ReactionSelector.tsx b/src/components/middle/message/ReactionSelector.tsx index efa3d9f04..fec81e191 100644 --- a/src/components/middle/message/ReactionSelector.tsx +++ b/src/components/middle/message/ReactionSelector.tsx @@ -114,7 +114,7 @@ const ReactionSelector: FC = ({ className={cn('show-more')} onClick={handleShowMoreClick} > - + )}
    diff --git a/src/components/middle/message/RoundVideo.scss b/src/components/middle/message/RoundVideo.scss index 3f1f40b45..d6245234c 100644 --- a/src/components/middle/message/RoundVideo.scss +++ b/src/components/middle/message/RoundVideo.scss @@ -2,7 +2,7 @@ position: relative; width: 15rem; height: 15rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); .video-wrapper { position: absolute; diff --git a/src/components/middle/message/RoundVideo.tsx b/src/components/middle/message/RoundVideo.tsx index ef41eb0bb..eafad9dfb 100644 --- a/src/components/middle/message/RoundVideo.tsx +++ b/src/components/middle/message/RoundVideo.tsx @@ -219,11 +219,11 @@ const RoundVideo: FC = ({
    )} {!mediaData && !isLoadAllowed && ( - + )}
    {isActivated ? formatMediaDuration(playerRef.current!.currentTime) : formatMediaDuration(video.duration)} - {(!isActivated || playerRef.current!.paused) && } + {(!isActivated || playerRef.current!.paused) && }
    ); diff --git a/src/components/middle/message/Sticker.scss b/src/components/middle/message/Sticker.scss index 35ad29262..f8c5a18ca 100644 --- a/src/components/middle/message/Sticker.scss +++ b/src/components/middle/message/Sticker.scss @@ -8,7 +8,7 @@ } &:not(.inactive) { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } &.inactive { diff --git a/src/components/middle/message/Video.tsx b/src/components/middle/message/Video.tsx index 348e7eebd..87fbcee2e 100644 --- a/src/components/middle/message/Video.tsx +++ b/src/components/middle/message/Video.tsx @@ -234,7 +234,7 @@ const Video: FC = ({ /> )} {isProtected && } - + = ({
)} {!isLoadAllowed && !fullMediaData && ( - + )} {isTransferring ? ( diff --git a/src/components/middle/message/_message-content.scss b/src/components/middle/message/_message-content.scss index 2c748ccf1..30b1267e6 100644 --- a/src/components/middle/message/_message-content.scss +++ b/src/components/middle/message/_message-content.scss @@ -269,7 +269,7 @@ &.interactive, & > .interactive { - cursor: pointer; + cursor: var(--custom-cursor, pointer); &:hover { opacity: 0.85; @@ -542,7 +542,7 @@ align-items: center; &.interactive { - cursor: pointer; + cursor: var(--custom-cursor, pointer); &.dark video, &.dark canvas { @@ -638,7 +638,7 @@ &.opacity-transition { transition: opacity 0.15s ease; - &:not(.open) { + &.not-open { opacity: 0; } } @@ -884,7 +884,7 @@ color: var(--color-links) !important; text-decoration: none !important; word-break: break-word; - cursor: pointer; + cursor: var(--custom-cursor, pointer); unicode-bidi: initial; &:hover, @@ -903,7 +903,7 @@ border-radius: 4px; &.clickable { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } } diff --git a/src/components/middle/message/hocs/withSelectControl.tsx b/src/components/middle/message/hocs/withSelectControl.tsx index ac42e28fe..44d92026b 100644 --- a/src/components/middle/message/hocs/withSelectControl.tsx +++ b/src/components/middle/message/hocs/withSelectControl.tsx @@ -64,7 +64,7 @@ export default function withSelectControl(WrappedComponent: FC) { {isInSelectMode && (
{isSelected && ( - + )}
)} diff --git a/src/components/payment/Checkout.module.scss b/src/components/payment/Checkout.module.scss index 3b5ba33c9..568b407bf 100644 --- a/src/components/payment/Checkout.module.scss +++ b/src/components/payment/Checkout.module.scss @@ -76,7 +76,7 @@ background: var(--color-primary-opacity); color: var(--color-primary); transition: background-color 200ms, color 200ms; - cursor: pointer; + cursor: var(--custom-cursor, pointer); font-weight: 500; &:hover, diff --git a/src/components/payment/Checkout.tsx b/src/components/payment/Checkout.tsx index 335be29b6..9665de616 100644 --- a/src/components/payment/Checkout.tsx +++ b/src/components/payment/Checkout.tsx @@ -258,7 +258,7 @@ function renderCheckoutItem({ inactive={!onClick} onClick={onClick} > - {customIcon && } + {customIcon && }
{title || label}
diff --git a/src/components/payment/PaymentModal.tsx b/src/components/payment/PaymentModal.tsx index 429ad2ef4..744d49f74 100644 --- a/src/components/payment/PaymentModal.tsx +++ b/src/components/payment/PaymentModal.tsx @@ -550,7 +550,10 @@ const PaymentModal: FC = ({ onClick={step === PaymentStep.Checkout ? closeModal : handleBackClick} ariaLabel="Close" > - +

{modalHeader}

diff --git a/src/components/payment/ReceiptModal.tsx b/src/components/payment/ReceiptModal.tsx index a78633b05..77a09e71b 100644 --- a/src/components/payment/ReceiptModal.tsx +++ b/src/components/payment/ReceiptModal.tsx @@ -95,7 +95,7 @@ const ReceiptModal: FC = ({ onClick={closeModal} ariaLabel="Close" > - +

{lang('PaymentReceipt')}

diff --git a/src/components/right/AddChatMembers.tsx b/src/components/right/AddChatMembers.tsx index e0c8f4ea8..4be3e1d57 100644 --- a/src/components/right/AddChatMembers.tsx +++ b/src/components/right/AddChatMembers.tsx @@ -10,7 +10,7 @@ import type { import { NewChatMembersProgress } from '../../types'; import { unique } from '../../util/iteratees'; -import { selectChat, selectTabState } from '../../global/selectors'; +import { selectChat, selectChatFullInfo, selectTabState } from '../../global/selectors'; import { filterUsersByName, isChatChannel, isUserBot, sortChatIds, } from '../../global/helpers'; @@ -140,7 +140,7 @@ const AddChatMembers: FC = ({ {isLoading ? ( ) : ( - + )}
@@ -166,7 +166,7 @@ export default memo(withGlobal( return { isChannel, - members: chat?.fullInfo?.members, + members: selectChatFullInfo(global, chatId)?.members, currentUserId, chatsById, localContactIds, diff --git a/src/components/right/CreateTopic.tsx b/src/components/right/CreateTopic.tsx index 6440115e4..accff316a 100644 --- a/src/components/right/CreateTopic.tsx +++ b/src/components/right/CreateTopic.tsx @@ -110,7 +110,7 @@ const CreateTopic: FC = ({
{lang('CreateTopicTitle')} = ({ {isLoading ? ( ) : ( - + )}
diff --git a/src/components/right/EditTopic.tsx b/src/components/right/EditTopic.tsx index 1bf188b97..5b7b3f5d2 100644 --- a/src/components/right/EditTopic.tsx +++ b/src/components/right/EditTopic.tsx @@ -121,7 +121,7 @@ const EditTopic: FC = ({
{lang('CreateTopicTitle')} = ({ {isLoading ? ( ) : ( - + )}
diff --git a/src/components/right/ManageTopic.module.scss b/src/components/right/ManageTopic.module.scss index 1dd9a4403..2abdc857e 100644 --- a/src/components/right/ManageTopic.module.scss +++ b/src/components/right/ManageTopic.module.scss @@ -47,7 +47,7 @@ } .clickable { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } .heading { diff --git a/src/components/right/Profile.scss b/src/components/right/Profile.scss index 12834b385..f5300c02b 100644 --- a/src/components/right/Profile.scss +++ b/src/components/right/Profile.scss @@ -56,11 +56,11 @@ .Tab { padding: 1rem 0.75rem; - span { + &_inner { white-space: nowrap; } - i { + .platform { bottom: -1rem; } } diff --git a/src/components/right/Profile.tsx b/src/components/right/Profile.tsx index 1db64ce1c..d3ac10474 100644 --- a/src/components/right/Profile.tsx +++ b/src/components/right/Profile.tsx @@ -28,12 +28,13 @@ import { getHasAdminRight, isChatAdmin, isChatChannel, isChatGroup, isUserBot, isUserId, isUserRightBanned, } from '../../global/helpers'; import { - selectChatMessages, + selectActiveDownloadIds, selectChat, + selectChatFullInfo, + selectChatMessages, selectCurrentMediaSearch, selectIsRightColumnShown, selectTheme, - selectActiveDownloadIds, selectUser, } from '../../global/selectors'; import { captureEvents, SwipeDirection } from '../../util/captureEvents'; @@ -454,7 +455,7 @@ const Profile: FC = ({ .Transition__slide--active.${resultType}-list > .scroll-item`} + itemSelector={`.shared-media-transition > .Transition_slide-active.${resultType}-list > .scroll-item`} items={canRenderContent ? viewportIds : undefined} cacheBuster={cacheBuster} sensitiveArea={PROFILE_SENSITIVE_AREA} @@ -472,7 +473,7 @@ const Profile: FC = ({ > = ({ onClick={handleNewMemberDialogOpen} ariaLabel={lang('lng_channel_add_users')} > - + )} {canDeleteMembers && ( @@ -518,6 +519,7 @@ function renderProfileInfo(chatId: string, resolvedUserId: string | undefined, i export default memo(withGlobal( (global, { chatId, topicId, isMobile }): StateProps => { const chat = selectChat(global, chatId); + const chatFullInfo = selectChatFullInfo(global, chatId); const messagesById = selectChatMessages(global, chatId); const { currentType: mediaSearchType, resultsByType } = selectCurrentMediaSearch(global) || {}; const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {}; @@ -528,10 +530,10 @@ export default memo(withGlobal( const isGroup = chat && isChatGroup(chat); const isChannel = chat && isChatChannel(chat); const hasMembersTab = !topicId && (isGroup || (isChannel && isChatAdmin(chat!))); - const members = chat?.fullInfo?.members; - const adminMembersById = chat?.fullInfo?.adminMembersById; + const members = chatFullInfo?.members; + const adminMembersById = chatFullInfo?.adminMembersById; const areMembersHidden = hasMembersTab && chat - && (chat.isForbidden || (chat.fullInfo && !chat.fullInfo.canViewMembers)); + && (chat.isForbidden || (chatFullInfo && !chatFullInfo.canViewMembers)); const canAddMembers = hasMembersTab && chat && (getHasAdminRight(chat, 'inviteUsers') || !isUserRightBanned(chat, 'inviteUsers') || chat.isCreator); const canDeleteMembers = hasMembersTab && chat && (getHasAdminRight(chat, 'banUsers') || chat.isCreator); diff --git a/src/components/right/RightColumn.scss b/src/components/right/RightColumn.scss index 7debc9d51..e6606edfd 100644 --- a/src/components/right/RightColumn.scss +++ b/src/components/right/RightColumn.scss @@ -45,7 +45,7 @@ margin: 0 auto 0.5rem; &.no-photo { - cursor: default !important; + cursor: var(--custom-cursor, default) !important; } } diff --git a/src/components/right/RightColumn.tsx b/src/components/right/RightColumn.tsx index 53ecdc9ad..92caca433 100644 --- a/src/components/right/RightColumn.tsx +++ b/src/components/right/RightColumn.tsx @@ -357,7 +357,7 @@ const RightColumn: FC = ({ onScreenSelect={setManagementScreen} /> .Transition__slide { + > .Transition_slide { display: flex; align-items: center; } diff --git a/src/components/right/RightHeader.tsx b/src/components/right/RightHeader.tsx index 5c85deecc..0cb6da236 100644 --- a/src/components/right/RightHeader.tsx +++ b/src/components/right/RightHeader.tsx @@ -13,10 +13,12 @@ import { debounce } from '../../util/schedulers'; import buildClassName from '../../util/buildClassName'; import { selectChat, + selectChatFullInfo, selectCurrentGifSearch, - selectCurrentStickerSearch, selectTabState, + selectCurrentStickerSearch, selectCurrentTextSearch, selectIsChatWithSelf, + selectTabState, selectUser, } from '../../global/selectors'; import { @@ -328,7 +330,7 @@ const RightHeader: FC = ({ onClick={() => openHistoryCalendar({ selectedAt: getDayStartAt(Date.now()) })} ariaLabel="Search messages by date" > - + ); @@ -375,7 +377,7 @@ const RightHeader: FC = ({ ariaLabel={lang('Edit')} onClick={handleEditInviteClick} > - + )} {currentInviteInfo && currentInviteInfo.isRevoked && ( @@ -387,7 +389,7 @@ const RightHeader: FC = ({ ariaLabel={lang('Delete')} onClick={openDeleteDialog} > - + = ({ ariaLabel={lang('AddContact')} onClick={handleAddContact} > - + )} {canManage && !isInsideTopic && ( @@ -467,7 +469,7 @@ const RightHeader: FC = ({ ariaLabel={lang('Edit')} onClick={handleToggleManagement} > - + )} {canEditTopic && ( @@ -478,7 +480,7 @@ const RightHeader: FC = ({ ariaLabel={lang('EditTopic')} onClick={toggleEditTopic} > - + )} {canViewStatistics && ( @@ -489,7 +491,7 @@ const RightHeader: FC = ({ ariaLabel={lang('Statistics')} onClick={handleToggleStatistics} > - + )} @@ -526,7 +528,7 @@ const RightHeader: FC = ({
{renderHeaderContent()} @@ -562,7 +564,9 @@ export default memo(withGlobal( && (isUserId(chat.id) || ((isChatAdmin(chat) || chat.isCreator) && !chat.isNotJoined)), ); const isEditingInvite = Boolean(chatId && tabState.management.byChatId[chatId]?.editingInvite); - const canViewStatistics = !isInsideTopic && chat?.fullInfo?.canViewStatistics; + const canViewStatistics = !isInsideTopic && chatId + ? selectChatFullInfo(global, chatId)?.canViewStatistics + : undefined; const currentInviteInfo = chatId ? tabState.management.byChatId[chatId]?.inviteInfo?.invite : undefined; diff --git a/src/components/right/RightSearch.tsx b/src/components/right/RightSearch.tsx index bf315273a..8c54f59a1 100644 --- a/src/components/right/RightSearch.tsx +++ b/src/components/right/RightSearch.tsx @@ -4,7 +4,9 @@ import React, { import { getActions, getGlobal, withGlobal } from '../../global'; import type { FC } from '../../lib/teact/teact'; -import type { ApiMessage, ApiUser, ApiChat } from '../../api/types'; +import type { + ApiMessage, ApiUser, ApiChat, ApiPhoto, +} from '../../api/types'; import type { AnimationLevel } from '../../types'; import { MEMO_EMPTY_ARRAY } from '../../util/memo'; @@ -13,6 +15,7 @@ import { selectChatMessages, selectChat, selectCurrentTextSearch, + selectUserPhotoFromFullInfo, } from '../../global/selectors'; import { isChatChannel, @@ -102,6 +105,7 @@ const RightSearch: FC = ({ } const senderUser = message.senderId ? selectUser(getGlobal(), message.senderId) : undefined; + const senderUserProfilePhoto = senderUser ? selectUserPhotoFromFullInfo(getGlobal(), senderUser.id) : undefined; let senderChat; if (chat && isChatChannel(chat)) { @@ -116,6 +120,7 @@ const RightSearch: FC = ({ return { message, senderUser, + senderUserProfilePhoto, senderChat, onClick: () => focusMessage({ chatId, threadId, messageId: id }), }; @@ -130,11 +135,12 @@ const RightSearch: FC = ({ }, '.ListItem-button', true); const renderSearchResult = ({ - message, senderUser, senderChat, onClick, + message, senderUser, senderChat, senderUserProfilePhoto, onClick, }: { message: ApiMessage; senderUser?: ApiUser; senderChat?: ApiChat; + senderUserProfilePhoto?: ApiPhoto; onClick: NoneToVoidFunction; }) => { const text = renderMessageSummary(lang, message, undefined, query); @@ -146,7 +152,13 @@ const RightSearch: FC = ({ className="chat-item-clickable search-result-message m-0" onClick={onClick} > - +
diff --git a/src/components/right/management/JoinRequest.scss b/src/components/right/management/JoinRequest.scss index 518f1dc28..669ae4605 100644 --- a/src/components/right/management/JoinRequest.scss +++ b/src/components/right/management/JoinRequest.scss @@ -9,7 +9,7 @@ &__top { display: flex; - cursor: pointer; + cursor: var(--custom-cursor, pointer); padding: 0.5rem; border-radius: 0.5rem; diff --git a/src/components/right/management/JoinRequest.tsx b/src/components/right/management/JoinRequest.tsx index 844e69eb8..147ea009e 100644 --- a/src/components/right/management/JoinRequest.tsx +++ b/src/components/right/management/JoinRequest.tsx @@ -3,11 +3,11 @@ import React, { memo, useCallback } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { AnimationLevel } from '../../../types'; -import type { ApiUser } from '../../../api/types'; +import type { ApiPhoto, ApiUser } from '../../../api/types'; import useLang from '../../../hooks/useLang'; import { getUserFullName } from '../../../global/helpers'; -import { selectUser } from '../../../global/selectors'; +import { selectUser, selectUserPhotoFromFullInfo } from '../../../global/selectors'; import { formatHumanDate, formatTime, isToday } from '../../../util/dateFormat'; import { getServerTime } from '../../../util/serverTime'; import { createClassNameBuilder } from '../../../util/buildClassName'; @@ -27,6 +27,7 @@ type OwnProps = { type StateProps = { user?: ApiUser; + userProfilePhoto?: ApiPhoto; isSavedMessages?: boolean; animationLevel: AnimationLevel; }; @@ -38,6 +39,7 @@ const JoinRequest: FC = ({ date, isChannel, user, + userProfilePhoto, animationLevel, }) => { const { openChat, hideChatJoinRequest } = getActions(); @@ -71,6 +73,7 @@ const JoinRequest: FC = ({ key={userId} size="medium" user={user} + userProfilePhoto={userProfilePhoto} animationLevel={animationLevel} withVideo /> @@ -99,6 +102,7 @@ export default memo(withGlobal( return { user, + userProfilePhoto: user ? selectUserPhotoFromFullInfo(global, userId) : undefined, animationLevel: global.settings.byKey.animationLevel, }; }, diff --git a/src/components/right/management/ManageChannel.tsx b/src/components/right/management/ManageChannel.tsx index 5e3feb067..67d51b28b 100644 --- a/src/components/right/management/ManageChannel.tsx +++ b/src/components/right/management/ManageChannel.tsx @@ -5,14 +5,16 @@ import React, { } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import { ManagementScreens, ManagementProgress } from '../../../types'; -import type { ApiAvailableReaction, ApiChat, ApiExportedInvite } from '../../../api/types'; +import { ManagementProgress, ManagementScreens } from '../../../types'; +import type { + ApiAvailableReaction, ApiChat, ApiChatFullInfo, ApiExportedInvite, +} from '../../../api/types'; import { ApiMediaFormat } from '../../../api/types'; import { getChatAvatarHash, getHasAdminRight, isChatPublic } from '../../../global/helpers'; import useMedia from '../../../hooks/useMedia'; import useLang from '../../../hooks/useLang'; -import { selectChat, selectTabState } from '../../../global/selectors'; +import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors'; import useFlag from '../../../hooks/useFlag'; import useHistoryBack from '../../../hooks/useHistoryBack'; import { formatInteger } from '../../../util/textFormat'; @@ -37,6 +39,7 @@ type OwnProps = { type StateProps = { chat: ApiChat; + chatFullInfo?: ApiChatFullInfo; progress?: ManagementProgress; isSignaturesShown: boolean; canChangeInfo?: boolean; @@ -52,6 +55,7 @@ const CHANNEL_MAX_DESCRIPTION = 255; const ManageChannel: FC = ({ chatId, chat, + chatFullInfo, progress, isSignaturesShown, canChangeInfo, @@ -75,8 +79,8 @@ const ManageChannel: FC = ({ } = getActions(); const currentTitle = chat?.title || ''; - const currentAbout = chat?.fullInfo ? (chat.fullInfo.about || '') : ''; - const hasLinkedChat = chat?.fullInfo?.linkedChatId; + const currentAbout = chatFullInfo?.about || ''; + const hasLinkedChat = Boolean(chatFullInfo?.linkedChatId); const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag(); const [isProfileFieldsTouched, setIsProfileFieldsTouched] = useState(false); @@ -108,8 +112,10 @@ const ManageChannel: FC = ({ } }, [progress]); - const adminsCount = Object.keys(chat.fullInfo?.adminMembersById || {}).length; - const removedUsersCount = (chat?.fullInfo?.kickedMembers?.length) || 0; + const adminsCount = useMemo(() => { + return Object.keys(chatFullInfo?.adminMembersById || {}).length; + }, [chatFullInfo?.adminMembersById]); + const removedUsersCount = chatFullInfo?.kickedMembers?.length || 0; const handleClickEditType = useCallback(() => { onScreenSelect(ManagementScreens.ChatPrivacyType); @@ -192,20 +198,19 @@ const ManageChannel: FC = ({ }, [chat.isCreator, chat.id, closeDeleteDialog, closeManagement, leaveChannel, deleteChannel, openChat]); const chatReactionsDescription = useMemo(() => { - if (!chat.fullInfo?.enabledReactions) { + if (!chatFullInfo?.enabledReactions) { return lang('ReactionsOff'); } - if (chat.fullInfo.enabledReactions.type === 'all') { + if (chatFullInfo.enabledReactions.type === 'all') { return lang('ReactionsAll'); } - const enabledLength = chat.fullInfo.enabledReactions.allowed.length; + const enabledLength = chatFullInfo.enabledReactions.allowed.length; const totalLength = availableReactions?.filter((reaction) => !reaction.isInactive).length || 0; - const text = totalLength ? `${enabledLength} / ${totalLength}` : `${enabledLength}`; - return text; - }, [availableReactions, chat, lang]); + return totalLength ? `${enabledLength} / ${totalLength}` : `${enabledLength}`; + }, [availableReactions, chatFullInfo?.enabledReactions, lang]); const isChannelPublic = useMemo(() => isChatPublic(chat), [chat]); if (chat.isRestricted || chat.isForbidden) { @@ -343,7 +348,7 @@ const ManageChannel: FC = ({ {isLoading ? ( ) : ( - + )} ( return { chat, + chatFullInfo: selectChatFullInfo(global, chatId), progress, isSignaturesShown, canChangeInfo: getHasAdminRight(chat, 'changeInfo'), diff --git a/src/components/right/management/ManageChatAdministrators.tsx b/src/components/right/management/ManageChatAdministrators.tsx index fa27fc24c..8b24924a4 100644 --- a/src/components/right/management/ManageChatAdministrators.tsx +++ b/src/components/right/management/ManageChatAdministrators.tsx @@ -6,7 +6,7 @@ import { ManagementScreens } from '../../../types'; import type { ApiChat, ApiChatMember } from '../../../api/types'; import { getUserFullName, isChatChannel } from '../../../global/helpers'; -import { selectChat } from '../../../global/selectors'; +import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import useLang from '../../../hooks/useLang'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -26,12 +26,14 @@ type StateProps = { chat: ApiChat; currentUserId?: string; isChannel: boolean; + adminMembersById?: Record; }; const ManageChatAdministrators: FC = ({ chat, isChannel, currentUserId, + adminMembersById, onScreenSelect, onChatMemberSelect, onClose, @@ -44,18 +46,14 @@ const ManageChatAdministrators: FC = ({ onBack: onClose, }); - const handleRecentActionsClick = useCallback(() => { - onScreenSelect(ManagementScreens.GroupRecentActions); - }, [onScreenSelect]); - const canAddNewAdmins = Boolean(chat.isCreator || chat.adminRights?.addAdmins); const adminMembers = useMemo(() => { - if (!chat.fullInfo?.adminMembersById) { + if (!adminMembersById) { return []; } - return Object.values(chat.fullInfo.adminMembersById).sort((a, b) => { + return Object.values(adminMembersById).sort((a, b) => { if (a.isOwner) { return -1; } else if (b.isOwner) { @@ -64,7 +62,7 @@ const ManageChatAdministrators: FC = ({ return 0; }); - }, [chat]); + }, [adminMembersById]); const handleAdminMemberClick = useCallback((member: ApiChatMember) => { onChatMemberSelect(member.userId, member.promotedByUserId === currentUserId); @@ -98,7 +96,7 @@ const ManageChatAdministrators: FC = ({ {lang('EventLog')} {lang(isChannel ? 'EventLogInfoDetailChannel' : 'EventLogInfoDetail')} @@ -132,7 +130,7 @@ const ManageChatAdministrators: FC = ({ onClick={handleAddAdminClick} ariaLabel={lang('Channel.Management.AddModerator')} > - +
@@ -148,6 +146,7 @@ export default memo(withGlobal( chat, currentUserId: global.currentUserId, isChannel: isChatChannel(chat), + adminMembersById: selectChatFullInfo(global, chatId)?.adminMembersById, }; }, )(ManageChatAdministrators)); diff --git a/src/components/right/management/ManageChatPrivacyType.tsx b/src/components/right/management/ManageChatPrivacyType.tsx index 7fe5445c3..84b77c219 100644 --- a/src/components/right/management/ManageChatPrivacyType.tsx +++ b/src/components/right/management/ManageChatPrivacyType.tsx @@ -9,7 +9,9 @@ import type { ApiChat } from '../../../api/types'; import { ManagementProgress } from '../../../types'; import { PURCHASE_USERNAME, TME_LINK_PREFIX, USERNAME_PURCHASE_ERROR } from '../../../config'; -import { selectChat, selectTabState, selectManagement } from '../../../global/selectors'; +import { + selectChat, selectTabState, selectManagement, selectChatFullInfo, +} from '../../../global/selectors'; import { isChatChannel, isChatPublic } from '../../../global/helpers'; import { selectCurrentLimit } from '../../../global/selectors/limits'; @@ -45,6 +47,7 @@ type StateProps = { error?: string; isProtected?: boolean; maxPublicLinks: number; + privateInviteLink?: string; }; const ManageChatPrivacyType: FC = ({ @@ -57,6 +60,7 @@ const ManageChatPrivacyType: FC = ({ error, isProtected, maxPublicLinks, + privateInviteLink, onClose, }) => { const { @@ -69,7 +73,6 @@ const ManageChatPrivacyType: FC = ({ const firstEditableUsername = useMemo(() => chat.usernames?.find(({ isEditable }) => isEditable), [chat.usernames]); const currentUsername = firstEditableUsername?.username || ''; const isPublic = useMemo(() => isChatPublic(chat), [chat]); - const privateLink = chat.fullInfo?.inviteLink; const [isProfileFieldsTouched, setIsProfileFieldsTouched] = useState(false); const [privacyType, setPrivacyType] = useState(isPublic ? 'public' : 'private'); @@ -97,10 +100,10 @@ const ManageChatPrivacyType: FC = ({ }, [currentUsername]); useEffect(() => { - if (privacyType && !privateLink) { + if (privacyType && !privateInviteLink) { updatePrivateLink(); } - }, [privacyType, privateLink, updatePrivateLink]); + }, [privacyType, privateInviteLink, updatePrivateLink]); const handleUsernameChange = useCallback((value: string) => { setEditableUsername(value); @@ -198,9 +201,9 @@ const ManageChatPrivacyType: FC = ({
{privacyType === 'private' ? (
- {privateLink ? ( + {privateInviteLink ? ( <> - +

{lang(`${langPrefix1}PrivateLinkHelp`)}

@@ -270,7 +273,7 @@ const ManageChatPrivacyType: FC = ({ {isLoading ? ( ) : ( - + )} ( checkedUsername, isProtected: chat?.isProtected, maxPublicLinks: selectCurrentLimit(global, 'channelsPublic'), + privateInviteLink: selectChatFullInfo(global, chatId)?.inviteLink, }; }, )(ManageChatPrivacyType)); diff --git a/src/components/right/management/ManageChatRemovedUsers.tsx b/src/components/right/management/ManageChatRemovedUsers.tsx index 2b9567e2c..b63d31009 100644 --- a/src/components/right/management/ManageChatRemovedUsers.tsx +++ b/src/components/right/management/ManageChatRemovedUsers.tsx @@ -1,10 +1,11 @@ import type { FC } from '../../../lib/teact/teact'; -import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; +import React, { memo, useCallback } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { ApiChat, ApiChatMember, ApiUser } from '../../../api/types'; -import { selectChat } from '../../../global/selectors'; +import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; +import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import { getHasAdminRight, getUserFullName, isChatChannel } from '../../../global/helpers'; import useLang from '../../../hooks/useLang'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -24,6 +25,7 @@ type OwnProps = { type StateProps = { chat?: ApiChat; usersById: Record; + removedMembers: ApiChatMember[]; canDeleteMembers?: boolean; isChannel?: boolean; }; @@ -32,6 +34,7 @@ const ManageChatRemovedUsers: FC = ({ chat, usersById, canDeleteMembers, + removedMembers, isChannel, onClose, isActive, @@ -46,14 +49,6 @@ const ManageChatRemovedUsers: FC = ({ onBack: onClose, }); - const removedMembers = useMemo(() => { - if (!chat || !chat.fullInfo || !chat.fullInfo.kickedMembers) { - return []; - } - - return chat.fullInfo.kickedMembers; - }, [chat]); - const getRemovedBy = useCallback((member: ApiChatMember) => { if (!member.kickedByUserId) { return undefined; @@ -110,7 +105,7 @@ const ManageChatRemovedUsers: FC = ({ onClick={openRemoveUserModal} ariaLabel={lang('Channel.EditAdmin.Permission.BanUsers')} > - + )} {chat && canDeleteMembers && ( @@ -136,6 +131,7 @@ export default memo(withGlobal( chat, usersById, canDeleteMembers, + removedMembers: selectChatFullInfo(global, chatId)?.kickedMembers || MEMO_EMPTY_ARRAY, isChannel: chat && isChatChannel(chat), }; }, diff --git a/src/components/right/management/ManageDiscussion.tsx b/src/components/right/management/ManageDiscussion.tsx index 95fd44959..290555050 100644 --- a/src/components/right/management/ManageDiscussion.tsx +++ b/src/components/right/management/ManageDiscussion.tsx @@ -9,7 +9,7 @@ import { ManagementScreens } from '../../../types'; import { STICKER_SIZE_DISCUSSION_GROUPS } from '../../../config'; import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; -import { selectChat } from '../../../global/selectors'; +import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import useLang from '../../../hooks/useLang'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -208,7 +208,7 @@ const ManageDiscussion: FC = ({ icon="group" ripple teactOrderKey={0} - className="not-implemented" + disabled > {lang('DiscussionCreateGroup')} @@ -283,9 +283,10 @@ const ManageDiscussion: FC = ({ export default memo(withGlobal( (global, { chatId }): StateProps => { const chat = selectChat(global, chatId); + const { linkedChatId } = selectChatFullInfo(global, chatId) || {}; const { forDiscussionIds, byId: chatsByIds } = global.chats; - const linkedChat = chat?.fullInfo?.linkedChatId - ? selectChat(global, chat.fullInfo.linkedChatId) + const linkedChat = linkedChatId + ? selectChat(global, linkedChatId) : undefined; return { diff --git a/src/components/right/management/ManageGroup.tsx b/src/components/right/management/ManageGroup.tsx index 07434e528..a6e3583f2 100644 --- a/src/components/right/management/ManageGroup.tsx +++ b/src/components/right/management/ManageGroup.tsx @@ -7,7 +7,7 @@ import { getActions, withGlobal } from '../../../global'; import { ManagementProgress, ManagementScreens } from '../../../types'; import type { - ApiAvailableReaction, ApiChat, ApiChatBannedRights, ApiExportedInvite, + ApiAvailableReaction, ApiChat, ApiChatBannedRights, ApiChatFullInfo, ApiExportedInvite, } from '../../../api/types'; import { ApiMediaFormat } from '../../../api/types'; @@ -18,7 +18,7 @@ import { isChatPublic, } from '../../../global/helpers'; import { debounce } from '../../../util/schedulers'; -import { selectChat, selectTabState } from '../../../global/selectors'; +import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors'; import { formatInteger } from '../../../util/textFormat'; import renderText from '../../common/helpers/renderText'; import useMedia from '../../../hooks/useMedia'; @@ -47,6 +47,7 @@ type OwnProps = { type StateProps = { chat: ApiChat; + chatFullInfo?: ApiChatFullInfo; progress?: ManagementProgress; isBasicGroup: boolean; hasLinkedChannel: boolean; @@ -87,6 +88,7 @@ const runDebounced = debounce((cb) => cb(), 500, false); const ManageGroup: FC = ({ chatId, chat, + chatFullInfo, progress, isBasicGroup, hasLinkedChannel, @@ -117,7 +119,7 @@ const ManageGroup: FC = ({ const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag(); const currentTitle = chat.title; - const currentAbout = chat.fullInfo ? (chat.fullInfo.about || '') : ''; + const currentAbout = chatFullInfo?.about || ''; const [isProfileFieldsTouched, setIsProfileFieldsTouched] = useState(false); const [title, setTitle] = useState(currentTitle); @@ -222,14 +224,14 @@ const ManageGroup: FC = ({ }, [onScreenSelect]); const handleTogglePreHistory = useCallback(() => { - if (!chat.fullInfo) { + if (!chatFullInfo) { return; } - const { isPreHistoryHidden } = chat.fullInfo; + const { isPreHistoryHidden } = chatFullInfo; togglePreHistoryHidden({ chatId: chat.id, isEnabled: !isPreHistoryHidden }); - }, [chat, togglePreHistoryHidden]); + }, [chat.id, chatFullInfo]); const handleForumToggle = useCallback(() => { setIsForumEnabled((current) => { @@ -251,25 +253,25 @@ const ManageGroup: FC = ({ // Teact does not have full support of controlled form components, we need to "disable" input value change manually // TODO Teact support added, this can now be removed const checkbox = isPreHistoryHiddenCheckboxRef.current?.querySelector('input') as HTMLInputElement; - checkbox.checked = !chat.fullInfo?.isPreHistoryHidden; - }, [isChannelsPremiumLimitReached, chat.fullInfo?.isPreHistoryHidden]); + checkbox.checked = !chatFullInfo?.isPreHistoryHidden; + }, [isChannelsPremiumLimitReached, chatFullInfo?.isPreHistoryHidden]); const chatReactionsDescription = useMemo(() => { - if (!chat.fullInfo?.enabledReactions) { + if (!chatFullInfo?.enabledReactions) { return lang('ReactionsOff'); } - if (chat.fullInfo.enabledReactions.type === 'all') { + if (chatFullInfo.enabledReactions.type === 'all') { return lang('ReactionsAll'); } - const enabledLength = chat.fullInfo.enabledReactions.allowed.length; + const enabledLength = chatFullInfo.enabledReactions.allowed.length; const totalLength = availableReactions?.filter((reaction) => !reaction.isInactive).length || 0; return totalLength ? `${enabledLength} / ${totalLength}` : `${enabledLength}`; - }, [availableReactions, chat, lang]); + }, [availableReactions, chatFullInfo?.enabledReactions, lang]); const enabledPermissionsCount = useMemo(() => { if (!chat.defaultBannedRights) { @@ -294,8 +296,8 @@ const ManageGroup: FC = ({ }, [chat.defaultBannedRights, isForumEnabled]); const adminsCount = useMemo(() => { - return Object.keys(chat.fullInfo?.adminMembersById || {}).length; - }, [chat.fullInfo?.adminMembersById]); + return Object.keys(chatFullInfo?.adminMembersById || {}).length; + }, [chatFullInfo?.adminMembersById]); const handleDeleteGroup = useCallback(() => { if (isBasicGroup) { @@ -440,10 +442,10 @@ const ManageGroup: FC = ({ {formatInteger(chat.membersCount ?? 0)} - {!isPublicGroup && chat.fullInfo && ( + {!isPublicGroup && Boolean(chatFullInfo) && (
= ({ {isLoading ? ( ) : ( - + )} = ({ export default memo(withGlobal( (global, { chatId }): StateProps => { const chat = selectChat(global, chatId)!; + const chatFullInfo = selectChatFullInfo(global, chatId); const { management, limitReachedModal } = selectTabState(global); const { progress } = management; - const hasLinkedChannel = Boolean(chat.fullInfo?.linkedChatId); + const hasLinkedChannel = Boolean(chatFullInfo?.linkedChatId); const isBasicGroup = isChatBasicGroup(chat); const { invites } = management.byChatId[chatId] || {}; const canEditForum = !hasLinkedChannel && (getHasAdminRight(chat, 'changeInfo') || chat.isCreator); return { chat, + chatFullInfo, progress, isBasicGroup, hasLinkedChannel, diff --git a/src/components/right/management/ManageGroupAdminRights.tsx b/src/components/right/management/ManageGroupAdminRights.tsx index a7ffade61..7b9ad15fa 100644 --- a/src/components/right/management/ManageGroupAdminRights.tsx +++ b/src/components/right/management/ManageGroupAdminRights.tsx @@ -4,10 +4,12 @@ import React, { } from '../../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../../global'; -import type { ApiChat, ApiChatAdminRights, ApiUser } from '../../../api/types'; +import type { + ApiChat, ApiChatAdminRights, ApiChatMember, ApiUser, +} from '../../../api/types'; import { ManagementScreens } from '../../../types'; -import { selectChat } from '../../../global/selectors'; +import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import { getUserFullName, isChatBasicGroup, isChatChannel } from '../../../global/helpers'; import useLang from '../../../hooks/useLang'; import useFlag from '../../../hooks/useFlag'; @@ -34,6 +36,8 @@ type OwnProps = { type StateProps = { chat: ApiChat; usersById: Record; + adminMembersById?: Record; + hasFullInfo: boolean; currentUserId?: string; isChannel: boolean; isFormFullyDisabled: boolean; @@ -51,6 +55,8 @@ const ManageGroupAdminRights: FC = ({ chat, usersById, currentUserId, + adminMembersById, + hasFullInfo, isChannel, isForum, isFormFullyDisabled, @@ -72,7 +78,7 @@ const ManageGroupAdminRights: FC = ({ }); const selectedChatMember = useMemo(() => { - const selectedAdminMember = selectedUserId ? chat.fullInfo?.adminMembersById?.[selectedUserId] : undefined; + const selectedAdminMember = selectedUserId ? adminMembersById?.[selectedUserId] : undefined; // If `selectedAdminMember` variable is filled with a value, then we have already saved the administrator, // so now we need to return to the list of administrators @@ -93,13 +99,13 @@ const ManageGroupAdminRights: FC = ({ } return selectedAdminMember; - }, [chat.fullInfo?.adminMembersById, defaultRights, isNewAdmin, lang, selectedUserId]); + }, [adminMembersById, defaultRights, isNewAdmin, lang, selectedUserId]); useEffect(() => { - if (chat?.fullInfo && selectedUserId && !selectedChatMember) { + if (hasFullInfo && selectedUserId && !selectedChatMember) { onScreenSelect(ManagementScreens.ChatAdministrators); } - }, [chat, onScreenSelect, selectedChatMember, selectedUserId]); + }, [chat, hasFullInfo, onScreenSelect, selectedChatMember, selectedUserId]); useEffect(() => { setPermissions(selectedChatMember?.adminRights || {}); @@ -366,7 +372,7 @@ const ManageGroupAdminRights: FC = ({ {isLoading ? ( ) : ( - + )} @@ -387,6 +393,7 @@ const ManageGroupAdminRights: FC = ({ export default memo(withGlobal( (global, { chatId, isPromotedByCurrentUser }): StateProps => { const chat = selectChat(global, chatId)!; + const fullInfo = selectChatFullInfo(global, chatId); const { byId: usersById } = global.users; const { currentUserId } = global; const isChannel = isChatChannel(chat); @@ -401,6 +408,8 @@ export default memo(withGlobal( isForum, isFormFullyDisabled, defaultRights: chat.adminRights, + hasFullInfo: Boolean(fullInfo), + adminMembersById: fullInfo?.adminMembersById, }; }, )(ManageGroupAdminRights)); diff --git a/src/components/right/management/ManageGroupMembers.tsx b/src/components/right/management/ManageGroupMembers.tsx index b318fdecb..629df1f18 100644 --- a/src/components/right/management/ManageGroupMembers.tsx +++ b/src/components/right/management/ManageGroupMembers.tsx @@ -8,7 +8,7 @@ import type { ApiChatMember, ApiUserStatus } from '../../../api/types'; import { ManagementScreens } from '../../../types'; import { unique } from '../../../util/iteratees'; -import { selectChat, selectTabState } from '../../../global/selectors'; +import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors'; import { sortUserIds, isChatChannel, filterUsersByName, sortChatIds, isUserBot, getHasAdminRight, isChatBasicGroup, } from '../../../global/helpers'; @@ -251,8 +251,7 @@ export default memo(withGlobal( (global, { chatId }): StateProps => { const chat = selectChat(global, chatId); const { statusesById: userStatusesById } = global.users; - const members = chat?.fullInfo?.members; - const adminMembersById = chat?.fullInfo?.adminMembersById; + const { members, adminMembersById, areParticipantsHidden } = selectChatFullInfo(global, chatId) || {}; const isChannel = chat && isChatChannel(chat); const { userIds: localContactIds } = global.contactList || {}; const hiddenMembersMinCount = global.appConfig?.hiddenMembersMinCount; @@ -270,7 +269,7 @@ export default memo(withGlobal( } = selectTabState(global).userSearch; return { - areParticipantsHidden: Boolean(chat && chat.fullInfo?.areParticipantsHidden), + areParticipantsHidden: Boolean(chat && areParticipantsHidden), members, adminMembersById, userStatusesById, diff --git a/src/components/right/management/ManageGroupPermissions.tsx b/src/components/right/management/ManageGroupPermissions.tsx index 07bd9cffc..664d4d9fd 100644 --- a/src/components/right/management/ManageGroupPermissions.tsx +++ b/src/components/right/management/ManageGroupPermissions.tsx @@ -10,8 +10,8 @@ import type { ApiChat, ApiChatBannedRights, ApiChatMember } from '../../../api/t import stopEvent from '../../../util/stopEvent'; import buildClassName from '../../../util/buildClassName'; import { isChatPublic } from '../../../global/helpers'; +import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import useLang from '../../../hooks/useLang'; -import { selectChat } from '../../../global/selectors'; import useHistoryBack from '../../../hooks/useHistoryBack'; import useManagePermissions from '../hooks/useManagePermissions'; @@ -33,6 +33,8 @@ type StateProps = { chat?: ApiChat; currentUserId?: string; hasLinkedChat?: boolean; + removedUsersCount: number; + members?: ApiChatMember[]; }; const ITEM_HEIGHT = 24 + 32; @@ -84,6 +86,8 @@ const ManageGroupPermissions: FC = ({ chat, currentUserId, hasLinkedChat, + removedUsersCount, + members, onClose, isActive, }) => { @@ -134,21 +138,13 @@ const ManageGroupPermissions: FC = ({ updateChatDefaultBannedRights({ chatId: chat.id, bannedRights: permissions }); }, [chat, permissions, setIsLoading, updateChatDefaultBannedRights]); - const removedUsersCount = useMemo(() => { - if (!chat || !chat.fullInfo || !chat.fullInfo.kickedMembers) { - return 0; - } - - return chat.fullInfo.kickedMembers.length; - }, [chat]); - const exceptionMembers = useMemo(() => { - if (!chat || !chat.fullInfo || !chat.fullInfo.members) { + if (!members) { return []; } - return chat.fullInfo.members.filter(({ bannedRights }) => Boolean(bannedRights)); - }, [chat]); + return members.filter(({ bannedRights }) => Boolean(bannedRights)); + }, [members]); const getMemberExceptions = useCallback((member: ApiChatMember) => { const { bannedRights } = member; @@ -417,7 +413,7 @@ const ManageGroupPermissions: FC = ({ {isLoading ? ( ) : ( - + )}
@@ -427,8 +423,15 @@ const ManageGroupPermissions: FC = ({ export default memo(withGlobal( (global, { chatId }): StateProps => { const chat = selectChat(global, chatId); - const hasLinkedChat = Boolean(chat?.fullInfo?.linkedChatId); + const fullInfo = selectChatFullInfo(global, chatId); + const hasLinkedChat = Boolean(fullInfo?.linkedChatId); - return { chat, currentUserId: global.currentUserId, hasLinkedChat }; + return { + chat, + currentUserId: global.currentUserId, + hasLinkedChat, + removedUsersCount: fullInfo?.kickedMembers?.length || 0, + members: fullInfo?.members, + }; }, )(ManageGroupPermissions)); diff --git a/src/components/right/management/ManageGroupRecentActions.tsx b/src/components/right/management/ManageGroupRecentActions.tsx deleted file mode 100644 index adea6f838..000000000 --- a/src/components/right/management/ManageGroupRecentActions.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import type { FC } from '../../../lib/teact/teact'; -import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; -import { withGlobal } from '../../../global'; - -import type { ApiChat, ApiChatMember } from '../../../api/types'; -import useLang from '../../../hooks/useLang'; -import { selectChat } from '../../../global/selectors'; -import useHistoryBack from '../../../hooks/useHistoryBack'; - -import ListItem from '../../ui/ListItem'; -import Checkbox from '../../ui/Checkbox'; -import PrivateChatInfo from '../../common/PrivateChatInfo'; - -type OwnProps = { - chatId: string; - onClose: NoneToVoidFunction; - isActive: boolean; -}; - -type StateProps = { - chat?: ApiChat; -}; - -const ManageGroupRecentActions: FC = ({ chat, onClose, isActive }) => { - const lang = useLang(); - - useHistoryBack({ - isActive, - onBack: onClose, - }); - - const adminMembers = useMemo(() => { - if (!chat?.fullInfo?.adminMembersById) { - return []; - } - - return Object.values(chat.fullInfo.adminMembersById).sort((a, b) => { - if (a.isOwner) { - return -1; - } else if (b.isOwner) { - return 1; - } - - return 0; - }); - }, [chat]); - - const getMemberStatus = useCallback((member: ApiChatMember) => { - if (member.isOwner) { - return lang('ChannelCreator'); - } - - return lang('ChannelAdmin'); - }, [lang]); - - return ( -
-
-
-

Actions

- -
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-

{lang('Channel.Management.Title')}

- -
- -
- - {adminMembers.map((member) => ( - - - - - ))} -
-
-
- ); -}; - -export default memo(withGlobal( - (global, { chatId }): StateProps => { - const chat = selectChat(global, chatId); - - return { chat }; - }, -)(ManageGroupRecentActions)); diff --git a/src/components/right/management/ManageGroupUserPermissions.tsx b/src/components/right/management/ManageGroupUserPermissions.tsx index 6c627c569..0f43b77f7 100644 --- a/src/components/right/management/ManageGroupUserPermissions.tsx +++ b/src/components/right/management/ManageGroupUserPermissions.tsx @@ -4,10 +4,10 @@ import React, { } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import type { ApiChat, ApiChatBannedRights } from '../../../api/types'; +import type { ApiChat, ApiChatBannedRights, ApiChatMember } from '../../../api/types'; import { ManagementScreens } from '../../../types'; -import { selectChat } from '../../../global/selectors'; +import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import stopEvent from '../../../util/stopEvent'; import buildClassName from '../../../util/buildClassName'; import useManagePermissions from '../hooks/useManagePermissions'; @@ -33,6 +33,8 @@ type OwnProps = { type StateProps = { chat?: ApiChat; + hasFullInfo?: boolean; + members?: ApiChatMember[]; isFormFullyDisabled?: boolean; }; @@ -45,6 +47,8 @@ const ITEMS_COUNT = 9; const ManageGroupUserPermissions: FC = ({ chat, selectedChatMemberId, + hasFullInfo, + members, onScreenSelect, isFormFullyDisabled, onClose, @@ -53,12 +57,12 @@ const ManageGroupUserPermissions: FC = ({ const { updateChatMemberBannedRights } = getActions(); const selectedChatMember = useMemo(() => { - if (!chat || !chat.fullInfo || !chat.fullInfo.members) { + if (!members) { return undefined; } - return chat.fullInfo.members.find(({ userId }) => userId === selectedChatMemberId); - }, [chat, selectedChatMemberId]); + return members.find(({ userId }) => userId === selectedChatMemberId); + }, [members, selectedChatMemberId]); const { permissions, havePermissionChanged, isLoading, handlePermissionChange, setIsLoading, @@ -74,10 +78,10 @@ const ManageGroupUserPermissions: FC = ({ }); useEffect(() => { - if (chat?.fullInfo && selectedChatMemberId && !selectedChatMember) { + if (hasFullInfo && selectedChatMemberId && !selectedChatMember) { onScreenSelect(ManagementScreens.GroupPermissions); } - }, [chat, onScreenSelect, selectedChatMember, selectedChatMemberId]); + }, [chat, hasFullInfo, onScreenSelect, selectedChatMember, selectedChatMemberId]); const handleSavePermissions = useCallback(() => { if (!chat || !selectedChatMemberId) { @@ -344,7 +348,7 @@ const ManageGroupUserPermissions: FC = ({ {isLoading ? ( ) : ( - + )} @@ -363,8 +367,14 @@ const ManageGroupUserPermissions: FC = ({ export default memo(withGlobal( (global, { chatId, isPromotedByCurrentUser }): StateProps => { const chat = selectChat(global, chatId)!; + const fullInfo = selectChatFullInfo(global, chatId); const isFormFullyDisabled = !(chat.isCreator || isPromotedByCurrentUser); - return { chat, isFormFullyDisabled }; + return { + chat, + isFormFullyDisabled, + hasFullInfo: Boolean(fullInfo), + members: fullInfo?.members, + }; }, )(ManageGroupUserPermissions)); diff --git a/src/components/right/management/ManageGroupUserPermissionsCreate.tsx b/src/components/right/management/ManageGroupUserPermissionsCreate.tsx index 1c4cca1fb..77b0995ec 100644 --- a/src/components/right/management/ManageGroupUserPermissionsCreate.tsx +++ b/src/components/right/management/ManageGroupUserPermissionsCreate.tsx @@ -5,7 +5,7 @@ import { withGlobal } from '../../../global'; import type { ApiChatMember, ApiUser, ApiUserStatus } from '../../../api/types'; import { ManagementScreens } from '../../../types'; -import { selectChat } from '../../../global/selectors'; +import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import { sortUserIds, isChatChannel } from '../../../global/helpers'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -93,7 +93,7 @@ export default memo(withGlobal( (global, { chatId }): StateProps => { const chat = selectChat(global, chatId); const { byId: usersById, statusesById: userStatusesById } = global.users; - const members = chat?.fullInfo?.members; + const members = selectChatFullInfo(global, chatId)?.members; const isChannel = chat && isChatChannel(chat); return { diff --git a/src/components/right/management/ManageInvite.tsx b/src/components/right/management/ManageInvite.tsx index 509c438a2..9ae1bfd50 100644 --- a/src/components/right/management/ManageInvite.tsx +++ b/src/components/right/management/ManageInvite.tsx @@ -248,7 +248,7 @@ const ManageInvite: FC = ({ disabled={isSubmitBlocked} ariaLabel={editingInvite ? lang('SaveLink') : lang('CreateLink')} > - +
= ({ onClick={onTrigger} ariaLabel="Actions" > - + ); }, [isMobile]); @@ -337,7 +337,7 @@ const ManageInvites: FC = ({ {(!temporalInvites || !temporalInvites.length) && } {temporalInvites?.map((invite) => ( } + leftElement={} secondaryIcon="more" multiline // eslint-disable-next-line react/jsx-no-bind @@ -366,7 +366,7 @@ const ManageInvites: FC = ({ {revokedExportedInvites?.map((invite) => ( } + leftElement={} secondaryIcon="more" multiline // eslint-disable-next-line react/jsx-no-bind diff --git a/src/components/right/management/ManageReactions.tsx b/src/components/right/management/ManageReactions.tsx index 316461a46..24c310343 100644 --- a/src/components/right/management/ManageReactions.tsx +++ b/src/components/right/management/ManageReactions.tsx @@ -9,7 +9,7 @@ import type { } from '../../../api/types'; import { isSameReaction } from '../../../global/helpers'; -import { selectChat } from '../../../global/selectors'; +import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import useLang from '../../../hooks/useLang'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -169,7 +169,7 @@ const ManageReactions: FC = ({ {isLoading ? ( ) : ( - + )}
@@ -181,7 +181,7 @@ export default memo(withGlobal( const chat = selectChat(global, chatId)!; return { - enabledReactions: chat.fullInfo?.enabledReactions, + enabledReactions: selectChatFullInfo(global, chatId)?.enabledReactions, availableReactions: global.availableReactions, chat, }; diff --git a/src/components/right/management/ManageUser.tsx b/src/components/right/management/ManageUser.tsx index fa057c630..c0bba9f9a 100644 --- a/src/components/right/management/ManageUser.tsx +++ b/src/components/right/management/ManageUser.tsx @@ -5,12 +5,18 @@ import React, { } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import type { ApiUser } from '../../../api/types'; +import type { ApiPhoto, ApiUser } from '../../../api/types'; import { ManagementProgress } from '../../../types'; import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; import { - selectChat, selectTabState, selectNotifyExceptions, selectNotifySettings, selectUser, + selectChat, + selectNotifyExceptions, + selectNotifySettings, + selectTabState, + selectUser, + selectUserFullInfo, + selectUserPhotoFromFullInfo, } from '../../../global/selectors'; import { isUserBot, selectIsChatMuted } from '../../../global/helpers'; import useFlag from '../../../hooks/useFlag'; @@ -37,8 +43,11 @@ type OwnProps = { type StateProps = { user?: ApiUser; + userProfilePhoto?: ApiPhoto; progress?: ManagementProgress; isMuted?: boolean; + personalPhoto?: ApiPhoto; + notPersonalPhoto?: ApiPhoto; }; const ERROR_FIRST_NAME_MISSING = 'Please provide first name'; @@ -46,10 +55,13 @@ const ERROR_FIRST_NAME_MISSING = 'Please provide first name'; const ManageUser: FC = ({ userId, user, + userProfilePhoto, progress, isMuted, onClose, isActive, + personalPhoto, + notPersonalPhoto, }) => { const { updateContact, @@ -171,8 +183,6 @@ const ManageUser: FC = ({ const canSetPersonalPhoto = !isUserBot(user) && user.id !== SERVICE_NOTIFICATIONS_USER_ID; const isLoading = progress === ManagementProgress.InProgress; - const personalPhoto = user.fullInfo?.personalPhoto; - const notPersonalPhoto = user.fullInfo?.profilePhoto || user.fullInfo?.fallbackPhoto; return (
@@ -223,6 +233,7 @@ const ManageUser: FC = ({ photo={notPersonalPhoto} noPersonalPhoto user={user} + userProfilePhoto={userProfilePhoto} size="mini" className="personal-photo" /> @@ -251,7 +262,7 @@ const ManageUser: FC = ({ {isLoading ? ( ) : ( - + )} ( (global, { userId }): StateProps => { const user = selectUser(global, userId); const chat = selectChat(global, userId); + const userFullInfo = selectUserFullInfo(global, userId); const { progress } = selectTabState(global).management; const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)); + const personalPhoto = userFullInfo?.personalPhoto; + const notPersonalPhoto = userFullInfo?.profilePhoto || userFullInfo?.fallbackPhoto; + const userProfilePhoto = user ? selectUserPhotoFromFullInfo(global, user.id) : undefined; return { - user, progress, isMuted, + user, progress, isMuted, personalPhoto, notPersonalPhoto, userProfilePhoto, }; }, )(ManageUser)); diff --git a/src/components/right/management/Management.scss b/src/components/right/management/Management.scss index 6c475f5f6..7dbb60d14 100644 --- a/src/components/right/management/Management.scss +++ b/src/components/right/management/Management.scss @@ -205,7 +205,7 @@ } .primary-link-input { - cursor: pointer; + cursor: var(--custom-cursor, pointer); margin-bottom: 1rem; padding-right: 3rem; } diff --git a/src/components/right/management/Management.tsx b/src/components/right/management/Management.tsx index 2549abe78..c3617058d 100644 --- a/src/components/right/management/Management.tsx +++ b/src/components/right/management/Management.tsx @@ -16,7 +16,6 @@ import ManageChatPrivacyType from './ManageChatPrivacyType'; import ManageDiscussion from './ManageDiscussion'; import ManageGroupUserPermissions from './ManageGroupUserPermissions'; import ManageChatAdministrators from './ManageChatAdministrators'; -import ManageGroupRecentActions from './ManageGroupRecentActions'; import ManageGroupAdminRights from './ManageGroupAdminRights'; import ManageGroupMembers from './ManageGroupMembers'; import ManageGroupUserPermissionsCreate from './ManageGroupUserPermissionsCreate'; @@ -193,15 +192,6 @@ const Management: FC = ({ /> ); - case ManagementScreens.GroupRecentActions: - return ( - - ); - case ManagementScreens.ChatNewAdminRights: case ManagementScreens.ChatAdminRights: return ( diff --git a/src/components/right/management/RemoveGroupUserModal.tsx b/src/components/right/management/RemoveGroupUserModal.tsx index 0a243fdf9..97c37dddc 100644 --- a/src/components/right/management/RemoveGroupUserModal.tsx +++ b/src/components/right/management/RemoveGroupUserModal.tsx @@ -4,9 +4,10 @@ import React, { } from '../../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../../global'; -import type { ApiChat } from '../../../api/types'; +import type { ApiChat, ApiChatMember } from '../../../api/types'; import { filterUsersByName } from '../../../global/helpers'; +import { selectChatFullInfo } from '../../../global/selectors'; import useLang from '../../../hooks/useLang'; import ChatOrUserPicker from '../../common/ChatOrUserPicker'; @@ -19,11 +20,13 @@ export type OwnProps = { type StateProps = { currentUserId?: string; + chatMembers?: ApiChatMember[]; }; const RemoveGroupUserModal: FC = ({ chat, currentUserId, + chatMembers, isOpen, onClose, }) => { @@ -36,7 +39,7 @@ const RemoveGroupUserModal: FC = ({ const [search, setSearch] = useState(''); const usersId = useMemo(() => { - const availableMemberIds = (chat.fullInfo?.members || []) + const availableMemberIds = (chatMembers || []) .reduce((acc: string[], member) => { if (!member.isAdmin && !member.isOwner && member.userId !== currentUserId) { acc.push(member.userId); @@ -48,7 +51,7 @@ const RemoveGroupUserModal: FC = ({ const usersById = getGlobal().users.byId; return filterUsersByName(availableMemberIds, usersById, search); - }, [chat.fullInfo?.members, currentUserId, search]); + }, [chatMembers, currentUserId, search]); const handleRemoveUser = useCallback((userId: string) => { deleteChatMember({ chatId: chat.id, userId }); @@ -70,9 +73,12 @@ const RemoveGroupUserModal: FC = ({ }; export default memo(withGlobal( - (global): StateProps => { + (global, { chat }): StateProps => { const { currentUserId } = global; - return { currentUserId }; + return { + currentUserId, + chatMembers: selectChatFullInfo(global, chat.id)?.members, + }; }, )(RemoveGroupUserModal)); diff --git a/src/components/right/statistics/MessageStatistics.tsx b/src/components/right/statistics/MessageStatistics.tsx index c773393b6..e106777d5 100644 --- a/src/components/right/statistics/MessageStatistics.tsx +++ b/src/components/right/statistics/MessageStatistics.tsx @@ -6,8 +6,8 @@ import { getActions, withGlobal } from '../../../global'; import { callApi } from '../../../api/gramjs'; import type { ApiMessageStatistics, ApiMessagePublicForward, StatisticsGraph } from '../../../api/types'; -import { selectChat, selectTabState } from '../../../global/selectors'; +import { selectChatFullInfo, selectTabState } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import useLang from '../../../hooks/useLang'; import useForceUpdate from '../../../hooks/useForceUpdate'; @@ -173,8 +173,7 @@ const Statistics: FC = ({ export default memo(withGlobal( (global, { chatId }): StateProps => { - const chat = selectChat(global, chatId); - const dcId = chat?.fullInfo?.statisticsDcId; + const dcId = selectChatFullInfo(global, chatId)?.statisticsDcId; const tabState = selectTabState(global); const statistics = tabState.statistics.currentMessage; const messageId = tabState.statistics.currentMessageId; diff --git a/src/components/right/statistics/Statistics.tsx b/src/components/right/statistics/Statistics.tsx index bb70a8019..90de84166 100644 --- a/src/components/right/statistics/Statistics.tsx +++ b/src/components/right/statistics/Statistics.tsx @@ -12,7 +12,7 @@ import type { StatisticsRecentMessage as StatisticsRecentMessageType, StatisticsGraph, } from '../../../api/types'; -import { selectChat, selectStatistics } from '../../../global/selectors'; +import { selectChat, selectChatFullInfo, selectStatistics } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import useLang from '../../../hooks/useLang'; @@ -203,7 +203,7 @@ export default memo(withGlobal( (global, { chatId }): StateProps => { const statistics = selectStatistics(global, chatId); const chat = selectChat(global, chatId); - const dcId = chat?.fullInfo?.statisticsDcId; + const dcId = selectChatFullInfo(global, chatId)?.statisticsDcId; const isGroup = chat?.type === 'chatTypeSuperGroup'; return { diff --git a/src/components/right/statistics/StatisticsPublicForward.scss b/src/components/right/statistics/StatisticsPublicForward.scss index d41cb1c28..3c9466771 100644 --- a/src/components/right/statistics/StatisticsPublicForward.scss +++ b/src/components/right/statistics/StatisticsPublicForward.scss @@ -1,6 +1,6 @@ .StatisticsPublicForward { position: relative; - cursor: pointer; + cursor: var(--custom-cursor, pointer); padding: 0.5rem 0.75rem; display: flex; align-items: center; diff --git a/src/components/right/statistics/StatisticsRecentMessage.scss b/src/components/right/statistics/StatisticsRecentMessage.scss index da666f24d..8b6463026 100644 --- a/src/components/right/statistics/StatisticsRecentMessage.scss +++ b/src/components/right/statistics/StatisticsRecentMessage.scss @@ -1,6 +1,6 @@ .StatisticsRecentMessage { position: relative; - cursor: pointer; + cursor: var(--custom-cursor, pointer); padding: 0.5rem 0.75rem; &:hover, &:active { diff --git a/src/components/right/statistics/StatisticsRecentMessage.tsx b/src/components/right/statistics/StatisticsRecentMessage.tsx index 1cac6a4b5..ae1ecc7ff 100644 --- a/src/components/right/statistics/StatisticsRecentMessage.tsx +++ b/src/components/right/statistics/StatisticsRecentMessage.tsx @@ -73,7 +73,7 @@ function renderSummary(lang: LangFn, message: ApiMessage, blobUrl?: string, isRo return ( - {getMessageVideo(message) && } + {getMessageVideo(message) && } {renderMessageSummary(lang, message, true)} ); diff --git a/src/components/ui/AvatarEditable.scss b/src/components/ui/AvatarEditable.scss index e9d7fb787..18dc5fa0c 100644 --- a/src/components/ui/AvatarEditable.scss +++ b/src/components/ui/AvatarEditable.scss @@ -14,7 +14,7 @@ border-radius: 50%; color: white; font-size: 3rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); position: relative; overflow: hidden; outline: none !important; @@ -32,7 +32,7 @@ height: 100%; } - i { + .icon { transform: scale(1); transition: transform 0.15s linear; z-index: var(--z-register-add-avatar); @@ -41,7 +41,7 @@ // @optimization The weirdest workaround for screen animation lag @include while-transition() { input, - i, + .icon, &::after { display: none !important; } @@ -52,7 +52,7 @@ } &:hover { - i { + .icon { transform: scale(1.2); } } @@ -75,7 +75,7 @@ &.disabled { pointer-events: none; - i { + .icon { display: none; } diff --git a/src/components/ui/AvatarEditable.tsx b/src/components/ui/AvatarEditable.tsx index 55d08b519..37f3afe1c 100644 --- a/src/components/ui/AvatarEditable.tsx +++ b/src/components/ui/AvatarEditable.tsx @@ -76,7 +76,7 @@ const AvatarEditable: FC = ({ onChange={handleSelectFile} accept="image/png, image/jpeg" /> - + {croppedBlobUrl && Avatar} diff --git a/src/components/ui/Button.scss b/src/components/ui/Button.scss index f94ebd5fc..98c977a13 100644 --- a/src/components/ui/Button.scss +++ b/src/components/ui/Button.scss @@ -37,7 +37,7 @@ padding: 0.625rem; color: white; line-height: 1.2; - cursor: pointer; + cursor: var(--custom-cursor, pointer); text-transform: uppercase; flex-shrink: 0; position: relative; @@ -56,7 +56,7 @@ &.disabled { opacity: 0.5 !important; - cursor: default; + cursor: var(--custom-cursor, default); &:not(.click-allowed) { pointer-events: none; } @@ -66,7 +66,7 @@ width: 3.5rem; border-radius: 50%; - i { + .icon { font-size: 1.5rem; } } @@ -255,7 +255,7 @@ padding-left: 0.75rem; padding-right: 1.25rem; - i { + .icon { font-size: 1.5rem; margin-right: 0.5rem; } @@ -264,7 +264,7 @@ padding-left: 1.25rem; padding-right: 0.75rem; - i { + .icon { margin-left: 0.5rem; margin-right: 0; } @@ -293,7 +293,7 @@ border-radius: 50%; } - i { + .icon { font-size: 1.25rem; } diff --git a/src/components/ui/Checkbox.scss b/src/components/ui/Checkbox.scss index b1bc8a96c..8aa6db79e 100644 --- a/src/components/ui/Checkbox.scss +++ b/src/components/ui/Checkbox.scss @@ -5,7 +5,7 @@ text-align: left; margin-bottom: 1.5rem; line-height: 1.5rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); &.disabled { pointer-events: none; diff --git a/src/components/ui/Checkbox.tsx b/src/components/ui/Checkbox.tsx index 20c3f09c6..d9a16901e 100644 --- a/src/components/ui/Checkbox.tsx +++ b/src/components/ui/Checkbox.tsx @@ -104,7 +104,7 @@ const Checkbox: FC = ({
{typeof label === 'string' ? renderText(label) : label} - {rightIcon && } + {rightIcon && } {subLabel && {renderText(subLabel)}}
diff --git a/src/components/ui/CropModal.scss b/src/components/ui/CropModal.scss index 6d8a4576e..a2846f411 100644 --- a/src/components/ui/CropModal.scss +++ b/src/components/ui/CropModal.scss @@ -1,6 +1,6 @@ @mixin thumb-styles() { background: var(--color-primary); - cursor: pointer; + cursor: var(--custom-cursor, pointer); transform: scale(1); transition: transform 0.25s ease-in-out; diff --git a/src/components/ui/CropModal.tsx b/src/components/ui/CropModal.tsx index e3de729f2..1a1acd15a 100644 --- a/src/components/ui/CropModal.tsx +++ b/src/components/ui/CropModal.tsx @@ -125,7 +125,7 @@ const CropModal: FC = ({ file, onChange, onClose }: OwnProps) => { onClick={handleCropClick} ariaLabel={lang('CropImage')} > - + ); diff --git a/src/components/ui/Draggable.module.scss b/src/components/ui/Draggable.module.scss index 42e60a8df..bb0073ca0 100644 --- a/src/components/ui/Draggable.module.scss +++ b/src/components/ui/Draggable.module.scss @@ -11,7 +11,7 @@ .isDragging { z-index: 2; - > *:not(.knob) { + :global(.drag-item) { pointer-events: none; } } @@ -29,7 +29,7 @@ opacity: 0; transition: opacity 150ms; - cursor: grab !important; + cursor: var(--custom-cursor, grab) !important; transform: translateY(-50%); .container:hover & { @@ -38,7 +38,7 @@ .isDragging & { opacity: 1; - cursor: grabbing !important; + cursor: var(--custom-cursor, grabbing) !important; } @media (pointer: coarse) { diff --git a/src/components/ui/Draggable.tsx b/src/components/ui/Draggable.tsx index a154c9e34..83d4bec4d 100644 --- a/src/components/ui/Draggable.tsx +++ b/src/components/ui/Draggable.tsx @@ -157,12 +157,12 @@ const Draggable: FC = ({ aria-label={lang('i18n_dragToSort')} tabIndex={0} role="button" - className={buildClassName(styles.knob, 'draggable-knob')} + className={buildClassName(styles.knob, 'div-button', 'draggable-knob')} onMouseDown={handleMouseDown} onTouchStart={handleMouseDown} style={knobStyle} > - +
)}
diff --git a/src/components/ui/DropdownMenu.tsx b/src/components/ui/DropdownMenu.tsx index 223887310..bf502d3c3 100644 --- a/src/components/ui/DropdownMenu.tsx +++ b/src/components/ui/DropdownMenu.tsx @@ -85,7 +85,7 @@ const DropdownMenu: FC = ({ onClick={onTrigger} ariaLabel="More actions" > - + ); }, [trigger]); diff --git a/src/components/ui/ListItem.scss b/src/components/ui/ListItem.scss index ae289989b..e2b89ef0a 100644 --- a/src/components/ui/ListItem.scss +++ b/src/components/ui/ListItem.scss @@ -51,7 +51,7 @@ text-decoration: none; - > i { + > .icon { font-size: 1.5rem; margin-right: 2rem; color: var(--color-text-secondary); @@ -89,7 +89,7 @@ } &.multiline { - .ListItem-button > i { + .ListItem-button > .icon { position: relative; top: 0.25rem; } @@ -105,10 +105,10 @@ &:not(.disabled):not(.is-static) { .ListItem-button { - cursor: pointer; + cursor: var(--custom-cursor, pointer); body.cursor-ew-resize & { - cursor: ew-resize !important; + cursor: var(--custom-cursor, ew-resize) !important; } @media (hover: hover) { @@ -159,7 +159,7 @@ .ListItem-button { color: var(--color-error); - i { + .icon { color: inherit; } } @@ -443,7 +443,7 @@ } &[dir="rtl"] { - .ListItem-button > i { + .ListItem-button > .icon { margin-left: 2rem; margin-right: 0; } diff --git a/src/components/ui/ListItem.tsx b/src/components/ui/ListItem.tsx index 7c685c69e..5a4245ff8 100644 --- a/src/components/ui/ListItem.tsx +++ b/src/components/ui/ListItem.tsx @@ -229,7 +229,7 @@ const ListItem: FC = ({ > {leftElement} {icon && ( - + )} {multiline && (
{children}
)} {!multiline && children} @@ -245,7 +245,7 @@ const ListItem: FC = ({ onClick={IS_TOUCH_ENV ? handleSecondaryIconClick : undefined} onMouseDown={!IS_TOUCH_ENV ? handleSecondaryIconClick : undefined} > - + )} {rightElement} diff --git a/src/components/ui/Loading.scss b/src/components/ui/Loading.scss index 31312d67c..6f7f96f33 100644 --- a/src/components/ui/Loading.scss +++ b/src/components/ui/Loading.scss @@ -5,7 +5,7 @@ justify-content: center; &.interactive { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } .Spinner { diff --git a/src/components/ui/MenuItem.scss b/src/components/ui/MenuItem.scss index f26ef24af..32a685267 100644 --- a/src/components/ui/MenuItem.scss +++ b/src/components/ui/MenuItem.scss @@ -13,7 +13,7 @@ white-space: nowrap; color: var(--color-text); --ripple-color: rgba(0, 0, 0, 0.08); - cursor: pointer; + cursor: var(--custom-cursor, pointer); unicode-bidi: plaintext; .right-badge { @@ -64,7 +64,7 @@ } } - i { + .icon { font-size: 1.5rem; margin-right: 2rem; color: var(--color-icon-secondary); @@ -80,13 +80,13 @@ &.disabled { opacity: 0.5 !important; - cursor: default !important; + cursor: var(--custom-cursor, default) !important; } &.destructive { color: var(--color-error); - i { + .icon { color: inherit; } } @@ -109,7 +109,7 @@ } &[dir="rtl"] { - i { + .icon { margin-left: 2rem; margin-right: 0; } @@ -135,7 +135,7 @@ transform: scale(1); transition: 0.15s ease-in-out transform; - i { + .icon { max-width: 1.25rem; font-size: 1.25rem; margin-left: 0.5rem; diff --git a/src/components/ui/MenuItem.tsx b/src/components/ui/MenuItem.tsx index f297b762e..1cf708688 100644 --- a/src/components/ui/MenuItem.tsx +++ b/src/components/ui/MenuItem.tsx @@ -10,6 +10,7 @@ import './MenuItem.scss'; export type MenuItemProps = { icon?: string; + isCharIcon?: boolean; customIcon?: React.ReactNode; className?: string; children: React.ReactNode; @@ -27,6 +28,7 @@ export type MenuItemProps = { const MenuItem: FC = (props) => { const { icon, + isCharIcon, customIcon, className, children, @@ -81,7 +83,10 @@ const MenuItem: FC = (props) => { const content = ( <> {!customIcon && icon && ( - + )} {customIcon} {children} diff --git a/src/components/ui/Modal.scss b/src/components/ui/Modal.scss index a23d4c008..dc2676c81 100644 --- a/src/components/ui/Modal.scss +++ b/src/components/ui/Modal.scss @@ -17,7 +17,7 @@ } &.error { - .modal-content > div { + .modal-content .dialog-buttons { margin-top: 1rem; } } diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx index bd647fb7e..abf8ecf77 100644 --- a/src/components/ui/Modal.tsx +++ b/src/components/ui/Modal.tsx @@ -135,7 +135,7 @@ const Modal: FC = ({ ariaLabel={lang('Close')} onClick={onClose} > - + )}
{title}
diff --git a/src/components/ui/Notification.scss b/src/components/ui/Notification.scss index 07a09783f..bbf132086 100644 --- a/src/components/ui/Notification.scss +++ b/src/components/ui/Notification.scss @@ -50,7 +50,7 @@ &.bold-link b { color: var(--color-primary); - cursor: pointer; + cursor: var(--custom-cursor, pointer); } .notification-title { diff --git a/src/components/ui/ProgressSpinner.scss b/src/components/ui/ProgressSpinner.scss index 33e90c061..11818805c 100644 --- a/src/components/ui/ProgressSpinner.scss +++ b/src/components/ui/ProgressSpinner.scss @@ -7,7 +7,7 @@ url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkiIGhlaWdodD0iMTkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEwLjcxNyA5Ljc1TDE4LjMgMi4xNjdhLjY4NC42ODQgMCAxMC0uOTY3LS45NjdMOS43NSA4Ljc4MyAyLjE2NyAxLjJhLjY4NC42ODQgMCAxMC0uOTY3Ljk2N0w4Ljc4MyA5Ljc1IDEuMiAxNy4zMzNhLjY4NC42ODQgMCAxMC45NjcuOTY3bDcuNTgzLTcuNTgzIDcuNTgzIDcuNTgzYS42ODEuNjgxIDAgMDAuOTY3IDAgLjY4NC42ODQgMCAwMDAtLjk2N0wxMC43MTcgOS43NXoiIGZpbGw9IiNGRkYiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjRkZGIiBzdHJva2Utd2lkdGg9Ii43NSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==) no-repeat 49% 49%; border-radius: 50%; - cursor: pointer; + cursor: var(--custom-cursor, pointer); &.no-cross { background: transparent !important; diff --git a/src/components/ui/Radio.scss b/src/components/ui/Radio.scss index 26b94b5f7..1056dc616 100644 --- a/src/components/ui/Radio.scss +++ b/src/components/ui/Radio.scss @@ -5,7 +5,7 @@ text-align: left; margin-bottom: 1.5rem; line-height: 1.5rem; - cursor: pointer; + cursor: var(--custom-cursor, pointer); &.disabled { pointer-events: none; @@ -13,7 +13,7 @@ } &.hidden-widget { - cursor: default; + cursor: var(--custom-cursor, default); .Radio-main { &::before, &::after { diff --git a/src/components/ui/RangeSlider.scss b/src/components/ui/RangeSlider.scss index 72a5dc0ad..31e631506 100644 --- a/src/components/ui/RangeSlider.scss +++ b/src/components/ui/RangeSlider.scss @@ -6,7 +6,7 @@ height: 0.75rem; width: 0.75rem; border-radius: 50%; - cursor: pointer; + cursor: var(--custom-cursor, pointer); transform: scale(1); transition: transform 0.25s ease-in-out; @@ -115,7 +115,7 @@ .slider-option { font-size: 0.8125rem; text-align: center; - cursor: pointer; + cursor: var(--custom-cursor, pointer); color: var(--color-text-secondary); transition: color 0.2s ease; diff --git a/src/components/ui/SearchInput.scss b/src/components/ui/SearchInput.scss index 0b853644a..d3f890d6c 100644 --- a/src/components/ui/SearchInput.scss +++ b/src/components/ui/SearchInput.scss @@ -32,7 +32,7 @@ background-color: var(--color-background); input { - & + i { + & + .icon { color: var(--color-primary); } } @@ -53,7 +53,7 @@ } } - > i { + > .icon { position: absolute; top: 0.5rem; left: 0.75rem; @@ -87,7 +87,7 @@ padding-left: calc(2.625rem - var(--border-width)); } - i { + .icon { top: 0.5rem; } } @@ -97,7 +97,7 @@ direction: rtl; } - > i { + > .icon { left: auto; right: 0.75rem; } diff --git a/src/components/ui/SearchInput.tsx b/src/components/ui/SearchInput.tsx index 98bb70ca8..2149f4e06 100644 --- a/src/components/ui/SearchInput.tsx +++ b/src/components/ui/SearchInput.tsx @@ -133,7 +133,7 @@ const SearchInput: FC = ({ onBlur={handleBlur} onKeyDown={handleKeyDown} /> - + @@ -144,7 +144,7 @@ const SearchInput: FC = ({ color="translucent" onClick={onReset} > - + )}
diff --git a/src/components/ui/ShowMoreButton.scss b/src/components/ui/ShowMoreButton.scss index 70e701a54..052289c8a 100644 --- a/src/components/ui/ShowMoreButton.scss +++ b/src/components/ui/ShowMoreButton.scss @@ -7,7 +7,7 @@ padding-left: 0.75rem !important; opacity: 1 !important; - i { + .icon { font-size: 1.5rem; margin-right: 2rem; color: var(--color-text-secondary); diff --git a/src/components/ui/ShowMoreButton.tsx b/src/components/ui/ShowMoreButton.tsx index 0e9fe06ff..fcff45a55 100644 --- a/src/components/ui/ShowMoreButton.tsx +++ b/src/components/ui/ShowMoreButton.tsx @@ -34,7 +34,7 @@ const ShowMoreButton: FC = ({ isRtl={lang.isRtl} onClick={onClick} > - + Show {count} more {count > 1 ? itemPluralName || `${itemName}s` : itemName} ); diff --git a/src/components/ui/Spinner.scss b/src/components/ui/Spinner.scss index 19b6ac7f8..387be4d1f 100644 --- a/src/components/ui/Spinner.scss +++ b/src/components/ui/Spinner.scss @@ -9,22 +9,6 @@ width: var(--spinner-size); height: var(--spinner-size); - > div { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - - background-repeat: no-repeat; - background-size: 100%; - - animation-name: spin; - animation-duration: 1s; - animation-iteration-count: infinite; - animation-timing-function: linear; - } - &.with-background { &::before { content: ''; @@ -46,19 +30,19 @@ } &.white { - > div { + .Spinner__inner { background-image: var(--spinner-white-data); } &.with-background { - > div { + .Spinner__inner { background-image: var(--spinner-white-thin-data); } } } &.blue { - > div { + .Spinner__inner { background-image: var(--spinner-blue-data); .theme-dark & { @@ -68,30 +52,46 @@ } &.black { - > div { + .Spinner__inner { background-image: var(--spinner-black-data); } } &.green { - > div { + .Spinner__inner { background-image: var(--spinner-green-data); } } &.gray { - > div { + .Spinner__inner { background-image: var(--spinner-gray-data); } } &.yellow { - > div { + .Spinner__inner { background-image: var(--spinner-yellow-data); } } } +.Spinner__inner { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + + background-repeat: no-repeat; + background-size: 100%; + + animation-name: spin; + animation-duration: 1s; + animation-iteration-count: infinite; + animation-timing-function: linear; +} + @keyframes spin { from { transform: rotate(0deg); diff --git a/src/components/ui/Spinner.tsx b/src/components/ui/Spinner.tsx index 7faf98256..ae4a519dc 100644 --- a/src/components/ui/Spinner.tsx +++ b/src/components/ui/Spinner.tsx @@ -19,7 +19,7 @@ const Spinner: FC<{ 'Spinner', className, color, backgroundColor && 'with-background', backgroundColor && `bg-${backgroundColor}`, )} > -
+
); }; diff --git a/src/components/ui/Switcher.scss b/src/components/ui/Switcher.scss index c8c917e40..4aae57c1f 100644 --- a/src/components/ui/Switcher.scss +++ b/src/components/ui/Switcher.scss @@ -35,7 +35,7 @@ } .widget { - cursor: pointer; + cursor: var(--custom-cursor, pointer); text-indent: -999px; width: 2.125rem; height: 0.875rem; diff --git a/src/components/ui/Tab.scss b/src/components/ui/Tab.scss index 304313884..1665bb226 100644 --- a/src/components/ui/Tab.scss +++ b/src/components/ui/Tab.scss @@ -10,7 +10,7 @@ padding: 0.625rem 0.25rem; font-weight: 500; color: var(--color-text-secondary); - cursor: pointer; + cursor: var(--custom-cursor, pointer); border-top-left-radius: var(--border-radius-messages-small); border-top-right-radius: var(--border-radius-messages-small); @@ -21,7 +21,7 @@ } &--active { - cursor: default; + cursor: var(--custom-cursor, default); color: var(--color-primary); .platform { diff --git a/src/components/ui/Tab.tsx b/src/components/ui/Tab.tsx index 0b5002497..0bcf67d17 100644 --- a/src/components/ui/Tab.tsx +++ b/src/components/ui/Tab.tsx @@ -110,12 +110,12 @@ const Tab: FC = ({ onMouseDown={!IS_TOUCH_ENV ? handleClick : undefined} ref={tabRef} > - + {renderText(title)} {Boolean(badgeCount) && ( {badgeCount} )} - {isBlocked && } + {isBlocked && }
diff --git a/src/components/ui/Transition.scss b/src/components/ui/Transition.scss index 8f1c2fa9d..3e1827024 100644 --- a/src/components/ui/Transition.scss +++ b/src/components/ui/Transition.scss @@ -3,40 +3,37 @@ width: 100%; height: 100%; - > .Transition__slide { + &_slide { width: 100%; height: 100%; animation-fill-mode: forwards !important; - &.from, - &.to { + &-from, + &-to { position: absolute; top: 0; left: 0; } - } - &:not(.slide-optimized):not(.slide-optimized-rtl) { - > .Transition__slide:not(.Transition__slide--active):not(.from):not(.to) { + &-inactive { display: none !important; // Best performance when animating container //transform: scale(0); // Shortest initial delay } } - &.skip-slide-transition { - transition: none !important; - } - - &.slide-optimized, - &.slide-optimized-rtl { + &-slideOptimized, + &-slideOptimizedBackwards, + &-slideOptimizedRtl, + &-slideOptimizedRtlBackwards { contain: strict; + body.is-safari & { // Create a new composite layer to reduce the page repaint area when switching a tab (as `contain` is not supported in Safari) transform: translate3d(0, 0, 0); } - #root & > .Transition__slide { + #root & > .Transition_slide { position: absolute; top: 0; left: 0; @@ -46,381 +43,242 @@ } } - &.slide { - > .to { + &-slide { + > .Transition_slide-to { transform: translateX(100%); + animation: slide-in var(--slide-transition); } - &.animating { - > .from { - animation: slide-out var(--slide-transition); - } - - > .to { - animation: slide-in var(--slide-transition); - } + > .Transition_slide-from { + animation: slide-out var(--slide-transition); } } - &.slide.backwards { - > .to { + &-slideBackwards { + > .Transition_slide-to { transform: translateX(-100%); + animation: slide-out-backwards var(--slide-transition); } - &.animating { - > .from { - animation: slide-in-backwards var(--slide-transition); - } - - > .to { - animation: slide-out-backwards var(--slide-transition); - } + > .Transition_slide-from { + animation: slide-in-backwards var(--slide-transition); } } - &.slide-rtl { - > .to { + &-slideRtl { + > .Transition_slide-to { transform: translateX(-100%); + animation: slide-in var(--slide-transition); } - &.animating { - > .from { - animation: slide-out var(--slide-transition); - } - - > .to { - animation: slide-in var(--slide-transition); - } - } - - &.backwards { - > .to { - transform: translateX(100%); - } - - &.animating { - > .from { - animation: slide-in-backwards var(--slide-transition); - } - - > .to { - animation: slide-out-backwards var(--slide-transition); - } - } + > .Transition_slide-from { + animation: slide-out var(--slide-transition); } } - &.slide-vertical { - > .to { + + &-slideRtlBackwards { + > .Transition_slide-to { + transform: translateX(100%); + animation: slide-out-backwards var(--slide-transition); + } + + > .Transition_slide-from { + animation: slide-in-backwards var(--slide-transition); + } + } + + &-slideVertical { + > .Transition_slide-to { transform: translateY(100%); + animation: slide-vertical-in var(--slide-transition); } - &.animating { - > .from { - animation: slide-vertical-out var(--slide-transition); - } - - > .to { - animation: slide-vertical-in var(--slide-transition); - } + > .Transition_slide-from { + animation: slide-vertical-out var(--slide-transition); } } - &.slide-vertical.backwards { - > .to { + &-slideVerticalBackwards { + > .Transition_slide-to { transform: translateY(-100%); + animation: slide-vertical-out-backwards var(--slide-transition); } - &.animating { - > .from { - animation: slide-vertical-in-backwards var(--slide-transition); - } - - > .to { - animation: slide-vertical-out-backwards var(--slide-transition); - } + > .Transition_slide-from { + animation: slide-vertical-in-backwards var(--slide-transition); } } - &.slide-vertical-fade { - > .to { + &-slideVerticalFade { + > .Transition_slide-to { transform: translateY(100%); + animation: slide-vertical-fade-in var(--slide-transition); } - &.animating { - > .from { - animation: slide-vertical-fade-out var(--slide-transition); - } - - > .to { - animation: slide-vertical-fade-in var(--slide-transition); - } + > .Transition_slide-from { + animation: slide-vertical-fade-out var(--slide-transition); } } - &.slide-vertical-fade.backwards { - > .to { + &-slideVerticalFadeBackwards { + > .Transition_slide-to { transform: translateY(-100%); + animation: slide-vertical-fade-out-backwards var(--slide-transition); } - &.animating { - > .from { - animation: slide-vertical-fade-in-backwards var(--slide-transition); - } - - > .to { - animation: slide-vertical-fade-out-backwards var(--slide-transition); - } + > .Transition_slide-from { + animation: slide-vertical-fade-in-backwards var(--slide-transition); } } - &.mv-slide { - > .Transition__slide > div { - animation-fill-mode: forwards !important; - } - - > .to > div { - transform: translateX(100vw); - } - - &.animating { - > .from > div { - animation: mv-slide-out 0.4s ease; - } - - > .to > div { - animation: mv-slide-in 0.4s ease; - } - } - } - - &.mv-slide.backwards { - > .to > div { - transform: translateX(-100vw); - } - - &.animating { - > .from > div { - animation: mv-slide-in-backwards 0.4s ease; - } - - > .to > div { - animation: mv-slide-out-backwards 0.4s ease; - } - } - } - - &.slide-fade { - > .from { + &-slideFade { + > .Transition_slide-from { transform: translateX(0); transform-origin: left; - opacity: 1; + animation: fade-out-opacity var(--slide-transition), slide-fade-out-move var(--slide-transition); } - > .to { + > .Transition_slide-to { transform: translateX(1.5rem); transform-origin: left; - opacity: 0; - } - - &.animating { - > .from { - animation: fade-out-opacity var(--slide-transition), slide-fade-out-move var(--slide-transition); - } - - > .to { - animation: fade-in-opacity var(--slide-transition), slide-fade-in-move var(--slide-transition); - } + animation: fade-in-opacity var(--slide-transition), slide-fade-in-move var(--slide-transition); } } - &.slide-fade.backwards { - > .from { + &-slideFadeBackwards { + > .Transition_slide-from { transform: translateX(0); - opacity: 1; + animation: fade-in-backwards-opacity var(--slide-transition), + slide-fade-in-backwards-move var(--slide-transition); } - > .to { + > .Transition_slide-to { transform: translateX(-1.5rem); - opacity: 0; - } - - &.animating { - > .from { - animation: fade-in-backwards-opacity var(--slide-transition), - slide-fade-in-backwards-move var(--slide-transition); - } - - > .to { - animation: fade-out-backwards-opacity var(--slide-transition), - slide-fade-out-backwards-move var(--slide-transition); - } + animation: fade-out-backwards-opacity var(--slide-transition), + slide-fade-out-backwards-move var(--slide-transition); } } - &.zoom-fade { - > .from { + &-zoomFade { + > .Transition_slide-from { transform: scale(1); transform-origin: center; - opacity: 1; + animation: fade-out-opacity 0.15s ease; } - > .to { + > .Transition_slide-to { transform-origin: center; - opacity: 0; // We can omit `transform: scale(1.1);` here because `opacity` is 0. // We need to for proper position calculation in `InfiniteScroll`. - } - &.animating { - > .from { - animation: fade-out-opacity 0.15s ease; - } - - > .to { - animation: fade-in-opacity 0.15s ease, zoom-fade-in-move 0.15s ease; - } + animation: fade-in-opacity 0.15s ease, zoomFade-in-move 0.15s ease; } } - &.zoom-fade.backwards { - > .from { + &-zoomFadeBackwards { + > .Transition_slide-from { transform: scale(1); + animation: fade-in-backwards-opacity 0.1s ease, zoomFade-in-backwards-move 0.15s ease; } - > .to { + > .Transition_slide-to { transform: scale(0.95); - } - - &.animating { - > .from { - animation: fade-in-backwards-opacity 0.1s ease, zoom-fade-in-backwards-move 0.15s ease; - } - - > .to { - animation: fade-out-backwards-opacity 0.15s ease, zoom-fade-out-backwards-move 0.15s ease; - } + animation: fade-out-backwards-opacity 0.15s ease, zoomFade-out-backwards-move 0.15s ease; } } - &.fade { - > .from { + &-fade, + &-fadeBackwards { + > .Transition_slide-from { opacity: 1; + animation: fade-out-opacity 0.15s ease; } - > .to { + > .Transition_slide-to { opacity: 0; - } - - &.animating { - > .from { - animation: fade-out-opacity 0.15s ease; - } - - > .to { - animation: fade-in-opacity 0.15s ease; - } + animation: fade-in-opacity 0.15s ease; } } - &.semi-fade { - > .Transition__slide { + &-semiFade { + > .Transition_slide { isolation: isolate; } - > .from { + > .Transition_slide-from { opacity: 1; } - > .to { + > .Transition_slide-to { opacity: 0; - } - - &.animating { - > .to { - animation: fade-in-opacity 250ms ease; - } + animation: fade-in-opacity 250ms ease; } } - &.semi-fade.backwards { - > .from { + &-semiFadeBackwards { + > .Transition_slide-from { opacity: 1; + animation: fade-in-backwards-opacity 250ms ease; } - > .to { + > .Transition_slide-to { opacity: 1; - } - - &.animating { - > .from { - animation: fade-in-backwards-opacity 250ms ease; - } - - > .to { - animation: none !important; - } + animation: none !important; } } - &.slide-layers { + &-slideLayers { --background-color: var(--color-background); background: black !important; - > .Transition__slide { + > .Transition_slide { background: var(--background-color); } - > .to { + > .Transition_slide-to { transform: translateX(100%); + animation: slide-in var(--layer-transition); } - &.animating { - > .from { - animation: slide-layers-out var(--layer-transition); - } - - > .to { - animation: slide-in var(--layer-transition); - } + > .Transition_slide-from { + animation: slide-layers-out var(--layer-transition); } } - &.slide-layers.backwards { - > .to { + &-slideLayersBackwards { + --background-color: var(--color-background); + + background: black !important; + + > .Transition_slide-to { transform: translateX(-20%); - opacity: 0.75; + animation: slide-layers-out-backwards var(--layer-transition); } - &.animating { - > .from { - animation: slide-in-backwards var(--layer-transition); - } - - > .to { - animation: slide-layers-out-backwards var(--layer-transition); - } + > .Transition_slide-from { + animation: slide-in-backwards var(--layer-transition); } } - &.push-slide { - > .Transition__slide { + &-pushSlide { + > .Transition_slide { background: var(--color-background); } - > .from { + > .Transition_slide-from { transform: scale(1); transform-origin: center; - opacity: 1; + animation: push-out 0.25s ease-in-out; .custom-scroll { scrollbar-color: transparent !important; @@ -431,68 +289,44 @@ } } - > .to { + > .Transition_slide-to { transform: translateX(100%); - } - - &.animating { - > .from { - animation: push-out 0.25s ease-in-out; - } - - > .to { - animation: slide-in-200 0.25s ease-in-out; - } + animation: slide-in-200 0.25s ease-in-out; } } - &.push-slide.backwards { - > .to { + &-pushSlideBackwards { + > .Transition_slide { + background: var(--color-background); + } + + > .Transition_slide-to { transform: scale(0.7); - opacity: 0; + animation: push-out-backwards 0.25s ease-in-out; } - &.animating { - > .from { - animation: slide-in-200-backwards 0.25s ease-in-out; - } - - > .to { - animation: push-out-backwards 0.25s ease-in-out; - } + > .Transition_slide-from { + animation: slide-in-200-backwards 0.25s ease-in-out; } } - &.reveal { - > .to { + &-reveal { + > .Transition_slide-to { clip-path: inset(0 100% 0 0); - } - - &.animating { - > .to { - animation: reveal-in 350ms ease-in; - } + animation: reveal-in 350ms ease-in; } } - &.reveal.backwards { - > .from { + &-revealBackwards { + > .Transition_slide-from { clip-path: inset(0 0 0 0); + animation: reveal-in-backwards 350ms ease-out; } - > .to { + > .Transition_slide-to { clip-path: none; - } - - &.animating { - > .from { - animation: reveal-in-backwards 350ms ease-out; - } - - > .to { - animation: none; - } + animation: none; } } } @@ -657,42 +491,6 @@ } } -@keyframes mv-slide-in { - 0% { - transform: translateX(100vw); - } - 100% { - transform: translateX(0); - } -} - -@keyframes mv-slide-out { - 0% { - transform: translateX(0); - } - 100% { - transform: translateX(-100vw); - } -} - -@keyframes mv-slide-in-backwards { - 0% { - transform: translateX(0); - } - 100% { - transform: translateX(100vw); - } -} - -@keyframes mv-slide-out-backwards { - 0% { - transform: translateX(-100vw); - } - 100% { - transform: translateX(0); - } -} - @keyframes slide-fade-in-move { 0% { transform: translateX(1.5rem); @@ -729,7 +527,7 @@ } } -@keyframes zoom-fade-in-move { +@keyframes zoomFade-in-move { 0% { transform: scale(1.1); } @@ -738,7 +536,7 @@ } } -@keyframes zoom-fade-in-backwards-move { +@keyframes zoomFade-in-backwards-move { 0% { transform: scale(1); } @@ -747,7 +545,7 @@ } } -@keyframes zoom-fade-out-backwards-move { +@keyframes zoomFade-out-backwards-move { 0% { transform: scale(0.95); } diff --git a/src/components/ui/Transition.tsx b/src/components/ui/Transition.tsx index af781e8c0..d7df073c4 100644 --- a/src/components/ui/Transition.tsx +++ b/src/components/ui/Transition.tsx @@ -23,9 +23,9 @@ export type TransitionProps = { activeKey: number; nextKey?: number; name: ( - 'none' | 'slide' | 'slide-rtl' | 'mv-slide' | 'slide-fade' | 'zoom-fade' | 'slide-layers' - | 'fade' | 'push-slide' | 'reveal' | 'slide-optimized' | 'slide-optimized-rtl' | 'semi-fade' - | 'slide-vertical' | 'slide-vertical-fade' + 'none' | 'slide' | 'slideRtl' | 'slideFade' | 'zoomFade' | 'slideLayers' + | 'fade' | 'pushSlide' | 'reveal' | 'slideOptimized' | 'slideOptimizedRtl' | 'semiFade' + | 'slideVertical' | 'slideVerticalFade' ); direction?: 'auto' | 'inverse' | 1 | -1; renderCount?: number; @@ -46,9 +46,12 @@ export type TransitionProps = { const FALLBACK_ANIMATION_END = 1000; const CLASSES = { - slide: 'Transition__slide', - active: 'Transition__slide--active', - afterSlides: 'Transition__after-slides', + slide: 'Transition_slide', + active: 'Transition_slide-active', + from: 'Transition_slide-from', + to: 'Transition_slide-to', + inactive: 'Transition_slide-inactive', + afterSlides: 'Transition_afterSlides', }; function Transition({ @@ -96,6 +99,12 @@ function Transition({ rendersRef.current[nextKey] = children; } + const isBackwards = ( + direction === -1 + || (direction === 'auto' && prevActiveKey > activeKey) + || (direction === 'inverse' && prevActiveKey < activeKey) + ); + useLayoutEffect(() => { function cleanup() { if (!shouldCleanup) { @@ -109,6 +118,7 @@ function Transition({ forceUpdate(); } + const isSlideOptimized = name === 'slideOptimized' || name === 'slideOptimizedRtl'; const container = containerRef.current!; const keys = Object.keys(rendersRef.current).map(Number); const prevActiveIndex = renderCount ? prevActiveKey : keys.indexOf(prevActiveKey); @@ -134,12 +144,17 @@ function Transition({ if (childElements.length === 1 || (nextKey !== undefined && childElements.length === 2)) { const firstChild = childNodes[activeIndex] as HTMLElement; - if (name.startsWith('slide-optimized')) { + addExtraClass(firstChild, CLASSES.active); + + if (isSlideOptimized) { firstChild.style.transition = 'none'; firstChild.style.transform = 'translate3d(0, 0, 0)'; } - addExtraClass(firstChild, CLASSES.active); + if (childElements.length === 2) { + const nextChild = childElements[0] === firstChild ? childElements[1] : childElements[0]; + addExtraClass(nextChild, CLASSES.inactive); + } } return; @@ -147,13 +162,7 @@ function Transition({ currentKeyRef.current = activeKey; - const isBackwards = ( - direction === -1 - || (direction === 'auto' && prevActiveKey > activeKey) - || (direction === 'inverse' && prevActiveKey < activeKey) - ); - - if (name === 'slide-optimized' || name === 'slide-optimized-rtl') { + if (isSlideOptimized) { performSlideOptimized( animationLevel, name, @@ -175,10 +184,10 @@ function Transition({ if (name === 'none' || animationLevel === ANIMATION_LEVEL_MIN) { childNodes.forEach((node, i) => { if (node instanceof HTMLElement) { - removeExtraClass(node, 'from'); - removeExtraClass(node, 'through'); - removeExtraClass(node, 'to'); + removeExtraClass(node, CLASSES.from); + removeExtraClass(node, CLASSES.to); toggleExtraClass(node, CLASSES.active, i === activeIndex); + toggleExtraClass(node, CLASSES.inactive, i !== activeIndex); } }); @@ -190,19 +199,17 @@ function Transition({ childNodes.forEach((node, i) => { if (node instanceof HTMLElement) { removeExtraClass(node, CLASSES.active); - - toggleExtraClass(node, 'from', i === prevActiveIndex); - const isThrough = (i > prevActiveIndex && i < activeIndex) || (i < prevActiveIndex && i > activeIndex); - toggleExtraClass(node, 'through', isThrough); - toggleExtraClass(node, 'to', i === activeIndex); + toggleExtraClass(node, CLASSES.from, i === prevActiveIndex); + toggleExtraClass(node, CLASSES.to, i === activeIndex); + toggleExtraClass(node, CLASSES.inactive, i !== prevActiveIndex && i !== activeIndex); } }); const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent(); onStart?.(); - addExtraClass(container, 'animating'); - toggleExtraClass(container, 'backwards', isBackwards); + toggleExtraClass(container, `Transition-${name}`, !isBackwards); + toggleExtraClass(container, `Transition-${name}Backwards`, isBackwards); function onAnimationEnd() { const activeElement = container.querySelector(`.${CLASSES.active}`); @@ -213,15 +220,15 @@ function Transition({ return; } - removeExtraClass(container, 'animating'); - removeExtraClass(container, 'backwards'); + removeExtraClass(container, `Transition-${name}`); + removeExtraClass(container, `Transition-${name}Backwards`); childNodes.forEach((node, i) => { if (node instanceof HTMLElement) { - removeExtraClass(node, 'from'); - removeExtraClass(node, 'through'); - removeExtraClass(node, 'to'); + removeExtraClass(node, CLASSES.from); + removeExtraClass(node, CLASSES.to); toggleExtraClass(node, CLASSES.active, i === activeIndex); + toggleExtraClass(node, CLASSES.inactive, i !== activeIndex); } }); @@ -239,11 +246,9 @@ function Transition({ }); } - const watchedNode = name === 'mv-slide' - ? childNodes[activeIndex]?.firstChild - : name === 'reveal' && isBackwards - ? childNodes[prevActiveIndex] - : childNodes[activeIndex]; + const watchedNode = name === 'reveal' && isBackwards + ? childNodes[prevActiveIndex] + : childNodes[activeIndex]; if (watchedNode) { waitForAnimationEnd(watchedNode, onAnimationEnd, undefined, FALLBACK_ANIMATION_END); @@ -255,7 +260,7 @@ function Transition({ nextKey, prevActiveKey, activeKeyChanged, - direction, + isBackwards, name, onStart, onStop, @@ -275,7 +280,7 @@ function Transition({ const container = containerRef.current!; const activeElement = container.querySelector(`.${CLASSES.active}`) - || container.querySelector('.from'); + || container.querySelector(`.${CLASSES.from}`); if (!activeElement) { return; } @@ -320,7 +325,7 @@ function Transition({
{contents} @@ -332,7 +337,7 @@ export default Transition; function performSlideOptimized( animationLevel: GlobalState['settings']['byKey']['animationLevel'], - name: 'slide-optimized' | 'slide-optimized-rtl', + name: 'slideOptimized' | 'slideOptimizedRtl', isBackwards: boolean, cleanup: NoneToVoidFunction, activeKey: number, @@ -349,6 +354,9 @@ function performSlideOptimized( } if (animationLevel === ANIMATION_LEVEL_MIN) { + toggleExtraClass(container, `Transition-${name}`, !isBackwards); + toggleExtraClass(container, `Transition-${name}Backwards`, isBackwards); + fromSlide.style.transition = 'none'; fromSlide.style.transform = ''; removeExtraClass(fromSlide, CLASSES.active); @@ -362,7 +370,7 @@ function performSlideOptimized( return; } - if (name === 'slide-optimized-rtl') { + if (name === 'slideOptimizedRtl') { isBackwards = !isBackwards; } @@ -370,6 +378,9 @@ function performSlideOptimized( onStart?.(); + toggleExtraClass(container, `Transition-${name}`, !isBackwards); + toggleExtraClass(container, `Transition-${name}Backwards`, isBackwards); + fromSlide.style.transition = 'none'; fromSlide.style.transform = 'translate3d(0, 0, 0)'; diff --git a/src/config.ts b/src/config.ts index 740c5ab37..809a13f7c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -119,7 +119,7 @@ export const APP_CONFIG_REFETCH_INTERVAL = 10000 * 1000; export const EDITABLE_INPUT_ID = 'editable-message-text'; export const EDITABLE_INPUT_MODAL_ID = 'editable-message-text-modal'; // eslint-disable-next-line max-len -export const EDITABLE_INPUT_CSS_SELECTOR = `.messages-layout .Transition__slide--active #${EDITABLE_INPUT_ID}, .messages-layout .Transition > .to #${EDITABLE_INPUT_ID}`; +export const EDITABLE_INPUT_CSS_SELECTOR = `.messages-layout .Transition_slide-active #${EDITABLE_INPUT_ID}, .messages-layout .Transition > .Transition_slide-to #${EDITABLE_INPUT_ID}`; export const EDITABLE_INPUT_MODAL_CSS_SELECTOR = `#${EDITABLE_INPUT_MODAL_ID}`; export const CUSTOM_APPENDIX_ATTRIBUTE = 'data-has-custom-appendix'; diff --git a/src/global/actions/api/bots.ts b/src/global/actions/api/bots.ts index b3680c85e..92311923d 100644 --- a/src/global/actions/api/bots.ts +++ b/src/global/actions/api/bots.ts @@ -13,7 +13,7 @@ import { MAIN_THREAD_ID } from '../../../api/types'; import { callApi } from '../../../api/gramjs'; import { selectChat, selectChatBot, selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectTabState, - selectIsTrustedBot, selectReplyingToId, selectSendAs, selectUser, selectThreadTopMessageId, + selectIsTrustedBot, selectReplyingToId, selectSendAs, selectUser, selectThreadTopMessageId, selectUserFullInfo, } from '../../selectors'; import { addChats, addUsers, removeBlockedContact } from '../../reducers'; import { buildCollectionByKey } from '../../../util/iteratees'; @@ -386,14 +386,20 @@ addActionHandler('resetAllInlineBots', (global, actions, payload): ActionReturnT addActionHandler('startBot', async (global, actions, payload): Promise => { const { botId, param } = payload; - let bot = selectUser(global, botId); + const bot = selectUser(global, botId); if (!bot) { return; } - if (!bot.fullInfo) await callApi('fetchFullUser', { id: bot.id, accessHash: bot.accessHash }); - global = getGlobal(); - bot = selectUser(global, botId)!; - if (bot.fullInfo?.isBlocked) await callApi('unblockContact', bot.id, bot.accessHash); + + let fullInfo = selectUserFullInfo(global, botId); + if (!fullInfo) { + const result = await callApi('fetchFullUser', { id: bot.id, accessHash: bot.accessHash }); + fullInfo = result?.fullInfo; + } + + if (fullInfo?.isBlocked) { + await callApi('unblockContact', bot.id, bot.accessHash); + } await callApi('startBot', { bot, diff --git a/src/global/actions/api/chats.ts b/src/global/actions/api/chats.ts index e9d0bae00..9b8622e15 100644 --- a/src/global/actions/api/chats.ts +++ b/src/global/actions/api/chats.ts @@ -49,13 +49,15 @@ import { updateTopic, updateThreadInfo, updateListedTopicIds, + updateChatFullInfo, + replaceChatFullInfo, } from '../../reducers'; import { selectChat, selectUser, selectChatListType, selectIsChatPinned, selectChatFolder, selectSupportChat, selectChatByUsername, selectCurrentMessageList, selectThreadInfo, selectCurrentChat, selectLastServiceNotification, selectVisibleUsers, selectUserByPhoneNumber, selectDraft, selectThreadTopMessageId, - selectTabState, selectThreadOriginChat, selectThread, + selectTabState, selectThreadOriginChat, selectThread, selectChatFullInfo, } from '../../selectors'; import { buildCollectionByKey, omit } from '../../../util/iteratees'; import { debounce, pause, throttle } from '../../../util/schedulers'; @@ -1101,13 +1103,7 @@ addActionHandler('togglePreHistoryHidden', async (global, actions, payload): Pro } global = getGlobal(); - - global = updateChat(global, chat.id, { - fullInfo: { - ...chat.fullInfo, - isPreHistoryHidden: isEnabled, - }, - }); + global = updateChatFullInfo(global, chat.id, { isPreHistoryHidden: isEnabled }); setGlobal(global); void callApi('togglePreHistoryHidden', { chat, isEnabled }); @@ -1144,34 +1140,30 @@ addActionHandler('updateChatMemberBannedRights', async (global, actions, payload global = getGlobal(); - const chatAfterUpdate = selectChat(global, chatId); - - if (!chatAfterUpdate || !chatAfterUpdate.fullInfo) { + const updatedFullInfo = selectChatFullInfo(global, chatId); + if (!updatedFullInfo) { return; } - const { members, kickedMembers } = chatAfterUpdate.fullInfo; + const { members, kickedMembers } = updatedFullInfo; const isBanned = Boolean(bannedRights.viewMessages); const isUnblocked = !Object.keys(bannedRights).length; - global = updateChat(global, chatId, { - fullInfo: { - ...chatAfterUpdate.fullInfo, - ...(members && isBanned && { - members: members.filter((m) => m.userId !== userId), - }), - ...(members && !isBanned && { - members: members.map((m) => ( - m.userId === userId - ? { ...m, bannedRights } - : m - )), - }), - ...(isUnblocked && kickedMembers && { - kickedMembers: kickedMembers.filter((m) => m.userId !== userId), - }), - }, + global = updateChatFullInfo(global, chatId, { + ...(members && isBanned && { + members: members.filter((m) => m.userId !== userId), + }), + ...(members && !isBanned && { + members: members.map((m) => ( + m.userId === userId + ? { ...m, bannedRights } + : m + )), + }), + ...(isUnblocked && kickedMembers && { + kickedMembers: kickedMembers.filter((m) => m.userId !== userId), + }), }); setGlobal(global); }); @@ -1219,15 +1211,11 @@ addActionHandler('updateChatAdmin', async (global, actions, payload): Promise => { @@ -1236,6 +1224,7 @@ addActionHandler('updateChat', async (global, actions, payload): Promise = } = payload; const chat = selectChat(global, chatId); + const fullInfo = selectChatFullInfo(global, chatId); if (!chat) { return; } @@ -1248,7 +1237,7 @@ addActionHandler('updateChat', async (global, actions, payload): Promise = chat.title !== title ? callApi('updateChatTitle', chat, title) : undefined, - chat.fullInfo && chat.fullInfo.about !== about + fullInfo?.about !== about ? callApi('updateChatAbout', chat, about) : undefined, photo @@ -1265,13 +1254,8 @@ addActionHandler('updateChatPhoto', async (global, actions, payload): Promise= chat.membersCount) return; const result = await callApi('fetchMembers', chat.id, chat.accessHash!, 'recent', offset); @@ -1714,26 +1689,15 @@ addActionHandler('toggleParticipantsHidden', async (global, actions, payload): P return; } - const prevIsEnabled = chat.fullInfo?.areParticipantsHidden; - - global = updateChat(global, chatId, { - fullInfo: { - ...chat.fullInfo, - areParticipantsHidden: isEnabled, - }, - }); + const prevIsEnabled = selectChatFullInfo(global, chat.id)?.areParticipantsHidden; + global = updateChatFullInfo(global, chatId, { areParticipantsHidden: isEnabled }); setGlobal(global); const result = await callApi('toggleParticipantsHidden', { chat, isEnabled }); if (!result && prevIsEnabled !== undefined) { global = getGlobal(); - global = updateChat(global, chatId, { - fullInfo: { - ...chat.fullInfo, - areParticipantsHidden: prevIsEnabled, - }, - }); + global = updateChatFullInfo(global, chatId, { areParticipantsHidden: prevIsEnabled }); setGlobal(global); } }); @@ -2000,11 +1964,10 @@ export async function loadFullChat( ); } - global = updateChat(global, chat.id, { - fullInfo, - ...(membersCount && { membersCount }), - }); - + if (membersCount !== undefined) { + global = updateChat(global, chat.id, { membersCount }); + } + global = replaceChatFullInfo(global, chat.id, fullInfo); setGlobal(global); const stickerSet = fullInfo.stickerSet; diff --git a/src/global/actions/api/management.ts b/src/global/actions/api/management.ts index 4de5621e3..8eefed8fd 100644 --- a/src/global/actions/api/management.ts +++ b/src/global/actions/api/management.ts @@ -5,7 +5,7 @@ import type { ActionReturnType } from '../../types'; import { callApi } from '../../../api/gramjs'; import { - addUsers, updateChat, updateManagement, updateManagementProgress, + addUsers, updateChat, updateChatFullInfo, updateManagement, updateManagementProgress, } from '../../reducers'; import { selectChat, selectCurrentMessageList, selectTabState, selectUser, @@ -392,13 +392,10 @@ addActionHandler('hideAllChatJoinRequests', async (global, actions, payload): Pr const targetChat = selectChat(global, chatId); if (!targetChat) return; - global = updateChat(global, chatId, { - joinRequests: [], - fullInfo: { - ...targetChat.fullInfo, - recentRequesterIds: [], - requestsPending: 0, - }, + global = updateChat(global, chatId, { joinRequests: [] }); + global = updateChatFullInfo(global, chatId, { + recentRequesterIds: [], + requestsPending: 0, }); setGlobal(global); }); diff --git a/src/global/actions/api/messages.ts b/src/global/actions/api/messages.ts index b5cc2d20f..6b1a35cd1 100644 --- a/src/global/actions/api/messages.ts +++ b/src/global/actions/api/messages.ts @@ -46,6 +46,7 @@ import { safeReplacePinnedIds, safeReplaceViewportIds, updateChat, + updateChatFullInfo, updateChatMessage, updateListedIds, updateMessageTranslation, @@ -86,6 +87,7 @@ import { selectThreadOriginChat, selectThreadTopMessageId, selectUser, + selectUserFullInfo, selectViewportIds, } from '../../selectors'; import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers'; @@ -1213,12 +1215,7 @@ addActionHandler('saveDefaultSendAs', (global, actions, payload): ActionReturnTy void callApi('saveDefaultSendAs', { sendAs: sendAsChat, chat }); - return updateChat(global, chatId, { - fullInfo: { - ...chat.fullInfo, - sendAsId, - }, - }); + return updateChatFullInfo(global, chatId, { sendAsId }); }); addActionHandler('loadSendAs', async (global, actions, payload): Promise => { @@ -1379,13 +1376,16 @@ addActionHandler('setForwardChatOrTopic', async (global, actions, payload): Prom const { chatId, topicId, tabId = getCurrentTabId() } = payload; let user = selectUser(global, chatId); if (user && selectForwardsContainVoiceMessages(global, tabId)) { - if (!user.fullInfo) { + let fullInfo = selectUserFullInfo(global, chatId); + if (!fullInfo) { const { accessHash } = user; - user = await callApi('fetchFullUser', { id: chatId, accessHash }); + const result = await callApi('fetchFullUser', { id: chatId, accessHash }); global = getGlobal(); + user = result?.user; + fullInfo = result?.fullInfo; } - if (user?.fullInfo!.noVoiceMessages) { + if (fullInfo!.noVoiceMessages) { actions.showDialog({ data: { message: translate('VoiceMessagesRestrictedByPrivacy', getUserFullName(user)), diff --git a/src/global/actions/api/settings.ts b/src/global/actions/api/settings.ts index 041f31f64..01fe55a9b 100644 --- a/src/global/actions/api/settings.ts +++ b/src/global/actions/api/settings.ts @@ -20,7 +20,7 @@ import { getServerTime } from '../../../util/serverTime'; import { selectChat, selectUser, selectTabState } from '../../selectors'; import { addUsers, addBlockedContact, updateChats, updateUser, removeBlockedContact, replaceSettings, updateNotifySettings, - addNotifyExceptions, updateChat, + addNotifyExceptions, updateChat, updateUserFullInfo, } from '../../reducers'; import { isUserId } from '../../helpers'; import { updateTabState } from '../../reducers/tabs'; @@ -67,12 +67,9 @@ addActionHandler('updateProfile', async (global, actions, payload): Promise => { const { chatId, isGroup, tabId = getCurrentTabId() } = payload; const chat = selectChat(global, chatId); - if (!chat?.fullInfo) { + const fullInfo = selectChatFullInfo(global, chatId); + if (!chat || !fullInfo) { return; } - const result = await callApi(isGroup ? 'fetchGroupStatistics' : 'fetchChannelStatistics', { chat }); + const result = await callApi( + isGroup ? 'fetchGroupStatistics' : 'fetchChannelStatistics', + { chat, dcId: fullInfo.statisticsDcId }, + ); if (!result) { return; } @@ -38,11 +42,13 @@ addActionHandler('loadStatistics', async (global, actions, payload): Promise => { const { chatId, messageId, tabId = getCurrentTabId() } = payload; const chat = selectChat(global, chatId); - if (!chat?.fullInfo) { + const fullInfo = selectChatFullInfo(global, chatId); + if (!chat || !fullInfo) { return; } - let result = await callApi('fetchMessageStatistics', { chat, messageId }); + const dcId = fullInfo.statisticsDcId; + let result = await callApi('fetchMessageStatistics', { chat, messageId, dcId }); if (!result) { result = {}; } @@ -53,7 +59,6 @@ addActionHandler('loadMessageStatistics', async (global, actions, payload): Prom result.views = views; result.forwards = forwards; - const dcId = chat.fullInfo!.statisticsDcId; const publicForwards = await callApi('fetchMessagePublicForwards', { chat, messageId, dcId }); result.publicForwards = publicForwards?.length; result.publicForwardsData = publicForwards; @@ -68,12 +73,12 @@ addActionHandler('loadStatisticsAsyncGraph', async (global, actions, payload): P const { chatId, token, name, isPercentage, tabId = getCurrentTabId(), } = payload; - const chat = selectChat(global, chatId); - if (!chat?.fullInfo) { + const fullInfo = selectChatFullInfo(global, chatId); + if (!fullInfo) { return; } - const dcId = chat.fullInfo!.statisticsDcId; + const dcId = fullInfo.statisticsDcId; const result = await callApi('fetchStatisticsAsyncGraph', { token, dcId, isPercentage }); if (!result) { diff --git a/src/global/actions/api/users.ts b/src/global/actions/api/users.ts index 60878f1a0..fe771ac06 100644 --- a/src/global/actions/api/users.ts +++ b/src/global/actions/api/users.ts @@ -12,7 +12,7 @@ import { buildCollectionByKey, unique } from '../../../util/iteratees'; import { isUserBot, isUserId } from '../../helpers'; import { callApi } from '../../../api/gramjs'; import { - selectChat, selectCurrentMessageList, selectTabState, selectUser, + selectChat, selectCurrentMessageList, selectTabState, selectUser, selectUserFullInfo, } from '../../selectors'; import { addChats, @@ -43,13 +43,16 @@ addActionHandler('loadFullUser', async (global, actions, payload): Promise } const { id, accessHash } = user; - const newUser = await callApi('fetchFullUser', { id, accessHash }); - if (!newUser) return; + const result = await callApi('fetchFullUser', { id, accessHash }); + if (!result?.user) return; + global = getGlobal(); + const fullInfo = selectUserFullInfo(global, userId); + const { user: newUser, fullInfo: newFullInfo } = result; const hasChangedAvatarHash = user.avatarHash !== newUser.avatarHash; - const hasChangedProfilePhoto = user.fullInfo?.profilePhoto?.id !== newUser.fullInfo?.profilePhoto?.id; - const hasChangedFallbackPhoto = user.fullInfo?.fallbackPhoto?.id !== newUser.fullInfo?.fallbackPhoto?.id; - const hasChangedPersonalPhoto = user.fullInfo?.personalPhoto?.id !== newUser.fullInfo?.personalPhoto?.id; + const hasChangedProfilePhoto = fullInfo?.profilePhoto?.id !== newFullInfo?.profilePhoto?.id; + const hasChangedFallbackPhoto = fullInfo?.fallbackPhoto?.id !== newFullInfo?.fallbackPhoto?.id; + const hasChangedPersonalPhoto = fullInfo?.personalPhoto?.id !== newFullInfo?.personalPhoto?.id; if ((hasChangedAvatarHash || hasChangedProfilePhoto || hasChangedFallbackPhoto || hasChangedPersonalPhoto) && user.photos?.length) { actions.loadProfilePhotos({ profileId: userId }); @@ -246,10 +249,16 @@ addActionHandler('loadProfilePhotos', async (global, actions, payload): Promise< return; } - if (user && !user.fullInfo?.profilePhoto) { + let fullInfo = selectUserFullInfo(global, profileId); + if (user && !fullInfo?.profilePhoto) { const { id, accessHash } = user; - user = await callApi('fetchFullUser', { id, accessHash }); - if (!user) return; + const result = await callApi('fetchFullUser', { id, accessHash }); + if (!result?.user) { + return; + } + + user = result.user; + fullInfo = result.fullInfo; } const result = await callApi('fetchProfilePhotos', user, chat); @@ -262,8 +271,8 @@ addActionHandler('loadProfilePhotos', async (global, actions, payload): Promise< const userOrChat = user || chat; const { photos, users } = result; photos.sort((a) => (a.id === userOrChat?.avatarHash ? -1 : 1)); - const fallbackPhoto = user?.fullInfo?.fallbackPhoto; - const personalPhoto = user?.fullInfo?.personalPhoto; + const fallbackPhoto = fullInfo?.fallbackPhoto; + const personalPhoto = fullInfo?.personalPhoto; if (fallbackPhoto) photos.push(fallbackPhoto); if (personalPhoto) photos.unshift(personalPhoto); diff --git a/src/global/actions/apiUpdaters/calls.ts b/src/global/actions/apiUpdaters/calls.ts index 28367063e..dc9023d28 100644 --- a/src/global/actions/apiUpdaters/calls.ts +++ b/src/global/actions/apiUpdaters/calls.ts @@ -2,7 +2,7 @@ import { addActionHandler, getGlobal } from '../../index'; import { removeGroupCall, updateGroupCall, updateGroupCallParticipant } from '../../reducers/calls'; import { omit } from '../../../util/iteratees'; import { selectChat } from '../../selectors'; -import { updateChat } from '../../reducers'; +import { updateChat, updateChatFullInfo } from '../../reducers'; import { ARE_CALLS_SUPPORTED } from '../../../util/windowEnvironment'; import { notifyAboutCall } from '../../../util/notifications'; import { selectGroupCall, selectPhoneCallUser } from '../../selectors/calls'; @@ -43,11 +43,8 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { case 'updateGroupCallChatId': { const chat = selectChat(global, update.chatId); if (chat) { - global = updateChat(global, update.chatId, { - fullInfo: { - ...chat.fullInfo, - groupCallId: update.call.id, - }, + global = updateChatFullInfo(global, update.chatId, { + groupCallId: update.call.id, }); } return global; diff --git a/src/global/actions/apiUpdaters/chats.ts b/src/global/actions/apiUpdaters/chats.ts index d44a5b943..b5f4afa75 100644 --- a/src/global/actions/apiUpdaters/chats.ts +++ b/src/global/actions/apiUpdaters/chats.ts @@ -7,11 +7,13 @@ import { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config'; import { buildCollectionByKey, omit, pick } from '../../../util/iteratees'; import { closeMessageNotifications, notifyAboutMessage } from '../../../util/notifications'; import { + leaveChat, + replaceThreadParam, updateChat, + updateChatFullInfo, updateChatListIds, updateChatListType, - replaceThreadParam, - leaveChat, updateTopic, + updateTopic, } from '../../reducers'; import { selectChat, @@ -20,6 +22,7 @@ import { selectChatListType, selectCurrentMessageList, selectThreadParam, + selectChatFullInfo, } from '../../selectors'; import { updateUnreadReactions } from '../../reducers/reactions'; import type { ActionReturnType } from '../../types'; @@ -178,18 +181,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { } case 'updateChatFullInfo': { - const { fullInfo } = update; - const targetChat = global.chats.byId[update.id]; - if (!targetChat) { - return undefined; - } - - return updateChat(global, update.id, { - fullInfo: { - ...targetChat.fullInfo, - ...fullInfo, - }, - }); + return updateChatFullInfo(global, update.id, update.fullInfo); } case 'updatePinnedChatIds': { @@ -301,15 +293,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { } case 'updateChatMembers': { - const targetChat = global.chats.byId[update.id]; + const targetChatFullInfo = selectChatFullInfo(global, update.id); const { replacedMembers, addedMember, deletedMemberId } = update; - if (!targetChat) { + if (!targetChatFullInfo) { return undefined; } let shouldUpdate = false; - let members = targetChat.fullInfo?.members - ? [...targetChat.fullInfo.members] + let members = targetChatFullInfo?.members + ? [...targetChatFullInfo.members] : []; if (replacedMembers) { @@ -335,14 +327,13 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { const adminMembers = members.filter(({ isOwner, isAdmin }) => isOwner || isAdmin); // TODO Kicked members? - return updateChat(global, update.id, { - membersCount: members.length, - fullInfo: { - ...targetChat.fullInfo, - members, - adminMembersById: buildCollectionByKey(adminMembers, 'userId'), - }, + global = updateChat(global, update.id, { membersCount: members.length }); + global = updateChatFullInfo(global, update.id, { + members, + adminMembersById: buildCollectionByKey(adminMembers, 'userId'), }); + + return global; } return undefined; @@ -393,12 +384,9 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { return undefined; } - global = updateChat(global, chatId, { - fullInfo: { - ...chat.fullInfo, - requestsPending, - recentRequesterIds, - }, + global = updateChatFullInfo(global, chatId, { + requestsPending, + recentRequesterIds, }); setGlobal(global); diff --git a/src/global/actions/apiUpdaters/initial.ts b/src/global/actions/apiUpdaters/initial.ts index 78e1e5c4a..b17b6cad2 100644 --- a/src/global/actions/apiUpdaters/initial.ts +++ b/src/global/actions/apiUpdaters/initial.ts @@ -10,10 +10,11 @@ import type { ApiUpdateAuthorizationError, ApiUpdateConnectionState, ApiUpdateSession, - ApiUpdateCurrentUser, ApiUpdateServerTimeOffset, + ApiUpdateCurrentUser, + ApiUpdateServerTimeOffset, } from '../../../api/types'; import { SESSION_USER_KEY } from '../../../config'; -import { updateUser } from '../../reducers'; +import { updateUser, updateUserFullInfo } from '../../reducers'; import { setLanguage } from '../../../util/langProvider'; import { selectTabState } from '../../selectors'; import { forceWebsync } from '../../../util/websync'; @@ -226,12 +227,13 @@ function onUpdateServerTimeOffset(update: ApiUpdateServerTimeOffset) { } function onUpdateCurrentUser(global: T, update: ApiUpdateCurrentUser) { - const { currentUser } = update; + const { currentUser, currentUserFullInfo } = update; global = { ...updateUser(global, currentUser.id, currentUser), currentUserId: currentUser.id, }; + global = updateUserFullInfo(global, currentUser.id, currentUserFullInfo); setGlobal(global); updateSessionUserId(currentUser.id); diff --git a/src/global/actions/apiUpdaters/users.ts b/src/global/actions/apiUpdaters/users.ts index 3349c58e4..7f508f9c4 100644 --- a/src/global/actions/apiUpdaters/users.ts +++ b/src/global/actions/apiUpdaters/users.ts @@ -2,9 +2,11 @@ import { addActionHandler, getGlobal, setGlobal } from '../../index'; import type { ApiUserStatus } from '../../../api/types'; -import { deleteContact, replaceUserStatuses, updateUser } from '../../reducers'; +import { + deleteContact, replaceUserStatuses, updateUser, updateUserFullInfo, +} from '../../reducers'; import { throttle } from '../../../util/schedulers'; -import { selectIsCurrentUserPremium, selectUser } from '../../selectors'; +import { selectIsCurrentUserPremium, selectUserFullInfo } from '../../selectors'; import type { ActionReturnType, RequiredGlobalState } from '../../types'; const STATUS_UPDATE_THROTTLE = 3000; @@ -53,7 +55,12 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { } }); - return updateUser(global, update.id, update.user); + global = updateUser(global, update.id, update.user); + if (update.fullInfo) { + global = updateUserFullInfo(global, update.id, update.fullInfo); + } + + return global; } case 'updateRequestUserUpdate': { @@ -73,34 +80,22 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { case 'updateUserFullInfo': { const { id, fullInfo } = update; - const targetUser = global.users.byId[id]; - if (!targetUser) { - return undefined; - } - return updateUser(global, id, { - fullInfo: { - ...targetUser.fullInfo, - ...fullInfo, - }, - }); + return updateUserFullInfo(global, id, fullInfo); } case 'updateBotMenuButton': { const { botId, button } = update; - const targetUser = selectUser(global, botId); - if (!targetUser?.fullInfo?.botInfo) { + const targetUserFullInfo = selectUserFullInfo(global, botId); + if (!targetUserFullInfo?.botInfo) { return undefined; } - return updateUser(global, botId, { - fullInfo: { - ...targetUser.fullInfo, - botInfo: { - ...targetUser.fullInfo.botInfo, - menuButton: button, - }, + return updateUserFullInfo(global, botId, { + botInfo: { + ...targetUserFullInfo.botInfo, + menuButton: button, }, }); } diff --git a/src/global/actions/ui/calls.ts b/src/global/actions/ui/calls.ts index 23b61909c..542b4f9b9 100644 --- a/src/global/actions/ui/calls.ts +++ b/src/global/actions/ui/calls.ts @@ -6,7 +6,7 @@ import { } from '../../index'; import { callApi } from '../../../api/gramjs'; import { - selectChat, selectTabState, selectUser, + selectChat, selectChatFullInfo, selectTabState, selectUser, } from '../../selectors'; import { copyTextToClipboard } from '../../../util/clipboard'; import { fetchChatByUsername, loadFullChat } from '../api/chats'; @@ -199,7 +199,7 @@ addActionHandler('createGroupCallInviteLink', async (global, actions, payload): const hasPublicUsername = Boolean(getMainUsername(chat)); - let { inviteLink } = chat.fullInfo!; + let inviteLink = selectChatFullInfo(global, chat.id)?.inviteLink; if (hasPublicUsername) { inviteLink = await callApi('exportGroupCallInvite', { call: groupCall, diff --git a/src/global/cache.ts b/src/global/cache.ts index 76a4ce85a..7d13c1c25 100644 --- a/src/global/cache.ts +++ b/src/global/cache.ts @@ -4,7 +4,9 @@ import { addCallback, removeCallback } from '../lib/teact/teactn'; import { addActionHandler, getGlobal } from './index'; import type { ActionReturnType, GlobalState, MessageList } from './types'; -import type { ApiChat, ApiUser } from '../api/types'; +import type { + ApiChat, ApiChatFullInfo, ApiUser, ApiUserFullInfo, +} from '../api/types'; import { MAIN_THREAD_ID } from '../api/types'; import { onBeforeUnload, onIdle, throttle } from '../util/schedulers'; @@ -224,6 +226,46 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) { cached.customEmojis.forEmoji = {}; } + if (!cached.users.fullInfoById) { + const result = Object.entries(cached.users.byId).reduce((acc, [id, user]) => { + if ('fullInfo' in user) { + if (user.fullInfo !== undefined) { + acc.fullInfo[id] = user.fullInfo as ApiUserFullInfo; + } + delete user.fullInfo; + } + acc.users[id] = user; + + return acc; + }, { + users: {} as Record, + fullInfo: {} as Record, + }); + + cached.users.fullInfoById = result.fullInfo; + cached.users.byId = result.users; + } + + if (!cached.chats.fullInfoById) { + const result = Object.entries(cached.chats.byId).reduce((acc, [id, chat]) => { + if ('fullInfo' in chat) { + if (chat.fullInfo !== undefined) { + acc.fullInfo[id] = chat.fullInfo as ApiChatFullInfo; + } + delete chat.fullInfo; + } + acc.chats[id] = chat; + + return acc; + }, { + chats: {} as Record, + fullInfo: {} as Record, + }); + + cached.chats.fullInfoById = result.fullInfo; + cached.chats.byId = result.chats; + } + // TODO Remove in Jan 2023 (this was re-designed but can be hardcoded in cache) const { light: lightTheme } = cached.settings.themes; if (lightTheme?.patternColor === 'rgba(90, 110, 70, 0.6)' || !lightTheme?.patternColor) { @@ -382,7 +424,7 @@ function reduceCustomEmojis(global: T): GlobalState['cust } function reduceUsers(global: T): GlobalState['users'] { - const { users: { byId, statusesById }, currentUserId } = global; + const { users: { byId, statusesById, fullInfoById }, currentUserId } = global; const currentChatIds = compact( Object.values(global.byTabId) .map(({ id: tabId }) => selectCurrentMessageList(global, tabId)), @@ -406,6 +448,7 @@ function reduceUsers(global: T): GlobalState['users'] { return { byId: pick(byId, idsToSave), statusesById: pick(statusesById, idsToSave), + fullInfoById: pick(fullInfoById, idsToSave), }; } @@ -440,6 +483,7 @@ function reduceChats(global: T): GlobalState['chats'] { ...global.chats, isFullyLoaded: {}, byId: pick(global.chats.byId, idsToSave), + fullInfoById: pick(global.chats.fullInfoById, idsToSave), }; } diff --git a/src/global/helpers/chats.ts b/src/global/helpers/chats.ts index df3822342..2f6265ba7 100644 --- a/src/global/helpers/chats.ts +++ b/src/global/helpers/chats.ts @@ -3,7 +3,9 @@ import type { ApiUser, ApiChatBannedRights, ApiChatAdminRights, - ApiChatFolder, ApiTopic, ApiUserFullInfo, + ApiChatFolder, + ApiTopic, + ApiPhoto, } from '../../api/types'; import { MAIN_THREAD_ID, @@ -87,25 +89,10 @@ export function getChatTitle(lang: LangFn, chat: ApiChat, isSelf = false) { return chat.title || lang('HiddenName'); } -export function getChatDescription(chat: ApiChat) { - if (!chat.fullInfo) { - return undefined; - } - return chat.fullInfo.about; -} - export function getChatLink(chat: ApiChat) { - const { usernames } = chat; - if (usernames) { - const activeUsername = usernames.find((u) => u.isActive); - if (activeUsername) { - return `${TME_LINK_PREFIX}${activeUsername.username}`; - } - } + const activeUsername = chat.usernames?.find((u) => u.isActive); - const { inviteLink } = chat.fullInfo || {}; - - return inviteLink; + return activeUsername ? `${TME_LINK_PREFIX}${activeUsername.username}` : undefined; } export function getChatMessageLink(chatId: string, chatUsername?: string, threadId?: number, messageId?: number) { @@ -124,24 +111,14 @@ export function getChatAvatarHash( size: 'normal' | 'big' = 'normal', type: 'photo' | 'video' = 'photo', avatarHash = owner.avatarHash, + profilePhoto?: ApiPhoto, ) { if (!avatarHash) { return undefined; } - const { fullInfo } = owner; if (type === 'video') { - const userFullInfo = isUserId(owner.id) ? fullInfo as ApiUserFullInfo : undefined; - if (userFullInfo?.personalPhoto?.isVideo) { - return getVideoAvatarMediaHash(userFullInfo.personalPhoto); - } - if (fullInfo?.profilePhoto?.isVideo) { - return getVideoAvatarMediaHash(fullInfo.profilePhoto); - } - if (userFullInfo?.fallbackPhoto?.isVideo) { - return getVideoAvatarMediaHash(userFullInfo.fallbackPhoto); - } - return undefined; + return profilePhoto?.isVideo ? getVideoAvatarMediaHash(profilePhoto) : undefined; } switch (size) { @@ -310,14 +287,6 @@ export function getForumComposerPlaceholder( return undefined; } -export function getChatSlowModeOptions(chat?: ApiChat) { - if (!chat || !chat.fullInfo) { - return undefined; - } - - return chat.fullInfo.slowMode; -} - export function isChatArchived(chat: ApiChat) { return chat.folderId === ARCHIVED_FOLDER_ID; } diff --git a/src/global/initialState.ts b/src/global/initialState.ts index c70362a66..80227d984 100644 --- a/src/global/initialState.ts +++ b/src/global/initialState.ts @@ -38,6 +38,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = { users: { byId: {}, statusesById: {}, + fullInfoById: {}, }, chats: { @@ -46,6 +47,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = { orderedPinnedIds: {}, totalCount: {}, byId: {}, + fullInfoById: {}, }, messages: { diff --git a/src/global/reducers/calls.ts b/src/global/reducers/calls.ts index cffb1d2a4..84ad9cda9 100644 --- a/src/global/reducers/calls.ts +++ b/src/global/reducers/calls.ts @@ -3,7 +3,7 @@ import type { GlobalState } from '../types'; import type { ApiGroupCall } from '../../api/types'; import { selectGroupCall } from '../selectors/calls'; import { omit } from '../../util/iteratees'; -import { updateChat } from './chats'; +import { updateChatFullInfo } from './chats'; import { selectChat } from '../selectors'; export function updateGroupCall( @@ -53,11 +53,8 @@ export function removeGroupCall( if (groupCall && groupCall.chatId) { const chat = selectChat(global, groupCall.chatId); if (chat) { - global = updateChat(global, groupCall.chatId, { - fullInfo: { - ...chat.fullInfo, - groupCallId: undefined, - }, + global = updateChatFullInfo(global, groupCall.chatId, { + groupCallId: undefined, }); } } diff --git a/src/global/reducers/chats.ts b/src/global/reducers/chats.ts index bcaf7d239..4afcad474 100644 --- a/src/global/reducers/chats.ts +++ b/src/global/reducers/chats.ts @@ -1,13 +1,13 @@ import type { GlobalState } from '../types'; import type { - ApiChat, ApiChatMember, ApiTopic, ApiPhoto, + ApiChat, ApiChatMember, ApiTopic, ApiPhoto, ApiChatFullInfo, } from '../../api/types'; import { ARCHIVED_FOLDER_ID } from '../../config'; import { areSortedArraysEqual, buildCollectionByKey, omit, unique, } from '../../util/iteratees'; -import { selectChat, selectChatListType } from '../selectors'; +import { selectChat, selectChatFullInfo, selectChatListType } from '../selectors'; import { updateThread, updateThreadInfo } from './messages'; import { areDeepEqual } from '../../util/areDeepEqual'; @@ -73,6 +73,50 @@ export function updateChat( }); } +export function updateChatFullInfo( + global: T, chatId: string, fullInfoUpdate: Partial, +): T { + const currentFullInfo = selectChatFullInfo(global, chatId); + const updatedFullInfo = { + ...currentFullInfo, + ...fullInfoUpdate, + }; + + if (areDeepEqual(currentFullInfo, updatedFullInfo)) { + return global; + } + + return { + ...global, + chats: { + ...global.chats, + fullInfoById: { + ...global.chats.fullInfoById, + [chatId]: updatedFullInfo, + }, + }, + }; +} + +export function replaceChatFullInfo(global: T, chatId: string, fullInfo: ApiChatFullInfo): T { + const currentFullInfo = selectChatFullInfo(global, chatId); + + if (areDeepEqual(currentFullInfo, fullInfo)) { + return global; + } + + return { + ...global, + chats: { + ...global.chats, + fullInfoById: { + ...global.chats.fullInfoById, + [chatId]: fullInfo, + }, + }, + }; +} + export function updateChats(global: T, newById: Record): T { const updatedById = Object.keys(newById).reduce((acc: Record, id) => { const updatedChat = getUpdatedChat(global, id, newById[id]); @@ -248,10 +292,10 @@ export function leaveChat(global: T, leftChatId: string): } export function addChatMembers(global: T, chat: ApiChat, membersToAdd: ApiChatMember[]): T { - const currentMembers = chat.fullInfo?.members; + const currentMembers = selectChatFullInfo(global, chat.id)?.members; const newMemberIds = new Set(membersToAdd.map((m) => m.userId)); const updatedMembers = [ - ...currentMembers?.filter((m) => !newMemberIds.has(m.userId)) || [], + ...currentMembers?.filter(({ userId }) => !newMemberIds.has(userId)) || [], ...membersToAdd, ]; const currentIds = currentMembers?.map(({ userId }) => userId) || []; @@ -263,12 +307,9 @@ export function addChatMembers(global: T, chat: ApiChat, const adminMembers = updatedMembers.filter(({ isAdmin, isOwner }) => isAdmin || isOwner); - return updateChat(global, chat.id, { - fullInfo: { - ...chat.fullInfo, - members: updatedMembers, - adminMembersById: buildCollectionByKey(adminMembers, 'userId'), - }, + return updateChatFullInfo(global, chat.id, { + members: updatedMembers, + adminMembersById: buildCollectionByKey(adminMembers, 'userId'), }); } diff --git a/src/global/reducers/users.ts b/src/global/reducers/users.ts index 68329ce72..6e14b5ba8 100644 --- a/src/global/reducers/users.ts +++ b/src/global/reducers/users.ts @@ -1,5 +1,5 @@ import type { TabState, GlobalState, TabArgs } from '../types'; -import type { ApiUser, ApiUserStatus } from '../../api/types'; +import type { ApiUser, ApiUserFullInfo, ApiUserStatus } from '../../api/types'; import { omit, pick } from '../../util/iteratees'; import { MEMO_EMPTY_ARRAY } from '../../util/memo'; @@ -189,19 +189,13 @@ export function updateUserSearchFetchingStatus( } export function updateUserBlockedState(global: T, userId: string, isBlocked: boolean): T { - const { byId } = global.users; - const user = byId[userId]; - if (!user || !user.fullInfo) { + const { fullInfoById } = global.users; + const fullInfo = fullInfoById[userId]; + if (!fullInfo) { return global; } - return updateUser(global, userId, { - ...user, - fullInfo: { - ...user.fullInfo, - isBlocked, - }, - }); + return updateUserFullInfo(global, userId, { isBlocked }); } export function replaceUserStatuses(global: T, newById: Record): T { @@ -214,6 +208,26 @@ export function replaceUserStatuses(global: T, newById: R }; } +export function updateUserFullInfo( + global: T, userId: string, fullInfo: Partial, +): T { + const userFullInfo = global.users.fullInfoById[userId]; + + return { + ...global, + users: { + ...global.users, + fullInfoById: { + ...global.users.fullInfoById, + [userId]: { + ...userFullInfo, + ...fullInfo, + }, + }, + }, + }; +} + // @optimization Allows to avoid redundant updates which cause a lot of renders export function addUserStatuses(global: T, newById: Record): T { const { statusesById } = global.users; diff --git a/src/global/selectors/calls.ts b/src/global/selectors/calls.ts index 64603373e..04b8bb865 100644 --- a/src/global/selectors/calls.ts +++ b/src/global/selectors/calls.ts @@ -1,13 +1,13 @@ import type { GlobalState } from '../types'; -import { selectChat } from './chats'; +import { selectChat, selectChatFullInfo } from './chats'; import { isChatBasicGroup } from '../helpers'; import { selectUser } from './users'; export function selectChatGroupCall(global: T, chatId: string) { - const chat = selectChat(global, chatId); - if (!chat || !chat.fullInfo || !chat.fullInfo.groupCallId) return undefined; + const fullInfo = selectChatFullInfo(global, chatId); + if (!fullInfo || !fullInfo.groupCallId) return undefined; - return selectGroupCall(global, chat.fullInfo.groupCallId); + return selectGroupCall(global, fullInfo.groupCallId); } export function selectGroupCall(global: T, groupCallId: string) { diff --git a/src/global/selectors/chats.ts b/src/global/selectors/chats.ts index 975199874..e696302de 100644 --- a/src/global/selectors/chats.ts +++ b/src/global/selectors/chats.ts @@ -1,4 +1,4 @@ -import type { ApiChatType, ApiChat } from '../../api/types'; +import type { ApiChatType, ApiChat, ApiChatFullInfo } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types'; import type { GlobalState, TabArgs } from '../types'; @@ -16,6 +16,10 @@ export function selectChat(global: T, chatId: string): Ap return global.chats.byId[chatId]; } +export function selectChatFullInfo(global: T, chatId: string): ApiChatFullInfo | undefined { + return global.chats.fullInfoById[chatId]; +} + export function selectChatUser(global: T, chat: ApiChat) { const userId = getPrivateChatUserId(chat); if (!userId) { @@ -39,15 +43,16 @@ export function selectSupportChat(global: T) { } export function selectChatOnlineCount(global: T, chat: ApiChat) { - if (isUserId(chat.id) || isChatChannel(chat) || !chat.fullInfo) { + const fullInfo = selectChatFullInfo(global, chat.id); + if (isUserId(chat.id) || isChatChannel(chat) || !fullInfo) { return undefined; } - if (!chat.fullInfo.members || chat.fullInfo.members.length === MEMBERS_LOAD_SLICE) { - return chat.fullInfo.onlineCount; + if (!fullInfo.members || fullInfo.members.length === MEMBERS_LOAD_SLICE) { + return fullInfo.onlineCount; } - return chat.fullInfo.members.reduce((onlineCount, { userId }) => { + return fullInfo.members.reduce((onlineCount, { userId }) => { if ( userId !== global.currentUserId && global.users.byId[userId] @@ -195,7 +200,7 @@ export function selectSendAs(global: T, chatId: string) { const chat = selectChat(global, chatId); if (!chat) return undefined; - const id = chat?.fullInfo?.sendAsId; + const id = selectChatFullInfo(global, chatId)?.sendAsId; if (!id) return undefined; return selectUser(global, id) || selectChat(global, id); diff --git a/src/global/selectors/messages.ts b/src/global/selectors/messages.ts index c16f7d599..bbdffe4b1 100644 --- a/src/global/selectors/messages.ts +++ b/src/global/selectors/messages.ts @@ -15,7 +15,7 @@ import { GENERAL_TOPIC_ID, LOCAL_MESSAGE_MIN_ID, REPLIES_USER_ID, SERVICE_NOTIFICATIONS_USER_ID, } from '../../config'; import { - selectChat, selectChatBot, selectIsChatWithSelf, + selectChat, selectChatBot, selectChatFullInfo, selectIsChatWithSelf, } from './chats'; import { selectIsCurrentUserPremium, selectIsUserOrChatContact, selectUser, selectUserStatus, @@ -1200,7 +1200,7 @@ export function selectDefaultReaction(global: T, chatId: return defaultReaction; } - const chatReactions = selectChat(global, chatId)?.fullInfo?.enabledReactions; + const chatReactions = selectChatFullInfo(global, chatId)?.enabledReactions; if (!chatReactions || !canSendReaction(defaultReaction, chatReactions)) { return undefined; } @@ -1266,7 +1266,8 @@ export function selectMessageCustomEmojiSets( // If some emoji still loading, do not return empty array if (!documents.every(Boolean)) return undefined; const sets = documents.map((doc) => doc.stickerSetInfo); - const setsWithoutDuplicates = sets.reduce((acc, set) => { + + return sets.reduce((acc, set) => { if ('shortName' in set) { if (acc.some((s) => 'shortName' in s && s.shortName === set.shortName)) { return acc; @@ -1281,7 +1282,6 @@ export function selectMessageCustomEmojiSets( acc.push(set); // Optimization return acc; }, [] as ApiStickerSetInfo[]); - return setsWithoutDuplicates; } export function selectForwardsContainVoiceMessages( diff --git a/src/global/selectors/statistics.ts b/src/global/selectors/statistics.ts index ecd79f0fa..afa9c72f3 100644 --- a/src/global/selectors/statistics.ts +++ b/src/global/selectors/statistics.ts @@ -1,7 +1,7 @@ import type { GlobalState, TabArgs } from '../types'; import { selectCurrentMessageList } from './messages'; -import { selectChat } from './chats'; +import { selectChatFullInfo } from './chats'; import { selectTabState } from './tabs'; import { getCurrentTabId } from '../../util/establishMultitabRole'; @@ -21,9 +21,8 @@ export function selectIsStatisticsShown( } const { chatId: currentChatId } = selectCurrentMessageList(global, tabId) || {}; - const chat = currentChatId ? selectChat(global, currentChatId) : undefined; - return chat?.fullInfo?.canViewStatistics; + return currentChatId ? selectChatFullInfo(global, currentChatId)?.canViewStatistics : undefined; } export function selectIsMessageStatisticsShown( diff --git a/src/global/selectors/users.ts b/src/global/selectors/users.ts index d995a6dc2..75f32262d 100644 --- a/src/global/selectors/users.ts +++ b/src/global/selectors/users.ts @@ -1,5 +1,7 @@ import type { GlobalState } from '../types'; -import type { ApiChat, ApiUser, ApiUserStatus } from '../../api/types'; +import type { + ApiChat, ApiUser, ApiUserFullInfo, ApiUserStatus, +} from '../../api/types'; import { isUserBot } from '../helpers'; export function selectUser(global: T, userId: string): ApiUser | undefined { @@ -10,10 +12,18 @@ export function selectUserStatus(global: T, userId: strin return global.users.statusesById[userId]; } -export function selectIsUserBlocked(global: T, userId: string) { - const user = selectUser(global, userId); +export function selectUserFullInfo(global: T, userId: string): ApiUserFullInfo | undefined { + return global.users.fullInfoById[userId]; +} - return user?.fullInfo?.isBlocked; +export function selectUserPhotoFromFullInfo(global: T, userId: string) { + const fullInfo = selectUserFullInfo(global, userId); + + return fullInfo?.personalPhoto || fullInfo?.profilePhoto || fullInfo?.fallbackPhoto; +} + +export function selectIsUserBlocked(global: T, userId: string) { + return selectUserFullInfo(global, userId)?.isBlocked; } export function selectIsCurrentUserPremium(global: T) { diff --git a/src/global/types.ts b/src/global/types.ts index edb4dd405..08c938d96 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -58,6 +58,8 @@ import type { ApiWallpaper, ApiWebPage, ApiWebSession, + ApiUserFullInfo, + ApiChatFullInfo, } from '../api/types'; import type { ApiInvoiceContainer, @@ -635,6 +637,8 @@ export type GlobalState = { users: { byId: Record; statusesById: Record; + // Obtained from GetFullUser / UserFullInfo + fullInfoById: Record; }; chats: { @@ -657,6 +661,8 @@ export type GlobalState = { archived?: boolean; }; forDiscussionIds?: string[]; + // Obtained from GetFullChat / GetFullChannel + fullInfoById: Record; }; messages: { diff --git a/src/hooks/useBoundsInSharedCanvas.ts b/src/hooks/useBoundsInSharedCanvas.ts index c2f784954..89924edce 100644 --- a/src/hooks/useBoundsInSharedCanvas.ts +++ b/src/hooks/useBoundsInSharedCanvas.ts @@ -33,6 +33,10 @@ export default function useBoundsInSharedCanvas( const target = container.classList.contains('sticker-set-cover') || container.classList.contains('sticker-reaction') ? container : container.querySelector('img')!; + if (!target) { + return; + } + const targetBounds = target.getBoundingClientRect(); const canvasBounds = canvas.getBoundingClientRect(); diff --git a/src/hooks/useShowTransition.ts b/src/hooks/useShowTransition.ts index 1d305bac8..fd12945b6 100644 --- a/src/hooks/useShowTransition.ts +++ b/src/hooks/useShowTransition.ts @@ -55,7 +55,9 @@ const useShowTransition = ( className && 'opacity-transition', className, shouldHaveOpenClassName && 'open', + !shouldHaveOpenClassName && 'not-open', shouldRender && 'shown', + !shouldRender && 'not-shown', isClosing && 'closing', ); diff --git a/src/lib/lovely-chart/captureEvents.js b/src/lib/lovely-chart/captureEvents.js index 1eca9a6b5..093bee625 100644 --- a/src/lib/lovely-chart/captureEvents.js +++ b/src/lib/lovely-chart/captureEvents.js @@ -24,7 +24,7 @@ export function captureEvents(element, options) { } if (options.draggingCursor) { - document.documentElement.classList.add(`cursor-${options.draggingCursor}`); + document.body.classList.add(`cursor-${options.draggingCursor}`); } options.onCapture && options.onCapture(e); @@ -42,7 +42,7 @@ export function captureEvents(element, options) { } if (options.draggingCursor) { - document.documentElement.classList.remove(`cursor-${options.draggingCursor}`); + document.body.classList.remove(`cursor-${options.draggingCursor}`); } removeEventListener(document, 'mouseup', onRelease); diff --git a/src/lib/lovely-chart/styles/_common.scss b/src/lib/lovely-chart/styles/_common.scss index 37f43fd0e..b7f557721 100644 --- a/src/lib/lovely-chart/styles/_common.scss +++ b/src/lib/lovely-chart/styles/_common.scss @@ -120,14 +120,6 @@ } } -html.cursor-ew-resize, html.cursor-ew-resize * { - cursor: ew-resize !important; -} - -html.cursor-grabbing, html.cursor-grabbing * { - cursor: grabbing !important; -} - .lovely-chart--spinner { width: 16px; height: 16px; diff --git a/src/lib/lovely-chart/styles/_header.scss b/src/lib/lovely-chart/styles/_header.scss index 56e64e3b7..e3d678654 100644 --- a/src/lib/lovely-chart/styles/_header.scss +++ b/src/lib/lovely-chart/styles/_header.scss @@ -35,7 +35,7 @@ display: flex; align-items: center; color: var(--zoom-out-text); - cursor: pointer; + cursor: var(--custom-cursor, pointer); text-transform: none; &::before { diff --git a/src/lib/lovely-chart/styles/_minimap.scss b/src/lib/lovely-chart/styles/_minimap.scss index 87eb096bc..0baa9e662 100644 --- a/src/lib/lovely-chart/styles/_minimap.scss +++ b/src/lib/lovely-chart/styles/_minimap.scss @@ -65,7 +65,7 @@ width: calc(100% - 16px); height: 100%; background: transparent !important; - cursor: grab; + cursor: var(--custom-cursor, grab); // transition: border-color 300ms ease-out; } @@ -74,7 +74,7 @@ height: 100%; position: relative; background: var(--minimap-mask); - cursor: ew-resize; + cursor: var(--custom-cursor, ew-resize); // transition: background-color 300ms ease-out; &::before, &::after { diff --git a/src/lib/lovely-chart/styles/_tooltip.scss b/src/lib/lovely-chart/styles/_tooltip.scss index 22f0c1552..3bcb0d09a 100644 --- a/src/lib/lovely-chart/styles/_tooltip.scss +++ b/src/lib/lovely-chart/styles/_tooltip.scss @@ -20,7 +20,7 @@ box-shadow: 0 1px 2px 1px rgba(211, 211, 211, 0.8); opacity: 0; transition: opacity 200ms ease-out; - cursor: pointer; + cursor: var(--custom-cursor, pointer); pointer-events: none; &.lovely-chart--state-shown { @@ -29,7 +29,7 @@ } &.lovely-chart--state-inactive { - cursor: default; + cursor: var(--custom-cursor, default); .lovely-chart--tooltip-title::after { display: none; diff --git a/src/styles/_forms.scss b/src/styles/_forms.scss index 817b306a2..6bbc57904 100644 --- a/src/styles/_forms.scss +++ b/src/styles/_forms.scss @@ -24,7 +24,7 @@ font-weight: 400; color: var(--color-placeholders); transition: transform 0.15s ease-out, color 0.15s ease-out; - cursor: text; + cursor: var(--custom-cursor, text); pointer-events: none; transform-origin: left center; white-space: nowrap; @@ -216,7 +216,7 @@ textarea.form-control { display: flex; align-items: center; justify-content: center; - cursor: pointer; + cursor: var(--custom-cursor, pointer); outline: none !important; color: var(--color-text-secondary); diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index e6e8b3420..70aebc220 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -1,6 +1,6 @@ // @optimization @mixin while-transition() { - .Transition__slide:not(.Transition__slide--active) & { + .Transition_slide:not(.Transition_slide-active) & { @content; } } @@ -29,7 +29,7 @@ &::-ms-track { width: 100%; - cursor: pointer; + cursor: var(--custom-cursor, pointer); background: transparent; border-color: transparent; @@ -45,11 +45,11 @@ } &::-webkit-slider-runnable-track { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } &::-moz-range-track, &::-moz-range-progress { - cursor: pointer; + cursor: var(--custom-cursor, pointer); } } } diff --git a/src/styles/icons.scss b/src/styles/icons.scss index b6092dd37..9473e6067 100644 --- a/src/styles/icons.scss +++ b/src/styles/icons.scss @@ -7,7 +7,7 @@ font-display: block; } -[class^="icon-"], [class*=" icon-"] { +.icon { /* use !important to prevent issues with browser extensions that change fonts */ /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */ font-family: 'icomoon' !important; @@ -23,14 +23,12 @@ -moz-osx-font-smoothing: grayscale; } -[class^="icon-char-"], [class*=" icon-char-"] { - &::before { - font-family: Roboto, "Helvetica Neue", sans-serif; - content: attr(data-char); - width: 1.5rem; - text-align: center; - display: block; - } +.icon-char::before { + font-family: Roboto, "Helvetica Neue", sans-serif; + content: attr(data-char); + width: 1.5rem; + text-align: center; + display: block; } .icon-loop:before { diff --git a/src/styles/index.scss b/src/styles/index.scss index ad8456625..31d98921a 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -70,12 +70,13 @@ body.is-ios { --border-radius-messages-small: 0.5rem; } -body.cursor-grabbing, -body.cursor-grabbing * { +body.cursor-grabbing { + --custom-cursor: grabbing; cursor: grabbing !important; } body.cursor-ew-resize { + --custom-cursor: ew-resize; cursor: ew-resize !important; } @@ -118,7 +119,7 @@ body.cursor-ew-resize { bottom: 0; width: 0.25rem; z-index: var(--z-resize-handle); - cursor: ew-resize; + cursor: var(--custom-cursor, ew-resize); @media (min-width: 926px) { display: block; @@ -144,25 +145,6 @@ body.cursor-ew-resize { z-index: -1; } -.not-implemented { - opacity: 0.5; - - &, - & * { - cursor: not-allowed !important; - } -} - -* { - box-sizing: border-box; -} - -.no-animations #root *, -.no-animations #root *::before, -.no-animations #root *::after { - transition: none !important; -} - .custom-scroll, .custom-scroll-x { scrollbar-width: thin; @@ -230,20 +212,20 @@ body:not(.is-ios) { } } -div[role="button"] { +.div-button { outline: none !important; - cursor: pointer; + cursor: var(--custom-cursor, pointer); } .opacity-transition { opacity: 1; transition: opacity 0.15s ease; - &:not(.open) { + &.not-open { opacity: 0; } - &:not(.shown) { + &.not-shown { display: none; } diff --git a/src/styles/print.scss b/src/styles/print.scss index 7fb70c708..014488a7e 100644 --- a/src/styles/print.scss +++ b/src/styles/print.scss @@ -31,7 +31,7 @@ .MessageList, .messages-layout, .Transition, - .Transition > .Transition__slide { + .Transition > .Transition_slide { height: auto !important; overflow: visible !important; display: block !important; diff --git a/src/styles/reboot.css b/src/styles/reboot.css index 11306cac7..7f54e3385 100644 --- a/src/styles/reboot.css +++ b/src/styles/reboot.css @@ -206,6 +206,7 @@ label { button { border-radius: 0; + -webkit-appearance: button; } button:focus { @@ -234,17 +235,7 @@ select { text-transform: none; } -button, -html [type="button"], -[type="reset"], -[type="submit"] { - -webkit-appearance: button; -} - -button::-moz-focus-inner, -[type="button"]::-moz-focus-inner, -[type="reset"]::-moz-focus-inner, -[type="submit"]::-moz-focus-inner { +button::-moz-focus-inner { padding: 0; border-style: none; } diff --git a/src/util/switchTheme.ts b/src/util/switchTheme.ts index 62c32744c..6f63965e6 100644 --- a/src/util/switchTheme.ts +++ b/src/util/switchTheme.ts @@ -24,11 +24,27 @@ const RGB_VARIABLES = new Set([ '--color-text-secondary', ]); +const DISABLE_ANIMATION_CSS = ` +.no-animations #root *, +.no-animations #root *::before, +.no-animations #root *::after { + transition: none !important; +}`; + const colors = (Object.keys(themeColors) as Array).map((property) => ({ property, colors: [hexToRgb(themeColors[property][0]), hexToRgb(themeColors[property][1])], })); +const injectCss = (css: string) => { + const style = document.createElement('style'); + style.textContent = css; + document.head.appendChild(style); + return () => { + document.head.removeChild(style); + }; +}; + const switchTheme = (theme: ISettings['theme'], withAnimation: boolean) => { const themeClassName = `theme-${theme}`; if (document.documentElement.classList.contains(themeClassName)) { @@ -43,7 +59,9 @@ const switchTheme = (theme: ISettings['theme'], withAnimation: boolean) => { requestMutation(() => { document.documentElement.classList.remove(`theme-${isDarkTheme ? 'light' : 'dark'}`); + let uninjectCss: (() => void) | undefined; if (isInitialized) { + uninjectCss = injectCss(DISABLE_ANIMATION_CSS); document.documentElement.classList.add('no-animations'); } document.documentElement.classList.add(themeClassName); @@ -53,6 +71,7 @@ const switchTheme = (theme: ISettings['theme'], withAnimation: boolean) => { setTimeout(() => { requestMutation(() => { + uninjectCss?.(); document.documentElement.classList.remove('no-animations'); }); }, ENABLE_ANIMATION_DELAY_MS); diff --git a/src/util/windowEnvironment.ts b/src/util/windowEnvironment.ts index 6242ed1bd..5dd4c5317 100644 --- a/src/util/windowEnvironment.ts +++ b/src/util/windowEnvironment.ts @@ -75,7 +75,7 @@ export const IS_CANVAS_FILTER_SUPPORTED = ( ); export const IS_REQUEST_FULLSCREEN_SUPPORTED = 'requestFullscreen' in document.createElement('div'); export const ARE_CALLS_SUPPORTED = !navigator.userAgent.includes('Firefox'); -export const LAYERS_ANIMATION_NAME = IS_ANDROID ? 'slide-fade' : IS_IOS ? 'slide-layers' : 'push-slide'; +export const LAYERS_ANIMATION_NAME = IS_ANDROID ? 'slideFade' : IS_IOS ? 'slideLayers' : 'pushSlide'; const TEST_VIDEO = document.createElement('video'); // `canPlayType(VIDEO_MOV_TYPE)` returns false negative at least for macOS Chrome and iOS Safari