Introduce Multi-Accounts

This commit is contained in:
Alexander Zinchuk 2025-04-23 18:57:46 +02:00
parent 65e0d05ac4
commit a6f8f232a8
248 changed files with 1841 additions and 853 deletions

View File

@ -11,9 +11,10 @@ function compatTest() {
var hasNumberFormat = hasIntl && typeof Intl.NumberFormat !== 'undefined';
var hasWebLocks = typeof navigator.locks !== 'undefined';
var hasBigInt = typeof BigInt !== 'undefined';
var hasBroadcastChannel = typeof BroadcastChannel !== 'undefined';
var isCompatible = hasPromise && hasWebSockets && hasWebCrypto && hasObjectFromEntries && hasResizeObserver
&& hasCssSupports && hasDisplayNames && hasPluralRules && hasNumberFormat && hasWebLocks && hasBigInt;
&& hasCssSupports && hasDisplayNames && hasPluralRules && hasNumberFormat && hasWebLocks && hasBigInt && hasBroadcastChannel;
if (isCompatible || (window.localStorage && window.localStorage.getItem('tt-ignore-compat'))) {
window.isCompatTestPassed = true;
@ -33,6 +34,7 @@ function compatTest() {
console.warn('Intl.NumberFormat', hasNumberFormat);
console.warn('WebLocks', hasWebLocks);
console.warn('BigInt', hasBigInt);
console.warn('BroadcastChannel', hasBroadcastChannel);
}
// Hardcoded page because server forbids iframe embedding

View File

@ -159,6 +159,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
chatlistJoined: getLimit(appConfig, 'chatlist_joined_limit', 'chatlistJoined'),
recommendedChannels: getLimit(appConfig, 'recommended_channels_limit', 'recommendedChannels'),
savedDialogsPinned: getLimit(appConfig, 'saved_dialogs_pinned_limit', 'savedDialogsPinned'),
moreAccounts: DEFAULT_LIMITS.moreAccounts,
},
hash,
areStoriesHidden: appConfig.stories_all_hidden,

View File

@ -1,13 +1,11 @@
import BigInt from 'big-integer';
import { Api as GramJs } from '../../lib/gramjs';
import { DATA_BROADCAST_CHANNEL_NAME, DEBUG } from '../../config';
import { DEBUG } from '../../config';
import { DATA_BROADCAST_CHANNEL_NAME } from '../../util/multiaccount';
import { throttle } from '../../util/schedulers';
import { omitVirtualClassFields } from './apiBuilders/helpers';
// eslint-disable-next-line no-restricted-globals
const IS_MULTITAB_SUPPORTED = 'BroadcastChannel' in self;
export type StoryRepairInfo = {
type: 'story';
peerId: string;
@ -36,7 +34,7 @@ export interface LocalDb {
channelPtsById: Record<string, number>;
}
const channel = IS_MULTITAB_SUPPORTED ? new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME) : undefined;
const channel = new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME);
let batchedUpdates: {
name: string;
@ -44,7 +42,7 @@ let batchedUpdates: {
value: any;
}[] = [];
const throttledLocalDbUpdate = throttle(() => {
channel!.postMessage({
channel.postMessage({
type: 'localDbUpdate',
batchedUpdates,
});
@ -109,9 +107,7 @@ function createLocalDbInitial(initial?: LocalDb): LocalDb {
return acc2;
}, {} as Record<string, any>);
acc[key] = IS_MULTITAB_SUPPORTED
? createProxy(key, convertedValue)
: convertedValue;
acc[key] = createProxy(key, convertedValue);
return acc;
}, {} as LocalDb) as LocalDb;
}

View File

@ -76,7 +76,7 @@ export async function init(initialArgs: ApiInitialArgs) {
const {
userAgent, platform, sessionData, isWebmSupported, maxBufferSize, webAuthToken, dcId,
mockScenario, shouldForceHttpTransport, shouldAllowHttpTransport,
shouldDebugExportedSenders, langCode, isTestServerRequested,
shouldDebugExportedSenders, langCode, isTestServerRequested, accountIds,
} = initialArgs;
const session = new sessions.CallbackSession(sessionData, onSessionUpdate);
@ -133,6 +133,7 @@ export async function init(initialArgs: ApiInitialArgs) {
webAuthToken,
webAuthTokenFailed: onWebAuthTokenFailed,
mockScenario,
accountIds,
});
} catch (err: any) {
// eslint-disable-next-line no-console

View File

@ -1,17 +1,17 @@
import type { Api } from '../../../lib/gramjs';
import type { TypedBroadcastChannel } from '../../../util/multitab';
import type { TypedBroadcastChannel } from '../../../util/browser/multitab';
import type { ApiInitialArgs, ApiOnProgress, OnApiUpdate } from '../../types';
import type { LocalDb } from '../localDb';
import type { MethodArgs, MethodResponse, Methods } from '../methods/types';
import type { OriginPayload, ThenArg, WorkerMessageEvent } from './types';
import { DATA_BROADCAST_CHANNEL_NAME, DEBUG, IGNORE_UNHANDLED_ERRORS } from '../../../config';
import { DEBUG, IGNORE_UNHANDLED_ERRORS } from '../../../config';
import { logDebugMessage } from '../../../util/debugConsole';
import Deferred from '../../../util/Deferred';
import { getCurrentTabId, subscribeToMasterChange } from '../../../util/establishMultitabRole';
import generateUniqueId from '../../../util/generateUniqueId';
import { ACCOUNT_SLOT, DATA_BROADCAST_CHANNEL_NAME } from '../../../util/multiaccount';
import { pause, throttleWithTickEnd } from '../../../util/schedulers';
import { IS_MULTITAB_SUPPORTED } from '../../../util/windowEnvironment';
type RequestState = {
messageId: string;
@ -48,9 +48,7 @@ subscribeToMasterChange((isMasterTabNew) => {
isMasterTab = isMasterTabNew;
});
const channel = IS_MULTITAB_SUPPORTED
? new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME) as TypedBroadcastChannel
: undefined;
const channel = new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME) as TypedBroadcastChannel;
const postMessagesOnTickEnd = throttleWithTickEnd(() => {
const payloads = pendingPayloads;
@ -64,8 +62,6 @@ function postMessageOnTickEnd(payload: OriginPayload) {
}
export function initApiOnMasterTab(initialArgs: ApiInitialArgs) {
if (!channel) return;
channel.postMessage({
type: 'initApi',
token: getCurrentTabId(),
@ -93,7 +89,14 @@ export function initApi(onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) {
console.log('>>> START LOAD WORKER');
}
worker = new Worker(new URL('./worker.ts', import.meta.url));
const params = new URLSearchParams();
if (ACCOUNT_SLOT) {
params.set('account', String(ACCOUNT_SLOT));
}
worker = new Worker(new URL('./worker.ts', import.meta.url), {
name: params.toString(),
});
subscribeToWorker(onUpdate);
if (initialArgs.platform === 'iOS') {
@ -132,8 +135,6 @@ export function updateFullLocalDb(initial: LocalDb) {
}
export function callApiOnMasterTab(payload: any) {
if (!channel) return;
channel.postMessage({
type: 'callApi',
token: getCurrentTabId(),
@ -254,8 +255,6 @@ export function cancelApiProgress(progressCallback: ApiOnProgress) {
if (isMasterTab) {
cancelApiProgressMaster(messageId);
} else {
if (!channel) return;
channel.postMessage({
type: 'cancelApiProgress',
token: getCurrentTabId(),

View File

@ -23,6 +23,7 @@ export interface ApiInitialArgs {
shouldDebugExportedSenders?: boolean;
langCode: string;
isTestServerRequested?: boolean;
accountIds?: string[];
}
export interface ApiOnProgress {
@ -102,7 +103,7 @@ export interface ApiWebSession {
export interface ApiSessionData {
mainDcId: number;
keys: Record<number, string | number[]>;
keys: Record<number, string>;
isTest?: true;
}
@ -343,10 +344,11 @@ export type ApiLimitType =
| 'chatlistInvites'
| 'chatlistJoined'
| 'recommendedChannels'
| 'savedDialogsPinned';
| 'savedDialogsPinned'
| 'moreAccounts';
export type ApiLimitTypeWithModal = Exclude<ApiLimitType, (
'captionLength' | 'aboutLength' | 'stickersFaved' | 'savedGifs' | 'recommendedChannels'
'captionLength' | 'aboutLength' | 'stickersFaved' | 'savedGifs' | 'recommendedChannels' | 'moreAccounts'
)>;
export type ApiLimitTypeForPromo = Exclude<ApiLimitType,

View File

@ -71,6 +71,9 @@
"PremiumPreviewUploadsDescription" = "4 GB per each document, unlimited storage for your chats and media overall.";
"PremiumPreviewAdvancedChatManagementDescription" = "Tools to set the default folder, auto-archive and hide new chats from non-contacts.";
"PremiumPreviewAnimatedProfilesDescription" = "Video avatars animated in chat lists and chats to allow for additional self-expression.";
"PremiumLimitAccountsTitle" = "Limit Reached";
"PremiumLimitAccountsNoPremium" = "You have reached your current limit of connected accounts. You can free one more place by subscribing to Telegram Premium.";
"PremiumLimitAccounts" = "You have reached your current limit of connected accounts. You can free one more place by subscribing to Telegram Premium with one of these connected accounts.";
"SendMessage" = "Send Message";
"ConversationDefaultRestrictedMedia" = "Sending media isn't allowed in this group.";
"AccDescrVoiceMessage" = "Record voice message";
@ -172,6 +175,7 @@
"LoginRegisterLastNamePlaceholder" = "Last Name";
"Next" = "Next";
"LoginSelectCountryTitle" = "Country";
"MenuAddAccount" = "Add Account";
"CountryNone" = "Country not found";
"VoipGroupVoiceChat" = "Video Chat";
"AccDescrMoreOptions" = "More options";

View File

@ -1,5 +1,5 @@
import { initializeSoundsForSafari } from '../global/actions/ui/calls';
import { IS_IOS, IS_SAFARI } from '../util/windowEnvironment';
import { IS_IOS, IS_SAFARI } from '../util/browser/windowEnvironment';
export { default as GroupCall } from '../components/calls/group/GroupCall';
export { default as ActiveCallHeader } from '../components/calls/ActiveCallHeader';

View File

@ -1,7 +1,4 @@
import { getActions, getGlobal } from '../global';
import { DEBUG } from '../config';
import { IS_MULTITAB_SUPPORTED } from '../util/windowEnvironment';
export { default as Main } from '../components/main/Main';
export { default as LockScreen } from '../components/main/LockScreen';
@ -10,8 +7,3 @@ if (DEBUG) {
// eslint-disable-next-line no-console
console.log('>>> FINISH LOAD MAIN BUNDLE');
}
const { passcode: { isScreenLocked }, connectionState } = getGlobal();
if (!connectionState && !isScreenLocked && !IS_MULTITAB_SUPPORTED) {
getActions().initApi();
}

View File

@ -1,6 +1,6 @@
import type { FC } from '../lib/teact/teact';
import React, { useEffect, useLayoutEffect } from '../lib/teact/teact';
import { getActions, withGlobal } from '../global';
import { withGlobal } from '../global';
import type { GlobalState } from '../global/types';
import type { ThemeKey } from '../types';
@ -10,12 +10,12 @@ import {
DARK_THEME_BG_COLOR, INACTIVE_MARKER, LIGHT_THEME_BG_COLOR, PAGE_TITLE,
} from '../config';
import { selectTabState, selectTheme } from '../global/selectors';
import { addActiveTabChangeListener } from '../util/activeTabMonitor';
import { IS_INSTALL_PROMPT_SUPPORTED, PLATFORM_ENV } from '../util/browser/windowEnvironment';
import buildClassName from '../util/buildClassName';
import { setupBeforeInstallPrompt } from '../util/installPrompt';
import { parseInitialLocationHash } from '../util/routing';
import { ACCOUNT_SLOT, getAccountsInfo, getAccountSlotUrl } from '../util/multiaccount';
import { getInitialLocationHash, parseInitialLocationHash } from '../util/routing';
import { hasStoredSession } from '../util/sessions';
import { IS_INSTALL_PROMPT_SUPPORTED, IS_MULTITAB_SUPPORTED, PLATFORM_ENV } from '../util/windowEnvironment';
import { updateSizes } from '../util/windowSize';
import useAppLayout from '../hooks/useAppLayout';
@ -61,8 +61,6 @@ const App: FC<StateProps> = ({
isTestServer,
theme,
}) => {
const { disconnect } = getActions();
const [isInactive, markInactive, unmarkInactive] = useFlag(false);
const { isMobile } = useAppLayout();
const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
@ -73,6 +71,22 @@ const App: FC<StateProps> = ({
}
}, []);
useEffect(() => {
const hash = getInitialLocationHash();
// If there is no stored session on first slot, navigate to any other slot with stored session
if (!hasStoredSession() && !ACCOUNT_SLOT && !hash) {
const accounts = getAccountsInfo();
Object.keys(accounts).forEach((key) => {
const slot = Number(key);
const account = accounts[slot];
if (account) {
const url = getAccountSlotUrl(slot);
window.location.href = `${url}#${hash || 'login'}`;
}
});
}
}, []);
// Prevent drop on elements that do not accept it
useEffect(() => {
const body = document.body;
@ -161,17 +175,6 @@ const App: FC<StateProps> = ({
updateSizes();
}, []);
useEffect(() => {
if (IS_MULTITAB_SUPPORTED) return;
addActiveTabChangeListener(() => {
disconnect();
document.title = INACTIVE_PAGE_TITLE;
markInactive();
});
}, [activeKey, disconnect, markInactive]);
useEffect(() => {
if (isInactiveAuth) {
document.title = INACTIVE_PAGE_TITLE;

View File

@ -225,6 +225,12 @@
right: 0.5rem;
}
.auth-close {
position: absolute;
top: 1rem;
left: 1rem;
}
@keyframes qr-show {
0% {
opacity: 0;

View File

@ -6,7 +6,7 @@ import { getActions, withGlobal } from '../../global';
import type { GlobalState } from '../../global/types';
import { PLATFORM_ENV } from '../../util/windowEnvironment';
import { PLATFORM_ENV } from '../../util/browser/windowEnvironment';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
import useElectronDrag from '../../hooks/useElectronDrag';

View File

@ -7,8 +7,8 @@ import { getActions, withGlobal } from '../../global';
import type { GlobalState } from '../../global/types';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import { pick } from '../../util/iteratees';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import useHistoryBack from '../../hooks/useHistoryBack';
import useLang from '../../hooks/useLang';

View File

@ -1,7 +1,7 @@
import type { ChangeEvent } from 'react';
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
@ -9,18 +9,23 @@ import type { ApiCountryCode } from '../../api/types';
import type { GlobalState } from '../../global/types';
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
import { IS_SAFARI, IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import { preloadImage } from '../../util/files';
import preloadFonts from '../../util/fonts';
import { pick } from '../../util/iteratees';
import { getAccountSlotUrl } from '../../util/multiaccount';
import { oldSetLanguage } from '../../util/oldLangProvider';
import { formatPhoneNumber, getCountryCodeByIso, getCountryFromPhoneNumber } from '../../util/phoneNumber';
import { IS_SAFARI, IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { navigateBack } from './helpers/backNavigation';
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang';
import useLangString from '../../hooks/useLangString';
import useLastCallback from '../../hooks/useLastCallback';
import useMultiaccountInfo from '../../hooks/useMultiaccountInfo';
import Icon from '../common/icons/Icon';
import Button from '../ui/Button';
import Checkbox from '../ui/Checkbox';
import InputText from '../ui/InputText';
@ -62,7 +67,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
loadCountryList,
clearAuthErrorKey,
goToAuthQrCode,
setSettingOption,
setSharedSettingOption,
} = getActions();
const lang = useLang();
@ -78,6 +83,16 @@ const AuthPhoneNumber: FC<StateProps> = ({
const [lastSelection, setLastSelection] = useState<[number, number] | undefined>();
const [isLoading, markIsLoading, unmarkIsLoading] = useFlag();
const accountsInfo = useMultiaccountInfo();
const hasActiveAccount = Object.values(accountsInfo).length > 0;
const phoneNumberSlots = useMemo(() => (
Object.entries(accountsInfo)
.reduce((acc, [key, { phone }]) => {
if (phone) acc[phone] = Number(key);
return acc;
}, {} as Record<string, number>)
), [accountsInfo]);
const fullNumber = country ? `+${country.countryCode} ${phoneNumber || ''}` : phoneNumber;
const canSubmit = fullNumber && fullNumber.replace(/[^\d]+/g, '').length >= MIN_NUMBER_LENGTH;
@ -105,7 +120,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
}
}, [country, authNearestCountry, isTouched, phoneCodeList]);
const parseFullNumber = useCallback((newFullNumber: string) => {
const parseFullNumber = useLastCallback((newFullNumber: string) => {
if (!newFullNumber.length) {
setPhoneNumber('');
}
@ -123,17 +138,17 @@ const AuthPhoneNumber: FC<StateProps> = ({
setCountry(selectedCountry);
}
setPhoneNumber(formatPhoneNumber(newFullNumber, selectedCountry));
}, [phoneCodeList, country]);
});
const handleLangChange = useCallback(() => {
const handleLangChange = useLastCallback(() => {
markIsLoading();
void oldSetLanguage(suggestedLanguage, () => {
unmarkIsLoading();
setSettingOption({ language: suggestedLanguage });
setSharedSettingOption({ language: suggestedLanguage });
});
}, [markIsLoading, setSettingOption, suggestedLanguage, unmarkIsLoading]);
});
useEffect(() => {
if (phoneNumber === undefined && authPhoneNumber) {
@ -148,19 +163,23 @@ const AuthPhoneNumber: FC<StateProps> = ({
}, [lastSelection]);
const isJustPastedRef = useRef(false);
const handlePaste = useCallback(() => {
const handlePaste = useLastCallback(() => {
isJustPastedRef.current = true;
requestMeasure(() => {
isJustPastedRef.current = false;
});
}, []);
});
const handleCountryChange = useCallback((value: ApiCountryCode) => {
const handleBackNavigation = useLastCallback(() => {
navigateBack();
});
const handleCountryChange = useLastCallback((value: ApiCountryCode) => {
setCountry(value);
setPhoneNumber('');
}, []);
});
const handlePhoneNumberChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const handlePhoneNumberChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
if (authErrorKey) {
clearAuthErrorKey();
}
@ -186,11 +205,11 @@ const AuthPhoneNumber: FC<StateProps> = ({
&& value.length - fullNumber.length > 1 && !isJustPastedRef.current
);
parseFullNumber(shouldFixSafariAutoComplete ? `${country!.countryCode} ${value}` : value);
}, [authErrorKey, country, fullNumber, parseFullNumber]);
});
const handleKeepSessionChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const handleKeepSessionChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
setAuthRememberMe(e.target.checked);
}, [setAuthRememberMe]);
});
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
@ -199,19 +218,30 @@ const AuthPhoneNumber: FC<StateProps> = ({
return;
}
const adaptedPhoneNumber = fullNumber?.replace(/[^\d]/g, '');
if (adaptedPhoneNumber && phoneNumberSlots[adaptedPhoneNumber]) {
window.location.replace(getAccountSlotUrl(phoneNumberSlots[adaptedPhoneNumber]));
return;
}
if (canSubmit) {
setAuthPhoneNumber({ phoneNumber: fullNumber });
}
}
const handleGoToAuthQrCode = useCallback(() => {
const handleGoToAuthQrCode = useLastCallback(() => {
goToAuthQrCode();
}, [goToAuthQrCode]);
});
const isAuthReady = authState === 'authorizationStateWaitPhoneNumber';
return (
<div id="auth-phone-number-form" className="custom-scroll">
{hasActiveAccount && (
<Button size="smaller" round color="translucent" className="auth-close" onClick={handleBackNavigation}>
<Icon name="close" />
</Button>
)}
<div className="auth-form">
<div id="logo" />
<h1>{lang('AuthTitle')}</h1>
@ -263,7 +293,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
export default memo(withGlobal(
(global): StateProps => {
const {
settings: { byKey: { language } },
sharedState: { settings: { language } },
countryList: { phoneCodes: phoneCodeList },
} = global;

View File

@ -1,5 +1,5 @@
import React, {
memo, useCallback, useLayoutEffect, useRef,
memo, useLayoutEffect, useRef,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
@ -7,18 +7,23 @@ import type { GlobalState } from '../../global/types';
import { STRICTERDOM_ENABLED } from '../../config';
import { disableStrict, enableStrict } from '../../lib/fasterdom/stricterdom';
import { selectSharedSettings } from '../../global/selectors/sharedState';
import buildClassName from '../../util/buildClassName';
import { oldSetLanguage } from '../../util/oldLangProvider';
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
import { navigateBack } from './helpers/backNavigation';
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
import useAsync from '../../hooks/useAsync';
import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang';
import useLangString from '../../hooks/useLangString';
import useLastCallback from '../../hooks/useLastCallback';
import useMediaTransitionDeprecated from '../../hooks/useMediaTransitionDeprecated';
import useMultiaccountInfo from '../../hooks/useMultiaccountInfo';
import AnimatedIcon from '../common/AnimatedIcon';
import Icon from '../common/icons/Icon';
import Button from '../ui/Button';
import Loading from '../ui/Loading';
@ -26,7 +31,9 @@ import blankUrl from '../../assets/blank.png';
type StateProps =
Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>
& { language?: string };
& {
language?: string;
};
const DATA_PREFIX = 'tg://login?token=';
const QR_SIZE = 280;
@ -50,7 +57,7 @@ const AuthCode = ({
}: StateProps) => {
const {
returnToAuthPhoneNumber,
setSettingOption,
setSharedSettingOption,
} = getActions();
const suggestedLanguage = getSuggestedLanguage();
@ -63,6 +70,9 @@ const AuthCode = ({
const [isLoading, markIsLoading, unmarkIsLoading] = useFlag();
const [isQrMounted, markQrMounted, unmarkQrMounted] = useFlag();
const accountsInfo = useMultiaccountInfo();
const hasActiveAccount = Object.values(accountsInfo).length > 0;
const { result: qrCode } = useAsync(async () => {
const QrCodeStyling = (await ensureQrCodeStyling()).default;
return new QrCodeStyling({
@ -125,24 +135,33 @@ const AuthCode = ({
return undefined;
}, [isConnected, authQrCode, isQrMounted, markQrMounted, unmarkQrMounted, qrCode]);
const handleLangChange = useCallback(() => {
const handleBackNavigation = useLastCallback(() => {
navigateBack();
});
const handleLangChange = useLastCallback(() => {
markIsLoading();
void oldSetLanguage(suggestedLanguage, () => {
unmarkIsLoading();
setSettingOption({ language: suggestedLanguage });
setSharedSettingOption({ language: suggestedLanguage });
});
}, [markIsLoading, setSettingOption, suggestedLanguage, unmarkIsLoading]);
});
const habdleReturnToAuthPhoneNumber = useCallback(() => {
const handleReturnToAuthPhoneNumber = useLastCallback(() => {
returnToAuthPhoneNumber();
}, [returnToAuthPhoneNumber]);
});
const isAuthReady = authState === 'authorizationStateWaitQrCode';
return (
<div id="auth-qr-form" className="custom-scroll">
{hasActiveAccount && (
<Button size="smaller" round color="translucent" className="auth-close" onClick={handleBackNavigation}>
<Icon name="close" />
</Button>
)}
<div className="auth-form qr">
<div className="qr-outer">
<div
@ -172,7 +191,7 @@ const AuthCode = ({
<li><span>{lang('LoginQRHelp3')}</span></li>
</ol>
{isAuthReady && (
<Button size="smaller" isText onClick={habdleReturnToAuthPhoneNumber}>{lang('LoginQRCancel')}</Button>
<Button size="smaller" isText onClick={handleReturnToAuthPhoneNumber}>{lang('LoginQRCancel')}</Button>
)}
{suggestedLanguage && suggestedLanguage !== language && continueText && (
<Button size="smaller" isText isLoading={isLoading} onClick={handleLangChange}>{continueText}</Button>
@ -185,9 +204,11 @@ const AuthCode = ({
export default memo(withGlobal(
(global): StateProps => {
const {
connectionState, authState, authQrCode, settings: { byKey: { language } },
connectionState, authState, authQrCode,
} = global;
const { language } = selectSharedSettings(global);
return {
connectionState,
authState,

View File

@ -7,10 +7,10 @@ import { withGlobal } from '../../global';
import type { ApiCountryCode } from '../../api/types';
import { ANIMATION_END_DELAY } from '../../config';
import { IS_EMOJI_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { isoToEmoji } from '../../util/emoji/emoji';
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
import { IS_EMOJI_SUPPORTED } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText';
import useLang from '../../hooks/useLang';

View File

@ -0,0 +1,13 @@
import { getAccountSlotUrl } from '../../../util/multiaccount';
export function navigateBack() {
const currentUrl = new URL(window.location.href);
const referrer = document.referrer ? new URL(document.referrer) : undefined;
if (referrer?.origin === currentUrl.origin && referrer.pathname === currentUrl.pathname) {
window.history.back(); // Return to previous account with it's state
return;
}
const url = getAccountSlotUrl(1);
window.location.href = url;
}

View File

@ -14,9 +14,9 @@ import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { getUserStreams, THRESHOLD } from '../../../lib/secret-sauce';
import { selectChat, selectUser } from '../../../global/selectors';
import { animate } from '../../../util/animation';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { fastRaf } from '../../../util/schedulers';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../../util/windowEnvironment';
import formatGroupCallVolume from './helpers/formatGroupCallVolume';
import useInterval from '../../../hooks/schedulers/useInterval';

View File

@ -13,13 +13,13 @@ import {
} from '../../../lib/secret-sauce';
import { selectTabState } from '../../../global/selectors';
import { selectPhoneCallUser } from '../../../global/selectors/calls';
import buildClassName from '../../../util/buildClassName';
import { formatMediaDuration } from '../../../util/dates/dateFormat';
import {
IS_ANDROID,
IS_IOS,
IS_REQUEST_FULLSCREEN_SUPPORTED,
} from '../../../util/windowEnvironment';
} from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { formatMediaDuration } from '../../../util/dates/dateFormat';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import renderText from '../../common/helpers/renderText';

View File

@ -13,11 +13,11 @@ import type RLottieInstance from '../../lib/rlottie/RLottie';
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
import { ensureRLottie, getRLottie } from '../../lib/rlottie/RLottie.async';
import { IS_ELECTRON } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle';
import generateUniqueId from '../../util/generateUniqueId';
import { hexToRgb } from '../../util/switchTheme';
import { IS_ELECTRON } from '../../util/windowEnvironment';
import useColorFilter from '../../hooks/stickers/useColorFilter';
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';

View File

@ -9,7 +9,7 @@ import type {
} from '../../api/types';
import type { BufferedRange } from '../../hooks/useBuffering';
import type { OldLangFn } from '../../hooks/useOldLang';
import type { ISettings } from '../../types';
import type { ThemeKey } from '../../types';
import { ApiMediaFormat } from '../../api/types';
import { AudioOrigin } from '../../types';
@ -51,7 +51,7 @@ import Icon from './icons/Icon';
import './Audio.scss';
type OwnProps = {
theme: ISettings['theme'];
theme: ThemeKey;
message: ApiMessage;
senderTitle?: string;
uploadProgress?: number;
@ -628,7 +628,7 @@ function renderVoice(
}
function useWaveformCanvas(
theme: ISettings['theme'],
theme: ThemeKey,
media?: ApiVoice | ApiVideo,
playProgress = 0,
isOwn = false,

View File

@ -69,6 +69,7 @@ type OwnProps = {
peer?: ApiPeer | CustomPeer;
photo?: ApiPhoto;
webPhoto?: ApiWebDocument;
previewUrl?: string;
text?: string;
isSavedMessages?: boolean;
isSavedDialog?: boolean;
@ -93,6 +94,7 @@ const Avatar: FC<OwnProps> = ({
peer,
photo,
webPhoto,
previewUrl,
text,
isSavedMessages,
isSavedDialog,
@ -169,7 +171,8 @@ const Avatar: FC<OwnProps> = ({
const imgBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl);
const hasBlobUrl = Boolean(imgBlobUrl || videoBlobUrl);
const imgUrl = imgBlobUrl || previewUrl;
const hasBlobUrl = Boolean(imgUrl || videoBlobUrl);
// `videoBlobUrl` can be taken from memory cache, so we need to check `shouldLoadVideo` again
const shouldPlayVideo = Boolean(videoBlobUrl && shouldLoadVideo);
@ -205,7 +208,7 @@ const Avatar: FC<OwnProps> = ({
content = (
<>
<img
src={imgBlobUrl}
src={imgUrl}
className={buildClassName(cn.media, 'avatar-media', transitionClassNames, videoBlobUrl && 'poster')}
alt={author}
decoding="async"
@ -262,10 +265,10 @@ const Avatar: FC<OwnProps> = ({
withStorySolid && forceFriendStorySolid && 'close-friend',
withStorySolid && (realPeer?.hasUnreadStories || forceUnreadStorySolid) && 'has-unread-story',
onClick && 'interactive',
(!isSavedMessages && !imgBlobUrl) && 'no-photo',
(!isSavedMessages && !imgUrl) && 'no-photo',
);
const hasMedia = Boolean(isSavedMessages || imgBlobUrl);
const hasMedia = Boolean(isSavedMessages || imgUrl);
const { handleClick, handleMouseDown } = useFastClick((e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => {
if (withStory && storyViewerMode !== 'disabled' && realPeer?.hasStories) {

View File

@ -35,9 +35,9 @@ import type {
import type {
IAnchorPosition,
InlineBotSettings,
ISettings,
MessageList,
MessageListType,
ThemeKey,
ThreadId,
} from '../../types';
import { MAIN_THREAD_ID } from '../../api/types';
@ -103,6 +103,8 @@ import {
selectUserFullInfo,
} from '../../global/selectors';
import { selectCurrentLimit } from '../../global/selectors/limits';
import { selectSharedSettings } from '../../global/selectors/sharedState';
import { IS_IOS, IS_VOICE_RECORDING_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { formatMediaDuration, formatVoiceRecordDuration } from '../../util/dates/dateFormat';
import { processDeepLink } from '../../util/deeplink';
@ -115,7 +117,6 @@ import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import parseHtmlAsFormattedText from '../../util/parseHtmlAsFormattedText';
import { insertHtmlInSelection } from '../../util/selection';
import { getServerTime } from '../../util/serverTime';
import { IS_IOS, IS_VOICE_RECORDING_SUPPORTED } from '../../util/windowEnvironment';
import windowSize from '../../util/windowSize';
import applyIosAutoCapitalizationFix from '../middle/composer/helpers/applyIosAutoCapitalizationFix';
import buildAttachment, { prepareAttachmentsToSend } from '../middle/composer/helpers/buildAttachment';
@ -256,7 +257,7 @@ type StateProps =
requestedDraftFiles?: File[];
attachBots: GlobalState['attachMenu']['bots'];
attachMenuPeerType?: ApiAttachMenuPeerType;
theme: ISettings['theme'];
theme: ThemeKey;
fileSizeLimit: number;
captionLimit: number;
isCurrentUserPremium?: boolean;
@ -2300,9 +2301,9 @@ export default memo(withGlobal<OwnProps>(
const messageWithActualBotKeyboard = (isChatWithBot || !isChatWithUser)
&& selectNewestMessageWithBotKeyboardButtons(global, chatId, threadId);
const {
language, shouldSuggestStickers, shouldSuggestCustomEmoji, shouldUpdateStickerSetOrder,
shouldPaidMessageAutoApprove,
shouldSuggestStickers, shouldSuggestCustomEmoji, shouldUpdateStickerSetOrder, shouldPaidMessageAutoApprove,
} = global.settings.byKey;
const { language, shouldCollectDebugLogs } = selectSharedSettings(global);
const {
forwardMessages: { messageIds: forwardMessageIds },
} = selectTabState(global);
@ -2434,7 +2435,7 @@ export default memo(withGlobal<OwnProps>(
canBuyPremium: !isCurrentUserPremium && !selectIsPremiumPurchaseBlocked(global),
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
canSendOneTimeMedia: !isChatWithSelf && isChatWithUser && !isChatWithBot && !isInScheduledList,
shouldCollectDebugLogs: global.settings.byKey.shouldCollectDebugLogs,
shouldCollectDebugLogs,
sentStoryReaction,
stealthMode: global.stories.stealthMode,
replyToTopic,

View File

@ -30,9 +30,9 @@ import {
selectIsCurrentUserPremium,
} from '../../global/selectors';
import animateHorizontalScroll from '../../util/animateHorizontalScroll';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { pickTruthy, unique, uniqueByField } from '../../util/iteratees';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { REM } from './helpers/mediaDimensions';
import useAppLayout from '../../hooks/useAppLayout';

View File

@ -74,7 +74,7 @@ const Document = ({
onMediaClick,
onDateClick,
}: OwnProps) => {
const { cancelMediaDownload, downloadMedia, setSettingOption } = getActions();
const { cancelMediaDownload, downloadMedia, setSharedSettingOption } = getActions();
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
@ -158,7 +158,7 @@ const Document = ({
});
const handleSvgConfirm = useLastCallback(() => {
setSettingOption({ shouldWarnAboutSvg: !shouldNotWarnAboutSvg });
setSharedSettingOption({ shouldWarnAboutSvg: !shouldNotWarnAboutSvg });
closeSvgDialog();
handleDownload();
});

View File

@ -5,9 +5,9 @@ import React, {
import type { IconName } from '../../types/icons';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { formatMediaDateTime, formatPastTimeShort } from '../../util/dates/dateFormat';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment';
import { getColorFromExtension, getFileSizeString } from './helpers/documentInfo';
import { getDocumentThumbnailDimensions } from './helpers/mediaDimensions';
import renderText from './helpers/renderText';

View File

@ -63,9 +63,9 @@ const FullNameTitle: FC<OwnProps> = ({
noLoopLimit,
canCopyTitle,
iconElement,
statusSparklesColor,
onEmojiStatusClick,
observeIntersection,
statusSparklesColor,
}) => {
const lang = useOldLang();
const { showNotification } = getActions();
@ -73,9 +73,10 @@ const FullNameTitle: FC<OwnProps> = ({
const customPeer = 'isCustomPeer' in peer ? peer : undefined;
const isUser = realPeer && isApiPeerUser(realPeer);
const title = realPeer && (isUser ? getUserFullName(realPeer) : getChatTitle(lang, realPeer));
const isPremium = isUser && realPeer.isPremium;
const canShowEmojiStatus = withEmojiStatus && !isSavedMessages && realPeer;
const emojiStatus = realPeer?.emojiStatus;
const isPremium = (isUser && realPeer.isPremium) || customPeer?.isPremium;
const canShowEmojiStatus = withEmojiStatus && !isSavedMessages;
const emojiStatus = realPeer?.emojiStatus
|| (customPeer?.emojiStatusId ? { type: 'regular', documentId: customPeer.emojiStatusId } : undefined);
const handleTitleClick = useLastCallback((e) => {
if (!title || !canCopyTitle) {
@ -89,7 +90,7 @@ const FullNameTitle: FC<OwnProps> = ({
const specialTitle = useMemo(() => {
if (customPeer) {
return customPeer.title || lang(customPeer.titleKey!);
return renderText(customPeer.title || lang(customPeer.titleKey!));
}
if (isSavedMessages) {

View File

@ -7,8 +7,8 @@ import type { ApiVideo } from '../../api/types';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { getVideoMediaHash, getVideoPreviewMediaHash } from '../../global/helpers';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
import useBuffering from '../../hooks/useBuffering';

View File

@ -6,9 +6,9 @@ import React, {
import { MIN_PASSWORD_LENGTH } from '../../config';
import { requestMutation } from '../../lib/fasterdom/fasterdom';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import stopEvent from '../../util/stopEvent';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import useTimeout from '../../hooks/schedulers/useTimeout';
import useAppLayout from '../../hooks/useAppLayout';

View File

@ -20,10 +20,10 @@ import {
selectUser,
selectUserStatus,
} from '../../global/selectors';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import renderText from './helpers/renderText';
import useIntervalForceUpdate from '../../hooks/schedulers/useIntervalForceUpdate';

View File

@ -17,9 +17,9 @@ import {
isDeletedUser,
isUserId,
} from '../../global/helpers';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { getFirstLetters } from '../../util/textFormat';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment';
import { getPeerColorClass } from './helpers/peerColor';
import renderText from './helpers/renderText';

View File

@ -1,8 +1,8 @@
import type { FC } from '../../lib/teact/teact';
import React, { memo, useCallback } from '../../lib/teact/teact';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import Button from '../ui/Button';
import Icon from './icons/Icon';

View File

@ -7,9 +7,9 @@ import { getActions } from '../../global';
import type { ApiBotInlineMediaResult, ApiSticker } from '../../api/types';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { getServerTime } from '../../util/serverTime';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
import useDynamicColorListener from '../../hooks/stickers/useDynamicColorListener';

View File

@ -7,9 +7,9 @@ import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { getStickerMediaHash } from '../../global/helpers';
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
import { IS_ANDROID, IS_IOS, IS_WEBM_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import * as mediaLoader from '../../util/mediaLoader';
import { IS_ANDROID, IS_IOS, IS_WEBM_SUPPORTED } from '../../util/windowEnvironment';
import useColorFilter from '../../hooks/stickers/useColorFilter';
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';

View File

@ -4,7 +4,7 @@ import type {
import { STICKER_SIZE_INLINE_DESKTOP_FACTOR, STICKER_SIZE_INLINE_MOBILE_FACTOR } from '../../../config';
import { getPhotoInlineDimensions, getVideoDimensions } from '../../../global/helpers';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import windowSize from '../../../util/windowSize';
export const MEDIA_VIEWER_MEDIA_QUERY = '(max-height: 640px)';

View File

@ -7,6 +7,7 @@ import {
BASE_URL, IS_PACKAGED_ELECTRON, RE_LINK_TEMPLATE, RE_MENTION_TEMPLATE,
} from '../../../config';
import EMOJI_REGEX from '../../../lib/twemojiRegex';
import { IS_EMOJI_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { isDeepLink } from '../../../util/deepLinkParser';
import {
@ -16,7 +17,6 @@ import {
} from '../../../util/emoji/emoji';
import fixNonStandardEmoji from '../../../util/emoji/fixNonStandardEmoji';
import { compact } from '../../../util/iteratees';
import { IS_EMOJI_SUPPORTED } from '../../../util/windowEnvironment';
import MentionLink from '../../middle/message/MentionLink';
import SafeLink from '../SafeLink';

View File

@ -3,9 +3,9 @@ import { getActions } from '../../../global';
import type { ActiveEmojiInteraction } from '../../../types';
import { IS_ELECTRON } from '../../../util/browser/windowEnvironment';
import buildStyle from '../../../util/buildStyle';
import safePlay from '../../../util/safePlay';
import { IS_ELECTRON } from '../../../util/windowEnvironment';
import { REM } from '../helpers/mediaDimensions';
import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -1,7 +1,7 @@
import React, { type TeactNode } from '../../../lib/teact/teact';
import { IS_IOS } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { IS_IOS } from '../../../util/windowEnvironment';
import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang';

View File

@ -5,12 +5,12 @@ import React, {
import type { ApiBusinessWorkHours } from '../../../api/types';
import { requestMeasure, requestMutation } from '../../../lib/fasterdom/fasterdom';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { formatTime, formatWeekday } from '../../../util/dates/dateFormat';
import {
getUtcOffset, getWeekStart, shiftTimeRanges, splitDays,
} from '../../../util/dates/workHours';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useSelectorSignal from '../../../hooks/data/useSelectorSignal';
import useInterval from '../../../hooks/schedulers/useInterval';

View File

@ -10,11 +10,11 @@ import {
import { requestMeasure } from '../../../lib/fasterdom/fasterdom';
import { getStickerMediaHash } from '../../../global/helpers';
import { selectIsPremiumPurchaseBlocked } from '../../../global/selectors';
import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { formatDateToString } from '../../../util/dates/dateFormat';
import { buildCollectionByKey } from '../../../util/iteratees';
import * as mediaLoader from '../../../util/mediaLoader';
import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/windowEnvironment';
import renderText from '../helpers/renderText';
import useTimeout from '../../../hooks/schedulers/useTimeout';

View File

@ -4,9 +4,9 @@ import React, { memo, useMemo } from '../../../lib/teact/teact';
import type { ApiEmojiStatusType, ApiReactionCustomEmoji } from '../../../api/types';
import { getStickerHashById } from '../../../global/helpers';
import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import buildStyle from '../../../util/buildStyle';
import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/windowEnvironment';
import useMedia from '../../../hooks/useMedia';

View File

@ -8,8 +8,8 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import { isSameReaction } from '../../../global/helpers';
import { selectPerformanceSettingsValue, selectTabState } from '../../../global/selectors';
import { IS_ANDROID, IS_IOS } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { IS_ANDROID, IS_IOS } from '../../../util/windowEnvironment';
import { LOCAL_TGS_URLS } from '../helpers/animatedAssets';
import { REM } from '../helpers/mediaDimensions';

View File

@ -10,11 +10,11 @@ import type { ReducerAction } from '../../hooks/useReducer';
import { LeftColumnContent, SettingsScreens } from '../../types';
import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors';
import captureEscKeyListener from '../../util/captureEscKeyListener';
import { captureControlledSwipe } from '../../util/swipeController';
import {
IS_APP, IS_FIREFOX, IS_MAC_OS, IS_TOUCH_ENV, LAYERS_ANIMATION_NAME,
} from '../../util/windowEnvironment';
} from '../../util/browser/windowEnvironment';
import captureEscKeyListener from '../../util/captureEscKeyListener';
import { captureControlledSwipe } from '../../util/swipeController';
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
import { useHotkeys } from '../../hooks/useHotkeys';

View File

@ -0,0 +1,117 @@
import React, { memo, useMemo } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { ApiUser } from '../../../api/types';
import type { CustomPeer } from '../../../types';
import { getCurrentMaxAccountCount, getCurrentProdAccountCount } from '../../../global/helpers';
import { getAccountSlotUrl } from '../../../util/multiaccount';
import { REM } from '../../common/helpers/mediaDimensions';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import useMultiaccountInfo from '../../../hooks/useMultiaccountInfo';
import Avatar from '../../common/Avatar';
import FullNameTitle from '../../common/FullNameTitle';
import MenuItem from '../../ui/MenuItem';
import MenuSeparator from '../../ui/MenuSeparator';
type OwnProps = {
currentUser: ApiUser;
totalLimit: number;
onSelectCurrent?: VoidFunction;
};
const NOTIFICATION_DURATION = 7000;
const AccountMenuItems = ({
currentUser,
totalLimit,
onSelectCurrent,
}: OwnProps) => {
const { showNotification } = getActions();
const lang = useLang();
const accounts = useMultiaccountInfo(currentUser);
const currentCount = getCurrentProdAccountCount();
const maxCount = getCurrentMaxAccountCount();
const shouldShowLimit = currentCount >= maxCount;
const handleLimitClick = useLastCallback(() => {
showNotification({
title: lang('PremiumLimitAccountsTitle'),
message: currentUser.isPremium ? lang('PremiumLimitAccounts') : lang('PremiumLimitAccountsNoPremium'),
duration: NOTIFICATION_DURATION,
});
});
const newAccountUrl = useMemo(() => {
if (!Object.values(accounts).length) {
return undefined;
}
if (currentCount === totalLimit) {
return undefined;
}
let freeIndex = 1;
while (accounts[freeIndex]) {
freeIndex += 1;
}
return getAccountSlotUrl(freeIndex, true);
}, [accounts, currentCount, totalLimit]);
return (
<>
{Object.entries(accounts || {})
.sort(([, account]) => (account.userId === currentUser.id ? -1 : 1))
.map(([slot, account], index, arr) => {
const mockUser: CustomPeer = {
title: [account.firstName, account.lastName].filter(Boolean).join(' '),
isCustomPeer: true,
peerColorId: account.color,
emojiStatusId: account.emojiStatusId,
isPremium: account.isPremium,
};
const hasSeparator = account.userId === currentUser.id && (newAccountUrl || arr.length > 1);
return (
<>
<MenuItem
className="account-menu-item"
customIcon={(
<Avatar
size="mini"
className="account-avatar"
peer={mockUser}
previewUrl={account.avatarUri}
/>
)}
onClick={account.userId === currentUser.id ? onSelectCurrent : undefined}
href={account.userId !== currentUser.id ? getAccountSlotUrl(Number(slot)) : undefined}
>
<FullNameTitle peer={mockUser} withEmojiStatus emojiStatusSize={REM} />
</MenuItem>
{hasSeparator && <MenuSeparator />}
</>
);
})}
{newAccountUrl && (
<MenuItem
icon="add"
rel="noopener" // Allow referrer to be passed
href={!shouldShowLimit ? newAccountUrl : undefined}
onClick={shouldShowLimit ? handleLimitClick : undefined}
>
{lang('MenuAddAccount')}
</MenuItem>
)}
</>
);
};
export default memo(AccountMenuItems);

View File

@ -48,9 +48,9 @@ import {
selectUser,
selectUserStatus,
} from '../../../global/selectors';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { createLocationHash } from '../../../util/routing';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment';
import useSelectorSignal from '../../../hooks/data/useSelectorSignal';
import useAppLayout from '../../../hooks/useAppLayout';

View File

@ -14,11 +14,11 @@ import type { TabWithProperties } from '../../ui/TabList';
import { ALL_FOLDER_ID } from '../../../config';
import { selectCanShareFolder, selectTabState } from '../../../global/selectors';
import { selectCurrentLimit } from '../../../global/selectors/limits';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
import useDerivedState from '../../../hooks/useDerivedState';

View File

@ -19,10 +19,10 @@ import {
FRESH_AUTH_PERIOD,
SAVED_FOLDER_ID,
} from '../../../config';
import { IS_APP, IS_MAC_OS } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { getOrderKey, getPinnedChatsCount } from '../../../util/folderManager';
import { getServerTime } from '../../../util/serverTime';
import { IS_APP, IS_MAC_OS } from '../../../util/windowEnvironment';
import usePeerStoriesPolling from '../../../hooks/polling/usePeerStoriesPolling';
import useTopOverscroll from '../../../hooks/scroll/useTopOverscroll';

View File

@ -22,11 +22,11 @@ import {
selectTabState,
selectTopicsInfo,
} from '../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useAppLayout from '../../../hooks/useAppLayout';
import useHistoryBack from '../../../hooks/useHistoryBack';

View File

@ -9,8 +9,8 @@ import type { SettingsScreens } from '../../../types';
import { LeftColumnContent } from '../../../types';
import { PRODUCTION_URL } from '../../../config';
import { IS_ELECTRON, IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { IS_ELECTRON, IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useForumPanelRender from '../../../hooks/useForumPanelRender';
import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -111,20 +111,22 @@
}
}
.emoji-status-effect {
top: 50%;
left: 50%;
}
.StatusButton {
.emoji-status-effect {
top: 50%;
left: 50%;
}
.emoji-status {
overflow: visible;
--custom-emoji-size: 1.5rem;
color: var(--color-primary);
}
.emoji-status {
overflow: visible;
--custom-emoji-size: 1.5rem;
color: var(--color-primary);
}
.StarIcon {
width: 1.5rem;
height: 1.5rem;
.StarIcon {
width: 1.5rem;
height: 1.5rem;
}
}
// @optimization
@ -153,4 +155,19 @@
right: -0.125rem;
}
}
.account-menu-item {
--custom-emoji-size: 1rem;
.account-avatar {
margin-inline: 0.375rem 1.125rem;
}
.fullName {
margin: 0;
font-size: 1em;
line-height: 1;
padding-top: 0.1875rem;
}
}
}

View File

@ -5,7 +5,7 @@ import React, {
import { getActions, withGlobal } from '../../../global';
import type { GlobalState } from '../../../global/types';
import type { ISettings } from '../../../types';
import type { ThemeKey } from '../../../types';
import { LeftColumnContent, SettingsScreens } from '../../../types';
import {
@ -20,10 +20,11 @@ import {
selectTabState,
selectTheme,
} from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import { IS_APP, IS_ELECTRON, IS_MAC_OS } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { formatDateToString } from '../../../util/dates/dateFormat';
import { IS_APP, IS_ELECTRON, IS_MAC_OS } from '../../../util/windowEnvironment';
import useAppLayout from '../../../hooks/useAppLayout';
import useConnectionStatus from '../../../hooks/useConnectionStatus';
@ -68,10 +69,10 @@ type StateProps =
isLoading: boolean;
globalSearchChatId?: string;
searchDate?: number;
theme: ISettings['theme'];
theme: ThemeKey;
isMessageListOpen: boolean;
isCurrentUserPremium?: boolean;
isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized'];
isConnectionStatusMinimized?: boolean;
areChatsLoaded?: boolean;
hasPasscode?: boolean;
canSetPasscode?: boolean;
@ -109,7 +110,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
}) => {
const {
setGlobalSearchDate,
setSettingOption,
setSharedSettingOption,
setGlobalSearchChatId,
lockScreen,
requestNextSettingsScreen,
@ -185,7 +186,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
});
const toggleConnectionStatus = useLastCallback(() => {
setSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized });
setSharedSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized });
});
const handleLockScreen = useLastCallback(() => {
@ -340,7 +341,7 @@ export default memo(withGlobal<OwnProps>(
const {
connectionState, isSyncing, isFetchingDifference,
} = global;
const { isConnectionStatusMinimized } = global.settings.byKey;
const { isConnectionStatusMinimized } = selectSharedSettings(global);
return {
searchQuery,

View File

@ -1,6 +1,7 @@
import React, { memo, useMemo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiUser } from '../../../api/types';
import type { GlobalState } from '../../../global/types';
import type { AnimationLevel, ThemeKey } from '../../../types';
@ -20,10 +21,13 @@ import {
INITIAL_PERFORMANCE_STATE_MID,
INITIAL_PERFORMANCE_STATE_MIN,
} from '../../../global/initialState';
import { selectTabState, selectTheme } from '../../../global/selectors';
import { selectTabState, selectTheme, selectUser } from '../../../global/selectors';
import { selectPremiumLimit } from '../../../global/selectors/limits';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import { IS_MULTIACCOUNT_SUPPORTED } from '../../../util/browser/globalEnvironment';
import { IS_ELECTRON } from '../../../util/browser/windowEnvironment';
import { getPromptInstall } from '../../../util/installPrompt';
import { switchPermanentWebVersion } from '../../../util/permanentWebVersion';
import { IS_ELECTRON } from '../../../util/windowEnvironment';
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
import useLang from '../../../hooks/useLang';
@ -32,8 +36,10 @@ import useOldLang from '../../../hooks/useOldLang';
import AttachBotItem from '../../middle/composer/AttachBotItem';
import MenuItem from '../../ui/MenuItem';
import MenuSeparator from '../../ui/MenuSeparator';
import Switcher from '../../ui/Switcher';
import Toggle from '../../ui/Toggle';
import AccountMenuItems from './AccountMenuItems';
type OwnProps = {
onSelectSettings: NoneToVoidFunction;
@ -45,9 +51,11 @@ type OwnProps = {
type StateProps = {
animationLevel: AnimationLevel;
currentUser?: ApiUser;
theme: ThemeKey;
canInstall?: boolean;
attachBots: GlobalState['attachMenu']['bots'];
accountsTotalLimit: number;
} & Pick<GlobalState, 'currentUserId' | 'archiveSettings'>;
const LeftSideMenuItems = ({
@ -57,6 +65,8 @@ const LeftSideMenuItems = ({
theme,
canInstall,
attachBots,
currentUser,
accountsTotalLimit,
onSelectArchived,
onSelectContacts,
onSelectSettings,
@ -65,7 +75,7 @@ const LeftSideMenuItems = ({
}: OwnProps & StateProps) => {
const {
openChat,
setSettingOption,
setSharedSettingOption,
updatePerformanceSettings,
openChatByUsername,
openUrl,
@ -91,8 +101,8 @@ const LeftSideMenuItems = ({
e.stopPropagation();
const newTheme = theme === 'light' ? 'dark' : 'light';
setSettingOption({ theme: newTheme });
setSettingOption({ shouldUseSystemTheme: false });
setSharedSettingOption({ theme: newTheme });
setSharedSettingOption({ shouldUseSystemTheme: false });
});
const handleAnimationLevelChange = useLastCallback((e: React.SyntheticEvent<HTMLElement>) => {
@ -106,7 +116,7 @@ const LeftSideMenuItems = ({
? INITIAL_PERFORMANCE_STATE_MIN
: (newLevel === ANIMATION_LEVEL_MAX ? INITIAL_PERFORMANCE_STATE_MAX : INITIAL_PERFORMANCE_STATE_MID);
setSettingOption({ animationLevel: newLevel as AnimationLevel });
setSharedSettingOption({ animationLevel: newLevel as AnimationLevel });
updatePerformanceSettings(performanceSettings);
});
@ -132,6 +142,16 @@ const LeftSideMenuItems = ({
return (
<>
{IS_MULTIACCOUNT_SUPPORTED && currentUser && (
<>
<AccountMenuItems
currentUser={currentUser}
totalLimit={accountsTotalLimit}
onSelectCurrent={onSelectSettings}
/>
<MenuSeparator />
</>
)}
<MenuItem
icon="saved-messages"
onClick={handleSelectSaved}
@ -244,16 +264,18 @@ export default memo(withGlobal<OwnProps>(
const {
currentUserId, archiveSettings,
} = global;
const { animationLevel } = global.settings.byKey;
const { animationLevel } = selectSharedSettings(global);
const attachBots = global.attachMenu.bots;
return {
currentUserId,
currentUser: selectUser(global, currentUserId!),
theme: selectTheme(global),
animationLevel,
canInstall: Boolean(tabState.canInstall),
archiveSettings,
attachBots,
accountsTotalLimit: selectPremiumLimit(global, 'moreAccounts'),
};
},
)(LeftSideMenuItems));

View File

@ -64,7 +64,7 @@ const StatusButton: FC<StateProps> = ({ emojiStatus, collectibleStatuses }) => {
}, [openStatusPicker]);
return (
<div className="extra-spacing">
<div className="StatusButton extra-spacing">
{Boolean(isEffectShown && emojiStatus) && (
<CustomEmojiEffect
reaction={emojiStatus!}

View File

@ -27,9 +27,9 @@ import {
selectThreadParam,
selectTopics,
} from '../../../global/selectors';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { createLocationHash } from '../../../util/routing';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment';
import renderText from '../../common/helpers/renderText';
import useFlag from '../../../hooks/useFlag';

View File

@ -5,8 +5,8 @@ import type { ApiChat, ApiTopic } from '../../../../api/types';
import type { MenuItemContextAction } from '../../../ui/ListItem';
import { getCanManageTopic, getHasAdminRight } from '../../../../global/helpers';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../../util/browser/windowEnvironment';
import { compact } from '../../../../util/iteratees';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../../util/windowEnvironment';
import useOldLang from '../../../../hooks/useOldLang';

View File

@ -3,7 +3,7 @@ import React, { memo, useCallback, useState } from '../../../lib/teact/teact';
import { LeftColumnContent } from '../../../types';
import { LAYERS_ANIMATION_NAME } from '../../../util/windowEnvironment';
import { LAYERS_ANIMATION_NAME } from '../../../util/browser/windowEnvironment';
import Transition from '../../ui/Transition';
import NewChatStep1 from './NewChatStep1';

View File

@ -2,13 +2,14 @@ import type {
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiUser,
} from '../../../../api/types';
import type { GlobalState, TabState } from '../../../../global/types';
import type { ISettings } from '../../../../types';
import type { ThemeKey } from '../../../../types';
import type { SearchResultKey } from '../../../../util/keys/searchResultKey';
import { selectChat, selectTabState, selectTheme } from '../../../../global/selectors';
import { selectSharedSettings } from '../../../../global/selectors/sharedState';
export type StateProps = {
theme: ISettings['theme'];
theme: ThemeKey;
isLoading?: boolean;
chatsById: Record<string, ApiChat>;
usersById: Record<string, ApiUser>;
@ -29,6 +30,8 @@ export function createMapStateToProps(type: ApiGlobalMessageSearchType) {
fetchingStatus, resultsByType, chatId,
} = tabState.globalSearch;
const { shouldWarnAboutSvg } = selectSharedSettings(global);
// One component is used for two different types of results.
// The differences between them are only in the isVoice property.
// The rest of the search results use their own personal components.
@ -50,7 +53,7 @@ export function createMapStateToProps(type: ApiGlobalMessageSearchType) {
searchChatId: chatId,
activeDownloads,
isChatProtected: chatId ? selectChat(global, chatId)?.isProtected : undefined,
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
shouldWarnAboutSvg,
};
};
}

View File

@ -6,7 +6,7 @@ import type { FolderEditDispatch, FoldersState } from '../../../hooks/reducers/u
import { SettingsScreens } from '../../../types';
import { selectTabState } from '../../../global/selectors';
import { LAYERS_ANIMATION_NAME } from '../../../util/windowEnvironment';
import { LAYERS_ANIMATION_NAME } from '../../../util/browser/windowEnvironment';
import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer';
import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -5,7 +5,7 @@ import React, {
import { getActions, withGlobal } from '../../../global';
import type { ApiSticker, ApiStickerSet } from '../../../api/types';
import type { ISettings } from '../../../types';
import type { AccountSettings } from '../../../types';
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
import { pick } from '../../../util/iteratees';
@ -23,7 +23,7 @@ type OwnProps = {
onReset: () => void;
};
type StateProps = Pick<ISettings, (
type StateProps = Pick<AccountSettings, (
'shouldSuggestCustomEmoji'
)> & {
customEmojiSetIds?: string[];

View File

@ -2,7 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ISettings } from '../../../types';
import type { AccountSettings } from '../../../types';
import { AUTODOWNLOAD_FILESIZE_MB_LIMITS } from '../../../config';
import { pick } from '../../../util/iteratees';
@ -18,7 +18,7 @@ type OwnProps = {
onReset: () => void;
};
type StateProps = Pick<ISettings, (
type StateProps = Pick<AccountSettings, (
'canAutoLoadPhotoFromContacts' |
'canAutoLoadPhotoInPrivateChats' |
'canAutoLoadPhotoInGroups' |

View File

@ -4,7 +4,7 @@ import React, {
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ISettings } from '../../../types';
import type { AccountSettings } from '../../../types';
import { SUPPORTED_TRANSLATION_LANGUAGES } from '../../../config';
import buildClassName from '../../../util/buildClassName';
@ -50,7 +50,7 @@ type OwnProps = {
onReset: () => void;
};
type StateProps = Pick<ISettings, 'doNotTranslate'>;
type StateProps = Pick<AccountSettings, 'doNotTranslate'>;
const SettingsDoNotTranslate: FC<OwnProps & StateProps> = ({
isActive,

View File

@ -5,9 +5,14 @@ import React, {
import { getActions, withGlobal } from '../../../global';
import { DEBUG_LOG_FILENAME } from '../../../config';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import {
IS_ELECTRON,
IS_SNAP_EFFECT_SUPPORTED,
IS_WAVE_TRANSFORM_SUPPORTED,
} from '../../../util/browser/windowEnvironment';
import { getDebugLogs } from '../../../util/debugConsole';
import download from '../../../util/download';
import { IS_ELECTRON, IS_SNAP_EFFECT_SUPPORTED, IS_WAVE_TRANSFORM_SUPPORTED } from '../../../util/windowEnvironment';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import useHistoryBack from '../../../hooks/useHistoryBack';
@ -39,7 +44,7 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
shouldCollectDebugLogs,
shouldDebugExportedSenders,
}) => {
const { requestConfetti, setSettingOption, requestWave } = getActions();
const { requestConfetti, setSharedSettingOption, requestWave } = getActions();
// eslint-disable-next-line no-null/no-null
const snapButtonRef = useRef<HTMLDivElement>(null);
@ -128,7 +133,7 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
label="Allow HTTP Transport"
checked={Boolean(shouldAllowHttpTransport)}
// eslint-disable-next-line react/jsx-no-bind
onCheck={() => setSettingOption({ shouldAllowHttpTransport: !shouldAllowHttpTransport })}
onCheck={() => setSharedSettingOption({ shouldAllowHttpTransport: !shouldAllowHttpTransport })}
/>
<Checkbox
@ -136,21 +141,21 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
disabled={!shouldAllowHttpTransport}
checked={Boolean(shouldForceHttpTransport)}
// eslint-disable-next-line react/jsx-no-bind
onCheck={() => setSettingOption({ shouldForceHttpTransport: !shouldForceHttpTransport })}
onCheck={() => setSharedSettingOption({ shouldForceHttpTransport: !shouldForceHttpTransport })}
/>
<Checkbox
label={lang('DebugMenuEnableLogs')}
checked={Boolean(shouldCollectDebugLogs)}
// eslint-disable-next-line react/jsx-no-bind
onCheck={() => setSettingOption({ shouldCollectDebugLogs: !shouldCollectDebugLogs })}
onCheck={() => setSharedSettingOption({ shouldCollectDebugLogs: !shouldCollectDebugLogs })}
/>
<Checkbox
label="Enable exported senders debug"
checked={Boolean(shouldDebugExportedSenders)}
// eslint-disable-next-line react/jsx-no-bind
onCheck={() => setSettingOption({ shouldDebugExportedSenders: !shouldDebugExportedSenders })}
onCheck={() => setSharedSettingOption({ shouldDebugExportedSenders: !shouldDebugExportedSenders })}
/>
{IS_ELECTRON && (
@ -174,11 +179,18 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
export default memo(withGlobal(
(global): StateProps => {
const {
shouldForceHttpTransport,
shouldAllowHttpTransport,
shouldCollectDebugLogs,
shouldDebugExportedSenders,
} = selectSharedSettings(global);
return {
shouldForceHttpTransport: global.settings.byKey.shouldForceHttpTransport,
shouldAllowHttpTransport: global.settings.byKey.shouldAllowHttpTransport,
shouldCollectDebugLogs: global.settings.byKey.shouldCollectDebugLogs,
shouldDebugExportedSenders: global.settings.byKey.shouldDebugExportedSenders,
shouldForceHttpTransport,
shouldAllowHttpTransport,
shouldCollectDebugLogs,
shouldDebugExportedSenders,
};
},
)(SettingsExperimental));

View File

@ -4,16 +4,16 @@ import React, {
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ISettings, TimeFormat } from '../../../types';
import type { SharedSettings, ThemeKey, TimeFormat } from '../../../types';
import type { IRadioOption } from '../../ui/RadioGroup';
import { SettingsScreens } from '../../../types';
import { pick } from '../../../util/iteratees';
import { setTimeFormat } from '../../../util/oldLangProvider';
import { getSystemTheme } from '../../../util/systemTheme';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import {
IS_ANDROID, IS_ELECTRON, IS_IOS, IS_MAC_OS, IS_WINDOWS,
} from '../../../util/windowEnvironment';
} from '../../../util/browser/windowEnvironment';
import { setTimeFormat } from '../../../util/oldLangProvider';
import { getSystemTheme } from '../../../util/systemTheme';
import useAppLayout from '../../../hooks/useAppLayout';
import useHistoryBack from '../../../hooks/useHistoryBack';
@ -31,28 +31,26 @@ type OwnProps = {
};
type StateProps =
Pick<ISettings, (
Pick<SharedSettings, (
'messageTextSize' |
'animationLevel' |
'messageSendKeyCombo' |
'timeFormat'
)> & {
theme: ISettings['theme'];
shouldUseSystemTheme: boolean;
};
'timeFormat' |
'theme' |
'shouldUseSystemTheme'
)>;
const SettingsGeneral: FC<OwnProps & StateProps> = ({
isActive,
onScreenSelect,
onReset,
messageTextSize,
messageSendKeyCombo,
timeFormat,
theme,
shouldUseSystemTheme,
onScreenSelect,
onReset,
}) => {
const {
setSettingOption,
setSharedSettingOption,
} = getActions();
const lang = useLang();
@ -96,26 +94,26 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
document.documentElement.style.setProperty('--message-text-size', `${newSize}px`);
document.documentElement.setAttribute('data-message-text-size', newSize.toString());
setSettingOption({ messageTextSize: newSize });
}, [setSettingOption]);
setSharedSettingOption({ messageTextSize: newSize });
}, []);
const handleAppearanceThemeChange = useCallback((value: string) => {
const newTheme = value === 'auto' ? getSystemTheme() : value as ISettings['theme'];
const newTheme = value === 'auto' ? getSystemTheme() : value as ThemeKey;
setSettingOption({ theme: newTheme });
setSettingOption({ shouldUseSystemTheme: value === 'auto' });
}, [setSettingOption]);
setSharedSettingOption({ theme: newTheme });
setSharedSettingOption({ shouldUseSystemTheme: value === 'auto' });
}, []);
const handleTimeFormatChange = useCallback((newTimeFormat: string) => {
setSettingOption({ timeFormat: newTimeFormat as TimeFormat });
setSettingOption({ wasTimeFormatSetManually: true });
setSharedSettingOption({ timeFormat: newTimeFormat as TimeFormat });
setSharedSettingOption({ wasTimeFormatSetManually: true });
setTimeFormat(newTimeFormat as TimeFormat);
}, [setSettingOption]);
}, []);
const handleMessageSendComboChange = useCallback((newCombo: string) => {
setSettingOption({ messageSendKeyCombo: newCombo as ISettings['messageSendKeyCombo'] });
}, [setSettingOption]);
setSharedSettingOption({ messageSendKeyCombo: newCombo as SharedSettings['messageSendKeyCombo'] });
}, []);
const [isTrayIconEnabled, setIsTrayIconEnabled] = useState(false);
useEffect(() => {
@ -204,17 +202,18 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const { theme, shouldUseSystemTheme } = global.settings.byKey;
const {
theme,
shouldUseSystemTheme,
messageSendKeyCombo,
messageTextSize,
timeFormat,
} = selectSharedSettings(global);
return {
...pick(global.settings.byKey, [
'messageTextSize',
'animationLevel',
'messageSendKeyCombo',
'isSensitiveEnabled',
'canChangeSensitive',
'timeFormat',
]),
messageSendKeyCombo,
messageTextSize,
timeFormat,
theme,
shouldUseSystemTheme,
};

View File

@ -9,7 +9,7 @@ import type { ThemeKey } from '../../../types';
import { SettingsScreens, UPLOADING_WALLPAPER_SLUG } from '../../../types';
import { DARK_THEME_PATTERN_COLOR, DEFAULT_PATTERN_COLOR } from '../../../config';
import { selectTheme } from '../../../global/selectors';
import { selectTheme, selectThemeValues } from '../../../global/selectors';
import { getAverageColor, getPatternColor, rgb2hex } from '../../../util/colors';
import { validateFiles } from '../../../util/files';
import { throttle } from '../../../util/schedulers';
@ -173,7 +173,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const theme = selectTheme(global);
const { background, isBlurred } = global.settings.themes[theme] || {};
const { background, isBlurred } = selectThemeValues(global, theme) || {};
const { loadedWallpapers } = global.settings;
return {

View File

@ -8,7 +8,7 @@ import { getActions, withGlobal } from '../../../global';
import type { ThemeKey } from '../../../types';
import type { RealTouchEvent } from '../../../util/captureEvents';
import { selectTheme } from '../../../global/selectors';
import { selectTheme, selectThemeValues } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { captureEvents } from '../../../util/captureEvents';
import {
@ -351,7 +351,7 @@ function drawHue(canvas: HTMLCanvasElement) {
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const theme = selectTheme(global);
const { backgroundColor } = global.settings.themes[theme] || {};
const { backgroundColor } = selectThemeValues(global, theme) || {};
return {
backgroundColor,
theme,

View File

@ -4,13 +4,13 @@ import React, {
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiLanguage } from '../../../api/types';
import type { ISettings, LangCode } from '../../../types';
import type { AccountSettings, LangCode, SharedSettings } from '../../../types';
import { SettingsScreens } from '../../../types';
import { selectIsCurrentUserPremium } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import { IS_TRANSLATION_SUPPORTED } from '../../../util/browser/windowEnvironment';
import { oldSetLanguage } from '../../../util/oldLangProvider';
import { IS_TRANSLATION_SUPPORTED } from '../../../util/windowEnvironment';
import useFlag from '../../../hooks/useFlag';
import useHistoryBack from '../../../hooks/useHistoryBack';
@ -30,8 +30,8 @@ type OwnProps = {
type StateProps = {
isCurrentUserPremium: boolean;
languages?: ApiLanguage[];
} & Pick<ISettings, | 'language' | 'canTranslate' | 'canTranslateChats' | 'doNotTranslate'>;
} & Pick<AccountSettings, 'canTranslate' | 'canTranslateChats' | 'doNotTranslate'>
& Pick<SharedSettings, 'language' | 'languages'>;
const SettingsLanguage: FC<OwnProps & StateProps> = ({
isActive,
@ -47,6 +47,7 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
const {
loadLanguages,
setSettingOption,
setSharedSettingOption,
openPremiumModal,
} = getActions();
@ -70,7 +71,7 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
void oldSetLanguage(langCode as LangCode, () => {
unmarkIsLoading();
setSettingOption({ language: langCode as LangCode });
setSharedSettingOption({ language: langCode as LangCode });
});
});
@ -182,9 +183,9 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const {
language, canTranslate, canTranslateChats, doNotTranslate,
canTranslate, canTranslateChats, doNotTranslate,
} = global.settings.byKey;
const languages = global.settings.languages;
const { language, languages } = selectSharedSettings(global);
const isCurrentUserPremium = selectIsCurrentUserPremium(global);

View File

@ -16,7 +16,7 @@ import {
} from '../../../global/initialState';
import { selectPerformanceSettings } from '../../../global/selectors';
import { areDeepEqual } from '../../../util/areDeepEqual';
import { IS_BACKDROP_BLUR_SUPPORTED, IS_SNAP_EFFECT_SUPPORTED } from '../../../util/windowEnvironment';
import { IS_BACKDROP_BLUR_SUPPORTED, IS_SNAP_EFFECT_SUPPORTED } from '../../../util/browser/windowEnvironment';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang';
@ -81,7 +81,7 @@ function SettingsPerformance({
onReset,
}: OwnProps & StateProps) {
const {
setSettingOption,
setSharedSettingOption,
updatePerformanceSettings,
} = getActions();
@ -138,9 +138,9 @@ function SettingsPerformance({
? INITIAL_PERFORMANCE_STATE_MIN
: (newLevel === ANIMATION_LEVEL_MED ? INITIAL_PERFORMANCE_STATE_MID : INITIAL_PERFORMANCE_STATE_MAX);
setSettingOption({ animationLevel: newLevel as AnimationLevel });
setSharedSettingOption({ animationLevel: newLevel as AnimationLevel });
updatePerformanceSettings(performance);
}, [setSettingOption]);
}, []);
const handlePropertyGroupChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const { name, checked } = e.target;

View File

@ -7,6 +7,7 @@ import type { GlobalState } from '../../../global/types';
import { SettingsScreens } from '../../../types';
import { selectCanSetPasscode, selectIsCurrentUserPremium } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang';
@ -66,7 +67,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
loadGlobalPrivacySettings,
updateGlobalPrivacySettings,
loadWebAuthorizations,
setSettingOption,
setSharedSettingOption,
} = getActions();
useEffect(() => {
@ -97,7 +98,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
}, [updateGlobalPrivacySettings]);
const handleChatInTitleChange = useCallback((isChecked: boolean) => {
setSettingOption({
setSharedSettingOption({
canDisplayChatInTitle: isChecked,
});
}, []);
@ -405,7 +406,7 @@ export default memo(withGlobal<OwnProps>(
settings: {
byKey: {
hasPassword, isSensitiveEnabled, canChangeSensitive, shouldArchiveAndMuteNewNonContact,
canDisplayChatInTitle, shouldNewNonContactPeersRequirePremium, nonContactPeersPaidStars,
shouldNewNonContactPeersRequirePremium, nonContactPeersPaidStars,
},
privacy,
},
@ -416,6 +417,7 @@ export default memo(withGlobal<OwnProps>(
appConfig,
} = global;
const { canDisplayChatInTitle } = selectSharedSettings(global);
const shouldChargeForMessages = Boolean(nonContactPeersPaidStars);
return {

View File

@ -10,7 +10,7 @@ import type {
ApiSticker,
ApiStickerSet,
} from '../../../api/types';
import type { ISettings } from '../../../types';
import type { AccountSettings } from '../../../types';
import { SettingsScreens } from '../../../types';
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
@ -36,7 +36,7 @@ type OwnProps = {
};
type StateProps =
Pick<ISettings, (
Pick<AccountSettings, (
'shouldSuggestStickers' | 'shouldUpdateStickerSetOrder'
)> & {
addedSetIds?: string[];

View File

@ -7,7 +7,7 @@ import { withGlobal } from '../../../../global';
import type { ApiSticker } from '../../../../api/types';
import { selectAnimatedEmoji, selectTabState } from '../../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../../util/windowEnvironment';
import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment';
import useAppLayout from '../../../../hooks/useAppLayout';
import useHistoryBack from '../../../../hooks/useHistoryBack';

View File

@ -7,7 +7,7 @@ import { withGlobal } from '../../../../global';
import type { ApiSticker } from '../../../../api/types';
import { selectAnimatedEmoji } from '../../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../../util/windowEnvironment';
import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment';
import renderText from '../../../common/helpers/renderText';
import useAppLayout from '../../../../hooks/useAppLayout';

View File

@ -6,10 +6,10 @@ import type { TabState } from '../../global/types';
import { ApiMediaFormat } from '../../api/types';
import { selectTabState } from '../../global/selectors';
import { IS_OPFS_SUPPORTED, IS_SERVICE_WORKER_SUPPORTED, MAX_BUFFER_SIZE } from '../../util/browser/windowEnvironment';
import download from '../../util/download';
import generateUniqueId from '../../util/generateUniqueId';
import * as mediaLoader from '../../util/mediaLoader';
import { IS_OPFS_SUPPORTED, IS_SERVICE_WORKER_SUPPORTED, MAX_BUFFER_SIZE } from '../../util/windowEnvironment';
import useLastCallback from '../../hooks/useLastCallback';
import useRunDebounced from '../../hooks/useRunDebounced';

View File

@ -30,13 +30,14 @@ import {
selectTabState,
selectUser,
} from '../../global/selectors';
import { selectSharedSettings } from '../../global/selectors/sharedState';
import { IS_ANDROID, IS_ELECTRON, IS_WAVE_TRANSFORM_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners';
import { processDeepLink } from '../../util/deeplink';
import { Bundles, loadBundle } from '../../util/moduleLoader';
import { parseInitialLocationHash, parseLocationHash } from '../../util/routing';
import updateIcon from '../../util/updateIcon';
import { IS_ANDROID, IS_ELECTRON, IS_WAVE_TRANSFORM_SUPPORTED } from '../../util/windowEnvironment';
import useInterval from '../../hooks/schedulers/useInterval';
import useTimeout from '../../hooks/schedulers/useTimeout';
@ -598,11 +599,6 @@ const Main = ({
export default memo(withGlobal<OwnProps>(
(global, { isMobile }): StateProps => {
const {
settings: {
byKey: {
wasTimeFormatSetManually,
},
},
currentUserId,
} = global;
@ -631,6 +627,8 @@ export default memo(withGlobal<OwnProps>(
deleteFolderDialogModal,
} = selectTabState(global);
const { wasTimeFormatSetManually } = selectSharedSettings(global);
const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId);
const gameTitle = gameMessage?.content.game?.title;
const { chatId } = selectCurrentMessageList(global) || {};

View File

@ -8,8 +8,8 @@ import type { ApiCountryCode, ApiUser, ApiUserStatus } from '../../api/types';
import { getUserStatus } from '../../global/helpers';
import { selectUser, selectUserStatus } from '../../global/selectors';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import { formatPhoneNumberWithCode } from '../../util/phoneNumber';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';

View File

@ -88,6 +88,7 @@ const LIMITS_TITLES: Record<ApiLimitTypeForPromo, string> = {
dialogFilters: 'FoldersLimitTitle',
dialogFiltersChats: 'ChatPerFolderLimitTitle',
recommendedChannels: 'SimilarChannelsLimitTitle',
moreAccounts: 'ConnectedAccountsLimitTitle',
};
const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeForPromo, string> = {
@ -101,6 +102,7 @@ const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeForPromo, string> = {
dialogFilters: 'FoldersLimitSubtitle',
dialogFiltersChats: 'ChatPerFolderLimitSubtitle',
recommendedChannels: 'SimilarChannelsLimitSubtitle',
moreAccounts: 'ConnectedAccountsLimitSubtitle',
};
const BORDER_THRESHOLD = 20;
@ -216,13 +218,16 @@ const PremiumFeatureModal: FC<OwnProps> = ({
stopScrolling();
});
const currentSection = filteredSections[currentSlideIndex];
const hasHeaderBackdrop = currentSection !== 'double_limits' && currentSection !== 'stories';
return (
<div className={styles.root}>
<Button
round
size="smaller"
className={buildClassName(styles.backButton, currentSlideIndex !== 0 && styles.whiteBackButton)}
color={currentSlideIndex === 0 ? 'translucent' : 'translucent-white'}
className={buildClassName(styles.backButton, hasHeaderBackdrop && styles.whiteBackButton)}
color={hasHeaderBackdrop ? 'translucent-white' : 'translucent'}
onClick={onBack}
ariaLabel={oldLang('Back')}
>

View File

@ -11,9 +11,10 @@ import { MEDIA_TIMESTAMP_SAVE_MINIMUM_DURATION } from '../../config';
import {
selectIsMessageProtected, selectMessageTimestampableDuration, selectTabState,
} from '../../global/selectors';
import { ARE_WEBCODECS_SUPPORTED } from '../../util/browser/globalEnvironment';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import stopEvent from '../../util/stopEvent';
import { ARE_WEBCODECS_SUPPORTED, IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { calculateMediaViewerDimensions } from '../common/helpers/mediaDimensions';
import { renderMessageText } from '../common/helpers/renderMessageText';
import getViewableMedia from './helpers/getViewableMedia';

View File

@ -3,9 +3,9 @@ import React, { useEffect, useState } from '../../lib/teact/teact';
import type { TextPart } from '../../types';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { throttle } from '../../util/schedulers';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { REM } from '../common/helpers/mediaDimensions';
import useAppLayout from '../../hooks/useAppLayout';

View File

@ -8,6 +8,7 @@ import type { RealTouchEvent } from '../../util/captureEvents';
import type { MediaViewerItem } from './helpers/getViewableMedia';
import { animateNumber, timingFunctions } from '../../util/animation';
import { IS_IOS, IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import {
captureEvents,
@ -17,7 +18,6 @@ import {
} from '../../util/captureEvents';
import { clamp, isBetween, round } from '../../util/math';
import { debounce } from '../../util/schedulers';
import { IS_IOS, IS_TOUCH_ENV } from '../../util/windowEnvironment';
import useTimeout from '../../hooks/schedulers/useTimeout';
import useDebouncedCallback from '../../hooks/useDebouncedCallback';

View File

@ -9,11 +9,11 @@ import type { BufferedRange } from '../../hooks/useBuffering';
import { createVideoPreviews, getPreviewDimensions, renderVideoPreview } from '../../lib/video-preview/VideoPreview';
import { animateNumber } from '../../util/animation';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { captureEvents } from '../../util/captureEvents';
import { formatMediaDuration } from '../../util/dates/dateFormat';
import { clamp, round } from '../../util/math';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { useThrottledSignal } from '../../hooks/useAsyncResolvers';
import useCurrentTimeSignal from '../../hooks/useCurrentTimeSignal';

View File

@ -6,10 +6,10 @@ import { getActions } from '../../global';
import type { ApiDimensions } from '../../api/types';
import { IS_IOS, IS_TOUCH_ENV, IS_YA_BROWSER } from '../../util/browser/windowEnvironment';
import { clamp } from '../../util/math';
import safePlay from '../../util/safePlay';
import stopEvent from '../../util/stopEvent';
import { IS_IOS, IS_TOUCH_ENV, IS_YA_BROWSER } from '../../util/windowEnvironment';
import useUnsupportedMedia from '../../hooks/media/useUnsupportedMedia';
import useAppLayout from '../../hooks/useAppLayout';

View File

@ -9,10 +9,10 @@ import type { ApiDimensions } from '../../api/types';
import type { BufferedRange } from '../../hooks/useBuffering';
import type { IconName } from '../../types/icons';
import { IS_IOS, IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { formatMediaDuration } from '../../util/dates/dateFormat';
import { formatFileSize } from '../../util/textFormat';
import { IS_IOS, IS_TOUCH_ENV } from '../../util/windowEnvironment';
import useAppLayout from '../../hooks/useAppLayout';
import useCurrentTimeSignal from '../../hooks/useCurrentTimeSignal';

View File

@ -5,10 +5,10 @@ import { ANIMATION_END_DELAY, MESSAGE_CONTENT_SELECTOR } from '../../../config';
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { getMessageHtmlId } from '../../../global/helpers';
import { applyStyles } from '../../../util/animation';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import stopEvent from '../../../util/stopEvent';
import getOffsetToContainer from '../../../util/visibility/getOffsetToContainer';
import { isElementInViewport } from '../../../util/visibility/isElementInViewport';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import windowSize from '../../../util/windowSize';
import {
calculateDimensions,

View File

@ -10,8 +10,8 @@ import type { ActiveEmojiInteraction } from '../../types';
import {
selectAnimatedEmojiEffect,
} from '../../global/selectors';
import { IS_ANDROID } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import { IS_ANDROID } from '../../util/windowEnvironment';
import useFlag from '../../hooks/useFlag';
import useLastCallback from '../../hooks/useLastCallback';

View File

@ -31,7 +31,7 @@ import {
selectTranslationLanguage,
selectUserFullInfo,
} from '../../global/selectors';
import { ARE_CALLS_SUPPORTED, IS_APP } from '../../util/windowEnvironment';
import { ARE_CALLS_SUPPORTED, IS_APP } from '../../util/browser/windowEnvironment';
import { useHotkeys } from '../../hooks/useHotkeys';
import useLastCallback from '../../hooks/useLastCallback';

View File

@ -15,6 +15,7 @@ import {
selectSelectedMessagesCount,
selectTabState,
} from '../../global/selectors';
import { selectSharedSettings } from '../../global/selectors/sharedState';
import buildClassName from '../../util/buildClassName';
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
@ -75,7 +76,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
showNotification,
reportMessages,
openDeleteMessageModal,
setSettingOption,
setSharedSettingOption,
} = getActions();
const lang = useOldLang();
@ -133,7 +134,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
});
const handleSvgConfirm = useLastCallback(() => {
setSettingOption({ shouldWarnAboutSvg: false });
setSharedSettingOption({ shouldWarnAboutSvg: false });
closeSvgDialog();
handleDownload();
});
@ -238,7 +239,9 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const tabState = selectTabState(global);
const { shouldWarnAboutSvg } = selectSharedSettings(global);
const chat = selectCurrentChat(global);
const { type: messageListType, chatId } = selectCurrentMessageList(global) || {};
const isSchedule = messageListType === 'scheduled';
const { canDelete } = selectCanDeleteSelectedMessages(global);
@ -263,7 +266,7 @@ export default memo(withGlobal<OwnProps>(
selectedMessageIds,
hasProtectedMessage,
isAnyModalOpen,
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
shouldWarnAboutSvg,
};
},
)(MessageSelectToolbar));

View File

@ -54,17 +54,18 @@ import {
selectPinnedIds,
selectTabState,
selectTheme,
selectThemeValues,
selectThreadInfo,
selectTopic,
selectTopics,
selectUserFullInfo,
} from '../../global/selectors';
import {
IS_ANDROID, IS_ELECTRON, IS_IOS, IS_SAFARI, IS_TRANSLATION_SUPPORTED, MASK_IMAGE_DISABLED,
} from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle';
import captureEscKeyListener from '../../util/captureEscKeyListener';
import {
IS_ANDROID, IS_ELECTRON, IS_IOS, IS_SAFARI, IS_TRANSLATION_SUPPORTED, MASK_IMAGE_DISABLED,
} from '../../util/windowEnvironment';
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
import useAppLayout from '../../hooks/useAppLayout';
@ -726,7 +727,7 @@ export default memo(withGlobal<OwnProps>(
const theme = selectTheme(global);
const {
isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor,
} = global.settings.themes[theme] || {};
} = selectThemeValues(global, theme) || {};
const {
messageLists, isLeftColumnShown, activeEmojiInteractions,

View File

@ -2,7 +2,7 @@ import React, { memo } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import { getUserFirstOrLastName } from '../../global/helpers';
import { selectTheme, selectUser } from '../../global/selectors';
import { selectTheme, selectThemeValues, selectUser } from '../../global/selectors';
import { formatStarsAsIcon } from '../../util/localization/format';
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
import renderText from '../common/helpers/renderText';
@ -95,7 +95,7 @@ function RequirementToContactMessage({ patternColor, userName, paidMessagesStars
export default memo(
withGlobal<OwnProps>((global, { userId }): StateProps => {
const theme = selectTheme(global);
const { patternColor } = global.settings.themes[theme] || {};
const { patternColor } = selectThemeValues(global, theme) || {};
const user = selectUser(global, userId);
return {

View File

@ -2,7 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo, useMemo } from '../../../lib/teact/teact';
import type { ApiDocument } from '../../../api/types';
import type { ISettings } from '../../../types';
import type { ThemeKey } from '../../../types';
import { ApiMediaFormat } from '../../../api/types';
import { getDocumentMediaHash } from '../../../global/helpers';
@ -15,7 +15,7 @@ import styles from './AttachBotIcon.module.scss';
type OwnProps = {
icon: ApiDocument;
theme: ISettings['theme'];
theme: ThemeKey;
};
const ADDITIONAL_STROKE_WIDTH = '0.5px';

View File

@ -5,7 +5,7 @@ import React, {
import { getActions } from '../../../global';
import type { ApiAttachBot } from '../../../api/types';
import type { IAnchorPosition, ISettings, ThreadId } from '../../../types';
import type { IAnchorPosition, ThemeKey, ThreadId } from '../../../types';
import useFlag from '../../../hooks/useFlag';
import useLastCallback from '../../../hooks/useLastCallback';
@ -17,7 +17,7 @@ import AttachBotIcon from './AttachBotIcon';
type OwnProps = {
bot: ApiAttachBot;
theme: ISettings['theme'];
theme: ThemeKey;
isInSideMenu?: true;
chatId?: string;
threadId?: ThreadId;

View File

@ -6,7 +6,7 @@ import React, {
import type { ApiAttachMenuPeerType, ApiMessage } from '../../../api/types';
import type { GlobalState } from '../../../global/types';
import type { ISettings, MessageListType, ThreadId } from '../../../types';
import type { MessageListType, ThemeKey, ThreadId } from '../../../types';
import {
CONTENT_TYPES_WITH_PREVIEW, DEBUG_LOG_FILENAME, SUPPORTED_AUDIO_CONTENT_TYPES,
@ -20,11 +20,11 @@ import {
getMessageWebPagePhoto,
getMessageWebPageVideo,
} from '../../../global/helpers';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { getDebugLogs } from '../../../util/debugConsole';
import { validateFiles } from '../../../util/files';
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useFlag from '../../../hooks/useFlag';
import useLang from '../../../hooks/useLang';
@ -54,7 +54,7 @@ export type OwnProps = {
attachBots?: GlobalState['attachMenu']['bots'];
peerType?: ApiAttachMenuPeerType;
shouldCollectDebugLogs?: boolean;
theme: ISettings['theme'];
theme: ThemeKey;
onFileSelect: (files: File[], shouldSuggestCompression?: boolean) => void;
onPollCreate: NoneToVoidFunction;
onMenuOpen: NoneToVoidFunction;

View File

@ -22,6 +22,7 @@ import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { getAttachmentMediaType, isUserId } from '../../../global/helpers';
import { selectChatFullInfo, selectIsChatWithSelf } from '../../../global/selectors';
import { selectCurrentLimit } from '../../../global/selectors/limits';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { validateFiles } from '../../../util/files';
@ -750,7 +751,8 @@ export default memo(withGlobal<OwnProps>(
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
const { language, shouldSuggestCustomEmoji } = global.settings.byKey;
const { shouldSuggestCustomEmoji } = global.settings.byKey;
const { language } = selectSharedSettings(global);
const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG];
const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined;

View File

@ -4,7 +4,7 @@ import { getActions } from '../../../global';
import type { ApiBotCommand } from '../../../api/types';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import useAppLayout from '../../../hooks/useAppLayout';
import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -5,7 +5,7 @@ import { getActions, withGlobal } from '../../../global';
import type { ApiMessage } from '../../../api/types';
import { selectChatMessage, selectCurrentMessageList } from '../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import renderKeyboardButtonText from './helpers/renderKeyboardButtonText';
import useMouseInside from '../../../hooks/useMouseInside';

View File

@ -10,8 +10,8 @@ import type {
} from '../../../api/types';
import type { IAnchorPosition } from '../../../types';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
import useFlag from '../../../hooks/useFlag';

View File

@ -2,9 +2,9 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo } from '../../../lib/teact/teact';
import { BASE_URL, IS_PACKAGED_ELECTRON } from '../../../config';
import { IS_EMOJI_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { handleEmojiLoad, LOADED_EMOJIS } from '../../../util/emoji/emoji';
import { IS_EMOJI_SUPPORTED } from '../../../util/windowEnvironment';
import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -16,11 +16,11 @@ import type {
import { MENU_TRANSITION_DURATION, RECENT_SYMBOL_SET_ID } from '../../../config';
import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
import animateScroll from '../../../util/animateScroll';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { uncompressEmoji } from '../../../util/emoji/emoji';
import { pick } from '../../../util/iteratees';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import { REM } from '../../common/helpers/mediaDimensions';
import useAppLayout from '../../../hooks/useAppLayout';

View File

@ -8,8 +8,8 @@ import type { ApiVideo } from '../../../api/types';
import { SLIDE_TRANSITION_DURATION } from '../../../config';
import { selectCurrentMessageList, selectIsChatWithSelf } from '../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -7,11 +7,11 @@ import type {
} from '../../../api/types';
import { LoadMoreDirection } from '../../../types';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { throttle } from '../../../util/schedulers';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';

View File

@ -9,22 +9,23 @@ import { getActions, withGlobal } from '../../../global';
import type { ApiInputMessageReplyInfo } from '../../../api/types';
import type {
IAnchorPosition, ISettings, MessageListType, ThreadId,
IAnchorPosition, MessageListType, SharedSettings, ThreadId,
} from '../../../types';
import type { Signal } from '../../../util/signals';
import { EDITABLE_INPUT_ID } from '../../../config';
import { requestForcedReflow, requestMutation } from '../../../lib/fasterdom/fasterdom';
import { selectCanPlayAnimatedEmojis, selectDraft, selectIsInSelectMode } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import {
IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_TOUCH_ENV,
} from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
import { getIsDirectTextInputDisabled } from '../../../util/directInputManager';
import parseEmojiOnlyString from '../../../util/emoji/parseEmojiOnlyString';
import focusEditableElement from '../../../util/focusEditableElement';
import { debounce } from '../../../util/schedulers';
import {
IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_TOUCH_ENV,
} from '../../../util/windowEnvironment';
import renderText from '../../common/helpers/renderText';
import { isSelectionInsideInput } from './helpers/selection';
@ -83,7 +84,7 @@ type OwnProps = {
type StateProps = {
replyInfo?: ApiInputMessageReplyInfo;
isSelectModeActive?: boolean;
messageSendKeyCombo?: ISettings['messageSendKeyCombo'];
messageSendKeyCombo?: SharedSettings['messageSendKeyCombo'];
canPlayAnimatedEmojis: boolean;
};
@ -649,7 +650,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global, { chatId, threadId }: OwnProps): StateProps => {
const { messageSendKeyCombo } = global.settings.byKey;
const { messageSendKeyCombo } = selectSharedSettings(global);
return {
messageSendKeyCombo,

View File

@ -4,9 +4,9 @@ import { getActions, getGlobal } from '../../../global';
import type { ApiSendAsPeerId } from '../../../api/types';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useLastCallback from '../../../hooks/useLastCallback';
import useMouseInside from '../../../hooks/useMouseInside';

Some files were not shown because too many files have changed in this diff Show More