Merge pull request #3020 from yougotwill/fix/ses-1409/multiselect_border_shadow

SES 1409, 579, 714
pull/3023/head
Audric Ackermann 1 year ago committed by GitHub
commit 427b6ade3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -113,7 +113,7 @@
"react-dom": "^17.0.2",
"react-draggable": "^4.4.4",
"react-h5-audio-player": "^3.2.0",
"react-intersection-observer": "^8.30.3",
"react-intersection-observer": "^9.7.0",
"react-mentions": "^4.4.9",
"react-qr-svg": "^2.2.1",
"react-redux": "8.0.4",

@ -134,7 +134,6 @@ textarea {
.module-message__container {
position: relative;
display: inline-block;
overflow: hidden;
min-width: 30px;
// To limit messages with things forcing them wider, like long attachment names.
width: 100%;

@ -48,15 +48,14 @@ const CrownWrapper = styled.div`
align-items: center;
justify-content: center;
position: absolute;
bottom: 0%;
right: 12%;
height: 20px;
width: 20px;
bottom: 11%;
right: 11%;
height: 18px;
width: 18px;
transform: translate(20%, 20%); // getting over 23% creates a glitch
color: #f7c347;
background: var(--background-primary-color);
border-radius: 50%;
box-shadow: var(--drop-shadow);
`;
export const CrownIcon = () => {

@ -5,6 +5,7 @@ import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import { setNextMessageToPlayId } from '../../state/ducks/conversations';
import { useMessageSelected } from '../../state/selectors';
import {
getNextMessageToPlayId,
getSortedMessagesOfSelectedConversation,
@ -25,7 +26,7 @@ const StyledSpeedButton = styled.div`
}
`;
export const StyledH5AudioPlayer = styled(H5AudioPlayer)`
export const StyledH5AudioPlayer = styled(H5AudioPlayer)<{ dropShadow?: boolean }>`
&.rhap_container {
min-width: 220px;
padding: 0px;
@ -145,6 +146,8 @@ export const StyledH5AudioPlayer = styled(H5AudioPlayer)`
height: 20px;
margin-right: 0px;
}
${props => props.dropShadow && 'box-shadow: var(--drop-shadow);'}
`;
export const AudioPlayerWithEncryptedFile = (props: {
@ -162,6 +165,7 @@ export const AudioPlayerWithEncryptedFile = (props: {
const messageProps = useSelector(getSortedMessagesOfSelectedConversation);
const nextMessageToPlayId = useSelector(getNextMessageToPlayId);
const multiSelectMode = useSelector(isMessageSelectionMode);
const selected = useMessageSelected(messageId);
const dataTestId = `audio-${messageId}`;
@ -263,6 +267,7 @@ export const AudioPlayerWithEncryptedFile = (props: {
play: <SessionIcon iconType="play" iconSize="small" />,
pause: <SessionIcon iconType="pause" iconSize="small" />,
}}
dropShadow={selected}
/>
);
};

@ -24,6 +24,7 @@ type Props = {
playIconOverlay?: boolean;
softCorners: boolean;
forceSquare?: boolean;
dropShadow?: boolean;
attachmentIndex?: number;
onClick?: (attachment: AttachmentTypeWithPath | AttachmentType) => void;
@ -55,6 +56,7 @@ export const Image = (props: Props) => {
playIconOverlay,
softCorners,
forceSquare,
dropShadow,
attachmentIndex,
url,
width: _width,
@ -101,6 +103,7 @@ export const Image = (props: Props) => {
maxWidth: width,
minHeight: height,
minWidth: width,
boxShadow: dropShadow ? 'var(--drop-shadow)' : undefined,
}}
data-attachmentindex={attachmentIndex}
>

@ -10,14 +10,16 @@ import {
isVideoAttachment,
} from '../../types/Attachment';
import { useMessageSelected } from '../../state/selectors';
import { THUMBNAIL_SIDE } from '../../types/attachments/VisualAttachment';
import { Image } from './Image';
import { IsMessageVisibleContext } from './message/message-content/MessageContent';
import { THUMBNAIL_SIDE } from '../../types/attachments/VisualAttachment';
type Props = {
attachments: Array<AttachmentTypeWithPath>;
onError: () => void;
onClickAttachment?: (attachment: AttachmentTypeWithPath | AttachmentType) => void;
messageId?: string;
};
const StyledImageGrid = styled.div<{ flexDirection: 'row' | 'column' }>`
@ -28,7 +30,12 @@ const StyledImageGrid = styled.div<{ flexDirection: 'row' | 'column' }>`
`;
const Row = (
props: Props & { renderedSize: number; startIndex: number; totalAttachmentsCount: number }
props: Props & {
renderedSize: number;
startIndex: number;
totalAttachmentsCount: number;
selected: boolean;
}
) => {
const {
attachments,
@ -37,6 +44,7 @@ const Row = (
startIndex,
onClickAttachment,
totalAttachmentsCount,
selected,
} = props;
const isMessageVisible = useContext(IsMessageVisibleContext);
const moreMessagesOverlay = totalAttachmentsCount > 3;
@ -61,6 +69,7 @@ const Row = (
softCorners={true}
darkOverlay={showOverlay}
overlayText={showOverlay ? moreMessagesOverlayText : undefined}
dropShadow={selected}
/>
);
})}
@ -69,7 +78,9 @@ const Row = (
};
export const ImageGrid = (props: Props) => {
const { attachments, onError, onClickAttachment } = props;
const { attachments, onError, onClickAttachment, messageId } = props;
const selected = useMessageSelected(messageId);
if (!attachments || !attachments.length) {
return null;
@ -85,6 +96,7 @@ export const ImageGrid = (props: Props) => {
renderedSize={THUMBNAIL_SIDE}
startIndex={0}
totalAttachmentsCount={attachments.length}
selected={selected}
/>
</StyledImageGrid>
);
@ -101,6 +113,7 @@ export const ImageGrid = (props: Props) => {
renderedSize={THUMBNAIL_SIDE}
startIndex={0}
totalAttachmentsCount={attachments.length}
selected={selected}
/>
</StyledImageGrid>
);
@ -118,6 +131,7 @@ export const ImageGrid = (props: Props) => {
renderedSize={THUMBNAIL_SIDE}
startIndex={0}
totalAttachmentsCount={attachments.length}
selected={selected}
/>
<StyledImageGrid flexDirection={'column'}>
@ -128,6 +142,7 @@ export const ImageGrid = (props: Props) => {
renderedSize={columnImageSide}
startIndex={1}
totalAttachmentsCount={attachments.length}
selected={selected}
/>
</StyledImageGrid>
</StyledImageGrid>

@ -51,8 +51,7 @@ type Props = SessionMessageListProps & {
scrollToNow: () => Promise<void>;
};
// isGroup is used to align the ExpireTimer with the member avatars
const StyledMessagesContainer = styled.div<{ isGroup: boolean }>`
const StyledMessagesContainer = styled.div`
display: flex;
flex-grow: 1;
gap: var(--margins-sm);
@ -61,11 +60,8 @@ const StyledMessagesContainer = styled.div<{ isGroup: boolean }>`
overflow-x: hidden;
min-width: 370px;
scrollbar-width: 4px;
padding-top: var(--margins-sm);
padding-right: var(--margins-lg);
padding-bottom: var(--margins-xl);
padding-left: ${props => (props.isGroup ? 'var(--margins-xs)' : 'var(--margins-lg)')};
.session-icon-button {
display: flex;
@ -126,7 +122,6 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
<StyledMessagesContainer
className="messages-container"
id={messageContainerDomID}
isGroup={!conversation.isPrivate}
onScroll={this.handleScroll}
ref={this.props.messageContainerRef}
data-testid="messages-container"

@ -18,32 +18,26 @@ import {
} from '../../basic/SessionButton';
import { SessionIconButton } from '../../icon';
function onDeleteSelectedMessagesForEveryone(
selectedConversationKey: string,
selectedMessageIds: Array<string>
) {
if (selectedConversationKey) {
void deleteMessagesByIdForEveryone(selectedMessageIds, selectedConversationKey);
}
}
export const SelectionOverlay = () => {
const selectedMessageIds = useSelector(getSelectedMessageIds);
const selectedConversationKey = useSelectedConversationKey();
const isPublic = useSelectedIsPublic();
const dispatch = useDispatch();
const { i18n } = window;
function onCloseOverlay() {
dispatch(resetSelectedMessageIds());
}
function onDeleteSelectedMessages() {
if (selectedConversationKey) {
void deleteMessagesById(selectedMessageIds, selectedConversationKey);
}
}
function onDeleteSelectedMessagesForEveryone() {
if (selectedConversationKey) {
void deleteMessagesByIdForEveryone(selectedMessageIds, selectedConversationKey);
}
}
const isOnlyServerDeletable = isPublic;
const deleteMessageButtonText = i18n('delete');
const deleteForEveryoneMessageButtonText = i18n('deleteForEveryone');
return (
<div className="message-selection-overlay">
@ -52,21 +46,23 @@ export const SelectionOverlay = () => {
</div>
<div className="button-group">
{!isOnlyServerDeletable && (
<SessionButton
buttonColor={SessionButtonColor.Danger}
buttonShape={SessionButtonShape.Square}
buttonType={SessionButtonType.Solid}
text={deleteMessageButtonText}
onClick={onDeleteSelectedMessages}
/>
)}
<SessionButton
buttonColor={SessionButtonColor.Danger}
buttonShape={SessionButtonShape.Square}
buttonType={SessionButtonType.Solid}
text={deleteForEveryoneMessageButtonText}
onClick={onDeleteSelectedMessagesForEveryone}
text={window.i18n('delete')}
onClick={() => {
if (selectedConversationKey) {
if (isOnlyServerDeletable) {
void onDeleteSelectedMessagesForEveryone(
selectedConversationKey,
selectedMessageIds
);
} else {
void deleteMessagesById(selectedMessageIds, selectedConversationKey);
}
}
}}
/>
</div>
</div>

@ -1,9 +1,9 @@
import React, { useCallback, useState } from 'react';
import classNames from 'classnames';
import React, { useCallback, useState } from 'react';
import { MediaItemType } from '../../lightbox/LightboxGallery';
import { AttachmentSection } from './AttachmentSection';
import { EmptyState } from './EmptyState';
import { MediaItemType } from '../../lightbox/LightboxGallery';
type Props = {
documents: Array<MediaItemType>;

@ -11,6 +11,7 @@ import {
toggleSelectedMessageId,
} from '../../../../state/ducks/conversations';
import { StateType } from '../../../../state/reducer';
import { useMessageSelected } from '../../../../state/selectors';
import {
getMessageAttachmentProps,
isMessageSelectionMode,
@ -32,7 +33,7 @@ import { AudioPlayerWithEncryptedFile } from '../../H5AudioPlayer';
import { ImageGrid } from '../../ImageGrid';
import { LightBoxOptions } from '../../SessionConversation';
import { ClickToTrustSender } from './ClickToTrustSender';
import { StyledMessageHighlighter } from './MessageContent';
import { MessageHighlighter } from './MessageHighlighter';
export type MessageAttachmentSelectorProps = Pick<
MessageRenderingProps,
@ -53,7 +54,7 @@ type Props = {
highlight?: boolean;
};
const StyledAttachmentContainer = styled.div<{
const StyledImageGridContainer = styled.div<{
messageDirection: MessageModelType;
}>`
text-align: center;
@ -63,6 +64,10 @@ const StyledAttachmentContainer = styled.div<{
justify-content: ${props => (props.messageDirection === 'incoming' ? 'flex-start' : 'flex-end')};
`;
const StyledGenericAttachmentContainer = styled(MessageHighlighter)<{ selected: boolean }>`
${props => props.selected && 'box-shadow: var(--drop-shadow);'}
`;
export const MessageAttachment = (props: Props) => {
const { messageId, imageBroken, handleImageError, highlight = false } = props;
@ -72,6 +77,7 @@ export const MessageAttachment = (props: Props) => {
);
const multiSelectMode = useSelector(isMessageSelectionMode);
const selected = useMessageSelected(messageId);
const onClickOnImageGrid = useCallback(
(attachment: AttachmentTypeWithPath | AttachmentType) => {
if (multiSelectMode) {
@ -138,21 +144,22 @@ export const MessageAttachment = (props: Props) => {
(isVideo(attachments) && hasVideoScreenshot(attachments)))
) {
return (
<StyledMessageHighlighter highlight={highlight}>
<StyledAttachmentContainer messageDirection={direction}>
<MessageHighlighter highlight={highlight}>
<StyledImageGridContainer messageDirection={direction}>
<ImageGrid
messageId={messageId}
attachments={attachments}
onError={handleImageError}
onClickAttachment={onClickOnImageGrid}
/>
</StyledAttachmentContainer>
</StyledMessageHighlighter>
</StyledImageGridContainer>
</MessageHighlighter>
);
}
if (!firstAttachment.pending && !firstAttachment.error && isAudio(attachments)) {
return (
<StyledMessageHighlighter
<MessageHighlighter
highlight={highlight}
role="main"
onClick={(e: any) => {
@ -168,14 +175,18 @@ export const MessageAttachment = (props: Props) => {
contentType={firstAttachment.contentType}
messageId={messageId}
/>
</StyledMessageHighlighter>
</MessageHighlighter>
);
}
const { pending, fileName, fileSize, contentType } = firstAttachment;
const extension = getExtensionForDisplay({ contentType, fileName });
return (
<StyledMessageHighlighter highlight={highlight} className="module-message__generic-attachment">
<StyledGenericAttachmentContainer
highlight={highlight}
selected={selected}
className={'module-message__generic-attachment'}
>
{pending ? (
<div className="module-message__generic-attachment__spinner-container">
<Spinner size="small" />
@ -211,7 +222,7 @@ export const MessageAttachment = (props: Props) => {
{fileSize}
</div>
</div>
</StyledMessageHighlighter>
</StyledGenericAttachmentContainer>
);
};

@ -4,23 +4,25 @@ import moment from 'moment';
import React, { createContext, useCallback, useContext, useLayoutEffect, useState } from 'react';
import { InView } from 'react-intersection-observer';
import { useSelector } from 'react-redux';
import styled, { css, keyframes } from 'styled-components';
import styled from 'styled-components';
import { MessageModelType, MessageRenderingProps } from '../../../../models/messageType';
import { StateType } from '../../../../state/reducer';
import { useHideAvatarInMsgList, useMessageIsDeleted } from '../../../../state/selectors';
import {
useHideAvatarInMsgList,
useMessageIsDeleted,
useMessageSelected,
} from '../../../../state/selectors';
import {
getMessageContentSelectorProps,
getQuotedMessageToAnimate,
getShouldHighlightMessage,
} from '../../../../state/selectors/conversations';
import {
useSelectedIsGroup,
useSelectedIsPrivate,
} from '../../../../state/selectors/selectedConversation';
import { useSelectedIsPrivate } from '../../../../state/selectors/selectedConversation';
import { canDisplayImage } from '../../../../types/Attachment';
import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer';
import { MessageAttachment } from './MessageAttachment';
import { MessageAvatar } from './MessageAvatar';
import { MessageHighlighter } from './MessageHighlighter';
import { MessageLinkPreview } from './MessageLinkPreview';
import { MessageQuote } from './MessageQuote';
import { MessageText } from './MessageText';
@ -54,41 +56,13 @@ function onClickOnMessageInnerContainer(event: React.MouseEvent<HTMLDivElement>)
const StyledMessageContent = styled.div<{ msgDirection: MessageModelType }>`
display: flex;
align-self: ${props => (props.msgDirection === 'incoming' ? 'flex-start' : 'flex-end')};
`;
const opacityAnimation = keyframes`
0% {
opacity: 1;
}
25% {
opacity: 0.2;
}
50% {
opacity: 1;
}
75% {
opacity: 0.2;
}
100% {
opacity: 1;
}
`;
export const StyledMessageHighlighter = styled.div<{
highlight: boolean;
}>`
${props =>
props.highlight &&
css`
animation: ${opacityAnimation} 1s linear;
`}
`;
const StyledMessageOpaqueContent = styled(StyledMessageHighlighter)<{
const StyledMessageOpaqueContent = styled(MessageHighlighter)<{
isIncoming: boolean;
highlight: boolean;
selected: boolean;
}>`
background: ${props =>
props.isIncoming
@ -98,13 +72,13 @@ const StyledMessageOpaqueContent = styled(StyledMessageHighlighter)<{
padding: var(--padding-message-content);
border-radius: var(--border-radius-message-box);
width: 100%;
${props => props.selected && `box-shadow: var(--drop-shadow);`}
`;
export const IsMessageVisibleContext = createContext(false);
// NOTE aligns group member avatars with the ExpireTimer
const StyledAvatarContainer = styled.div<{ hideAvatar: boolean; isGroup: boolean }>`
/* margin-inline-start: ${props => (!props.hideAvatar && props.isGroup ? '-11px' : '')}; */
const StyledAvatarContainer = styled.div`
align-self: flex-end;
`;
@ -119,16 +93,12 @@ export const MessageContent = (props: Props) => {
const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext);
const selectedIsPrivate = useSelectedIsPrivate();
const isGroup = useSelectedIsGroup();
const hideAvatar = useHideAvatarInMsgList(props.messageId);
const [imageBroken, setImageBroken] = useState(false);
const onVisible = (inView: boolean | object) => {
if (
inView === true ||
((inView as any).type === 'focus' && (inView as any).returnValue === true)
) {
const onVisible = (inView: boolean, _: IntersectionObserverEntry) => {
if (inView) {
if (isMessageVisible !== true) {
setMessageIsVisible(true);
}
@ -142,6 +112,7 @@ export const MessageContent = (props: Props) => {
const quotedMessageToAnimate = useSelector(getQuotedMessageToAnimate);
const shouldHighlightMessage = useSelector(getShouldHighlightMessage);
const isQuotedMessageToAnimate = quotedMessageToAnimate === props.messageId;
const selected = useMessageSelected(props.messageId);
useLayoutEffect(() => {
if (isQuotedMessageToAnimate) {
@ -200,7 +171,7 @@ export const MessageContent = (props: Props) => {
title={toolTipTitle}
msgDirection={direction}
>
<StyledAvatarContainer hideAvatar={hideAvatar} isGroup={isGroup}>
<StyledAvatarContainer>
<MessageAvatar
messageId={props.messageId}
hideAvatar={hideAvatar}
@ -210,6 +181,7 @@ export const MessageContent = (props: Props) => {
<InView
id={`inview-content-${props.messageId}`}
as={'div'}
onChange={onVisible}
threshold={0}
rootMargin="500px 0px 500px 0px"
@ -222,7 +194,11 @@ export const MessageContent = (props: Props) => {
>
<IsMessageVisibleContext.Provider value={isMessageVisible}>
{hasContentBeforeAttachment && (
<StyledMessageOpaqueContent isIncoming={direction === 'incoming'} highlight={highlight}>
<StyledMessageOpaqueContent
isIncoming={direction === 'incoming'}
highlight={highlight}
selected={selected}
>
{!isDeleted && (
<>
<MessageQuote messageId={props.messageId} />

@ -88,19 +88,29 @@ const StyledEmojiPanelContainer = styled.div<{ x: number; y: number }>`
}
`;
const DeleteForEveryone = ({ messageId }: { messageId: string }) => {
const DeleteItem = ({ messageId }: { messageId: string }) => {
const convoId = useSelectedConversationKey();
const isPublic = useSelectedIsPublic();
const isDeletable = useMessageIsDeletable(messageId);
const isDeletableForEveryone = useMessageIsDeletableForEveryone(messageId);
if (!convoId || !isDeletableForEveryone) {
const onDelete = useCallback(() => {
if (convoId) {
if (!isPublic && isDeletable) {
void deleteMessagesById([messageId], convoId);
}
if (isPublic && isDeletableForEveryone) {
void deleteMessagesByIdForEveryone([messageId], convoId);
}
}
}, [convoId, isDeletable, isDeletableForEveryone, isPublic, messageId]);
if (!convoId || (isPublic && !isDeletableForEveryone) || (!isPublic && !isDeletable)) {
return null;
}
const onDeleteForEveryone = () => {
void deleteMessagesByIdForEveryone([messageId], convoId);
};
const unsendMessageText = window.i18n('deleteForEveryone');
return <Item onClick={onDeleteForEveryone}>{unsendMessageText}</Item>;
return <Item onClick={onDelete}>{window.i18n('delete')}</Item>;
};
type MessageId = { messageId: string };
@ -193,7 +203,6 @@ export const MessageContextMenu = (props: Props) => {
const isSelectedBlocked = useSelectedIsBlocked();
const convoId = useSelectedConversationKey();
const isPublic = useSelectedIsPublic();
const direction = useMessageDirection(messageId);
const status = useMessageStatus(messageId);
@ -238,7 +247,6 @@ export const MessageContextMenu = (props: Props) => {
);
const selectMessageText = window.i18n('selectMessage');
const deleteMessageJustForMeText = window.i18n('deleteJustForMe');
const onReply = useCallback(() => {
if (isSelectedBlocked) {
@ -256,12 +264,6 @@ export const MessageContextMenu = (props: Props) => {
dispatch(toggleSelectedMessageId(messageId));
}, [dispatch, messageId]);
const onDelete = useCallback(() => {
if (convoId) {
void deleteMessagesById([messageId], convoId);
}
}, [convoId, messageId]);
const onShowEmoji = () => {
hideAll();
setMouseX(docX);
@ -388,10 +390,7 @@ export const MessageContextMenu = (props: Props) => {
</Item>
<RetryItem messageId={messageId} />
{isDeletable ? <Item onClick={onSelect}>{selectMessageText}</Item> : null}
{isDeletable && !isPublic ? (
<Item onClick={onDelete}>{deleteMessageJustForMeText}</Item>
) : null}
<DeleteForEveryone messageId={messageId} />
<DeleteItem messageId={messageId} />
<AdminActionItems messageId={messageId} />
</Menu>
</SessionContextMenuContainer>

@ -0,0 +1,29 @@
import styled, { css, keyframes } from 'styled-components';
const opacityAnimation = keyframes`
0% {
opacity: 1;
}
25% {
opacity: 0.2;
}
50% {
opacity: 1;
}
75% {
opacity: 0.2;
}
100% {
opacity: 1;
}
`;
export const MessageHighlighter = styled.div<{
highlight: boolean;
}>`
${props =>
props.highlight &&
css`
animation: ${opacityAnimation} 1s linear;
`}
`;

@ -7,9 +7,9 @@ import styled, { keyframes } from 'styled-components';
import { MessageRenderingProps } from '../../../../models/messageType';
import { getConversationController } from '../../../../session/conversations';
import { StateType } from '../../../../state/reducer';
import { useMessageSelected } from '../../../../state/selectors';
import {
getGenericReadableMessageSelectorProps,
getIsMessageSelected,
isMessageSelectionMode,
} from '../../../../state/selectors/conversations';
import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus';
@ -62,18 +62,6 @@ const StyledReadableMessage = styled.div<{
`
background-color: var(--conversation-tab-background-selected-color);
`}
${props =>
props.selected &&
`
&.message-selected {
.module-message {
&__container {
box-shadow: var(--drop-shadow);
}
}
}
`}
`;
export const GenericReadableMessage = (props: Props) => {
@ -85,9 +73,8 @@ export const GenericReadableMessage = (props: Props) => {
getGenericReadableMessageSelectorProps(state, props.messageId)
);
const isMessageSelected = useSelector((state: StateType) =>
getIsMessageSelected(state, props.messageId)
);
const isMessageSelected = useMessageSelected(props.messageId);
const multiSelectMode = useSelector(isMessageSelectionMode);
const [isRightClicked, setIsRightClicked] = useState(false);
@ -155,7 +142,7 @@ export const GenericReadableMessage = (props: Props) => {
selected={selected}
isDetailView={isDetailView}
isRightClicked={isRightClicked}
className={classNames(selected && 'message-selected')}
className={classNames(selected ? 'message-selected' : undefined)}
onContextMenu={handleContextMenu}
key={`readable-message-${messageId}`}
>

@ -26,6 +26,7 @@ const StyledGroupInvitation = styled.div`
display: inline-block;
padding: 4px;
margin: var(--margins-xs) calc(var(--margins-lg) + var(--margins-md)) 0 var(--margins-lg);
border-radius: var(--border-radius-message-box);

@ -116,7 +116,7 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
});
const onVisible = useCallback(
async (inView: boolean | object) => {
async (inView: boolean, _: IntersectionObserverEntry) => {
if (!selectedConversationKey) {
return;
}
@ -135,30 +135,16 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
}
}
if (
inView === true &&
isAppFocused &&
oldestMessageId === messageId &&
!fetchingMoreInProgress
) {
if (inView && isAppFocused && oldestMessageId === messageId && !fetchingMoreInProgress) {
debouncedTriggerLoadMoreTop(selectedConversationKey, oldestMessageId);
}
if (
inView === true &&
isAppFocused &&
youngestMessageId === messageId &&
!fetchingMoreInProgress
) {
if (inView && isAppFocused && youngestMessageId === messageId && !fetchingMoreInProgress) {
debouncedTriggerLoadMoreBottom(selectedConversationKey, youngestMessageId);
}
// this part is just handling the marking of the message as read if needed
if (
(inView === true ||
((inView as any).type === 'focus' && (inView as any).returnValue === true)) &&
isAppFocused
) {
if (inView) {
if (isUnread) {
// TODOLATER this is pretty expensive and should instead use values from the redux store
const found = await Data.getMessageById(messageId);

@ -42,11 +42,11 @@ import { AttachmentTypeWithPath } from '../../../../types/Attachment';
import { getAbsoluteAttachmentPath } from '../../../../types/MessageAttachment';
import { Avatar, AvatarSize } from '../../../avatar/Avatar';
import { Flex } from '../../../basic/Flex';
import { SpacerMD } from '../../../basic/Text';
import { SpacerLG, SpacerMD, SpacerXL } from '../../../basic/Text';
import { PanelButtonGroup, PanelIconButton } from '../../../buttons';
import { MediaItemType } from '../../../lightbox/LightboxGallery';
import { MediaGallery } from '../../media-gallery/MediaGallery';
import { Header } from './components';
import { Header, StyledScrollContainer } from './components';
async function getMediaGalleryProps(
conversationId: string
@ -278,77 +278,81 @@ export const OverlayRightPanelSettings = () => {
};
return (
<>
<HeaderItem />
<PanelButtonGroup style={{ margin: '0 var(--margins-lg)' }}>
{showUpdateGroupNameButton && (
<PanelIconButton
iconType={'group'}
text={isPublic ? window.i18n('editGroup') : window.i18n('editGroupName')}
onClick={() => {
void showUpdateGroupNameByConvoId(selectedConvoKey);
}}
dataTestId="edit-group-name"
/>
)}
{showAddRemoveModeratorsButton && (
<>
<StyledScrollContainer>
<Flex container={true} flexDirection={'column'} alignItems={'center'}>
<HeaderItem />
<PanelButtonGroup style={{ margin: '0 var(--margins-lg)' }}>
{showUpdateGroupNameButton && (
<PanelIconButton
iconType={'addModerator'}
text={window.i18n('addModerators')}
iconType={'group'}
text={isPublic ? window.i18n('editGroup') : window.i18n('editGroupName')}
onClick={() => {
showAddModeratorsByConvoId(selectedConvoKey);
void showUpdateGroupNameByConvoId(selectedConvoKey);
}}
dataTestId="add-moderators"
dataTestId="edit-group-name"
/>
)}
{showAddRemoveModeratorsButton && (
<>
<PanelIconButton
iconType={'addModerator'}
text={window.i18n('addModerators')}
onClick={() => {
showAddModeratorsByConvoId(selectedConvoKey);
}}
dataTestId="add-moderators"
/>
<PanelIconButton
iconType={'deleteModerator'}
text={window.i18n('removeModerators')}
onClick={() => {
showRemoveModeratorsByConvoId(selectedConvoKey);
}}
dataTestId="remove-moderators"
/>
</>
)}
{showUpdateGroupMembersButton && (
<PanelIconButton
iconType={'deleteModerator'}
text={window.i18n('removeModerators')}
iconType={'group'}
text={window.i18n('groupMembers')}
onClick={() => {
showRemoveModeratorsByConvoId(selectedConvoKey);
void showUpdateGroupMembersByConvoId(selectedConvoKey);
}}
dataTestId="remove-moderators"
dataTestId="group-members"
/>
</>
)}
)}
{showUpdateGroupMembersButton && (
<PanelIconButton
iconType={'group'}
text={window.i18n('groupMembers')}
onClick={() => {
void showUpdateGroupMembersByConvoId(selectedConvoKey);
}}
dataTestId="group-members"
/>
)}
{hasDisappearingMessages && (
<PanelIconButton
iconType={'timer50'}
text={window.i18n('disappearingMessages')}
subtitle={disappearingMessagesSubtitle}
dataTestId="disappearing-messages"
onClick={() => {
dispatch(setRightOverlayMode({ type: 'disappearing_messages', params: null }));
}}
/>
)}
{hasDisappearingMessages && (
<PanelIconButton
iconType={'timer50'}
text={window.i18n('disappearingMessages')}
subtitle={disappearingMessagesSubtitle}
dataTestId="disappearing-messages"
onClick={() => {
dispatch(setRightOverlayMode({ type: 'disappearing_messages', params: null }));
}}
/>
)}
<MediaGallery documents={documents} media={media} />
{isGroup && (
<PanelIconButton
text={leaveGroupString}
dataTestId="leave-group-button"
disabled={isKickedFromGroup || left}
onClick={() => void deleteConvoAction()}
color={'var(--danger-color)'}
iconType={'delete'}
/>
)}
</PanelButtonGroup>
</>
<MediaGallery documents={documents} media={media} />
{isGroup && (
<PanelIconButton
text={leaveGroupString}
dataTestId="leave-group-button"
disabled={isKickedFromGroup || left}
onClick={() => void deleteConvoAction()}
color={'var(--danger-color)'}
iconType={'delete'}
/>
)}
</PanelButtonGroup>
<SpacerLG />
<SpacerXL />
</Flex>
</StyledScrollContainer>
);
};

@ -77,15 +77,13 @@ const MessageBody = ({
);
};
const StyledMessageDetailContainer = styled.div`
const StyledMessageInfoContainer = styled.div`
height: calc(100% - 48px);
width: 100%;
overflow-y: auto;
max-width: 650px;
overflow: hidden auto;
z-index: 2;
`;
const StyledMessageDetail = styled.div`
max-width: 650px;
margin-inline-start: auto;
margin-inline-end: auto;
padding: var(--margins-sm) var(--margins-2xl) var(--margins-lg);
@ -254,96 +252,94 @@ export const OverlayMessageInfo = () => {
<Header hideBackButton={true} closeButtonOnClick={closePanel}>
<HeaderTitle>{window.i18n('messageInfo')}</HeaderTitle>
</Header>
<StyledMessageDetailContainer>
<StyledMessageDetail>
<MessageBody
messageId={messageId}
supportsAttachmentCarousel={supportsAttachmentCarousel}
<StyledMessageInfoContainer>
<MessageBody
messageId={messageId}
supportsAttachmentCarousel={supportsAttachmentCarousel}
/>
{hasAttachments && (
<>
{supportsAttachmentCarousel && (
<>
<AttachmentCarousel
messageId={messageId}
attachments={attachments}
visibleIndex={visibleAttachmentIndex}
nextAction={() => {
handleChangeAttachment(1);
}}
previousAction={() => {
handleChangeAttachment(-1);
}}
/>
<SpacerXL />
</>
)}
<AttachmentInfo attachment={attachments[visibleAttachmentIndex]} />
<SpacerMD />
</>
)}
<MessageInfo messageId={messageId} errors={messageInfo.errors} />
<SpacerLG />
<PanelButtonGroup style={{ margin: '0' }}>
<PanelIconButton
text={window.i18n('replyToMessage')}
iconType="reply"
onClick={() => {
// eslint-disable-next-line more/no-then
void replyToMessage(messageId).then(foundIt => {
if (foundIt) {
dispatch(closeRightPanel());
dispatch(resetRightOverlayMode());
}
});
}}
dataTestId="reply-to-msg-from-details"
/>
{hasErrors && direction === 'outgoing' && (
<PanelIconButton
text={window.i18n('resend')}
iconType="resend"
onClick={() => {
void resendMessage(messageId);
dispatch(closeRightPanel());
dispatch(resetRightOverlayMode());
}}
dataTestId="resend-msg-from-details"
/>
)}
{hasAttachments && (
<>
{supportsAttachmentCarousel && (
<>
<AttachmentCarousel
messageId={messageId}
attachments={attachments}
visibleIndex={visibleAttachmentIndex}
nextAction={() => {
handleChangeAttachment(1);
}}
previousAction={() => {
handleChangeAttachment(-1);
}}
/>
<SpacerXL />
</>
)}
<AttachmentInfo attachment={attachments[visibleAttachmentIndex]} />
<SpacerMD />
</>
<PanelIconButton
text={window.i18n('save')}
iconType="saveToDisk"
dataTestId="save-attachment-from-details"
onClick={() => {
if (hasAttachments) {
void saveAttachmentToDisk({
conversationId: convoId,
messageSender: sender,
messageTimestamp: serverTimestamp || timestamp || Date.now(),
attachment: attachments[visibleAttachmentIndex],
index: visibleAttachmentIndex,
});
}
}}
/>
)}
<MessageInfo messageId={messageId} errors={messageInfo.errors} />
<SpacerLG />
<PanelButtonGroup style={{ margin: '0' }}>
{isDeletable && (
<PanelIconButton
text={window.i18n('replyToMessage')}
iconType="reply"
text={window.i18n('delete')}
iconType="delete"
color={'var(--danger-color)'}
dataTestId="delete-from-details"
onClick={() => {
// eslint-disable-next-line more/no-then
void replyToMessage(messageId).then(foundIt => {
if (foundIt) {
dispatch(closeRightPanel());
dispatch(resetRightOverlayMode());
}
});
void deleteMessagesById([messageId], convoId);
}}
dataTestId="reply-to-msg-from-details"
/>
{hasErrors && direction === 'outgoing' && (
<PanelIconButton
text={window.i18n('resend')}
iconType="resend"
onClick={() => {
void resendMessage(messageId);
dispatch(closeRightPanel());
dispatch(resetRightOverlayMode());
}}
dataTestId="resend-msg-from-details"
/>
)}
{hasAttachments && (
<PanelIconButton
text={window.i18n('save')}
iconType="saveToDisk"
dataTestId="save-attachment-from-details"
onClick={() => {
if (hasAttachments) {
void saveAttachmentToDisk({
conversationId: convoId,
messageSender: sender,
messageTimestamp: serverTimestamp || timestamp || Date.now(),
attachment: attachments[visibleAttachmentIndex],
index: visibleAttachmentIndex,
});
}
}}
/>
)}
{isDeletable && (
<PanelIconButton
text={window.i18n('delete')}
iconType="delete"
color={'var(--danger-color)'}
dataTestId="delete-from-details"
onClick={() => {
void deleteMessagesById([messageId], convoId);
}}
/>
)}
</PanelButtonGroup>
<SpacerXL />
</StyledMessageDetail>
</StyledMessageDetailContainer>
)}
</PanelButtonGroup>
<SpacerXL />
</StyledMessageInfoContainer>
</Flex>
</StyledScrollContainer>
);

@ -8,7 +8,7 @@ import {
ReduxConversationType,
} from '../ducks/conversations';
import { StateType } from '../reducer';
import { getMessagePropsByMessageId } from './conversations';
import { getIsMessageSelected, getMessagePropsByMessageId } from './conversations';
import { useSelectedIsPrivate } from './selectedConversation';
function useMessagePropsByMessageId(messageId: string | undefined) {
@ -145,3 +145,7 @@ export function useHideAvatarInMsgList(messageId?: string) {
const selectedIsPrivate = useSelectedIsPrivate();
return msgProps?.propsForMessage.direction === 'outgoing' || selectedIsPrivate;
}
export function useMessageSelected(messageId?: string) {
return useSelector((state: StateType) => getIsMessageSelected(state, messageId));
}

@ -10,7 +10,11 @@ import { UserUtils } from '../../session/utils';
import { ReleasedFeatures } from '../../util/releaseFeature';
import { ReduxConversationType } from '../ducks/conversations';
import { StateType } from '../reducer';
import { getIsMessageSelectionMode, getSelectedConversation } from './conversations';
import {
getIsMessageSelectionMode,
getSelectedConversation,
getSelectedMessageIds,
} from './conversations';
import { getCanWrite, getModerators, getSubscriberCount } from './sogsRoomInfo';
/**
@ -387,3 +391,7 @@ export function useIsMessageSelectionMode() {
export function useSelectedLastMessage() {
return useSelector((state: StateType) => getSelectedConversation(state)?.lastMessage);
}
export function useSelectedMessageIds() {
return useSelector(getSelectedMessageIds);
}

@ -6155,10 +6155,10 @@ react-h5-audio-player@^3.2.0:
"@iconify/icons-mdi" "~1.1.0"
"@iconify/react" "^3.1.3"
react-intersection-observer@^8.30.3:
version "8.34.0"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.34.0.tgz#6f6e67831c52e6233f6b6cc7eb55814820137c42"
integrity sha512-TYKh52Zc0Uptp5/b4N91XydfSGKubEhgZRtcg1rhTKABXijc4Sdr1uTp5lJ8TN27jwUsdXxjHXtHa0kPj704sw==
react-intersection-observer@^9.7.0:
version "9.7.0"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.7.0.tgz#da65834ace0852e04b73cb97f0c48bdaa5b13589"
integrity sha512-euleEjBVaMRwSOMNVcMX5WGn74GfZ9I78nx9SUb5a0eXd0IhegjJcUliSO9Jd+xiaZ5rgFvbGoVln66lpMyUUg==
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"

Loading…
Cancel
Save