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 eslintReact from '@eslint-react/eslint-plugin';
import stylisticJs from '@stylistic/eslint-plugin';
import { globalIgnores } from 'eslint/config';
import importsPlugin from 'eslint-plugin-import';
import { defineConfig, globalIgnores } from 'eslint/config';
import { importX } from 'eslint-plugin-import-x';
import jestPlugin from 'eslint-plugin-jest';
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
import noNullPlugin from 'eslint-plugin-no-null';
import reactPlugin from 'eslint-plugin-react';
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 ttMultitabPlugin from 'eslint-plugin-tt-multitab';
import unusedImports from 'eslint-plugin-unused-imports';
import tseslint from 'typescript-eslint';
export default tseslint.config(
export default defineConfig(
eslint.configs.recommended,
tseslint.configs.recommendedTypeChecked,
tseslint.configs.stylistic,
reactPlugin.configs.flat.recommended,
reactPlugin.configs.flat['jsx-runtime'],
reactXPlugin.configs['recommended-type-checked'],
jsxA11yPlugin.flatConfigs.recommended,
eslintReact.configs['recommended-typescript'],
importX.flatConfigs.recommended,
importX.flatConfigs.typescript,
ttMultitabPlugin.configs.recommended,
stylisticJs.configs.customize({
semi: true,
@ -68,6 +65,16 @@ export default tseslint.config(
'no-prototype-builtins': 'off',
'no-undef': '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/operator-linebreak': 'off',
'@stylistic/max-len': ['error', {
@ -170,6 +177,8 @@ export default tseslint.config(
'@typescript-eslint/prefer-promise-reject-errors': 'off',
'@typescript-eslint/unbound-method': 'off',
'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-static-deps/exhaustive-deps': [
'error',
@ -185,40 +194,24 @@ export default tseslint.config(
},
},
],
'react/prop-types': 'off',
'react/no-unknown-property': 'off',
'react/display-name': 'off',
'react/jsx-key': 'off',
'react/jsx-curly-spacing': [
'error',
{
when: 'never',
attributes: true,
children: true,
allowMultiline: true,
},
],
'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',
'@eslint-react/exhaustive-deps': 'off',
'@eslint-react/set-state-in-effect': 'off',
'@eslint-react/unsupported-syntax': 'off',
'@eslint-react/no-clone-element': 'off',
'@eslint-react/component-hook-factories': 'off',
'@eslint-react/no-use-context': 'off',
'@eslint-react/no-context-provider': 'off',
'@eslint-react/no-array-index-key': 'off',
'@eslint-react/web-api/no-leaked-timeout': 'off',
'@eslint-react/no-missing-key': 'off',
'@eslint-react/no-nested-component-definitions': 'off',
'@eslint-react/no-unused-props': 'off',
'@eslint-react/no-leaked-conditional-rendering': 'error',
},
plugins: {
'no-null': noNullPlugin,
'simple-import-sort': simpleImportSortPlugin,
import: importsPlugin,
'unused-imports': unusedImports,
react: reactPlugin,
'react-hooks-static-deps': reactHooksStaticDeps,
jest: jestPlugin,
'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": {
"@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-typescript": "^7.28.5",
"@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",
"@mytonwallet/stylelint-whole-pixel": "github:mytonwallet-org/stylelint-whole-pixel#18b0b8c",
"@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/webpack-plugin": "5.29.0",
"@stylistic/eslint-plugin": "^5.7.1",
"@stylistic/stylelint-config": "^4.0.0",
"@stylistic/stylelint-plugin": "^5.0.1",
"@tauri-apps/cli": "^2.9.6",
"@stylistic/eslint-plugin": "^5.10.0",
"@stylistic/stylelint-config": "^5.0.0",
"@stylistic/stylelint-plugin": "^5.1.0",
"@tauri-apps/cli": "^2.10.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/hast": "^3.0.4",
"@types/jest": "^30.0.0",
"@types/node": "^25.1.0",
"@types/react": "^19.2.10",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/webpack": "^5.28.5",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/parser": "^8.54.0",
"@typescript-eslint/eslint-plugin": "^8.58.0",
"@typescript-eslint/parser": "^8.58.0",
"@webpack-cli/serve": "^3.0.1",
"autoprefixer": "^10.4.24",
"babel-loader": "^10.0.0",
"autoprefixer": "^10.4.27",
"babel-loader": "^10.1.1",
"babel-plugin-transform-import-meta": "^2.3.3",
"buffer": "^6.0.3",
"concurrently": "^9.2.1",
"copy-webpack-plugin": "^13.0.1",
"copy-webpack-plugin": "^14.0.0",
"cross-env": "^10.1.0",
"css-loader": "^7.1.3",
"dotenv": "^17.2.3",
"eslint": "^9.39.2",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.12.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"css-loader": "^7.1.4",
"dotenv": "^17.3.1",
"eslint": "^10.1.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-jest": "^29.15.1",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks-static-deps": "git+https://github.com/zubiden/eslint-plugin-react-hooks-static-deps#c16f35b",
"eslint-plugin-react-x": "^2.8.1",
"eslint-plugin-react-hooks-static-deps": "github:zubiden/eslint-plugin-react-hooks-static-deps#160ac4a",
"eslint-plugin-react-x": "^3.0.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-tt-multitab": "git+https://github.com/zubiden/eslint-plugin-tt-multitab#137cf50",
"eslint-plugin-unused-imports": "^4.3.0",
"eslint-plugin-tt-multitab": "github:zubiden/eslint-plugin-tt-multitab#137cf50",
"eslint-plugin-unused-imports": "^4.4.1",
"fake-indexeddb": "^6.2.5",
"@twbs/fantasticon": "^3.1.0",
"git-revision-webpack-plugin": "^5.0.0",
"gitlog": "^5.1.0",
"html-webpack-plugin": "^5.6.6",
"husky": "^9.1.7",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"lint-staged": "^16.2.7",
"mini-css-extract-plugin": "^2.10.0",
"postcss": "^8.5.6",
"jest": "^30.3.0",
"jest-environment-jsdom": "^30.3.0",
"lint-staged": "^16.4.0",
"mini-css-extract-plugin": "^2.10.2",
"postcss": "^8.5.8",
"postcss-load-config": "^6.0.1",
"postcss-loader": "^8.2.0",
"postcss-loader": "^8.2.1",
"postcss-modules": "^6.0.1",
"react": "^19.2.4",
"sass": "^1.97.3",
"sass-loader": "^16.0.6",
"sass": "^1.98.0",
"sass-loader": "^16.0.7",
"script-loader": "^0.7.2",
"serve": "^14.2.5",
"stylelint": "^17.1.0",
"stylelint-config-clean-order": "^8.0.0",
"serve": "^14.2.6",
"stylelint": "^17.6.0",
"stylelint-config-clean-order": "^8.0.1",
"stylelint-config-recommended-scss": "^17.0.0",
"stylelint-declaration-block-no-ignored-properties": "^3.0.0",
"stylelint-group-selectors": "^1.0.10",
"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",
"tsx": "^4.21.0",
"typescript": "5.9.3",
"typescript-eslint": "^8.54.0",
"user-agent-data-types": "^0.4.2",
"webpack": "^5.104.1",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.0",
"user-agent-data-types": "^0.4.3",
"webpack": "^5.105.4",
"webpack-dev-server": "^5.2.3"
},
"dependencies": {
"@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-process": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.4",
"@tauri-apps/plugin-updater": "^2.9.0",
"@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-updater": "^2.10.0",
"async-mutex": "^0.5.0",
"emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#30529a2",
"fflate": "^0.8.2",
"idb-keyval": "^6.2.2",
"lowlight": "^3.3.0",
"music-metadata": "^11.11.1",
"music-metadata": "^11.12.3",
"opus-recorder": "github:Ajaxy/opus-recorder#116830a",
"os-browserify": "^0.3.0",
"path-browserify": "^1.0.1",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1702,7 +1702,7 @@ export async function searchMessagesGlobal({
const messages = result.messages.map(buildApiMessage).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) {
totalCount = result.count;
} else {
@ -1761,7 +1761,7 @@ export async function searchPublicPosts({
const messages = result.messages.map(buildApiMessage).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) {
totalCount = result.count;
} else {

View File

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

View File

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

View File

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

View File

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

View File

@ -218,9 +218,13 @@ const PhoneCall = ({
useEffect(() => {
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 (
<Modal

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -80,11 +80,16 @@ export function useStickerPickerObservers(
freezeForSet();
freezeForShowingItems();
} else {
setTimeout(() => {
const timeout = window.setTimeout(() => {
unfreezeForShowingItems();
unfreezeForSet();
}, SLIDE_TRANSITION_DURATION);
return () => {
clearTimeout(timeout);
};
}
return undefined;
}, [freezeForSet, freezeForShowingItems, isHidden, unfreezeForSet, unfreezeForShowingItems]);
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';
@ -33,7 +33,7 @@ const ChatTags = ({
const visibleFolderIds = orderedFolderIds.slice(0, MAX_VISIBLE_TAGS);
const remainingCount = orderedFolderIds.length - visibleFolderIds.length;
const getFolderTitle = useCallback((folder: ApiChatFolder) => {
function getFolderTitle(folder: ApiChatFolder) {
let text = folder.title.text;
let entities = folder.title.entities;
@ -56,7 +56,7 @@ const ChatTags = ({
noCustomEmojiPlayback: folder.noTitleAnimations,
emojiSize: CUSTOM_EMOJI_SIZE,
});
}, [isFoldersSidebarShown]);
};
return (
<div className={styles.wrapper}>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -251,7 +251,7 @@ function MiddleColumn({
const oldLang = useOldLang();
const lang = useLang();
const [dropAreaState, setDropAreaState] = useState(DropAreaState.None);
const [isScrollDownNeeded, setIsScrollDownShown] = useState(false);
const [isScrollDownNeeded, setIsScrollDownNeeded] = useState(false);
const isScrollDownShown = isScrollDownNeeded && (!isMobile || !hasActiveMiddleSearch);
const [isNotchShown, setIsNotchShown] = useState<boolean | undefined>();
const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false);
@ -571,7 +571,7 @@ function MiddleColumn({
type={renderingMessageListType!}
isComments={isComments}
canPost={renderingCanPost!}
onScrollDownToggle={setIsScrollDownShown}
onScrollDownToggle={setIsScrollDownNeeded}
onNotchToggle={setIsNotchShown}
isReady={isReady}
isContactRequirePremium={isContactRequirePremium}
@ -900,18 +900,23 @@ function useIsReady(
const forceUpdate = useForceUpdate();
const willSwitchMessageList = prevTransitionKey !== undefined && prevTransitionKey !== currentTransitionKey;
if (willSwitchMessageList) {
if (withAnimations) {
setIsReady(false);
// 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 {
useSyncEffect(() => {
if (!willSwitchMessageList) return;
if (!withAnimations) {
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(() => {
if (!withAnimations) {

View File

@ -129,7 +129,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
} = getActions();
const lang = useOldLang();
const isBackButtonActive = useRef(true);
const isBackButtonActiveRef = useRef(true);
const { isDesktop, isTablet } = useAppLayout();
const { width: windowWidth } = useWindowSize();
@ -164,7 +164,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
const setBackButtonActive = useLastCallback(() => {
setTimeout(() => {
isBackButtonActive.current = true;
isBackButtonActiveRef.current = true;
}, BACK_BUTTON_INACTIVE_TIME);
});
@ -187,10 +187,10 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
});
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
isBackButtonActive.current = false;
isBackButtonActiveRef.current = false;
if (isMobile) {
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
messageInput?.blur();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ export default function useContainerHeight(containerRef: ElementRef<HTMLDivEleme
}
}, [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;
}
setTimeout(markShown, appearanceOrder * MESSAGE_APPEARANCE_DELAY);
}, [appearanceOrder, markShown, noAppearanceAnimation]);
const timeout = setTimeout(markShown, appearanceOrder * MESSAGE_APPEARANCE_DELAY);
return () => {
clearTimeout(timeout);
};
}, [appearanceOrder, noAppearanceAnimation]);
const { ref: refWithTransition } = useShowTransition({
isOpen: isShown,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -42,6 +42,10 @@ const MiddleSearchResult = ({
}: OwnProps) => {
const lang = useLang();
const handleClick = useLastCallback(() => {
onClick(message);
});
if (peer && !message) {
const username = getMainUsername(peer);
@ -83,10 +87,6 @@ const MiddleSearchResult = ({
const senderPeer = shouldShowChat ? messageChat : peer;
const senderName = shouldShowChat && peer ? getMessageSenderName(lang, message.chatId, peer) : undefined;
const handleClick = useLastCallback(() => {
onClick(message);
});
return (
<div
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 { getUserFullName } from '../../../global/helpers';
import { getPeerTitle, isApiPeerChat, isApiPeerUser } from '../../../global/helpers/peers';
import { selectTabState } from '../../../global/selectors';
import { selectPeer, selectUserFullInfo } from '../../../global/selectors';
import { selectPeer, selectTabState, selectUserFullInfo } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { getNextArrowReplacement } from '../../../util/localization/format';
import { throttle } from '../../../util/schedulers';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,9 @@
import type React from '../../../lib/teact/teact';
import {
memo, useEffect,
useState } from '../../../lib/teact/teact';
memo, useEffect, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiDraft, ApiStarsAmount, ApiTypeCurrencyAmount } from '../../../api/types';
import type { ApiPeer } from '../../../api/types';
import type { ApiDraft, ApiPeer, ApiStarsAmount, ApiTypeCurrencyAmount } from '../../../api/types';
import type { TabState } from '../../../global/types';
import { MAIN_THREAD_ID } from '../../../api/types';
@ -108,7 +106,7 @@ const SuggestMessageModal = ({
const oldLang = useOldLang();
const isCurrencyStars = selectedCurrency === STARS_CURRENCY_CODE;
const now = Math.floor(Date.now() / 1000);
const now = getServerTime();
const minAt = (now + futureMin) * 1000;
const maxAt = (now + futureMax) * 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 { convertTonFromNanos } from '../../../util/formatCurrency';
import { formatStarsAsText, formatTonAsText } from '../../../util/localization/format';
import { getServerTime } from '../../../util/serverTime';
import renderText from '../../common/helpers/renderText';
import useFlag from '../../../hooks/useFlag';
@ -62,7 +63,7 @@ const SuggestedPostApprovalModal = ({
const oldLang = useOldLang();
const [isCalendarOpened, openCalendar, closeCalendar] = useFlag();
const now = Math.floor(Date.now() / 1000);
const now = getServerTime();
const minAt = (now + futureMin) * 1000;
const maxAt = (now + futureMax) * 1000;
const defaultSelectedTime = now + futureMin * 2;

View File

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

View File

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

View File

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

View File

@ -4,26 +4,26 @@ import useLastCallback from '../../../../hooks/useLastCallback';
export default function usePopupLimit(sequentialLimit: number, resetAfter: number) {
const [unlockPopupsAt, setUnlockPopupsAt] = useState(0);
const sequentialCalls = useRef(0);
const lastClosedDate = useRef(0);
const sequentialCallsRef = useRef(0);
const lastClosedDateRef = useRef(0);
const handlePopupOpened = useLastCallback(() => {
const now = Date.now();
if (now - lastClosedDate.current > resetAfter) {
sequentialCalls.current = 0;
if (now - lastClosedDateRef.current > resetAfter) {
sequentialCallsRef.current = 0;
}
sequentialCalls.current += 1;
sequentialCallsRef.current += 1;
if (sequentialCalls.current >= sequentialLimit) {
if (sequentialCallsRef.current >= sequentialLimit) {
setUnlockPopupsAt(now + resetAfter);
}
});
const handlePopupClosed = useLastCallback(() => {
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,
} = getActions();
const isReloadSupported = useRef<boolean>(false);
const reloadTimeout = useRef<ReturnType<typeof setTimeout>>();
const isReloadSupportedRef = useRef<boolean>(false);
const reloadTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const ignoreEventsRef = useRef<boolean>(false);
const lastFrameSizeRef = useRef<{ width: number; height: number; isResizing?: boolean }>();
const windowSize = useWindowSize();
@ -98,11 +98,11 @@ const useWebAppFrame = (
});
const reloadFrame = useCallback((url: string) => {
if (isReloadSupported.current) {
if (isReloadSupportedRef.current) {
sendEvent({
eventType: 'reload_iframe',
});
reloadTimeout.current = setTimeout(() => {
reloadTimeoutRef.current = setTimeout(() => {
forceReloadFrame(url);
}, RELOAD_TIMEOUT);
return;
@ -213,11 +213,11 @@ const useWebAppFrame = (
if (eventType === 'iframe_ready') {
const scrollbarColor = getComputedStyle(document.body).getPropertyValue('--color-scrollbar');
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') {
clearTimeout(reloadTimeout.current);
clearTimeout(reloadTimeoutRef.current);
}
if (eventType === 'web_app_data_send') {

View File

@ -79,7 +79,7 @@ const ConfirmPayment: FC<OwnProps> = ({
src={url}
title={lang('Checkout.WebConfirmation.Title')}
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"
/>
</div>

View File

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

View File

@ -43,8 +43,10 @@ const JoinRequest: FC<OwnProps & StateProps> = ({
const lang = useOldLang();
const fullName = getUserFullName(user);
// eslint-disable-next-line @eslint-react/purity
const fixedDate = (date - getServerTime()) * 1000 + Date.now();
// eslint-disable-next-line @eslint-react/purity
const dateString = isToday(new Date(fixedDate))
? 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 { getActions, withGlobal } from '../../../global';
@ -32,7 +31,7 @@ type StateProps = {
const BULLET = '\u2022';
const ManageInviteInfo: FC<OwnProps & StateProps> = ({
const ManageInviteInfo = ({
chatId,
invite,
importers,
@ -40,7 +39,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
isChannel,
isActive,
onClose,
}) => {
}: OwnProps & StateProps) => {
const {
loadChatInviteImporters,
loadChatInviteRequesters,
@ -51,6 +50,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
const {
usage = 0, usageLimit, link, adminId,
} = invite || {};
// eslint-disable-next-line @eslint-react/purity
const expireDate = invite?.expireDate && (invite.expireDate - getServerTime()) * 1000 + Date.now();
const isExpired = ((invite?.expireDate || 0) - getServerTime()) < 0;

View File

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

View File

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

View File

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

View File

@ -70,8 +70,8 @@ const MonetizationStatistics = ({
const containerRef = useRef<HTMLDivElement>();
const [isReady, setIsReady] = useState(false);
const loadedCharts = useRef<Set<string>>(new Set());
const errorCharts = useRef<Set<string>>(new Set());
const loadedChartsRef = useRef<Set<string>>(new Set());
const errorChartsRef = useRef<Set<string>>(new Set());
const forceUpdate = useForceUpdate();
const [isAboutMonetizationModalOpen, openAboutMonetizationModal, closeAboutMonetizationModal] = useFlag(false);
@ -104,8 +104,8 @@ const MonetizationStatistics = ({
});
}
loadedCharts.current.clear();
errorCharts.current.clear();
loadedChartsRef.current.clear();
errorChartsRef.current.clear();
if (!statistics || !containerRef.current) {
return;
@ -119,13 +119,13 @@ const MonetizationStatistics = ({
const isAsync = graph.graphType === 'async';
const isError = graph.graphType === 'error';
if (isAsync || loadedCharts.current.has(name)) {
if (isAsync || loadedChartsRef.current.has(name)) {
return;
}
if (isError) {
loadedCharts.current.add(name);
errorCharts.current.add(name);
loadedChartsRef.current.add(name);
errorChartsRef.current.add(name);
return;
}
@ -135,7 +135,7 @@ const MonetizationStatistics = ({
...graph,
});
loadedCharts.current.add(name);
loadedChartsRef.current.add(name);
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}>
{MONETIZATION_GRAPHS.filter(Boolean).map((graph) => (

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
/* eslint-disable @eslint-react/purity */
import type { FC } 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 { withGlobal } from '../../global';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,9 +35,9 @@ const ImageCropper = ({
}: OwnProps) => {
const [imagePosition, setImagePosition] = useState({ x: 0, y: 0 });
const [zoom, setZoom] = useState(MIN_ZOOM);
const isDragging = useRef(false);
const lastMousePosition = useRef({ x: 0, y: 0 });
const lastImagePosition = useRef({ x: 0, y: 0 });
const isDraggingRef = useRef(false);
const lastMousePositionRef = useRef({ x: 0, y: 0 });
const lastImagePositionRef = useRef({ x: 0, y: 0 });
const { width: windowWidth } = useWindowSize();
const previewContainerSize = Math.min(PREVIEW_SIZE, windowWidth - MODAL_INLINE_PADDING * 2);
@ -71,9 +71,9 @@ const ImageCropper = ({
};
const startDrag = (e: any) => {
isDragging.current = true;
lastMousePosition.current = getPointerPosition(e);
lastImagePosition.current = { ...imagePosition };
isDraggingRef.current = true;
lastMousePositionRef.current = getPointerPosition(e);
lastImagePositionRef.current = { ...imagePosition };
document.addEventListener('mousemove', moveDrag);
document.addEventListener('touchmove', moveDrag);
document.addEventListener('mouseup', endDrag);
@ -81,19 +81,19 @@ const ImageCropper = ({
};
const moveDrag = useLastCallback((e: any) => {
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 deltaX = mouseX - lastMousePosition.current.x;
const deltaY = mouseY - lastMousePosition.current.y;
const deltaX = mouseX - lastMousePositionRef.current.x;
const deltaY = mouseY - lastMousePositionRef.current.y;
const newPosition = clampPosition(
lastImagePosition.current.x + deltaX,
lastImagePosition.current.y + deltaY,
lastImagePositionRef.current.x + deltaX,
lastImagePositionRef.current.y + deltaY,
previewImageSize,
);
setImagePosition(newPosition);
});
const endDrag = useLastCallback(() => {
isDragging.current = false;
isDraggingRef.current = false;
document.removeEventListener('mousemove', moveDrag);
document.removeEventListener('touchmove', moveDrag);
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 {
beginHeavyAnimation,
type FC, memo, useEffect, useRef,
beginHeavyAnimation, type ElementRef, memo, useEffect, useRef,
} from '../../lib/teact/teact';
import type { MenuPositionOptions } from '../../hooks/useMenuPosition';
@ -56,7 +52,7 @@ type OwnProps =
const ANIMATION_DURATION = 200;
const Menu: FC<OwnProps> = ({
const Menu = ({
ref: externalRef,
shouldCloseFast,
isOpen,
@ -78,7 +74,7 @@ const Menu: FC<OwnProps> = ({
onMouseEnterBackdrop,
nested,
...positionOptions
}) => {
}: OwnProps) => {
const { isTouchScreen } = useAppLayout();
const containerRef = useRef<HTMLDivElement>();

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