diff --git a/ts/components/conversation/message/message-item/MessageDetail.tsx b/ts/components/conversation/message/message-item/MessageDetail.tsx
deleted file mode 100644
index 24653ca38..000000000
--- a/ts/components/conversation/message/message-item/MessageDetail.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import classNames from 'classnames';
-import moment from 'moment';
-import React from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import useKey from 'react-use/lib/useKey';
-
-import { Message } from './Message';
-
-import { deleteMessagesById } from '../../../../interactions/conversations/unsendingInteractions';
-import {
- ContactPropsMessageDetail,
- closeMessageDetailsView,
-} from '../../../../state/ducks/conversations';
-import { getMessageDetailsViewProps } from '../../../../state/selectors/conversations';
-import { Avatar, AvatarSize } from '../../../avatar/Avatar';
-import { ContactName } from '../../ContactName';
-
-import { useMessageIsDeletable } from '../../../../state/selectors';
-import { SessionButton, SessionButtonColor, SessionButtonType } from '../../../basic/SessionButton';
-
-const AvatarItem = (props: { pubkey: string }) => {
- const { pubkey } = props;
-
- return
;
-};
-
-const DeleteButtonItem = (props: { messageId: string; convoId: string; isDeletable: boolean }) => {
- const { i18n } = window;
-
- return props.isDeletable ? (
-
- {
- await deleteMessagesById([props.messageId], props.convoId);
- }}
- />
-
- ) : null;
-};
-
-const ContactsItem = (props: { contacts: Array
}) => {
- const { contacts } = props;
-
- if (!contacts || !contacts.length) {
- return null;
- }
-
- return (
-
- {contacts.map(contact => (
-
- ))}
-
- );
-};
-
-const ContactItem = (props: { contact: ContactPropsMessageDetail }) => {
- const { contact } = props;
- const errors = contact.errors || [];
-
- const statusComponent = (
-
- );
-
- return (
-
-
-
-
-
-
- {errors.map((error, index) => (
-
- {error.message}
-
- ))}
-
- {statusComponent}
-
- );
-};
-
-export const MessageDetail = () => {
- const { i18n } = window;
-
- const messageDetailProps = useSelector(getMessageDetailsViewProps);
- const isDeletable = useMessageIsDeletable(messageDetailProps?.messageId);
-
- const dispatch = useDispatch();
-
- useKey('Escape', () => {
- dispatch(closeMessageDetailsView());
- });
-
- if (!messageDetailProps) {
- return null;
- }
-
- const { errors, receivedAt, sentAt, convoId, direction, messageId } = messageDetailProps;
-
- return (
-
-
-
-
-
-
-
- {(errors || []).map((error, index) => (
-
- {i18n('error')} |
-
- {' '}
- {error.message}{' '}
- |
-
- ))}
-
- {i18n('sent')} |
-
- {moment(sentAt).format('LLLL')} ({sentAt})
- |
-
- {receivedAt ? (
-
- {i18n('received')} |
-
- {moment(receivedAt).format('LLLL')} ({receivedAt})
- |
-
- ) : null}
-
-
- {direction === 'incoming' ? i18n('from') : i18n('to')}
- |
-
-
-
-
-
-
-
- );
-};
diff --git a/ts/components/conversation/right-panel/overlay/OverlayRightPanelSettings.tsx b/ts/components/conversation/right-panel/overlay/OverlayRightPanelSettings.tsx
index 8f6d5cdf6..7d15923a6 100644
--- a/ts/components/conversation/right-panel/overlay/OverlayRightPanelSettings.tsx
+++ b/ts/components/conversation/right-panel/overlay/OverlayRightPanelSettings.tsx
@@ -19,7 +19,7 @@ import {
} from '../../../../interactions/conversationInteractions';
import { Constants } from '../../../../session';
import { closeRightPanel } from '../../../../state/ducks/conversations';
-import { setRightOverlayMode } from '../../../../state/ducks/section';
+import { resetRightOverlayMode, setRightOverlayMode } from '../../../../state/ducks/section';
import {
useSelectedConversationKey,
useSelectedDisplayNameInProfile,
@@ -35,11 +35,13 @@ import {
import { AttachmentTypeWithPath } from '../../../../types/Attachment';
import { getAbsoluteAttachmentPath } from '../../../../types/MessageAttachment';
import { Avatar, AvatarSize } from '../../../avatar/Avatar';
+import { Flex } from '../../../basic/Flex';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../../../basic/SessionButton';
-import { SpacerLG } from '../../../basic/Text';
+import { SpacerMD } from '../../../basic/Text';
import { PanelButtonGroup, PanelIconButton } from '../../../buttons';
import { MediaItemType } from '../../../lightbox/LightboxGallery';
import { MediaGallery } from '../../media-gallery/MediaGallery';
+import { Header } from './components';
async function getMediaGalleryProps(
conversationId: string
@@ -116,44 +118,62 @@ async function getMediaGalleryProps(
const HeaderItem = () => {
const selectedConvoKey = useSelectedConversationKey();
+ const displayNameInProfile = useSelectedDisplayNameInProfile();
const dispatch = useDispatch();
const isBlocked = useSelectedIsBlocked();
const isKickedFromGroup = useSelectedIsKickedFromGroup();
const left = useSelectedIsLeft();
const isGroup = useSelectedIsGroup();
+ const subscriberCount = useSelectedSubscriberCount();
if (!selectedConvoKey) {
return null;
}
const showInviteContacts = isGroup && !isKickedFromGroup && !isBlocked && !left;
+ const showMemberCount = !!(subscriberCount && subscriberCount > 0);
return (
-
-
{
- dispatch(closeRightPanel());
- }}
- style={{ position: 'absolute' }}
- dataTestId="back-button-conversation-options"
- />
-
- {showInviteContacts && (
- {
- if (selectedConvoKey) {
- showInviteContactByConvoId(selectedConvoKey);
- }
- }}
- dataTestId="add-user-button"
- />
+
+
);
};
@@ -176,26 +196,6 @@ const StyledLeaveButton = styled.div`
}
`;
-const StyledGroupSettingsItem = styled.div`
- display: flex;
- align-items: center;
- min-height: 3rem;
- font-size: var(--font-size-md);
- color: var(--right-panel-item-text-color);
- background-color: var(--right-panel-item-background-color);
- border-top: 1px solid var(--border-color);
- border-bottom: 1px solid var(--border-color);
-
- width: -webkit-fill-available;
- padding: 0 var(--margins-md);
- transition: var(--default-duration);
- cursor: pointer;
-
- &:hover {
- background-color: var(--right-panel-item-background-hover-color);
- }
-`;
-
const StyledName = styled.h4`
padding-inline: var(--margins-md);
font-size: var(--font-size-md);
@@ -208,10 +208,8 @@ export const OverlayRightPanelSettings = () => {
const selectedConvoKey = useSelectedConversationKey();
const dispatch = useDispatch();
const isShowing = useIsRightPanelShowing();
- const subscriberCount = useSelectedSubscriberCount();
const isActive = useSelectedIsActive();
- const displayNameInProfile = useSelectedDisplayNameInProfile();
const isBlocked = useSelectedIsBlocked();
const isKickedFromGroup = useSelectedIsKickedFromGroup();
const left = useSelectedIsLeft();
@@ -265,7 +263,6 @@ export const OverlayRightPanelSettings = () => {
return null;
}
- const showMemberCount = !!(subscriberCount && subscriberCount > 0);
const commonNoShow = isKickedFromGroup || left || isBlocked || !isActive;
const hasDisappearingMessages = !isPublic && !commonNoShow;
const leaveGroupString = isPublic
@@ -292,65 +289,48 @@ export const OverlayRightPanelSettings = () => {
return (
<>
- {displayNameInProfile}
- {showMemberCount && (
- <>
-
-
- {window.i18n('members', [`${subscriberCount}`])}
-
-
- >
- )}
- {showUpdateGroupNameButton && (
- {
- void showUpdateGroupNameByConvoId(selectedConvoKey);
- }}
- >
- {isPublic ? window.i18n('editGroup') : window.i18n('editGroupName')}
-
- )}
- {showAddRemoveModeratorsButton && (
- <>
-
+ {showUpdateGroupNameButton && (
+ {
- showAddModeratorsByConvoId(selectedConvoKey);
+ void showUpdateGroupNameByConvoId(selectedConvoKey);
}}
- >
- {window.i18n('addModerators')}
-
-
+ )}
+
+ {showAddRemoveModeratorsButton && (
+ <>
+ {
+ showAddModeratorsByConvoId(selectedConvoKey);
+ }}
+ />
+
+ {
+ showRemoveModeratorsByConvoId(selectedConvoKey);
+ }}
+ />
+ >
+ )}
+
+ {showUpdateGroupMembersButton && (
+ {
- showRemoveModeratorsByConvoId(selectedConvoKey);
+ void showUpdateGroupMembersByConvoId(selectedConvoKey);
}}
- >
- {window.i18n('removeModerators')}
-
- >
- )}
-
- {showUpdateGroupMembersButton && (
- {
- void showUpdateGroupMembersByConvoId(selectedConvoKey);
- }}
- >
- {window.i18n('groupMembers')}
-
- )}
+ />
+ )}
- {hasDisappearingMessages && (
- /* TODO Move ButtonGroup around all settings items */
-
+ {hasDisappearingMessages && (
{
dispatch(setRightOverlayMode({ type: 'disappearing_messages', params: null }));
}}
/>
-
- )}
-
-
- {isGroup && (
-
-
-
- )}
+ )}
+
+
+ {isGroup && (
+
+
+
+ )}
+
>
);
};
diff --git a/ts/components/conversation/right-panel/overlay/components/Containers.tsx b/ts/components/conversation/right-panel/overlay/components/Containers.tsx
new file mode 100644
index 000000000..7489477f1
--- /dev/null
+++ b/ts/components/conversation/right-panel/overlay/components/Containers.tsx
@@ -0,0 +1,7 @@
+import styled from 'styled-components';
+
+export const StyledScrollContainer = styled.div`
+ width: 100%;
+ height: 100%;
+ overflow: hidden auto;
+`;
diff --git a/ts/components/conversation/right-panel/overlay/components/Header.tsx b/ts/components/conversation/right-panel/overlay/components/Header.tsx
new file mode 100644
index 000000000..b2a84712f
--- /dev/null
+++ b/ts/components/conversation/right-panel/overlay/components/Header.tsx
@@ -0,0 +1,89 @@
+import React, { ReactNode } from 'react';
+import { useDispatch } from 'react-redux';
+import styled from 'styled-components';
+import { closeRightPanel } from '../../../../../state/ducks/conversations';
+import { resetRightOverlayMode } from '../../../../../state/ducks/section';
+import { Flex } from '../../../../basic/Flex';
+import { SessionIconButton } from '../../../../icon';
+
+export const HeaderTitle = styled.h2`
+ font-family: var(--font-default);
+ font-size: var(--font-size-h2);
+ text-align: center;
+ margin-top: 0px;
+ margin-bottom: 0px;
+`;
+
+export const HeaderSubtitle = styled.h3`
+ font-family: var(--font-default);
+ font-size: 11px;
+ font-weight: 400;
+ text-align: center;
+ padding-top: 0px;
+ margin-top: 0;
+`;
+
+type HeaderProps = {
+ hideBackButton?: boolean;
+ backButtonDirection?: 'left' | 'right';
+ backButtonOnClick?: () => void;
+ hideCloseButton?: boolean;
+ closeButtonOnClick?: () => void;
+ children?: ReactNode;
+};
+
+export const Header = (props: HeaderProps) => {
+ const {
+ children,
+ hideBackButton = false,
+ backButtonDirection = 'left',
+ backButtonOnClick,
+ hideCloseButton = false,
+ closeButtonOnClick,
+ } = props;
+ const dispatch = useDispatch();
+
+ return (
+
+ {!hideBackButton && (
+ {
+ if (backButtonOnClick) {
+ backButtonOnClick();
+ } else {
+ dispatch(resetRightOverlayMode());
+ }
+ }}
+ dataTestId="back-button-conversation-options"
+ />
+ )}
+
+ {children}
+
+ {!hideCloseButton && (
+ {
+ if (closeButtonOnClick) {
+ closeButtonOnClick();
+ } else {
+ dispatch(closeRightPanel());
+ dispatch(resetRightOverlayMode());
+ }
+ }}
+ />
+ )}
+
+ );
+};
diff --git a/ts/components/conversation/right-panel/overlay/components/index.tsx b/ts/components/conversation/right-panel/overlay/components/index.tsx
new file mode 100644
index 000000000..970fd27ed
--- /dev/null
+++ b/ts/components/conversation/right-panel/overlay/components/index.tsx
@@ -0,0 +1,4 @@
+import { StyledScrollContainer } from './Containers';
+import { Header, HeaderSubtitle, HeaderTitle } from './Header';
+
+export { Header, HeaderSubtitle, HeaderTitle, StyledScrollContainer };
diff --git a/ts/components/conversation/right-panel/overlay/message-info/components/MessageInfo.tsx b/ts/components/conversation/right-panel/overlay/message-info/components/MessageInfo.tsx
new file mode 100644
index 000000000..f4688d6c1
--- /dev/null
+++ b/ts/components/conversation/right-panel/overlay/message-info/components/MessageInfo.tsx
@@ -0,0 +1,97 @@
+import { ipcRenderer } from 'electron';
+import { isEmpty } from 'lodash';
+import moment from 'moment';
+import React from 'react';
+import { useSelector } from 'react-redux';
+import styled from 'styled-components';
+import { MessageFrom } from '.';
+import { getMessageDetailsViewProps } from '../../../../../../state/selectors/conversations';
+import { Flex } from '../../../../../basic/Flex';
+import { SpacerSM } from '../../../../../basic/Text';
+
+export const MessageInfoLabel = styled.label<{ color?: string }>`
+ font-size: var(--font-size-lg);
+ font-weight: bold;
+ ${props => props.color && `color: ${props.color};`}
+`;
+
+const MessageInfoData = styled.div<{ color?: string }>`
+ font-size: var(--font-size-md);
+ user-select: text;
+ ${props => props.color && `color: ${props.color};`}
+`;
+
+const LabelWithInfoContainer = styled.div`
+ margin-bottom: var(--margins-md);
+ ${props => props.onClick && 'cursor: pointer;'}
+`;
+
+type LabelWithInfoProps = {
+ label: string;
+ info: string;
+ labelColor?: string;
+ dataColor?: string;
+ title?: string;
+ onClick?: () => void;
+};
+
+export const LabelWithInfo = (props: LabelWithInfoProps) => {
+ return (
+
+ {props.label}
+ {props.info}
+
+ );
+};
+
+// Message timestamp format: "06:02 PM Tue, 15/11/2022"
+const formatTimestamps = 'hh:mm A ddd, D/M/Y';
+
+const showDebugLog = () => {
+ ipcRenderer.send('show-debug-log');
+};
+
+export const MessageInfo = () => {
+ const messageDetailProps = useSelector(getMessageDetailsViewProps);
+
+ if (!messageDetailProps) {
+ return null;
+ }
+
+ const { errors, receivedAt, sentAt, direction, sender } = messageDetailProps;
+
+ const sentAtStr = `${moment(sentAt).format(formatTimestamps)}`;
+ const receivedAtStr = `${moment(receivedAt).format(formatTimestamps)}`;
+
+ const hasError = !isEmpty(errors);
+ const errorString = hasError
+ ? errors?.reduce((previous, current, currentIndex) => {
+ return `${previous}${current.message}${
+ errors.length > 1 && currentIndex < errors.length - 1 ? ', ' : ''
+ }`;
+ }, '')
+ : null;
+
+ return (
+
+
+ {direction === 'incoming' ? (
+
+ ) : null}
+
+
+ {hasError && (
+ <>
+
+
+ >
+ )}
+
+ );
+};
diff --git a/ts/components/icon/Icons.tsx b/ts/components/icon/Icons.tsx
index 7965c6bd7..86b2d917d 100644
--- a/ts/components/icon/Icons.tsx
+++ b/ts/components/icon/Icons.tsx
@@ -2,6 +2,7 @@
/* eslint-disable no-multi-str */
export type SessionIconType =
| 'addUser'
+ | 'addModerator'
| 'arrow'
| 'bell'
| 'brand'
@@ -22,6 +23,7 @@ export type SessionIconType =
| 'crown'
| 'communities'
| 'delete'
+ | 'deleteModerator'
| 'ellipses'
| 'emoji'
| 'error'
@@ -31,6 +33,7 @@ export type SessionIconType =
| 'fullscreen'
| 'gear'
| 'group'
+ | 'groupMembers'
| 'hangup'
| 'image'
| 'info'
@@ -52,6 +55,8 @@ export type SessionIconType =
| 'plusThin'
| 'plusFat'
| 'reply'
+ | 'resend'
+ | 'saveToDisk'
| 'save'
| 'send'
| 'search'
@@ -91,6 +96,12 @@ export const icons: Record;
+ sender: string;
contacts: Array;
convoId: string;
messageId: string;
direction: MessageModelType;
+ attachments: Array;
+ timestamp?: number;
+ serverTimestamp?: number;
};
export type LastMessageStatusType = 'sending' | 'sent' | 'read' | 'error' | undefined;
@@ -148,6 +152,7 @@ export type PropsForAttachment = {
size: number;
width?: number;
height?: number;
+ duration?: string;
url: string;
path: string;
fileSize: string | null;
diff --git a/ts/state/selectors/messages.ts b/ts/state/selectors/messages.ts
index c23e9c41f..9acf29166 100644
--- a/ts/state/selectors/messages.ts
+++ b/ts/state/selectors/messages.ts
@@ -4,6 +4,7 @@ import {
LastMessageStatusType,
MessageModelPropsWithConvoProps,
PropsForAttachment,
+ PropsForQuote,
ReduxConversationType,
} from '../ducks/conversations';
import { StateType } from '../reducer';
@@ -110,7 +111,7 @@ export function useMessageSender(messageId: string) {
return useMessagePropsByMessageId(messageId)?.propsForMessage.sender;
}
-export function useMessageIsDeletableForEveryone(messageId: string) {
+export function useMessageIsDeletableForEveryone(messageId: string | undefined) {
return useMessagePropsByMessageId(messageId)?.propsForMessage.isDeletableForEveryone;
}
@@ -125,3 +126,11 @@ export function useMessageTimestamp(messageId: string) {
export function useMessageBody(messageId: string) {
return useMessagePropsByMessageId(messageId)?.propsForMessage.text;
}
+
+export const useMessageQuote = (messageId: string | undefined): PropsForQuote | undefined => {
+ return useMessagePropsByMessageId(messageId)?.propsForMessage.quote;
+};
+
+export const useMessageText = (messageId: string | undefined): string | undefined => {
+ return useMessagePropsByMessageId(messageId)?.propsForMessage.text;
+};
diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts
index 89f6d81c9..ba389ddad 100644
--- a/ts/types/LocalizerKeys.ts
+++ b/ts/types/LocalizerKeys.ts
@@ -162,6 +162,7 @@ export type LocalizerKeys =
| 'documentsEmptyState'
| 'done'
| 'downloadAttachment'
+ | 'duration'
| 'editGroup'
| 'editGroupName'
| 'editMenuCopy'
@@ -192,7 +193,10 @@ export type LocalizerKeys =
| 'failedToAddAsModerator'
| 'failedToRemoveFromModerator'
| 'faq'
+ | 'fileId'
+ | 'fileSize'
| 'fileSizeWarning'
+ | 'fileType'
| 'from'
| 'getStarted'
| 'goToReleaseNotes'
@@ -263,6 +267,7 @@ export type LocalizerKeys =
| 'messageBodyMissing'
| 'messageDeletedPlaceholder'
| 'messageDeletionForbidden'
+ | 'messageInfo'
| 'messageRequestAccepted'
| 'messageRequestAcceptedOurs'
| 'messageRequestAcceptedOursNoName'
@@ -297,6 +302,7 @@ export type LocalizerKeys =
| 'noModeratorsToRemove'
| 'noNameOrMessage'
| 'noSearchResults'
+ | 'notApplicable'
| 'noteToSelf'
| 'notificationForConvo'
| 'notificationForConvo_all'
@@ -389,6 +395,7 @@ export type LocalizerKeys =
| 'requestsPlaceholder'
| 'requestsSubtitle'
| 'resend'
+ | 'resolution'
| 'respondingToRequestWarning'
| 'restoreUsingRecoveryPhrase'
| 'ringing'
@@ -495,6 +502,7 @@ export type LocalizerKeys =
| 'unblocked'
| 'unknown'
| 'unknownCountry'
+ | 'unknownError'
| 'unpinConversation'
| 'unreadMessages'
| 'updateGroupDialogTitle'