[Perf] Middle Column: Various optimizations for opening chat
This commit is contained in:
parent
2f63a0fcf0
commit
d872273def
@ -1,6 +1,6 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
||||
memo, useCallback, useEffect, useMemo, useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
@ -55,17 +55,17 @@ import resetScroll, { patchChromiumScroll } from '../../util/resetScroll';
|
||||
import fastSmoothScroll, { isAnimatingScroll } from '../../util/fastSmoothScroll';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
|
||||
import { useStateRef } from '../../hooks/useStateRef';
|
||||
import useSyncEffect from '../../hooks/useSyncEffect';
|
||||
import useStickyDates from './hooks/useStickyDates';
|
||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useWindowSize from '../../hooks/useWindowSize';
|
||||
import useInterval from '../../hooks/useInterval';
|
||||
import useNativeCopySelectedMessages from '../../hooks/useNativeCopySelectedMessages';
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
|
||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||
import useResizeObserver from '../../hooks/useResizeObserver';
|
||||
import useContainerHeight from './hooks/useContainerHeight';
|
||||
|
||||
import Loading from '../ui/Loading';
|
||||
import MessageListContent from './MessageListContent';
|
||||
@ -182,10 +182,11 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
|
||||
// We update local cached `scrollOffsetRef` when opening chat.
|
||||
// Then we update global version every second on scrolling.
|
||||
const scrollOffsetRef = useRef<number>((type === 'thread'
|
||||
&& selectScrollOffset(getGlobal(), chatId, threadId))
|
||||
const scrollOffsetRef = useRef<number>(
|
||||
(type === 'thread' && selectScrollOffset(getGlobal(), chatId, threadId))
|
||||
|| selectLastScrollOffset(getGlobal(), chatId, threadId)
|
||||
|| 0);
|
||||
|| 0,
|
||||
);
|
||||
|
||||
const anchorIdRef = useRef<string>();
|
||||
const anchorTopRef = useRef<number>();
|
||||
@ -196,8 +197,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
const isScrollTopJustUpdatedRef = useRef(false);
|
||||
const shouldAnimateAppearanceRef = useRef(Boolean(lastMessage));
|
||||
|
||||
const [containerHeight, setContainerHeight] = useState<number | undefined>();
|
||||
|
||||
const botInfoPhotoUrl = useMedia(botInfo?.photo ? getBotCoverMediaHash(botInfo.photo) : undefined);
|
||||
const botInfoGifUrl = useMedia(botInfo?.gif ? getDocumentMediaHash(botInfo.gif) : undefined);
|
||||
const botInfoDimensions = botInfo?.photo ? getPhotoFullDimensions(botInfo.photo) : botInfo?.gif
|
||||
@ -247,8 +246,11 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const viewportIds = threadTopMessageId && threadFirstMessageId !== threadTopMessageId
|
||||
const viewportIds = (
|
||||
threadTopMessageId
|
||||
&& threadFirstMessageId !== threadTopMessageId
|
||||
&& (!messageIds[0] || threadFirstMessageId === messageIds[0])
|
||||
)
|
||||
? [threadTopMessageId, ...messageIds]
|
||||
: messageIds;
|
||||
|
||||
@ -342,18 +344,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
updateStickyDates, hasTools, getForceNextPinnedInHeader, onPinnedIntersectionChange, type, chatId, threadId,
|
||||
]);
|
||||
|
||||
// Container resize observer (caused by Composer reply/webpage panels)
|
||||
const handleResize = useCallback((entry: ResizeObserverEntry) => {
|
||||
setContainerHeight(entry.contentRect.height);
|
||||
}, []);
|
||||
useResizeObserver(containerRef, handleResize);
|
||||
|
||||
// Memorize height for scroll animation
|
||||
const { height: windowHeight } = useWindowSize();
|
||||
|
||||
useEffect(() => {
|
||||
containerRef.current!.dataset.normalHeight = String(containerRef.current!.offsetHeight);
|
||||
}, [windowHeight, canPost]);
|
||||
const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive);
|
||||
|
||||
// Initial message loading
|
||||
useEffect(() => {
|
||||
@ -377,8 +368,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [isChatLoaded, messageIds, loadMoreAround, focusingId, isRestricted]);
|
||||
|
||||
// Remember scroll position before repositioning it
|
||||
useSyncEffect(() => {
|
||||
const rememberScrollPositionRef = useStateRef(() => {
|
||||
if (!messageIds || !listItemElementsRef.current) {
|
||||
return;
|
||||
}
|
||||
@ -395,13 +385,25 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
|
||||
anchorIdRef.current = anchor.id;
|
||||
anchorTopRef.current = anchor.getBoundingClientRect().top;
|
||||
// This should match deps for `useLayoutEffectWithPrevDeps` below
|
||||
}, [messageIds, isViewportNewest, containerHeight, hasTools]);
|
||||
});
|
||||
|
||||
useSyncEffect(
|
||||
() => rememberScrollPositionRef.current(),
|
||||
// This will run before modifying content and should match deps for `useLayoutEffectWithPrevDeps` below
|
||||
[messageIds, isViewportNewest, hasTools, rememberScrollPositionRef],
|
||||
);
|
||||
useEffect(
|
||||
() => rememberScrollPositionRef.current(),
|
||||
// This is only needed to react on signal updates
|
||||
[getContainerHeight, rememberScrollPositionRef],
|
||||
);
|
||||
|
||||
// Handles updated message list, takes care of scroll repositioning
|
||||
useLayoutEffectWithPrevDeps(([
|
||||
prevMessageIds, prevIsViewportNewest, prevContainerHeight,
|
||||
]) => {
|
||||
useLayoutEffectWithPrevDeps(([prevMessageIds, prevIsViewportNewest]) => {
|
||||
const containerHeight = getContainerHeight();
|
||||
const prevContainerHeight = prevContainerHeightRef.current;
|
||||
prevContainerHeightRef.current = containerHeight;
|
||||
|
||||
const container = containerRef.current!;
|
||||
listItemElementsRef.current = Array.from(container.querySelectorAll<HTMLDivElement>('.message-list-item'));
|
||||
|
||||
@ -476,7 +478,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
console.time('scrollTop');
|
||||
}
|
||||
|
||||
const isResized = prevContainerHeight !== undefined && prevContainerHeight !== containerHeight;
|
||||
const isResized = prevContainerHeight && prevContainerHeight !== containerHeight;
|
||||
const anchor = anchorIdRef.current && container.querySelector(`#${anchorIdRef.current}`);
|
||||
const unreadDivider = (
|
||||
!anchor
|
||||
@ -523,7 +525,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
console.timeEnd('scrollTop');
|
||||
}
|
||||
// This should match deps for `useSyncEffect` above
|
||||
}, [messageIds, isViewportNewest, containerHeight, hasTools]);
|
||||
}, [messageIds, isViewportNewest, getContainerHeight, prevContainerHeightRef, hasTools]);
|
||||
|
||||
useEffectWithPrevDeps(([prevIsSelectModeActive]) => {
|
||||
if (prevIsSelectModeActive !== undefined) {
|
||||
|
||||
@ -208,6 +208,14 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
|
||||
const [isNotchShown, setIsNotchShown] = useState<boolean | undefined>();
|
||||
const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false);
|
||||
|
||||
const {
|
||||
onIntersectionChanged,
|
||||
onFocusPinnedMessage,
|
||||
getCurrentPinnedIndexes,
|
||||
getLoadingPinnedId,
|
||||
getForceNextPinnedInHeader,
|
||||
} = usePinnedMessage(chatId, threadId, pinnedIds);
|
||||
|
||||
const isMobileSearchActive = isMobile && hasCurrentTextSearch;
|
||||
const closeAnimationDuration = isMobile ? LAYER_ANIMATION_DURATION_MS : undefined;
|
||||
const hasTools = hasPinned && (
|
||||
@ -235,6 +243,10 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
|
||||
const renderingIsChannel = usePrevDuringAnimation(isChannel, closeAnimationDuration);
|
||||
const renderingShouldJoinToSend = usePrevDuringAnimation(shouldJoinToSend, closeAnimationDuration);
|
||||
const renderingShouldSendJoinRequest = usePrevDuringAnimation(shouldSendJoinRequest, closeAnimationDuration);
|
||||
const renderingOnPinnedIntersectionChange = usePrevDuringAnimation(
|
||||
chatId ? onIntersectionChanged : undefined,
|
||||
closeAnimationDuration,
|
||||
);
|
||||
|
||||
const prevTransitionKey = usePrevious(currentTransitionKey);
|
||||
|
||||
@ -356,14 +368,6 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const customBackgroundValue = useCustomBackground(theme, customBackground);
|
||||
|
||||
const {
|
||||
onIntersectionChanged,
|
||||
onFocusPinnedMessage,
|
||||
getCurrentPinnedIndexes,
|
||||
getLoadingPinnedId,
|
||||
getForceNextPinnedInHeader,
|
||||
} = usePinnedMessage(chatId, threadId, pinnedIds);
|
||||
|
||||
const className = buildClassName(
|
||||
renderingHasTools && 'has-header-tools',
|
||||
MASK_IMAGE_DISABLED ? 'mask-image-disabled' : 'mask-image-enabled',
|
||||
@ -449,13 +453,13 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
|
||||
style={customBackgroundValue ? `--custom-background: ${customBackgroundValue}` : undefined}
|
||||
/>
|
||||
<div id="middle-column-portals" />
|
||||
{renderingChatId && renderingThreadId && (
|
||||
{Boolean(renderingChatId && renderingThreadId) && (
|
||||
<>
|
||||
<div className="messages-layout" onDragEnter={renderingCanPost ? handleDragEnter : undefined}>
|
||||
<MiddleHeader
|
||||
chatId={renderingChatId}
|
||||
threadId={renderingThreadId}
|
||||
messageListType={renderingMessageListType}
|
||||
chatId={renderingChatId!}
|
||||
threadId={renderingThreadId!}
|
||||
messageListType={renderingMessageListType!}
|
||||
isReady={isReady}
|
||||
isMobile={isMobile}
|
||||
getCurrentPinnedIndexes={getCurrentPinnedIndexes}
|
||||
@ -471,25 +475,25 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
<MessageList
|
||||
key={`${renderingChatId}-${renderingThreadId}-${renderingMessageListType}`}
|
||||
chatId={renderingChatId}
|
||||
threadId={renderingThreadId}
|
||||
type={renderingMessageListType}
|
||||
canPost={renderingCanPost}
|
||||
chatId={renderingChatId!}
|
||||
threadId={renderingThreadId!}
|
||||
type={renderingMessageListType!}
|
||||
canPost={renderingCanPost!}
|
||||
hasTools={renderingHasTools}
|
||||
onFabToggle={setIsFabShown}
|
||||
onNotchToggle={setIsNotchShown}
|
||||
isReady={isReady}
|
||||
withBottomShift={withMessageListBottomShift}
|
||||
withDefaultBg={Boolean(!customBackground && !backgroundColor)}
|
||||
onPinnedIntersectionChange={onIntersectionChanged}
|
||||
onPinnedIntersectionChange={renderingOnPinnedIntersectionChange!}
|
||||
getForceNextPinnedInHeader={getForceNextPinnedInHeader}
|
||||
/>
|
||||
<div className={footerClassName}>
|
||||
{renderingCanPost && (
|
||||
<Composer
|
||||
chatId={renderingChatId}
|
||||
threadId={renderingThreadId}
|
||||
messageListType={renderingMessageListType}
|
||||
chatId={renderingChatId!}
|
||||
threadId={renderingThreadId!}
|
||||
messageListType={renderingMessageListType!}
|
||||
dropAreaState={dropAreaState}
|
||||
onDropHide={handleHideDropArea}
|
||||
isReady={isReady}
|
||||
@ -519,8 +523,9 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isMobile
|
||||
&& (renderingCanSubscribe || (renderingShouldJoinToSend && !renderingShouldSendJoinRequest)) && (
|
||||
{(
|
||||
isMobile && (renderingCanSubscribe || (renderingShouldJoinToSend && !renderingShouldSendJoinRequest))
|
||||
) && (
|
||||
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button
|
||||
size="tiny"
|
||||
@ -584,7 +589,7 @@ const MiddleColumn: FC<OwnProps & StateProps> = ({
|
||||
</Transition>
|
||||
|
||||
<FloatingActionButtons
|
||||
isShown={renderingIsFabShown}
|
||||
isShown={renderingIsFabShown!}
|
||||
canPost={renderingCanPost}
|
||||
withExtraShift={withExtraShift}
|
||||
/>
|
||||
|
||||
28
src/components/middle/hooks/useContainerHeight.ts
Normal file
28
src/components/middle/hooks/useContainerHeight.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import type { RefObject } from 'react';
|
||||
import { useCallback, useEffect, useRef } from '../../../lib/teact/teact';
|
||||
|
||||
import useSignal from '../../../hooks/useSignal';
|
||||
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||
|
||||
export default function useContainerHeight(containerRef: RefObject<HTMLDivElement>, isComposerVisible: boolean) {
|
||||
const [getContainerHeight, setContainerHeight] = useSignal<number | undefined>();
|
||||
|
||||
// Container resize observer (caused by Composer reply/webpage panels)
|
||||
const handleResize = useCallback((entry: ResizeObserverEntry) => {
|
||||
setContainerHeight(entry.contentRect.height);
|
||||
}, [setContainerHeight]);
|
||||
useResizeObserver(containerRef, handleResize);
|
||||
|
||||
useEffect(() => {
|
||||
const currentNormalHeight = Number(containerRef.current!.dataset.normalHeight) || 0;
|
||||
const containerHeight = getContainerHeight();
|
||||
|
||||
if (containerHeight && containerHeight > currentNormalHeight && isComposerVisible) {
|
||||
containerRef.current!.dataset.normalHeight = String(containerHeight);
|
||||
}
|
||||
}, [isComposerVisible, containerRef, getContainerHeight]);
|
||||
|
||||
const prevContainerHeight = useRef<number>();
|
||||
|
||||
return [getContainerHeight, prevContainerHeight] as const;
|
||||
}
|
||||
@ -576,7 +576,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
text, photo, video, audio, voice, document, sticker, contact, poll, webPage, invoice, location, action, game,
|
||||
} = getMessageContent(message);
|
||||
|
||||
const { result: detectedLanguage } = useTextLanguage(areTranslationsEnabled ? text?.text : undefined);
|
||||
const detectedLanguage = useTextLanguage(areTranslationsEnabled ? text?.text : undefined);
|
||||
|
||||
const { isPending: isTranslationPending, translatedText } = useMessageTranslation(
|
||||
chatTranslations, chatId, messageId, requestedTranslationLanguage,
|
||||
|
||||
@ -43,6 +43,7 @@ import {
|
||||
removeRequestedMessageTranslation,
|
||||
replaceScheduledMessages,
|
||||
replaceThreadParam,
|
||||
safeReplacePinnedIds,
|
||||
safeReplaceViewportIds,
|
||||
updateChat,
|
||||
updateChatMessage,
|
||||
@ -73,14 +74,16 @@ import {
|
||||
selectLanguageCode,
|
||||
selectListedIds,
|
||||
selectNoWebPage,
|
||||
selectOutlyingListByMessageId, selectPinnedIds,
|
||||
selectOutlyingListByMessageId,
|
||||
selectPinnedIds,
|
||||
selectRealLastReadId,
|
||||
selectReplyingToId,
|
||||
selectScheduledMessage,
|
||||
selectSendAs,
|
||||
selectSponsoredMessage,
|
||||
selectTabState,
|
||||
selectThreadIdFromMessage, selectThreadOriginChat,
|
||||
selectThreadIdFromMessage,
|
||||
selectThreadOriginChat,
|
||||
selectThreadTopMessageId,
|
||||
selectUser,
|
||||
selectViewportIds,
|
||||
@ -1175,7 +1178,7 @@ addActionHandler('loadPinnedMessages', async (global, actions, payload): Promise
|
||||
|
||||
global = getGlobal();
|
||||
global = addChatMessagesById(global, chat.id, byId);
|
||||
global = replaceThreadParam(global, chat.id, threadId, 'pinnedIds', ids);
|
||||
global = safeReplacePinnedIds(global, chat.id, threadId, ids);
|
||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||
global = addChats(global, buildCollectionByKey(chats, 'id'));
|
||||
setGlobal(global);
|
||||
|
||||
@ -14,6 +14,7 @@ import { getUserFullName } from './users';
|
||||
import { IS_OPUS_SUPPORTED, isWebpSupported } from '../../util/windowEnvironment';
|
||||
import { getChatTitle, isUserId } from './chats';
|
||||
import { getGlobal } from '../index';
|
||||
import { areSortedArraysIntersecting, unique } from '../../util/iteratees';
|
||||
|
||||
const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i');
|
||||
|
||||
@ -253,3 +254,44 @@ export function getMessageSingleInlineButton(message: ApiMessage) {
|
||||
&& message.inlineButtons[0].length === 1
|
||||
&& message.inlineButtons[0][0];
|
||||
}
|
||||
|
||||
export function orderHistoryIds(listedIds: number[]) {
|
||||
return listedIds.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
export function orderPinnedIds(pinnedIds: number[]) {
|
||||
return pinnedIds.sort((a, b) => b - a);
|
||||
}
|
||||
|
||||
export function mergeIdRanges(ranges: number[][], idsUpdate: number[]): number[][] {
|
||||
let hasIntersection = false;
|
||||
let newOutlyingLists = ranges.length ? ranges.map((list) => {
|
||||
if (areSortedArraysIntersecting(list, idsUpdate) && !hasIntersection) {
|
||||
hasIntersection = true;
|
||||
return orderHistoryIds(unique(list.concat(idsUpdate)));
|
||||
}
|
||||
return list;
|
||||
}) : [idsUpdate];
|
||||
|
||||
if (!hasIntersection) {
|
||||
newOutlyingLists = newOutlyingLists.concat([idsUpdate]);
|
||||
}
|
||||
|
||||
newOutlyingLists.sort((a, b) => a[0] - b[0]);
|
||||
|
||||
let length = newOutlyingLists.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const array = newOutlyingLists[i];
|
||||
const prevArray = newOutlyingLists[i - 1];
|
||||
|
||||
if (prevArray && (prevArray.includes(array[0]) || prevArray.includes(array[0] - 1))) {
|
||||
newOutlyingLists[i - 1] = orderHistoryIds(unique(array.concat(prevArray)));
|
||||
newOutlyingLists.splice(i, 1);
|
||||
|
||||
length--;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return newOutlyingLists;
|
||||
}
|
||||
|
||||
@ -27,11 +27,13 @@ import {
|
||||
selectTabState, selectOutlyingLists,
|
||||
} from '../selectors';
|
||||
import {
|
||||
areSortedArraysEqual, mergeIdRanges, omit, orderHistoryIds, pickTruthy, unique,
|
||||
areSortedArraysEqual, omit, pickTruthy, unique,
|
||||
} from '../../util/iteratees';
|
||||
import { updateTabState } from './tabs';
|
||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
import { isLocalMessageId } from '../helpers';
|
||||
import {
|
||||
isLocalMessageId, mergeIdRanges, orderHistoryIds, orderPinnedIds,
|
||||
} from '../helpers';
|
||||
|
||||
type MessageStoreSections = {
|
||||
byId: Record<number, ApiMessage>;
|
||||
@ -454,6 +456,24 @@ export function safeReplaceViewportIds<T extends GlobalState>(
|
||||
);
|
||||
}
|
||||
|
||||
export function safeReplacePinnedIds<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: number,
|
||||
newPinnedIds: number[],
|
||||
): T {
|
||||
const currentIds = selectPinnedIds(global, chatId, threadId) || [];
|
||||
const newIds = orderPinnedIds(newPinnedIds);
|
||||
|
||||
return replaceThreadParam(
|
||||
global,
|
||||
chatId,
|
||||
threadId,
|
||||
'pinnedIds',
|
||||
areSortedArraysEqual(currentIds, newIds) ? currentIds : newIds,
|
||||
);
|
||||
}
|
||||
|
||||
export function updateThreadInfo<T extends GlobalState>(
|
||||
global: T, chatId: string, threadId: number, update: Partial<ApiThreadInfo> | undefined,
|
||||
): T {
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { useEffect } from '../lib/teact/teact';
|
||||
import usePrevious from './usePrevious';
|
||||
import { useEffect, useRef } from '../lib/teact/teact';
|
||||
|
||||
const useEffectWithPrevDeps = <const T extends readonly any[]>(
|
||||
cb: (args: T | readonly []) => void, dependencies: T, debugKey?: string,
|
||||
) => {
|
||||
const prevDeps = usePrevious<T>(dependencies);
|
||||
const prevDepsRef = useRef<T>();
|
||||
|
||||
return useEffect(() => {
|
||||
const prevDeps = prevDepsRef.current;
|
||||
prevDepsRef.current = dependencies;
|
||||
|
||||
return cb(prevDeps || []);
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
||||
}, dependencies, debugKey);
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { useLayoutEffect } from '../lib/teact/teact';
|
||||
import usePrevious from './usePrevious';
|
||||
import { useLayoutEffect, useRef } from '../lib/teact/teact';
|
||||
|
||||
const useLayoutEffectWithPrevDeps = <const T extends readonly any[]>(
|
||||
cb: (args: T | readonly []) => void, dependencies: T, debugKey?: string,
|
||||
) => {
|
||||
const prevDeps = usePrevious<T>(dependencies);
|
||||
const prevDepsRef = useRef<T>();
|
||||
|
||||
return useLayoutEffect(() => {
|
||||
const prevDeps = prevDepsRef.current;
|
||||
prevDepsRef.current = dependencies;
|
||||
|
||||
return cb(prevDeps || []);
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
||||
}, dependencies, debugKey);
|
||||
|
||||
@ -4,7 +4,7 @@ import usePrevious from './usePrevious';
|
||||
import useForceUpdate from './useForceUpdate';
|
||||
import useSyncEffect from './useSyncEffect';
|
||||
|
||||
export default function usePrevDuringAnimation(current: any, duration?: number) {
|
||||
export default function usePrevDuringAnimation<T>(current: T, duration?: number) {
|
||||
const prev = usePrevious(current, true);
|
||||
const timeoutRef = useRef<number>();
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
import { useState } from '../lib/teact/teact';
|
||||
|
||||
import { detectLanguage } from '../util/languageDetection';
|
||||
import useAsync from './useAsync';
|
||||
|
||||
import useSyncEffect from './useSyncEffect';
|
||||
|
||||
export default function useTextLanguage(text?: string) {
|
||||
const language = useAsync(() => (text ? detectLanguage(text) : Promise.resolve(undefined)), [text], undefined);
|
||||
const [language, setLanguage] = useState<string>();
|
||||
|
||||
useSyncEffect(() => {
|
||||
if (text) {
|
||||
detectLanguage(text).then(setLanguage);
|
||||
}
|
||||
}, [text]);
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
@ -734,6 +734,7 @@ export function useMemo<T extends any>(resolver: () => T, dependencies: any[], d
|
||||
|
||||
if (
|
||||
byCursor[cursor] === undefined
|
||||
|| dependencies.length !== byCursor[cursor].dependencies.length
|
||||
|| dependencies.some((dependency, i) => dependency !== byCursor[cursor].dependencies[i])
|
||||
) {
|
||||
if (DEBUG && debugKey) {
|
||||
@ -741,7 +742,7 @@ export function useMemo<T extends any>(resolver: () => T, dependencies: any[], d
|
||||
console.log(
|
||||
`[Teact.useMemo] ${renderingInstance.name} (${debugKey}): Update is caused by:`,
|
||||
byCursor[cursor]
|
||||
? getUnequalProps(dependencies, byCursor[cursor].dependencies).join(', ')
|
||||
? getUnequalProps(byCursor[cursor].dependencies, dependencies).join(', ')
|
||||
: '[first render]',
|
||||
);
|
||||
}
|
||||
|
||||
@ -148,43 +148,6 @@ function isObject(value: any): value is object {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
|
||||
export function orderHistoryIds(listedIds: number[]) {
|
||||
return listedIds.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
export function mergeIdRanges(ranges: number[][], idsUpdate: number[]): number[][] {
|
||||
let hasIntersection = false;
|
||||
let newOutlyingLists = ranges.length ? ranges.map((list) => {
|
||||
if (areSortedArraysIntersecting(list, idsUpdate) && !hasIntersection) {
|
||||
hasIntersection = true;
|
||||
return orderHistoryIds(unique(list.concat(idsUpdate)));
|
||||
}
|
||||
return list;
|
||||
}) : [idsUpdate];
|
||||
|
||||
if (!hasIntersection) {
|
||||
newOutlyingLists = newOutlyingLists.concat([idsUpdate]);
|
||||
}
|
||||
|
||||
newOutlyingLists.sort((a, b) => a[0] - b[0]);
|
||||
|
||||
let length = newOutlyingLists.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const array = newOutlyingLists[i];
|
||||
const prevArray = newOutlyingLists[i - 1];
|
||||
|
||||
if (prevArray && (prevArray.includes(array[0]) || prevArray.includes(array[0] - 1))) {
|
||||
newOutlyingLists[i - 1] = orderHistoryIds(unique(array.concat(prevArray)));
|
||||
newOutlyingLists.splice(i, 1);
|
||||
|
||||
length--;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return newOutlyingLists;
|
||||
}
|
||||
|
||||
export function findLast<T>(array: Array<T>, predicate: (value: T, index: number, obj: T[]) => boolean): T | undefined {
|
||||
let cursor = array.length;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user