Message List: Appearance animation
This commit is contained in:
parent
240e78de0b
commit
8c818080be
@ -1,4 +1,6 @@
|
||||
import React, { FC, memo, useRef } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
FC, memo, useEffect, useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { ApiUser, ApiMessage, ApiChat } from '../../api/types';
|
||||
@ -21,11 +23,14 @@ import useFocusMessage from './message/hooks/useFocusMessage';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import ContextMenuContainer from './message/ContextMenuContainer.async';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
|
||||
type OwnProps = {
|
||||
message: ApiMessage;
|
||||
observeIntersection?: ObserveFn;
|
||||
isEmbedded?: boolean;
|
||||
appearanceOrder?: number;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -38,10 +43,13 @@ type StateProps = {
|
||||
noFocusHighlight?: boolean;
|
||||
};
|
||||
|
||||
const APPEARANCE_DELAY = 10;
|
||||
|
||||
const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
message,
|
||||
observeIntersection,
|
||||
isEmbedded,
|
||||
appearanceOrder = 0,
|
||||
sender,
|
||||
targetUser,
|
||||
targetMessage,
|
||||
@ -59,6 +67,17 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
|
||||
useLang();
|
||||
|
||||
const noAppearanceAnimation = appearanceOrder <= 0;
|
||||
const [isShown, markShown] = useFlag(noAppearanceAnimation);
|
||||
useEffect(() => {
|
||||
if (noAppearanceAnimation) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(markShown, appearanceOrder * APPEARANCE_DELAY);
|
||||
}, [appearanceOrder, markShown, noAppearanceAnimation]);
|
||||
const { transitionClassNames } = useShowTransition(isShown, undefined, noAppearanceAnimation, false);
|
||||
|
||||
const content = renderActionMessageText(
|
||||
message,
|
||||
sender,
|
||||
@ -86,6 +105,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
'ActionMessage message-list-item',
|
||||
isFocused && !noFocusHighlight && 'focused',
|
||||
isContextMenuShown && 'has-menu-open',
|
||||
transitionClassNames,
|
||||
)}
|
||||
data-message-id={message.id}
|
||||
onMouseDown={handleBeforeContextMenu}
|
||||
|
||||
@ -153,6 +153,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const memoFirstUnreadIdRef = useRef<number>();
|
||||
const memoFocusingIdRef = useRef<number>();
|
||||
const isScrollTopJustUpdatedRef = useRef(false);
|
||||
const shouldAnimateAppearanceRef = useRef(!messageIds);
|
||||
|
||||
const [containerHeight, setContainerHeight] = useState<number | undefined>();
|
||||
const [hasFocusing, setHasFocusing] = useState<boolean>(Boolean(focusingId));
|
||||
@ -165,6 +166,12 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
// We update local cached `scrollOffsetRef` when opening chat.
|
||||
// Then we update global version every second on scrolling.
|
||||
scrollOffsetRef.current = (type === 'thread' && selectScrollOffset(getGlobal(), chatId, threadId)) || 0;
|
||||
|
||||
// We need it just first time when message list appears
|
||||
// TODO Figure out why `onTickEnd`/100ms is not enough
|
||||
setTimeout(() => {
|
||||
shouldAnimateAppearanceRef.current = false;
|
||||
}, 1000);
|
||||
}, [Boolean(messageIds)]);
|
||||
|
||||
useOnChange(() => {
|
||||
@ -516,6 +523,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
{messageGroups && renderMessages(
|
||||
lang,
|
||||
messageGroups,
|
||||
shouldAnimateAppearanceRef.current ? messageIds.length : 0,
|
||||
observeIntersectionForReading,
|
||||
observeIntersectionForMedia,
|
||||
observeIntersectionForAnimatedStickers,
|
||||
@ -537,6 +545,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
{renderMessages(
|
||||
lang,
|
||||
groupMessages([lastMessage]),
|
||||
0,
|
||||
observeIntersectionForReading,
|
||||
observeIntersectionForMedia,
|
||||
observeIntersectionForAnimatedStickers,
|
||||
@ -561,6 +570,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
function renderMessages(
|
||||
lang: LangFn,
|
||||
messageGroups: MessageDateGroup[],
|
||||
messageCountToAnimate: number,
|
||||
observeIntersectionForReading: ObserveFn,
|
||||
observeIntersectionForMedia: ObserveFn,
|
||||
observeIntersectionForAnimatedStickers: ObserveFn,
|
||||
@ -580,6 +590,8 @@ function renderMessages(
|
||||
</div>
|
||||
);
|
||||
|
||||
let appearanceIndex = 0;
|
||||
|
||||
const dateGroups = messageGroups.map((
|
||||
dateGroup: MessageDateGroup,
|
||||
dateGroupIndex: number,
|
||||
@ -599,6 +611,7 @@ function renderMessages(
|
||||
key={message.id}
|
||||
message={message}
|
||||
observeIntersection={observeIntersectionForReading}
|
||||
appearanceOrder={messageCountToAnimate - ++appearanceIndex}
|
||||
/>,
|
||||
]);
|
||||
}
|
||||
@ -660,6 +673,7 @@ function renderMessages(
|
||||
threadId={threadId}
|
||||
messageListType={type}
|
||||
noComments={hasLinkedChat === false}
|
||||
appearanceOrder={messageCountToAnimate - ++appearanceIndex}
|
||||
isFirstInGroup={position.isFirstInGroup}
|
||||
isLastInGroup={position.isLastInGroup}
|
||||
isFirstInDocumentGroup={position.isFirstInDocumentGroup}
|
||||
|
||||
@ -19,6 +19,15 @@
|
||||
--select-message-scale: 0.9;
|
||||
--select-background-color: white;
|
||||
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
transition: opacity .2s ease, transform .2s ease;
|
||||
|
||||
&:not(.open) {
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
> .Avatar,
|
||||
> .message-content-wrapper {
|
||||
opacity: 1;
|
||||
@ -299,6 +308,7 @@
|
||||
.message-select-control:not(.group-select) {
|
||||
background: var(--background-color);
|
||||
border-color: var(--background-color);
|
||||
|
||||
&::after {
|
||||
background: var(--background-color);
|
||||
border-color: rgba(var(--color-text-green-rgb), 0.5);
|
||||
@ -309,6 +319,7 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.focused,
|
||||
&.has-menu-open,
|
||||
&.is-forwarding,
|
||||
|
||||
@ -3,6 +3,7 @@ import React, {
|
||||
FC,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
@ -64,6 +65,8 @@ import { ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserve
|
||||
import useFocusMessage from './hooks/useFocusMessage';
|
||||
import useWindowSize from '../../../hooks/useWindowSize';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
import Avatar from '../../common/Avatar';
|
||||
@ -106,6 +109,7 @@ type OwnProps = {
|
||||
threadId: number;
|
||||
messageListType: MessageListType;
|
||||
noComments: boolean;
|
||||
appearanceOrder: number;
|
||||
} & MessagePositionProperties;
|
||||
|
||||
type StateProps = {
|
||||
@ -153,6 +157,7 @@ const GROUP_MESSAGE_HOVER_ATTRIBUTE = 'data-is-document-group-hover';
|
||||
const APPENDIX_OWN = '<svg width="9" height="20" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="a"><feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><path d="M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z" fill="#000" filter="url(#a)"/><path d="M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z" fill="#EEFFDE" class="corner"/></g></svg>';
|
||||
// eslint-disable-next-line max-len
|
||||
const APPENDIX_NOT_OWN = '<svg width="9" height="20" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="a"><feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><path d="M3 17h6V0c-.193 2.84-.876 5.767-2.05 8.782-.904 2.325-2.446 4.485-4.625 6.48A1 1 0 003 17z" fill="#000" filter="url(#a)"/><path d="M3 17h6V0c-.193 2.84-.876 5.767-2.05 8.782-.904 2.325-2.446 4.485-4.625 6.48A1 1 0 003 17z" fill="#FFF" class="corner"/></g></svg>';
|
||||
const APPEARANCE_DELAY = 10;
|
||||
|
||||
const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
message,
|
||||
@ -163,6 +168,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
withAvatar,
|
||||
withSenderName,
|
||||
noComments,
|
||||
appearanceOrder,
|
||||
isFirstInGroup,
|
||||
isLastInGroup,
|
||||
isFirstInDocumentGroup,
|
||||
@ -226,6 +232,17 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
handleContextMenuClose, handleContextMenuHide,
|
||||
} = useContextMenuHandlers(ref);
|
||||
|
||||
const noAppearanceAnimation = appearanceOrder <= 0;
|
||||
const [isShown, markShown] = useFlag(noAppearanceAnimation);
|
||||
useEffect(() => {
|
||||
if (noAppearanceAnimation) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(markShown, appearanceOrder * APPEARANCE_DELAY);
|
||||
}, [appearanceOrder, markShown, noAppearanceAnimation]);
|
||||
const { transitionClassNames } = useShowTransition(isShown, undefined, noAppearanceAnimation, false);
|
||||
|
||||
const { chatId, id: messageId, threadInfo } = message;
|
||||
|
||||
const isOwn = isOwnMessage(message);
|
||||
@ -264,6 +281,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isInSelectMode && 'is-in-selection-mode',
|
||||
isThreadTop && 'is-thread-top',
|
||||
Boolean(message.inlineButtons) && 'has-inline-buttons',
|
||||
transitionClassNames,
|
||||
);
|
||||
const contentClassName = buildContentClassName(message, {
|
||||
hasReply,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user