diff --git a/ts/components/conversation/message/message-content/MessageContextMenu.tsx b/ts/components/conversation/message/message-content/MessageContextMenu.tsx index 044184561..26a27c7bd 100644 --- a/ts/components/conversation/message/message-content/MessageContextMenu.tsx +++ b/ts/components/conversation/message/message-content/MessageContextMenu.tsx @@ -203,7 +203,6 @@ export const MessageContextMenu = (props: Props) => { const attachments = useMessageAttachments(messageId); const timestamp = useMessageTimestamp(messageId); const serverTimestamp = useMessageServerTimestamp(messageId); - const sender = useMessageSender(messageId); const isOutgoing = direction === 'outgoing'; @@ -368,7 +367,11 @@ export const MessageContextMenu = (props: Props) => { {enableReactions && ( // eslint-disable-next-line @typescript-eslint/no-misused-promises - + )} {attachments?.length ? ( {window.i18n('downloadAttachment')} diff --git a/ts/components/conversation/message/message-content/MessageReactBar.tsx b/ts/components/conversation/message/message-content/MessageReactBar.tsx index 34068ef0a..9052ddf14 100644 --- a/ts/components/conversation/message/message-content/MessageReactBar.tsx +++ b/ts/components/conversation/message/message-content/MessageReactBar.tsx @@ -1,16 +1,18 @@ -import { isEqual } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import useMount from 'react-use/lib/useMount'; +import React, { ReactElement } from 'react'; import styled from 'styled-components'; -import { RecentReactions } from '../../../../types/Reaction'; +import { isEmpty } from 'lodash'; +import moment from 'moment'; +import { useMessageExpirationPropsById } from '../../../../hooks/useParamSelector'; import { nativeEmojiData } from '../../../../util/emoji'; import { getRecentReactions } from '../../../../util/storage'; -import { SessionIconButton } from '../../../icon'; +import { SpacerSM } from '../../../basic/Text'; +import { SessionIcon, SessionIconButton } from '../../../icon'; type Props = { action: (...args: Array) => void; additionalAction: (...args: Array) => void; + messageId: string; }; const StyledMessageReactBar = styled.div` @@ -18,8 +20,6 @@ const StyledMessageReactBar = styled.div` border-radius: 25px; box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.2), 0 0px 20px 0 rgba(0, 0, 0, 0.19); - position: absolute; - top: -56px; padding: 4px 8px; white-space: nowrap; width: 302px; @@ -51,45 +51,133 @@ const ReactButton = styled.span` } `; -export const MessageReactBar = (props: Props): ReactElement => { - const { action, additionalAction } = props; - const [recentReactions, setRecentReactions] = useState(); +const StyledContainer = styled.div<{ expirationTimestamp: number | null }>` + position: absolute; + top: ${props => (props.expirationTimestamp ? '-106px' : '-56px')}; + display: flex; + flex-direction: column; + min-width: 0; + align-items: flex-start; + left: -1px; +`; - useMount(() => { - const reactions = new RecentReactions(getRecentReactions()); - if (reactions && !isEqual(reactions, recentReactions)) { - setRecentReactions(reactions); - } - }); +const StyledExpiresIn = styled.div` + border-radius: 8px; + padding: 10px; + white-space: nowrap; + color: var(--text-primary-color); + size: var(--font-size-sm); + background-color: var(--context-menu-background-color); + box-shadow: 0px 0px 9px 0px var(--context-menu-shadow-color); + margin-top: 7px; + display: flex; + align-items: center; + min-width: 0; +`; + +function useIsRenderedExpiresInItem(messageId: string) { + const expiryDetails = useMessageExpirationPropsById(messageId); + + if ( + !expiryDetails || + isEmpty(expiryDetails) || + !expiryDetails.expirationDurationMs || + expiryDetails.isExpired || + !expiryDetails.expirationTimestamp + ) { + return null; + } + + return expiryDetails.expirationTimestamp; +} + +function formatExpiry({ expirationTimestamp }: { expirationTimestamp: number }) { + const diffMs = expirationTimestamp - Date.now(); + const diff = moment(diffMs).utc(); + + if (diffMs <= 0) { + return `0s`; + } + + const prefix = 'Message will expire in'; - if (!recentReactions) { - return <>; + if (diff.isBefore(moment.utc(0).add(1, 'minute'))) { + return `${prefix} ${diff.seconds()}s`; } + if (diff.isBefore(moment.utc(0).add(1, 'hour'))) { + const extraUnit = diff.seconds() ? ` ${diff.seconds()}s` : ''; + return `${prefix} ${diff.minutes()}m${extraUnit}`; + } + + if (diff.isBefore(moment.utc(0).add(1, 'day'))) { + const extraUnit = diff.minutes() ? ` ${diff.minutes()}m` : ''; + return `${prefix} ${diff.hours()}h${extraUnit}`; + } + + if (diff.isBefore(moment.utc(0).add(7, 'day'))) { + const extraUnit = diff.hours() ? ` ${diff.hours()}h` : ''; + return `${prefix} ${diff.dayOfYear() - 1}d${extraUnit}`; + } + + if (diff.isBefore(moment.utc(0).add(31, 'day'))) { + const days = diff.dayOfYear() - 1; + const weeks = Math.floor(days / 7); + const daysLeft = days % 7; + const extraUnit = daysLeft ? ` ${daysLeft}d` : ''; + return `${prefix} ${weeks}w${extraUnit}`; + } + + return '...'; +} + +const ExpiresInItem = ({ expirationTimestamp }: { expirationTimestamp?: number | null }) => { + if (!expirationTimestamp) { + return null; + } + + return ( + + + + {formatExpiry({ expirationTimestamp })} + + ); +}; + +export const MessageReactBar = ({ action, additionalAction, messageId }: Props): ReactElement => { + const recentReactions = getRecentReactions(); + const expirationTimestamp = useIsRenderedExpiresInItem(messageId); + return ( - - {recentReactions && - recentReactions.items.map(emoji => ( - { - action(emoji); - }} - > - {emoji} - - ))} - - + + + {recentReactions && + recentReactions.map(emoji => ( + { + action(emoji); + }} + > + {emoji} + + ))} + + + + ); }; diff --git a/ts/session/constants.ts b/ts/session/constants.ts index 3d0c05094..7a55b33f5 100644 --- a/ts/session/constants.ts +++ b/ts/session/constants.ts @@ -13,6 +13,8 @@ export const DURATION = { HOURS: hours, /** 24 * 60 * 60 * 1000 = 86,400,000 ms */ DAYS: days, + /** 7 * 24 * 60 * 60 * 1000 = 604,800,000 ms */ + WEEKS: days * 7, }; export const TTL_DEFAULT = {