From 7c80f9e2332288b4fdc5367df1eb56c8dc6bd51e Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 30 Aug 2022 17:09:53 +1000 Subject: [PATCH] feat: move attachments outside of the message box --- stylesheets/_conversation.scss | 2 +- stylesheets/_modules.scss | 46 +-- stylesheets/_quote.scss | 2 + stylesheets/_session.scss | 5 - stylesheets/_session_constants.scss | 5 +- stylesheets/_session_conversation.scss | 3 + stylesheets/_session_theme.scss | 43 +-- ts/components/conversation/Image.tsx | 3 - ts/components/conversation/ImageGrid.tsx | 277 +++++------------- .../message-content/MessageContent.tsx | 152 ++-------- ...sagePreview.tsx => MessageLinkPreview.tsx} | 11 +- .../message/message-content/MessageText.tsx | 9 +- .../message/message-item/Message.tsx | 3 +- ts/components/lightbox/Lightbox.tsx | 5 +- ts/state/selectors/conversations.ts | 8 +- ts/types/Attachment.ts | 42 +-- 16 files changed, 164 insertions(+), 452 deletions(-) rename ts/components/conversation/message/message-content/{MessagePreview.tsx => MessageLinkPreview.tsx} (89%) diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index 32879b7fe..0e5f5753e 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -27,7 +27,7 @@ margin: 4px 16px; padding: 4px; - border-radius: 15px; + border-radius: $border-radius-message-box; align-self: flex-start; diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 5950e22d3..aed9529b2 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -145,17 +145,13 @@ .module-message__link-preview { cursor: pointer; - border-top-left-radius: $session_message-container-border-radius; - border-top-right-radius: $session_message-container-border-radius; } .module-message__link-preview__content { padding: 8px; - background-color: $color-white; display: flex; flex-direction: row; align-items: flex-start; - border: 1px solid $color-black-015; } .module-message__link-preview__image_container { @@ -208,7 +204,6 @@ } .module-message__link-preview__title { - color: $color-gray-90; font-size: 16px; font-weight: 300; letter-spacing: 0.15px; @@ -222,7 +217,6 @@ .module-message__link-preview__location { margin-top: 4px; - color: $color-gray-60; font-size: 12px; height: 16px; letter-spacing: 0.4px; @@ -787,7 +781,7 @@ } .module-image--soft-corners { - border-radius: 4px; + border-radius: $border-radius-message-box; } .module-image__border-overlay { @@ -819,21 +813,6 @@ } } -.module-image__bottom-overlay { - height: 48px; - background-image: linear-gradient( - to bottom, - rgba(0, 0, 0, 0), - rgba(0, 0, 0, 0) 9%, - rgba(0, 0, 0, 0.6) - ); - position: absolute; - bottom: 0; - z-index: 1; - left: 0; - right: 0; -} - .module-image__play-overlay__circle { position: absolute; top: 50%; @@ -885,29 +864,6 @@ background-image: url('../images/x-shadow-16.svg'); } -// Module: Image Grid - -.module-image-grid { - display: inline-flex; - flex-direction: row; - align-items: center; - - margin: -1px; -} - -.module-image-grid__column { - display: inline-flex; - flex-direction: column; - align-items: center; -} - -.module-image-grid__row { - display: inline-flex; - flex-direction: row; - align-items: center; - flex-grow: 1; -} - // Module: Typing Animation .module-typing-animation { diff --git a/stylesheets/_quote.scss b/stylesheets/_quote.scss index 31a990a60..c25200f0c 100644 --- a/stylesheets/_quote.scss +++ b/stylesheets/_quote.scss @@ -20,6 +20,7 @@ &--outgoing { .module-quote__primary__author { color: var(--color-sent-message-text); + font-weight: bold; .module-contact-name { @@ -38,6 +39,7 @@ &--incoming { .module-quote__primary__author { color: var(--color-received-message-text); + font-weight: bold; .module-contact-name { font-weight: bold; diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index fe23d4724..aa85702c5 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -345,11 +345,6 @@ label { text-align: center; position: relative; overflow: hidden; - - border-top-left-radius: $session_message-container-border-radius; - border-bottom-left-radius: $session_message-container-border-radius; - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; } .conversation-header { diff --git a/stylesheets/_session_constants.scss b/stylesheets/_session_constants.scss index 4258baa0d..d733ec863 100644 --- a/stylesheets/_session_constants.scss +++ b/stylesheets/_session_constants.scss @@ -216,8 +216,6 @@ $session-modal-size-sm: 220px; $session-modal-size-md: 400px; $session-modal-size-lg: 650px; -$session_message-container-border-radius: 13px; - // Spacing $session-margin-xs: 5px; $session-margin-sm: 10px; @@ -241,3 +239,6 @@ $session-transition-duration: 0.25s; // ////////////////////////////////////////////// $composition-container-height: 60px; + +$padding-message-content: 7px 13px; // FIXME to move to SessionTheme +$border-radius-message-box: 16px; // FIXME to move to SessionTheme diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index b74026687..adf22e3ce 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -348,6 +348,9 @@ $rhap_font-family: inherit !default; padding: 0px; background-color: transparent; box-shadow: none; + background: var(--color-accent); + padding: $padding-message-content; + border-radius: $border-radius-message-box; svg { transition: fill $session-transition-duration; diff --git a/stylesheets/_session_theme.scss b/stylesheets/_session_theme.scss index 9bb593b5e..855b7e2f5 100644 --- a/stylesheets/_session_theme.scss +++ b/stylesheets/_session_theme.scss @@ -6,6 +6,22 @@ background: var(--color-cell-background); } +.module-message__container { + .module-message__text { + font-size: 14px; + line-height: 18px; + text-align: start; + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; + white-space: pre-wrap; + + a { + text-decoration: underline; + } + } +} + .module-message { position: relative; display: inline-flex; @@ -26,22 +42,18 @@ word-break: break-word; white-space: pre-wrap; + padding: $padding-message-content; + border-radius: $border-radius-message-box; + a { text-decoration: underline; } } &__container--incoming { - &--opaque { - background: var(--color-received-message-background); - } - - &--transparent { - background: none; - } + color: var(--color-received-message-text); .module-message__text { - color: var(--color-received-message-text); display: flex; flex-direction: row; align-items: center; @@ -52,26 +64,17 @@ a { text-decoration: underline; - color: var(--color-received-message-text); + color: inherit; } } } + color: var(--color-sent-message-text); &__container--outgoing { - &--opaque { - background: var(--color-sent-message-background); - } - - &--transparent { - background: none; - } - .module-message__text { - color: var(--color-sent-message-text); - a { text-decoration: underline; - color: var(--color-sent-message-text); + color: inherit; } } } diff --git a/ts/components/conversation/Image.tsx b/ts/components/conversation/Image.tsx index 15d409af0..b6fcb3776 100644 --- a/ts/components/conversation/Image.tsx +++ b/ts/components/conversation/Image.tsx @@ -16,7 +16,6 @@ type Props = { overlayText?: string; - bottomOverlay?: boolean; closeButton?: boolean; darkOverlay?: boolean; @@ -35,7 +34,6 @@ export const Image = (props: Props) => { const { alt, attachment, - bottomOverlay, closeButton, darkOverlay, height, @@ -154,7 +152,6 @@ export const Image = (props: Props) => { className="module-image__close-button" /> ) : null} - {bottomOverlay ?
: null} {!(pending || loading) && playIconOverlay ? (
diff --git a/ts/components/conversation/ImageGrid.tsx b/ts/components/conversation/ImageGrid.tsx index 152c192de..72c2d3e29 100644 --- a/ts/components/conversation/ImageGrid.tsx +++ b/ts/components/conversation/ImageGrid.tsx @@ -1,263 +1,142 @@ import React, { useContext } from 'react'; -import classNames from 'classnames'; import { areAllAttachmentsVisual, AttachmentType, AttachmentTypeWithPath, getAlt, - getImageDimensionsInAttachment, getThumbnailUrl, isVideoAttachment, } from '../../types/Attachment'; import { Image } from './Image'; import { IsMessageVisibleContext } from './message/message-content/MessageContent'; +import styled from 'styled-components'; +import { THUMBNAIL_SIDE } from '../../types/attachments/VisualAttachment'; type Props = { attachments: Array; - bottomOverlay?: boolean; onError: () => void; onClickAttachment?: (attachment: AttachmentTypeWithPath | AttachmentType) => void; }; + +const StyledImageGrid = styled.div` + display: inline-flex; + flex-direction: row; + align-items: center; + gap: var(--margins-sm); +`; + +const StyledImageGridColumn = styled.div` + display: inline-flex; + flex-direction: column; + align-items: center; + gap: var(--margins-sm); +`; + // tslint:disable: cyclomatic-complexity max-func-body-length use-simple-attributes export const ImageGrid = (props: Props) => { - const { attachments, bottomOverlay, onError, onClickAttachment } = props; + const { attachments, onError, onClickAttachment } = props; const isMessageVisible = useContext(IsMessageVisibleContext); - const withBottomOverlay = Boolean(bottomOverlay); - if (!attachments || !attachments.length) { return null; } - if (attachments.length === 1 || !areAllAttachmentsVisual(attachments)) { - const { height, width } = getImageDimensionsInAttachment(attachments[0]); + const shared = { + onClick: onClickAttachment, + onError: onError, + softCorners: true, + }; + if (attachments.length === 1 || !areAllAttachmentsVisual(attachments)) { return ( -
+ {getAlt(attachments[0])} -
+ ); } if (attachments.length === 2) { + // when we got 2 attachments we render them side by side with the full size of THUMBNAIL_SIDE return ( -
+ {getAlt(attachments[0])} {getAlt(attachments[1])} -
+ ); } - if (attachments.length === 3) { - return ( -
- {getAlt(attachments[0])} -
- {getAlt(attachments[1])} - {getAlt(attachments[2])} -
-
- ); - } + const moreMessagesOverlay = attachments.length > 3; + const moreMessagesOverlayText = moreMessagesOverlay ? `+${attachments.length - 3}` : undefined; - if (attachments.length === 4) { - return ( -
-
-
- {getAlt(attachments[0])} - {getAlt(attachments[1])} -
-
- {getAlt(attachments[2])} - {getAlt(attachments[3])} -
-
-
- ); - } - - const moreMessagesOverlay = attachments.length > 5; - const moreMessagesOverlayText = moreMessagesOverlay ? `+${attachments.length - 5}` : undefined; + const columnImageSide = THUMBNAIL_SIDE / 2 - 5; + // we know only support having 3 attachments displayed at most. return ( -
-
-
- {getAlt(attachments[0])} - {getAlt(attachments[1])} -
-
- {getAlt(attachments[2])} - {getAlt(attachments[3])} - {getAlt(attachments[4])} -
-
-
+ + {getAlt(attachments[0])} + + {getAlt(attachments[1])} + {getAlt(attachments[2])} + + ); }; diff --git a/ts/components/conversation/message/message-content/MessageContent.tsx b/ts/components/conversation/message/message-content/MessageContent.tsx index 5159389ce..ee0745bec 100644 --- a/ts/components/conversation/message/message-content/MessageContent.tsx +++ b/ts/components/conversation/message/message-content/MessageContent.tsx @@ -4,27 +4,16 @@ import React, { createContext, useCallback, useContext, useLayoutEffect, useStat import { InView } from 'react-intersection-observer'; import { useSelector } from 'react-redux'; import { isEmpty } from 'lodash'; -import { MessageRenderingProps } from '../../../../models/messageType'; +import { MessageModelType, MessageRenderingProps } from '../../../../models/messageType'; import { getMessageContentSelectorProps, getMessageTextProps, getQuotedMessageToAnimate, getShouldHighlightMessage, } from '../../../../state/selectors/conversations'; -import { - canDisplayImage, - getGridDimensions, - getImageDimensionsInAttachment, - hasImage, - hasVideoScreenshot, - isImage, - isImageAttachment, - isVideo, -} from '../../../../types/Attachment'; -import { Flex } from '../../../basic/Flex'; -import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../message-item/Message'; + import { MessageAttachment } from './MessageAttachment'; -import { MessagePreview } from './MessagePreview'; +import { MessageLinkPreview } from './MessageLinkPreview'; import { MessageQuote } from './MessageQuote'; import { MessageText } from './MessageText'; import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer'; @@ -40,40 +29,6 @@ type Props = { isDetailView?: boolean; }; -function getIsShowingImage( - props: Pick & { imageBroken: boolean } -): boolean { - const { attachments, previews, text, imageBroken } = props; - - if (imageBroken) { - return false; - } - - if (attachments && attachments.length) { - const displayImage = canDisplayImage(attachments); - const hasText = text?.length; - return Boolean( - displayImage && - !hasText && - ((isImage(attachments) && hasImage(attachments)) || - (isVideo(attachments) && hasVideoScreenshot(attachments))) - ); - } - - if (previews && previews.length) { - const first = previews[0]; - const { image } = first; - - if (!image) { - return false; - } - - return isImageAttachment(image); - } - - return false; -} - function onClickOnMessageInnerContainer(event: React.MouseEvent) { const selection = window.getSelection(); // Text is being selected @@ -88,11 +43,17 @@ function onClickOnMessageInnerContainer(event: React.MouseEvent) } } -const StyledMessageContent = styled.div` - border-radius: 18px; +const StyledMessageOpaqueContent = styled.div<{ messageDirection: MessageModelType }>` + background: ${props => + props.messageDirection === 'incoming' + ? 'var(--color-received-message-background)' + : 'var(--color-sent-message-background)'}; + padding: 7px 13px; // FIXME + border-radius: 16px; //FIXME `; export const IsMessageVisibleContext = createContext(false); +// tslint:disable: use-simple-attributes export const MessageContent = (props: Props) => { const [flashGreen, setFlashGreen] = useState(false); @@ -151,15 +112,7 @@ export const MessageContent = (props: Props) => { return null; } - const { - direction, - text, - timestamp, - serverTimestamp, - previews, - quote, - attachments, - } = contentProps; + const { direction, text, timestamp, serverTimestamp, previews } = contentProps; const selectedMsg = useSelector(state => getMessageTextProps(state as any, props.messageId)); @@ -168,30 +121,17 @@ export const MessageContent = (props: Props) => { isDeleted = selectedMsg.isDeleted; } - const width = getWidth({ previews, attachments }); - const isShowingImage = getIsShowingImage({ attachments, imageBroken, previews, text }); - const hasText = Boolean(text); - const hasQuote = !isEmpty(quote); const hasContentAfterAttachmentAndQuote = !isEmpty(previews) || !isEmpty(text); - const bgShouldBeTransparent = isShowingImage && !hasText && !hasQuote; const toolTipTitle = moment(serverTimestamp || timestamp).format('llll'); - // tslint:disable: use-simple-attributes return ( - { triggerOnce={false} > - {!isDeleted && ( - <> - - - - )} {hasContentAfterAttachmentAndQuote ? ( - <> + {!isDeleted && ( - + <> + + + )} - - - - + + ) : null} + {!isDeleted && ( + + )} - +
); }; - -function getWidth( - props: Pick -): number | undefined { - const { attachments, previews } = props; - - if (attachments && attachments.length) { - const dimensions = getGridDimensions(attachments); - if (dimensions) { - return dimensions.width; - } - } - - if (previews && previews.length) { - const first = previews[0]; - - if (!first || !first.image) { - return; - } - const { width } = first.image; - - if (isImageAttachment(first.image) && width && width >= MINIMUM_LINK_PREVIEW_IMAGE_WIDTH) { - const dimensions = getImageDimensionsInAttachment(first.image); - if (dimensions) { - return dimensions.width; - } - } - } - - return; -} diff --git a/ts/components/conversation/message/message-content/MessagePreview.tsx b/ts/components/conversation/message/message-content/MessageLinkPreview.tsx similarity index 89% rename from ts/components/conversation/message/message-content/MessagePreview.tsx rename to ts/components/conversation/message/message-content/MessageLinkPreview.tsx index 0550a9e71..74861e4ee 100644 --- a/ts/components/conversation/message/message-content/MessagePreview.tsx +++ b/ts/components/conversation/message/message-content/MessageLinkPreview.tsx @@ -5,20 +5,23 @@ import { ImageGrid } from '../../ImageGrid'; import { Image } from '../../Image'; import { MessageRenderingProps } from '../../../../models/messageType'; import { useDispatch, useSelector } from 'react-redux'; -import { getMessagePreviewProps } from '../../../../state/selectors/conversations'; +import { getMessageLinkPreviewProps } from '../../../../state/selectors/conversations'; import { SessionIcon } from '../../../icon'; import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../message-item/Message'; import { showLinkVisitWarningDialog } from '../../../dialog/SessionConfirm'; -export type MessagePreviewSelectorProps = Pick; +export type MessageLinkPreviewSelectorProps = Pick< + MessageRenderingProps, + 'attachments' | 'previews' +>; type Props = { handleImageError: () => void; messageId: string; }; -export const MessagePreview = (props: Props) => { - const selected = useSelector(state => getMessagePreviewProps(state as any, props.messageId)); +export const MessageLinkPreview = (props: Props) => { + const selected = useSelector(state => getMessageLinkPreviewProps(state as any, props.messageId)); const dispatch = useDispatch(); if (!selected) { diff --git a/ts/components/conversation/message/message-content/MessageText.tsx b/ts/components/conversation/message/message-content/MessageText.tsx index 3c623ae4c..5a697cc28 100644 --- a/ts/components/conversation/message/message-content/MessageText.tsx +++ b/ts/components/conversation/message/message-content/MessageText.tsx @@ -38,14 +38,7 @@ export const MessageText = (props: Props) => { } return ( -
+
{isDeleted && } { if (!props || isEmpty(props)) { return undefined; } - const msgProps: MessagePreviewSelectorProps = pick(props.propsForMessage, [ + const msgProps: MessageLinkPreviewSelectorProps = pick(props.propsForMessage, [ 'attachments', 'previews', ]); diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index 5335ce0f1..ac2f00a21 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -6,11 +6,12 @@ import { saveURLAsFile } from '../util/saveURLAsFile'; import { SignalService } from '../protobuf'; import { isImageTypeSupported, isVideoTypeSupported } from '../util/GoogleChrome'; import { ATTACHMENT_DEFAULT_MAX_SIDE } from '../util/attachmentsUtil'; +import { THUMBNAIL_SIDE } from './attachments/VisualAttachment'; -const MAX_WIDTH = 200; -const MAX_HEIGHT = MAX_WIDTH; -const MIN_WIDTH = MAX_WIDTH; -const MIN_HEIGHT = MAX_WIDTH; +const MAX_WIDTH = THUMBNAIL_SIDE; +const MAX_HEIGHT = THUMBNAIL_SIDE; +const MIN_WIDTH = THUMBNAIL_SIDE; +const MIN_HEIGHT = THUMBNAIL_SIDE; // Used for display @@ -217,39 +218,6 @@ export function areAllAttachmentsVisual(attachments?: Array): bo return true; } -export function getGridDimensions(attachments?: Array): null | DimensionsType { - if (!attachments || !attachments.length) { - return null; - } - - if (!isImage(attachments) && !isVideo(attachments)) { - return null; - } - - if (attachments.length === 1) { - return getImageDimensionsInAttachment(attachments[0]); - } - - if (attachments.length === 2) { - return { - height: 150, - width: 300, - }; - } - - if (attachments.length === 4) { - return { - height: 300, - width: 300, - }; - } - - return { - height: 200, - width: 300, - }; -} - export function getAlt(attachment: AttachmentType): string { return isVideoAttachment(attachment) ? window.i18n('videoAttachmentAlt')