feat: updated MessageContent with nested components

pull/3017/head
William Grant 2 years ago
parent 22b0ab5f2f
commit 3bf3bf73b1

@ -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}
/>
<StyledMessageWithAuthor isIncoming={isIncoming}>
<MessageAuthorText messageId={messageId} />
{!isDetailView && <MessageAuthorText messageId={messageId} />}
<MessageContent messageId={messageId} isDetailView={isDetailView} />
</StyledMessageWithAuthor>
<MessageStatus
dataTestId="msg-status-outgoing"
messageId={messageId}
isCorrectSide={!isIncoming}
isDetailView={isDetailView}
/>
{!isDeleted && (
<MessageContextMenu
@ -165,6 +174,7 @@ export const MessageContentWithStatuses = (props: Props) => {
setPopupReaction={setPopupReaction}
onPopupClick={handlePopupClick}
noAvatar={hideAvatar}
isDetailView={isDetailView}
/>
)}
</StyledMessageContentContainer>

@ -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<SortedReactionList>([]);
@ -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,
};

@ -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<MessageRenderingProps, 'direction' | 'status'>;
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 <OutgoingMessageStatus dataTestId={dataTestId} status={status} />;
return <OutgoingMessageStatus messageId={messageId} dataTestId={dataTestId} status={status} />;
};

@ -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 (
<MessageStatusSendingContainer
data-testid={dataTestId}
data-testtype="failed"
onClick={showDebugLog}
onClick={() => {
if (messageId) {
void showMessageInfoOverlay({ messageId, dispatch });
}
}}
title={window.i18n('sendFailed')}
>
<SessionIcon iconColor={'var(--danger-color'} iconType="error" iconSize="tiny" />
@ -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 <MessageStatusSending dataTestId={dataTestId} />;
@ -68,7 +78,7 @@ export const OutgoingMessageStatus = (props: {
case 'read':
return <MessageStatusRead dataTestId={dataTestId} />;
case 'error':
return <MessageStatusError dataTestId={dataTestId} />;
return <MessageStatusError messageId={messageId} dataTestId={dataTestId} />;
default:
return null;
}

@ -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 (
<StyledReadableMessage
selected={selected}
isDetailView={isDetailView}
isRightClicked={isRightClicked}
className={classNames(selected && 'message-selected')}
onContextMenu={handleContextMenu}

@ -1,16 +1,24 @@
import React, { ReactElement, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useMouse } from 'react-use';
import styled from 'styled-components';
import { isUsAnySogsFromCache } from '../../../../session/apis/open_group_api/sogsv3/knownBlindedkeys';
import { UserUtils } from '../../../../session/utils';
import { getRightOverlayMode } from '../../../../state/selectors/section';
import { useIsMessageSelectionMode } from '../../../../state/selectors/selectedConversation';
import { THEME_GLOBALS } from '../../../../themes/globals';
import { SortedReactionList } from '../../../../types/Reaction';
import { abbreviateNumber } from '../../../../util/abbreviateNumber';
import { nativeEmojiData } from '../../../../util/emoji';
import { popupXDefault, popupYDefault } from '../message-content/MessageReactions';
import { POPUP_WIDTH, ReactionPopup, TipPosition } from './ReactionPopup';
const StyledReaction = styled.button<{ selected: boolean; inModal: boolean; showCount: boolean }>`
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<HTMLDivElement>(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

@ -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);

@ -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).

Loading…
Cancel
Save