fix attachment logic with ui redesign

pull/1884/head
audric 4 years ago
parent a9dcdea796
commit ee4ed2e075

@ -85,80 +85,16 @@
right: 8px;
}
.module-message__attachment-container {
// Entirely to ensure that images are centered if they aren't full width of bubble
text-align: center;
position: relative;
margin-inline-start: -12px;
margin-inline-end: -12px;
margin-top: -10px;
margin-bottom: -10px;
border-radius: $session_message-container-border-radius;
overflow: hidden;
// no background by default for the attachment container
}
.module-message--outgoing {
.module-message__attachment-container--with-content-below,
.module-message__attachment-container--with-content-above {
background: none;
}
}
.module-message--incoming {
.module-message__attachment-container--with-content-below,
.module-message__attachment-container--with-content-above {
background: none;
}
}
.module-message__attachment-container--with-content-below {
margin-bottom: 7px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
.module-message__attachment-container--with-content-above {
margin-top: 4px;
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
.module-message__img-attachment {
margin-bottom: -3px;
// redundant with attachment-container, but we get cursor flashing on move otherwise
cursor: pointer;
}
.module-message__audio-attachment {
margin-top: 2px;
}
.module-message__audio-attachment--with-content-below {
margin-bottom: 5px;
}
.module-message__audio-attachment--with-content-above {
margin-top: 6px;
}
.module-message__generic-attachment {
display: flex;
flex-direction: row;
align-items: center;
}
.module-message__generic-attachment--with-content-below {
padding-bottom: 6px;
}
.module-message__generic-attachment--with-content-above {
padding-top: 4px;
}
.module-message__generic-attachment__icon-container {
position: relative;
cursor: pointer;
@ -269,12 +205,6 @@
border-top-right-radius: $session_message-container-border-radius;
}
.module-message__link-preview--with-content-above {
margin-top: 4px;
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
.module-message__link-preview__content {
padding: 8px;
border-top-left-radius: $session_message-container-border-radius;
@ -286,13 +216,6 @@
border: 1px solid $color-black-015;
}
.module-message__link-preview__content--with-content-above {
border-top: none;
border-bottom: none;
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
.module-message__link-preview__image_container {
margin: -2px;
margin-inline-end: 8px;
@ -389,70 +312,6 @@
align-items: center;
}
// Module: Embedded Contact
.module-embedded-contact {
// Cursor is always a pointer because this component is always wired up to the contact detail screen
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
}
.module-embedded-contact--with-content-above {
padding-top: 4px;
}
.module-embedded-contact--with-content-below {
padding-bottom: 4px;
}
.module-embedded-contact__spinner-container {
padding-inline-start: 5px;
padding-inline-end: 5px;
}
.module-embedded-contact__text-container {
flex-grow: 1;
margin-inline-start: 8px;
max-width: calc(100% - 58px);
}
.module-embedded-contact__contact-name {
font-size: 14px;
line-height: 18px;
font-weight: 300;
margin-top: 6px;
color: $color-gray-90;
max-width: 100%;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.module-embedded-contact__contact-name--incoming {
color: $color-white;
}
.module-embedded-contact__contact-method {
font-size: 11px;
line-height: 16px;
letter-spacing: 0.3px;
margin-top: 3px;
color: $color-gray-60;
max-width: 100%;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.module-embedded-contact__contact-method--incoming {
color: $color-white-07;
}
// Module: Contact Detail
.module-contact-detail {
@ -543,45 +402,6 @@
font-weight: bold;
}
// Module: Reset Session Notification
.module-reset-session-notification {
margin-top: 14px;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.3px;
color: $color-gray-60;
text-align: center;
}
.module-verification-notification__button {
margin-top: 5px;
display: inline-block;
cursor: pointer;
font-size: 13px;
font-weight: 300;
line-height: 18px;
padding: 12px;
color: $color-loki-green;
background-color: $color-light-02;
border-radius: 4px;
}
// Module: Verification Notification
.module-verification-notification {
margin-top: 14px;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.3px;
color: $color-gray-60;
text-align: center;
}
.module-verification-notification__contact {
font-weight: 300;
}
// Module: Timer Notification
.module-timer-notification {
@ -751,6 +571,7 @@
align-items: center;
-webkit-user-select: text;
cursor: pointer;
.module-contact-name__profile-name {
width: 100%;
@ -1226,22 +1047,6 @@
border-radius: 4px;
}
.module-image--curved-top-left {
border-top-left-radius: $session_message-container-border-radius;
}
.module-image--curved-top-right {
border-top-right-radius: $session_message-container-border-radius;
}
.module-image--curved-bottom-left {
border-bottom-left-radius: $session_message-container-border-radius;
}
.module-image--curved-bottom-right {
border-bottom-right-radius: $session_message-container-border-radius;
}
.module-image--small-curved-top-left {
border-top-left-radius: 10px;
}
.module-image__border-overlay {
position: absolute;
top: 0;
@ -1249,7 +1054,6 @@
z-index: 1;
left: 0;
right: 0;
box-shadow: inset 0px 0px 0px 1px $color-black-015;
}
.module-image__border-overlay--dark {
@ -1265,10 +1069,7 @@
.module-image__image {
object-fit: cover;
// redundant with attachment-container, but we get cursor flashing on move otherwise
cursor: pointer;
margin-bottom: -3px;
}
.module-image__bottom-overlay {
@ -1347,10 +1148,6 @@
margin: -1px;
}
.module-image-grid--one-image {
margin-bottom: -5px;
}
.module-image-grid__column {
display: inline-flex;
flex-direction: column;

@ -131,15 +131,9 @@
}
.module-quote-container {
margin-inline-start: -6px;
margin-inline-end: -6px;
margin-top: -4px;
margin-bottom: 5px;
padding-left: 5px;
}
.module-quote-container--with-content-above {
margin-top: 3px;
margin-top: 10px;
padding-left: 10px;
}
.module-quote--no-click {

@ -342,10 +342,6 @@ textarea {
.module-message__container {
position: relative;
display: inline-block;
padding-inline-end: 10px;
padding-inline-start: 10px;
padding-top: 10px;
padding-bottom: 10px;
overflow: hidden;
min-width: 30px;
// To limit messages with things forcing them wider, like long attachment names
@ -355,12 +351,17 @@ textarea {
label {
user-select: none;
}
.module-message__attachment-container {
// Entirely to ensure that images are centered if they aren't full width of bubble
text-align: center;
position: relative;
border-radius: $session_message-container-border-radius;
overflow: hidden;
// no background by default for the attachment container
}
.module-message__attachment-container,
.module-image--curved-bottom-right,
.module-image--curved-top-left,
.module-image--curved-top-right,
.module-image--curved-bottom-left {
.module-message__attachment-container {
border-top-left-radius: $session_message-container-border-radius;
border-top-right-radius: $session_message-container-border-radius;
border-bottom-left-radius: $session_message-container-border-radius;

@ -207,11 +207,6 @@
border: 1px solid $color-gray-60;
}
.module-message__link-preview__content--with-content-above {
border-top: none;
border-bottom: none;
}
.module-message__link-preview__title {
color: $color-gray-05;
}
@ -220,24 +215,6 @@
color: $color-gray-25;
}
// Module: Embedded Contact
.module-embedded-contact__contact-name {
color: $color-dark-05;
}
.module-embedded-contact__contact-name--incoming {
color: $color-white;
}
.module-embedded-contact__contact-method {
color: $color-white-07;
}
.module-embedded-contact__contact-method--incoming {
color: $color-white-07;
}
// Module: Contact Detail
.module-contact-detail__send-message {
@ -259,23 +236,6 @@
color: $color-dark-30;
}
// Module: Reset Session Notification
.module-reset-session-notification {
color: $color-dark-30;
}
.module-verification-notification__button {
color: $color-loki-green;
background-color: $color-gray-75;
}
// Module: Verification Notification
.module-verification-notification {
color: $color-dark-30;
}
// Module: Timer Notification
.module-timer-notification {
@ -404,10 +364,6 @@
background: none;
}
.module-image__border-overlay {
box-shadow: inset 0px 0px 0px 1px $color-white-015;
}
.module-image__loading-placeholder {
background-color: $color-white-015;
}

@ -18,6 +18,7 @@ import {
getSelectedMessageIds,
isMessageDetailView,
isMessageSelectionMode,
isRightPanelShowing,
} from '../../state/selectors/conversations';
import { useDispatch, useSelector } from 'react-redux';
import { useMembersAvatars } from '../../hooks/useMembersAvatar';
@ -25,6 +26,7 @@ import { useMembersAvatars } from '../../hooks/useMembersAvatar';
import { deleteMessagesById } from '../../interactions/conversationInteractions';
import {
closeMessageDetailsView,
closeRightPanel,
NotificationForConvoOption,
openRightPanel,
resetSelectedMessageIds,
@ -210,6 +212,8 @@ export type ConversationHeaderTitleProps = {
const ConversationHeaderTitle = () => {
const headerTitleProps = useSelector(getConversationHeaderTitleProps);
const notificationSetting = useSelector(getCurrentNotificationSettingText);
const isRightPanelOn = useSelector(isRightPanelShowing);
const dispatch = useDispatch();
if (!headerTitleProps) {
return null;
}
@ -257,7 +261,17 @@ const ConversationHeaderTitle = () => {
const title = profileName || name || phoneNumber;
return (
<div className="module-conversation-header__title">
<div
className="module-conversation-header__title"
onClick={() => {
if (isRightPanelOn) {
dispatch(closeRightPanel());
} else {
dispatch(openRightPanel());
}
}}
role="button"
>
<span className="module-contact-name__profile-name">{title}</span>
<StyledSubtitleContainer>
<ConversationHeaderSubtitle text={fullTextSubtitle} />

@ -17,12 +17,6 @@ type Props = {
bottomOverlay?: boolean;
closeButton?: boolean;
curveBottomLeft?: boolean;
curveBottomRight?: boolean;
curveTopLeft?: boolean;
curveTopRight?: boolean;
smallCurveTopLeft?: boolean;
darkOverlay?: boolean;
playIconOverlay?: boolean;
@ -40,10 +34,6 @@ export const Image = (props: Props) => {
attachment,
bottomOverlay,
closeButton,
curveBottomLeft,
curveBottomRight,
curveTopLeft,
curveTopRight,
darkOverlay,
height,
onClick,
@ -51,7 +41,6 @@ export const Image = (props: Props) => {
onError,
overlayText,
playIconOverlay,
smallCurveTopLeft,
softCorners,
url,
width,
@ -83,11 +72,6 @@ export const Image = (props: Props) => {
className={classNames(
'module-image',
canClick ? 'module-image__with-click-handler' : null,
curveBottomLeft ? 'module-image--curved-bottom-left' : null,
curveBottomRight ? 'module-image--curved-bottom-right' : null,
curveTopLeft ? 'module-image--curved-top-left' : null,
curveTopRight ? 'module-image--curved-top-right' : null,
smallCurveTopLeft ? 'module-image--small-curved-top-left' : null,
softCorners ? 'module-image--soft-corners' : null
)}
>
@ -125,11 +109,6 @@ export const Image = (props: Props) => {
<div
className={classNames(
'module-image__border-overlay',
curveTopLeft ? 'module-image--curved-top-left' : null,
curveTopRight ? 'module-image--curved-top-right' : null,
curveBottomLeft ? 'module-image--curved-bottom-left' : null,
curveBottomRight ? 'module-image--curved-bottom-right' : null,
smallCurveTopLeft ? 'module-image--small-curved-top-left' : null,
softCorners ? 'module-image--soft-corners' : null,
darkOverlay ? 'module-image__border-overlay--dark' : null
)}
@ -146,15 +125,7 @@ export const Image = (props: Props) => {
className="module-image__close-button"
/>
) : null}
{bottomOverlay ? (
<div
className={classNames(
'module-image__bottom-overlay',
curveBottomLeft ? 'module-image--curved-bottom-left' : null,
curveBottomRight ? 'module-image--curved-bottom-right' : null
)}
/>
) : null}
{bottomOverlay ? <div className={classNames('module-image__bottom-overlay')} /> : null}
{!(pending || loading) && playIconOverlay ? (
<div className="module-image__play-overlay__circle">
<div className="module-image__play-overlay__icon" />

@ -16,8 +16,6 @@ import { Image } from './Image';
type Props = {
attachments: Array<AttachmentTypeWithPath>;
withContentAbove?: boolean;
withContentBelow?: boolean;
bottomOverlay?: boolean;
onError: () => void;
@ -26,23 +24,9 @@ type Props = {
export const ImageGrid = (props: Props) => {
// tslint:disable-next-line max-func-body-length */
const {
attachments,
bottomOverlay,
onError,
onClickAttachment,
withContentAbove,
withContentBelow,
} = props;
const { attachments, bottomOverlay, onError, onClickAttachment } = props;
const curveTopLeft = !Boolean(withContentAbove);
const curveTopRight = curveTopLeft;
const curveBottom = !Boolean(withContentBelow);
const curveBottomLeft = curveBottom;
const curveBottomRight = curveBottom;
const withBottomOverlay = Boolean(bottomOverlay && curveBottom);
const withBottomOverlay = Boolean(bottomOverlay);
if (!attachments || !attachments.length) {
return null;
@ -56,10 +40,6 @@ export const ImageGrid = (props: Props) => {
<Image
alt={getAlt(attachments[0])}
bottomOverlay={withBottomOverlay}
curveTopLeft={curveTopLeft}
curveTopRight={curveTopRight}
curveBottomLeft={curveBottomLeft}
curveBottomRight={curveBottomRight}
attachment={attachments[0]}
playIconOverlay={isVideoAttachment(attachments[0])}
height={height}
@ -79,8 +59,6 @@ export const ImageGrid = (props: Props) => {
alt={getAlt(attachments[0])}
attachment={attachments[0]}
bottomOverlay={withBottomOverlay}
curveTopLeft={curveTopLeft}
curveBottomLeft={curveBottomLeft}
playIconOverlay={isVideoAttachment(attachments[0])}
height={149}
width={149}
@ -91,8 +69,6 @@ export const ImageGrid = (props: Props) => {
<Image
alt={getAlt(attachments[1])}
bottomOverlay={withBottomOverlay}
curveTopRight={curveTopRight}
curveBottomRight={curveBottomRight}
playIconOverlay={isVideoAttachment(attachments[1])}
height={149}
width={149}
@ -111,8 +87,6 @@ export const ImageGrid = (props: Props) => {
<Image
alt={getAlt(attachments[0])}
bottomOverlay={withBottomOverlay}
curveTopLeft={curveTopLeft}
curveBottomLeft={curveBottomLeft}
attachment={attachments[0]}
playIconOverlay={isVideoAttachment(attachments[0])}
height={200}
@ -124,7 +98,6 @@ export const ImageGrid = (props: Props) => {
<div className="module-image-grid__column">
<Image
alt={getAlt(attachments[1])}
curveTopRight={curveTopRight}
height={99}
width={99}
attachment={attachments[1]}
@ -136,7 +109,6 @@ export const ImageGrid = (props: Props) => {
<Image
alt={getAlt(attachments[2])}
bottomOverlay={withBottomOverlay}
curveBottomRight={curveBottomRight}
height={99}
width={99}
attachment={attachments[2]}
@ -157,7 +129,6 @@ export const ImageGrid = (props: Props) => {
<div className="module-image-grid__row">
<Image
alt={getAlt(attachments[0])}
curveTopLeft={curveTopLeft}
attachment={attachments[0]}
playIconOverlay={isVideoAttachment(attachments[0])}
height={149}
@ -168,7 +139,6 @@ export const ImageGrid = (props: Props) => {
/>
<Image
alt={getAlt(attachments[1])}
curveTopRight={curveTopRight}
playIconOverlay={isVideoAttachment(attachments[1])}
height={149}
width={149}
@ -182,7 +152,6 @@ export const ImageGrid = (props: Props) => {
<Image
alt={getAlt(attachments[2])}
bottomOverlay={withBottomOverlay}
curveBottomLeft={curveBottomLeft}
playIconOverlay={isVideoAttachment(attachments[2])}
height={149}
width={149}
@ -194,7 +163,6 @@ export const ImageGrid = (props: Props) => {
<Image
alt={getAlt(attachments[3])}
bottomOverlay={withBottomOverlay}
curveBottomRight={curveBottomRight}
playIconOverlay={isVideoAttachment(attachments[3])}
height={149}
width={149}
@ -218,7 +186,6 @@ export const ImageGrid = (props: Props) => {
<div className="module-image-grid__row">
<Image
alt={getAlt(attachments[0])}
curveTopLeft={curveTopLeft}
attachment={attachments[0]}
playIconOverlay={isVideoAttachment(attachments[0])}
height={149}
@ -229,7 +196,6 @@ export const ImageGrid = (props: Props) => {
/>
<Image
alt={getAlt(attachments[1])}
curveTopRight={curveTopRight}
playIconOverlay={isVideoAttachment(attachments[1])}
height={149}
width={149}
@ -243,7 +209,6 @@ export const ImageGrid = (props: Props) => {
<Image
alt={getAlt(attachments[2])}
bottomOverlay={withBottomOverlay}
curveBottomLeft={curveBottomLeft}
playIconOverlay={isVideoAttachment(attachments[2])}
height={99}
width={99}
@ -266,7 +231,6 @@ export const ImageGrid = (props: Props) => {
<Image
alt={getAlt(attachments[4])}
bottomOverlay={withBottomOverlay}
curveBottomRight={curveBottomRight}
playIconOverlay={isVideoAttachment(attachments[4])}
height={99}
width={99}

@ -211,26 +211,13 @@ class MessageInner extends React.PureComponent<Props, State> {
// tslint:disable-next-line max-func-body-length cyclomatic-complexity
public renderAttachment() {
const {
id,
attachments,
text,
conversationType,
direction,
quote,
isTrustedForAttachmentDownload,
} = this.props;
const { id, attachments, direction, isTrustedForAttachmentDownload } = this.props;
const { imageBroken } = this.state;
if (!attachments || !attachments[0]) {
return null;
}
const firstAttachment = attachments[0];
// For attachments which aren't full-frame
const withContentBelow = Boolean(text);
const withContentAbove =
Boolean(quote) || (conversationType === 'group' && direction === 'incoming');
const displayImage = canDisplayImage(attachments);
if (!isTrustedForAttachmentDownload) {
@ -244,17 +231,9 @@ class MessageInner extends React.PureComponent<Props, State> {
(isVideo(attachments) && hasVideoScreenshot(attachments)))
) {
return (
<div
className={classNames(
'module-message__attachment-container',
withContentAbove ? 'module-message__attachment-container--with-content-above' : null,
withContentBelow ? 'module-message__attachment-container--with-content-below' : null
)}
>
<div className={classNames('module-message__attachment-container')}>
<ImageGrid
attachments={attachments}
withContentAbove={withContentAbove}
withContentBelow={withContentBelow}
onError={this.handleImageError}
onClickAttachment={this.onClickOnImageGrid}
/>
@ -281,13 +260,7 @@ class MessageInner extends React.PureComponent<Props, State> {
const isDangerous = isFileDangerous(fileName || '');
return (
<div
className={classNames(
'module-message__generic-attachment',
withContentBelow ? 'module-message__generic-attachment--with-content-below' : null,
withContentAbove ? 'module-message__generic-attachment--with-content-above' : null
)}
>
<div className={classNames('module-message__generic-attachment')}>
{pending ? (
<div className="module-message__generic-attachment__spinner-container">
<Spinner size="small" direction={direction} />
@ -337,7 +310,7 @@ class MessageInner extends React.PureComponent<Props, State> {
// tslint:disable-next-line cyclomatic-complexity
public renderPreview() {
const { attachments, conversationType, direction, previews, quote } = this.props;
const { attachments, previews } = this.props;
// Attachments take precedence over Link Previews
if (attachments && attachments.length) {
@ -353,41 +326,19 @@ class MessageInner extends React.PureComponent<Props, State> {
return null;
}
const withContentAbove =
Boolean(quote) || (conversationType === 'group' && direction === 'incoming');
const previewHasImage = first.image && isImageAttachment(first.image);
const width = first.image && first.image.width;
const isFullSizeImage = width && width >= MINIMUM_LINK_PREVIEW_IMAGE_WIDTH;
return (
<div
role="button"
className={classNames(
'module-message__link-preview',
withContentAbove ? 'module-message__link-preview--with-content-above' : null
)}
>
<div role="button" className={classNames('module-message__link-preview')}>
{first.image && previewHasImage && isFullSizeImage ? (
<ImageGrid
attachments={[first.image]}
withContentAbove={withContentAbove}
withContentBelow={true}
onError={this.handleImageError}
/>
<ImageGrid attachments={[first.image]} onError={this.handleImageError} />
) : null}
<div
className={classNames(
'module-message__link-preview__content',
withContentAbove || isFullSizeImage
? 'module-message__link-preview__content--with-content-above'
: null
)}
>
<div className={classNames('module-message__link-preview__content')}>
{first.image && previewHasImage && !isFullSizeImage ? (
<div className="module-message__link-preview__image_container">
<Image
smallCurveTopLeft={!withContentAbove}
softCorners={true}
alt={window.i18n('previewThumbnail', [first.domain])}
height={72}
@ -423,13 +374,11 @@ class MessageInner extends React.PureComponent<Props, State> {
}
public renderQuote() {
const { conversationType, direction, quote, isPublic, convoId } = this.props;
const { direction, quote } = this.props;
if (!quote || !quote.authorPhoneNumber || !quote.messageId) {
return null;
}
const withContentAbove = conversationType === 'group' && direction === 'incoming';
const shortenedPubkey = PubKey.shorten(quote.authorPhoneNumber);
const displayedPubkey = quote.authorProfileName ? shortenedPubkey : quote.authorPhoneNumber;
@ -440,15 +389,11 @@ class MessageInner extends React.PureComponent<Props, State> {
text={quote.text}
attachment={quote.attachment}
isIncoming={direction === 'incoming'}
conversationType={conversationType}
convoId={convoId}
isPublic={isPublic}
authorPhoneNumber={displayedPubkey}
authorProfileName={quote.authorProfileName}
authorName={quote.authorName}
referencedMessageNotFound={quote.referencedMessageNotFound}
isFromMe={quote.isFromMe}
withContentAbove={withContentAbove}
/>
);
}
@ -538,6 +483,18 @@ class MessageInner extends React.PureComponent<Props, State> {
return <OutgoingMessageStatus status={status} />;
}
public renderExpireTimer(isCorrectSide: boolean) {
const { expirationLength, expirationTimestamp } = this.props;
if (!(isCorrectSide && expirationLength && expirationTimestamp)) {
return null;
}
return (
<ExpireTimer expirationLength={expirationLength} expirationTimestamp={expirationTimestamp} />
);
}
public getWidth(): number | undefined {
const { attachments, previews } = this.props;
@ -600,8 +557,7 @@ class MessageInner extends React.PureComponent<Props, State> {
return false;
}
// tslint:disable-next-line: cyclomatic-complexity
// tslint:disable-next-line: max-func-body-length
// tslint:disable-next-line: cyclomatic-complexity cyclomatic-complexity
public render() {
const {
direction,
@ -610,142 +566,122 @@ class MessageInner extends React.PureComponent<Props, State> {
selectedMessages,
receivedAt,
isUnread,
text,
timestamp,
serverTimestamp,
expirationLength,
expirationTimestamp,
firstMessageOfSeries,
lastMessageOfSeries,
} = this.props;
const { expired, expiring } = this.state;
const { expired } = this.state;
if (expired) {
return null;
}
const selected = selectedMessages.includes(messageId) || false;
const width = this.getWidth();
const isShowingImage = this.isShowingImage();
const divClasses = ['session-message-wrapper'];
if (selected) {
divClasses.push('message-selected');
}
if (conversationType === 'group') {
divClasses.push('public-chat-message-wrapper');
}
if (this.props.quotedMessageToAnimate === messageId) {
divClasses.push('flash-green-once');
}
const isGroup = conversationType === 'group';
const isQuotedMessageToAnimate = this.props.quotedMessageToAnimate === messageId;
const isIncoming = direction === 'incoming';
if (isIncoming) {
divClasses.push('session-message-wrapper-incoming');
} else {
divClasses.push('session-message-wrapper-outgoing');
}
const hasText = Boolean(text);
const bgShouldBeTransparent = isShowingImage && !hasText;
const toolTipTitle = moment(serverTimestamp || timestamp).format('llll');
return (
<ReadableMessage
messageId={messageId}
className={classNames(divClasses)}
className={classNames(
'session-message-wrapper',
selected && 'message-selected',
isGroup && 'public-chat-message-wrapper',
isQuotedMessageToAnimate && 'flash-green-once',
isIncoming ? 'session-message-wrapper-incoming' : 'session-message-wrapper-outgoing'
)}
onContextMenu={this.handleContextMenu}
receivedAt={receivedAt}
isUnread={isUnread}
key={`readable-message-${messageId}`}
>
{this.renderAvatar()}
{!isIncoming && expirationLength && expirationTimestamp ? (
<ExpireTimer
expirationLength={expirationLength}
expirationTimestamp={expirationTimestamp}
/>
) : null}
<div
className={classNames(
'module-message',
`module-message--${direction}`,
expiring ? 'module-message--expired' : null
)}
role="button"
onClick={this.onClickOnMessageOuterContainer}
>
{this.renderStatus(isIncoming)}
<Flex container={true} flexDirection="column">
<MessageAuthorText
authorName={this.props.authorName}
authorPhoneNumber={this.props.authorPhoneNumber}
authorProfileName={this.props.authorProfileName}
conversationType={this.props.conversationType}
direction={this.props.direction}
firstMessageOfSeries={this.props.firstMessageOfSeries}
isPublic={this.props.isPublic}
/>
{this.renderExpireTimer(!isIncoming)}
{this.renderMessageContentWithStatuses()}
{this.renderExpireTimer(isIncoming)}
</ReadableMessage>
);
}
<div
className={classNames(
'module-message__container',
`module-message__container--${direction}`,
bgShouldBeTransparent
? `module-message__container--${direction}--transparent`
: `module-message__container--${direction}--opaque`,
firstMessageOfSeries
? `module-message__container--${direction}--first-of-series`
: '',
lastMessageOfSeries ? `module-message__container--${direction}--last-of-series` : ''
)}
style={{
width: isShowingImage ? width : undefined,
}}
role="button"
onClick={this.onClickOnMessageInnerContainer}
title={toolTipTitle}
>
{this.renderQuote()}
{this.renderAttachment()}
{this.renderPreview()}
{this.renderText()}
</div>
</Flex>
{this.renderStatus(!isIncoming)}
private renderMessageContentWithStatuses() {
const { expiring } = this.state;
const { direction } = this.props;
const isIncoming = direction === 'incoming';
<MessageContextMenu
return (
<div
className={classNames(
'module-message',
`module-message--${direction}`,
expiring ? 'module-message--expired' : null
)}
role="button"
onClick={this.onClickOnMessageOuterContainer}
>
{this.renderStatus(isIncoming)}
<Flex container={true} flexDirection="column">
<MessageAuthorText
authorName={this.props.authorName}
authorPhoneNumber={this.props.authorPhoneNumber}
convoId={this.props.convoId}
contextMenuId={this.ctxMenuID}
authorProfileName={this.props.authorProfileName}
direction={this.props.direction}
isBlocked={this.props.isBlocked}
isDeletable={this.props.isDeletable}
messageId={this.props.id}
text={this.props.text}
timestamp={this.props.timestamp}
serverTimestamp={this.props.serverTimestamp}
attachments={this.props.attachments}
isAdmin={this.props.isSenderAdmin}
isOpenGroupV2={this.props.isOpenGroupV2}
isPublic={this.props.isPublic}
status={this.props.status}
weAreAdmin={this.props.weAreAdmin}
/>
</div>
{isIncoming && expirationLength && expirationTimestamp ? (
<ExpireTimer
expirationLength={expirationLength}
expirationTimestamp={expirationTimestamp}
firstMessageOfSeries={this.props.firstMessageOfSeries}
/>
{this.renderMessageContent()}
</Flex>
{this.renderStatus(!isIncoming)}
{this.renderContextMenu()}
</div>
);
}
private renderMessageContent() {
const {
direction,
text,
timestamp,
serverTimestamp,
firstMessageOfSeries,
lastMessageOfSeries,
} = this.props;
const width = this.getWidth();
const isShowingImage = this.isShowingImage();
const hasText = Boolean(text);
const hasQuote = !_.isEmpty(this.props.quote);
const hasContentAfterAttachmentAndQuote =
!_.isEmpty(this.props.previews) || !_.isEmpty(this.props.text);
const bgShouldBeTransparent = isShowingImage && !hasText && !hasQuote;
const toolTipTitle = moment(serverTimestamp || timestamp).format('llll');
return (
<div
className={classNames(
'module-message__container',
`module-message__container--${direction}`,
bgShouldBeTransparent
? `module-message__container--${direction}--transparent`
: `module-message__container--${direction}--opaque`,
firstMessageOfSeries ? `module-message__container--${direction}--first-of-series` : '',
lastMessageOfSeries ? `module-message__container--${direction}--last-of-series` : ''
)}
style={{
width: isShowingImage ? width : undefined,
}}
role="button"
onClick={this.onClickOnMessageInnerContainer}
title={toolTipTitle}
>
{this.renderQuote()}
{this.renderAttachment()}
{hasContentAfterAttachmentAndQuote ? (
<Flex padding="5px 5px 10px 5px">
{this.renderPreview()}
{this.renderText()}
</Flex>
) : null}
</ReadableMessage>
</div>
);
}
@ -765,6 +701,29 @@ class MessageInner extends React.PureComponent<Props, State> {
}
}
private renderContextMenu() {
return (
<MessageContextMenu
authorPhoneNumber={this.props.authorPhoneNumber}
convoId={this.props.convoId}
contextMenuId={this.ctxMenuID}
direction={this.props.direction}
isBlocked={this.props.isBlocked}
isDeletable={this.props.isDeletable}
messageId={this.props.id}
text={this.props.text}
timestamp={this.props.timestamp}
serverTimestamp={this.props.serverTimestamp}
attachments={this.props.attachments}
isAdmin={this.props.isSenderAdmin}
isOpenGroupV2={this.props.isOpenGroupV2}
isPublic={this.props.isPublic}
status={this.props.status}
weAreAdmin={this.props.weAreAdmin}
/>
);
}
private onQuoteClick(e: any) {
const { quote, multiSelectMode, id } = this.props;
e.preventDefault();

@ -1,6 +1,6 @@
// tslint:disable:react-this-binding-issue
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import classNames from 'classnames';
import * as MIME from '../../../ts/types/MIME';
@ -9,9 +9,15 @@ import * as GoogleChrome from '../../../ts/util/GoogleChrome';
import { MessageBody } from './MessageBody';
import { ContactName } from './ContactName';
import { PubKey } from '../../session/types';
import { ConversationTypeEnum } from '../../models/conversation';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import { useSelector } from 'react-redux';
import {
getSelectedConversationKey,
isGroupConversation,
isPublicGroupConversation,
} from '../../state/selectors/conversations';
import { noop } from 'underscore';
export type QuotePropsWithoutListener = {
attachment?: QuotedAttachmentType;
@ -20,10 +26,6 @@ export type QuotePropsWithoutListener = {
authorName?: string;
isFromMe: boolean;
isIncoming: boolean;
conversationType: ConversationTypeEnum;
convoId: string;
isPublic?: boolean;
withContentAbove: boolean;
text: string | null;
referencedMessageNotFound: boolean;
};
@ -107,7 +109,12 @@ export const QuoteIcon = (props: any) => {
);
};
export const QuoteImage = (props: any) => {
export const QuoteImage = (props: {
handleImageErrorBound: () => void;
url: string;
contentType: string;
icon?: string;
}) => {
const { url, icon, contentType, handleImageErrorBound } = props;
const { loading, urlToLoad } = useEncryptedFileFetch(url, contentType);
@ -144,7 +151,9 @@ export const QuoteImage = (props: any) => {
);
};
export const QuoteGenericFile = (props: any) => {
export const QuoteGenericFile = (
props: Pick<QuotePropsWithoutListener, 'attachment' | 'isIncoming'>
) => {
const { attachment, isIncoming } = props;
if (!attachment) {
@ -176,7 +185,12 @@ export const QuoteGenericFile = (props: any) => {
);
};
export const QuoteIconContainer = (props: any) => {
export const QuoteIconContainer = (
props: Pick<QuotePropsWithoutListener, 'attachment'> & {
handleImageErrorBound: () => void;
imageBroken: boolean;
}
) => {
const { attachment, imageBroken, handleImageErrorBound } = props;
if (!attachment) {
@ -188,7 +202,12 @@ export const QuoteIconContainer = (props: any) => {
if (GoogleChrome.isVideoTypeSupported(contentType)) {
return objectUrl && !imageBroken ? (
<QuoteImage url={objectUrl} icon={'play'} />
<QuoteImage
url={objectUrl}
contentType={MIME.IMAGE_JPEG}
icon="play"
handleImageErrorBound={noop}
/>
) : (
<QuoteIcon icon="movie" />
);
@ -210,9 +229,12 @@ export const QuoteIconContainer = (props: any) => {
return null;
};
export const QuoteText = (props: any) => {
const { text, attachment, isIncoming, conversationType, convoId } = props;
const isGroup = conversationType === ConversationTypeEnum.GROUP;
export const QuoteText = (
props: Pick<QuotePropsWithoutListener, 'text' | 'attachment' | 'isIncoming'>
) => {
const { text, attachment, isIncoming } = props;
const isGroup = useSelector(isGroupConversation);
const convoId = useSelector(getSelectedConversationKey);
if (text) {
return (
@ -285,7 +307,9 @@ const QuoteAuthor = (props: QuoteAuthorProps) => {
);
};
export const QuoteReferenceWarning = (props: any) => {
export const QuoteReferenceWarning = (
props: Pick<QuotePropsWithoutListener, 'isIncoming' | 'referencedMessageNotFound'>
) => {
const { isIncoming, referencedMessageNotFound } = props;
if (!referencedMessageNotFound) {
@ -318,21 +342,21 @@ export const QuoteReferenceWarning = (props: any) => {
};
export const Quote = (props: QuotePropsWithListener) => {
const handleImageErrorBound = null;
const [imageBroken, setImageBroken] = useState(false);
const handleImageErrorBound = () => {
setImageBroken(true);
};
const { isIncoming, onClick, referencedMessageNotFound, withContentAbove } = props;
const isPublic = useSelector(isPublicGroupConversation);
if (!validateQuote(props)) {
return null;
}
const { isIncoming, referencedMessageNotFound, attachment, text, onClick } = props;
return (
<div
className={classNames(
'module-quote-container',
withContentAbove ? 'module-quote-container--with-content-above' : null
)}
>
<div className={classNames('module-quote-container')}>
<div
onClick={onClick}
role="button"
@ -340,7 +364,6 @@ export const Quote = (props: QuotePropsWithListener) => {
'module-quote',
isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',
!onClick ? 'module-quote--no-click' : null,
withContentAbove ? 'module-quote--with-content-above' : null,
referencedMessageNotFound ? 'module-quote--with-reference-warning' : null
)}
>
@ -351,14 +374,21 @@ export const Quote = (props: QuotePropsWithListener) => {
authorProfileName={props.authorProfileName}
isFromMe={props.isFromMe}
isIncoming={props.isIncoming}
showPubkeyForAuthor={props.isPublic}
showPubkeyForAuthor={isPublic}
/>
<QuoteGenericFile {...props} />
<QuoteText {...props} />
<QuoteText isIncoming={isIncoming} text={text} attachment={attachment} />
</div>
<QuoteIconContainer {...props} handleImageErrorBound={handleImageErrorBound} />
<QuoteIconContainer
attachment={attachment}
handleImageErrorBound={handleImageErrorBound}
imageBroken={imageBroken}
/>
</div>
<QuoteReferenceWarning {...props} />
<QuoteReferenceWarning
isIncoming={isIncoming}
referencedMessageNotFound={referencedMessageNotFound}
/>
</div>
);
};

@ -1,7 +1,11 @@
import React from 'react';
import { ConversationTypeEnum } from '../../../models/conversation';
import { useSelector } from 'react-redux';
import { MessageModelType } from '../../../models/messageType';
import { PubKey } from '../../../session/types/PubKey';
import {
isGroupConversation,
isPublicGroupConversation,
} from '../../../state/selectors/conversations';
import { Flex } from '../../basic/Flex';
import { ContactName } from '../ContactName';
@ -9,9 +13,7 @@ export type MessageAuthorProps = {
authorName: string | null;
authorProfileName: string | null;
authorPhoneNumber: string;
conversationType: ConversationTypeEnum;
direction: MessageModelType;
isPublic: boolean;
firstMessageOfSeries: boolean;
};
@ -20,15 +22,16 @@ export const MessageAuthorText = (props: MessageAuthorProps) => {
authorName,
authorPhoneNumber,
authorProfileName,
conversationType,
direction,
isPublic,
firstMessageOfSeries,
} = props;
const isPublic = useSelector(isPublicGroupConversation);
const isGroup = useSelector(isGroupConversation);
const title = authorName ? authorName : authorPhoneNumber;
if (direction !== 'incoming' || conversationType !== 'group' || !title || !firstMessageOfSeries) {
if (direction !== 'incoming' || !isGroup || !title || !firstMessageOfSeries) {
return null;
}
@ -37,17 +40,15 @@ export const MessageAuthorText = (props: MessageAuthorProps) => {
const displayedPubkey = authorProfileName ? shortenedPubkey : authorPhoneNumber;
return (
<div className="module-message__author">
<Flex container={true}>
<ContactName
phoneNumber={displayedPubkey}
name={authorName}
profileName={authorProfileName}
module="module-message__author"
boldProfileName={true}
shouldShowPubkey={Boolean(isPublic)}
/>
</Flex>
</div>
<Flex container={true}>
<ContactName
phoneNumber={displayedPubkey}
name={authorName}
profileName={authorProfileName}
module="module-message__author"
boldProfileName={true}
shouldShowPubkey={Boolean(isPublic)}
/>
</Flex>
);
};

@ -93,10 +93,6 @@ export const SessionQuotedMessageComposition = () => {
attachment={firstImageAttachment}
height={100}
width={100}
curveTopLeft={true}
curveTopRight={true}
curveBottomLeft={true}
curveBottomRight={true}
url={firstImageAttachment.thumbnail.objectUrl}
/>
)}

@ -363,6 +363,7 @@ class SettingsViewInner extends React.Component<SettingsViewProps, State> {
title: window.i18n('linkPreviewsTitle'),
message: window.i18n('linkPreviewsConfirmMessage'),
okTheme: SessionButtonColor.Danger,
// onClickOk:
})
);
}

@ -48,6 +48,37 @@ export const getSelectedConversation = createSelector(
}
);
/**
* Returns true if the current conversation selected is a group conversation.
* Returns false if the current conversation selected is not a group conversation, or none are selected
*/
export const isGroupConversation = createSelector(
getSelectedConversation,
(state: ReduxConversationType | undefined): boolean => {
return state?.type === 'group' || false;
}
);
/**
* Returns true if the current conversation selected is a closed group and false otherwise.
*/
export const isClosedGroupConversation = createSelector(
getSelectedConversation,
(state: ReduxConversationType | undefined): boolean => {
return (state?.type === 'group' && !state.isPublic) || false;
}
);
/**
* Returns true if the current conversation selected is a public group and false otherwise.
*/
export const isPublicGroupConversation = createSelector(
getSelectedConversation,
(state: ReduxConversationType | undefined): boolean => {
return (state?.type === 'group' && state.isPublic) || false;
}
);
export const getOurPrimaryConversation = createSelector(
getConversations,
(state: ConversationsStateType): ReduxConversationType =>

Loading…
Cancel
Save