[Refactoring] Main: Simplify column animation
This commit is contained in:
parent
c67560d431
commit
4d448e4d07
@ -7,7 +7,7 @@ import { getActions, withGlobal } from '../../../global';
|
||||
import { IAnchorPosition } from '../../../types';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import useThrottle from '../../../hooks/useThrottle';
|
||||
import useRunThrottled from '../../../hooks/useRunThrottled';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import { selectIsAdminInActiveGroupCall } from '../../../global/selectors/calls';
|
||||
@ -80,7 +80,7 @@ const GroupCallParticipantMenu: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id]);
|
||||
|
||||
const runThrottled = useThrottle(VOLUME_CHANGE_THROTTLE);
|
||||
const runThrottled = useRunThrottled(VOLUME_CHANGE_THROTTLE);
|
||||
|
||||
const handleRemove = useCallback((e: React.SyntheticEvent<any>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import useDebounce from '../../../hooks/useDebounce';
|
||||
import useRunDebounced from '../../../hooks/useRunDebounced';
|
||||
import React, {
|
||||
FC, memo, useCallback, useEffect,
|
||||
} from '../../../lib/teact/teact';
|
||||
@ -59,7 +59,7 @@ const SettingsNotifications: FC<OwnProps & StateProps> = ({
|
||||
loadNotificationSettings();
|
||||
}, [loadNotificationSettings]);
|
||||
|
||||
const runDebounced = useDebounce(500, true);
|
||||
const runDebounced = useRunDebounced(500, true);
|
||||
|
||||
const handleSettingsChange = useCallback((
|
||||
e: ChangeEvent<HTMLInputElement>,
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
getMessageContentFilename, getMessageMediaHash,
|
||||
} from '../../global/helpers';
|
||||
|
||||
import useDebounce from '../../hooks/useDebounce';
|
||||
import useRunDebounced from '../../hooks/useRunDebounced';
|
||||
|
||||
type StateProps = {
|
||||
activeDownloads: Record<string, number[]>;
|
||||
@ -33,17 +33,17 @@ const DownloadManager: FC<StateProps> = ({
|
||||
}) => {
|
||||
const { cancelMessagesMediaDownload } = getActions();
|
||||
|
||||
const debouncedGlobalUpdate = useDebounce(GLOBAL_UPDATE_DEBOUNCE, true);
|
||||
const runDebounced = useRunDebounced(GLOBAL_UPDATE_DEBOUNCE, true);
|
||||
|
||||
const handleMessageDownloaded = useCallback((message: ApiMessage) => {
|
||||
downloadedMessages.add(message);
|
||||
debouncedGlobalUpdate(() => {
|
||||
runDebounced(() => {
|
||||
if (downloadedMessages.size) {
|
||||
cancelMessagesMediaDownload({ messages: Array.from(downloadedMessages) });
|
||||
downloadedMessages.clear();
|
||||
}
|
||||
});
|
||||
}, [cancelMessagesMediaDownload, debouncedGlobalUpdate]);
|
||||
}, [cancelMessagesMediaDownload, runDebounced]);
|
||||
|
||||
useEffect(() => {
|
||||
const activeMessages = Object.entries(activeDownloads).map(([chatId, messageIds]) => (
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
|
||||
.has-call-header {
|
||||
--call-header-height: 2rem;
|
||||
|
||||
#LeftColumn, #MiddleColumn, #RightColumn-wrapper {
|
||||
height: calc(100% - 2rem);
|
||||
margin-top: 2rem;
|
||||
@ -74,9 +75,10 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: black;
|
||||
opacity: var(--layer-blackout-opacity);
|
||||
opacity: 0;
|
||||
transition: opacity var(--layer-transition);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
@ -87,18 +89,18 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.is-android .middle-column-shown & {
|
||||
body.is-android #Main.left-column-animating & {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body:not(.is-android) #Main:not(.left-column-open) &,
|
||||
body.android-left-blackout-open & {
|
||||
opacity: var(--layer-blackout-opacity);
|
||||
}
|
||||
}
|
||||
|
||||
#Main:not(.middle-column-open) & {
|
||||
#Main.left-column-open & {
|
||||
transform: translate3d(0, 0, 0);
|
||||
|
||||
&::after {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
#Main.history-animation-disabled & {
|
||||
@ -154,7 +156,7 @@
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#Main:not(.middle-column-open) & {
|
||||
#Main.left-column-open & {
|
||||
transform: translate3d(26.5rem, 0, 0);
|
||||
}
|
||||
}
|
||||
@ -162,7 +164,7 @@
|
||||
@media (max-width: 600px) {
|
||||
border-left: none;
|
||||
|
||||
#Main:not(.middle-column-open) & {
|
||||
#Main.left-column-open & {
|
||||
transform: translate3d(100vw, 0, 0);
|
||||
}
|
||||
|
||||
@ -187,7 +189,7 @@ body.is-android.animation-level-1 {
|
||||
transition: transform var(--layer-transition), opacity var(--layer-transition);
|
||||
}
|
||||
|
||||
#Main:not(.middle-column-shown) {
|
||||
#Main.left-column-open:not(.left-column-animating) {
|
||||
#MiddleColumn {
|
||||
display: none;
|
||||
}
|
||||
@ -200,14 +202,15 @@ body.is-android.animation-level-1 {
|
||||
}
|
||||
}
|
||||
|
||||
#Main.middle-column-open {
|
||||
#Main:not(.left-column-open) {
|
||||
#LeftColumn {
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#Main:not(.right-column-shown) {
|
||||
// @optimization
|
||||
#Main:not(.right-column-open):not(.right-column-animating) {
|
||||
#RightColumn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, {
|
||||
FC, useEffect, memo, useCallback,
|
||||
FC, useEffect, memo, useCallback, useState, useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
@ -11,6 +11,7 @@ import '../../global/actions/all';
|
||||
import {
|
||||
BASE_EMOJI_KEYWORD_LANG, DEBUG, INACTIVE_MARKER, PAGE_TITLE,
|
||||
} from '../../config';
|
||||
import { IS_ANDROID } from '../../util/environment';
|
||||
import {
|
||||
selectChatMessage,
|
||||
selectIsForwardModalOpen,
|
||||
@ -20,18 +21,19 @@ import {
|
||||
} from '../../global/selectors';
|
||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { fastRaf } from '../../util/schedulers';
|
||||
import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners';
|
||||
import { processDeepLink } from '../../util/deeplink';
|
||||
import stopEvent from '../../util/stopEvent';
|
||||
import windowSize from '../../util/windowSize';
|
||||
import { getAllNotificationsCount } from '../../util/folderManager';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
||||
import useBeforeUnload from '../../hooks/useBeforeUnload';
|
||||
import useOnChange from '../../hooks/useOnChange';
|
||||
import usePreventPinchZoomGesture from '../../hooks/usePreventPinchZoomGesture';
|
||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
import { LOCATION_HASH } from '../../hooks/useHistoryBack';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import { fastRaf } from '../../util/schedulers';
|
||||
|
||||
import StickerSetModal from '../common/StickerSetModal.async';
|
||||
import UnreadCount from '../common/UnreadCounter';
|
||||
@ -59,8 +61,8 @@ type StateProps = {
|
||||
connectionState?: ApiUpdateConnectionStateType;
|
||||
authState?: ApiUpdateAuthorizationStateType;
|
||||
lastSyncTime?: number;
|
||||
isLeftColumnShown: boolean;
|
||||
isRightColumnShown: boolean;
|
||||
isLeftColumnOpen: boolean;
|
||||
isRightColumnOpen: boolean;
|
||||
isMediaViewerOpen: boolean;
|
||||
isForwardModalOpen: boolean;
|
||||
hasNotifications: boolean;
|
||||
@ -95,8 +97,8 @@ const Main: FC<StateProps> = ({
|
||||
connectionState,
|
||||
authState,
|
||||
lastSyncTime,
|
||||
isLeftColumnShown,
|
||||
isRightColumnShown,
|
||||
isLeftColumnOpen,
|
||||
isRightColumnOpen,
|
||||
isMediaViewerOpen,
|
||||
isForwardModalOpen,
|
||||
hasNotifications,
|
||||
@ -224,51 +226,69 @@ const Main: FC<StateProps> = ({
|
||||
};
|
||||
}, [activeGroupCallId]);
|
||||
|
||||
const {
|
||||
transitionClassNames: middleColumnTransitionClassNames,
|
||||
} = useShowTransition(!isLeftColumnShown, undefined, true, undefined, shouldSkipHistoryAnimations);
|
||||
|
||||
const {
|
||||
transitionClassNames: rightColumnTransitionClassNames,
|
||||
} = useShowTransition(isRightColumnShown, undefined, true, undefined, shouldSkipHistoryAnimations);
|
||||
|
||||
const className = buildClassName(
|
||||
middleColumnTransitionClassNames.replace(/([\w-]+)/g, 'middle-column-$1'),
|
||||
rightColumnTransitionClassNames.replace(/([\w-]+)/g, 'right-column-$1'),
|
||||
shouldSkipHistoryAnimations && 'history-animation-disabled',
|
||||
const leftColumnTransition = useShowTransition(
|
||||
isLeftColumnOpen, undefined, true, undefined, shouldSkipHistoryAnimations,
|
||||
);
|
||||
const willAnimateLeftColumnRef = useRef(false);
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
// Dispatch heavy transition event when opening middle column
|
||||
useOnChange(([prevIsLeftColumnShown]) => {
|
||||
if (prevIsLeftColumnShown === undefined || animationLevel === 0) {
|
||||
// Handle opening middle column
|
||||
useOnChange(([prevIsLeftColumnOpen]) => {
|
||||
if (prevIsLeftColumnOpen === undefined || animationLevel === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
willAnimateLeftColumnRef.current = true;
|
||||
|
||||
if (IS_ANDROID) {
|
||||
fastRaf(() => {
|
||||
document.body.classList.toggle('android-left-blackout-open', !isLeftColumnOpen);
|
||||
});
|
||||
}
|
||||
|
||||
const dispatchHeavyAnimationEnd = dispatchHeavyAnimationEvent();
|
||||
|
||||
waitForTransitionEnd(document.getElementById('MiddleColumn')!, dispatchHeavyAnimationEnd);
|
||||
}, [isLeftColumnShown]);
|
||||
waitForTransitionEnd(document.getElementById('MiddleColumn')!, () => {
|
||||
dispatchHeavyAnimationEnd();
|
||||
willAnimateLeftColumnRef.current = false;
|
||||
forceUpdate();
|
||||
});
|
||||
}, [isLeftColumnOpen]);
|
||||
|
||||
// Dispatch heavy transition event and add body class when opening right column
|
||||
useOnChange(([prevIsRightColumnShown]) => {
|
||||
if (prevIsRightColumnShown === undefined || animationLevel === 0) {
|
||||
const rightColumnTransition = useShowTransition(
|
||||
isRightColumnOpen, undefined, true, undefined, shouldSkipHistoryAnimations,
|
||||
);
|
||||
const willAnimateRightColumnRef = useRef(false);
|
||||
const [isNarrowMessageList, setIsNarrowMessageList] = useState(isRightColumnOpen);
|
||||
|
||||
// Handle opening right column
|
||||
useOnChange(([prevIsRightColumnOpen]) => {
|
||||
if (prevIsRightColumnOpen === undefined || animationLevel === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
fastRaf(() => {
|
||||
document.body.classList.add('animating-right-column');
|
||||
});
|
||||
willAnimateRightColumnRef.current = true;
|
||||
|
||||
const dispatchHeavyAnimationEnd = dispatchHeavyAnimationEvent();
|
||||
|
||||
waitForTransitionEnd(document.getElementById('RightColumn')!, () => {
|
||||
dispatchHeavyAnimationEnd();
|
||||
|
||||
fastRaf(() => {
|
||||
document.body.classList.remove('animating-right-column');
|
||||
});
|
||||
willAnimateRightColumnRef.current = false;
|
||||
forceUpdate();
|
||||
setIsNarrowMessageList(isRightColumnOpen);
|
||||
});
|
||||
}, [isRightColumnShown]);
|
||||
}, [isRightColumnOpen]);
|
||||
|
||||
const className = buildClassName(
|
||||
leftColumnTransition.hasShownClass && 'left-column-shown',
|
||||
leftColumnTransition.hasOpenClass && 'left-column-open',
|
||||
willAnimateLeftColumnRef.current && 'left-column-animating',
|
||||
rightColumnTransition.hasShownClass && 'right-column-shown',
|
||||
rightColumnTransition.hasOpenClass && 'right-column-open',
|
||||
willAnimateRightColumnRef.current && 'right-column-animating',
|
||||
isNarrowMessageList && 'narrow-message-list',
|
||||
shouldSkipHistoryAnimations && 'history-animation-disabled',
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
updateIsOnline(false);
|
||||
@ -390,8 +410,8 @@ export default memo(withGlobal(
|
||||
connectionState: global.connectionState,
|
||||
authState: global.authState,
|
||||
lastSyncTime: global.lastSyncTime,
|
||||
isLeftColumnShown: global.isLeftColumnShown,
|
||||
isRightColumnShown: selectIsRightColumnShown(global),
|
||||
isLeftColumnOpen: global.isLeftColumnShown,
|
||||
isRightColumnOpen: selectIsRightColumnShown(global),
|
||||
isMediaViewerOpen: selectIsMediaViewerOpen(global),
|
||||
isForwardModalOpen: selectIsForwardModalOpen(global),
|
||||
hasNotifications: Boolean(global.notifications.length),
|
||||
|
||||
@ -4,18 +4,18 @@ import React, {
|
||||
|
||||
import { MediaViewerOrigin } from '../../types';
|
||||
|
||||
import useDebounce from '../../hooks/useDebounce';
|
||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
import { animateNumber, timingFunctions } from '../../util/animation';
|
||||
import arePropsShallowEqual from '../../util/arePropsShallowEqual';
|
||||
import { captureEvents, IOS_SCREEN_EDGE_THRESHOLD, RealTouchEvent } from '../../util/captureEvents';
|
||||
import { IS_IOS, IS_TOUCH_ENV } from '../../util/environment';
|
||||
import { debounce } from '../../util/schedulers';
|
||||
import useTimeout from '../../hooks/useTimeout';
|
||||
import useDebouncedCallback from '../../hooks/useDebouncedCallback';
|
||||
|
||||
import MediaViewerContent from './MediaViewerContent';
|
||||
|
||||
import './MediaViewerSlides.scss';
|
||||
import useTimeout from '../../hooks/useTimeout';
|
||||
|
||||
type OwnProps = {
|
||||
messageId?: number;
|
||||
@ -96,14 +96,14 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
||||
forceUpdate();
|
||||
}, [forceUpdate]);
|
||||
|
||||
const setIsActive = useCallback((value: boolean) => {
|
||||
const selectMessageDebounced = useDebouncedCallback(selectMessage, [], DEBOUNCE_MESSAGE, true);
|
||||
const clearSwipeDirectionDebounced = useDebouncedCallback(() => {
|
||||
swipeDirectionRef.current = undefined;
|
||||
}, [], DEBOUNCE_SWIPE, true);
|
||||
const setIsActiveDebounced = useDebouncedCallback((value: boolean) => {
|
||||
isActiveRef.current = value;
|
||||
forceUpdate();
|
||||
}, [forceUpdate]);
|
||||
|
||||
const debounceSetMessage = useDebounce(DEBOUNCE_MESSAGE, true);
|
||||
const debounceSwipeDirection = useDebounce(DEBOUNCE_SWIPE, true);
|
||||
const debounceActive = useDebounce(DEBOUNCE_ACTIVE, true);
|
||||
}, [forceUpdate], DEBOUNCE_ACTIVE, true);
|
||||
|
||||
const handleToggleFooterVisibility = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
if (!IS_TOUCH_ENV) return;
|
||||
@ -156,10 +156,8 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
||||
transformRef.current.x += offset;
|
||||
isActiveRef.current = false;
|
||||
setActiveMessageId(mId);
|
||||
debounceSetMessage(() => selectMessage(mId));
|
||||
debounceActive(() => {
|
||||
setIsActive(true);
|
||||
});
|
||||
selectMessageDebounced(mId);
|
||||
setIsActiveDebounced(true);
|
||||
lastTransform = { x: 0, y: 0, scale: 1 };
|
||||
cancelAnimation = animateNumber({
|
||||
from: transformRef.current.x,
|
||||
@ -351,12 +349,8 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
||||
y,
|
||||
} = transformRef.current;
|
||||
|
||||
debounceSwipeDirection(() => {
|
||||
swipeDirectionRef.current = undefined;
|
||||
});
|
||||
debounceActive(() => {
|
||||
setIsActive(true);
|
||||
});
|
||||
clearSwipeDirectionDebounced();
|
||||
setIsActiveDebounced(true);
|
||||
|
||||
// If scale is less than 1 we need to bounce back
|
||||
if (scale < 1) {
|
||||
@ -476,7 +470,7 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
||||
// We shift everything by one screen width and then set new active message id
|
||||
transformRef.current.x += offset;
|
||||
setActiveMessageId(mId);
|
||||
debounceSetMessage(() => selectMessage(mId));
|
||||
selectMessageDebounced(mId);
|
||||
}
|
||||
// Then we always return to the original position
|
||||
cancelAnimation = animateNumber({
|
||||
@ -493,14 +487,15 @@ const MediaViewerSlides: FC<OwnProps> = ({
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
isZoomed,
|
||||
onClose,
|
||||
setTransform,
|
||||
getMessageId,
|
||||
activeMessageId,
|
||||
setIsActive,
|
||||
selectMessageDebounced,
|
||||
setIsActiveDebounced,
|
||||
clearSwipeDirectionDebounced,
|
||||
]);
|
||||
|
||||
if (!activeMessageId) return undefined;
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
|
||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||
transition: bottom 150ms ease-out, transform var(--layer-transition);
|
||||
|
||||
body.keyboard-visible & {
|
||||
position: relative;
|
||||
bottom: calc(0px - env(safe-area-inset-bottom));
|
||||
@ -95,6 +96,7 @@
|
||||
@media (max-width: 600px) {
|
||||
&.with-bottom-shift {
|
||||
margin-bottom: 0;
|
||||
|
||||
.last-in-list {
|
||||
margin-bottom: 4.25rem;
|
||||
|
||||
@ -317,9 +319,7 @@
|
||||
transform: translate3d(calc(var(--right-column-width) / -2), 0, 0);
|
||||
}
|
||||
|
||||
body:not(.animating-right-column) #Main.right-column-open &.select-mode-active,
|
||||
#Main.right-column-open &:not(.select-mode-active),
|
||||
body.animating-right-column &:not(.select-mode-active) {
|
||||
#Main.narrow-message-list & {
|
||||
width: calc(100% - var(--right-column-width));
|
||||
|
||||
.messages-container {
|
||||
|
||||
@ -185,7 +185,7 @@
|
||||
}
|
||||
|
||||
// @optimization
|
||||
@include while-transition() {
|
||||
#Main.right-column-animating & {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&:not(.middle-column-open) {
|
||||
&.left-column-open {
|
||||
transform: translate3d(100vw, 0, 0) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,7 +211,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
const className = buildClassName(
|
||||
'SymbolMenu mobile-menu',
|
||||
transitionClassNames,
|
||||
!isLeftColumnShown && 'middle-column-open',
|
||||
isLeftColumnShown && 'left-column-open',
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useCallback } from '../../../lib/teact/teact';
|
||||
|
||||
import { fastRaf } from '../../../util/schedulers';
|
||||
import useDebounce from '../../../hooks/useDebounce';
|
||||
import useRunDebounced from '../../../hooks/useRunDebounced';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
|
||||
const DEBOUNCE = 1000;
|
||||
@ -13,7 +13,7 @@ export default function useStickyDates() {
|
||||
// so we will add `position: sticky` only after first scroll. There would be no animation on the first show though.
|
||||
const [isScrolled, markIsScrolled] = useFlag(false);
|
||||
|
||||
const runDebounced = useDebounce(DEBOUNCE, true);
|
||||
const runDebounced = useRunDebounced(DEBOUNCE, true);
|
||||
|
||||
const updateStickyDates = useCallback((container: HTMLDivElement, hasTools?: boolean) => {
|
||||
markIsScrolled();
|
||||
|
||||
@ -114,8 +114,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
#Main.right-column-open &,
|
||||
body.animating-right-column & {
|
||||
#Main.right-column-shown & {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
ManagementScreens, NewChatMembersProgress, ProfileState, RightColumnContent,
|
||||
} from '../../types';
|
||||
|
||||
import { MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config';
|
||||
import { ANIMATION_END_DELAY, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config';
|
||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||
import {
|
||||
selectAreActiveChatsLoaded,
|
||||
@ -41,7 +41,7 @@ type StateProps = {
|
||||
nextManagementScreen?: ManagementScreens;
|
||||
};
|
||||
|
||||
const CLOSE_ANIMATION_DURATION = 300;
|
||||
const ANIMATION_DURATION = 450 + ANIMATION_END_DELAY;
|
||||
const MAIN_SCREENS_COUNT = Object.keys(RightColumnContent).length / 2;
|
||||
const MANAGEMENT_SCREENS_COUNT = Object.keys(ManagementScreens).length / 2;
|
||||
|
||||
@ -191,7 +191,7 @@ const RightColumn: FC<StateProps> = ({
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setShouldSkipTransition(!isOpen);
|
||||
}, CLOSE_ANIMATION_DURATION);
|
||||
}, ANIMATION_DURATION);
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -300,7 +300,6 @@ const RightColumn: FC<StateProps> = ({
|
||||
profileState={profileState}
|
||||
managementScreen={managementScreen}
|
||||
onClose={close}
|
||||
shouldSkipAnimation={shouldSkipTransition || shouldSkipHistoryAnimations}
|
||||
onScreenSelect={setManagementScreen}
|
||||
/>
|
||||
<Transition
|
||||
|
||||
@ -6,6 +6,7 @@ import { getActions, withGlobal } from '../../global';
|
||||
import { ManagementScreens, ProfileState } from '../../types';
|
||||
import { ApiExportedInvite } from '../../api/types';
|
||||
|
||||
import { ANIMATION_END_DELAY } from '../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { debounce } from '../../util/schedulers';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
@ -43,7 +44,6 @@ type OwnProps = {
|
||||
isGifSearch?: boolean;
|
||||
isPollResults?: boolean;
|
||||
isAddingChatMembers?: boolean;
|
||||
shouldSkipAnimation?: boolean;
|
||||
profileState?: ProfileState;
|
||||
managementScreen?: ManagementScreens;
|
||||
onClose: () => void;
|
||||
@ -61,9 +61,10 @@ type StateProps = {
|
||||
gifSearchQuery?: string;
|
||||
isEditingInvite?: boolean;
|
||||
currentInviteInfo?: ApiExportedInvite;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
};
|
||||
|
||||
const COLUMN_CLOSE_DELAY_MS = 300;
|
||||
const COLUMN_ANIMATION_DURATION = 450 + ANIMATION_END_DELAY;
|
||||
const runDebouncedForSearch = debounce((cb) => cb(), 200, false);
|
||||
|
||||
enum HeaderContent {
|
||||
@ -121,10 +122,10 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
messageSearchQuery,
|
||||
stickerSearchQuery,
|
||||
gifSearchQuery,
|
||||
shouldSkipAnimation,
|
||||
isEditingInvite,
|
||||
canViewStatistics,
|
||||
currentInviteInfo,
|
||||
shouldSkipHistoryAnimations,
|
||||
}) => {
|
||||
const {
|
||||
setLocalTextSearchQuery,
|
||||
@ -179,7 +180,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setShouldSkipTransition(!isColumnOpen);
|
||||
}, COLUMN_CLOSE_DELAY_MS);
|
||||
}, COLUMN_ANIMATION_DURATION);
|
||||
}, [isColumnOpen]);
|
||||
|
||||
const lang = useLang();
|
||||
@ -436,7 +437,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
const buttonClassName = buildClassName(
|
||||
'animated-close-icon',
|
||||
isBackButton && 'state-back',
|
||||
(shouldSkipTransition || shouldSkipAnimation) && 'no-transition',
|
||||
(shouldSkipTransition || shouldSkipHistoryAnimations) && 'no-transition',
|
||||
);
|
||||
|
||||
return (
|
||||
@ -452,7 +453,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
<div ref={backButtonRef} className={buttonClassName} />
|
||||
</Button>
|
||||
<Transition
|
||||
name={(shouldSkipTransition || shouldSkipAnimation) ? 'none' : 'slide-fade'}
|
||||
name={(shouldSkipTransition || shouldSkipHistoryAnimations) ? 'none' : 'slide-fade'}
|
||||
activeKey={renderingContentKey}
|
||||
>
|
||||
{renderHeaderContent()}
|
||||
@ -495,6 +496,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
gifSearchQuery,
|
||||
isEditingInvite,
|
||||
currentInviteInfo,
|
||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||
};
|
||||
},
|
||||
)(RightHeader));
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import { useMemo } from '../lib/teact/teact';
|
||||
|
||||
import { debounce } from '../util/schedulers';
|
||||
|
||||
export default function useDebounce(ms: number, noFirst?: boolean, noLast?: boolean) {
|
||||
return useMemo(() => {
|
||||
return debounce((cb) => cb(), ms, !noFirst, !noLast);
|
||||
}, [ms, noFirst, noLast]);
|
||||
}
|
||||
18
src/hooks/useDebouncedCallback.ts
Normal file
18
src/hooks/useDebouncedCallback.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { useCallback, useMemo } from '../lib/teact/teact';
|
||||
|
||||
import { debounce } from '../util/schedulers';
|
||||
|
||||
export default function useDebouncedCallback<T extends AnyToVoidFunction>(
|
||||
fn: T,
|
||||
deps: any[],
|
||||
ms: number,
|
||||
noFirst?: boolean,
|
||||
noLast?: boolean,
|
||||
) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const fnMemo = useCallback(fn, deps);
|
||||
|
||||
return useMemo(() => {
|
||||
return debounce(fnMemo, ms, !noFirst, !noLast);
|
||||
}, [fnMemo, ms, noFirst, noLast]);
|
||||
}
|
||||
@ -1,21 +1,20 @@
|
||||
import { useState } from '../lib/teact/teact';
|
||||
import { useCallback, useRef, useState } from '../lib/teact/teact';
|
||||
|
||||
import useDebounce from './useDebounce';
|
||||
import useRunDebounced from './useRunDebounced';
|
||||
import useOnChange from './useOnChange';
|
||||
import useHeavyAnimationCheck from './useHeavyAnimationCheck';
|
||||
import useFlag from './useFlag';
|
||||
import useHeavyAnimationCheck, { isHeavyAnimating } from './useHeavyAnimationCheck';
|
||||
import useForceUpdate from './useForceUpdate';
|
||||
|
||||
export default function useDebouncedMemo<R extends any, D extends any[]>(
|
||||
resolverFn: () => R, ms: number, dependencies: D,
|
||||
): R | undefined {
|
||||
const runDebounced = useDebounce(ms, true);
|
||||
const [value, setValue] = useState<R>();
|
||||
const [isFrozen, freeze, unfreeze] = useFlag();
|
||||
|
||||
useHeavyAnimationCheck(freeze, unfreeze);
|
||||
const { isFrozen, updateWhenUnfrozen } = useHeavyAnimationFreeze();
|
||||
const runDebounced = useRunDebounced(ms, true);
|
||||
|
||||
useOnChange(() => {
|
||||
if (isFrozen) {
|
||||
updateWhenUnfrozen();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -26,3 +25,30 @@ export default function useDebouncedMemo<R extends any, D extends any[]>(
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function useHeavyAnimationFreeze() {
|
||||
const isPending = useRef(false);
|
||||
|
||||
const updateWhenUnfrozen = useCallback(() => {
|
||||
isPending.current = true;
|
||||
}, []);
|
||||
|
||||
const forceUpdate = useForceUpdate();
|
||||
const handleUnfreeze = useCallback(() => {
|
||||
if (!isPending.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
isPending.current = false;
|
||||
forceUpdate();
|
||||
}, [forceUpdate]);
|
||||
useHeavyAnimationCheck(noop, handleUnfreeze);
|
||||
|
||||
return {
|
||||
isFrozen: isHeavyAnimating(),
|
||||
updateWhenUnfrozen,
|
||||
};
|
||||
}
|
||||
|
||||
function noop() {
|
||||
}
|
||||
|
||||
7
src/hooks/useRunDebounced.ts
Normal file
7
src/hooks/useRunDebounced.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import useDebouncedCallback from './useDebouncedCallback';
|
||||
|
||||
export default function useRunDebounced(ms: number, noFirst?: boolean, noLast?: boolean) {
|
||||
return useDebouncedCallback((cb: NoneToVoidFunction) => {
|
||||
cb();
|
||||
}, [], ms, noFirst, noLast);
|
||||
}
|
||||
7
src/hooks/useRunThrottled.ts
Normal file
7
src/hooks/useRunThrottled.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import useThrottledCallback from './useThrottledCallback';
|
||||
|
||||
export default function useRunThrottled(ms: number, noFirst?: boolean) {
|
||||
return useThrottledCallback((cb: NoneToVoidFunction) => {
|
||||
cb();
|
||||
}, [], ms, noFirst);
|
||||
}
|
||||
@ -60,6 +60,8 @@ const useShowTransition = (
|
||||
return {
|
||||
shouldRender,
|
||||
transitionClassNames,
|
||||
hasShownClass: shouldRender,
|
||||
hasOpenClass: shouldHaveOpenClassName,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
import { useMemo } from '../lib/teact/teact';
|
||||
|
||||
import { throttle } from '../util/schedulers';
|
||||
|
||||
const useThrottle = (ms: number, noFirst = false) => {
|
||||
return useMemo(() => {
|
||||
return throttle((cb) => cb(), ms, !noFirst);
|
||||
}, [ms, noFirst]);
|
||||
};
|
||||
|
||||
export default useThrottle;
|
||||
17
src/hooks/useThrottledCallback.ts
Normal file
17
src/hooks/useThrottledCallback.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { useCallback, useMemo } from '../lib/teact/teact';
|
||||
|
||||
import { throttle } from '../util/schedulers';
|
||||
|
||||
export default function useThrottledCallback<T extends AnyToVoidFunction>(
|
||||
fn: T,
|
||||
deps: any[],
|
||||
ms: number,
|
||||
noFirst?: boolean,
|
||||
) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const fnMemo = useCallback(fn, deps);
|
||||
|
||||
return useMemo(() => {
|
||||
return throttle(fnMemo, ms, !noFirst);
|
||||
}, [fnMemo, ms, noFirst]);
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { useState } from '../lib/teact/teact';
|
||||
|
||||
import useThrottle from './useThrottle';
|
||||
import useOnChange from './useOnChange';
|
||||
import useHeavyAnimationCheck from './useHeavyAnimationCheck';
|
||||
import useFlag from './useFlag';
|
||||
|
||||
export default function useThrottledMemo<R extends any, D extends any[]>(
|
||||
resolverFn: () => R, ms: number, dependencies: D,
|
||||
): R | undefined {
|
||||
const runThrottled = useThrottle(ms, true);
|
||||
const [value, setValue] = useState<R>();
|
||||
const [isFrozen, freeze, unfreeze] = useFlag();
|
||||
|
||||
useHeavyAnimationCheck(freeze, unfreeze);
|
||||
|
||||
useOnChange(() => {
|
||||
if (isFrozen) {
|
||||
return;
|
||||
}
|
||||
|
||||
runThrottled(() => {
|
||||
setValue(resolverFn());
|
||||
});
|
||||
}, [...dependencies, isFrozen]);
|
||||
|
||||
return value;
|
||||
}
|
||||
@ -464,9 +464,7 @@ export function setTarget($element: VirtualElement, target: Node) {
|
||||
}
|
||||
}
|
||||
|
||||
export function useState<T>(): [T, StateHookSetter<T>];
|
||||
export function useState<T>(initial: T): [T, StateHookSetter<T>];
|
||||
export function useState<T>(initial?: T): [T, StateHookSetter<T>] {
|
||||
export function useState<T>(initial?: T, debugKey?: string): [T, StateHookSetter<T>] {
|
||||
const { cursor, byCursor } = renderingInstance.hooks.state;
|
||||
|
||||
if (byCursor[cursor] === undefined) {
|
||||
@ -501,7 +499,9 @@ export function useState<T>(initial?: T): [T, StateHookSetter<T>] {
|
||||
componentInstance.Component && (componentInstance.Component as FC_withDebug).DEBUG_contentComponentName
|
||||
? `> ${(componentInstance.Component as FC_withDebug).DEBUG_contentComponentName}`
|
||||
: '',
|
||||
`Forced update at cursor #${cursor}, next value: `,
|
||||
debugKey
|
||||
? `State update for ${debugKey}, next value: `
|
||||
: `State update at cursor #${cursor}, next value: `,
|
||||
byCursor[cursor].nextValue,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// @optimization
|
||||
@mixin while-transition() {
|
||||
.Transition > div:not(.Transition__slide--active) &, body.animating-right-column & {
|
||||
.Transition > div:not(.Transition__slide--active) & {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export default function findInViewport(
|
||||
container: HTMLElement,
|
||||
selectorOrElements: string | NodeListOf<HTMLElement>,
|
||||
selectorOrElements: string | NodeListOf<HTMLElement> | HTMLElement[],
|
||||
margin = 0,
|
||||
isDense = false,
|
||||
shouldContainBottom = false,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user