diff --git a/ts/components/conversation/message/message-content/MessageContentWithStatus.tsx b/ts/components/conversation/message/message-content/MessageContentWithStatus.tsx index 8876b4b93..1406516a6 100644 --- a/ts/components/conversation/message/message-content/MessageContentWithStatus.tsx +++ b/ts/components/conversation/message/message-content/MessageContentWithStatus.tsx @@ -95,25 +95,32 @@ export const MessageContentWithStatuses = (props: Props) => { } }; - const { messageId, ctxMenuID, isDetailView, dataTestId, enableReactions } = props; + const { messageId, ctxMenuID, isDetailView = false, dataTestId, enableReactions } = props; const [popupReaction, setPopupReaction] = useState(''); if (!contentProps) { return null; } - const { conversationType, direction, isDeleted, isGroup } = contentProps; + const { conversationType, direction: _direction, isDeleted, isGroup } = contentProps; + // NOTE we want messages on the left in the message detail view regardless of direction + const direction = isDetailView ? 'incoming' : _direction; const isIncoming = direction === 'incoming'; const isPrivate = conversationType === 'private'; - const hideAvatar = isPrivate || direction === 'outgoing'; + const hideAvatar = isPrivate || direction === 'outgoing' || isDetailView; const handleMessageReaction = async (emoji: string) => { await Reactions.sendMessageReaction(messageId, emoji); }; const handlePopupClick = () => { - dispatch(updateReactListModal({ reaction: popupReaction, messageId })); + dispatch( + updateReactListModal({ + reaction: popupReaction, + messageId, + }) + ); }; return ( @@ -138,15 +145,17 @@ export const MessageContentWithStatuses = (props: Props) => { dataTestId="msg-status-incoming" messageId={messageId} isCorrectSide={isIncoming} + isDetailView={isDetailView} /> - + {!isDetailView && } {!isDeleted && ( { setPopupReaction={setPopupReaction} onPopupClick={handlePopupClick} noAvatar={hideAvatar} + isDetailView={isDetailView} /> )} diff --git a/ts/components/conversation/message/message-content/MessageReactions.tsx b/ts/components/conversation/message/message-content/MessageReactions.tsx index b7567d70e..223c55188 100644 --- a/ts/components/conversation/message/message-content/MessageReactions.tsx +++ b/ts/components/conversation/message/message-content/MessageReactions.tsx @@ -3,6 +3,7 @@ import React, { ReactElement, useEffect, useState } from 'react'; import styled from 'styled-components'; import { useMessageReactsPropsById } from '../../../../hooks/useParamSelector'; import { MessageRenderingProps } from '../../../../models/messageType'; +import { REACT_LIMIT } from '../../../../session/constants'; import { useSelectedIsGroup } from '../../../../state/selectors/selectedConversation'; import { SortedReactionList } from '../../../../types/Reaction'; import { nativeEmojiData } from '../../../../util/emoji'; @@ -10,7 +11,6 @@ import { Flex } from '../../../basic/Flex'; import { SessionIcon } from '../../../icon'; import { Reaction, ReactionProps } from '../reactions/Reaction'; import { StyledPopupContainer } from '../reactions/ReactionPopup'; -import { REACT_LIMIT } from '../../../../session/constants'; export const popupXDefault = -81; export const popupYDefault = -90; @@ -147,6 +147,7 @@ type Props = { inModal?: boolean; onSelected?: (emoji: string) => boolean; noAvatar: boolean; + isDetailView?: boolean; }; export const MessageReactions = (props: Props) => { @@ -160,6 +161,7 @@ export const MessageReactions = (props: Props) => { inModal = false, onSelected, noAvatar, + isDetailView, } = props; const [reactions, setReactions] = useState([]); @@ -202,10 +204,10 @@ export const MessageReactions = (props: Props) => { inGroup, handlePopupX: setPopupX, handlePopupY: setPopupY, - onClick, + onClick: !isDetailView ? onClick : undefined, popupReaction, onSelected, - handlePopupReaction: setPopupReaction, + handlePopupReaction: !isDetailView ? setPopupReaction : undefined, handlePopupClick: onPopupClick, }; diff --git a/ts/components/conversation/message/message-content/MessageStatus.tsx b/ts/components/conversation/message/message-content/MessageStatus.tsx index 9ff95f502..27dc7d86d 100644 --- a/ts/components/conversation/message/message-content/MessageStatus.tsx +++ b/ts/components/conversation/message/message-content/MessageStatus.tsx @@ -1,10 +1,11 @@ import React from 'react'; import { MessageRenderingProps } from '../../../../models/messageType'; -import { OutgoingMessageStatus } from './OutgoingMessageStatus'; import { useMessageDirection, useMessageStatus } from '../../../../state/selectors'; +import { OutgoingMessageStatus } from './OutgoingMessageStatus'; type Props = { isCorrectSide: boolean; + isDetailView: boolean; messageId: string; dataTestId?: string; }; @@ -12,15 +13,15 @@ type Props = { export type MessageStatusSelectorProps = Pick; export const MessageStatus = (props: Props) => { - const { isCorrectSide, dataTestId } = props; + const { messageId, isCorrectSide, isDetailView, dataTestId } = props; const direction = useMessageDirection(props.messageId); const status = useMessageStatus(props.messageId); - if (!props.messageId) { + if (!messageId) { return null; } - if (!isCorrectSide) { + if (!isCorrectSide || !isDetailView) { return null; } const isIncoming = direction === 'incoming'; @@ -30,5 +31,5 @@ export const MessageStatus = (props: Props) => { return null; } - return ; + return ; }; diff --git a/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx b/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx index c71d4097e..da03566c6 100644 --- a/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx +++ b/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx @@ -1,8 +1,9 @@ -import { ipcRenderer } from 'electron'; import React from 'react'; +import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import { LastMessageStatusType } from '../../../../state/ducks/conversations'; import { SessionIcon } from '../../../icon'; +import { showMessageInfoOverlay } from './MessageContextMenu'; const MessageStatusSendingContainer = styled.div` display: inline-block; @@ -38,16 +39,24 @@ const MessageStatusRead = ({ dataTestId }: { dataTestId?: string }) => { ); }; -const MessageStatusError = ({ dataTestId }: { dataTestId?: string }) => { - const showDebugLog = () => { - ipcRenderer.send('show-debug-log'); - }; +const MessageStatusError = ({ + messageId, + dataTestId, +}: { + messageId?: string; + dataTestId?: string; +}) => { + const dispatch = useDispatch(); return ( { + if (messageId) { + void showMessageInfoOverlay({ messageId, dispatch }); + } + }} title={window.i18n('sendFailed')} > @@ -57,9 +66,10 @@ const MessageStatusError = ({ dataTestId }: { dataTestId?: string }) => { export const OutgoingMessageStatus = (props: { status: LastMessageStatusType | null; + messageId?: string; dataTestId?: string; }) => { - const { status, dataTestId } = props; + const { status, messageId, dataTestId } = props; switch (status) { case 'sending': return ; @@ -68,7 +78,7 @@ export const OutgoingMessageStatus = (props: { case 'read': return ; case 'error': - return ; + return ; default: return null; } diff --git a/ts/components/conversation/message/message-item/GenericReadableMessage.tsx b/ts/components/conversation/message/message-item/GenericReadableMessage.tsx index 847ea0c10..80cee4094 100644 --- a/ts/components/conversation/message/message-item/GenericReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/GenericReadableMessage.tsx @@ -41,11 +41,13 @@ const highlightedMessageAnimation = keyframes` const StyledReadableMessage = styled.div<{ selected: boolean; isRightClicked: boolean; + isDetailView?: boolean; }>` display: flex; align-items: center; width: 100%; letter-spacing: 0.03rem; + padding: ${props => (props.isDetailView ? '0' : 'var(--margins-xs) var(--margins-lg) 0')}; &.message-highlighted { animation: ${highlightedMessageAnimation} 1s ease-in-out; @@ -151,6 +153,7 @@ export const GenericReadableMessage = (props: Props) => { return ( ` +const StyledReaction = styled.button<{ + selected: boolean; + inModal: boolean; + showCount: boolean; + hasOnClick?: boolean; +}>` display: flex; justify-content: ${props => (props.showCount ? 'flex-start' : 'center')}; align-items: center; @@ -29,6 +37,8 @@ const StyledReaction = styled.button<{ selected: boolean; inModal: boolean; show span { width: 100%; } + + ${props => !props.hasOnClick && 'cursor: not-allowed;'} `; const StyledReactionContainer = styled.div<{ @@ -46,7 +56,7 @@ export type ReactionProps = { inGroup: boolean; handlePopupX: (x: number) => void; handlePopupY: (y: number) => void; - onClick: (emoji: string) => void; + onClick?: (emoji: string) => void; popupReaction?: string; onSelected?: (emoji: string) => boolean; handlePopupReaction?: (emoji: string) => void; @@ -69,6 +79,7 @@ export const Reaction = (props: ReactionProps): ReactElement => { handlePopupClick, } = props; + const rightOverlayMode = useSelector(getRightOverlayMode); const isMessageSelection = useIsMessageSelectionMode(); const reactionsMap = (reactions && Object.fromEntries(reactions)) || {}; const senders = reactionsMap[emoji]?.senders || []; @@ -76,7 +87,7 @@ export const Reaction = (props: ReactionProps): ReactElement => { const showCount = count !== undefined && (count > 1 || inGroup); const reactionRef = useRef(null); - const { docX, elW } = useMouse(reactionRef); + const { docX: _docX, elW } = useMouse(reactionRef); const gutterWidth = 380; // TODOLATER make this a variable which can be shared in CSS and JS const tooltipMidPoint = POPUP_WIDTH / 2; // px @@ -96,7 +107,9 @@ export const Reaction = (props: ReactionProps): ReactElement => { const handleReactionClick = () => { if (!isMessageSelection) { - onClick(emoji); + if (onClick) { + onClick(emoji); + } } }; @@ -107,12 +120,28 @@ export const Reaction = (props: ReactionProps): ReactElement => { selected={selected()} inModal={inModal} onClick={handleReactionClick} + hasOnClick={Boolean(onClick)} onMouseEnter={() => { if (inGroup && !isMessageSelection) { - const { innerWidth: windowWidth } = window; + const { innerWidth } = window; + let windowWidth = innerWidth; + + let docX = _docX; + // if the right panel is open we may need to show a reaction tooltip relative to it + if (rightOverlayMode && rightOverlayMode.type === 'message_info') { + const rightPanelWidth = Number(THEME_GLOBALS['--right-panel-width'].split('px')[0]); + + // we need to check that the reaction we are hovering over is inside of the right panel and not in the messages list + if (docX > windowWidth - rightPanelWidth) { + // make the values relative to the right panel + docX = docX - windowWidth + rightPanelWidth; + windowWidth = rightPanelWidth; + } + } + if (handlePopupReaction) { // overflow on far right means we shift left - if (docX + elW + tooltipMidPoint > windowWidth) { + if (docX + elW + tooltipMidPoint > innerWidth) { handlePopupX(Math.abs(popupXDefault) * 1.5 * -1); setTooltipPosition('right'); // overflow onto conversations means we lock to the right diff --git a/ts/state/selectors/section.ts b/ts/state/selectors/section.ts index 07cc2f042..f87d540e5 100644 --- a/ts/state/selectors/section.ts +++ b/ts/state/selectors/section.ts @@ -1,7 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { SessionSettingCategory } from '../../components/settings/SessionSettings'; -import { OverlayMode, SectionStateType, SectionType } from '../ducks/section'; +import { OverlayMode, RightOverlayMode, SectionStateType, SectionType } from '../ducks/section'; import { StateType } from '../reducer'; export const getSection = (state: StateType): SectionStateType => state.section; @@ -31,6 +31,10 @@ export const getOverlayMode = createSelector( (state: SectionStateType): OverlayMode | undefined => state.overlayMode ); +export const getRightOverlayMode = (state: StateType): RightOverlayMode | undefined => { + return state.section.rightOverlayMode; +}; + export const getIsMessageRequestOverlayShown = (state: StateType) => { const focusedSection = getFocusedSection(state); const overlayMode = getOverlayMode(state); diff --git a/ts/themes/globals.tsx b/ts/themes/globals.tsx index a5d1d1339..6727a8171 100644 --- a/ts/themes/globals.tsx +++ b/ts/themes/globals.tsx @@ -36,6 +36,7 @@ export type ThemeGlobals = { '--main-view-header-height': string; '--composition-container-height': string; '--search-input-height': string; + '--toggle-width': string; /* Animations */ '--default-duration': string; @@ -83,6 +84,12 @@ export type ThemeGlobals = { /* Also used for FileDropZone */ /* Used for Quote References Not Found */ '--message-link-preview-background-color': string; + + /* Right Panel */ + '--right-panel-width': string; + '--right-panel-height': string; + '--right-panel-attachment-width': string; + '--right-panel-attachment-height': string; }; // These are only set once in the global style (at root). @@ -115,6 +122,7 @@ export const THEME_GLOBALS: ThemeGlobals = { '--main-view-header-height': '68px', '--composition-container-height': '60px', '--search-input-height': '34px', + '--toggle-width': '51px', '--default-duration': '0.25s', @@ -149,6 +157,11 @@ export const THEME_GLOBALS: ThemeGlobals = { '--avatar-border-color': 'var(--transparent-color)', '--message-link-preview-background-color': `rgba(${hexColorToRGB(COLORS.BLACK)}, 0.06)`, + + '--right-panel-width': '420px', + '--right-panel-height': '100%', + '--right-panel-attachment-width': '350px', + '--right-panel-attachment-height': '350px', }; // These should only be needed for the global style (at root).