From 37704a7715e7894e33bb687a72c76dba58e2ff37 Mon Sep 17 00:00:00 2001 From: Roma Koval Date: Mon, 10 Jun 2024 21:26:19 +0200 Subject: [PATCH] feat: Improved delivered status indicator WPB-9170 --- .../Message/ContentMessage/ContentMessage.tsx | 231 +++++++++--------- .../Message/ContentMessage/asset/index.tsx | 16 +- .../DeliveredIndicator.styles.ts | 42 ++++ .../DeliveredIndicator/DeliveredIndicator.tsx | 50 ++++ .../MessagesList/Message/PingMessage.tsx | 42 ++-- .../Message/ReadIndicator/ReadIndicator.tsx | 8 - .../Message/ReadReceiptStatus.tsx | 15 +- src/style/components/asset/file-asset.less | 2 +- .../content/conversation/message-list.less | 1 - 9 files changed, 236 insertions(+), 171 deletions(-) create mode 100644 src/script/components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator.styles.ts create mode 100644 src/script/components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator.tsx diff --git a/src/script/components/MessagesList/Message/ContentMessage/ContentMessage.tsx b/src/script/components/MessagesList/Message/ContentMessage/ContentMessage.tsx index e55e45afc72..e8859d3b096 100644 --- a/src/script/components/MessagesList/Message/ContentMessage/ContentMessage.tsx +++ b/src/script/components/MessagesList/Message/ContentMessage/ContentMessage.tsx @@ -23,6 +23,7 @@ import {QualifiedId} from '@wireapp/api-client/lib/user'; import cx from 'classnames'; import ko from 'knockout'; +import {DeliveredIndicator} from 'Components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator'; import {ReadIndicator} from 'Components/MessagesList/Message/ReadIndicator'; import {Conversation} from 'src/script/entity/Conversation'; import {CompositeMessage} from 'src/script/entity/message/CompositeMessage'; @@ -48,6 +49,7 @@ import {ContextMenuEntry} from '../../../../ui/ContextMenu'; import {EphemeralTimer} from '../EphemeralTimer'; import {MessageTime} from '../MessageTime'; import {useMessageFocusedTabIndex} from '../util'; + export interface ContentMessageProps extends Omit { contextMenu: {entries: ko.Subscribable}; conversation: Conversation; @@ -88,6 +90,7 @@ export const ContentMessageComponent = ({ onClickDetails, }: ContentMessageProps) => { const messageRef = useRef(null); + const [deliveryIndicatorRef, setDeliveryIndicatorRef] = useState(null); // check if current message is focused and its elements focusable const msgFocusState = useMemo(() => isMsgElementsFocusable && isFocused, [isMsgElementsFocusable, isFocused]); @@ -163,134 +166,138 @@ export const ContentMessageComponent = ({ useClickOutside(messageRef, hideActionMenuVisibility); return ( -
{ - // open another floating action menu if none already open - if (!isMenuOpen) { - setActionMenuVisibility(true); - } - }} - onMouseLeave={() => { - // close floating message actions when no active menu is open like context menu/emoji picker - if (!isMenuOpen) { - setActionMenuVisibility(false); - } - }} - > - {(was_edited || !hideHeader) && ( - - {was_edited && ( - - )} - - - - {timeAgo} - - - - )} - + <>
{ + // open another floating action menu if none already open + if (!isMenuOpen) { + setActionMenuVisibility(true); + } + }} + onMouseLeave={() => { + // close floating message actions when no active menu is open like context menu/emoji picker + if (!isMenuOpen) { + setActionMenuVisibility(false); + } + }} > - {ephemeral_status === EphemeralStatusType.ACTIVE && ( -
- -
+ {(was_edited || !hideHeader) && ( + + {was_edited && ( + + )} + + + + {timeAgo} + + + )} - {quote && ( - + {ephemeral_status === EphemeralStatusType.ACTIVE && ( +
+ +
+ )} + + {quote && ( + + )} + + {assets.map(asset => ( + onClickDetails(message)} + /> + ))} + + {isAssetMessage && ( + + )} + + {!isConversationReadonly && isActionMenuVisible && ( + + )} +
+ + {[StatusType.FAILED, StatusType.FEDERATION_ERROR].includes(status) && ( + onRetry(message)} /> )} - {assets.map(asset => ( - onClickDetails(message)} - /> - ))} - - {isAssetMessage && ( - )} - {!isConversationReadonly && isActionMenuVisible && ( - onClickReactionDetails(message)} + onLastReactionKeyEvent={() => setActionMenuVisibility(false)} isRemovedFromConversation={conversation.removed_from_conversation()} + users={conversation.allUserEntities()} /> )}
- - {[StatusType.FAILED, StatusType.FEDERATION_ERROR].includes(status) && ( - onRetry(message)} - /> - )} - - {failedToSend && ( - - )} - - {!!reactions.length && ( - onClickReactionDetails(message)} - onLastReactionKeyEvent={() => setActionMenuVisibility(false)} - isRemovedFromConversation={conversation.removed_from_conversation()} - users={conversation.allUserEntities()} - /> - )} - + + ); }; diff --git a/src/script/components/MessagesList/Message/ContentMessage/asset/index.tsx b/src/script/components/MessagesList/Message/ContentMessage/asset/index.tsx index 6698717be89..8237f1ebfd0 100644 --- a/src/script/components/MessagesList/Message/ContentMessage/asset/index.tsx +++ b/src/script/components/MessagesList/Message/ContentMessage/asset/index.tsx @@ -54,7 +54,6 @@ interface ContentAssetProps { selfId: QualifiedId; isMessageFocused: boolean; is1to1Conversation: boolean; - isLastDeliveredMessage: boolean; onClickDetails: () => void; } @@ -67,7 +66,6 @@ const ContentAsset = ({ onClickButton, isMessageFocused, is1to1Conversation, - isLastDeliveredMessage, onClickDetails, }: ContentAssetProps) => { const {isObfuscated, status} = useKoSubscribableChildren(message, ['isObfuscated', 'status']); @@ -96,12 +94,7 @@ const ContentAsset = ({ )} {shouldRenderText && ( - + )} {previews.map(() => ( @@ -109,12 +102,7 @@ const ContentAsset = ({ {!shouldRenderText && ( - + )} ))} diff --git a/src/script/components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator.styles.ts b/src/script/components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator.styles.ts new file mode 100644 index 00000000000..4a071621256 --- /dev/null +++ b/src/script/components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator.styles.ts @@ -0,0 +1,42 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {CSSObject} from '@emotion/react'; + +export const DeliveryIndicatorContainerStyles = (height?: number): CSSObject => ({ + display: 'flex', + boxSizing: 'border-box', + justifyContent: 'center', + alignItems: 'center', + position: 'absolute', + top: 0, + right: 0, + overflow: 'unset', + padding: '0 24px 0 16px', + height: height ? `${height}px` : 'min-content', +}); + +export const DeliveredIndicatorStyles = (isLastDeliveredMessage: boolean): CSSObject => ({ + color: 'var(--content-message-timestamp)', + fontSize: 'var(--font-size-small)', + fontWeight: 'var(--font-weight-regular)', + wordWrap: 'normal', + width: 'min-content', + visibility: isLastDeliveredMessage ? 'unset' : 'hidden', +}); diff --git a/src/script/components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator.tsx b/src/script/components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator.tsx new file mode 100644 index 00000000000..4e3bf5494dd --- /dev/null +++ b/src/script/components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator.tsx @@ -0,0 +1,50 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {forwardRef, ForwardRefRenderFunction} from 'react'; + +import { + DeliveredIndicatorStyles, + DeliveryIndicatorContainerStyles, +} from 'Components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator.styles'; +import {t} from 'Util/LocalizerUtil'; + +export interface DeliveredIndicatorProps { + height?: number; + isLastDeliveredMessage: boolean; +} + +const DeliveredIndicatorComponent: ForwardRefRenderFunction = ( + {height, isLastDeliveredMessage}, + ref, +) => { + return ( +
+ + {t('conversationMessageDelivered')} + +
+ ); +}; + +export const DeliveredIndicator = forwardRef(DeliveredIndicatorComponent); +DeliveredIndicator.displayName = 'DeliveredIndicator'; diff --git a/src/script/components/MessagesList/Message/PingMessage.tsx b/src/script/components/MessagesList/Message/PingMessage.tsx index e653620ca3d..da23f40c126 100644 --- a/src/script/components/MessagesList/Message/PingMessage.tsx +++ b/src/script/components/MessagesList/Message/PingMessage.tsx @@ -19,6 +19,7 @@ import cx from 'classnames'; +import {DeliveredIndicator} from 'Components/MessagesList/Message/DeliveredIndicator/DeliveredIndicator'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {ReadReceiptStatus} from './ReadReceiptStatus'; @@ -38,29 +39,28 @@ const PingMessage = ({message, is1to1Conversation, isLastDeliveredMessage}: Ping ); return ( -
-
-
-
-
-

- {unsafeSenderName} - {caption} -

+ <> +
+
+
+
+
+

+ {unsafeSenderName} + {caption} +

- + +
-
+ + ); }; diff --git a/src/script/components/MessagesList/Message/ReadIndicator/ReadIndicator.tsx b/src/script/components/MessagesList/Message/ReadIndicator/ReadIndicator.tsx index 9377b3972dd..4f8b6f96c36 100644 --- a/src/script/components/MessagesList/Message/ReadIndicator/ReadIndicator.tsx +++ b/src/script/components/MessagesList/Message/ReadIndicator/ReadIndicator.tsx @@ -19,7 +19,6 @@ import {Icon} from 'Components/Icon'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; -import {t} from 'Util/LocalizerUtil'; import {formatTimeShort} from 'Util/TimeUtil'; import {ReadIndicatorContainer, ReadIndicatorStyles, ReadReceiptText} from './ReadIndicator.styles'; @@ -29,7 +28,6 @@ import {Message} from '../../../../entity/message/Message'; interface ReadIndicatorProps { message: Message; is1to1Conversation?: boolean; - isLastDeliveredMessage?: boolean; showIconOnly?: boolean; onClick?: (message: Message) => void; } @@ -37,7 +35,6 @@ interface ReadIndicatorProps { export const ReadIndicator = ({ message, is1to1Conversation = false, - isLastDeliveredMessage = false, showIconOnly = false, onClick, }: ReadIndicatorProps) => { @@ -45,15 +42,10 @@ export const ReadIndicator = ({ if (is1to1Conversation) { const readReceiptText = readReceipts.length ? formatTimeShort(readReceipts[0].time) : ''; - const showDeliveredMessage = isLastDeliveredMessage && readReceiptText === ''; return (
- {showDeliveredMessage && ( - {t('conversationMessageDelivered')} - )} - {showIconOnly && readReceiptText && } {!showIconOnly && !!readReceiptText && ( diff --git a/src/script/components/MessagesList/Message/ReadReceiptStatus.tsx b/src/script/components/MessagesList/Message/ReadReceiptStatus.tsx index a060e4c3078..19ac189d0b8 100644 --- a/src/script/components/MessagesList/Message/ReadReceiptStatus.tsx +++ b/src/script/components/MessagesList/Message/ReadReceiptStatus.tsx @@ -29,17 +29,11 @@ import {formatTimeShort} from 'Util/TimeUtil'; export interface ReadReceiptStatusProps { is1to1Conversation: boolean; - isLastDeliveredMessage: boolean; message: Message; onClickDetails?: (message: Message) => void; } -export const ReadReceiptStatus = ({ - message, - is1to1Conversation, - isLastDeliveredMessage, - onClickDetails, -}: ReadReceiptStatusProps) => { +export const ReadReceiptStatus = ({message, is1to1Conversation, onClickDetails}: ReadReceiptStatusProps) => { const [readReceiptText, setReadReceiptText] = useState(''); const {readReceipts} = useKoSubscribableChildren(message, ['readReceipts']); @@ -50,17 +44,10 @@ export const ReadReceiptStatus = ({ } }, [is1to1Conversation, readReceipts]); - const showDeliveredMessage = isLastDeliveredMessage && readReceiptText === ''; const showEyeIndicator = !!readReceiptText; return ( <> - {showDeliveredMessage && ( - - {t('conversationMessageDelivered')} - - )} - {showEyeIndicator && (