put saveAttachemntToDisk outside of component

pull/1783/head
Audric Ackermann 4 years ago
parent 3e4aceabcf
commit 9a380b716b
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -10,7 +10,9 @@ import { AttachmentTypeWithPath } from '../types/Attachment';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { showLightBox } from '../state/ducks/conversations';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { saveAttachmentToDisk } from '../util/attachmentsUtil';
import { getSelectedConversationKey } from '../state/selectors/conversations';
export interface MediaItemType {
objectURL?: string;
@ -25,13 +27,13 @@ export interface MediaItemType {
type Props = {
media: Array<MediaItemType>;
onSave?: (saveData: MediaItemType) => void;
selectedIndex: number;
};
export const LightboxGallery = (props: Props) => {
const { media, onSave } = props;
const { media } = props;
const [currentIndex, setCurrentIndex] = useState(-1);
const selectedConversation = useSelector(getSelectedConversationKey) as string;
const dispatch = useDispatch();
@ -56,12 +58,8 @@ export const LightboxGallery = (props: Props) => {
}, [currentIndex, lastIndex]);
const handleSave = useCallback(() => {
if (!onSave) {
return;
}
const mediaItem = media[currentIndex];
onSave(mediaItem);
void saveAttachmentToDisk({ ...mediaItem, conversationId: selectedConversation });
}, [currentIndex, media]);
useKey(
@ -96,14 +94,13 @@ export const LightboxGallery = (props: Props) => {
const objectURL = selectedMedia?.objectURL || 'images/alert-outline.svg';
const { attachment } = selectedMedia;
const saveCallback = onSave ? handleSave : undefined;
const caption = attachment?.caption;
return (
// tslint:disable: use-simple-attributes
<Lightbox
onPrevious={hasPrevious ? onPrevious : undefined}
onNext={hasNext ? onNext : undefined}
onSave={saveCallback}
onSave={handleSave}
objectURL={objectURL}
caption={caption}
contentType={selectedMedia.contentType}

@ -11,79 +11,70 @@ import {
isVideoAttachment,
} from '../../types/Attachment';
interface Props {
type Props = {
attachments: Array<AttachmentType>;
// onError: () => void;
onClickAttachment: (attachment: AttachmentType) => void;
onCloseAttachment: (attachment: AttachmentType) => void;
onAddAttachment: () => void;
onClose: () => void;
}
};
const IMAGE_WIDTH = 120;
const IMAGE_HEIGHT = 120;
export class AttachmentList extends React.Component<Props> {
// tslint:disable-next-line max-func-body-length */
public render() {
const {
attachments,
onAddAttachment,
onClickAttachment,
onCloseAttachment,
onClose,
} = this.props;
export const AttachmentList = (props: Props) => {
const { attachments, onAddAttachment, onClickAttachment, onCloseAttachment, onClose } = props;
if (!attachments.length) {
return null;
}
const allVisualAttachments = areAllAttachmentsVisual(attachments);
return (
<div className="module-attachments">
{attachments.length > 1 ? (
<div className="module-attachments__header">
<div role="button" onClick={onClose} className="module-attachments__close-button" />
</div>
) : null}
<div className="module-attachments__rail">
{(attachments || []).map((attachment, index) => {
const { contentType } = attachment;
if (isImageTypeSupported(contentType) || isVideoTypeSupported(contentType)) {
const imageKey = getUrl(attachment) || attachment.fileName || index;
const clickCallback = attachments.length > 1 ? onClickAttachment : undefined;
if (!attachments.length) {
return null;
}
return (
<Image
key={imageKey}
alt={window.i18n('stagedImageAttachment', [attachment.fileName])}
attachment={attachment}
softCorners={true}
playIconOverlay={isVideoAttachment(attachment)}
height={IMAGE_HEIGHT}
width={IMAGE_WIDTH}
url={getUrl(attachment)}
closeButton={true}
onClick={clickCallback}
onClickClose={onCloseAttachment}
/>
);
}
const allVisualAttachments = areAllAttachmentsVisual(attachments);
const genericKey = getUrl(attachment) || attachment.fileName || index;
return (
<div className="module-attachments">
{attachments.length > 1 ? (
<div className="module-attachments__header">
<div role="button" onClick={onClose} className="module-attachments__close-button" />
</div>
) : null}
<div className="module-attachments__rail">
{(attachments || []).map((attachment, index) => {
const { contentType } = attachment;
if (isImageTypeSupported(contentType) || isVideoTypeSupported(contentType)) {
const imageKey = getUrl(attachment) || attachment.fileName || index;
const clickCallback = attachments.length > 1 ? onClickAttachment : undefined;
return (
<StagedGenericAttachment
key={genericKey}
<Image
key={imageKey}
alt={window.i18n('stagedImageAttachment', [attachment.fileName])}
attachment={attachment}
onClose={onCloseAttachment}
softCorners={true}
playIconOverlay={isVideoAttachment(attachment)}
height={IMAGE_HEIGHT}
width={IMAGE_WIDTH}
url={getUrl(attachment)}
closeButton={true}
onClick={clickCallback}
onClickClose={onCloseAttachment}
/>
);
})}
{allVisualAttachments ? <StagedPlaceholderAttachment onClick={onAddAttachment} /> : null}
</div>
}
const genericKey = getUrl(attachment) || attachment.fileName || index;
return (
<StagedGenericAttachment
key={genericKey}
attachment={attachment}
onClose={onCloseAttachment}
/>
);
})}
{allVisualAttachments ? <StagedPlaceholderAttachment onClick={onAddAttachment} /> : null}
</div>
);
}
}
</div>
);
};

@ -2,12 +2,12 @@ import React from 'react';
import classNames from 'classnames';
import { Spinner } from '../basic/Spinner';
import { AttachmentType } from '../../types/Attachment';
import { AttachmentTypeWithPath } from '../../types/Attachment';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
type Props = {
alt: string;
attachment: AttachmentType;
attachment: AttachmentTypeWithPath;
url: string;
height?: number;
@ -28,8 +28,8 @@ type Props = {
playIconOverlay?: boolean;
softCorners?: boolean;
onClick?: (attachment: AttachmentType) => void;
onClickClose?: (attachment: AttachmentType) => void;
onClick?: (attachment: AttachmentTypeWithPath) => void;
onClickClose?: (attachment: AttachmentTypeWithPath) => void;
onError?: () => void;
};

@ -3,7 +3,7 @@ import classNames from 'classnames';
import {
areAllAttachmentsVisual,
AttachmentType,
AttachmentTypeWithPath,
getAlt,
getImageDimensions,
getThumbnailUrl,
@ -14,13 +14,13 @@ import {
import { Image } from './Image';
type Props = {
attachments: Array<AttachmentType>;
attachments: Array<AttachmentTypeWithPath>;
withContentAbove?: boolean;
withContentBelow?: boolean;
bottomOverlay?: boolean;
onError: () => void;
onClickAttachment?: (attachment: AttachmentType) => void;
onClickAttachment?: (attachment: AttachmentTypeWithPath) => void;
};
export const ImageGrid = (props: Props) => {

@ -10,6 +10,7 @@ import { ContactName } from './ContactName';
import { Quote } from './Quote';
import {
AttachmentTypeWithPath,
canDisplayImage,
getExtensionForDisplay,
getGridDimensions,
@ -43,11 +44,18 @@ import { AudioPlayerWithEncryptedFile } from './H5AudioPlayer';
import { ClickToTrustSender } from './message/ClickToTrustSender';
import { getMessageById } from '../../data/data';
import { deleteMessagesById } from '../../interactions/conversationInteractions';
import { getSelectedMessage } from '../../state/selectors/search';
import { connect } from 'react-redux';
import { StateType } from '../../state/reducer';
import { getSelectedMessageIds } from '../../state/selectors/conversations';
import { showMessageDetailsView, toggleSelectedMessageId } from '../../state/ducks/conversations';
import {
PropsForAttachment,
PropsForMessage,
showLightBox,
showMessageDetailsView,
toggleSelectedMessageId,
} from '../../state/ducks/conversations';
import { saveAttachmentToDisk } from '../../util/attachmentsUtil';
import { LightBoxOptions } from '../session/conversation/SessionConversation';
// Same as MIN_WIDTH in ImageGrid.tsx
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
@ -63,6 +71,41 @@ const EXPIRED_DELAY = 600;
type Props = MessageRegularProps & { selectedMessages: Array<string> };
const onClickAttachment = async (onClickProps: {
attachment: AttachmentTypeWithPath;
messageId: string;
}) => {
let index = -1;
const found = await getMessageById(onClickProps.messageId);
if (!found) {
window.log.warn('Such message not found');
return;
}
const msgAttachments = found.getPropsForMessage().attachments;
const media = (msgAttachments || []).map(attachmentForMedia => {
index++;
const messageTimestamp =
found.get('timestamp') || found.get('serverTimestamp') || found.get('received_at');
return {
index: _.clone(index),
objectURL: attachmentForMedia.url || undefined,
contentType: attachmentForMedia.contentType,
attachment: attachmentForMedia,
messageSender: found.getSource(),
messageTimestamp,
messageId: onClickProps.messageId,
};
});
const lightBoxOptions: LightBoxOptions = {
media: media as any,
attachment: onClickProps.attachment,
};
window.inboxStore?.dispatch(showLightBox(lightBoxOptions));
};
class MessageInner extends React.PureComponent<Props, State> {
public expirationCheckInterval: any;
public expiredTimeout: any;
@ -150,7 +193,6 @@ class MessageInner extends React.PureComponent<Props, State> {
conversationType,
direction,
quote,
onClickAttachment,
multiSelectMode,
isTrustedForAttachmentDownload,
} = this.props;
@ -191,11 +233,14 @@ class MessageInner extends React.PureComponent<Props, State> {
withContentBelow={withContentBelow}
bottomOverlay={!collapseMetadata}
onError={this.handleImageError}
onClickAttachment={(attachment: AttachmentType) => {
onClickAttachment={(attachment: AttachmentTypeWithPath) => {
if (multiSelectMode) {
window.inboxStore?.dispatch(toggleSelectedMessageId(id));
} else if (onClickAttachment) {
onClickAttachment(attachment);
} else {
void onClickAttachment({
attachment,
messageId: id,
});
}
}}
/>
@ -241,10 +286,15 @@ class MessageInner extends React.PureComponent<Props, State> {
role="button"
className="module-message__generic-attachment__icon"
onClick={(e: any) => {
if (this.props?.onDownload) {
e.stopPropagation();
this.props.onDownload(firstAttachment);
}
e.stopPropagation();
const messageTimestamp = this.props.timestamp || this.props.serverTimestamp || 0;
void saveAttachmentToDisk({
attachment: firstAttachment,
messageTimestamp,
messageSender: this.props.authorPhoneNumber,
conversationId: this.props.convoId,
});
}}
>
{extension ? (
@ -520,7 +570,6 @@ class MessageInner extends React.PureComponent<Props, State> {
status,
isDeletable,
id,
onDownload,
isPublic,
isOpenGroupV2,
weAreAdmin,
@ -567,9 +616,14 @@ class MessageInner extends React.PureComponent<Props, State> {
{!multipleAttachments && attachments && attachments[0] ? (
<Item
onClick={(e: any) => {
if (onDownload) {
onDownload(attachments[0]);
}
e.stopPropagation();
const messageTimestamp = this.props.timestamp || this.props.serverTimestamp || 0;
void saveAttachmentToDisk({
attachment: attachments[0],
messageTimestamp,
messageSender: this.props.authorPhoneNumber,
conversationId: this.props.convoId,
});
}}
>
{window.i18n('downloadAttachment')}

@ -10,6 +10,7 @@ import { AttachmentTypeWithPath, save } from '../../../types/Attachment';
import { MediaItemType } from '../../LightboxGallery';
import { useSelector } from 'react-redux';
import { getSelectedConversationKey } from '../../../state/selectors/conversations';
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
type Props = {
// Required
@ -22,27 +23,6 @@ type Props = {
mediaItem: MediaItemType;
};
const saveAttachment = async ({
attachment,
messageTimestamp,
messageSender,
conversationId,
}: {
attachment: AttachmentTypeWithPath;
messageTimestamp: number;
messageSender: string;
conversationId: string;
}) => {
attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType);
save({
attachment,
document,
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
timestamp: messageTimestamp,
});
await sendDataExtractionNotification(conversationId, messageSender, messageTimestamp);
};
export const DocumentListItem = (props: Props) => {
const { shouldShowSeparator, fileName, fileSize, timestamp } = props;
@ -50,7 +30,7 @@ export const DocumentListItem = (props: Props) => {
const selectedConversationKey = useSelector(getSelectedConversationKey) as string;
const saveAttachmentCallback = useCallback(() => {
void saveAttachment({
void saveAttachmentToDisk({
messageSender: props.mediaItem.messageSender,
messageTimestamp: props.mediaItem.messageTimestamp,
attachment: props.mediaItem.attachment,

@ -84,7 +84,6 @@ interface Props {
quotedMessageProps?: ReplyingToMessageProps;
removeQuotedMessage: () => void;
textarea: React.RefObject<HTMLDivElement>;
stagedAttachments: Array<StagedAttachmentType>;
clearAttachments: () => any;
removeAttachment: (toRemove: AttachmentType) => void;
@ -149,7 +148,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
super(props);
this.state = getDefaultState();
this.textarea = props.textarea;
this.textarea = React.createRef();
this.fileInput = React.createRef();
// Emojis

@ -70,7 +70,6 @@ interface Props {
}
export class SessionConversation extends React.Component<Props, State> {
private readonly compositionBoxRef: React.RefObject<HTMLDivElement>;
private readonly messageContainerRef: React.RefObject<HTMLDivElement>;
private dragCounter: number;
private publicMembersRefreshTimeout?: NodeJS.Timeout;
@ -87,7 +86,6 @@ export class SessionConversation extends React.Component<Props, State> {
stagedAttachments: [],
isDraggingFile: false,
};
this.compositionBoxRef = React.createRef();
this.messageContainerRef = React.createRef();
this.dragCounter = 0;
this.updateMemberList = _.debounce(this.updateMemberListBouncy.bind(this), 1000);
@ -255,7 +253,6 @@ export class SessionConversation extends React.Component<Props, State> {
removeQuotedMessage={() => {
void this.replyToMessage(undefined);
}}
textarea={this.compositionBoxRef}
clearAttachments={this.clearAttachments}
removeAttachment={this.removeAttachment}
onChoseAttachments={this.onChoseAttachments}
@ -296,14 +293,9 @@ export class SessionConversation extends React.Component<Props, State> {
}
public getMessagesListProps(): SessionMessageListProps {
const { messagesProps } = this.props;
return {
messagesProps,
messageContainerRef: this.messageContainerRef,
replyToMessage: this.replyToMessage,
onClickAttachment: this.onClickAttachment,
onDownloadAttachment: this.saveAttachment,
};
}
@ -350,36 +342,10 @@ export class SessionConversation extends React.Component<Props, State> {
}
}
}
this.setState({ quotedMessageTimestamp, quotedMessageProps }, () => {
this.compositionBoxRef.current?.focus();
});
this.setState({ quotedMessageTimestamp, quotedMessageProps });
}
}
private onClickAttachment(attachment: AttachmentTypeWithPath, propsForMessage: PropsForMessage) {
let index = -1;
const media = (propsForMessage.attachments || []).map(attachmentForMedia => {
index++;
const messageTimestamp =
propsForMessage.timestamp || propsForMessage.serverTimestamp || propsForMessage.receivedAt;
return {
index: _.clone(index),
objectURL: attachmentForMedia.url || undefined,
contentType: attachmentForMedia.contentType,
attachment: attachmentForMedia,
messageSender: propsForMessage.authorPhoneNumber,
messageTimestamp,
messageId: propsForMessage.id,
};
});
const lightBoxOptions: LightBoxOptions = {
media: media as any,
attachment,
};
window.inboxStore?.dispatch(showLightBox(lightBoxOptions));
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~ KEYBOARD NAVIGATION ~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -470,35 +436,7 @@ export class SessionConversation extends React.Component<Props, State> {
? media.findIndex(mediaMessage => mediaMessage.attachment.path === attachment.path)
: 0;
console.warn('renderLightBox', { media, attachment });
return (
<LightboxGallery media={media} selectedIndex={selectedIndex} onSave={this.saveAttachment} />
);
}
// THIS DOES NOT DOWNLOAD ANYTHING! it just saves it where the user wants
private async saveAttachment({
attachment,
messageTimestamp,
messageSender,
}: {
attachment: AttachmentType;
messageTimestamp: number;
messageSender: string;
}) {
const { getAbsoluteAttachmentPath } = window.Signal.Migrations;
attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType);
save({
attachment,
document,
getAbsolutePath: getAbsoluteAttachmentPath,
timestamp: messageTimestamp,
});
await sendDataExtractionNotification(
this.props.selectedConversationKey,
messageSender,
messageTimestamp
);
return <LightboxGallery media={media} selectedIndex={selectedIndex} />;
}
private async onChoseAttachments(attachmentsFileList: Array<File>) {

@ -7,11 +7,15 @@ import { SessionScrollButton } from '../SessionScrollButton';
import { Constants } from '../../../session';
import _ from 'lodash';
import { contextMenu } from 'react-contexify';
import { AttachmentType } from '../../../types/Attachment';
import { AttachmentType, AttachmentTypeWithPath } from '../../../types/Attachment';
import { GroupNotification } from '../../conversation/GroupNotification';
import { GroupInvitation } from '../../conversation/GroupInvitation';
import {
fetchMessagesForConversation,
PropsForExpirationTimer,
PropsForGroupInvitation,
PropsForGroupUpdate,
PropsForMessage,
ReduxConversationType,
SortedMessageModelProps,
} from '../../../state/ducks/conversations';
@ -20,18 +24,25 @@ import { ToastUtils } from '../../../session/utils';
import { TypingBubble } from '../../conversation/TypingBubble';
import { getConversationController } from '../../../session/conversations';
import { MessageModel } from '../../../models/message';
import { MessageRegularProps, QuoteClickOptions } from '../../../models/messageType';
import {
MessageRegularProps,
PropsForDataExtractionNotification,
QuoteClickOptions,
} from '../../../models/messageType';
import { getMessagesBySentAt } from '../../../data/data';
import autoBind from 'auto-bind';
import { ConversationTypeEnum } from '../../../models/conversation';
import { DataExtractionNotification } from '../../conversation/DataExtractionNotification';
import { StateType } from '../../../state/reducer';
import { connect } from 'react-redux';
import { connect, useSelector } from 'react-redux';
import {
getMessagesOfSelectedConversation,
getSelectedConversation,
getSelectedConversationKey,
getSelectedMessageIds,
isMessageSelectionMode,
} from '../../../state/selectors/conversations';
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
interface State {
showScrollButton: boolean;
@ -39,24 +50,125 @@ interface State {
nextMessageToPlay: number | undefined;
}
export type SessionMessageListProps = {
messageContainerRef: React.RefObject<any>;
replyToMessage: (messageId: number) => Promise<void>;
};
type Props = SessionMessageListProps & {
conversationKey?: string;
selectedMessages: Array<string>;
messagesProps: Array<SortedMessageModelProps>;
conversation?: ReduxConversationType;
};
export type SessionMessageListProps = {
messagesProps: Array<SortedMessageModelProps>;
messageContainerRef: React.RefObject<any>;
const UnreadIndicator = (props: { messageId: string; show: boolean }) => (
<SessionLastSeenIndicator show={props.show} key={`unread-indicator-${props.messageId}`} />
);
const GroupUpdateItem = (props: {
messageId: string;
groupNotificationProps: PropsForGroupUpdate;
showUnreadIndicator: boolean;
}) => {
return (
<React.Fragment key={props.messageId}>
<GroupNotification key={props.messageId} {...props.groupNotificationProps} />
<UnreadIndicator messageId={props.messageId} show={props.showUnreadIndicator} />
</React.Fragment>
);
};
replyToMessage: (messageId: number) => Promise<void>;
onClickAttachment: (attachment: any, message: any) => void;
onDownloadAttachment: (toDownload: {
attachment: any;
messageTimestamp: number;
messageSender: string;
}) => void;
const GroupInvitationItem = (props: {
messageId: string;
propsForGroupInvitation: PropsForGroupInvitation;
showUnreadIndicator: boolean;
}) => {
return (
<React.Fragment key={props.messageId}>
<GroupInvitation key={props.messageId} {...props.propsForGroupInvitation} />
<UnreadIndicator messageId={props.messageId} show={props.showUnreadIndicator} />
</React.Fragment>
);
};
const DataExtractionNotificationItem = (props: {
messageId: string;
propsForDataExtractionNotification: PropsForDataExtractionNotification;
showUnreadIndicator: boolean;
}) => {
return (
<React.Fragment key={props.messageId}>
<DataExtractionNotification
key={props.messageId}
{...props.propsForDataExtractionNotification}
/>
<UnreadIndicator messageId={props.messageId} show={props.showUnreadIndicator} />
</React.Fragment>
);
};
const TimerNotificationItem = (props: {
messageId: string;
timerProps: PropsForExpirationTimer;
showUnreadIndicator: boolean;
}) => {
return (
<React.Fragment key={props.messageId}>
<TimerNotification key={props.messageId} {...props.timerProps} />
<UnreadIndicator messageId={props.messageId} show={props.showUnreadIndicator} />
</React.Fragment>
);
};
const GenericMessageItem = (props: {
messageId: string;
messageProps: SortedMessageModelProps;
playableMessageIndex?: number;
showUnreadIndicator: boolean;
}) => {
const multiSelectMode = useSelector(isMessageSelectionMode);
// const selectedConversation = useSelector(getSelectedConversationKey) as string;
const messageId = props.messageId;
console.warn('FIXME audric');
if (!props.messageProps) {
debugger;
}
// const onQuoteClick = props.messageProps.propsForMessage.quote
// ? this.scrollToQuoteMessage
// : async () => {};
const regularProps: MessageRegularProps = {
...props.messageProps.propsForMessage,
// firstMessageOfSeries,
multiSelectMode,
// isQuotedMessageToAnimate: messageId === this.state.animateQuotedMessageId,
// nextMessageToPlay: this.state.nextMessageToPlay,
onReply: props.replyToMessage,
// playNextMessage: this.playNextMessage,
// onQuoteClick,
};
return (
<React.Fragment key={props.messageId}>
<Message
{...regularProps}
playableMessageIndex={props.playableMessageIndex}
multiSelectMode={multiSelectMode}
// onQuoteClick={onQuoteClick}
key={messageId}
/>
<UnreadIndicator messageId={props.messageId} show={props.showUnreadIndicator} />
</React.Fragment>
);
};
class SessionMessagesListInner extends React.Component<Props, State> {
@ -171,8 +283,7 @@ class SessionMessagesListInner extends React.Component<Props, State> {
}
private renderMessages() {
const { selectedMessages, messagesProps } = this.props;
const multiSelectMode = Boolean(selectedMessages.length);
const { messagesProps } = this.props;
let playableMessageIndex = 0;
return (
@ -185,62 +296,56 @@ class SessionMessagesListInner extends React.Component<Props, State> {
const groupNotificationProps = messageProps.propsForGroupNotification;
// IF we found the last read message
// IF we found the first unread message
// AND we are not scrolled all the way to the bottom
// THEN, show the unread banner for the current message
const showUnreadIndicator =
Boolean(messageProps.firstUnread) && this.getScrollOffsetBottomPx() !== 0;
const unreadIndicator = (
<SessionLastSeenIndicator
show={showUnreadIndicator}
key={`unread-indicator-${messageProps.propsForMessage.id}`}
/>
);
if (groupNotificationProps) {
return (
<React.Fragment>
<GroupNotification
key={messageProps.propsForMessage.id}
{...groupNotificationProps}
/>
{unreadIndicator}
</React.Fragment>
<GroupUpdateItem
key={messageProps.propsForMessage.id}
groupNotificationProps={groupNotificationProps}
messageId={messageProps.propsForMessage.id}
showUnreadIndicator={showUnreadIndicator}
/>
);
}
if (propsForGroupInvitation) {
return (
<React.Fragment key={messageProps.propsForMessage.id}>
<GroupInvitation
{...propsForGroupInvitation}
key={messageProps.propsForMessage.id}
/>
{unreadIndicator}
</React.Fragment>
<GroupInvitationItem
key={messageProps.propsForMessage.id}
propsForGroupInvitation={propsForGroupInvitation}
messageId={messageProps.propsForMessage.id}
showUnreadIndicator={showUnreadIndicator}
/>
);
}
if (propsForDataExtractionNotification) {
return (
<React.Fragment key={messageProps.propsForMessage.id}>
<DataExtractionNotification
{...propsForDataExtractionNotification}
key={messageProps.propsForMessage.id}
/>
{unreadIndicator}
</React.Fragment>
<DataExtractionNotificationItem
key={messageProps.propsForMessage.id}
propsForDataExtractionNotification={propsForDataExtractionNotification}
messageId={messageProps.propsForMessage.id}
showUnreadIndicator={showUnreadIndicator}
/>
);
}
if (timerProps) {
return (
<React.Fragment key={messageProps.propsForMessage.id}>
<TimerNotification {...timerProps} key={messageProps.propsForMessage.id} />
{unreadIndicator}
</React.Fragment>
<TimerNotificationItem
key={messageProps.propsForMessage.id}
timerProps={timerProps}
messageId={messageProps.propsForMessage.id}
showUnreadIndicator={showUnreadIndicator}
/>
);
}
if (!messageProps) {
return;
}
@ -250,68 +355,19 @@ class SessionMessagesListInner extends React.Component<Props, State> {
// firstMessageOfSeries tells us to render the avatar only for the first message
// in a series of messages from the same user
return (
<React.Fragment key={messageProps.propsForMessage.id}>
{this.renderMessage(
messageProps,
messageProps.firstMessageOfSeries,
multiSelectMode,
playableMessageIndex
)}
{unreadIndicator}
</React.Fragment>
<GenericMessageItem
key={messageProps.propsForMessage.id}
playableMessageIndex={playableMessageIndex}
messageId={messageProps.propsForMessage.id}
messageProps={messageProps}
showUnreadIndicator={showUnreadIndicator}
/>
);
})}
</>
);
}
private renderMessage(
messageProps: SortedMessageModelProps,
firstMessageOfSeries: boolean,
multiSelectMode: boolean,
playableMessageIndex: number
) {
const messageId = messageProps.propsForMessage.id;
const onClickAttachment = (attachment: AttachmentType) => {
this.props.onClickAttachment(attachment, messageProps.propsForMessage);
};
// tslint:disable-next-line: no-async-without-await
const onQuoteClick = messageProps.propsForMessage.quote
? this.scrollToQuoteMessage
: async () => {};
const onDownload = (attachment: AttachmentType) => {
const messageTimestamp =
messageProps.propsForMessage.timestamp ||
messageProps.propsForMessage.serverTimestamp ||
messageProps.propsForMessage.receivedAt ||
0;
this.props.onDownloadAttachment({
attachment,
messageTimestamp,
messageSender: messageProps.propsForMessage.authorPhoneNumber,
});
};
const regularProps: MessageRegularProps = {
...messageProps.propsForMessage,
firstMessageOfSeries,
multiSelectMode,
isQuotedMessageToAnimate: messageId === this.state.animateQuotedMessageId,
nextMessageToPlay: this.state.nextMessageToPlay,
playableMessageIndex,
onReply: this.props.replyToMessage,
onClickAttachment,
onDownload,
playNextMessage: this.playNextMessage,
onQuoteClick,
};
return <Message {...regularProps} onQuoteClick={onQuoteClick} key={messageId} />;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~ MESSAGE HANDLING ~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -586,9 +642,9 @@ class SessionMessagesListInner extends React.Component<Props, State> {
const mapStateToProps = (state: StateType) => {
return {
selectedMessages: getSelectedMessageIds(state),
conversationKey: getSelectedConversationKey(state),
conversation: getSelectedConversation(state),
messagesProps: getMessagesOfSelectedConversation(state),
};
};

@ -774,9 +774,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
conversationType: ConversationTypeEnum.PRIVATE,
multiSelectMode: false,
firstMessageOfSeries: false,
onClickAttachment: noop,
onReply: noop,
onDownload: noop,
// tslint:disable-next-line: no-async-without-await no-empty
onQuoteClick: async () => {},
},

@ -2,7 +2,7 @@ import { DefaultTheme } from 'styled-components';
import _ from 'underscore';
import { v4 as uuidv4 } from 'uuid';
import { QuotedAttachmentType } from '../components/conversation/Quote';
import { AttachmentType } from '../types/Attachment';
import { AttachmentType, AttachmentTypeWithPath } from '../types/Attachment';
import { Contact } from '../types/Contact';
import { ConversationTypeEnum } from './conversation';
@ -219,7 +219,7 @@ export interface MessageRegularProps {
/** Note: this should be formatted for display */
authorPhoneNumber: string;
conversationType: ConversationTypeEnum;
attachments?: Array<AttachmentType>;
attachments?: Array<AttachmentTypeWithPath>;
quote?: {
text: string;
attachment?: QuotedAttachmentType;
@ -246,9 +246,7 @@ export interface MessageRegularProps {
isQuotedMessageToAnimate?: boolean;
isTrustedForAttachmentDownload: boolean;
onClickAttachment: (attachment: AttachmentType) => void;
onReply: (messagId: number) => void;
onDownload: (attachment: AttachmentType) => void;
onQuoteClick: (options: QuoteClickOptions) => Promise<void>;
playableMessageIndex?: number;

@ -2,6 +2,9 @@ import { StagedAttachmentType } from '../components/session/conversation/Session
import { SignalService } from '../protobuf';
import { Constants } from '../session';
import loadImage from 'blueimp-load-image';
import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsManager';
import { sendDataExtractionNotification } from '../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage';
import { AttachmentType, AttachmentTypeWithPath, save } from '../types/Attachment';
export interface MaxScaleSize {
maxSize?: number;
maxHeight?: number;
@ -135,3 +138,24 @@ export async function readFile(attachment: any): Promise<AttachmentFileType> {
FR.readAsArrayBuffer(attachment.file);
});
}
export const saveAttachmentToDisk = async ({
attachment,
messageTimestamp,
messageSender,
conversationId,
}: {
attachment: AttachmentType;
messageTimestamp: number;
messageSender: string;
conversationId: string;
}) => {
const decryptedUrl = await getDecryptedMediaUrl(attachment.url, attachment.contentType);
save({
attachment: { ...attachment, url: decryptedUrl },
document,
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
timestamp: messageTimestamp,
});
await sendDataExtractionNotification(conversationId, messageSender, messageTimestamp);
};

Loading…
Cancel
Save