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 { withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiUser, ApiMessage, ApiChat } from '../../api/types';
|
import { ApiUser, ApiMessage, ApiChat } from '../../api/types';
|
||||||
@ -21,11 +23,14 @@ import useFocusMessage from './message/hooks/useFocusMessage';
|
|||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import ContextMenuContainer from './message/ContextMenuContainer.async';
|
import ContextMenuContainer from './message/ContextMenuContainer.async';
|
||||||
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
isEmbedded?: boolean;
|
isEmbedded?: boolean;
|
||||||
|
appearanceOrder?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -38,10 +43,13 @@ type StateProps = {
|
|||||||
noFocusHighlight?: boolean;
|
noFocusHighlight?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const APPEARANCE_DELAY = 10;
|
||||||
|
|
||||||
const ActionMessage: FC<OwnProps & StateProps> = ({
|
const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||||
message,
|
message,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
isEmbedded,
|
isEmbedded,
|
||||||
|
appearanceOrder = 0,
|
||||||
sender,
|
sender,
|
||||||
targetUser,
|
targetUser,
|
||||||
targetMessage,
|
targetMessage,
|
||||||
@ -59,6 +67,17 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useLang();
|
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(
|
const content = renderActionMessageText(
|
||||||
message,
|
message,
|
||||||
sender,
|
sender,
|
||||||
@ -86,6 +105,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
'ActionMessage message-list-item',
|
'ActionMessage message-list-item',
|
||||||
isFocused && !noFocusHighlight && 'focused',
|
isFocused && !noFocusHighlight && 'focused',
|
||||||
isContextMenuShown && 'has-menu-open',
|
isContextMenuShown && 'has-menu-open',
|
||||||
|
transitionClassNames,
|
||||||
)}
|
)}
|
||||||
data-message-id={message.id}
|
data-message-id={message.id}
|
||||||
onMouseDown={handleBeforeContextMenu}
|
onMouseDown={handleBeforeContextMenu}
|
||||||
|
|||||||
@ -153,6 +153,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const memoFirstUnreadIdRef = useRef<number>();
|
const memoFirstUnreadIdRef = useRef<number>();
|
||||||
const memoFocusingIdRef = useRef<number>();
|
const memoFocusingIdRef = useRef<number>();
|
||||||
const isScrollTopJustUpdatedRef = useRef(false);
|
const isScrollTopJustUpdatedRef = useRef(false);
|
||||||
|
const shouldAnimateAppearanceRef = useRef(!messageIds);
|
||||||
|
|
||||||
const [containerHeight, setContainerHeight] = useState<number | undefined>();
|
const [containerHeight, setContainerHeight] = useState<number | undefined>();
|
||||||
const [hasFocusing, setHasFocusing] = useState<boolean>(Boolean(focusingId));
|
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.
|
// We update local cached `scrollOffsetRef` when opening chat.
|
||||||
// Then we update global version every second on scrolling.
|
// Then we update global version every second on scrolling.
|
||||||
scrollOffsetRef.current = (type === 'thread' && selectScrollOffset(getGlobal(), chatId, threadId)) || 0;
|
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)]);
|
}, [Boolean(messageIds)]);
|
||||||
|
|
||||||
useOnChange(() => {
|
useOnChange(() => {
|
||||||
@ -516,6 +523,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
{messageGroups && renderMessages(
|
{messageGroups && renderMessages(
|
||||||
lang,
|
lang,
|
||||||
messageGroups,
|
messageGroups,
|
||||||
|
shouldAnimateAppearanceRef.current ? messageIds.length : 0,
|
||||||
observeIntersectionForReading,
|
observeIntersectionForReading,
|
||||||
observeIntersectionForMedia,
|
observeIntersectionForMedia,
|
||||||
observeIntersectionForAnimatedStickers,
|
observeIntersectionForAnimatedStickers,
|
||||||
@ -537,6 +545,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
{renderMessages(
|
{renderMessages(
|
||||||
lang,
|
lang,
|
||||||
groupMessages([lastMessage]),
|
groupMessages([lastMessage]),
|
||||||
|
0,
|
||||||
observeIntersectionForReading,
|
observeIntersectionForReading,
|
||||||
observeIntersectionForMedia,
|
observeIntersectionForMedia,
|
||||||
observeIntersectionForAnimatedStickers,
|
observeIntersectionForAnimatedStickers,
|
||||||
@ -561,6 +570,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
function renderMessages(
|
function renderMessages(
|
||||||
lang: LangFn,
|
lang: LangFn,
|
||||||
messageGroups: MessageDateGroup[],
|
messageGroups: MessageDateGroup[],
|
||||||
|
messageCountToAnimate: number,
|
||||||
observeIntersectionForReading: ObserveFn,
|
observeIntersectionForReading: ObserveFn,
|
||||||
observeIntersectionForMedia: ObserveFn,
|
observeIntersectionForMedia: ObserveFn,
|
||||||
observeIntersectionForAnimatedStickers: ObserveFn,
|
observeIntersectionForAnimatedStickers: ObserveFn,
|
||||||
@ -580,6 +590,8 @@ function renderMessages(
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let appearanceIndex = 0;
|
||||||
|
|
||||||
const dateGroups = messageGroups.map((
|
const dateGroups = messageGroups.map((
|
||||||
dateGroup: MessageDateGroup,
|
dateGroup: MessageDateGroup,
|
||||||
dateGroupIndex: number,
|
dateGroupIndex: number,
|
||||||
@ -599,6 +611,7 @@ function renderMessages(
|
|||||||
key={message.id}
|
key={message.id}
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersectionForReading}
|
observeIntersection={observeIntersectionForReading}
|
||||||
|
appearanceOrder={messageCountToAnimate - ++appearanceIndex}
|
||||||
/>,
|
/>,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -660,6 +673,7 @@ function renderMessages(
|
|||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
messageListType={type}
|
messageListType={type}
|
||||||
noComments={hasLinkedChat === false}
|
noComments={hasLinkedChat === false}
|
||||||
|
appearanceOrder={messageCountToAnimate - ++appearanceIndex}
|
||||||
isFirstInGroup={position.isFirstInGroup}
|
isFirstInGroup={position.isFirstInGroup}
|
||||||
isLastInGroup={position.isLastInGroup}
|
isLastInGroup={position.isLastInGroup}
|
||||||
isFirstInDocumentGroup={position.isFirstInDocumentGroup}
|
isFirstInDocumentGroup={position.isFirstInDocumentGroup}
|
||||||
|
|||||||
@ -19,6 +19,15 @@
|
|||||||
--select-message-scale: 0.9;
|
--select-message-scale: 0.9;
|
||||||
--select-background-color: white;
|
--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,
|
> .Avatar,
|
||||||
> .message-content-wrapper {
|
> .message-content-wrapper {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@ -299,6 +308,7 @@
|
|||||||
.message-select-control:not(.group-select) {
|
.message-select-control:not(.group-select) {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
border-color: var(--background-color);
|
border-color: var(--background-color);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
border-color: rgba(var(--color-text-green-rgb), 0.5);
|
border-color: rgba(var(--color-text-green-rgb), 0.5);
|
||||||
@ -309,6 +319,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.focused,
|
&.focused,
|
||||||
&.has-menu-open,
|
&.has-menu-open,
|
||||||
&.is-forwarding,
|
&.is-forwarding,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import React, {
|
|||||||
FC,
|
FC,
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useEffect,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
@ -64,6 +65,8 @@ import { ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserve
|
|||||||
import useFocusMessage from './hooks/useFocusMessage';
|
import useFocusMessage from './hooks/useFocusMessage';
|
||||||
import useWindowSize from '../../../hooks/useWindowSize';
|
import useWindowSize from '../../../hooks/useWindowSize';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import Avatar from '../../common/Avatar';
|
import Avatar from '../../common/Avatar';
|
||||||
@ -106,6 +109,7 @@ type OwnProps = {
|
|||||||
threadId: number;
|
threadId: number;
|
||||||
messageListType: MessageListType;
|
messageListType: MessageListType;
|
||||||
noComments: boolean;
|
noComments: boolean;
|
||||||
|
appearanceOrder: number;
|
||||||
} & MessagePositionProperties;
|
} & MessagePositionProperties;
|
||||||
|
|
||||||
type StateProps = {
|
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>';
|
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
|
// 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 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> = ({
|
const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
message,
|
message,
|
||||||
@ -163,6 +168,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
withAvatar,
|
withAvatar,
|
||||||
withSenderName,
|
withSenderName,
|
||||||
noComments,
|
noComments,
|
||||||
|
appearanceOrder,
|
||||||
isFirstInGroup,
|
isFirstInGroup,
|
||||||
isLastInGroup,
|
isLastInGroup,
|
||||||
isFirstInDocumentGroup,
|
isFirstInDocumentGroup,
|
||||||
@ -226,6 +232,17 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref);
|
} = 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 { chatId, id: messageId, threadInfo } = message;
|
||||||
|
|
||||||
const isOwn = isOwnMessage(message);
|
const isOwn = isOwnMessage(message);
|
||||||
@ -264,6 +281,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
isInSelectMode && 'is-in-selection-mode',
|
isInSelectMode && 'is-in-selection-mode',
|
||||||
isThreadTop && 'is-thread-top',
|
isThreadTop && 'is-thread-top',
|
||||||
Boolean(message.inlineButtons) && 'has-inline-buttons',
|
Boolean(message.inlineButtons) && 'has-inline-buttons',
|
||||||
|
transitionClassNames,
|
||||||
);
|
);
|
||||||
const contentClassName = buildContentClassName(message, {
|
const contentClassName = buildContentClassName(message, {
|
||||||
hasReply,
|
hasReply,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user