UI Rendering: Fix flicks (#6352)
This commit is contained in:
parent
ff0d9edc73
commit
89e37c280b
@ -97,6 +97,7 @@ type OwnProps = {
|
|||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
onDragEnter?: (chatId: string) => void;
|
onDragEnter?: (chatId: string) => void;
|
||||||
withTags?: boolean;
|
withTags?: boolean;
|
||||||
|
onReorderAnimationEnd?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -172,6 +173,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
chatFoldersById,
|
chatFoldersById,
|
||||||
areTagsEnabled,
|
areTagsEnabled,
|
||||||
withTags,
|
withTags,
|
||||||
|
onReorderAnimationEnd,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
@ -234,6 +236,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
orderDiff,
|
orderDiff,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
isPreview,
|
isPreview,
|
||||||
|
onReorderAnimationEnd,
|
||||||
topics,
|
topics,
|
||||||
noForumTitle: shouldRenderTags,
|
noForumTitle: shouldRenderTags,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -105,7 +105,7 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
? archiveSettings?.isMinimized ? ARCHIVE_MINIMIZED_HEIGHT : CHAT_HEIGHT_PX : 0;
|
? archiveSettings?.isMinimized ? ARCHIVE_MINIMIZED_HEIGHT : CHAT_HEIGHT_PX : 0;
|
||||||
const frozenNotificationHeight = shouldShowFrozenAccountNotification ? 68 : 0;
|
const frozenNotificationHeight = shouldShowFrozenAccountNotification ? 68 : 0;
|
||||||
|
|
||||||
const { orderDiffById, getAnimationType } = useOrderDiff(orderedIds);
|
const { orderDiffById, getAnimationType, onReorderAnimationEnd: onReorderAnimationEnd } = useOrderDiff(orderedIds);
|
||||||
|
|
||||||
const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, CHAT_LIST_SLICE);
|
const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, CHAT_LIST_SLICE);
|
||||||
|
|
||||||
@ -242,6 +242,7 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
isSavedDialog={isSaved}
|
isSavedDialog={isSaved}
|
||||||
animationType={getAnimationType(id)}
|
animationType={getAnimationType(id)}
|
||||||
orderDiff={orderDiffById[id]}
|
orderDiff={orderDiffById[id]}
|
||||||
|
onReorderAnimationEnd={onReorderAnimationEnd}
|
||||||
offsetTop={offsetTop}
|
offsetTop={offsetTop}
|
||||||
observeIntersection={observe}
|
observeIntersection={observe}
|
||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
|
|||||||
@ -130,7 +130,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
: [];
|
: [];
|
||||||
}, [topicsInfo]);
|
}, [topicsInfo]);
|
||||||
|
|
||||||
const { orderDiffById, getAnimationType } = useOrderDiff(orderedIds, chat?.id);
|
const { orderDiffById, getAnimationType, onReorderAnimationEnd } = useOrderDiff(orderedIds, chat?.id);
|
||||||
|
|
||||||
const [viewportIds, getMore] = useInfiniteScroll(() => {
|
const [viewportIds, getMore] = useInfiniteScroll(() => {
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
@ -207,6 +207,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
observeIntersection={observe}
|
observeIntersection={observe}
|
||||||
animationType={getAnimationType(id)}
|
animationType={getAnimationType(id)}
|
||||||
orderDiff={orderDiffById[id]}
|
orderDiff={orderDiffById[id]}
|
||||||
|
onReorderAnimationEnd={onReorderAnimationEnd}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,6 +57,7 @@ type OwnProps = {
|
|||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
orderDiff: number;
|
orderDiff: number;
|
||||||
animationType: ChatAnimationTypes;
|
animationType: ChatAnimationTypes;
|
||||||
|
onReorderAnimationEnd?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -96,6 +97,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
draft,
|
draft,
|
||||||
wasTopicOpened,
|
wasTopicOpened,
|
||||||
topics,
|
topics,
|
||||||
|
onReorderAnimationEnd,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openThread,
|
openThread,
|
||||||
@ -152,6 +154,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
animationType,
|
animationType,
|
||||||
withInterfaceAnimations,
|
withInterfaceAnimations,
|
||||||
orderDiff,
|
orderDiff,
|
||||||
|
onReorderAnimationEnd,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleOpenTopic = useLastCallback((e: React.MouseEvent) => {
|
const handleOpenTopic = useLastCallback((e: React.MouseEvent) => {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import type {
|
|||||||
} from '../../../../api/types';
|
} from '../../../../api/types';
|
||||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import { ANIMATION_END_DELAY, CHAT_HEIGHT_PX } from '../../../../config';
|
import { CHAT_HEIGHT_PX } from '../../../../config';
|
||||||
import { requestMutation } from '../../../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
getMessageIsSpoiler,
|
getMessageIsSpoiler,
|
||||||
@ -17,6 +17,7 @@ import {
|
|||||||
getMessageVideo,
|
getMessageVideo,
|
||||||
} from '../../../../global/helpers';
|
} from '../../../../global/helpers';
|
||||||
import { getMessageSenderName } from '../../../../global/helpers/peers';
|
import { getMessageSenderName } from '../../../../global/helpers/peers';
|
||||||
|
import { waitStartingTransitionsEnd } from '../../../../util/animations/waitTransitionEnd';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
import renderText from '../../../common/helpers/renderText';
|
import renderText from '../../../common/helpers/renderText';
|
||||||
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
||||||
@ -33,8 +34,6 @@ import Icon from '../../../common/icons/Icon';
|
|||||||
import MessageSummary from '../../../common/MessageSummary';
|
import MessageSummary from '../../../common/MessageSummary';
|
||||||
import TypingStatus from '../../../common/TypingStatus';
|
import TypingStatus from '../../../common/TypingStatus';
|
||||||
|
|
||||||
const ANIMATION_DURATION = 200;
|
|
||||||
|
|
||||||
export default function useChatListEntry({
|
export default function useChatListEntry({
|
||||||
chat,
|
chat,
|
||||||
topics,
|
topics,
|
||||||
@ -53,6 +52,7 @@ export default function useChatListEntry({
|
|||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
isPreview,
|
isPreview,
|
||||||
noForumTitle,
|
noForumTitle,
|
||||||
|
onReorderAnimationEnd,
|
||||||
}: {
|
}: {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
topics?: Record<number, ApiTopic>;
|
topics?: Record<number, ApiTopic>;
|
||||||
@ -72,6 +72,7 @@ export default function useChatListEntry({
|
|||||||
orderDiff: number;
|
orderDiff: number;
|
||||||
withInterfaceAnimations?: boolean;
|
withInterfaceAnimations?: boolean;
|
||||||
noForumTitle?: boolean;
|
noForumTitle?: boolean;
|
||||||
|
onReorderAnimationEnd?: NoneToVoidFunction;
|
||||||
}) {
|
}) {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
@ -171,6 +172,18 @@ export default function useChatListEntry({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isCancelled = false;
|
||||||
|
|
||||||
|
const notifyAnimationEnd = () => {
|
||||||
|
if (isCancelled) return;
|
||||||
|
requestMutation(() => {
|
||||||
|
element.classList.remove('animate-opacity', 'animate-transform');
|
||||||
|
element.style.opacity = '';
|
||||||
|
element.style.transform = '';
|
||||||
|
});
|
||||||
|
onReorderAnimationEnd?.();
|
||||||
|
};
|
||||||
|
|
||||||
// TODO Refactor animation: create `useListAnimation` that owns `orderDiff` and `animationType`
|
// TODO Refactor animation: create `useListAnimation` that owns `orderDiff` and `animationType`
|
||||||
if (animationType === ChatAnimationTypes.Opacity) {
|
if (animationType === ChatAnimationTypes.Opacity) {
|
||||||
element.style.opacity = '0';
|
element.style.opacity = '0';
|
||||||
@ -178,6 +191,8 @@ export default function useChatListEntry({
|
|||||||
requestMutation(() => {
|
requestMutation(() => {
|
||||||
element.classList.add('animate-opacity');
|
element.classList.add('animate-opacity');
|
||||||
element.style.opacity = '1';
|
element.style.opacity = '1';
|
||||||
|
|
||||||
|
waitStartingTransitionsEnd(element).then(notifyAnimationEnd);
|
||||||
});
|
});
|
||||||
} else if (animationType === ChatAnimationTypes.Move) {
|
} else if (animationType === ChatAnimationTypes.Move) {
|
||||||
element.style.transform = `translate3d(0, ${-orderDiff * CHAT_HEIGHT_PX}px, 0)`;
|
element.style.transform = `translate3d(0, ${-orderDiff * CHAT_HEIGHT_PX}px, 0)`;
|
||||||
@ -185,19 +200,17 @@ export default function useChatListEntry({
|
|||||||
requestMutation(() => {
|
requestMutation(() => {
|
||||||
element.classList.add('animate-transform');
|
element.classList.add('animate-transform');
|
||||||
element.style.transform = '';
|
element.style.transform = '';
|
||||||
|
|
||||||
|
waitStartingTransitionsEnd(element).then(notifyAnimationEnd);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
return () => {
|
||||||
requestMutation(() => {
|
isCancelled = true;
|
||||||
element.classList.remove('animate-opacity', 'animate-transform');
|
};
|
||||||
element.style.opacity = '';
|
}, [withInterfaceAnimations, orderDiff, animationType, onReorderAnimationEnd]);
|
||||||
element.style.transform = '';
|
|
||||||
});
|
|
||||||
}, ANIMATION_DURATION + ANIMATION_END_DELAY);
|
|
||||||
}, [withInterfaceAnimations, orderDiff, animationType]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
renderSubtitle,
|
renderSubtitle,
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import { useMemo } from '../../../../lib/teact/teact';
|
import { useMemo, useRef } from '../../../../lib/teact/teact';
|
||||||
|
|
||||||
import { mapValues } from '../../../../util/iteratees';
|
import { mapValues } from '../../../../util/iteratees';
|
||||||
import { useChatAnimationType } from './useChatAnimationType';
|
import { useChatAnimationType } from './useChatAnimationType';
|
||||||
|
|
||||||
|
import useForceUpdate from '../../../../hooks/useForceUpdate';
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
import usePreviousDeprecated from '../../../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../../../hooks/usePreviousDeprecated';
|
||||||
|
import useSyncEffect from '../../../../hooks/useSyncEffect';
|
||||||
|
|
||||||
|
const EMPTY_ORDER_DIFF = {};
|
||||||
|
|
||||||
export default function useOrderDiff(orderedIds: (string | number)[] | undefined, key?: string) {
|
export default function useOrderDiff(orderedIds: (string | number)[] | undefined, key?: string) {
|
||||||
const orderById = useMemo(() => {
|
const orderById = useMemo(() => {
|
||||||
@ -20,20 +25,35 @@ export default function useOrderDiff(orderedIds: (string | number)[] | undefined
|
|||||||
const prevOrderById = usePreviousDeprecated(orderById);
|
const prevOrderById = usePreviousDeprecated(orderById);
|
||||||
const prevChatId = usePreviousDeprecated(key);
|
const prevChatId = usePreviousDeprecated(key);
|
||||||
|
|
||||||
const orderDiffById = useMemo(() => {
|
const orderDiffByIdRef = useRef<Record<string | number, number>>(EMPTY_ORDER_DIFF);
|
||||||
|
const forceUpdate = useForceUpdate();
|
||||||
|
|
||||||
|
const onReorderAnimationEnd = useLastCallback(() => {
|
||||||
|
if (orderDiffByIdRef.current === EMPTY_ORDER_DIFF) return;
|
||||||
|
|
||||||
|
orderDiffByIdRef.current = EMPTY_ORDER_DIFF;
|
||||||
|
forceUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
useSyncEffect(() => {
|
||||||
if (!orderById || !prevOrderById || key !== prevChatId) {
|
if (!orderById || !prevOrderById || key !== prevChatId) {
|
||||||
return {};
|
orderDiffByIdRef.current = EMPTY_ORDER_DIFF;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapValues(orderById, (order, id) => {
|
const diff = mapValues(orderById, (order, id) => {
|
||||||
return prevOrderById[id] !== undefined ? order - prevOrderById[id] : -Infinity;
|
return prevOrderById[id] !== undefined ? order - prevOrderById[id] : -Infinity;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasChanges = Object.values(diff).some((value) => value !== 0);
|
||||||
|
orderDiffByIdRef.current = hasChanges ? diff : EMPTY_ORDER_DIFF;
|
||||||
}, [key, orderById, prevChatId, prevOrderById]);
|
}, [key, orderById, prevChatId, prevOrderById]);
|
||||||
|
|
||||||
const getAnimationType = useChatAnimationType(orderDiffById);
|
const getAnimationType = useChatAnimationType(orderDiffByIdRef.current);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
orderDiffById,
|
orderDiffById: orderDiffByIdRef.current,
|
||||||
getAnimationType,
|
getAnimationType,
|
||||||
|
onReorderAnimationEnd,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
|
|
||||||
transition: background-color 0.15s, color 0.15s;
|
transition: background-color 0.15s, color 0.15s, opacity 150ms, backdrop-filter 150ms, filter 150ms;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -70,8 +70,6 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
background-color: var(--pattern-color);
|
background-color: var(--pattern-color);
|
||||||
|
|
||||||
transition: opacity 150ms, backdrop-filter 150ms, filter 150ms;
|
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: attr(data-cnt);
|
content: attr(data-cnt);
|
||||||
|
|
||||||
@ -96,6 +94,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
background-color: var(--pattern-color);
|
||||||
|
}
|
||||||
|
|
||||||
.Message:hover &, &.loading {
|
.Message:hover &, &.loading {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@ -139,6 +141,10 @@
|
|||||||
filter: none;
|
filter: none;
|
||||||
backdrop-filter: none;
|
backdrop-filter: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
background-color: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -249,6 +255,8 @@
|
|||||||
&.disabled {
|
&.disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
cursor: var(--custom-cursor, default);
|
cursor: var(--custom-cursor, default);
|
||||||
|
opacity: 0.65;
|
||||||
|
background-color: var(--hover-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import Spinner from '../../ui/Spinner';
|
|||||||
import './CommentButton.scss';
|
import './CommentButton.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
threadInfo: ApiCommentsInfo;
|
threadInfo?: ApiCommentsInfo;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
isCustomShape?: boolean;
|
isCustomShape?: boolean;
|
||||||
@ -45,10 +45,15 @@ const CommentButton: FC<OwnProps> = ({
|
|||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const {
|
const {
|
||||||
originMessageId, chatId, messagesCount, lastMessageId, lastReadInboxMessageId, recentReplierIds, originChannelId,
|
originMessageId, chatId, messagesCount, lastMessageId, lastReadInboxMessageId, recentReplierIds, originChannelId,
|
||||||
} = threadInfo;
|
} = threadInfo || {};
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
|
|
||||||
|
if (!originMessageId || !originChannelId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (selectIsCurrentUserFrozen(global)) {
|
if (selectIsCurrentUserFrozen(global)) {
|
||||||
openFrozenAccountModal();
|
openFrozenAccountModal();
|
||||||
return;
|
return;
|
||||||
@ -71,10 +76,6 @@ const CommentButton: FC<OwnProps> = ({
|
|||||||
}).filter(Boolean);
|
}).filter(Boolean);
|
||||||
}, [recentReplierIds]);
|
}, [recentReplierIds]);
|
||||||
|
|
||||||
if (messagesCount === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderRecentRepliers() {
|
function renderRecentRepliers() {
|
||||||
return (
|
return (
|
||||||
Boolean(recentRepliers?.length) && (
|
Boolean(recentRepliers?.length) && (
|
||||||
@ -102,7 +103,7 @@ const CommentButton: FC<OwnProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-cnt={formatIntegerCompact(lang, messagesCount)}
|
data-cnt={formatIntegerCompact(lang, messagesCount || 0)}
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
'CommentButton',
|
'CommentButton',
|
||||||
hasUnread && 'has-unread',
|
hasUnread && 'has-unread',
|
||||||
|
|||||||
@ -787,11 +787,14 @@ const Message = ({
|
|||||||
|
|
||||||
const phoneCall = action?.type === 'phoneCall' ? action : undefined;
|
const phoneCall = action?.type === 'phoneCall' ? action : undefined;
|
||||||
|
|
||||||
const isMediaWithCommentButton = (repliesThreadInfo || (hasLinkedChat && isChannel && isLocal))
|
const commentsThreadInfo = repliesThreadInfo?.isCommentsInfo ? repliesThreadInfo : undefined;
|
||||||
|
const isLocalWithCommentButton = hasLinkedChat && isChannel && isLocal;
|
||||||
|
|
||||||
|
const isMediaWithCommentButton = (commentsThreadInfo || isLocalWithCommentButton)
|
||||||
&& !isInDocumentGroupNotLast
|
&& !isInDocumentGroupNotLast
|
||||||
&& messageListType === 'thread'
|
&& messageListType === 'thread'
|
||||||
&& !noComments;
|
&& !noComments;
|
||||||
const withCommentButton = repliesThreadInfo?.isCommentsInfo
|
const withCommentButton = (commentsThreadInfo || isLocalWithCommentButton)
|
||||||
&& !isInDocumentGroupNotLast && messageListType === 'thread'
|
&& !isInDocumentGroupNotLast && messageListType === 'thread'
|
||||||
&& !noComments;
|
&& !noComments;
|
||||||
const withQuickReactionButton = !isTouchScreen && !phoneCall && !isInSelectMode && defaultReaction
|
const withQuickReactionButton = !isTouchScreen && !phoneCall && !isInSelectMode && defaultReaction
|
||||||
@ -1726,8 +1729,8 @@ const Message = ({
|
|||||||
>
|
>
|
||||||
{withCommentButton && isCustomShape && (
|
{withCommentButton && isCustomShape && (
|
||||||
<CommentButton
|
<CommentButton
|
||||||
threadInfo={repliesThreadInfo}
|
threadInfo={commentsThreadInfo}
|
||||||
disabled={noComments}
|
disabled={noComments || !commentsThreadInfo}
|
||||||
isLoading={isLoadingComments}
|
isLoading={isLoadingComments}
|
||||||
isCustomShape
|
isCustomShape
|
||||||
asActionButton
|
asActionButton
|
||||||
@ -1761,8 +1764,8 @@ const Message = ({
|
|||||||
)}
|
)}
|
||||||
{withCommentButton && !isCustomShape && (
|
{withCommentButton && !isCustomShape && (
|
||||||
<CommentButton
|
<CommentButton
|
||||||
threadInfo={repliesThreadInfo}
|
threadInfo={commentsThreadInfo}
|
||||||
disabled={noComments}
|
disabled={noComments || !commentsThreadInfo}
|
||||||
isLoading={isLoadingComments}
|
isLoading={isLoadingComments}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -179,6 +179,14 @@ const Photo = <T,>({
|
|||||||
isOpen: !fullMediaData && !isLoadAllowed,
|
isOpen: !fullMediaData && !isLoadAllowed,
|
||||||
withShouldRender: true,
|
withShouldRender: true,
|
||||||
});
|
});
|
||||||
|
const {
|
||||||
|
ref: transferProgressRef,
|
||||||
|
shouldRender: shouldRenderTransferProgress,
|
||||||
|
} = useShowTransition({
|
||||||
|
isOpen: isTransferring,
|
||||||
|
noMountTransition: wasLoadDisabled,
|
||||||
|
withShouldRender: true,
|
||||||
|
});
|
||||||
|
|
||||||
const handleClick = useLastCallback((e: React.MouseEvent<HTMLElement>) => {
|
const handleClick = useLastCallback((e: React.MouseEvent<HTMLElement>) => {
|
||||||
if (isUploading) {
|
if (isUploading) {
|
||||||
@ -291,10 +299,9 @@ const Photo = <T,>({
|
|||||||
className="media-spoiler"
|
className="media-spoiler"
|
||||||
isNsfw={isMediaNsfw}
|
isNsfw={isMediaNsfw}
|
||||||
/>
|
/>
|
||||||
{isTransferring && (
|
{shouldRenderTransferProgress && (
|
||||||
<span className="message-transfer-progress">
|
<span ref={transferProgressRef} className="message-transfer-progress">
|
||||||
{Math.round(transferProgress * 100)}
|
{`${Math.round(transferProgress * 100)}%`}
|
||||||
%
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<SensitiveContentConfirmModal
|
<SensitiveContentConfirmModal
|
||||||
|
|||||||
@ -188,6 +188,14 @@ const Video = <T,>({
|
|||||||
} = useShowTransition({
|
} = useShowTransition({
|
||||||
isOpen: Boolean((isLoadAllowed || fullMediaData) && !isPlayAllowed && !shouldRenderSpinner),
|
isOpen: Boolean((isLoadAllowed || fullMediaData) && !isPlayAllowed && !shouldRenderSpinner),
|
||||||
});
|
});
|
||||||
|
const {
|
||||||
|
ref: transferProgressRef,
|
||||||
|
shouldRender: shouldRenderTransferProgress,
|
||||||
|
} = useShowTransition({
|
||||||
|
isOpen: isTransferring && (!isUnsupported || isDownloading),
|
||||||
|
noMountTransition: wasLoadDisabled,
|
||||||
|
withShouldRender: true,
|
||||||
|
});
|
||||||
|
|
||||||
const [playProgress, setPlayProgress] = useState<number>(0);
|
const [playProgress, setPlayProgress] = useState<number>(0);
|
||||||
const handleTimeUpdate = useLastCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
|
const handleTimeUpdate = useLastCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
|
||||||
@ -324,8 +332,8 @@ const Video = <T,>({
|
|||||||
{!isLoadAllowed && !fullMediaData && (
|
{!isLoadAllowed && !fullMediaData && (
|
||||||
<Icon name="download" />
|
<Icon name="download" />
|
||||||
)}
|
)}
|
||||||
{isTransferring && (!isUnsupported || isDownloading) ? (
|
{shouldRenderTransferProgress ? (
|
||||||
<span className="message-transfer-progress">
|
<span ref={transferProgressRef} className="message-transfer-progress">
|
||||||
{(isUploading || isDownloading) ? `${Math.round(transferProgress * 100)}%` : '...'}
|
{(isUploading || isDownloading) ? `${Math.round(transferProgress * 100)}%` : '...'}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { addCallback } from '../../../lib/teact/teactn';
|
import { addCallback } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
|
import type { ApiThreadInfo } from '../../../api/types/messages';
|
||||||
import type { Thread, ThreadId } from '../../../types';
|
import type { Thread, ThreadId } from '../../../types';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type { ActionReturnType, GlobalState } from '../../types';
|
import type { ActionReturnType, GlobalState } from '../../types';
|
||||||
@ -128,6 +129,19 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
selectChatMessage(global, global.currentUserId!, Number(messageId))
|
selectChatMessage(global, global.currentUserId!, Number(messageId))
|
||||||
)).filter(Boolean);
|
)).filter(Boolean);
|
||||||
|
|
||||||
|
// Memoize thread infos for last messages
|
||||||
|
const lastMessagesThreadInfos: { chatId: string; messageId: number; threadInfo: ApiThreadInfo }[] = [];
|
||||||
|
lastMessages.forEach((message) => {
|
||||||
|
const threadInfo = selectThreadInfo(global, message.chatId, message.id);
|
||||||
|
if (threadInfo) {
|
||||||
|
lastMessagesThreadInfos.push({
|
||||||
|
chatId: message.chatId,
|
||||||
|
messageId: message.id,
|
||||||
|
threadInfo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (const { id: tabId } of Object.values(global.byTabId)) {
|
for (const { id: tabId } of Object.values(global.byTabId)) {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global, tabId) || {};
|
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global, tabId) || {};
|
||||||
@ -256,6 +270,14 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Restore thread infos
|
||||||
|
lastMessagesThreadInfos.forEach(({ chatId, messageId, threadInfo }) => {
|
||||||
|
const memoThreadInfo = selectThreadInfo(global, chatId, messageId);
|
||||||
|
if (!memoThreadInfo) {
|
||||||
|
global = updateThreadInfo(global, chatId, String(messageId), threadInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Restore last messages
|
// Restore last messages
|
||||||
global = addMessages(global, lastMessages);
|
global = addMessages(global, lastMessages);
|
||||||
global = addMessages(global, savedLastMessages);
|
global = addMessages(global, savedLastMessages);
|
||||||
|
|||||||
@ -587,6 +587,9 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
} = update;
|
} = update;
|
||||||
|
|
||||||
global = updateThreadInfos(global, [threadInfo]);
|
global = updateThreadInfos(global, [threadInfo]);
|
||||||
|
setGlobal(global);
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
const { chatId, threadId } = threadInfo;
|
const { chatId, threadId } = threadInfo;
|
||||||
if (!chatId || !threadId) return;
|
if (!chatId || !threadId) return;
|
||||||
|
|
||||||
|
|||||||
19
src/util/animations/waitTransitionEnd.ts
Normal file
19
src/util/animations/waitTransitionEnd.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { fastRaf } from '../schedulers';
|
||||||
|
|
||||||
|
export type Scheduler = typeof requestAnimationFrame | typeof fastRaf;
|
||||||
|
|
||||||
|
export function waitCurrentTransitionsEnd(element: HTMLElement) {
|
||||||
|
return Promise.allSettled(
|
||||||
|
element.getAnimations({ subtree: true })
|
||||||
|
.filter((a) => a instanceof CSSTransition)
|
||||||
|
.map((a) => a.finished),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function waitStartingTransitionsEnd(element: HTMLElement, scheduler: Scheduler = fastRaf) {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
scheduler(() => {
|
||||||
|
waitCurrentTransitionsEnd(element).then(() => resolve());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user