[Memory] Fixes and optimizations (#3453)

This commit is contained in:
Alexander Zinchuk 2023-07-05 13:14:52 +02:00
parent a136a66e61
commit 23eef3034d
28 changed files with 609 additions and 1913 deletions

View File

@ -1,17 +1,17 @@
import { Api as GramJs } from '../../../lib/gramjs';
import type {
ApiConfig,
ApiCountry, ApiSession, ApiUrlAuthResult, ApiWallpaper, ApiWebSession,
ApiConfig, ApiCountry, ApiSession, ApiUrlAuthResult, ApiWallpaper, ApiWebSession, ApiLangString,
} from '../../types';
import type { ApiPrivacySettings, ApiPrivacyKey, PrivacyVisibility } from '../../../types';
import { buildApiDocument, buildApiReaction } from './messages';
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
import { pick } from '../../../util/iteratees';
import { omit, pick } from '../../../util/iteratees';
import { getServerTime } from '../../../util/serverTime';
import { buildApiUser } from './users';
import { addUserToLocalDb } from '../helpers';
import { omitVirtualClassFields } from './helpers';
export function buildApiWallpaper(wallpaper: GramJs.TypeWallPaper): ApiWallpaper | undefined {
if (wallpaper instanceof GramJs.WallPaperNoFile) {
@ -253,3 +253,18 @@ export function buildApiConfig(config: GramJs.Config): ApiConfig {
autologinToken: config.autologinToken,
};
}
export function buildLangPack(mtpLangPack: GramJs.LangPackDifference) {
return mtpLangPack.strings.reduce<Record<string, ApiLangString | undefined>>((acc, mtpString) => {
acc[mtpString.key] = buildLangPackString(mtpString);
return acc;
}, {});
}
export function buildLangPackString(mtpString: GramJs.TypeLangPackString) {
return mtpString instanceof GramJs.LangPackString
? mtpString.value
: mtpString instanceof GramJs.LangPackStringPluralized
? omit(omitVirtualClassFields(mtpString), ['key'])
: undefined;
}

View File

@ -5,9 +5,10 @@ import type {
ApiAppConfig,
ApiConfig,
ApiError,
ApiLangString,
ApiLanguage,
ApiNotifyException, ApiPhoto, ApiUser,
ApiNotifyException,
ApiPhoto,
ApiUser,
} from '../../types';
import type { ApiPrivacyKey, InputPrivacyRules, LangCode } from '../../../types';
import type { LANG_PACKS } from '../../../config';
@ -20,7 +21,7 @@ import {
buildApiNotifyException,
buildApiSession,
buildApiWallpaper,
buildApiWebSession,
buildApiWebSession, buildLangPack, buildLangPackString,
buildPrivacyRules,
} from '../apiBuilders/misc';
@ -426,12 +427,7 @@ export async function fetchLangPack({ sourceLangPacks, langCode }: {
}));
}));
const collections = results
.filter(Boolean)
.map((result) => {
return buildCollectionByKey(result.strings.map<ApiLangString>(omitVirtualClassFields), 'key');
});
const collections = results.filter(Boolean).map(buildLangPack);
if (!collections.length) {
return undefined;
}
@ -452,7 +448,7 @@ export async function fetchLangStrings({ langPack, langCode, keys }: {
return undefined;
}
return result.map(omitVirtualClassFields);
return result.map(buildLangPackString);
}
export async function fetchPrivacySettings(privacyKey: ApiPrivacyKey) {

View File

@ -7,7 +7,7 @@ import type { TypedBroadcastChannel } from '../../../util/multitab';
import { IS_MULTITAB_SUPPORTED } from '../../../util/windowEnvironment';
import { DATA_BROADCAST_CHANNEL_NAME, DEBUG } from '../../../config';
import generateIdFor from '../../../util/generateIdFor';
import generateUniqueId from '../../../util/generateUniqueId';
import { pause } from '../../../util/schedulers';
import { getCurrentTabId, subscribeToMasterChange } from '../../../util/establishMultitabRole';
import Deferred from '../../../util/Deferred';
@ -284,7 +284,7 @@ function makeRequestToMaster(message: {
args: MethodArgs<keyof Methods>;
withCallback?: boolean;
}) {
const messageId = generateIdFor(requestStates);
const messageId = generateUniqueId();
const payload = {
messageId,
...message,
@ -323,7 +323,7 @@ function makeRequestToMaster(message: {
}
function makeRequest(message: OriginRequest) {
const messageId = generateIdFor(requestStates);
const messageId = generateUniqueId();
const payload: OriginRequest = {
messageId,
...message,

View File

@ -12,15 +12,13 @@ export interface ApiLanguage {
translationsUrl: string;
}
export interface ApiLangString {
key: string;
value?: string;
export type ApiLangString = string | {
zeroValue?: string;
oneValue?: string;
twoValue?: string;
fewValue?: string;
manyValue?: string;
otherValue?: string;
}
};
export type ApiLangPack = Record<string, ApiLangString>;
export type ApiLangPack = Record<string, ApiLangString | undefined>;

View File

@ -5,12 +5,12 @@ import { requestMeasure } from '../../lib/fasterdom/fasterdom';
import { ensureRLottie, getRLottie } from '../../lib/rlottie/RLottie.async';
import React, {
useEffect, useRef, memo, useState, useMemo,
useEffect, useRef, memo, useState,
} from '../../lib/teact/teact';
import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle';
import generateIdFor from '../../util/generateIdFor';
import generateUniqueId from '../../util/generateUniqueId';
import { hexToRgb } from '../../util/switchTheme';
import useLastCallback from '../../hooks/useLastCallback';
@ -23,6 +23,7 @@ import useSharedIntersectionObserver from '../../hooks/useSharedIntersectionObse
import useThrottledCallback from '../../hooks/useThrottledCallback';
import useColorFilter from '../../hooks/stickers/useColorFilter';
import useSyncEffect from '../../hooks/useSyncEffect';
import useUniqueId from '../../hooks/useUniqueId';
export type OwnProps = {
ref?: RefObject<HTMLDivElement>;
@ -49,7 +50,6 @@ export type OwnProps = {
};
const THROTTLE_MS = 150;
const ID_STORE = {};
const AnimatedSticker: FC<OwnProps> = ({
ref,
@ -80,7 +80,7 @@ const AnimatedSticker: FC<OwnProps> = ({
containerRef = ref;
}
const viewId = useMemo(() => generateIdFor(ID_STORE, true), []);
const viewId = useUniqueId();
const [animation, setAnimation] = useState<RLottieInstance>();
const animationRef = useRef<RLottieInstance>();
@ -129,7 +129,7 @@ const AnimatedSticker: FC<OwnProps> = ({
const newAnimation = getRLottie().init(
tgsUrl,
container,
renderId || generateIdFor(ID_STORE, true),
renderId || generateUniqueId(),
{
size,
noLoop,

View File

@ -1,7 +1,7 @@
import type { FC } from '../../lib/teact/teact';
import React, { memo, useMemo } from '../../lib/teact/teact';
import generateIdFor from '../../util/generateIdFor';
import generateUniqueId from '../../util/generateUniqueId';
import buildClassName from '../../util/buildClassName';
@ -9,7 +9,6 @@ import './PremiumIcon.scss';
// eslint-disable-next-line max-len
const PREMIUM_ICON = { __html: '<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M6.63869 12.1902L3.50621 14.1092C3.18049 14.3087 2.75468 14.2064 2.55515 13.8807C2.45769 13.7216 2.42864 13.5299 2.47457 13.3491L2.95948 11.4405C3.13452 10.7515 3.60599 10.1756 4.24682 9.86791L7.6642 8.22716C7.82352 8.15067 7.89067 7.95951 7.81418 7.80019C7.75223 7.67116 7.61214 7.59896 7.47111 7.62338L3.66713 8.28194C2.89387 8.41581 2.1009 8.20228 1.49941 7.69823L0.297703 6.69116C0.00493565 6.44581 -0.0335059 6.00958 0.211842 5.71682C0.33117 5.57442 0.502766 5.48602 0.687982 5.47153L4.35956 5.18419C4.61895 5.16389 4.845 4.99974 4.94458 4.75937L6.36101 1.3402C6.5072 0.987302 6.91179 0.819734 7.26469 0.965925C7.43413 1.03612 7.56876 1.17075 7.63896 1.3402L9.05539 4.75937C9.15496 4.99974 9.38101 5.16389 9.6404 5.18419L13.3322 5.47311C13.713 5.50291 13.9975 5.83578 13.9677 6.2166C13.9534 6.39979 13.8667 6.56975 13.7269 6.68896L10.9114 9.08928C10.7131 9.25826 10.6267 9.52425 10.6876 9.77748L11.5532 13.3733C11.6426 13.7447 11.414 14.1182 11.0427 14.2076C10.8642 14.2506 10.676 14.2208 10.5195 14.1249L7.36128 12.1902C7.13956 12.0544 6.8604 12.0544 6.63869 12.1902Z" fill="var(--color-fill)"/></svg>' };
const store: Record<string, boolean> = {};
type OwnProps = {
withGradient?: boolean;
@ -41,7 +40,7 @@ const PremiumIcon: FC<OwnProps> = ({
};
function getPremiumIconGradient() {
const id = generateIdFor(store);
const id = generateUniqueId();
return {
// eslint-disable-next-line max-len
__html: `<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="${id}" x1="3" y1="63.5001" x2="84.1475" y2="-1.32262" gradientUnits="userSpaceOnUse"><stop stop-color="#6B93FF"/><stop offset="0.439058" stop-color="#976FFF"/><stop offset="1" stop-color="#E46ACE"/></linearGradient></defs><path fill-rule="evenodd" clip-rule="evenodd" d="M6.63869 12.1902L3.50621 14.1092C3.18049 14.3087 2.75468 14.2064 2.55515 13.8807C2.45769 13.7216 2.42864 13.5299 2.47457 13.3491L2.95948 11.4405C3.13452 10.7515 3.60599 10.1756 4.24682 9.86791L7.6642 8.22716C7.82352 8.15067 7.89067 7.95951 7.81418 7.80019C7.75223 7.67116 7.61214 7.59896 7.47111 7.62338L3.66713 8.28194C2.89387 8.41581 2.1009 8.20228 1.49941 7.69823L0.297703 6.69116C0.00493565 6.44581 -0.0335059 6.00958 0.211842 5.71682C0.33117 5.57442 0.502766 5.48602 0.687982 5.47153L4.35956 5.18419C4.61895 5.16389 4.845 4.99974 4.94458 4.75937L6.36101 1.3402C6.5072 0.987302 6.91179 0.819734 7.26469 0.965925C7.43413 1.03612 7.56876 1.17075 7.63896 1.3402L9.05539 4.75937C9.15496 4.99974 9.38101 5.16389 9.6404 5.18419L13.3322 5.47311C13.713 5.50291 13.9975 5.83578 13.9677 6.2166C13.9534 6.39979 13.8667 6.56975 13.7269 6.68896L10.9114 9.08928C10.7131 9.25826 10.6267 9.52425 10.6876 9.77748L11.5532 13.3733C11.6426 13.7447 11.414 14.1182 11.0427 14.2076C10.8642 14.2506 10.676 14.2208 10.5195 14.1249L7.36128 12.1902C7.13956 12.0544 6.8604 12.0544 6.63869 12.1902Z" fill="url(#${id})"/></svg>`,

View File

@ -1,6 +1,4 @@
import React, {
memo, useMemo,
} from '../../lib/teact/teact';
import React, { memo } from '../../lib/teact/teact';
import { getGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
@ -10,7 +8,6 @@ import type { ApiSticker } from '../../api/types';
import { IS_ANDROID, IS_WEBM_SUPPORTED } from '../../util/windowEnvironment';
import * as mediaLoader from '../../util/mediaLoader';
import buildClassName from '../../util/buildClassName';
import generateIdFor from '../../util/generateIdFor';
import { getStickerPreviewHash } from '../../global/helpers';
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
@ -22,6 +19,7 @@ import useFlag from '../../hooks/useFlag';
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck';
import useColorFilter from '../../hooks/stickers/useColorFilter';
import useUniqueId from '../../hooks/useUniqueId';
import AnimatedSticker from './AnimatedSticker';
import OptimizedVideo from '../ui/OptimizedVideo';
@ -54,7 +52,6 @@ type OwnProps = {
const SHARED_PREFIX = 'shared';
const STICKER_SIZE = 24;
const ID_STORE = {};
const StickerView: FC<OwnProps> = ({
containerRef,
@ -127,7 +124,7 @@ const StickerView: FC<OwnProps> = ({
// Preload preview for Message Input and local message
useMedia(previewMediaHash, !shouldLoad || !shouldPreloadPreview);
const randomIdPrefix = useMemo(() => generateIdFor(ID_STORE, true), []);
const randomIdPrefix = useUniqueId();
const renderId = [
(withSharedAnimation ? SHARED_PREFIX : randomIdPrefix),
id,

View File

@ -11,11 +11,10 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useEnsureCustomEmoji from '../../../hooks/useEnsureCustomEmoji';
export default function useCustomEmoji(documentId?: string) {
const global = getGlobal();
const [customEmoji, setCustomEmoji] = useState<ApiSticker | undefined>(
documentId ? global.customEmojis.byId[documentId] : undefined,
documentId ? getGlobal().customEmojis.byId[documentId] : undefined,
);
const [canPlay, setCanPlay] = useState(selectCanPlayAnimatedEmojis(global));
const [canPlay, setCanPlay] = useState(selectCanPlayAnimatedEmojis(getGlobal()));
useEnsureCustomEmoji(documentId);

View File

@ -122,13 +122,10 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
return undefined;
}
const global = getGlobal();
return displayedFolders.map((folder, i) => {
const { id, title } = folder;
const isBlocked = id !== ALL_FOLDER_ID && i > maxFolders - 1;
const canShareFolder = selectCanShareFolder(global, id);
const canShareFolder = selectCanShareFolder(getGlobal(), id);
const contextActions = [];
if (canShareFolder) {

View File

@ -322,12 +322,13 @@ addActionHandler('loadAllChats', async (global, actions, payload): Promise<void>
.sort((chat1, chat2) => getOrderDate(chat1)! - getOrderDate(chat2)!)[0]
: undefined;
await loadChats(global,
await loadChats(
listType,
oldestChat?.id,
oldestChat ? getOrderDate(oldestChat) : undefined,
shouldReplace,
true);
true,
);
if (shouldReplace) {
onReplace?.();
@ -352,10 +353,10 @@ addActionHandler('loadFullChat', (global, actions, payload): ActionReturnType =>
}
});
addActionHandler('loadTopChats', (global): ActionReturnType => {
addActionHandler('loadTopChats', (): ActionReturnType => {
runThrottledForLoadTopChats(() => {
loadChats(global, 'active');
loadChats(global, 'archived');
loadChats('active');
loadChats('archived');
});
});
@ -2157,15 +2158,15 @@ addActionHandler('openDeleteChatFolderModal', async (global, actions, payload):
setGlobal(global);
});
async function loadChats<T extends GlobalState>(
global: T,
async function loadChats(
listType: 'active' | 'archived',
offsetId?: string,
offsetDate?: number,
shouldReplace = false,
isFullDraftSync?: boolean,
) {
global = getGlobal();
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
let global = getGlobal();
let lastLocalServiceMessage = selectLastServiceNotification(global)?.message;
const result = await callApi('fetchChats', {
limit: CHAT_LIST_LOAD_SLICE,

View File

@ -604,7 +604,6 @@ addActionHandler('setStickerSearchQuery', (global, actions, payload): ActionRetu
if (query) {
void searchThrottled(async () => {
const result = await callApi('searchStickers', { query });
if (!result) {
return;
}
@ -643,6 +642,7 @@ addActionHandler('setGifSearchQuery', (global, actions, payload): ActionReturnTy
if (typeof query === 'string') {
void searchThrottled(() => {
global = getGlobal();
searchGifs(global, query, global.config?.gifSearchUsername, undefined, tabId);
});
}
@ -654,6 +654,7 @@ addActionHandler('searchMoreGifs', (global, actions, payload): ActionReturnType
if (typeof query === 'string') {
void searchThrottled(() => {
global = getGlobal();
searchGifs(global, query, global.config?.gifSearchUsername, offset, tabId);
});
}

View File

@ -19,7 +19,7 @@ import {
selectIsTrustedBot,
selectTabState,
} from '../../selectors';
import generateIdFor from '../../../util/generateIdFor';
import generateUniqueId from '../../../util/generateUniqueId';
import { compact, unique } from '../../../util/iteratees';
import { getAllMultitabTokens, getCurrentTabId, reestablishMasterToSelf } from '../../../util/establishMultitabRole';
import { getAllNotificationsCount } from '../../../util/folderManager';
@ -273,7 +273,7 @@ addActionHandler('reorderStickerSets', (global, actions, payload): ActionReturnT
addActionHandler('showNotification', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId(), ...notification } = payload;
notification.localId = generateIdFor({});
notification.localId = generateUniqueId();
const newNotifications = [...selectTabState(global, tabId).notifications];
const existingNotificationIndex = newNotifications.findIndex((n) => n.message === notification.message);

View File

@ -7,6 +7,7 @@ import { IS_IOS } from '../util/windowEnvironment';
import useSyncEffect from './useSyncEffect';
import useEffectOnce from './useEffectOnce';
import useLastCallback from './useLastCallback';
const PATH_BASE = `${window.location.pathname}${window.location.search}`;
// Carefully selected by swiping and observing visual changes
@ -241,6 +242,8 @@ export default function useHistoryBack({
shouldResetUrlHash?: boolean;
onBack: VoidFunction;
}) {
const lastOnBack = useLastCallback(onBack);
// Active index of the record
const indexRef = useRef<number>();
const wasReplaced = useRef(false);
@ -262,7 +265,7 @@ export default function useHistoryBack({
historyState[indexRef.current] = {
index: indexRef.current,
onBack,
onBack: lastOnBack,
shouldBeReplaced,
markReplaced: () => {
wasReplaced.current = true;
@ -283,7 +286,7 @@ export default function useHistoryBack({
// Space is a hack to make the browser completely remove the hash
hash: hash ? `#${hash}` : (shouldResetUrlHash ? ' ' : undefined),
});
}, [hash, onBack, shouldBeReplaced, shouldResetUrlHash]);
}, [hash, shouldBeReplaced, shouldResetUrlHash]);
const processBack = useCallback(() => {
// Only process back on open records

View File

@ -1,15 +1,16 @@
import * as langProvider from '../util/langProvider';
import useForceUpdate from './useForceUpdate';
import useSyncEffect from './useSyncEffect';
import useEffectOnce from './useEffectOnce';
export type LangFn = langProvider.LangFn;
const useLang = (): LangFn => {
const forceUpdate = useForceUpdate();
useSyncEffect(() => {
useEffectOnce(() => {
return langProvider.addCallback(forceUpdate);
}, [forceUpdate]);
});
return langProvider.getTranslationFn();
};

View File

@ -1,10 +1,22 @@
import usePrevious from './usePrevious';
import { useRef } from '../lib/teact/teact';
import useEffectOnce from './useEffectOnce';
const useSyncEffect = <const T extends readonly any[]>(cb: (args: T | readonly []) => void, dependencies: T) => {
export default function useSyncEffect<const T extends readonly any[]>(
effect: (args: T | readonly []) => NoneToVoidFunction | void,
dependencies: T,
) {
const prevDeps = usePrevious<T>(dependencies);
if (!prevDeps || dependencies.some((d, i) => d !== prevDeps[i])) {
cb(prevDeps || []);
}
};
const cleanupRef = useRef<NoneToVoidFunction>();
export default useSyncEffect;
if (!prevDeps || dependencies.some((d, i) => d !== prevDeps[i])) {
cleanupRef.current?.();
cleanupRef.current = effect(prevDeps || []) ?? undefined;
}
useEffectOnce(() => {
return () => {
cleanupRef.current?.();
};
});
}

View File

@ -1,17 +1,12 @@
import { useRef } from '../lib/teact/teact';
import generateIdFor from '../util/generateIdFor';
import generateUniqueId from '../util/generateUniqueId';
const store: Record<string, boolean> = {};
const useUniqueId = () => {
export default function useUniqueId() {
const idRef = useRef<string>();
if (!idRef.current) {
idRef.current = generateIdFor(store);
store[idRef.current] = true;
idRef.current = generateUniqueId();
}
return idRef.current;
};
export default useUniqueId;
}

View File

@ -5,7 +5,7 @@ import {
} from '../../util/windowEnvironment';
import { animate } from '../../util/animation';
import cycleRestrict from '../../util/cycleRestrict';
import generateIdFor from '../../util/generateIdFor';
import generateUniqueId from '../../util/generateUniqueId';
import launchMediaWorkers, { MAX_WORKERS } from '../../util/launchMediaWorkers';
interface Params {
@ -27,7 +27,6 @@ const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75;
const LOW_PRIORITY_QUALITY_SIZE_THRESHOLD = 24;
const HIGH_PRIORITY_CACHE_MODULO = IS_SAFARI ? 2 : 4;
const LOW_PRIORITY_CACHE_MODULO = 0;
const ID_STORE = {};
const workers = launchMediaWorkers().map(({ connector }) => connector);
const instancesByRenderId = new Map<string, RLottie>();
@ -92,7 +91,7 @@ class RLottie {
, canvas,
renderId,
params,
viewId = generateIdFor(ID_STORE, true), ,
viewId = generateUniqueId(), ,
onLoad,
] = args;
let instance = instancesByRenderId.get(renderId);
@ -113,7 +112,7 @@ class RLottie {
private container: HTMLDivElement | HTMLCanvasElement,
private renderId: string,
private params: Params,
viewId: string = generateIdFor(ID_STORE, true),
viewId: string = generateUniqueId(),
private customColor?: [number, number, number],
private onLoad?: NoneToVoidFunction | undefined,
private onEnded?: (isDestroyed?: boolean) => void,

View File

@ -397,7 +397,7 @@ export function renderComponent(componentInstance: ComponentInstance) {
DEBUG_components[componentName] = {
componentName,
renderCount: 0,
renderTimes: [],
avgRenderTime: 0,
};
}
@ -420,7 +420,9 @@ export function renderComponent(componentInstance: ComponentInstance) {
// eslint-disable-next-line no-console
console.warn(`[Teact] Slow component render: ${componentName}, ${Math.round(duration)} ms`);
}
DEBUG_components[componentName].renderTimes.push(duration);
const { renderCount, avgRenderTime } = DEBUG_components[componentName];
DEBUG_components[componentName].avgRenderTime = (avgRenderTime * renderCount + duration) / (renderCount + 1);
DEBUG_components[componentName].renderCount++;
DEBUG_components.TOTAL.renderCount++;

View File

@ -1,15 +1,16 @@
/* eslint-disable eslint-multitab-tt/set-global-only-variable */
import type { FC, FC_withDebug, Props } from './teact';
import React, { useEffect, useState } from './teact';
import React, { useEffect } from './teact';
import { requestMeasure } from '../fasterdom/fasterdom';
import { DEBUG, DEBUG_MORE } from '../../config';
import useForceUpdate from '../../hooks/useForceUpdate';
import generateIdFor from '../../util/generateIdFor';
import { throttleWithTickEnd } from '../../util/schedulers';
import arePropsShallowEqual, { getUnequalProps } from '../../util/arePropsShallowEqual';
import { orderBy } from '../../util/iteratees';
import { handleError } from '../../util/handleError';
import useForceUpdate from '../../hooks/useForceUpdate';
import useUniqueId from '../../hooks/useUniqueId';
import { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck';
export default React;
@ -252,7 +253,7 @@ export function withGlobal<OwnProps extends AnyLiteral>(
return function TeactNContainer(props: OwnProps) {
(TeactNContainer as FC_withDebug).DEBUG_contentComponentName = Component.name;
const [id] = useState(generateIdFor(containers));
const id = useUniqueId();
const forceUpdate = useForceUpdate();
useEffect(() => {

View File

@ -1,4 +1,4 @@
import generateIdFor from '../../util/generateIdFor';
import generateUniqueId from '../../util/generateUniqueId';
import { pause } from '../../util/schedulers';
declare const self: WorkerGlobalScope;
@ -15,7 +15,7 @@ const PART_TIMEOUT = 30000;
const requestStates = new Map<string, RequestStates>();
export function requestPart(params: RequestPartParams): Promise<ArrayBuffer | undefined> {
const messageId = generateIdFor(requestStates);
const messageId = generateUniqueId();
const requestState = {} as RequestStates;
let isResolved = false;

View File

@ -1,5 +1,5 @@
import { pause } from '../util/schedulers';
import generateIdFor from '../util/generateIdFor';
import generateUniqueId from '../util/generateUniqueId';
import {
DEBUG,
MEDIA_CACHE_MAX_BYTES,
@ -149,7 +149,7 @@ export async function requestPart(
return undefined;
}
const messageId = generateIdFor(requestStates);
const messageId = generateUniqueId();
const requestState = {} as RequestStates;
let isResolved = false;

View File

@ -1,4 +1,4 @@
import generateIdFor from './generateIdFor';
import generateUniqueId from './generateUniqueId';
export interface CancellableCallback {
(
@ -108,7 +108,7 @@ class ConnectorClass<T extends InputRequestTypes> {
request(messageData: RequestTypes<T>) {
const { requestStates, requestStatesByCallback } = this;
const messageId = generateIdFor(requestStates);
const messageId = generateUniqueId();
const payload: CallMethodData = {
type: 'callMethod',
messageId,

View File

@ -10,7 +10,7 @@ import { selectCanPlayAnimatedEmojis } from '../global/selectors';
import { getStickerPreviewHash } from '../global/helpers';
import * as mediaLoader from './mediaLoader';
import { throttle } from './schedulers';
import generateIdFor from './generateIdFor';
import generateUniqueId from './generateUniqueId';
import { IS_WEBM_SUPPORTED } from './windowEnvironment';
import { createCallbackManager } from './callbacks';
@ -20,7 +20,6 @@ import blankSrc from '../assets/blank.png';
type CustomEmojiLoadCallback = (customEmojis: GlobalState['customEmojis']) => void;
type CustomEmojiInputRenderCallback = (emojiId: string) => void;
const ID_STORE = {};
const DOM_PROCESS_THROTTLE = 500;
const INPUT_WAITING_CUSTOM_EMOJI_IDS: Set<string> = new Set();
@ -120,7 +119,7 @@ export function getInputCustomEmojiParams(customEmoji?: ApiSticker) {
const isUsingSharedCanvas = customEmoji.isLottie || (customEmoji.isVideo && !shouldUseStaticFallback);
if (isUsingSharedCanvas) {
fetchAndProcess(`sticker${customEmoji.id}`);
return [false, blankSrc, generateIdFor(ID_STORE, true)];
return [false, blankSrc, generateUniqueId()];
}
const mediaData = getCustomEmojiMediaDataForInput(customEmoji.id, shouldUseStaticFallback);

File diff suppressed because it is too large Load Diff

View File

@ -3,112 +3,31 @@
import type { ApiLangPack } from '../api/types';
export const fallbackLangPackInitial = {
WrongNumber: {
key: 'WrongNumber',
value: 'Wrong number?',
},
SentAppCode: {
key: 'SentAppCode',
value: 'We\'ve sent the code to the **Telegram** app on your other device.',
},
'Login.JustSentSms': {
key: 'Login.JustSentSms',
value: 'We have sent you a code via SMS. Please enter it above.',
},
'Login.Header.Password': {
key: 'Login.Header.Password',
value: 'Enter Password',
},
'Login.EnterPasswordDescription': {
key: 'Login.EnterPasswordDescription',
value: 'You have Two-Step Verification enabled, so your account is protected with an additional password.',
},
StartText: {
key: 'StartText',
value: 'Please confirm your country code and enter your phone number.',
},
'Login.PhonePlaceholder': {
key: 'Login.PhonePlaceholder',
value: 'Your phone number',
},
'Login.Next': {
key: 'Login.Next',
value: 'Next',
},
'Login.QR.Login': {
key: 'Login.QR.Login',
value: 'Log in by QR Code',
},
'Login.QR.Title': {
key: 'Login.QR.Title',
value: 'Log in to Telegram by QR Code',
},
'Login.QR.Help1': {
key: 'Login.QR.Help1',
value: 'Open Telegram on your phone',
},
'Login.QR.Help2': {
key: 'Login.QR.Help2',
value: 'Go to **Settings** > **Devices** > **Link Desktop Device**',
},
'Login.QR2.Help2': {
key: 'Login.QR.Help2',
value: 'Go to **Settings** → **Devices** → **Link Desktop Device**',
},
'Login.QR.Help3': {
key: 'Login.QR.Help3',
value: 'Point your phone at this screen to confirm login',
},
'Login.QR.Cancel': {
key: 'Login.QR.Cancel',
value: 'Log in by phone Number',
},
YourName: {
key: 'YourName',
value: 'Your Name',
},
'Login.Register.Desc': {
key: 'Login.Register.Desc',
value: 'Enter your name and add a profile picture.',
},
'Login.Register.FirstName.Placeholder': {
key: 'Login.Register.FirstName.Placeholder',
value: 'First Name',
},
'Login.Register.LastName.Placeholder': {
key: 'Login.Register.LastName.Placeholder',
value: 'Last Name',
},
'Login.SelectCountry.Title': {
key: 'Login.SelectCountry.Title',
value: 'Country',
},
lng_country_none: {
key: 'lng_country_none',
value: 'Country not found',
},
PleaseEnterPassword: {
key: 'PleaseEnterPassword',
value: 'Enter your new password',
},
PHONE_NUMBER_INVALID: {
key: 'PHONE_NUMBER_INVALID',
value: 'Invalid phone number',
},
PHONE_CODE_INVALID: {
key: 'PHONE_CODE_INVALID',
value: 'Invalid code',
},
PASSWORD_HASH_INVALID: {
key: 'PASSWORD_HASH_INVALID',
value: 'Incorrect password',
},
PHONE_PASSWORD_FLOOD: {
key: 'PHONE_PASSWORD_FLOOD',
value: 'Limit exceeded. Please try again later.',
},
PHONE_NUMBER_BANNED: {
key: 'PHONE_NUMBER_BANNED',
value: 'This phone number is banned.',
},
WrongNumber: 'Wrong number?',
SentAppCode: 'We\'ve sent the code to the **Telegram** app on your other device.',
'Login.JustSentSms': 'We have sent you a code via SMS. Please enter it above.',
'Login.Header.Password': 'Enter Password',
'Login.EnterPasswordDescription': 'You have Two-Step Verification enabled, so your account is protected with an additional password.',
StartText: 'Please confirm your country code and enter your phone number.',
'Login.PhonePlaceholder': 'Your phone number',
'Login.Next': 'Next',
'Login.QR.Login': 'Log in by QR Code',
'Login.QR.Title': 'Log in to Telegram by QR Code',
'Login.QR.Help1': 'Open Telegram on your phone',
'Login.QR.Help2': 'Go to **Settings** > **Devices** > **Link Desktop Device**',
'Login.QR2.Help2': 'Go to **Settings** → **Devices** → **Link Desktop Device**',
'Login.QR.Help3': 'Point your phone at this screen to confirm login',
'Login.QR.Cancel': 'Log in by phone Number',
YourName: 'Your Name',
'Login.Register.Desc': 'Enter your name and add a profile picture.',
'Login.Register.FirstName.Placeholder': 'First Name',
'Login.Register.LastName.Placeholder': 'Last Name',
'Login.SelectCountry.Title': 'Country',
lng_country_none: 'Country not found',
PleaseEnterPassword: 'Enter your new password',
PHONE_NUMBER_INVALID: 'Invalid phone number',
PHONE_CODE_INVALID: 'Invalid code',
PASSWORD_HASH_INVALID: 'Incorrect password',
PHONE_PASSWORD_FLOOD: 'Limit exceeded. Please try again later.',
PHONE_NUMBER_BANNED: 'This phone number is banned.',
} as ApiLangPack;

View File

@ -1,13 +0,0 @@
export default function generateIdFor(store: AnyLiteral, withAutoUpdate = false) {
let id;
do {
id = String(Math.random()).replace('0.', 'id');
} while (store[id]);
if (withAutoUpdate) {
store[id] = true;
}
return id;
}

View File

@ -0,0 +1,3 @@
export default function generateUniqueId() {
return Date.now().toString(36) + Math.random().toString(36).slice(2);
}

View File

@ -29,7 +29,10 @@ const PLURAL_RULES = {
en: (n: number) => (n !== 1 ? 6 : 2),
ar: (n: number) => (n === 0 ? 1 : n === 1 ? 2 : n === 2 ? 3 : n % 100 >= 3 && n % 100 <= 10 ? 4 : n % 100 >= 11 ? 5 : 6),
be: (n: number) => {
const s = String(n).split('.'); const t0 = Number(s[0]) === n; const n10 = t0 ? Number(s[0].slice(-1)) : 0; const n100 = t0 ? Number(s[0].slice(-2)) : 0;
const s = String(n).split('.');
const t0 = Number(s[0]) === n;
const n10 = t0 ? Number(s[0].slice(-1)) : 0;
const n100 = t0 ? Number(s[0].slice(-2)) : 0;
return n10 === 1 && n100 !== 11 ? 2
: (n10 >= 2 && n10 <= 4) && (n100 < 12 || n100 > 14) ? 4
: (t0 && n10 === 0) || (n10 >= 5 && n10 <= 9) || (n100 >= 11 && n100 <= 14) ? 5
@ -37,7 +40,9 @@ const PLURAL_RULES = {
},
ca: (n: number) => (n !== 1 ? 6 : 2),
cs: (n: number) => {
const s = String(n).split('.'); const i = Number(s[0]); const v0 = !s[1];
const s = String(n).split('.');
const i = Number(s[0]);
const v0 = !s[1];
return n === 1 && v0 ? 2 : (i >= 2 && i <= 4) && v0 ? 4 : !v0 ? 5 : 6;
},
de: (n: number) => (n !== 1 ? 6 : 2),
@ -48,8 +53,14 @@ const PLURAL_RULES = {
id: () => 0,
it: (n: number) => (n !== 1 ? 6 : 2),
hr: (n: number) => {
const s = String(n).split('.'); const i = s[0]; const f = s[1] || ''; const v0 = !s[1]; const i10 = Number(i.slice(-1));
const i100 = Number(i.slice(-2)); const f10 = Number(f.slice(-1)); const f100 = Number(f.slice(-2));
const s = String(n).split('.');
const i = s[0];
const f = s[1] || '';
const v0 = !s[1];
const i10 = Number(i.slice(-1));
const i100 = Number(i.slice(-2));
const f10 = Number(f.slice(-1));
const f100 = Number(f.slice(-2));
return (v0 && i10 === 1 && i100 !== 11) || (f10 === 1 && f100 !== 11) ? 2
: (v0 && (i10 >= 2 && i10 <= 4) && (i100 < 12 || i100 > 14)) || ((f10 >= 2 && f10 <= 4) && (f100 < 12 || f100 > 14)) ? 4
: 6;
@ -63,12 +74,20 @@ const PLURAL_RULES = {
'pt-br': (n: number) => (n > 1 ? 6 : 2),
ru: (n: number) => (n % 10 === 1 && n % 100 !== 11 ? 2 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 4 : 5),
sk: (n: number) => {
const s = String(n).split('.'); const i = Number(s[0]); const v0 = !s[1];
const s = String(n).split('.');
const i = Number(s[0]);
const v0 = !s[1];
return n === 1 && v0 ? 2 : (i >= 2 && i <= 4) && v0 ? 4 : !v0 ? 5 : 6;
},
sr: (n: number) => {
const s = String(n).split('.'); const i = s[0]; const f = s[1] || ''; const v0 = !s[1]; const i10 = Number(i.slice(-1));
const i100 = Number(i.slice(-2)); const f10 = Number(f.slice(-1)); const f100 = Number(f.slice(-2));
const s = String(n).split('.');
const i = s[0];
const f = s[1] || '';
const v0 = !s[1];
const i10 = Number(i.slice(-1));
const i100 = Number(i.slice(-2));
const f10 = Number(f.slice(-1));
const f100 = Number(f.slice(-2));
return (v0 && i10 === 1 && i100 !== 11) || (f10 === 1 && f100 !== 11) ? 2
: (v0 && (i10 >= 2 && i10 <= 4) && (i100 < 12 || i100 > 14)) || ((f10 >= 2 && f10 <= 4) && (f100 < 12 || f100 > 14)) ? 4
: 6;
@ -224,7 +243,7 @@ async function fetchRemoteString(
});
if (remote?.length) {
await cacheApi.save(LANG_CACHE_NAME, `${remoteLangPack}_${langCode}_${key}`, remote[0]);
await cacheApi.save(LANG_CACHE_NAME, `${remoteLangPack}_${langCode}_${key}`, remote[0] || '');
return remote[0];
}
@ -257,10 +276,16 @@ function processTranslation(
const preferredPluralOption = typeof value === 'number' || pluralValue !== undefined
? getPluralOption(pluralValue ?? value)
: 'value';
const template = langString ? (
langString[preferredPluralOption] || langString.otherValue || langString.value
) : undefined;
if (!template || !template.trim()) {
const template = typeof langString === 'string'
? langString
: preferredPluralOption === 'value'
// Support cached older `langString` interface
? (typeof langString === 'object' ? (langString as any).value : langString)
: typeof langString === 'object'
? langString[preferredPluralOption] || langString.otherValue
: undefined;
if (!template?.trim()) {
const parts = key.split('.');
return parts[parts.length - 1];