Bump dependencies (#6714)

This commit is contained in:
zubiden 2026-04-14 14:35:51 +02:00 committed by Alexander Zinchuk
parent 0d9d4bbebf
commit 319c0396a4
150 changed files with 3016 additions and 4593 deletions

View File

@ -1,26 +1,23 @@
import eslint from '@eslint/js'; import eslint from '@eslint/js';
import eslintReact from '@eslint-react/eslint-plugin';
import stylisticJs from '@stylistic/eslint-plugin'; import stylisticJs from '@stylistic/eslint-plugin';
import { globalIgnores } from 'eslint/config'; import { defineConfig, globalIgnores } from 'eslint/config';
import importsPlugin from 'eslint-plugin-import'; import { importX } from 'eslint-plugin-import-x';
import jestPlugin from 'eslint-plugin-jest'; import jestPlugin from 'eslint-plugin-jest';
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
import noNullPlugin from 'eslint-plugin-no-null'; import noNullPlugin from 'eslint-plugin-no-null';
import reactPlugin from 'eslint-plugin-react';
import reactHooksStaticDeps from 'eslint-plugin-react-hooks-static-deps'; import reactHooksStaticDeps from 'eslint-plugin-react-hooks-static-deps';
import reactXPlugin from 'eslint-plugin-react-x';
import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort'; import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort';
import ttMultitabPlugin from 'eslint-plugin-tt-multitab'; import ttMultitabPlugin from 'eslint-plugin-tt-multitab';
import unusedImports from 'eslint-plugin-unused-imports'; import unusedImports from 'eslint-plugin-unused-imports';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config( export default defineConfig(
eslint.configs.recommended, eslint.configs.recommended,
tseslint.configs.recommendedTypeChecked, tseslint.configs.recommendedTypeChecked,
tseslint.configs.stylistic, tseslint.configs.stylistic,
reactPlugin.configs.flat.recommended, eslintReact.configs['recommended-typescript'],
reactPlugin.configs.flat['jsx-runtime'], importX.flatConfigs.recommended,
reactXPlugin.configs['recommended-type-checked'], importX.flatConfigs.typescript,
jsxA11yPlugin.flatConfigs.recommended,
ttMultitabPlugin.configs.recommended, ttMultitabPlugin.configs.recommended,
stylisticJs.configs.customize({ stylisticJs.configs.customize({
semi: true, semi: true,
@ -68,6 +65,16 @@ export default tseslint.config(
'no-prototype-builtins': 'off', 'no-prototype-builtins': 'off',
'no-undef': 'off', 'no-undef': 'off',
'no-unused-vars': 'off', 'no-unused-vars': 'off',
'@stylistic/comma-dangle': ['error', {
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'always-multiline',
enums: 'always-multiline',
tuples: 'always-multiline',
generics: 'ignore',
}],
'@stylistic/multiline-ternary': 'off', '@stylistic/multiline-ternary': 'off',
'@stylistic/operator-linebreak': 'off', '@stylistic/operator-linebreak': 'off',
'@stylistic/max-len': ['error', { '@stylistic/max-len': ['error', {
@ -170,6 +177,8 @@ export default tseslint.config(
'@typescript-eslint/prefer-promise-reject-errors': 'off', '@typescript-eslint/prefer-promise-reject-errors': 'off',
'@typescript-eslint/unbound-method': 'off', '@typescript-eslint/unbound-method': 'off',
'unused-imports/no-unused-imports': 'error', 'unused-imports/no-unused-imports': 'error',
'import-x/namespace': ['error', { allowComputed: true }],
'import-x/no-named-as-default-member': 'off',
'react-hooks/exhaustive-deps': 'off', 'react-hooks/exhaustive-deps': 'off',
'react-hooks-static-deps/exhaustive-deps': [ 'react-hooks-static-deps/exhaustive-deps': [
'error', 'error',
@ -185,40 +194,24 @@ export default tseslint.config(
}, },
}, },
], ],
'react/prop-types': 'off', '@eslint-react/exhaustive-deps': 'off',
'react/no-unknown-property': 'off', '@eslint-react/set-state-in-effect': 'off',
'react/display-name': 'off', '@eslint-react/unsupported-syntax': 'off',
'react/jsx-key': 'off', '@eslint-react/no-clone-element': 'off',
'react/jsx-curly-spacing': [ '@eslint-react/component-hook-factories': 'off',
'error', '@eslint-react/no-use-context': 'off',
{ '@eslint-react/no-context-provider': 'off',
when: 'never', '@eslint-react/no-array-index-key': 'off',
attributes: true, '@eslint-react/web-api/no-leaked-timeout': 'off',
children: true, '@eslint-react/no-missing-key': 'off',
allowMultiline: true, '@eslint-react/no-nested-component-definitions': 'off',
}, '@eslint-react/no-unused-props': 'off',
], '@eslint-react/no-leaked-conditional-rendering': 'error',
'react-x/no-use-context': 'off',
'react-x/no-context-provider': 'off',
'react-x/no-array-index-key': 'off',
'react-x/no-missing-key': 'off',
'react-x/no-nested-component-definitions': 'off',
'react-x/no-unused-props': 'off',
'react-x/no-leaked-conditional-rendering': 'error',
'jsx-a11y/click-events-have-key-events': 'off',
'jsx-a11y/mouse-events-have-key-events': 'off',
'jsx-a11y/no-static-element-interactions': 'off',
'jsx-a11y/label-has-associated-control': 'off',
'jsx-a11y/anchor-is-valid': 'off',
'jsx-a11y/no-noninteractive-element-to-interactive-role': 'off',
'jsx-a11y/media-has-caption': 'off',
}, },
plugins: { plugins: {
'no-null': noNullPlugin, 'no-null': noNullPlugin,
'simple-import-sort': simpleImportSortPlugin, 'simple-import-sort': simpleImportSortPlugin,
import: importsPlugin,
'unused-imports': unusedImports, 'unused-imports': unusedImports,
react: reactPlugin,
'react-hooks-static-deps': reactHooksStaticDeps, 'react-hooks-static-deps': reactHooksStaticDeps,
jest: jestPlugin, jest: jestPlugin,
'tt-multitab': ttMultitabPlugin, 'tt-multitab': ttMultitabPlugin,

5656
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -42,100 +42,100 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.29.0", "@babel/core": "^7.29.0",
"@babel/preset-env": "^7.29.0", "@babel/preset-env": "^7.29.2",
"@babel/preset-react": "^7.28.5", "@babel/preset-react": "^7.28.5",
"@babel/preset-typescript": "^7.28.5", "@babel/preset-typescript": "^7.28.5",
"@babel/register": "^7.28.6", "@babel/register": "^7.28.6",
"@eslint/js": "^9.39.2", "@eslint-react/eslint-plugin": "^3.0.0",
"@eslint/js": "^10.0.1",
"@glen/jest-raw-loader": "^2.0.0", "@glen/jest-raw-loader": "^2.0.0",
"@mytonwallet/stylelint-whole-pixel": "github:mytonwallet-org/stylelint-whole-pixel#18b0b8c", "@mytonwallet/stylelint-whole-pixel": "github:mytonwallet-org/stylelint-whole-pixel#18b0b8c",
"@mytonwallet/webpack-watch-file-plugin": "github:mytonwallet-org/webpack-watch-file-plugin#0e36009", "@mytonwallet/webpack-watch-file-plugin": "github:mytonwallet-org/webpack-watch-file-plugin#0e36009",
"@playwright/test": "^1.58.1", "@playwright/test": "^1.58.2",
"@statoscope/cli": "5.29.0", "@statoscope/cli": "5.29.0",
"@statoscope/webpack-plugin": "5.29.0", "@statoscope/webpack-plugin": "5.29.0",
"@stylistic/eslint-plugin": "^5.7.1", "@stylistic/eslint-plugin": "^5.10.0",
"@stylistic/stylelint-config": "^4.0.0", "@stylistic/stylelint-config": "^5.0.0",
"@stylistic/stylelint-plugin": "^5.0.1", "@stylistic/stylelint-plugin": "^5.1.0",
"@tauri-apps/cli": "^2.9.6", "@tauri-apps/cli": "^2.10.1",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@types/dom-chromium-ai": "^0.0.13", "@twbs/fantasticon": "^3.1.0",
"@types/dom-chromium-ai": "^0.0.16",
"@types/dom-view-transitions": "^1.0.6", "@types/dom-view-transitions": "^1.0.6",
"@types/hast": "^3.0.4", "@types/hast": "^3.0.4",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/node": "^25.1.0", "@types/node": "^25.5.0",
"@types/react": "^19.2.10", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@types/webpack": "^5.28.5", "@types/webpack": "^5.28.5",
"@typescript-eslint/eslint-plugin": "^8.54.0", "@typescript-eslint/eslint-plugin": "^8.58.0",
"@typescript-eslint/parser": "^8.54.0", "@typescript-eslint/parser": "^8.58.0",
"@webpack-cli/serve": "^3.0.1", "@webpack-cli/serve": "^3.0.1",
"autoprefixer": "^10.4.24", "autoprefixer": "^10.4.27",
"babel-loader": "^10.0.0", "babel-loader": "^10.1.1",
"babel-plugin-transform-import-meta": "^2.3.3", "babel-plugin-transform-import-meta": "^2.3.3",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"copy-webpack-plugin": "^13.0.1", "copy-webpack-plugin": "^14.0.0",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"css-loader": "^7.1.3", "css-loader": "^7.1.4",
"dotenv": "^17.2.3", "dotenv": "^17.3.1",
"eslint": "^9.39.2", "eslint": "^10.1.0",
"eslint-plugin-import": "^2.32.0", "eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-jest": "^29.12.1", "eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-jest": "^29.15.1",
"eslint-plugin-no-null": "^1.0.2", "eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks-static-deps": "github:zubiden/eslint-plugin-react-hooks-static-deps#160ac4a",
"eslint-plugin-react-hooks-static-deps": "git+https://github.com/zubiden/eslint-plugin-react-hooks-static-deps#c16f35b", "eslint-plugin-react-x": "^3.0.0",
"eslint-plugin-react-x": "^2.8.1",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-tt-multitab": "git+https://github.com/zubiden/eslint-plugin-tt-multitab#137cf50", "eslint-plugin-tt-multitab": "github:zubiden/eslint-plugin-tt-multitab#137cf50",
"eslint-plugin-unused-imports": "^4.3.0", "eslint-plugin-unused-imports": "^4.4.1",
"fake-indexeddb": "^6.2.5", "fake-indexeddb": "^6.2.5",
"@twbs/fantasticon": "^3.1.0",
"git-revision-webpack-plugin": "^5.0.0", "git-revision-webpack-plugin": "^5.0.0",
"gitlog": "^5.1.0", "gitlog": "^5.1.0",
"html-webpack-plugin": "^5.6.6", "html-webpack-plugin": "^5.6.6",
"husky": "^9.1.7", "husky": "^9.1.7",
"jest": "^30.2.0", "jest": "^30.3.0",
"jest-environment-jsdom": "^30.2.0", "jest-environment-jsdom": "^30.3.0",
"lint-staged": "^16.2.7", "lint-staged": "^16.4.0",
"mini-css-extract-plugin": "^2.10.0", "mini-css-extract-plugin": "^2.10.2",
"postcss": "^8.5.6", "postcss": "^8.5.8",
"postcss-load-config": "^6.0.1", "postcss-load-config": "^6.0.1",
"postcss-loader": "^8.2.0", "postcss-loader": "^8.2.1",
"postcss-modules": "^6.0.1", "postcss-modules": "^6.0.1",
"react": "^19.2.4", "react": "^19.2.4",
"sass": "^1.97.3", "sass": "^1.98.0",
"sass-loader": "^16.0.6", "sass-loader": "^16.0.7",
"script-loader": "^0.7.2", "script-loader": "^0.7.2",
"serve": "^14.2.5", "serve": "^14.2.6",
"stylelint": "^17.1.0", "stylelint": "^17.6.0",
"stylelint-config-clean-order": "^8.0.0", "stylelint-config-clean-order": "^8.0.1",
"stylelint-config-recommended-scss": "^17.0.0", "stylelint-config-recommended-scss": "^17.0.0",
"stylelint-declaration-block-no-ignored-properties": "^3.0.0", "stylelint-declaration-block-no-ignored-properties": "^3.0.0",
"stylelint-group-selectors": "^1.0.10", "stylelint-group-selectors": "^1.0.10",
"stylelint-high-performance-animation": "^2.0.0", "stylelint-high-performance-animation": "^2.0.0",
"stylelint-plugin-use-baseline": "^1.2.1", "stylelint-plugin-use-baseline": "^1.4.1",
"telegraph-node": "^1.0.4", "telegraph-node": "^1.0.4",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "5.9.3", "typescript": "^6.0.2",
"typescript-eslint": "^8.54.0", "typescript-eslint": "^8.58.0",
"user-agent-data-types": "^0.4.2", "user-agent-data-types": "^0.4.3",
"webpack": "^5.104.1", "webpack": "^5.105.4",
"webpack-dev-server": "^5.2.3" "webpack-dev-server": "^5.2.3"
}, },
"dependencies": { "dependencies": {
"@cryptography/aes": "^0.1.1", "@cryptography/aes": "^0.1.1",
"@tauri-apps/api": "^2.9.1", "@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-notification": "^2.3.3", "@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.4", "@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-updater": "^2.9.0", "@tauri-apps/plugin-updater": "^2.10.0",
"async-mutex": "^0.5.0", "async-mutex": "^0.5.0",
"emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#30529a2", "emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#30529a2",
"fflate": "^0.8.2", "fflate": "^0.8.2",
"idb-keyval": "^6.2.2", "idb-keyval": "^6.2.2",
"lowlight": "^3.3.0", "lowlight": "^3.3.0",
"music-metadata": "^11.11.1", "music-metadata": "^11.12.3",
"opus-recorder": "github:Ajaxy/opus-recorder#116830a", "opus-recorder": "github:Ajaxy/opus-recorder#116830a",
"os-browserify": "^0.3.0", "os-browserify": "^0.3.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",

View File

@ -2,7 +2,8 @@
declare const process: NodeJS.Process; declare const process: NodeJS.Process;
declare module '*.module.scss'; declare module '*.css';
declare module '*.scss';
declare const APP_VERSION: string; declare const APP_VERSION: string;
declare const APP_REVISION: string; declare const APP_REVISION: string;

View File

@ -26,8 +26,7 @@ import { buildApiChatFromPreview } from '../apiBuilders/chats';
import { addDocumentToLocalDb } from '../helpers/localDb'; import { addDocumentToLocalDb } from '../helpers/localDb';
import { buildApiFormattedText } from './common'; import { buildApiFormattedText } from './common';
import { buildApiCurrencyAmount } from './payments'; import { buildApiCurrencyAmount } from './payments';
import { buildApiPeerId } from './peers'; import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
import { getApiChatIdFromMtpPeer } from './peers';
import { buildStickerFromDocument } from './symbols'; import { buildStickerFromDocument } from './symbols';
import { buildApiUser } from './users'; import { buildApiUser } from './users';

View File

@ -15,8 +15,7 @@ import type {
} from '../../types'; } from '../../types';
import { buildCollectionByCallback, omitUndefined } from '../../../util/iteratees'; import { buildCollectionByCallback, omitUndefined } from '../../../util/iteratees';
import { addDocumentToLocalDb } from '../helpers/localDb'; import { addDocumentToLocalDb, addPhotoToLocalDb } from '../helpers/localDb';
import { addPhotoToLocalDb } from '../helpers/localDb';
import { buildApiPhoto, buildPrivacyRules } from './common'; import { buildApiPhoto, buildPrivacyRules } from './common';
import { buildApiDocument, buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent'; import { buildApiDocument, buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
import { buildApiMessage } from './messages'; import { buildApiMessage } from './messages';

View File

@ -3,17 +3,17 @@ import { Api as GramJs } from '../../../lib/gramjs';
import { base64UrlToBuffer, base64UrlToString } from '../../../util/encoding/base64'; import { base64UrlToBuffer, base64UrlToString } from '../../../util/encoding/base64';
export function buildInputPasskeyCredential( export function buildInputPasskeyCredential(
credentialJson: PublicKeyCredentialJSON, credentialJson: RegistrationResponseJSON | AuthenticationResponseJSON,
): GramJs.TypeInputPasskeyCredential { ): GramJs.TypeInputPasskeyCredential {
let response: GramJs.TypeInputPasskeyResponse; let response: GramJs.TypeInputPasskeyResponse;
const clientData = base64UrlToString(credentialJson.response.clientDataJSON); const clientData = base64UrlToString(credentialJson.response.clientDataJSON);
if (credentialJson.response.attestationObject) { if ('attestationObject' in credentialJson.response) {
response = new GramJs.InputPasskeyResponseRegister({ response = new GramJs.InputPasskeyResponseRegister({
clientData: new GramJs.DataJSON({ data: clientData }), clientData: new GramJs.DataJSON({ data: clientData }),
attestationData: base64UrlToBuffer(credentialJson.response.attestationObject), attestationData: base64UrlToBuffer(credentialJson.response.attestationObject),
}); });
} else { } else {
const userHandle = base64UrlToString(credentialJson.response.userHandle); const userHandle = base64UrlToString(credentialJson.response.userHandle!);
response = new GramJs.InputPasskeyResponseLogin({ response = new GramJs.InputPasskeyResponseLogin({
clientData: new GramJs.DataJSON({ data: clientData }), clientData: new GramJs.DataJSON({ data: clientData }),

View File

@ -171,7 +171,7 @@ export function restartAuthWithQr() {
authController.reject(new Error('RESTART_AUTH_WITH_QR')); authController.reject(new Error('RESTART_AUTH_WITH_QR'));
} }
export function restartAuthWithPasskey(credentialJson: PublicKeyCredentialJSON) { export function restartAuthWithPasskey(credentialJson: AuthenticationResponseJSON) {
if (!authController.reject) { if (!authController.reject) {
return; return;
} }

View File

@ -1702,7 +1702,7 @@ export async function searchMessagesGlobal({
const messages = result.messages.map(buildApiMessage).filter(Boolean); const messages = result.messages.map(buildApiMessage).filter(Boolean);
const topics = result.topics.map(buildApiTopicWithState).filter(Boolean); const topics = result.topics.map(buildApiTopicWithState).filter(Boolean);
let totalCount = messages.length; let totalCount;
if (result instanceof GramJs.messages.MessagesSlice || result instanceof GramJs.messages.ChannelMessages) { if (result instanceof GramJs.messages.MessagesSlice || result instanceof GramJs.messages.ChannelMessages) {
totalCount = result.count; totalCount = result.count;
} else { } else {
@ -1761,7 +1761,7 @@ export async function searchPublicPosts({
const messages = result.messages.map(buildApiMessage).filter(Boolean); const messages = result.messages.map(buildApiMessage).filter(Boolean);
const topics = result.topics.map(buildApiTopicWithState).filter(Boolean); const topics = result.topics.map(buildApiTopicWithState).filter(Boolean);
let totalCount = messages.length; let totalCount;
if (result instanceof GramJs.messages.MessagesSlice || result instanceof GramJs.messages.ChannelMessages) { if (result instanceof GramJs.messages.MessagesSlice || result instanceof GramJs.messages.ChannelMessages) {
totalCount = result.count; totalCount = result.count;
} else { } else {

View File

@ -800,7 +800,7 @@ export async function initPasskeyRegistration() {
return undefined; return undefined;
} }
export async function registerPasskey(credentialJson: PublicKeyCredentialJSON) { export async function registerPasskey(credentialJson: RegistrationResponseJSON) {
const result = await invokeRequest(new GramJs.account.RegisterPasskey({ const result = await invokeRequest(new GramJs.account.RegisterPasskey({
credential: buildInputPasskeyCredential(credentialJson), credential: buildInputPasskeyCredential(credentialJson),
})); }));

View File

@ -1,6 +1,5 @@
import type { ApiGroupCall, ApiPhoneCallDiscardReason } from './calls'; import type { ApiGroupCall, ApiPhoneCallDiscardReason } from './calls';
import type { ApiBotApp, ApiFormattedText, ApiPhoto } from './messages'; import type { ApiBotApp, ApiFormattedText, ApiPhoto, ApiTodoItem } from './messages';
import type { ApiTodoItem } from './messages';
import type { ApiStarGiftRegular, ApiStarGiftUnique, ApiTypeCurrencyAmount } from './stars'; import type { ApiStarGiftRegular, ApiStarGiftUnique, ApiTypeCurrencyAmount } from './stars';
interface ActionMediaType { interface ActionMediaType {

View File

@ -127,7 +127,7 @@ const AuthCode = ({
} }
if (STRICTERDOM_ENABLED) { if (STRICTERDOM_ENABLED) {
setTimeout(() => { window.setTimeout(() => {
enableStrict(); enableStrict();
}, QR_CODE_MUTATION_DURATION); }, QR_CODE_MUTATION_DURATION);
} }

View File

@ -52,7 +52,7 @@ const MicrophoneButton: FC<OwnProps & StateProps> = ({
} = getActions(); } = getActions();
const lang = useOldLang(); const lang = useOldLang();
const muteMouseDownState = useRef('up'); const muteMouseDownStateRef = useRef('up');
const [isRequestingToSpeak, setIsRequestingToSpeak] = useState(false); const [isRequestingToSpeak, setIsRequestingToSpeak] = useState(false);
const isConnecting = connectionState !== 'connected'; const isConnecting = connectionState !== 'connected';
@ -113,11 +113,11 @@ const MicrophoneButton: FC<OwnProps & StateProps> = ({
}, REQUEST_TO_SPEAK_THROTTLE); }, REQUEST_TO_SPEAK_THROTTLE);
return; return;
} }
muteMouseDownState.current = 'down'; muteMouseDownStateRef.current = 'down';
if (noAudioStream) { if (noAudioStream) {
setTimeout(() => { setTimeout(() => {
if (muteMouseDownState.current === 'down') { if (muteMouseDownStateRef.current === 'down') {
muteMouseDownState.current = 'hold'; muteMouseDownStateRef.current = 'hold';
toggleMute(); toggleMute();
} }
}, HOLD_TO_SPEAK_TIME); }, HOLD_TO_SPEAK_TIME);
@ -129,7 +129,7 @@ const MicrophoneButton: FC<OwnProps & StateProps> = ({
return; return;
} }
toggleMute(); toggleMute();
muteMouseDownState.current = 'up'; muteMouseDownStateRef.current = 'up';
}, [shouldRaiseHand, toggleMute]); }, [shouldRaiseHand, toggleMute]);
return ( return (

View File

@ -218,9 +218,13 @@ const PhoneCall = ({
useEffect(() => { useEffect(() => {
if (phoneCall?.state === 'discarded') { if (phoneCall?.state === 'discarded') {
setTimeout(hangUp, 250); const timeout = setTimeout(hangUp, 250);
return () => {
clearTimeout(timeout);
};
} }
}, [hangUp, phoneCall?.reason, phoneCall?.state]); return undefined;
}, [phoneCall?.reason, phoneCall?.state]);
return ( return (
<Modal <Modal

View File

@ -58,11 +58,9 @@ function AnimatedIconWithPreview(props: OwnProps) {
style={buildStyle(size !== undefined && `width: ${size}px; height: ${size}px;`)} style={buildStyle(size !== undefined && `width: ${size}px; height: ${size}px;`)}
> >
{thumbDataUri && !isAnimationReady && ( {thumbDataUri && !isAnimationReady && (
// eslint-disable-next-line jsx-a11y/alt-text
<img src={thumbDataUri} className={buildClassName(styles.preview, thumbClassNames)} draggable={false} /> <img src={thumbDataUri} className={buildClassName(styles.preview, thumbClassNames)} draggable={false} />
)} )}
{previewUrl && !isAnimationReady && ( {previewUrl && !isAnimationReady && (
// eslint-disable-next-line jsx-a11y/alt-text
<img <img
src={previewUrl} src={previewUrl}
className={buildClassName(styles.preview, previewClassNames)} className={buildClassName(styles.preview, previewClassNames)}

View File

@ -97,7 +97,7 @@ const AnimatedSticker = ({
const [animation, setAnimation] = useState<RLottieInstance>(); const [animation, setAnimation] = useState<RLottieInstance>();
const animationRef = useRef<RLottieInstance>(); const animationRef = useRef<RLottieInstance>();
const isFirstRender = useRef(true); const isFirstRenderRef = useRef(true);
const shouldUseColorFilter = !sharedCanvas && color; const shouldUseColorFilter = !sharedCanvas && color;
const colorFilter = useColorFilter(shouldUseColorFilter ? color : undefined); const colorFilter = useColorFilter(shouldUseColorFilter ? color : undefined);
@ -106,7 +106,7 @@ const AnimatedSticker = ({
const playRef = useStateRef(play); const playRef = useStateRef(play);
const playSegmentRef = useStateRef(playSegment); const playSegmentRef = useStateRef(playSegment);
const rgbColor = useRef<[number, number, number] | undefined>(); const rgbColorRef = useRef<[number, number, number] | undefined>();
const shouldForceOnHeavyAnimation = forceAlways || forceOnHeavyAnimation; const shouldForceOnHeavyAnimation = forceAlways || forceOnHeavyAnimation;
// Delay initialization until heavy animation ends // Delay initialization until heavy animation ends
@ -121,9 +121,9 @@ const AnimatedSticker = ({
useSyncEffect(() => { useSyncEffect(() => {
if (color && !shouldUseColorFilter) { if (color && !shouldUseColorFilter) {
const { r, g, b } = hex2rgbaObj(color); const { r, g, b } = hex2rgbaObj(color);
rgbColor.current = [r, g, b]; rgbColorRef.current = [r, g, b];
} else { } else {
rgbColor.current = undefined; rgbColorRef.current = undefined;
} }
}, [color, shouldUseColorFilter]); }, [color, shouldUseColorFilter]);
@ -160,7 +160,7 @@ const AnimatedSticker = ({
coords: sharedCanvasCoords, coords: sharedCanvasCoords,
}, },
viewId, viewId,
rgbColor.current, rgbColorRef.current,
onLoad, onLoad,
onEnded, onEnded,
onLoop, onLoop,
@ -192,7 +192,7 @@ const AnimatedSticker = ({
useSharedIntersectionObserver(sharedCanvas, throttledInit); useSharedIntersectionObserver(sharedCanvas, throttledInit);
useEffect(() => { useEffect(() => {
animation?.setColor(rgbColor.current); animation?.setColor(rgbColorRef.current);
}, [color, animation]); }, [color, animation]);
useEffect(() => { useEffect(() => {
@ -261,8 +261,8 @@ const AnimatedSticker = ({
useEffect(() => { useEffect(() => {
if (animation) { if (animation) {
if (isFirstRender.current) { if (isFirstRenderRef.current) {
isFirstRender.current = false; isFirstRenderRef.current = false;
} else if (tgsUrl) { } else if (tgsUrl) {
animation.changeData(tgsUrl); animation.changeData(tgsUrl);
playAnimation(); playAnimation();

View File

@ -133,7 +133,7 @@ const Audio = ({
const media = (voice || video || audio)!; const media = (voice || video || audio)!;
const mediaSource = (voice || video); const mediaSource = (voice || video);
const isVoice = Boolean(voice || video); const isVoice = Boolean(voice || video);
const isSeeking = useRef<boolean>(false); const isSeekingRef = useRef<boolean>(false);
const seekerRef = useRef<HTMLDivElement>(); const seekerRef = useRef<HTMLDivElement>();
const oldLang = useOldLang(); const oldLang = useOldLang();
const lang = useLang(); const lang = useLang();
@ -266,7 +266,7 @@ const Audio = ({
}); });
const handleSeek = useLastCallback((e: MouseEvent | TouchEvent) => { const handleSeek = useLastCallback((e: MouseEvent | TouchEvent) => {
if (isSeeking.current && seekerRef.current) { if (isSeekingRef.current && seekerRef.current) {
const { width, left } = seekerRef.current.getBoundingClientRect(); const { width, left } = seekerRef.current.getBoundingClientRect();
const clientX = e instanceof MouseEvent ? e.clientX : e.targetTouches[0].clientX; const clientX = e instanceof MouseEvent ? e.clientX : e.targetTouches[0].clientX;
e.stopPropagation(); // Prevent Slide-to-Reply activation e.stopPropagation(); // Prevent Slide-to-Reply activation
@ -277,12 +277,12 @@ const Audio = ({
const handleStartSeek = useLastCallback((e: MouseEvent | TouchEvent) => { const handleStartSeek = useLastCallback((e: MouseEvent | TouchEvent) => {
if (e instanceof MouseEvent && e.button === 2) return; if (e instanceof MouseEvent && e.button === 2) return;
isSeeking.current = true; isSeekingRef.current = true;
handleSeek(e); handleSeek(e);
}); });
const handleStopSeek = useLastCallback(() => { const handleStopSeek = useLastCallback(() => {
isSeeking.current = false; isSeekingRef.current = false;
}); });
const handleDateClick = useLastCallback(() => { const handleDateClick = useLastCallback(() => {

View File

@ -92,6 +92,7 @@ const CalendarModal: FC<OwnProps & StateProps> = ({
const oldLang = useOldLang(); const oldLang = useOldLang();
const lang = useLang(); const lang = useLang();
// eslint-disable-next-line @eslint-react/purity
const now = new Date(); const now = new Date();
const { const {

View File

@ -499,7 +499,7 @@ const Composer = ({
const [getHtml, setHtml] = useSignal(''); const [getHtml, setHtml] = useSignal('');
const [isMounted, setIsMounted] = useState(false); const [isMounted, setIsMounted] = useState(false);
const getSelectionRange = useGetSelectionRange(editableInputCssSelector); const getSelectionRange = useGetSelectionRange(editableInputCssSelector);
const lastMessageSendTimeSeconds = useRef<number>(); const lastMessageSendTimeSecondsRef = useRef<number>();
const prevDropAreaState = usePreviousDeprecated(dropAreaState); const prevDropAreaState = usePreviousDeprecated(dropAreaState);
const { width: windowWidth } = windowSize.get(); const { width: windowWidth } = windowSize.get();
const forceUpdate = useForceUpdate(); const forceUpdate = useForceUpdate();
@ -526,7 +526,7 @@ const Composer = ({
useEffect(processMessageInputForCustomEmoji, [getHtml]); useEffect(processMessageInputForCustomEmoji, [getHtml]);
const customEmojiNotificationNumber = useRef(0); const customEmojiNotificationNumberRef = useRef(0);
const [requestCalendar, calendar] = useSchedule( const [requestCalendar, calendar] = useSchedule(
isInMessageList && canScheduleUntilOnline, isInMessageList && canScheduleUntilOnline,
@ -544,7 +544,7 @@ const Composer = ({
}, [isInMessageList, storyId]); }, [isInMessageList, storyId]);
useEffect(() => { useEffect(() => {
lastMessageSendTimeSeconds.current = undefined; lastMessageSendTimeSecondsRef.current = undefined;
}, [chatId]); }, [chatId]);
useEffect(() => { useEffect(() => {
@ -708,7 +708,7 @@ const Composer = ({
shouldSendInHighQuality: attachmentSettings.shouldSendInHighQuality, shouldSendInHighQuality: attachmentSettings.shouldSendInHighQuality,
}); });
const mediaEditRequestRef = useRef(Date.now()); const mediaEditRequestRef = useRef<number>();
useEffect(() => { useEffect(() => {
if (!shouldOpenMessageMediaEditor) return; if (!shouldOpenMessageMediaEditor) return;
const targetMessage = editingMessage || replyToMessage; const targetMessage = editingMessage || replyToMessage;
@ -945,7 +945,7 @@ const Composer = ({
&& !isForwarding && !isReplying && !draft?.suggestedPostInfo; && !isForwarding && !isReplying && !draft?.suggestedPostInfo;
const showCustomEmojiPremiumNotification = useLastCallback(() => { const showCustomEmojiPremiumNotification = useLastCallback(() => {
const notificationNumber = customEmojiNotificationNumber.current; const notificationNumber = customEmojiNotificationNumberRef.current;
if (!notificationNumber) { if (!notificationNumber) {
showNotification({ showNotification({
message: oldLang('UnlockPremiumEmojiHint'), message: oldLang('UnlockPremiumEmojiHint'),
@ -965,7 +965,7 @@ const Composer = ({
actionText: oldLang('Open'), actionText: oldLang('Open'),
}); });
} }
customEmojiNotificationNumber.current = Number(!notificationNumber); customEmojiNotificationNumberRef.current = Number(!notificationNumber);
}); });
const mainButtonState = useDerivedState(() => { const mainButtonState = useDerivedState(() => {
@ -1073,12 +1073,12 @@ const Composer = ({
const messageInput = document.querySelector<HTMLDivElement>(editableInputCssSelector); const messageInput = document.querySelector<HTMLDivElement>(editableInputCssSelector);
const nowSeconds = getServerTime(); const nowSeconds = getServerTime();
const secondsSinceLastMessage = lastMessageSendTimeSeconds.current const secondsSinceLastMessage = lastMessageSendTimeSecondsRef.current
&& Math.floor(nowSeconds - lastMessageSendTimeSeconds.current); && Math.floor(nowSeconds - lastMessageSendTimeSecondsRef.current);
const nextSendDateNotReached = slowMode.nextSendDate && slowMode.nextSendDate > nowSeconds; const nextSendDateNotReached = slowMode.nextSendDate && slowMode.nextSendDate > nowSeconds;
if ( if (
(secondsSinceLastMessage && secondsSinceLastMessage < slowMode.seconds) (secondsSinceLastMessage !== undefined && secondsSinceLastMessage < slowMode.seconds)
|| nextSendDateNotReached || nextSendDateNotReached
) { ) {
const secondsRemaining = nextSendDateNotReached const secondsRemaining = nextSendDateNotReached
@ -1168,7 +1168,7 @@ const Composer = ({
}); });
} }
lastMessageSendTimeSeconds.current = getServerTime(); lastMessageSendTimeSecondsRef.current = getServerTime();
clearDraft({ chatId, threadId, isLocalOnly: true }); clearDraft({ chatId, threadId, isLocalOnly: true });
@ -1277,7 +1277,7 @@ const Composer = ({
}); });
} }
lastMessageSendTimeSeconds.current = getServerTime(); lastMessageSendTimeSecondsRef.current = getServerTime();
clearDraft({ clearDraft({
chatId, threadId, isLocalOnly: true, shouldKeepReply: isForwarding, chatId, threadId, isLocalOnly: true, shouldKeepReply: isForwarding,
}); });
@ -1743,15 +1743,21 @@ const Composer = ({
}, [isRightColumnShown, closeSymbolMenu, isMobile]); }, [isRightColumnShown, closeSymbolMenu, isMobile]);
useEffect(() => { useEffect(() => {
if (!isReady) return; if (!isReady) return undefined;
let timeout: number | undefined;
if (isSelectModeActive) { if (isSelectModeActive) {
disableHover(); disableHover();
} else { } else {
setTimeout(() => { timeout = window.setTimeout(() => {
enableHover(); enableHover();
}, SELECT_MODE_TRANSITION_MS); }, SELECT_MODE_TRANSITION_MS);
} }
return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, [isSelectModeActive, enableHover, disableHover, isReady]); }, [isSelectModeActive, enableHover, disableHover, isReady]);
const hasText = useDerivedState(() => Boolean(getHtml()), [getHtml]); const hasText = useDerivedState(() => Boolean(getHtml()), [getHtml]);
@ -1887,13 +1893,6 @@ const Composer = ({
} }
}); });
const scheduledDefaultDate = new Date();
scheduledDefaultDate.setSeconds(0);
scheduledDefaultDate.setMilliseconds(0);
const scheduledMaxDate = new Date();
scheduledMaxDate.setFullYear(scheduledMaxDate.getFullYear() + 1);
let sendButtonAriaLabel = 'SendMessage'; let sendButtonAriaLabel = 'SendMessage';
switch (mainButtonState) { switch (mainButtonState) {
case MainButtonState.Forward: case MainButtonState.Forward:

View File

@ -1,5 +1,4 @@
import type { FC } from '../../../lib/teact/teact'; import { memo, useState } from '../../../lib/teact/teact';
import { memo, useCallback, useState } from '../../../lib/teact/teact';
import { ApiMessageEntityTypes } from '../../../api/types'; import { ApiMessageEntityTypes } from '../../../api/types';
@ -7,6 +6,7 @@ import buildClassName from '../../../util/buildClassName';
import { getPrettyCodeLanguageName } from '../../../util/prettyCodeLanguageNames'; import { getPrettyCodeLanguageName } from '../../../util/prettyCodeLanguageNames';
import useAsync from '../../../hooks/useAsync'; import useAsync from '../../../hooks/useAsync';
import useLastCallback from '../../../hooks/useLastCallback';
import PeerColorWrapper from '../PeerColorWrapper'; import PeerColorWrapper from '../PeerColorWrapper';
import CodeOverlay from './CodeOverlay'; import CodeOverlay from './CodeOverlay';
@ -19,8 +19,8 @@ export type OwnProps = {
noCopy?: boolean; noCopy?: boolean;
}; };
const CodeBlock: FC<OwnProps> = ({ text, language, noCopy }) => { const CodeBlock = ({ text, language, noCopy }: OwnProps) => {
const [isWordWrap, setWordWrap] = useState(true); const [isWordWrap, setIsWordWrap] = useState(true);
const { result: highlighted } = useAsync(() => { const { result: highlighted } = useAsync(() => {
if (!language) return Promise.resolve(undefined); if (!language) return Promise.resolve(undefined);
@ -28,9 +28,9 @@ const CodeBlock: FC<OwnProps> = ({ text, language, noCopy }) => {
.then((lib) => lib.default(text, language)); .then((lib) => lib.default(text, language));
}, [language, text]); }, [language, text]);
const handleWordWrapToggle = useCallback((wrap) => { const handleWordWrapToggle = useLastCallback((wrap) => {
setWordWrap(wrap); setIsWordWrap(wrap);
}, []); });
const blockClass = buildClassName( const blockClass = buildClassName(
'code-block', 'code-block',

View File

@ -1,5 +1,4 @@
import type { ElementRef } from '@teact'; import { type ElementRef, memo, type TeactNode, useRef } from '@teact';
import { memo, type TeactNode, useRef } from '@teact';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import buildStyle from '../../../util/buildStyle'; import buildStyle from '../../../util/buildStyle';

View File

@ -5,8 +5,7 @@ import type {
ApiEmojiStatusCollectible, ApiEmojiStatusType, ApiSavedStarGift, ApiStarGift, ApiEmojiStatusCollectible, ApiEmojiStatusType, ApiSavedStarGift, ApiStarGift,
} from '../../../api/types'; } from '../../../api/types';
import { DEFAULT_STATUS_ICON_ID, TME_LINK_PREFIX } from '../../../config'; import { DEFAULT_STATUS_ICON_ID, STARS_CURRENCY_CODE, TME_LINK_PREFIX } from '../../../config';
import { STARS_CURRENCY_CODE } from '../../../config';
import { copyTextToClipboard } from '../../../util/clipboard'; import { copyTextToClipboard } from '../../../util/clipboard';
import { formatDateAtTime } from '../../../util/dates/oldDateFormat'; import { formatDateAtTime } from '../../../util/dates/oldDateFormat';
import { getServerTime } from '../../../util/serverTime'; import { getServerTime } from '../../../util/serverTime';

View File

@ -38,8 +38,8 @@ export default function useAnimatedEmoji(
const size = preferredSize || SIZE; const size = preferredSize || SIZE;
const style = buildStyle(`width: ${size}px`, `height: ${size}px`, emoji && !IS_TAURI && 'cursor: pointer'); const style = buildStyle(`width: ${size}px`, `height: ${size}px`, emoji && !IS_TAURI && 'cursor: pointer');
const interactions = useRef<number[] | undefined>(undefined); const interactionsRef = useRef<number[] | undefined>(undefined);
const startedInteractions = useRef<number | undefined>(undefined); const startedInteractionsRef = useRef<number | undefined>(undefined);
const sendInteractionBunch = useLastCallback(() => { const sendInteractionBunch = useLastCallback(() => {
const container = ref.current; const container = ref.current;
@ -49,10 +49,10 @@ export default function useAnimatedEmoji(
chatId: chatId!, chatId: chatId!,
messageId: messageId!, messageId: messageId!,
emoji: emoji!, emoji: emoji!,
interactions: interactions.current!, interactions: interactionsRef.current!,
}); });
startedInteractions.current = undefined; startedInteractionsRef.current = undefined;
interactions.current = undefined; interactionsRef.current = undefined;
}); });
const play = useLastCallback(() => { const play = useLastCallback(() => {
@ -90,14 +90,14 @@ export default function useAnimatedEmoji(
isReversed: !isOwn, isReversed: !isOwn,
}); });
if (!interactions.current) { if (!interactionsRef.current) {
interactions.current = []; interactionsRef.current = [];
startedInteractions.current = performance.now(); startedInteractionsRef.current = performance.now();
setTimeout(sendInteractionBunch, INTERACTION_BUNCH_TIME); setTimeout(sendInteractionBunch, INTERACTION_BUNCH_TIME);
} }
interactions.current.push(startedInteractions.current interactionsRef.current.push(startedInteractionsRef.current
? (performance.now() - startedInteractions.current) / MS_DIVIDER ? (performance.now() - startedInteractionsRef.current) / MS_DIVIDER
: TIME_DEFAULT); : TIME_DEFAULT);
}); });

View File

@ -80,11 +80,16 @@ export function useStickerPickerObservers(
freezeForSet(); freezeForSet();
freezeForShowingItems(); freezeForShowingItems();
} else { } else {
setTimeout(() => { const timeout = window.setTimeout(() => {
unfreezeForShowingItems(); unfreezeForShowingItems();
unfreezeForSet(); unfreezeForSet();
}, SLIDE_TRANSITION_DURATION); }, SLIDE_TRANSITION_DURATION);
return () => {
clearTimeout(timeout);
};
} }
return undefined;
}, [freezeForSet, freezeForShowingItems, isHidden, unfreezeForSet, unfreezeForShowingItems]); }, [freezeForSet, freezeForShowingItems, isHidden, unfreezeForSet, unfreezeForShowingItems]);
const selectStickerSet = useLastCallback((index: number) => { const selectStickerSet = useLastCallback((index: number) => {

View File

@ -1,4 +1,4 @@
import { memo, useCallback } from '@teact'; import { memo } from '@teact';
import { type ApiChatFolder, ApiMessageEntityTypes } from '../../../api/types'; import { type ApiChatFolder, ApiMessageEntityTypes } from '../../../api/types';
@ -33,7 +33,7 @@ const ChatTags = ({
const visibleFolderIds = orderedFolderIds.slice(0, MAX_VISIBLE_TAGS); const visibleFolderIds = orderedFolderIds.slice(0, MAX_VISIBLE_TAGS);
const remainingCount = orderedFolderIds.length - visibleFolderIds.length; const remainingCount = orderedFolderIds.length - visibleFolderIds.length;
const getFolderTitle = useCallback((folder: ApiChatFolder) => { function getFolderTitle(folder: ApiChatFolder) {
let text = folder.title.text; let text = folder.title.text;
let entities = folder.title.entities; let entities = folder.title.entities;
@ -56,7 +56,7 @@ const ChatTags = ({
noCustomEmojiPlayback: folder.noTitleAnimations, noCustomEmojiPlayback: folder.noTitleAnimations,
emojiSize: CUSTOM_EMOJI_SIZE, emojiSize: CUSTOM_EMOJI_SIZE,
}); });
}, [isFoldersSidebarShown]); };
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>

View File

@ -86,18 +86,18 @@ const LeftMain: FC<OwnProps> = ({
transitionClassNames: updateButtonClassNames, transitionClassNames: updateButtonClassNames,
} = useShowTransitionDeprecated(isAppUpdateAvailable || Boolean(tauriUpdate)); } = useShowTransitionDeprecated(isAppUpdateAvailable || Boolean(tauriUpdate));
const isMouseInside = useRef(false); const isMouseInsideRef = useRef(false);
const handleMouseEnter = useLastCallback(() => { const handleMouseEnter = useLastCallback(() => {
if (content !== LeftColumnContent.ChatList) { if (content !== LeftColumnContent.ChatList) {
return; return;
} }
isMouseInside.current = true; isMouseInsideRef.current = true;
setIsNewChatButtonShown(true); setIsNewChatButtonShown(true);
}); });
const handleMouseLeave = useLastCallback(() => { const handleMouseLeave = useLastCallback(() => {
isMouseInside.current = false; isMouseInsideRef.current = false;
if (closeTimeout) { if (closeTimeout) {
clearTimeout(closeTimeout); clearTimeout(closeTimeout);
@ -105,7 +105,7 @@ const LeftMain: FC<OwnProps> = ({
} }
closeTimeout = window.setTimeout(() => { closeTimeout = window.setTimeout(() => {
if (!isMouseInside.current) { if (!isMouseInsideRef.current) {
setIsNewChatButtonShown(false); setIsNewChatButtonShown(false);
} }
}, BUTTON_CLOSE_DELAY_MS); }, BUTTON_CLOSE_DELAY_MS);
@ -148,7 +148,7 @@ const LeftMain: FC<OwnProps> = ({
autoCloseTimeout = window.setTimeout(() => { autoCloseTimeout = window.setTimeout(() => {
setIsNewChatButtonShown(false); setIsNewChatButtonShown(false);
}, BUTTON_CLOSE_DELAY_MS); }, BUTTON_CLOSE_DELAY_MS);
} else if (isMouseInside.current || IS_TOUCH_ENV) { } else if (isMouseInsideRef.current || IS_TOUCH_ENV) {
setIsNewChatButtonShown(true); setIsNewChatButtonShown(true);
} }

View File

@ -25,8 +25,7 @@ import {
import { selectTabState, selectTheme, selectUser } from '../../../global/selectors'; import { selectTabState, selectTheme, selectUser } from '../../../global/selectors';
import { selectPremiumLimit } from '../../../global/selectors/limits'; import { selectPremiumLimit } from '../../../global/selectors/limits';
import { selectSharedSettings } from '../../../global/selectors/sharedState'; import { selectSharedSettings } from '../../../global/selectors/sharedState';
import { IS_MULTIACCOUNT_SUPPORTED } from '../../../util/browser/globalEnvironment'; import { IS_MULTIACCOUNT_SUPPORTED, IS_TAURI } from '../../../util/browser/globalEnvironment';
import { IS_TAURI } from '../../../util/browser/globalEnvironment';
import { getPromptInstall } from '../../../util/installPrompt'; import { getPromptInstall } from '../../../util/installPrompt';
import { switchPermanentWebVersion } from '../../../util/permanentWebVersion'; import { switchPermanentWebVersion } from '../../../util/permanentWebVersion';
import { getSystemTheme } from '../../../util/systemTheme'; import { getSystemTheme } from '../../../util/systemTheme';

View File

@ -36,9 +36,10 @@ const StatusPickerMenu = ({
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { loadFeaturedEmojiStickers } = getActions(); const { loadFeaturedEmojiStickers } = getActions();
const transformOriginX = useRef<number>(0); const transformOriginXRef = useRef<number>(0);
useEffect(() => { useEffect(() => {
transformOriginX.current = statusButtonRef.current!.getBoundingClientRect().right; if (!statusButtonRef.current) return;
transformOriginXRef.current = statusButtonRef.current.getBoundingClientRect().right;
}, [isOpen, statusButtonRef]); }, [isOpen, statusButtonRef]);
useEffect(() => { useEffect(() => {
@ -60,7 +61,7 @@ const StatusPickerMenu = ({
positionX="left" positionX="left"
bubbleClassName={styles.menuContent} bubbleClassName={styles.menuContent}
onClose={onClose} onClose={onClose}
transformOriginX={transformOriginX.current} transformOriginX={transformOriginXRef.current}
> >
<CustomEmojiPicker <CustomEmojiPicker
idPrefix="status-emoji-set-" idPrefix="status-emoji-set-"

View File

@ -1,6 +1,5 @@
import { memo, useCallback, useMemo } from '../../../lib/teact/teact'; import { memo, useCallback, useMemo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, getGlobal, withGlobal } from '../../../global';
import { getGlobal } from '../../../global';
import type { ApiMessage, ApiSearchPostsFlood } from '../../../api/types'; import type { ApiMessage, ApiSearchPostsFlood } from '../../../api/types';
import type { AnimationLevel } from '../../../types'; import type { AnimationLevel } from '../../../types';

View File

@ -260,7 +260,6 @@ const SettingsHeader: FC<OwnProps> = ({
default: default:
return ( return (
<div className="settings-main-header"> <div className="settings-main-header">
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
<h3 onClick={handleMultiClick}> <h3 onClick={handleMultiClick}>
{oldLang('SETTINGS')} {oldLang('SETTINGS')}
</h3> </h3>

View File

@ -10,7 +10,6 @@ import type { ApiSticker } from '../../../../api/types';
import { selectAnimatedEmoji, selectTabState } from '../../../../global/selectors'; import { selectAnimatedEmoji, selectTabState } from '../../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment';
import useAppLayout from '../../../../hooks/useAppLayout';
import useHistoryBack from '../../../../hooks/useHistoryBack'; import useHistoryBack from '../../../../hooks/useHistoryBack';
import useOldLang from '../../../../hooks/useOldLang'; import useOldLang from '../../../../hooks/useOldLang';
@ -47,18 +46,14 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
recoveryEmail, recoveryEmail,
}) => { }) => {
const inputRef = useRef<HTMLInputElement>(); const inputRef = useRef<HTMLInputElement>();
const { isMobile } = useAppLayout();
const focusDelayTimeoutMs = isMobile ? 550 : 400;
const [value, setValue] = useState<string>(''); const [value, setValue] = useState<string>('');
useEffect(() => { useEffect(() => {
if (!IS_TOUCH_ENV) { if (!IS_TOUCH_ENV && isActive) {
setTimeout(() => { inputRef.current!.focus();
inputRef.current!.focus();
}, focusDelayTimeoutMs);
} }
}, [focusDelayTimeoutMs]); }, [isActive]);
const lang = useOldLang(); const lang = useOldLang();

View File

@ -11,7 +11,6 @@ import { selectAnimatedEmoji } from '../../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment';
import renderText from '../../../common/helpers/renderText'; import renderText from '../../../common/helpers/renderText';
import useAppLayout from '../../../../hooks/useAppLayout';
import useFlag from '../../../../hooks/useFlag'; import useFlag from '../../../../hooks/useFlag';
import useHistoryBack from '../../../../hooks/useHistoryBack'; import useHistoryBack from '../../../../hooks/useHistoryBack';
import useOldLang from '../../../../hooks/useOldLang'; import useOldLang from '../../../../hooks/useOldLang';
@ -53,19 +52,15 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
onReset, onReset,
}) => { }) => {
const inputRef = useRef<HTMLInputElement>(); const inputRef = useRef<HTMLInputElement>();
const { isMobile } = useAppLayout();
const focusDelayTimeoutMs = isMobile ? 550 : 400;
const [value, setValue] = useState<string>(''); const [value, setValue] = useState<string>('');
const [isConfirmShown, markIsConfirmShown, unmarkIsConfirmShown] = useFlag(false); const [isConfirmShown, markIsConfirmShown, unmarkIsConfirmShown] = useFlag(false);
useEffect(() => { useEffect(() => {
if (!IS_TOUCH_ENV) { if (!IS_TOUCH_ENV && isActive) {
setTimeout(() => { inputRef.current!.focus();
inputRef.current!.focus();
}, focusDelayTimeoutMs);
} }
}, [focusDelayTimeoutMs]); }, [isActive]);
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (error && clearError) { if (error && clearError) {

View File

@ -81,11 +81,12 @@ const DownloadManager = ({
const url = new URL(result, window.document.baseURI); const url = new URL(result, window.document.baseURI);
url.searchParams.set('filename', encodeURIComponent(filename)); url.searchParams.set('filename', encodeURIComponent(filename));
const downloadWindow = window.open(url.toString()); const downloadWindow = window.open(url.toString());
// eslint-disable-next-line @eslint-react/web-api/no-leaked-event-listener
downloadWindow?.addEventListener('beforeunload', () => { downloadWindow?.addEventListener('beforeunload', () => {
showNotification({ showNotification({
message: 'Download started. Please, do not close the app before it is finished.', message: 'Download started. Please, do not close the app before it is finished.',
}); });
}); }, { once: true });
} else if (result) { } else if (result) {
download(result, filename); download(result, filename);
} }

View File

@ -81,7 +81,7 @@ const GameModal: FC<OwnProps & StateProps> = ({ openedGame, gameTitle, canPost }
onLoad={handleLoad} onLoad={handleLoad}
src={url} src={url}
title={lang('AttachGame')} title={lang('AttachGame')}
sandbox="allow-scripts allow-same-origin allow-orientation-lock" sandbox="allow-scripts allow-orientation-lock"
allow="fullscreen" allow="fullscreen"
/> />
)} )}

View File

@ -57,6 +57,7 @@ const LockScreen: FC<OwnProps & StateProps> = ({
const [isSignOutDialogOpen, openSignOutConfirmation, closeSignOutConfirmation] = useFlag(false); const [isSignOutDialogOpen, openSignOutConfirmation, closeSignOutConfirmation] = useFlag(false);
const { shouldRender } = useShowTransitionDeprecated(isLocked); const { shouldRender } = useShowTransitionDeprecated(isLocked);
// eslint-disable-next-line @eslint-react/purity
useTimeout(resetInvalidUnlockAttempts, timeoutUntil ? timeoutUntil - Date.now() : undefined); useTimeout(resetInvalidUnlockAttempts, timeoutUntil ? timeoutUntil - Date.now() : undefined);
const handleClearError = useCallback(() => { const handleClearError = useCallback(() => {

View File

@ -271,7 +271,7 @@ const Main = ({
if (DEBUG && !DEBUG_isLogged) { if (DEBUG && !DEBUG_isLogged) {
DEBUG_isLogged = true; DEBUG_isLogged = true;
// eslint-disable-next-line no-console // eslint-disable-next-line no-console, @eslint-react/purity
console.log('>>> RENDER MAIN'); console.log('>>> RENDER MAIN');
} }

View File

@ -28,8 +28,6 @@ import TextArea from '../ui/TextArea';
import './NewContactModal.scss'; import './NewContactModal.scss';
const ANIMATION_DURATION = 200;
export type OwnProps = { export type OwnProps = {
isOpen: boolean; isOpen: boolean;
userId?: string; userId?: string;
@ -86,9 +84,7 @@ const NewContactModal: FC<OwnProps & StateProps> = ({
useEffect(() => { useEffect(() => {
if (!IS_TOUCH_ENV && isShown) { if (!IS_TOUCH_ENV && isShown) {
setTimeout(() => { inputRef.current?.focus();
inputRef.current?.focus();
}, ANIMATION_DURATION);
} }
}, [isShown]); }, [isShown]);

View File

@ -159,9 +159,9 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
} }
const [customExpireDate, setCustomExpireDate] = useState<number>(() => Date.now() + DEFAULT_CUSTOM_EXPIRE_DATE); const [customExpireDate, setCustomExpireDate] = useState<number>(() => Date.now() + DEFAULT_CUSTOM_EXPIRE_DATE);
const [isHeaderHidden, setHeaderHidden] = useState(true); const [isHeaderHidden, setIsHeaderHidden] = useState(true);
const [selectedRandomUserCount, setSelectedRandomUserCount] = useState<number>(DEFAULT_BOOST_COUNT); const [selectedRandomUserCount, setSelectedRandomUserCount] = useState<number>(DEFAULT_BOOST_COUNT);
const [selectedGiveawayOption, setGiveawayOption] = useState<ApiGiveawayType>(TYPE_OPTIONS[0].value); const [selectedGiveawayOption, setSelectedGiveawayOption] = useState<ApiGiveawayType>(TYPE_OPTIONS[0].value);
const [selectedStarOption, setSelectedStarOption] = useState<ApiStarGiveawayOption | undefined>(); const [selectedStarOption, setSelectedStarOption] = useState<ApiStarGiveawayOption | undefined>();
const [selectedSubscriberOption, setSelectedSubscriberOption] = useState<SubscribersType>('all'); const [selectedSubscriberOption, setSelectedSubscriberOption] = useState<SubscribersType>('all');
const [selectedMonthOption, setSelectedMonthOption] = useState<number | undefined>(); const [selectedMonthOption, setSelectedMonthOption] = useState<number | undefined>();
@ -390,7 +390,7 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
function handleScroll(e: React.UIEvent<HTMLDivElement>) { function handleScroll(e: React.UIEvent<HTMLDivElement>) {
const { scrollTop } = e.currentTarget; const { scrollTop } = e.currentTarget;
setHeaderHidden(scrollTop <= 150); setIsHeaderHidden(scrollTop <= 150);
} }
const handleChangeSubscriberOption = useLastCallback((value) => { const handleChangeSubscriberOption = useLastCallback((value) => {
@ -398,7 +398,7 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
}); });
const handleChangeTypeOption = useLastCallback((value: ApiGiveawayType) => { const handleChangeTypeOption = useLastCallback((value: ApiGiveawayType) => {
setGiveawayOption(value); setSelectedGiveawayOption(value);
setSelectedUserIds([]); setSelectedUserIds([]);
setSelectedRandomUserCount(DEFAULT_BOOST_COUNT); setSelectedRandomUserCount(DEFAULT_BOOST_COUNT);
}); });
@ -415,7 +415,7 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
const handleSelectedUserIdsChange = useLastCallback((newSelectedIds: string[]) => { const handleSelectedUserIdsChange = useLastCallback((newSelectedIds: string[]) => {
setSelectedUserIds(newSelectedIds); setSelectedUserIds(newSelectedIds);
if (!newSelectedIds.length) { if (!newSelectedIds.length) {
setGiveawayOption('premium_giveaway'); setSelectedGiveawayOption('premium_giveaway');
} }
}); });

View File

@ -150,13 +150,13 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
const oldLang = useOldLang(); const oldLang = useOldLang();
const lang = useLang(); const lang = useLang();
const [isHeaderHidden, setHeaderHidden] = useState(true); const [isHeaderHidden, setIsHeaderHidden] = useState(true);
const [currentSection, setCurrentSection] = useState<ApiPremiumSection | undefined>(initialSection); const [currentSection, setCurrentSection] = useState<ApiPremiumSection | undefined>(initialSection);
const [selectedSubscriptionOption, setSubscriptionOption] = useState<ApiPremiumSubscriptionOption>(); const [selectedSubscriptionOption, setSelectedSubscriptionOption] = useState<ApiPremiumSubscriptionOption>();
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {
setHeaderHidden(true); setIsHeaderHidden(true);
setCurrentSection(undefined); setCurrentSection(undefined);
} else if (initialSection) { } else if (initialSection) {
setCurrentSection(initialSection); setCurrentSection(initialSection);
@ -174,7 +174,7 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
function handleScroll(e: React.UIEvent<HTMLDivElement>) { function handleScroll(e: React.UIEvent<HTMLDivElement>) {
const { scrollTop } = e.currentTarget; const { scrollTop } = e.currentTarget;
setHeaderHidden(scrollTop <= 150); setIsHeaderHidden(scrollTop <= 150);
} }
const handleClickWithStartParam = useLastCallback((startParam?: string) => { const handleClickWithStartParam = useLastCallback((startParam?: string) => {
@ -204,7 +204,7 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
const handleChangeSubscriptionOption = useLastCallback((months: number) => { const handleChangeSubscriptionOption = useLastCallback((months: number) => {
const foundOption = promo?.options.find((option) => option.months === months); const foundOption = promo?.options.find((option) => option.months === months);
setSubscriptionOption(foundOption); setSelectedSubscriptionOption(foundOption);
}); });
const showConfetti = useLastCallback(() => { const showConfetti = useLastCallback(() => {
@ -250,7 +250,7 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
useEffect(() => { useEffect(() => {
const [defaultOption] = promo?.options ?? []; const [defaultOption] = promo?.options ?? [];
setSubscriptionOption(defaultOption); setSelectedSubscriptionOption(defaultOption);
}, [promo]); }, [promo]);
const handleOpenStatusSet = useLastCallback(() => { const handleOpenStatusSet = useLastCallback(() => {

View File

@ -195,6 +195,7 @@ const ConfettiContainer = ({ confetti }: StateProps) => {
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- Old timeout should be cleared only if new confetti is generated // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- Old timeout should be cleared only if new confetti is generated
}, [lastConfettiTime, forceUpdate, updateCanvas]); }, [lastConfettiTime, forceUpdate, updateCanvas]);
// eslint-disable-next-line @eslint-react/purity
if (!lastConfettiTime || Date.now() - lastConfettiTime > CONFETTI_FADEOUT_TIMEOUT) { if (!lastConfettiTime || Date.now() - lastConfettiTime > CONFETTI_FADEOUT_TIMEOUT) {
return undefined; return undefined;
} }

View File

@ -143,7 +143,7 @@ const MediaViewer = ({
const { media, isSingle } = viewableMedia || {}; const { media, isSingle } = viewableMedia || {};
/* Animation */ /* Animation */
const animationKey = useRef<number>(); const animationKeyRef = useRef<number>();
const senderId = message?.senderId || avatarOwner?.id || message?.chatId; const senderId = message?.senderId || avatarOwner?.id || message?.chatId;
const prevSenderId = usePreviousDeprecated<string | undefined>(senderId); const prevSenderId = usePreviousDeprecated<string | undefined>(senderId);
const headerAnimation = withAnimation ? 'slideFade' : 'none'; const headerAnimation = withAnimation ? 'slideFade' : 'none';
@ -178,8 +178,8 @@ const MediaViewer = ({
: getMessageContentIds(chatMessages || {}, collectedMessageIds || [], contentType || 'media'); : getMessageContentIds(chatMessages || {}, collectedMessageIds || [], contentType || 'media');
}, [chatMessages, collectedMessageIds, contentType, withDynamicLoading]); }, [chatMessages, collectedMessageIds, contentType, withDynamicLoading]);
if (isOpen && (!prevSenderId || prevSenderId !== senderId || animationKey.current === undefined)) { if (isOpen && (!prevSenderId || prevSenderId !== senderId || animationKeyRef.current === undefined)) {
animationKey.current = isSingle ? 0 : (messageId || mediaIndex); animationKeyRef.current = isSingle ? 0 : (messageId || mediaIndex);
} }
const [getIsPictureInPicture] = PICTURE_IN_PICTURE_SIGNAL; const [getIsPictureInPicture] = PICTURE_IN_PICTURE_SIGNAL;
@ -438,7 +438,7 @@ const MediaViewer = ({
onClick={handleClose} onClick={handleClose}
/> />
)} )}
<Transition activeKey={animationKey.current!} name={headerAnimation}> <Transition activeKey={animationKeyRef.current!} name={headerAnimation}>
<SenderInfo <SenderInfo
key={media?.id} key={media?.id}
item={currentItem} item={currentItem}

View File

@ -279,9 +279,6 @@ const MediaViewerSlides: FC<OwnProps> = ({
const initialContentRect = initialContentRectRef.current; const initialContentRect = initialContentRectRef.current;
if (!initialContentRect) return [{ x, y, scale }, true, true]; if (!initialContentRect) return [{ x, y, scale }, true, true];
// Get current content boundaries // Get current content boundaries
let inBoundsX = true;
let inBoundsY = true;
const centerX = (windowWidth - windowWidth * scale) / 2; const centerX = (windowWidth - windowWidth * scale) / 2;
const centerY = (windowHeight - windowHeight * scale) / 2; const centerY = (windowHeight - windowHeight * scale) / 2;
@ -289,12 +286,12 @@ const MediaViewerSlides: FC<OwnProps> = ({
// based on initial content rect and current scale // based on initial content rect and current scale
const minOffsetX = Math.max(-initialContentRect.left * scale, centerX); const minOffsetX = Math.max(-initialContentRect.left * scale, centerX);
const maxOffsetX = windowWidth - initialContentRect.right * scale; const maxOffsetX = windowWidth - initialContentRect.right * scale;
inBoundsX = isBetween(x, maxOffsetX, minOffsetX); const inBoundsX = isBetween(x, maxOffsetX, minOffsetX);
x = clamp(x, maxOffsetX, minOffsetX); x = clamp(x, maxOffsetX, minOffsetX);
const minOffsetY = Math.max(-initialContentRect.top * scale + offsetTop, centerY); const minOffsetY = Math.max(-initialContentRect.top * scale + offsetTop, centerY);
const maxOffsetY = windowHeight - initialContentRect.bottom * scale; const maxOffsetY = windowHeight - initialContentRect.bottom * scale;
inBoundsY = isBetween(y, maxOffsetY, minOffsetY); const inBoundsY = isBetween(y, maxOffsetY, minOffsetY);
y = clamp(y, maxOffsetY, minOffsetY); y = clamp(y, maxOffsetY, minOffsetY);
return [{ x, y, scale }, inBoundsX, inBoundsY]; return [{ x, y, scale }, inBoundsX, inBoundsY];

View File

@ -64,13 +64,13 @@ const SeekLine = ({
const [getPreviewOffset, setPreviewOffset] = useSignal(0); const [getPreviewOffset, setPreviewOffset] = useSignal(0);
const [getPreviewTime, setPreviewTime] = useSignal(0); const [getPreviewTime, setPreviewTime] = useSignal(0);
const isLockedRef = useRef<boolean>(false); const isLockedRef = useRef<boolean>(false);
const [isPreviewVisible, setPreviewVisible] = useState(false); const [isPreviewVisible, setIsPreviewVisible] = useState(false);
const [isSeeking, setIsSeeking] = useState(false); const [isSeeking, setIsSeeking] = useState(false);
const previewContainerRef = useRef<HTMLDivElement>(); const previewContainerRef = useRef<HTMLDivElement>();
const previewRef = useRef<HTMLDivElement>(); const previewRef = useRef<HTMLDivElement>();
const progressRef = useRef<HTMLDivElement>(); const progressRef = useRef<HTMLDivElement>();
const previewTimeRef = useRef<HTMLDivElement>(); const previewTimeRef = useRef<HTMLDivElement>();
const storyboardParser = useRef<StoryboardParser>(); const storyboardParserRef = useRef<StoryboardParser>();
const storyboardHash = storyboardInfo && getDocumentMediaHash(storyboardInfo.storyboardFile, 'full'); const storyboardHash = storyboardInfo && getDocumentMediaHash(storyboardInfo.storyboardFile, 'full');
const storyboardMapHash = storyboardInfo && getDocumentMediaHash(storyboardInfo.storyboardMapFile, 'full'); const storyboardMapHash = storyboardInfo && getDocumentMediaHash(storyboardInfo.storyboardMapFile, 'full');
@ -79,13 +79,13 @@ const SeekLine = ({
const storyboardMapData = useMedia(storyboardMapHash, !isReady, ApiMediaFormat.Text); const storyboardMapData = useMedia(storyboardMapHash, !isReady, ApiMediaFormat.Text);
useEffect(() => { useEffect(() => {
setPreviewVisible(false); setIsPreviewVisible(false);
}, [isActive]); }, [isActive]);
useEffect(() => { useEffect(() => {
if (!storyboardMapData) return; if (!storyboardMapData) return;
try { try {
storyboardParser.current = new StoryboardParser(storyboardMapData); storyboardParserRef.current = new StoryboardParser(storyboardMapData);
} catch (error) { } catch (error) {
if (DEBUG) { if (DEBUG) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -96,8 +96,8 @@ const SeekLine = ({
const setPreview = useLastCallback((time: number) => { const setPreview = useLastCallback((time: number) => {
const previewContainer = previewContainerRef.current; const previewContainer = previewContainerRef.current;
if (!storyboardParser.current || !previewContainer) return; if (!storyboardParserRef.current || !previewContainer) return;
const frame = storyboardParser.current.getNearestPreview(time); const frame = storyboardParserRef.current.getNearestPreview(time);
setPreviewTime(Math.floor(frame.time)); setPreviewTime(Math.floor(frame.time));
@ -189,7 +189,7 @@ const SeekLine = ({
const handleSeek = (e: MouseEvent | TouchEvent) => { const handleSeek = (e: MouseEvent | TouchEvent) => {
stopAnimation(); stopAnimation();
setPreviewVisible(true); setIsPreviewVisible(true);
([time, offset] = getPreviewProps(e)); ([time, offset] = getPreviewProps(e));
void setPreview(time); void setPreview(time);
setPreviewOffset(offset); setPreviewOffset(offset);
@ -198,7 +198,7 @@ const SeekLine = ({
const handleStartSeek = () => { const handleStartSeek = () => {
stopAnimation(); stopAnimation();
setPreviewVisible(true); setIsPreviewVisible(true);
setIsSeeking(true); setIsSeeking(true);
onSeekStart(); onSeekStart();
}; };
@ -206,7 +206,7 @@ const SeekLine = ({
const handleStopSeek = () => { const handleStopSeek = () => {
stopAnimation(); stopAnimation();
isLockedRef.current = true; isLockedRef.current = true;
setPreviewVisible(false); setIsPreviewVisible(false);
setIsSeeking(false); setIsSeeking(false);
setSelectedTime(time); setSelectedTime(time);
onSeek(time); onSeek(time);
@ -228,14 +228,14 @@ const SeekLine = ({
} }
const handleSeekMouseMove = (e: MouseEvent) => { const handleSeekMouseMove = (e: MouseEvent) => {
setPreviewVisible(true); setIsPreviewVisible(true);
([time, offset] = getPreviewProps(e)); ([time, offset] = getPreviewProps(e));
setPreviewOffset(offset); setPreviewOffset(offset);
void setPreview(time); void setPreview(time);
}; };
const handleSeekMouseLeave = () => { const handleSeekMouseLeave = () => {
setPreviewVisible(false); setIsPreviewVisible(false);
}; };
seeker.addEventListener('mousemove', handleSeekMouseMove); seeker.addEventListener('mousemove', handleSeekMouseMove);

View File

@ -113,11 +113,11 @@ const VideoPlayer: FC<OwnProps> = ({
const [, toggleControls, lockControls] = useControlsSignal(); const [, toggleControls, lockControls] = useControlsSignal();
const [getIsSeeking, setIsSeeking] = useSignal(false); const [getIsSeeking, setIsSeeking] = useSignal(false);
const lastMousePosition = useRef({ x: 0, y: 0 }); const lastMousePositionRef = useRef({ x: 0, y: 0 });
useEffect(() => { useEffect(() => {
const updateMousePosition = (e: MouseEvent | TouchEvent) => { const updateMousePosition = (e: MouseEvent | TouchEvent) => {
lastMousePosition.current = getPointerPosition(e); lastMousePositionRef.current = getPointerPosition(e);
}; };
window.addEventListener('mousemove', updateMousePosition); window.addEventListener('mousemove', updateMousePosition);
@ -151,7 +151,7 @@ const VideoPlayer: FC<OwnProps> = ({
const handleSeekingChange = useLastCallback((isSeeking: boolean) => { const handleSeekingChange = useLastCallback((isSeeking: boolean) => {
setIsSeeking(isSeeking); setIsSeeking(isSeeking);
if (!isSeeking) { if (!isSeeking) {
const { x, y } = lastMousePosition.current; const { x, y } = lastMousePositionRef.current;
checkMousePositionAndToggleControls(x, y); checkMousePositionAndToggleControls(x, y);
} }
}); });

View File

@ -80,7 +80,12 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
stop(); stop();
endHeavyAnimation(); endHeavyAnimation();
}, PLAYING_DURATION); }, PLAYING_DURATION);
}, [stop]);
return () => {
clearTimeout(timeoutRef.current);
endHeavyAnimation();
};
}, []);
const effectHash = effectAnimationId && `sticker${effectAnimationId}`; const effectHash = effectAnimationId && `sticker${effectAnimationId}`;
const effectTgsUrl = useMedia(effectHash, !effectAnimationId); const effectTgsUrl = useMedia(effectHash, !effectAnimationId);

View File

@ -251,7 +251,7 @@ function MiddleColumn({
const oldLang = useOldLang(); const oldLang = useOldLang();
const lang = useLang(); const lang = useLang();
const [dropAreaState, setDropAreaState] = useState(DropAreaState.None); const [dropAreaState, setDropAreaState] = useState(DropAreaState.None);
const [isScrollDownNeeded, setIsScrollDownShown] = useState(false); const [isScrollDownNeeded, setIsScrollDownNeeded] = useState(false);
const isScrollDownShown = isScrollDownNeeded && (!isMobile || !hasActiveMiddleSearch); const isScrollDownShown = isScrollDownNeeded && (!isMobile || !hasActiveMiddleSearch);
const [isNotchShown, setIsNotchShown] = useState<boolean | undefined>(); const [isNotchShown, setIsNotchShown] = useState<boolean | undefined>();
const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false); const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false);
@ -571,7 +571,7 @@ function MiddleColumn({
type={renderingMessageListType!} type={renderingMessageListType!}
isComments={isComments} isComments={isComments}
canPost={renderingCanPost!} canPost={renderingCanPost!}
onScrollDownToggle={setIsScrollDownShown} onScrollDownToggle={setIsScrollDownNeeded}
onNotchToggle={setIsNotchShown} onNotchToggle={setIsNotchShown}
isReady={isReady} isReady={isReady}
isContactRequirePremium={isContactRequirePremium} isContactRequirePremium={isContactRequirePremium}
@ -900,18 +900,23 @@ function useIsReady(
const forceUpdate = useForceUpdate(); const forceUpdate = useForceUpdate();
const willSwitchMessageList = prevTransitionKey !== undefined && prevTransitionKey !== currentTransitionKey; const willSwitchMessageList = prevTransitionKey !== undefined && prevTransitionKey !== currentTransitionKey;
if (willSwitchMessageList) { useSyncEffect(() => {
if (withAnimations) { if (!willSwitchMessageList) return;
setIsReady(false); if (!withAnimations) {
// Make sure to end even if end callback was not called (which was some hardly-reproducible bug)
setTimeout(() => {
setIsReady(true);
}, LAYER_ANIMATION_DURATION_MS);
} else {
forceUpdate(); forceUpdate();
return undefined;
} }
} setIsReady(false);
// Make sure to end even if end callback was not called (which was some hardly-reproducible bug)
const timeout = setTimeout(() => {
setIsReady(true);
}, LAYER_ANIMATION_DURATION_MS);
return () => {
clearTimeout(timeout);
};
}, [willSwitchMessageList, withAnimations]);
useSyncEffect(() => { useSyncEffect(() => {
if (!withAnimations) { if (!withAnimations) {

View File

@ -129,7 +129,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
} = getActions(); } = getActions();
const lang = useOldLang(); const lang = useOldLang();
const isBackButtonActive = useRef(true); const isBackButtonActiveRef = useRef(true);
const { isDesktop, isTablet } = useAppLayout(); const { isDesktop, isTablet } = useAppLayout();
const { width: windowWidth } = useWindowSize(); const { width: windowWidth } = useWindowSize();
@ -164,7 +164,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
const setBackButtonActive = useLastCallback(() => { const setBackButtonActive = useLastCallback(() => {
setTimeout(() => { setTimeout(() => {
isBackButtonActive.current = true; isBackButtonActiveRef.current = true;
}, BACK_BUTTON_INACTIVE_TIME); }, BACK_BUTTON_INACTIVE_TIME);
}); });
@ -187,10 +187,10 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
}); });
const handleBackClick = useLastCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => { const handleBackClick = useLastCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
if (!isBackButtonActive.current) return; if (!isBackButtonActiveRef.current) return;
// Workaround for missing UI when quickly clicking the Back button // Workaround for missing UI when quickly clicking the Back button
isBackButtonActive.current = false; isBackButtonActiveRef.current = false;
if (isMobile) { if (isMobile) {
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR); const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
messageInput?.blur(); messageInput?.blur();

View File

@ -109,9 +109,13 @@ const CustomSendMenu: FC<OwnProps> = ({
return; return;
} }
setTimeout(() => { const timeout = setTimeout(() => {
markIsReady(); markIsReady();
}, ANIMATION_DURATION); }, ANIMATION_DURATION);
return () => {
clearTimeout(timeout);
};
}, [isOpen, markIsReady, unmarkIsReady]); }, [isOpen, markIsReady, unmarkIsReady]);
return ( return (

View File

@ -147,7 +147,7 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
// Initialize data on first render. // Initialize data on first render.
useEffect(() => { useEffect(() => {
setTimeout(() => { const timeout = setTimeout(() => {
const exec = () => { const exec = () => {
setCategories(emojiData.categories); setCategories(emojiData.categories);
@ -161,6 +161,10 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
.then(exec); .then(exec);
} }
}, OPEN_ANIMATION_DELAY); }, OPEN_ANIMATION_DELAY);
return () => {
clearTimeout(timeout);
};
}, []); }, []);
const selectCategory = useLastCallback((index: number) => { const selectCategory = useLastCallback((index: number) => {

View File

@ -5,8 +5,7 @@ import {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ApiNewMediaTodo } from '../../../api/types'; import type { ApiMessage, ApiNewMediaTodo } from '../../../api/types';
import type { ApiMessage } from '../../../api/types';
import type { TabState } from '../../../global/types/tabState'; import type { TabState } from '../../../global/types/tabState';
import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom'; import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom';

View File

@ -53,14 +53,14 @@ export default function useInputCustomEmojis(
const customColor = useDynamicColorListener(inputRef, undefined, !isReady); const customColor = useDynamicColorListener(inputRef, undefined, !isReady);
const colorFilter = useColorFilter(customColor, true); const colorFilter = useColorFilter(customColor, true);
const dpr = useDevicePixelRatio(); const dpr = useDevicePixelRatio();
const playersById = useRef<Map<string, CustomEmojiPlayer>>(new Map()); const playersByIdRef = useRef<Map<string, CustomEmojiPlayer>>(new Map());
const clearPlayers = useLastCallback((ids: string[]) => { const clearPlayers = useLastCallback((ids: string[]) => {
ids.forEach((id) => { ids.forEach((id) => {
const player = playersById.current.get(id); const player = playersByIdRef.current.get(id);
if (player) { if (player) {
player.destroy(); player.destroy();
playersById.current.delete(id); playersByIdRef.current.delete(id);
} }
}); });
}); });
@ -69,7 +69,7 @@ export default function useInputCustomEmojis(
if (!isReady || !inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return; if (!isReady || !inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return;
const global = getGlobal(); const global = getGlobal();
const playerIdsToClear = new Set(playersById.current.keys()); const playerIdsToClear = new Set(playersByIdRef.current.keys());
const customEmojis = Array.from(inputRef.current.querySelectorAll<HTMLElement>('.custom-emoji')); const customEmojis = Array.from(inputRef.current.querySelectorAll<HTMLElement>('.custom-emoji'));
customEmojis.forEach((element) => { customEmojis.forEach((element) => {
@ -91,8 +91,8 @@ export default function useInputCustomEmojis(
const x = round((elementBounds.left - canvasBounds.left) / canvasBounds.width, 4); const x = round((elementBounds.left - canvasBounds.left) / canvasBounds.width, 4);
const y = round((elementBounds.top - canvasBounds.top) / canvasBounds.height, 4); const y = round((elementBounds.top - canvasBounds.top) / canvasBounds.height, 4);
if (playersById.current.has(playerId)) { if (playersByIdRef.current.has(playerId)) {
const player = playersById.current.get(playerId)!; const player = playersByIdRef.current.get(playerId)!;
player.updatePosition(x, y); player.updatePosition(x, y);
return; return;
} }
@ -123,7 +123,7 @@ export default function useInputCustomEmojis(
animation.play(); animation.play();
} }
playersById.current.set(playerId, animation); playersByIdRef.current.set(playerId, animation);
}); });
}); });
@ -135,7 +135,7 @@ export default function useInputCustomEmojis(
}, [synchronizeElements]); }, [synchronizeElements]);
useEffect(() => { useEffect(() => {
const activePlayersById = playersById.current; const activePlayersById = playersByIdRef.current;
// Always clear players on unmount // Always clear players on unmount
return () => { return () => {
clearPlayers(Array.from(activePlayersById.keys())); clearPlayers(Array.from(activePlayersById.keys()));
@ -144,7 +144,7 @@ export default function useInputCustomEmojis(
useEffect(() => { useEffect(() => {
if (!getHtml() || !inputRef.current || !sharedCanvasRef.current || !isActive || !isReady) { if (!getHtml() || !inputRef.current || !sharedCanvasRef.current || !isActive || !isReady) {
clearPlayers(Array.from(playersById.current.keys())); clearPlayers(Array.from(playersByIdRef.current.keys()));
return; return;
} }
@ -173,13 +173,13 @@ export default function useInputCustomEmojis(
useResizeObserver(sharedCanvasRef, throttledSynchronizeElements); useResizeObserver(sharedCanvasRef, throttledSynchronizeElements);
useEffectWithPrevDeps(([prevDpr]) => { useEffectWithPrevDeps(([prevDpr]) => {
if (dpr !== prevDpr) { if (dpr !== prevDpr) {
clearPlayers(Array.from(playersById.current.keys())); clearPlayers(Array.from(playersByIdRef.current.keys()));
synchronizeElements(); synchronizeElements();
} }
}, [dpr, synchronizeElements]); }, [dpr, synchronizeElements]);
const freezeAnimation = useLastCallback(() => { const freezeAnimation = useLastCallback(() => {
playersById.current.forEach((player) => { playersByIdRef.current.forEach((player) => {
player.pause(); player.pause();
}); });
}); });
@ -189,7 +189,7 @@ export default function useInputCustomEmojis(
return; return;
} }
playersById.current?.forEach((player) => { playersByIdRef.current.forEach((player) => {
player.play(); player.play();
}); });
}); });

View File

@ -14,8 +14,7 @@ export default function usePaidMessageConfirmation(
shouldPaidMessageAutoApprove, shouldPaidMessageAutoApprove,
} = getGlobal().settings.byKey; } = getGlobal().settings.byKey;
const [shouldAutoApprove, const [shouldAutoApprove, setShouldAutoApprove] = useState(Boolean(shouldPaidMessageAutoApprove));
setAutoApprove] = useState(Boolean(shouldPaidMessageAutoApprove));
const [isWaitingStarsTopup, setIsWaitingStarsTopup] = useState(false); const [isWaitingStarsTopup, setIsWaitingStarsTopup] = useState(false);
const confirmPaymentHandlerRef = useRef<NoneToVoidFunction | undefined>(undefined); const confirmPaymentHandlerRef = useRef<NoneToVoidFunction | undefined>(undefined);
@ -78,6 +77,6 @@ export default function usePaidMessageConfirmation(
handleWithConfirmation, handleWithConfirmation,
dialogHandler, dialogHandler,
shouldAutoApprove, shouldAutoApprove,
setAutoApprove, setAutoApprove: setShouldAutoApprove,
}; };
} }

View File

@ -23,7 +23,7 @@ export default function useContainerHeight(containerRef: ElementRef<HTMLDivEleme
} }
}, [isComposerVisible, containerRef, getContainerHeight]); }, [isComposerVisible, containerRef, getContainerHeight]);
const prevContainerHeight = useRef<number>(); const prevContainerHeightRef = useRef<number>();
return [getContainerHeight, prevContainerHeight] as const; return [getContainerHeight, prevContainerHeightRef] as const;
} }

View File

@ -309,8 +309,11 @@ const ActionMessage = ({
return; return;
} }
setTimeout(markShown, appearanceOrder * MESSAGE_APPEARANCE_DELAY); const timeout = setTimeout(markShown, appearanceOrder * MESSAGE_APPEARANCE_DELAY);
}, [appearanceOrder, markShown, noAppearanceAnimation]); return () => {
clearTimeout(timeout);
};
}, [appearanceOrder, noAppearanceAnimation]);
const { ref: refWithTransition } = useShowTransition({ const { ref: refWithTransition } = useShowTransition({
isOpen: isShown, isOpen: isShown,

View File

@ -12,9 +12,11 @@ import {
} from '../../../config'; } from '../../../config';
import { import {
getMainUsername, getMainUsername,
getMessageInvoice, getMessageTextWithFallback, isChatChannel, getMessageContent,
getMessageInvoice,
getMessageTextWithFallback,
isChatChannel,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getMessageContent } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers'; import { getPeerTitle } from '../../../global/helpers/peers';
import { getMessageReplyInfo } from '../../../global/helpers/replies'; import { getMessageReplyInfo } from '../../../global/helpers/replies';
import { import {
@ -30,8 +32,7 @@ import { ensureProtocol } from '../../../util/browser/url';
import { import {
formatDateTimeToString, formatScheduledDateTime, formatShortDuration, formatDateTimeToString, formatScheduledDateTime, formatShortDuration,
} from '../../../util/dates/oldDateFormat'; } from '../../../util/dates/oldDateFormat';
import { formatCurrency } from '../../../util/formatCurrency'; import { convertTonFromNanos, formatCurrency } from '../../../util/formatCurrency';
import { convertTonFromNanos } from '../../../util/formatCurrency';
import { formatCurrencyAmountAsText, formatStarsAsText, formatTonAsText } from '../../../util/localization/format'; import { formatCurrencyAmountAsText, formatStarsAsText, formatTonAsText } from '../../../util/localization/format';
import { conjuctionWithNodes } from '../../../util/localization/utils'; import { conjuctionWithNodes } from '../../../util/localization/utils';
import { getServerTime } from '../../../util/serverTime'; import { getServerTime } from '../../../util/serverTime';

View File

@ -692,9 +692,6 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
return undefined; return undefined;
} }
const scheduledMaxDate = new Date();
scheduledMaxDate.setFullYear(scheduledMaxDate.getFullYear() + 1);
return ( return (
<div ref={containerRef} className={buildClassName('ContextMenuContainer', className)}> <div ref={containerRef} className={buildClassName('ContextMenuContainer', className)}>
<MessageContextMenu <MessageContextMenu

View File

@ -61,7 +61,7 @@ const Giveaway = ({
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { openChat } = getActions(); const { openChat } = getActions();
const isLoadingInfo = useRef(false); const isLoadingInfoRef = useRef(false);
const [giveawayInfo, setGiveawayInfo] = useState<ApiGiveawayInfo | undefined>(); const [giveawayInfo, setGiveawayInfo] = useState<ApiGiveawayInfo | undefined>();
const lang = useOldLang(); const lang = useOldLang();
@ -90,15 +90,15 @@ const Giveaway = ({
}); });
const handleShowInfoClick = useLastCallback(async () => { const handleShowInfoClick = useLastCallback(async () => {
if (isLoadingInfo.current) return; if (isLoadingInfoRef.current) return;
isLoadingInfo.current = true; isLoadingInfoRef.current = true;
const result = await callApi('fetchGiveawayInfo', { const result = await callApi('fetchGiveawayInfo', {
peer: chat, peer: chat,
messageId: message.id, messageId: message.id,
}); });
setGiveawayInfo(result); setGiveawayInfo(result);
isLoadingInfo.current = false; isLoadingInfoRef.current = false;
}); });
const handleCloseInfo = useLastCallback(() => { const handleCloseInfo = useLastCallback(() => {
@ -240,7 +240,7 @@ const Giveaway = ({
? lang('BoostingGiveawayHowItWorksIncludeText', [chatTitle, quantity, prizeDescription], undefined, quantity) ? lang('BoostingGiveawayHowItWorksIncludeText', [chatTitle, quantity, prizeDescription], undefined, quantity)
: undefined; : undefined;
let secondKey = ''; let secondKey;
if (isResultsInfo) { if (isResultsInfo) {
secondKey = isSeveral ? 'BoostingGiveawayHowItWorksSubTextSeveralEnd' : 'BoostingGiveawayHowItWorksSubTextEnd'; secondKey = isSeveral ? 'BoostingGiveawayHowItWorksSubTextSeveralEnd' : 'BoostingGiveawayHowItWorksSubTextEnd';
} else { } else {

View File

@ -489,7 +489,7 @@ const Message = ({
const oldLang = useOldLang(); const oldLang = useOldLang();
const lang = useLang(); const lang = useLang();
const [isTranscriptionHidden, setTranscriptionHidden] = useState(false); const [isTranscriptionHidden, setIsTranscriptionHidden] = useState(false);
const [isPlayingSnapAnimation, setIsPlayingSnapAnimation] = useState(false); const [isPlayingSnapAnimation, setIsPlayingSnapAnimation] = useState(false);
const [isPlayingDeleteAnimation, setIsPlayingDeleteAnimation] = useState(false); const [isPlayingDeleteAnimation, setIsPlayingDeleteAnimation] = useState(false);
const [shouldPlayEffect, requestEffect, hideEffect] = useFlag(); const [shouldPlayEffect, requestEffect, hideEffect] = useFlag();
@ -530,8 +530,11 @@ const Message = ({
return; return;
} }
setTimeout(markShown, appearanceOrder * MESSAGE_APPEARANCE_DELAY); const timeout = setTimeout(markShown, appearanceOrder * MESSAGE_APPEARANCE_DELAY);
}, [appearanceOrder, markShown, noAppearanceAnimation]); return () => {
clearTimeout(timeout);
};
}, [appearanceOrder, noAppearanceAnimation]);
useShowTransition({ useShowTransition({
ref, ref,
@ -1300,7 +1303,7 @@ const Message = ({
canAutoLoad={canAutoLoadMedia} canAutoLoad={canAutoLoadMedia}
isDownloading={isDownloading} isDownloading={isDownloading}
onReadMedia={shouldReadMedia ? handleReadMedia : undefined} onReadMedia={shouldReadMedia ? handleReadMedia : undefined}
onHideTranscription={setTranscriptionHidden} onHideTranscription={setIsTranscriptionHidden}
isTranscriptionError={isTranscriptionError} isTranscriptionError={isTranscriptionError}
isTranscribed={Boolean(transcribedText)} isTranscribed={Boolean(transcribedText)}
canTranscribe={canTranscribeVoice && !hasTtl} canTranscribe={canTranscribeVoice && !hasTtl}
@ -1326,7 +1329,7 @@ const Message = ({
isTranscribed={Boolean(transcribedText)} isTranscribed={Boolean(transcribedText)}
isTranscriptionError={isTranscriptionError} isTranscriptionError={isTranscriptionError}
canDownload={!isProtected} canDownload={!isProtected}
onHideTranscription={setTranscriptionHidden} onHideTranscription={setIsTranscriptionHidden}
canTranscribe={canTranscribeVoice && !hasTtl} canTranscribe={canTranscribeVoice && !hasTtl}
/> />
)} )}
@ -1670,7 +1673,6 @@ const Message = ({
shouldSkipRenderForwardTitle: boolean = false, shouldSkipRenderAdminTitle: boolean = false, shouldSkipRenderForwardTitle: boolean = false, shouldSkipRenderAdminTitle: boolean = false,
) { ) {
let senderTitle; let senderTitle;
let senderColor;
if (senderPeer && !(isCustomShape && viaBotId)) { if (senderPeer && !(isCustomShape && viaBotId)) {
senderTitle = getPeerFullTitle(oldLang, senderPeer); senderTitle = getPeerFullTitle(oldLang, senderPeer);
} else if (forwardInfo?.hiddenUserName) { } else if (forwardInfo?.hiddenUserName) {
@ -1689,7 +1691,6 @@ const Message = ({
className={buildClassName( className={buildClassName(
'message-title-name-container', 'message-title-name-container',
forwardInfo?.hiddenUserName ? 'sender-hidden' : 'interactive', forwardInfo?.hiddenUserName ? 'sender-hidden' : 'interactive',
senderColor,
)} )}
dir="ltr" dir="ltr"
> >

View File

@ -337,10 +337,14 @@ const MessageContextMenu: FC<OwnProps> = ({
return; return;
} }
setTimeout(() => { const timeout = setTimeout(() => {
markIsReady(); markIsReady();
}, ANIMATION_DURATION); }, ANIMATION_DURATION);
}, [isOpen, markIsReady, unmarkIsReady]);
return () => {
clearTimeout(timeout);
};
}, [isOpen]);
useEffect(() => { useEffect(() => {
return disableScrolling(scrollableRef.current, '.ReactionPicker'); return disableScrolling(scrollableRef.current, '.ReactionPicker');

View File

@ -107,7 +107,7 @@ const Poll: FC<OwnProps> = ({
useLayoutEffect(() => { useLayoutEffect(() => {
if (closePeriod > 0) { if (closePeriod > 0) {
setTimeout(() => setClosePeriod(closePeriod - 1), TIMER_UPDATE_INTERVAL); window.setTimeout(() => setClosePeriod(closePeriod - 1), TIMER_UPDATE_INTERVAL);
} }
if (!timerCircleRef.current) return; if (!timerCircleRef.current) return;

View File

@ -1,4 +1,3 @@
import type { FC } from '../../../lib/teact/teact';
import { import {
memo, useEffect, useLayoutEffect, memo, useEffect, useLayoutEffect,
useMemo, useMemo,
@ -102,7 +101,7 @@ const INLINE_MEMBER_COUNT = 5;
const runDebouncedForSearch = debounce((cb) => cb(), 200, false); const runDebouncedForSearch = debounce((cb) => cb(), 200, false);
const MiddleSearch: FC<OwnProps & StateProps> = ({ const MiddleSearch = ({
isActive, isActive,
chat, chat,
monoforumChat, monoforumChat,
@ -122,7 +121,7 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
currentUserId, currentUserId,
fromPeerId, fromPeerId,
isGroupChat, isGroupChat,
}) => { }: OwnProps & StateProps) => {
const { const {
updateMiddleSearch, updateMiddleSearch,
resetMiddleSearch, resetMiddleSearch,
@ -414,7 +413,9 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
}); });
} }
// eslint-disable-next-line @eslint-react/web-api/no-leaked-event-listener
window.addEventListener('touchend', focus); window.addEventListener('touchend', focus);
// eslint-disable-next-line @eslint-react/web-api/no-leaked-event-listener
window.addEventListener('mouseup', focus); window.addEventListener('mouseup', focus);
window.addEventListener('touchstart', removeListeners); window.addEventListener('touchstart', removeListeners);

View File

@ -42,6 +42,10 @@ const MiddleSearchResult = ({
}: OwnProps) => { }: OwnProps) => {
const lang = useLang(); const lang = useLang();
const handleClick = useLastCallback(() => {
onClick(message);
});
if (peer && !message) { if (peer && !message) {
const username = getMainUsername(peer); const username = getMainUsername(peer);
@ -83,10 +87,6 @@ const MiddleSearchResult = ({
const senderPeer = shouldShowChat ? messageChat : peer; const senderPeer = shouldShowChat ? messageChat : peer;
const senderName = shouldShowChat && peer ? getMessageSenderName(lang, message.chatId, peer) : undefined; const senderName = shouldShowChat && peer ? getMessageSenderName(lang, message.chatId, peer) : undefined;
const handleClick = useLastCallback(() => {
onClick(message);
});
return ( return (
<div <div
role="button" role="button"

View File

@ -1,41 +0,0 @@
import type { TeactNode } from '../../../lib/teact/teact';
import { memo } from '../../../lib/teact/teact';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
import Link from '../../ui/Link';
import styles from './GiftEmptyState.module.scss';
type OwnProps = {
description: TeactNode;
linkText?: TeactNode;
onLinkClick?: NoneToVoidFunction;
};
const GiftEmptyState = ({ description, linkText, onLinkClick }: OwnProps) => {
return (
<div className={styles.root}>
<AnimatedIconWithPreview
size={160}
tgsUrl={LOCAL_TGS_URLS.SearchingDuck}
nonInteractive
noLoop
/>
<div className={styles.description}>
{description}
</div>
{Boolean(linkText && onLinkClick) && (
<Link
className={styles.link}
onClick={onLinkClick}
>
{linkText}
</Link>
)}
</div>
);
};
export default memo(GiftEmptyState);

View File

@ -20,8 +20,7 @@ import type { ResaleGiftsFilterOptions, StarGiftCategory } from '../../../types'
import { STARS_CURRENCY_CODE } from '../../../config'; import { STARS_CURRENCY_CODE } from '../../../config';
import { getUserFullName } from '../../../global/helpers'; import { getUserFullName } from '../../../global/helpers';
import { getPeerTitle, isApiPeerChat, isApiPeerUser } from '../../../global/helpers/peers'; import { getPeerTitle, isApiPeerChat, isApiPeerUser } from '../../../global/helpers/peers';
import { selectTabState } from '../../../global/selectors'; import { selectPeer, selectTabState, selectUserFullInfo } from '../../../global/selectors';
import { selectPeer, selectUserFullInfo } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { getNextArrowReplacement } from '../../../util/localization/format'; import { getNextArrowReplacement } from '../../../util/localization/format';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';

View File

@ -7,8 +7,7 @@ import type {
ApiSavedStarGift, ApiSavedStarGift,
} from '../../../api/types'; } from '../../../api/types';
import { DEFAULT_STATUS_ICON_ID } from '../../../config'; import { DEFAULT_STATUS_ICON_ID, STARS_CURRENCY_CODE } from '../../../config';
import { STARS_CURRENCY_CODE } from '../../../config';
import { selectTabState, selectUser } from '../../../global/selectors'; import { selectTabState, selectUser } from '../../../global/selectors';
import { formatDateAtTime } from '../../../util/dates/oldDateFormat'; import { formatDateAtTime } from '../../../util/dates/oldDateFormat';
import { getServerTime } from '../../../util/serverTime'; import { getServerTime } from '../../../util/serverTime';

View File

@ -380,7 +380,7 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
// DOM refs // DOM refs
const cubeRef = useRef<HTMLDivElement>(); const cubeRef = useRef<HTMLDivElement>();
const faceRefs = useRef<Record<FaceName, HTMLDivElement | undefined>>({ const facesRef = useRef<Record<FaceName, HTMLDivElement | undefined>>({
front: undefined, front: undefined,
back: undefined, back: undefined,
left: undefined, left: undefined,
@ -388,8 +388,8 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
top: undefined, top: undefined,
bottom: undefined, bottom: undefined,
}); });
const slotRefs = useRef<(HTMLDivElement | undefined)[]>([]); const slotsRef = useRef<(HTMLDivElement | undefined)[]>([]);
const slotInnerRefs = useRef<(HTMLDivElement | undefined)[]>([]); const slotsInnerRef = useRef<(HTMLDivElement | undefined)[]>([]);
const backfaceRemovedRef = useRef(false); const backfaceRemovedRef = useRef(false);
// Physics state refs (mutable, not triggering re-renders) // Physics state refs (mutable, not triggering re-renders)
@ -473,7 +473,7 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
} }
// Reset face backgrounds and backface-visibility // Reset face backgrounds and backface-visibility
Object.values(faceRefs.current).forEach((face) => { Object.values(facesRef.current).forEach((face) => {
if (face) { if (face) {
face.style.background = ''; face.style.background = '';
face.style.backfaceVisibility = ''; face.style.backfaceVisibility = '';
@ -481,7 +481,7 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
}); });
// Reset slot transforms // Reset slot transforms
slotRefs.current.forEach((slot) => { slotsRef.current.forEach((slot) => {
if (slot) { if (slot) {
slot.style.transform = ''; slot.style.transform = '';
slot.style.transition = ''; slot.style.transition = '';
@ -520,7 +520,7 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
// Fade out slots when result rotation is complete // Fade out slots when result rotation is complete
useEffect(() => { useEffect(() => {
if (isRotationStarted) { if (isRotationStarted) {
slotRefs.current.forEach((slot) => { slotsRef.current.forEach((slot) => {
if (slot) { if (slot) {
requestMutation(() => { requestMutation(() => {
slot.classList.add(styles.craftSlotHidden); slot.classList.add(styles.craftSlotHidden);
@ -817,7 +817,7 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
// Slot Flight Animation // Slot Flight Animation
// =================== // ===================
const flySlotToCube = useLastCallback((index: number) => { const flySlotToCube = useLastCallback((index: number) => {
const slot = slotRefs.current[index]; const slot = slotsRef.current[index];
const cube = cubeRef.current; const cube = cubeRef.current;
if (!slot || !cube || !faceTransformsRef.current) return; if (!slot || !cube || !faceTransformsRef.current) return;
@ -913,7 +913,7 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
setAnimatingSlots((prev) => new Set(prev).add(index)); setAnimatingSlots((prev) => new Set(prev).add(index));
// Pre-rotate slot and counter-rotate inner content to avoid visible rotation jump // Pre-rotate slot and counter-rotate inner content to avoid visible rotation jump
const slotInner = slotInnerRefs.current[index]; const slotInner = slotsInnerRef.current[index];
const finalTransformStr = finalTransform.toString(); const finalTransformStr = finalTransform.toString();
requestMutation(() => { requestMutation(() => {
@ -947,7 +947,7 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
// Remove backface-visibility from all faces on first slot stick // Remove backface-visibility from all faces on first slot stick
if (!backfaceRemovedRef.current) { if (!backfaceRemovedRef.current) {
backfaceRemovedRef.current = true; backfaceRemovedRef.current = true;
Object.values(faceRefs.current).forEach((face) => { Object.values(facesRef.current).forEach((face) => {
if (face) { if (face) {
face.style.backfaceVisibility = 'visible'; face.style.backfaceVisibility = 'visible';
} }
@ -1156,11 +1156,11 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
}); });
const slotRefCallbacks = useMemo(() => [0, 1, 2, 3].map((i) => (el: HTMLDivElement | undefined) => { const slotRefCallbacks = useMemo(() => [0, 1, 2, 3].map((i) => (el: HTMLDivElement | undefined) => {
slotRefs.current[i] = el; slotsRef.current[i] = el;
}), []); }), []);
const slotInnerRefCallbacks = useMemo(() => [0, 1, 2, 3].map((i) => (el: HTMLDivElement | undefined) => { const slotInnerRefCallbacks = useMemo(() => [0, 1, 2, 3].map((i) => (el: HTMLDivElement | undefined) => {
slotInnerRefs.current[i] = el; slotsInnerRef.current[i] = el;
}), []); }), []);
function renderCraftSlot(index: number) { function renderCraftSlot(index: number) {
@ -1260,7 +1260,7 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
return ( return (
<div <div
key={face.name} key={face.name}
ref={(el) => { faceRefs.current[face.name] = el || undefined; }} ref={(el) => { facesRef.current[face.name] = el || undefined; }}
className={buildClassName( className={buildClassName(
styles.face, styles.face,
shouldHide && styles.faceHidden, shouldHide && styles.faceHidden,
@ -1486,7 +1486,7 @@ const GiftCraftModal = ({ modal, craftAttributePermilles }: OwnProps & StateProp
} }
function renderInfoContent() { function renderInfoContent() {
let activeKey = 0; let activeKey;
let content; let content;
if (failedFace) { if (failedFace) {

View File

@ -9,8 +9,9 @@ import {
selectStarsGiftResaleCommission, selectStarsGiftResaleCommission,
selectTonGiftResaleCommission, selectTonGiftResaleCommission,
} from '../../../../global/selectors'; } from '../../../../global/selectors';
import { convertTonFromNanos, convertTonToNanos } from '../../../../util/formatCurrency'; import {
import { convertTonToUsd, formatCurrencyAsString } from '../../../../util/formatCurrency'; convertTonFromNanos, convertTonToNanos, convertTonToUsd, formatCurrencyAsString,
} from '../../../../util/formatCurrency';
import { formatStarsAsIcon, formatStarsAsText, formatTonAsIcon, import { formatStarsAsIcon, formatStarsAsText, formatTonAsIcon,
formatTonAsText } from '../../../../util/localization/format'; formatTonAsText } from '../../../../util/localization/format';

View File

@ -97,7 +97,7 @@ const ProfileRatingModal = ({
const pendingLevel = !showFutureRating && renderingPendingRating const pendingLevel = !showFutureRating && renderingPendingRating
? renderingPendingRating.level : renderingStarsRating.level; ? renderingPendingRating.level : renderingStarsRating.level;
let levelProgress = 0; let levelProgress;
if (!nextLevelStars) { if (!nextLevelStars) {
levelProgress = 1; levelProgress = 1;

View File

@ -81,7 +81,7 @@ const StarsBalanceModal = ({
const oldLang = useOldLang(); const oldLang = useOldLang();
const lang = useLang(); const lang = useLang();
const [isHeaderHidden, setHeaderHidden] = useState(true); const [isHeaderHidden, setIsHeaderHidden] = useState(true);
const [areTabsPinned, pinTabs, unpinTabs] = useFlag(false); const [areTabsPinned, pinTabs, unpinTabs] = useFlag(false);
const [selectedTabIndex, setSelectedTabIndex] = useState(0); const [selectedTabIndex, setSelectedTabIndex] = useState(0);
const [areBuyOptionsShown, showBuyOptions, hideBuyOptions] = useFlag(); const [areBuyOptionsShown, showBuyOptions, hideBuyOptions] = useFlag();
@ -162,7 +162,7 @@ const StarsBalanceModal = ({
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {
setHeaderHidden(true); setIsHeaderHidden(true);
setSelectedTabIndex(0); setSelectedTabIndex(0);
hideBuyOptions(); hideBuyOptions();
unpinTabs(); unpinTabs();
@ -280,7 +280,7 @@ const StarsBalanceModal = ({
function handleScroll(e: React.UIEvent<HTMLDivElement>) { function handleScroll(e: React.UIEvent<HTMLDivElement>) {
const { scrollTop } = e.currentTarget; const { scrollTop } = e.currentTarget;
setHeaderHidden(scrollTop <= 150); setIsHeaderHidden(scrollTop <= 150);
if (tabsRef.current) { if (tabsRef.current) {
const { top: tabsTop } = tabsRef.current.getBoundingClientRect(); const { top: tabsTop } = tabsRef.current.getBoundingClientRect();

View File

@ -59,11 +59,11 @@ const StarsGiftModal: FC<OwnProps & StateProps> = ({
const oldLang = useOldLang(); const oldLang = useOldLang();
const [selectedOption, setSelectedOption] = useState<ApiStarTopupOption | undefined>(); const [selectedOption, setSelectedOption] = useState<ApiStarTopupOption | undefined>();
const [isHeaderHidden, setHeaderHidden] = useState(true); const [isHeaderHidden, setIsHeaderHidden] = useState(true);
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {
setHeaderHidden(true); setIsHeaderHidden(true);
} }
}, [isOpen]); }, [isOpen]);
@ -115,7 +115,7 @@ const StarsGiftModal: FC<OwnProps & StateProps> = ({
function handleScroll(e: React.UIEvent<HTMLDivElement>) { function handleScroll(e: React.UIEvent<HTMLDivElement>) {
const { scrollTop } = e.currentTarget; const { scrollTop } = e.currentTarget;
setHeaderHidden(scrollTop <= 150); setIsHeaderHidden(scrollTop <= 150);
} }
const handleClose = useLastCallback(() => { const handleClose = useLastCallback(() => {

View File

@ -10,6 +10,7 @@ import { NNBSP } from '../../../../config';
import { getPeerTitle } from '../../../../global/helpers/peers'; import { getPeerTitle } from '../../../../global/helpers/peers';
import { selectPeer } from '../../../../global/selectors'; import { selectPeer } from '../../../../global/selectors';
import { formatDateToString } from '../../../../util/dates/oldDateFormat'; import { formatDateToString } from '../../../../util/dates/oldDateFormat';
import { getServerTime } from '../../../../util/serverTime';
import { formatInteger } from '../../../../util/textFormat'; import { formatInteger } from '../../../../util/textFormat';
import renderText from '../../../common/helpers/renderText'; import renderText from '../../../common/helpers/renderText';
@ -51,7 +52,7 @@ const StarsSubscriptionItem = ({ subscription }: OwnProps) => {
return undefined; return undefined;
} }
const hasExpired = until < Date.now() / 1000; const hasExpired = until < getServerTime();
const formattedDate = formatDateToString(until * 1000, lang.code, true, 'long'); const formattedDate = formatDateToString(until * 1000, lang.code, true, 'long');
return ( return (

View File

@ -1,11 +1,9 @@
import type React from '../../../lib/teact/teact';
import { import {
memo, useEffect, memo, useEffect, useState,
useState } from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ApiDraft, ApiStarsAmount, ApiTypeCurrencyAmount } from '../../../api/types'; import type { ApiDraft, ApiPeer, ApiStarsAmount, ApiTypeCurrencyAmount } from '../../../api/types';
import type { ApiPeer } from '../../../api/types';
import type { TabState } from '../../../global/types'; import type { TabState } from '../../../global/types';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
@ -108,7 +106,7 @@ const SuggestMessageModal = ({
const oldLang = useOldLang(); const oldLang = useOldLang();
const isCurrencyStars = selectedCurrency === STARS_CURRENCY_CODE; const isCurrencyStars = selectedCurrency === STARS_CURRENCY_CODE;
const now = Math.floor(Date.now() / 1000); const now = getServerTime();
const minAt = (now + futureMin) * 1000; const minAt = (now + futureMin) * 1000;
const maxAt = (now + futureMax) * 1000; const maxAt = (now + futureMax) * 1000;
const defaultSelectedTime = (now + futureMin * 2) * 1000; const defaultSelectedTime = (now + futureMin * 2) * 1000;

View File

@ -13,6 +13,7 @@ import { selectChatMessage, selectIsMonoforumAdmin, selectSender } from '../../.
import { formatScheduledDateTime, formatShortDuration } from '../../../util/dates/oldDateFormat'; import { formatScheduledDateTime, formatShortDuration } from '../../../util/dates/oldDateFormat';
import { convertTonFromNanos } from '../../../util/formatCurrency'; import { convertTonFromNanos } from '../../../util/formatCurrency';
import { formatStarsAsText, formatTonAsText } from '../../../util/localization/format'; import { formatStarsAsText, formatTonAsText } from '../../../util/localization/format';
import { getServerTime } from '../../../util/serverTime';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
@ -62,7 +63,7 @@ const SuggestedPostApprovalModal = ({
const oldLang = useOldLang(); const oldLang = useOldLang();
const [isCalendarOpened, openCalendar, closeCalendar] = useFlag(); const [isCalendarOpened, openCalendar, closeCalendar] = useFlag();
const now = Math.floor(Date.now() / 1000); const now = getServerTime();
const minAt = (now + futureMin) * 1000; const minAt = (now + futureMin) * 1000;
const maxAt = (now + futureMax) * 1000; const maxAt = (now + futureMax) * 1000;
const defaultSelectedTime = now + futureMin * 2; const defaultSelectedTime = now + futureMin * 2;

View File

@ -64,7 +64,7 @@ const UrlAuthModal = ({
); );
const isOpen = Boolean(modal?.url && modal?.request) && !isMatchCodePrecheckPending; const isOpen = Boolean(modal?.url && modal?.request) && !isMatchCodePrecheckPending;
const [isWriteAccessChecked, setWriteAccessChecked] = useState( const [isWriteAccessChecked, setIsWriteAccessChecked] = useState(
() => Boolean(modalRequest?.shouldRequestWriteAccess), () => Boolean(modalRequest?.shouldRequestWriteAccess),
); );
const [selectedMatchCode, setSelectedMatchCode] = useState<string | undefined>(); const [selectedMatchCode, setSelectedMatchCode] = useState<string | undefined>();
@ -84,7 +84,7 @@ const UrlAuthModal = ({
return; return;
} }
setWriteAccessChecked(Boolean(modalRequest.shouldRequestWriteAccess)); setIsWriteAccessChecked(Boolean(modalRequest.shouldRequestWriteAccess));
setSelectedMatchCode(undefined); setSelectedMatchCode(undefined);
setDialogState('closed'); setDialogState('closed');
}, [modalRequest]); }, [modalRequest]);
@ -152,7 +152,7 @@ const UrlAuthModal = ({
}); });
const handleTriggerWriteAccess = useLastCallback(() => { const handleTriggerWriteAccess = useLastCallback(() => {
setWriteAccessChecked(!isWriteAccessChecked); setIsWriteAccessChecked(!isWriteAccessChecked);
}); });
if (!renderingRequest) { if (!renderingRequest) {

View File

@ -234,27 +234,27 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
}, queryId ? PROLONG_INTERVAL : undefined, true); }, queryId ? PROLONG_INTERVAL : undefined, true);
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const sendEventCallback = useRef<((event: WebAppOutboundEvent) => void) | null>(null); const sendEventCallbackRef = useRef<((event: WebAppOutboundEvent) => void) | null>(null);
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const reloadFrameCallback = useRef<((url: string) => void) | null>(null); const reloadFrameCallbackRef = useRef<((url: string) => void) | null>(null);
const registerSendEventCallback = useLastCallback((callback: (event: WebAppOutboundEvent) => void) => { const registerSendEventCallback = useLastCallback((callback: (event: WebAppOutboundEvent) => void) => {
sendEventCallback.current = callback; sendEventCallbackRef.current = callback;
}); });
const sendEvent = useLastCallback((event: WebAppOutboundEvent) => { const sendEvent = useLastCallback((event: WebAppOutboundEvent) => {
if (sendEventCallback.current) { if (sendEventCallbackRef.current) {
sendEventCallback.current(event); sendEventCallbackRef.current(event);
} }
}); });
const registerReloadFrameCallback = useLastCallback((callback: (url: string) => void) => { const registerReloadFrameCallback = useLastCallback((callback: (url: string) => void) => {
reloadFrameCallback.current = callback; reloadFrameCallbackRef.current = callback;
}); });
const reloadFrame = useLastCallback((url: string) => { const reloadFrame = useLastCallback((url: string) => {
if (reloadFrameCallback.current) { if (reloadFrameCallbackRef.current) {
reloadFrameCallback.current(url); reloadFrameCallbackRef.current(url);
} }
}); });

View File

@ -94,7 +94,6 @@ const POPUP_RESET_DELAY = 2000; // 2s
const APP_NAME_DISPLAY_DURATION = 3800; const APP_NAME_DISPLAY_DURATION = 3800;
const SANDBOX_ATTRIBUTES = [ const SANDBOX_ATTRIBUTES = [
'allow-scripts', 'allow-scripts',
'allow-same-origin',
'allow-popups', 'allow-popups',
'allow-forms', 'allow-forms',
'allow-modals', 'allow-modals',
@ -857,79 +856,79 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
const hideDirection = (isHorizontalLayout const hideDirection = (isHorizontalLayout
&& (!shouldHideMainButton && !shouldHideSecondaryButton)) ? 'horizontal' : 'vertical'; && (!shouldHideMainButton && !shouldHideSecondaryButton)) ? 'horizontal' : 'vertical';
const mainButtonChangeTimeout = useRef<ReturnType<typeof setTimeout>>(); const mainButtonChangeTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const mainButtonFastTimeout = useRef<ReturnType<typeof setTimeout>>(); const mainButtonFastTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const secondaryButtonChangeTimeout = useRef<ReturnType<typeof setTimeout>>(); const secondaryButtonChangeTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const secondaryButtonFastTimeout = useRef<ReturnType<typeof setTimeout>>(); const secondaryButtonFastTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const appNameDisplayTimeout = useRef<ReturnType<typeof setTimeout>>(); const appNameDisplayTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
useEffect(() => { useEffect(() => {
if (isFullscreen && isOpen && Boolean(activeWebAppName)) { if (isFullscreen && isOpen && Boolean(activeWebAppName)) {
setShouldShowAppNameInFullscreen(true); setShouldShowAppNameInFullscreen(true);
if (appNameDisplayTimeout.current) { if (appNameDisplayTimeoutRef.current) {
clearTimeout(appNameDisplayTimeout.current); clearTimeout(appNameDisplayTimeoutRef.current);
} }
appNameDisplayTimeout.current = setTimeout(() => { appNameDisplayTimeoutRef.current = setTimeout(() => {
setShouldShowAppNameInFullscreen(false); setShouldShowAppNameInFullscreen(false);
appNameDisplayTimeout.current = undefined; appNameDisplayTimeoutRef.current = undefined;
}, APP_NAME_DISPLAY_DURATION); }, APP_NAME_DISPLAY_DURATION);
} else { } else {
setShouldShowAppNameInFullscreen(false); setShouldShowAppNameInFullscreen(false);
if (appNameDisplayTimeout.current) { if (appNameDisplayTimeoutRef.current) {
clearTimeout(appNameDisplayTimeout.current); clearTimeout(appNameDisplayTimeoutRef.current);
appNameDisplayTimeout.current = undefined; appNameDisplayTimeoutRef.current = undefined;
} }
} }
return () => { return () => {
if (appNameDisplayTimeout.current) { if (appNameDisplayTimeoutRef.current) {
clearTimeout(appNameDisplayTimeout.current); clearTimeout(appNameDisplayTimeoutRef.current);
} }
}; };
}, [isFullscreen, isOpen, activeWebAppName]); }, [isFullscreen, isOpen, activeWebAppName]);
useEffect(() => { useEffect(() => {
if (mainButtonChangeTimeout.current) clearTimeout(mainButtonChangeTimeout.current); if (mainButtonChangeTimeoutRef.current) clearTimeout(mainButtonChangeTimeoutRef.current);
if (mainButtonFastTimeout.current) clearTimeout(mainButtonFastTimeout.current); if (mainButtonFastTimeoutRef.current) clearTimeout(mainButtonFastTimeoutRef.current);
if (isMainButtonVisible) { if (isMainButtonVisible) {
mainButtonFastTimeout.current = setTimeout(() => { mainButtonFastTimeoutRef.current = setTimeout(() => {
setShouldShowMainButton(true); setShouldShowMainButton(true);
}, 35); }, 35);
setShouldHideMainButton(false); setShouldHideMainButton(false);
mainButtonChangeTimeout.current = setTimeout(() => { mainButtonChangeTimeoutRef.current = setTimeout(() => {
setShouldDecreaseWebFrameSize(true); setShouldDecreaseWebFrameSize(true);
}, MAIN_BUTTON_ANIMATION_TIME); }, MAIN_BUTTON_ANIMATION_TIME);
} }
if (!isMainButtonVisible) { if (!isMainButtonVisible) {
setShouldShowMainButton(false); setShouldShowMainButton(false);
mainButtonChangeTimeout.current = setTimeout(() => { mainButtonChangeTimeoutRef.current = setTimeout(() => {
setShouldHideMainButton(true); setShouldHideMainButton(true);
}, MAIN_BUTTON_ANIMATION_TIME); }, MAIN_BUTTON_ANIMATION_TIME);
} }
}, [isMainButtonVisible]); }, [isMainButtonVisible]);
useEffect(() => { useEffect(() => {
if (secondaryButtonChangeTimeout.current) clearTimeout(secondaryButtonChangeTimeout.current); if (secondaryButtonChangeTimeoutRef.current) clearTimeout(secondaryButtonChangeTimeoutRef.current);
if (secondaryButtonFastTimeout.current) clearTimeout(secondaryButtonFastTimeout.current); if (secondaryButtonFastTimeoutRef.current) clearTimeout(secondaryButtonFastTimeoutRef.current);
if (isSecondaryButtonVisible) { if (isSecondaryButtonVisible) {
secondaryButtonFastTimeout.current = setTimeout(() => { secondaryButtonFastTimeoutRef.current = setTimeout(() => {
setShouldShowSecondaryButton(true); setShouldShowSecondaryButton(true);
}, 35); }, 35);
setShouldHideSecondaryButton(false); setShouldHideSecondaryButton(false);
secondaryButtonChangeTimeout.current = setTimeout(() => { secondaryButtonChangeTimeoutRef.current = setTimeout(() => {
setShouldDecreaseWebFrameSize(true); setShouldDecreaseWebFrameSize(true);
}, MAIN_BUTTON_ANIMATION_TIME); }, MAIN_BUTTON_ANIMATION_TIME);
} }
if (!isSecondaryButtonVisible) { if (!isSecondaryButtonVisible) {
setShouldShowSecondaryButton(false); setShouldShowSecondaryButton(false);
secondaryButtonChangeTimeout.current = setTimeout(() => { secondaryButtonChangeTimeoutRef.current = setTimeout(() => {
setShouldHideSecondaryButton(true); setShouldHideSecondaryButton(true);
}, MAIN_BUTTON_ANIMATION_TIME); }, MAIN_BUTTON_ANIMATION_TIME);
} }

View File

@ -4,26 +4,26 @@ import useLastCallback from '../../../../hooks/useLastCallback';
export default function usePopupLimit(sequentialLimit: number, resetAfter: number) { export default function usePopupLimit(sequentialLimit: number, resetAfter: number) {
const [unlockPopupsAt, setUnlockPopupsAt] = useState(0); const [unlockPopupsAt, setUnlockPopupsAt] = useState(0);
const sequentialCalls = useRef(0); const sequentialCallsRef = useRef(0);
const lastClosedDate = useRef(0); const lastClosedDateRef = useRef(0);
const handlePopupOpened = useLastCallback(() => { const handlePopupOpened = useLastCallback(() => {
const now = Date.now(); const now = Date.now();
if (now - lastClosedDate.current > resetAfter) { if (now - lastClosedDateRef.current > resetAfter) {
sequentialCalls.current = 0; sequentialCallsRef.current = 0;
} }
sequentialCalls.current += 1; sequentialCallsRef.current += 1;
if (sequentialCalls.current >= sequentialLimit) { if (sequentialCallsRef.current >= sequentialLimit) {
setUnlockPopupsAt(now + resetAfter); setUnlockPopupsAt(now + resetAfter);
} }
}); });
const handlePopupClosed = useLastCallback(() => { const handlePopupClosed = useLastCallback(() => {
if (unlockPopupsAt < Date.now()) { // Prevent confused user from extending lock time if (unlockPopupsAt < Date.now()) { // Prevent confused user from extending lock time
lastClosedDate.current = Date.now(); lastClosedDateRef.current = Date.now();
} }
}); });

View File

@ -54,8 +54,8 @@ const useWebAppFrame = (
updateContentSettings, updateContentSettings,
} = getActions(); } = getActions();
const isReloadSupported = useRef<boolean>(false); const isReloadSupportedRef = useRef<boolean>(false);
const reloadTimeout = useRef<ReturnType<typeof setTimeout>>(); const reloadTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const ignoreEventsRef = useRef<boolean>(false); const ignoreEventsRef = useRef<boolean>(false);
const lastFrameSizeRef = useRef<{ width: number; height: number; isResizing?: boolean }>(); const lastFrameSizeRef = useRef<{ width: number; height: number; isResizing?: boolean }>();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
@ -98,11 +98,11 @@ const useWebAppFrame = (
}); });
const reloadFrame = useCallback((url: string) => { const reloadFrame = useCallback((url: string) => {
if (isReloadSupported.current) { if (isReloadSupportedRef.current) {
sendEvent({ sendEvent({
eventType: 'reload_iframe', eventType: 'reload_iframe',
}); });
reloadTimeout.current = setTimeout(() => { reloadTimeoutRef.current = setTimeout(() => {
forceReloadFrame(url); forceReloadFrame(url);
}, RELOAD_TIMEOUT); }, RELOAD_TIMEOUT);
return; return;
@ -213,11 +213,11 @@ const useWebAppFrame = (
if (eventType === 'iframe_ready') { if (eventType === 'iframe_ready') {
const scrollbarColor = getComputedStyle(document.body).getPropertyValue('--color-scrollbar'); const scrollbarColor = getComputedStyle(document.body).getPropertyValue('--color-scrollbar');
sendCustomStyle(SCROLLBAR_STYLE.replace(/%SCROLLBAR_COLOR%/g, scrollbarColor)); sendCustomStyle(SCROLLBAR_STYLE.replace(/%SCROLLBAR_COLOR%/g, scrollbarColor));
isReloadSupported.current = Boolean(eventData.reload_supported); isReloadSupportedRef.current = Boolean(eventData.reload_supported);
} }
if (eventType === 'iframe_will_reload') { if (eventType === 'iframe_will_reload') {
clearTimeout(reloadTimeout.current); clearTimeout(reloadTimeoutRef.current);
} }
if (eventType === 'web_app_data_send') { if (eventType === 'web_app_data_send') {

View File

@ -79,7 +79,7 @@ const ConfirmPayment: FC<OwnProps> = ({
src={url} src={url}
title={lang('Checkout.WebConfirmation.Title')} title={lang('Checkout.WebConfirmation.Title')}
allow="payment" allow="payment"
sandbox="allow-modals allow-forms allow-scripts allow-same-origin allow-top-navigation" sandbox="allow-modals allow-forms allow-scripts allow-top-navigation"
className="ConfirmPayment__content" className="ConfirmPayment__content"
/> />
</div> </div>

View File

@ -46,22 +46,22 @@ const StarGiftCollectionList = ({
} }
}); });
if (!collections || collections.length === 0) {
return undefined;
}
const items: TabItem[] = useMemo(() => [ const items: TabItem[] = useMemo(() => [
{ {
id: 'all', id: 'all',
title: lang('AllGiftsCategory'), title: lang('AllGiftsCategory'),
}, },
...collections.map((collection) => ({ ...(collections || []).map((collection) => ({
id: String(collection.collectionId), id: String(collection.collectionId),
title: collection.title, title: collection.title,
sticker: collection.icon, sticker: collection.icon,
})), })),
], [collections, lang]); ], [collections, lang]);
if (!collections || collections.length === 0) {
return undefined;
}
const selectedItemId = activeCollectionId ? String(activeCollectionId) : 'all'; const selectedItemId = activeCollectionId ? String(activeCollectionId) : 'all';
return ( return (

View File

@ -43,8 +43,10 @@ const JoinRequest: FC<OwnProps & StateProps> = ({
const lang = useOldLang(); const lang = useOldLang();
const fullName = getUserFullName(user); const fullName = getUserFullName(user);
// eslint-disable-next-line @eslint-react/purity
const fixedDate = (date - getServerTime()) * 1000 + Date.now(); const fixedDate = (date - getServerTime()) * 1000 + Date.now();
// eslint-disable-next-line @eslint-react/purity
const dateString = isToday(new Date(fixedDate)) const dateString = isToday(new Date(fixedDate))
? formatTime(lang, fixedDate) : formatHumanDate(lang, fixedDate, true, false, true); ? formatTime(lang, fixedDate) : formatHumanDate(lang, fixedDate, true, false, true);

View File

@ -1,4 +1,3 @@
import type { FC } from '../../../lib/teact/teact';
import { memo, useEffect } from '../../../lib/teact/teact'; import { memo, useEffect } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
@ -32,7 +31,7 @@ type StateProps = {
const BULLET = '\u2022'; const BULLET = '\u2022';
const ManageInviteInfo: FC<OwnProps & StateProps> = ({ const ManageInviteInfo = ({
chatId, chatId,
invite, invite,
importers, importers,
@ -40,7 +39,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
isChannel, isChannel,
isActive, isActive,
onClose, onClose,
}) => { }: OwnProps & StateProps) => {
const { const {
loadChatInviteImporters, loadChatInviteImporters,
loadChatInviteRequesters, loadChatInviteRequesters,
@ -51,6 +50,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
const { const {
usage = 0, usageLimit, link, adminId, usage = 0, usageLimit, link, adminId,
} = invite || {}; } = invite || {};
// eslint-disable-next-line @eslint-react/purity
const expireDate = invite?.expireDate && (invite.expireDate - getServerTime()) * 1000 + Date.now(); const expireDate = invite?.expireDate && (invite.expireDate - getServerTime()) * 1000 + Date.now();
const isExpired = ((invite?.expireDate || 0) - getServerTime()) < 0; const isExpired = ((invite?.expireDate || 0) - getServerTime()) < 0;

View File

@ -183,7 +183,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
const { const {
usage = 0, usageLimit, expireDate, isPermanent, requested, isRevoked, usage = 0, usageLimit, expireDate, isPermanent, requested, isRevoked,
} = invite; } = invite;
let text = ''; let text;
if (!isRevoked && usageLimit && usage < usageLimit) { if (!isRevoked && usageLimit && usage < usageLimit) {
text = oldLang('CanJoin', usageLimit - usage); text = oldLang('CanJoin', usageLimit - usage);
} else if (usage) { } else if (usage) {

View File

@ -1,7 +1,4 @@
import type { FC } from '../../../lib/teact/teact.ts'; import { memo, useState } from '../../../lib/teact/teact';
import type React from '../../../lib/teact/teact.ts';
import { useState } from '../../../lib/teact/teact.ts';
import { memo } from '../../../lib/teact/teact.ts';
import type { ApiChat } from '../../../api/types/index'; import type { ApiChat } from '../../../api/types/index';
import type { ManagementScreens } from '../../../types/index'; import type { ManagementScreens } from '../../../types/index';
@ -10,13 +7,13 @@ import { ChatCreationProgress } from '../../../types/index';
import { getActions, withGlobal } from '../../../global/index'; import { getActions, withGlobal } from '../../../global/index';
import { selectChat, selectTabState } from '../../../global/selectors/index'; import { selectChat, selectTabState } from '../../../global/selectors/index';
import useHistoryBack from '../../../hooks/useHistoryBack.ts'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang.ts'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback.ts'; import useLastCallback from '../../../hooks/useLastCallback';
import AvatarEditable from '../../ui/AvatarEditable.tsx'; import AvatarEditable from '../../ui/AvatarEditable';
import FloatingActionButton from '../../ui/FloatingActionButton.tsx'; import FloatingActionButton from '../../ui/FloatingActionButton';
import InputText from '../../ui/InputText.tsx'; import InputText from '../../ui/InputText';
type OwnProps = { type OwnProps = {
chatId: string; chatId: string;
@ -31,13 +28,13 @@ type StateProps = {
creationError?: string; creationError?: string;
}; };
const NewDiscussionGroup: FC<OwnProps & StateProps> = ({ const NewDiscussionGroup = ({
chat, chat,
onClose, onClose,
isActive, isActive,
creationProgress, creationProgress,
creationError, creationError,
}) => { }: OwnProps & StateProps) => {
const { createChannel } = getActions(); const { createChannel } = getActions();
const lang = useLang(); const lang = useLang();

View File

@ -65,8 +65,8 @@ function MessageStatistics({
const lang = useOldLang(); const lang = useOldLang();
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
const [isReady, setIsReady] = useState(false); const [isReady, setIsReady] = useState(false);
const loadedCharts = useRef<Set<string>>(new Set()); const loadedChartsRef = useRef<Set<string>>(new Set());
const errorCharts = useRef<Set<string>>(new Set()); const errorChartsRef = useRef<Set<string>>(new Set());
const { loadMessageStatistics, loadMessagePublicForwards, loadStatisticsAsyncGraph } = getActions(); const { loadMessageStatistics, loadMessagePublicForwards, loadStatisticsAsyncGraph } = getActions();
const forceUpdate = useForceUpdate(); const forceUpdate = useForceUpdate();
@ -79,8 +79,8 @@ function MessageStatistics({
useEffect(() => { useEffect(() => {
if (!isActive || messageId) { if (!isActive || messageId) {
loadedCharts.current.clear(); loadedChartsRef.current.clear();
errorCharts.current.clear(); errorChartsRef.current.clear();
setIsReady(false); setIsReady(false);
} }
}, [isActive, messageId]); }, [isActive, messageId]);
@ -125,13 +125,13 @@ function MessageStatistics({
const isAsync = graph.graphType === 'async'; const isAsync = graph.graphType === 'async';
const isError = graph.graphType === 'error'; const isError = graph.graphType === 'error';
if (isAsync || loadedCharts.current.has(name)) { if (isAsync || loadedChartsRef.current.has(name)) {
return; return;
} }
if (isError) { if (isError) {
loadedCharts.current.add(name); loadedChartsRef.current.add(name);
errorCharts.current.add(name); errorChartsRef.current.add(name);
return; return;
} }
@ -150,7 +150,7 @@ function MessageStatistics({
}, },
); );
loadedCharts.current.add(name); loadedChartsRef.current.add(name);
}); });
forceUpdate(); forceUpdate();
@ -176,11 +176,11 @@ function MessageStatistics({
> >
<StatisticsOverview statistics={statistics} type="message" title={lang('StatisticOverview')} /> <StatisticsOverview statistics={statistics} type="message" title={lang('StatisticOverview')} />
{(!loadedCharts.current.size || !statistics.publicForwardsData) && <Loading />} {(!loadedChartsRef.current.size || !statistics.publicForwardsData) && <Loading />}
<div ref={containerRef}> <div ref={containerRef}>
{GRAPHS.map((graph) => { {GRAPHS.map((graph) => {
const isGraphReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph); const isGraphReady = loadedChartsRef.current.has(graph) && !errorChartsRef.current.has(graph);
return ( return (
<div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} /> <div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} />
); );

View File

@ -70,8 +70,8 @@ const MonetizationStatistics = ({
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
const [isReady, setIsReady] = useState(false); const [isReady, setIsReady] = useState(false);
const loadedCharts = useRef<Set<string>>(new Set()); const loadedChartsRef = useRef<Set<string>>(new Set());
const errorCharts = useRef<Set<string>>(new Set()); const errorChartsRef = useRef<Set<string>>(new Set());
const forceUpdate = useForceUpdate(); const forceUpdate = useForceUpdate();
const [isAboutMonetizationModalOpen, openAboutMonetizationModal, closeAboutMonetizationModal] = useFlag(false); const [isAboutMonetizationModalOpen, openAboutMonetizationModal, closeAboutMonetizationModal] = useFlag(false);
@ -104,8 +104,8 @@ const MonetizationStatistics = ({
}); });
} }
loadedCharts.current.clear(); loadedChartsRef.current.clear();
errorCharts.current.clear(); errorChartsRef.current.clear();
if (!statistics || !containerRef.current) { if (!statistics || !containerRef.current) {
return; return;
@ -119,13 +119,13 @@ const MonetizationStatistics = ({
const isAsync = graph.graphType === 'async'; const isAsync = graph.graphType === 'async';
const isError = graph.graphType === 'error'; const isError = graph.graphType === 'error';
if (isAsync || loadedCharts.current.has(name)) { if (isAsync || loadedChartsRef.current.has(name)) {
return; return;
} }
if (isError) { if (isError) {
loadedCharts.current.add(name); loadedChartsRef.current.add(name);
errorCharts.current.add(name); errorChartsRef.current.add(name);
return; return;
} }
@ -135,7 +135,7 @@ const MonetizationStatistics = ({
...graph, ...graph,
}); });
loadedCharts.current.add(name); loadedChartsRef.current.add(name);
containerRef.current!.children[index].classList.remove(styles.hidden); containerRef.current!.children[index].classList.remove(styles.hidden);
}); });
@ -243,7 +243,7 @@ const MonetizationStatistics = ({
} }
/> />
{!loadedCharts.current.size && <Loading />} {!loadedChartsRef.current.size && <Loading />}
<div ref={containerRef} className={styles.section}> <div ref={containerRef} className={styles.section}>
{MONETIZATION_GRAPHS.filter(Boolean).map((graph) => ( {MONETIZATION_GRAPHS.filter(Boolean).map((graph) => (

View File

@ -96,8 +96,8 @@ const Statistics = ({
const lang = useOldLang(); const lang = useOldLang();
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
const [isReady, setIsReady] = useState(false); const [isReady, setIsReady] = useState(false);
const loadedCharts = useRef<Set<string>>(new Set()); const loadedChartsRef = useRef<Set<string>>(new Set());
const errorCharts = useRef<Set<string>>(new Set()); const errorChartsRef = useRef<Set<string>>(new Set());
const { loadStatistics, loadStatisticsAsyncGraph } = getActions(); const { loadStatistics, loadStatisticsAsyncGraph } = getActions();
const forceUpdate = useForceUpdate(); const forceUpdate = useForceUpdate();
@ -161,13 +161,13 @@ const Statistics = ({
const isAsync = graph.graphType === 'async'; const isAsync = graph.graphType === 'async';
const isError = graph.graphType === 'error'; const isError = graph.graphType === 'error';
if (isAsync || loadedCharts.current.has(name)) { if (isAsync || loadedChartsRef.current.has(name)) {
return; return;
} }
if (isError) { if (isError) {
loadedCharts.current.add(name); loadedChartsRef.current.add(name);
errorCharts.current.add(name); errorChartsRef.current.add(name);
return; return;
} }
@ -186,7 +186,7 @@ const Statistics = ({
}, },
); );
loadedCharts.current.add(name); loadedChartsRef.current.add(name);
containerRef.current!.children[index].classList.remove(styles.hidden); containerRef.current!.children[index].classList.remove(styles.hidden);
}); });
@ -207,11 +207,11 @@ const Statistics = ({
/> />
)} )}
{!loadedCharts.current.size && <Loading />} {!loadedChartsRef.current.size && <Loading />}
<div ref={containerRef}> <div ref={containerRef}>
{graphs.map((graph) => { {graphs.map((graph) => {
const isGraphReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph); const isGraphReady = loadedChartsRef.current.has(graph) && !errorChartsRef.current.has(graph);
return ( return (
<div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} /> <div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} />
); );

View File

@ -70,8 +70,8 @@ function StoryStatistics({
const lang = useOldLang(); const lang = useOldLang();
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
const [isReady, setIsReady] = useState(false); const [isReady, setIsReady] = useState(false);
const loadedCharts = useRef<Set<string>>(new Set()); const loadedChartsRef = useRef<Set<string>>(new Set());
const errorCharts = useRef<Set<string>>(new Set()); const errorChartsRef = useRef<Set<string>>(new Set());
const { loadStoryStatistics, loadStoryPublicForwards, loadStatisticsAsyncGraph } = getActions(); const { loadStoryStatistics, loadStoryPublicForwards, loadStatisticsAsyncGraph } = getActions();
const forceUpdate = useForceUpdate(); const forceUpdate = useForceUpdate();
@ -84,8 +84,8 @@ function StoryStatistics({
useEffect(() => { useEffect(() => {
if (!isActive || storyId) { if (!isActive || storyId) {
loadedCharts.current.clear(); loadedChartsRef.current.clear();
errorCharts.current.clear(); errorChartsRef.current.clear();
setIsReady(false); setIsReady(false);
} }
}, [isActive, storyId]); }, [isActive, storyId]);
@ -130,13 +130,13 @@ function StoryStatistics({
const isAsync = graph.graphType === 'async'; const isAsync = graph.graphType === 'async';
const isError = graph.graphType === 'error'; const isError = graph.graphType === 'error';
if (isAsync || loadedCharts.current.has(name)) { if (isAsync || loadedChartsRef.current.has(name)) {
return; return;
} }
if (isError) { if (isError) {
loadedCharts.current.add(name); loadedChartsRef.current.add(name);
errorCharts.current.add(name); errorChartsRef.current.add(name);
return; return;
} }
@ -155,7 +155,7 @@ function StoryStatistics({
}, },
); );
loadedCharts.current.add(name); loadedChartsRef.current.add(name);
}); });
forceUpdate(); forceUpdate();
@ -181,11 +181,11 @@ function StoryStatistics({
> >
<StatisticsOverview statistics={statistics} type="story" title={lang('StatisticOverview')} /> <StatisticsOverview statistics={statistics} type="story" title={lang('StatisticOverview')} />
{!loadedCharts.current.size && <Loading />} {!loadedChartsRef.current.size && <Loading />}
<div ref={containerRef}> <div ref={containerRef}>
{GRAPHS.map((graph) => { {GRAPHS.map((graph) => {
const isGraphReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph); const isGraphReady = loadedChartsRef.current.has(graph) && !errorChartsRef.current.has(graph);
return ( return (
<div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} /> <div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} />
); );

View File

@ -47,21 +47,21 @@ const StoryAlbumList = ({
} }
}); });
if (!albums?.length) {
return undefined;
}
const items: TabItem[] = useMemo(() => [ const items: TabItem[] = useMemo(() => [
{ {
id: 'all', id: 'all',
title: lang('AllStoriesCategory'), title: lang('AllStoriesCategory'),
}, },
...albums.map((album) => ({ ...(albums || []).map((album) => ({
id: String(album.albumId), id: String(album.albumId),
title: album.title, title: album.title,
})), })),
], [albums, lang]); ], [albums, lang]);
if (!albums?.length) {
return undefined;
}
const selectedItemId = selectedAlbumId ? String(selectedAlbumId) : 'all'; const selectedItemId = selectedAlbumId ? String(selectedAlbumId) : 'all';
return ( return (

View File

@ -1,3 +1,4 @@
/* eslint-disable @eslint-react/purity */
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import { withGlobal } from '../../global'; import { withGlobal } from '../../global';

View File

@ -1,3 +1,4 @@
/* eslint-disable @eslint-react/purity */
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import { useState } from '../../lib/teact/teact'; import { useState } from '../../lib/teact/teact';

View File

@ -1,3 +1,4 @@
/* eslint-disable @eslint-react/purity */
import { useState } from '../../lib/teact/teact'; import { useState } from '../../lib/teact/teact';
import { withGlobal } from '../../global'; import { withGlobal } from '../../global';

View File

@ -1,3 +1,4 @@
/* eslint-disable @eslint-react/purity */
import { import {
createContext, memo, useState, createContext, memo, useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';

View File

@ -1,3 +1,4 @@
/* eslint-disable @eslint-react/purity */
import { useRef, useState } from '../../lib/teact/teact'; import { useRef, useState } from '../../lib/teact/teact';
export function App() { export function App() {

View File

@ -1,3 +1,4 @@
/* eslint-disable @eslint-react/purity */
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import { getGlobal, setGlobal, withGlobal } from '../../global'; import { getGlobal, setGlobal, withGlobal } from '../../global';

View File

@ -1,3 +1,4 @@
/* eslint-disable @eslint-react/purity */
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import { useRef, useState } from '../../lib/teact/teact'; import { useRef, useState } from '../../lib/teact/teact';
@ -93,7 +94,7 @@ function MyComponent({
onChange: (newValue: string) => void; onChange: (newValue: string) => void;
onDelete: NoneToVoidFunction; onDelete: NoneToVoidFunction;
}) { }) {
const id = useRef(String(Math.random()).slice(-3)); const idRef = useRef(String(Math.random()).slice(-3));
const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value); onChange(e.target.value);
@ -103,7 +104,7 @@ function MyComponent({
<li> <li>
<input type="text" value={value} size={3} onChange={handleChange} /> <input type="text" value={value} size={3} onChange={handleChange} />
<input type="button" value="x" onClick={onDelete} /> <input type="button" value="x" onClick={onDelete} />
{id.current} {idRef.current}
</li> </li>
); );
} }

View File

@ -1,3 +1,4 @@
/* eslint-disable @eslint-react/purity */
import { useState } from '../../lib/teact/teact'; import { useState } from '../../lib/teact/teact';
import Portal from '../ui/Portal'; import Portal from '../ui/Portal';

View File

@ -136,7 +136,6 @@ const Checkbox: FC<OwnProps> = ({
return ( return (
<> <>
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
<label <label
className={labelClassName} className={labelClassName}
dir={lang.isRtl ? 'rtl' : undefined} dir={lang.isRtl ? 'rtl' : undefined}

View File

@ -35,9 +35,9 @@ const ImageCropper = ({
}: OwnProps) => { }: OwnProps) => {
const [imagePosition, setImagePosition] = useState({ x: 0, y: 0 }); const [imagePosition, setImagePosition] = useState({ x: 0, y: 0 });
const [zoom, setZoom] = useState(MIN_ZOOM); const [zoom, setZoom] = useState(MIN_ZOOM);
const isDragging = useRef(false); const isDraggingRef = useRef(false);
const lastMousePosition = useRef({ x: 0, y: 0 }); const lastMousePositionRef = useRef({ x: 0, y: 0 });
const lastImagePosition = useRef({ x: 0, y: 0 }); const lastImagePositionRef = useRef({ x: 0, y: 0 });
const { width: windowWidth } = useWindowSize(); const { width: windowWidth } = useWindowSize();
const previewContainerSize = Math.min(PREVIEW_SIZE, windowWidth - MODAL_INLINE_PADDING * 2); const previewContainerSize = Math.min(PREVIEW_SIZE, windowWidth - MODAL_INLINE_PADDING * 2);
@ -71,9 +71,9 @@ const ImageCropper = ({
}; };
const startDrag = (e: any) => { const startDrag = (e: any) => {
isDragging.current = true; isDraggingRef.current = true;
lastMousePosition.current = getPointerPosition(e); lastMousePositionRef.current = getPointerPosition(e);
lastImagePosition.current = { ...imagePosition }; lastImagePositionRef.current = { ...imagePosition };
document.addEventListener('mousemove', moveDrag); document.addEventListener('mousemove', moveDrag);
document.addEventListener('touchmove', moveDrag); document.addEventListener('touchmove', moveDrag);
document.addEventListener('mouseup', endDrag); document.addEventListener('mouseup', endDrag);
@ -81,19 +81,19 @@ const ImageCropper = ({
}; };
const moveDrag = useLastCallback((e: any) => { const moveDrag = useLastCallback((e: any) => {
if ('touches' in e && e.touches.length > 1) return; if ('touches' in e && e.touches.length > 1) return;
if (!isDragging.current) return; if (!isDraggingRef.current) return;
const { x: mouseX, y: mouseY } = getPointerPosition(e); const { x: mouseX, y: mouseY } = getPointerPosition(e);
const deltaX = mouseX - lastMousePosition.current.x; const deltaX = mouseX - lastMousePositionRef.current.x;
const deltaY = mouseY - lastMousePosition.current.y; const deltaY = mouseY - lastMousePositionRef.current.y;
const newPosition = clampPosition( const newPosition = clampPosition(
lastImagePosition.current.x + deltaX, lastImagePositionRef.current.x + deltaX,
lastImagePosition.current.y + deltaY, lastImagePositionRef.current.y + deltaY,
previewImageSize, previewImageSize,
); );
setImagePosition(newPosition); setImagePosition(newPosition);
}); });
const endDrag = useLastCallback(() => { const endDrag = useLastCallback(() => {
isDragging.current = false; isDraggingRef.current = false;
document.removeEventListener('mousemove', moveDrag); document.removeEventListener('mousemove', moveDrag);
document.removeEventListener('touchmove', moveDrag); document.removeEventListener('touchmove', moveDrag);
document.removeEventListener('mouseup', endDrag); document.removeEventListener('mouseup', endDrag);

View File

@ -1,9 +1,5 @@
import type {
ElementRef } from '../../lib/teact/teact';
import type React from '../../lib/teact/teact';
import { import {
beginHeavyAnimation, beginHeavyAnimation, type ElementRef, memo, useEffect, useRef,
type FC, memo, useEffect, useRef,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import type { MenuPositionOptions } from '../../hooks/useMenuPosition'; import type { MenuPositionOptions } from '../../hooks/useMenuPosition';
@ -56,7 +52,7 @@ type OwnProps =
const ANIMATION_DURATION = 200; const ANIMATION_DURATION = 200;
const Menu: FC<OwnProps> = ({ const Menu = ({
ref: externalRef, ref: externalRef,
shouldCloseFast, shouldCloseFast,
isOpen, isOpen,
@ -78,7 +74,7 @@ const Menu: FC<OwnProps> = ({
onMouseEnterBackdrop, onMouseEnterBackdrop,
nested, nested,
...positionOptions ...positionOptions
}) => { }: OwnProps) => {
const { isTouchScreen } = useAppLayout(); const { isTouchScreen } = useAppLayout();
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();

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