Allow stage and send of video, even if we can't get screenshot

This commit is contained in:
Scott Nonnenberg 2021-11-15 13:54:33 -08:00 committed by GitHub
parent 117cb074c7
commit a024ee4b96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 224 additions and 143 deletions

View file

@ -13,7 +13,7 @@ import { CompositionArea } from './CompositionArea';
import { setupI18n } from '../util/setupI18n'; import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json'; import enMessages from '../../_locales/en/messages.json';
import { fakeAttachment } from '../test-both/helpers/fakeAttachment'; import { fakeDraftAttachment } from '../test-both/helpers/fakeAttachment';
import { landscapeGreenUrl } from '../storybook/Fixtures'; import { landscapeGreenUrl } from '../storybook/Fixtures';
import { ThemeType } from '../types/Util'; import { ThemeType } from '../types/Util';
import { RecordingState } from '../state/ducks/audioRecorder'; import { RecordingState } from '../state/ducks/audioRecorder';
@ -165,7 +165,7 @@ story.add('SMS-only', () => {
story.add('Attachments', () => { story.add('Attachments', () => {
const props = createProps({ const props = createProps({
draftAttachments: [ draftAttachments: [
fakeAttachment({ fakeDraftAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: landscapeGreenUrl, url: landscapeGreenUrl,
}), }),

View file

@ -34,7 +34,10 @@ import type { PropsType as GroupV2PendingApprovalActionsPropsType } from './conv
import { GroupV2PendingApprovalActions } from './conversation/GroupV2PendingApprovalActions'; import { GroupV2PendingApprovalActions } from './conversation/GroupV2PendingApprovalActions';
import { AnnouncementsOnlyGroupBanner } from './AnnouncementsOnlyGroupBanner'; import { AnnouncementsOnlyGroupBanner } from './AnnouncementsOnlyGroupBanner';
import { AttachmentList } from './conversation/AttachmentList'; import { AttachmentList } from './conversation/AttachmentList';
import type { AttachmentType } from '../types/Attachment'; import type {
AttachmentDraftType,
InMemoryAttachmentDraftType,
} from '../types/Attachment';
import { isImageAttachment } from '../types/Attachment'; import { isImageAttachment } from '../types/Attachment';
import { AudioCapture } from './conversation/AudioCapture'; import { AudioCapture } from './conversation/AudioCapture';
import { CompositionUpload } from './CompositionUpload'; import { CompositionUpload } from './CompositionUpload';
@ -67,11 +70,11 @@ export type OwnProps = Readonly<{
acceptedMessageRequest?: boolean; acceptedMessageRequest?: boolean;
addAttachment: ( addAttachment: (
conversationId: string, conversationId: string,
attachment: AttachmentType attachment: InMemoryAttachmentDraftType
) => unknown; ) => unknown;
addPendingAttachment: ( addPendingAttachment: (
conversationId: string, conversationId: string,
pendingAttachment: AttachmentType pendingAttachment: AttachmentDraftType
) => unknown; ) => unknown;
announcementsOnly?: boolean; announcementsOnly?: boolean;
areWeAdmin?: boolean; areWeAdmin?: boolean;
@ -80,11 +83,11 @@ export type OwnProps = Readonly<{
cancelRecording: () => unknown; cancelRecording: () => unknown;
completeRecording: ( completeRecording: (
conversationId: string, conversationId: string,
onSendAudioRecording?: (rec: AttachmentType) => unknown onSendAudioRecording?: (rec: InMemoryAttachmentDraftType) => unknown
) => unknown; ) => unknown;
compositionApi?: MutableRefObject<CompositionAPIType>; compositionApi?: MutableRefObject<CompositionAPIType>;
conversationId: string; conversationId: string;
draftAttachments: ReadonlyArray<AttachmentType>; draftAttachments: ReadonlyArray<AttachmentDraftType>;
errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType; errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType;
errorRecording: (e: ErrorDialogAudioRecorderType) => unknown; errorRecording: (e: ErrorDialogAudioRecorderType) => unknown;
groupAdmins: Array<ConversationType>; groupAdmins: Array<ConversationType>;
@ -105,11 +108,11 @@ export type OwnProps = Readonly<{
processAttachments: (options: HandleAttachmentsProcessingArgsType) => unknown; processAttachments: (options: HandleAttachmentsProcessingArgsType) => unknown;
onSelectMediaQuality(isHQ: boolean): unknown; onSelectMediaQuality(isHQ: boolean): unknown;
onSendMessage(options: { onSendMessage(options: {
draftAttachments?: ReadonlyArray<AttachmentType>; draftAttachments?: ReadonlyArray<AttachmentDraftType>;
mentions?: BodyRangesType; mentions?: BodyRangesType;
message?: string; message?: string;
timestamp?: number; timestamp?: number;
voiceNoteAttachment?: AttachmentType; voiceNoteAttachment?: InMemoryAttachmentDraftType;
}): unknown; }): unknown;
openConversation(conversationId: string): unknown; openConversation(conversationId: string): unknown;
quotedMessageProps?: Omit< quotedMessageProps?: Omit<
@ -373,7 +376,9 @@ export const CompositionArea = ({
errorRecording={errorRecording} errorRecording={errorRecording}
i18n={i18n} i18n={i18n}
recordingState={recordingState} recordingState={recordingState}
onSendAudioRecording={(voiceNoteAttachment: AttachmentType) => { onSendAudioRecording={(
voiceNoteAttachment: InMemoryAttachmentDraftType
) => {
onSendMessage({ voiceNoteAttachment }); onSendMessage({ voiceNoteAttachment });
}} }}
startRecording={startRecording} startRecording={startRecording}

View file

@ -4,7 +4,10 @@
import type { ChangeEventHandler } from 'react'; import type { ChangeEventHandler } from 'react';
import React, { forwardRef, useState } from 'react'; import React, { forwardRef, useState } from 'react';
import type { AttachmentType } from '../types/Attachment'; import type {
InMemoryAttachmentDraftType,
AttachmentDraftType,
} from '../types/Attachment';
import { AttachmentToastType } from '../types/AttachmentToastType'; import { AttachmentToastType } from '../types/AttachmentToastType';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType } from '../types/Util';
@ -19,14 +22,14 @@ import type { HandleAttachmentsProcessingArgsType } from '../util/handleAttachme
export type PropsType = { export type PropsType = {
addAttachment: ( addAttachment: (
conversationId: string, conversationId: string,
attachment: AttachmentType attachment: InMemoryAttachmentDraftType
) => unknown; ) => unknown;
addPendingAttachment: ( addPendingAttachment: (
conversationId: string, conversationId: string,
pendingAttachment: AttachmentType pendingAttachment: AttachmentDraftType
) => unknown; ) => unknown;
conversationId: string; conversationId: string;
draftAttachments: ReadonlyArray<AttachmentType>; draftAttachments: ReadonlyArray<AttachmentDraftType>;
i18n: LocalizerType; i18n: LocalizerType;
processAttachments: (options: HandleAttachmentsProcessingArgsType) => unknown; processAttachments: (options: HandleAttachmentsProcessingArgsType) => unknown;
removeAttachment: (conversationId: string, filePath: string) => unknown; removeAttachment: (conversationId: string, filePath: string) => unknown;

View file

@ -8,7 +8,7 @@ import { action } from '@storybook/addon-actions';
import { text } from '@storybook/addon-knobs'; import { text } from '@storybook/addon-knobs';
import enMessages from '../../_locales/en/messages.json'; import enMessages from '../../_locales/en/messages.json';
import type { AttachmentType } from '../types/Attachment'; import type { AttachmentDraftType } from '../types/Attachment';
import type { PropsType } from './ForwardMessageModal'; import type { PropsType } from './ForwardMessageModal';
import { ForwardMessageModal } from './ForwardMessageModal'; import { ForwardMessageModal } from './ForwardMessageModal';
import { IMAGE_JPEG, VIDEO_MP4, stringToMIMEType } from '../types/MIME'; import { IMAGE_JPEG, VIDEO_MP4, stringToMIMEType } from '../types/MIME';
@ -16,15 +16,17 @@ import { getDefaultConversation } from '../test-both/helpers/getDefaultConversat
import { setupI18n } from '../util/setupI18n'; import { setupI18n } from '../util/setupI18n';
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext'; import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
const createAttachment = ( const createDraftAttachment = (
props: Partial<AttachmentType> = {} props: Partial<AttachmentDraftType> = {}
): AttachmentType => ({ ): AttachmentDraftType => ({
pending: false,
path: 'fileName.jpg',
contentType: stringToMIMEType( contentType: stringToMIMEType(
text('attachment contentType', props.contentType || '') text('attachment contentType', props.contentType || '')
), ),
fileName: text('attachment fileName', props.fileName || ''), fileName: text('attachment fileName', props.fileName || ''),
screenshot: props.screenshot, screenshotPath: props.pending === false ? props.screenshotPath : undefined,
url: text('attachment url', props.url || ''), url: text('attachment url', props.pending === false ? props.url || '' : ''),
size: 3433, size: 3433,
}); });
@ -81,7 +83,7 @@ story.add('link preview', () => {
date: Date.now(), date: Date.now(),
domain: 'https://www.signal.org', domain: 'https://www.signal.org',
url: 'signal.org', url: 'signal.org',
image: createAttachment({ image: createDraftAttachment({
url: '/fixtures/kitten-4-112-112.jpg', url: '/fixtures/kitten-4-112-112.jpg',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
}), }),
@ -99,22 +101,19 @@ story.add('media attachments', () => {
<ForwardMessageModal <ForwardMessageModal
{...useProps({ {...useProps({
attachments: [ attachments: [
createAttachment({ createDraftAttachment({
pending: true,
}),
createDraftAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
}), }),
createAttachment({ createDraftAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4', fileName: 'pixabay-Soap-Bubble-7141.mp4',
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
screenshot: { screenshotPath: '/fixtures/kitten-4-112-112.jpg',
height: 112,
width: 112,
url: '/fixtures/kitten-4-112-112.jpg',
contentType: IMAGE_JPEG,
path: 'originalPath',
},
}), }),
], ],
messageBody: 'cats', messageBody: 'cats',

View file

@ -16,7 +16,7 @@ import { animated } from '@react-spring/web';
import classNames from 'classnames'; import classNames from 'classnames';
import { AttachmentList } from './conversation/AttachmentList'; import { AttachmentList } from './conversation/AttachmentList';
import type { AttachmentType } from '../types/Attachment'; import type { AttachmentDraftType } from '../types/Attachment';
import { Button } from './Button'; import { Button } from './Button';
import type { InputApi } from './CompositionInput'; import type { InputApi } from './CompositionInput';
import { CompositionInput } from './CompositionInput'; import { CompositionInput } from './CompositionInput';
@ -38,13 +38,13 @@ import { filterAndSortConversationsByRecent } from '../util/filterAndSortConvers
import { useAnimated } from '../hooks/useAnimated'; import { useAnimated } from '../hooks/useAnimated';
export type DataPropsType = { export type DataPropsType = {
attachments?: Array<AttachmentType>; attachments?: Array<AttachmentDraftType>;
candidateConversations: ReadonlyArray<ConversationType>; candidateConversations: ReadonlyArray<ConversationType>;
conversationId: string; conversationId: string;
doForwardMessage: ( doForwardMessage: (
selectedContacts: Array<string>, selectedContacts: Array<string>,
messageBody?: string, messageBody?: string,
attachments?: Array<AttachmentType>, attachments?: Array<AttachmentDraftType>,
linkPreview?: LinkPreviewType linkPreview?: LinkPreviewType
) => void; ) => void;
i18n: LocalizerType; i18n: LocalizerType;
@ -100,7 +100,9 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
const [filteredConversations, setFilteredConversations] = useState( const [filteredConversations, setFilteredConversations] = useState(
filterAndSortConversationsByRecent(candidateConversations, '') filterAndSortConversationsByRecent(candidateConversations, '')
); );
const [attachmentsToForward, setAttachmentsToForward] = useState(attachments); const [attachmentsToForward, setAttachmentsToForward] = useState<
Array<AttachmentDraftType>
>(attachments || []);
const [isEditingMessage, setIsEditingMessage] = useState(false); const [isEditingMessage, setIsEditingMessage] = useState(false);
const [messageBodyText, setMessageBodyText] = useState(messageBody || ''); const [messageBodyText, setMessageBodyText] = useState(messageBody || '');
const [cannotMessage, setCannotMessage] = useState(false); const [cannotMessage, setCannotMessage] = useState(false);
@ -322,7 +324,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
<AttachmentList <AttachmentList
attachments={attachmentsToForward} attachments={attachmentsToForward}
i18n={i18n} i18n={i18n}
onCloseAttachment={(attachment: AttachmentType) => { onCloseAttachment={(attachment: AttachmentDraftType) => {
const newAttachments = attachmentsToForward.filter( const newAttachments = attachmentsToForward.filter(
currentAttachment => currentAttachment !== attachment currentAttachment => currentAttachment !== attachment
); );

View file

@ -18,7 +18,7 @@ import {
import { setupI18n } from '../../util/setupI18n'; import { setupI18n } from '../../util/setupI18n';
import enMessages from '../../../_locales/en/messages.json'; import enMessages from '../../../_locales/en/messages.json';
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment'; import { fakeDraftAttachment } from '../../test-both/helpers/fakeAttachment';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
@ -36,7 +36,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
story.add('One File', () => { story.add('One File', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
fakeAttachment({ fakeDraftAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
@ -49,24 +49,18 @@ story.add('One File', () => {
story.add('Multiple Visual Attachments', () => { story.add('Multiple Visual Attachments', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
fakeAttachment({ fakeDraftAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
}), }),
fakeAttachment({ fakeDraftAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4', fileName: 'pixabay-Soap-Bubble-7141.mp4',
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/kitten-4-112-112.jpg',
screenshot: { screenshotPath: '/fixtures/kitten-4-112-112.jpg',
height: 112,
width: 112,
url: '/fixtures/kitten-4-112-112.jpg',
contentType: IMAGE_JPEG,
path: 'originalpath',
},
}), }),
fakeAttachment({ fakeDraftAttachment({
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
fileName: 'giphy-GVNv0UpeYm17e', fileName: 'giphy-GVNv0UpeYm17e',
url: '/fixtures/giphy-GVNvOUpeYmI7e.gif', url: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
@ -80,34 +74,28 @@ story.add('Multiple Visual Attachments', () => {
story.add('Multiple with Non-Visual Types', () => { story.add('Multiple with Non-Visual Types', () => {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
fakeAttachment({ fakeDraftAttachment({
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'tina-rolf-269345-unsplash.jpg',
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
}), }),
fakeAttachment({ fakeDraftAttachment({
contentType: stringToMIMEType('text/plain'), contentType: stringToMIMEType('text/plain'),
fileName: 'lorem-ipsum.txt', fileName: 'lorem-ipsum.txt',
url: '/fixtures/lorem-ipsum.txt', url: '/fixtures/lorem-ipsum.txt',
}), }),
fakeAttachment({ fakeDraftAttachment({
contentType: AUDIO_MP3, contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3', fileName: 'incompetech-com-Agnus-Dei-X.mp3',
url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3', url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
}), }),
fakeAttachment({ fakeDraftAttachment({
contentType: VIDEO_MP4, contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4', fileName: 'pixabay-Soap-Bubble-7141.mp4',
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', url: '/fixtures/kitten-4-112-112.jpg',
screenshot: { screenshotPath: '/fixtures/kitten-4-112-112.jpg',
height: 112,
width: 112,
url: '/fixtures/kitten-4-112-112.jpg',
contentType: IMAGE_JPEG,
path: 'originalpath',
},
}), }),
fakeAttachment({ fakeDraftAttachment({
contentType: IMAGE_GIF, contentType: IMAGE_GIF,
fileName: 'giphy-GVNv0UpeYm17e', fileName: 'giphy-GVNv0UpeYm17e',
url: '/fixtures/giphy-GVNvOUpeYmI7e.gif', url: '/fixtures/giphy-GVNvOUpeYmI7e.gif',

View file

@ -7,21 +7,20 @@ import { Image } from './Image';
import { StagedGenericAttachment } from './StagedGenericAttachment'; import { StagedGenericAttachment } from './StagedGenericAttachment';
import { StagedPlaceholderAttachment } from './StagedPlaceholderAttachment'; import { StagedPlaceholderAttachment } from './StagedPlaceholderAttachment';
import type { LocalizerType } from '../../types/Util'; import type { LocalizerType } from '../../types/Util';
import type { AttachmentType } from '../../types/Attachment'; import type { AttachmentDraftType } from '../../types/Attachment';
import { import {
areAllAttachmentsVisual, areAllAttachmentsVisual,
getUrl,
isImageAttachment, isImageAttachment,
isVideoAttachment, isVideoAttachment,
} from '../../types/Attachment'; } from '../../types/Attachment';
export type Props = Readonly<{ export type Props = Readonly<{
attachments: ReadonlyArray<AttachmentType>; attachments: ReadonlyArray<AttachmentDraftType>;
i18n: LocalizerType; i18n: LocalizerType;
onAddAttachment?: () => void; onAddAttachment?: () => void;
onClickAttachment?: (attachment: AttachmentType) => void; onClickAttachment?: (attachment: AttachmentDraftType) => void;
onClose?: () => void; onClose?: () => void;
onCloseAttachment: (attachment: AttachmentType) => void; onCloseAttachment: (attachment: AttachmentDraftType) => void;
}>; }>;
const IMAGE_WIDTH = 120; const IMAGE_WIDTH = 120;
@ -31,6 +30,14 @@ const IMAGE_HEIGHT = 120;
const BLANK_VIDEO_THUMBNAIL = const BLANK_VIDEO_THUMBNAIL =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR42mNiAAAABgADm78GJQAAAABJRU5ErkJggg=='; 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR42mNiAAAABgADm78GJQAAAABJRU5ErkJggg==';
function getUrl(attachment: AttachmentDraftType): string | undefined {
if (attachment.pending) {
return undefined;
}
return attachment.url;
}
export const AttachmentList = ({ export const AttachmentList = ({
attachments, attachments,
i18n, i18n,
@ -65,11 +72,17 @@ export const AttachmentList = ({
const isImage = isImageAttachment(attachment); const isImage = isImageAttachment(attachment);
const isVideo = isVideoAttachment(attachment); const isVideo = isVideoAttachment(attachment);
const closeAttachment = () => onCloseAttachment(attachment);
if (isImage || isVideo || attachment.pending) { if (isImage || isVideo || attachment.pending) {
const isDownloaded = !attachment.pending;
const imageUrl = const imageUrl =
url || (isVideo ? BLANK_VIDEO_THUMBNAIL : undefined); url || (isVideo ? BLANK_VIDEO_THUMBNAIL : undefined);
const clickAttachment = onClickAttachment
? () => onClickAttachment(attachment)
: undefined;
return ( return (
<Image <Image
key={key} key={key}
@ -79,17 +92,16 @@ export const AttachmentList = ({
className="module-staged-attachment" className="module-staged-attachment"
i18n={i18n} i18n={i18n}
attachment={attachment} attachment={attachment}
isDownloaded={isDownloaded}
softCorners softCorners
playIconOverlay={isVideo} playIconOverlay={isVideo}
height={IMAGE_HEIGHT} height={IMAGE_HEIGHT}
width={IMAGE_WIDTH} width={IMAGE_WIDTH}
url={imageUrl} url={imageUrl}
closeButton closeButton
onClick={onClickAttachment} onClick={clickAttachment}
onClickClose={onCloseAttachment} onClickClose={closeAttachment}
onError={() => { onError={closeAttachment}
onCloseAttachment(attachment);
}}
/> />
); );
} }
@ -99,7 +111,7 @@ export const AttachmentList = ({
key={key} key={key}
attachment={attachment} attachment={attachment}
i18n={i18n} i18n={i18n}
onClose={onCloseAttachment} onClose={closeAttachment}
/> />
); );
})} })}

View file

@ -5,7 +5,10 @@ import React, { useCallback, useEffect, useState } from 'react';
import * as moment from 'moment'; import * as moment from 'moment';
import { noop } from 'lodash'; import { noop } from 'lodash';
import type { AttachmentType } from '../../types/Attachment'; import type {
AttachmentDraftType,
InMemoryAttachmentDraftType,
} from '../../types/Attachment';
import { ConfirmationDialog } from '../ConfirmationDialog'; import { ConfirmationDialog } from '../ConfirmationDialog';
import type { LocalizerType } from '../../types/Util'; import type { LocalizerType } from '../../types/Util';
import { import {
@ -20,7 +23,7 @@ import {
useKeyboardShortcuts, useKeyboardShortcuts,
} from '../../hooks/useKeyboardShortcuts'; } from '../../hooks/useKeyboardShortcuts';
type OnSendAudioRecordingType = (rec: AttachmentType) => unknown; type OnSendAudioRecordingType = (rec: InMemoryAttachmentDraftType) => unknown;
export type PropsType = { export type PropsType = {
cancelRecording: () => unknown; cancelRecording: () => unknown;
@ -29,7 +32,7 @@ export type PropsType = {
conversationId: string, conversationId: string,
onSendAudioRecording?: OnSendAudioRecordingType onSendAudioRecording?: OnSendAudioRecordingType
) => unknown; ) => unknown;
draftAttachments: ReadonlyArray<AttachmentType>; draftAttachments: ReadonlyArray<AttachmentDraftType>;
errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType; errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType;
errorRecording: (e: ErrorDialogAudioRecorderType) => unknown; errorRecording: (e: ErrorDialogAudioRecorderType) => unknown;
i18n: LocalizerType; i18n: LocalizerType;

View file

@ -15,6 +15,7 @@ export type Props = {
attachment: AttachmentType; attachment: AttachmentType;
url?: string; url?: string;
isDownloaded?: boolean;
className?: string; className?: string;
height?: number; height?: number;
width?: number; width?: number;
@ -145,6 +146,7 @@ export class Image extends React.Component<Props> {
curveTopLeft, curveTopLeft,
curveTopRight, curveTopRight,
darkOverlay, darkOverlay,
isDownloaded,
height = 0, height = 0,
i18n, i18n,
noBackground, noBackground,
@ -165,7 +167,9 @@ export class Image extends React.Component<Props> {
const { caption, pending } = attachment || { caption: null, pending: true }; const { caption, pending } = attachment || { caption: null, pending: true };
const canClick = this.canClick(); const canClick = this.canClick();
const imgNotDownloaded = hasNotDownloaded(attachment); const imgNotDownloaded = isDownloaded
? false
: hasNotDownloaded(attachment);
const resolvedBlurHash = blurHash || defaultBlurHash(theme); const resolvedBlurHash = blurHash || defaultBlurHash(theme);

8
ts/model-types.d.ts vendored
View file

@ -22,7 +22,11 @@ import {
} from './messages/MessageSendState'; } from './messages/MessageSendState';
import { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisions'; import { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisions';
import { ConversationColorType } from './types/Colors'; import { ConversationColorType } from './types/Colors';
import { AttachmentType, ThumbnailType } from './types/Attachment'; import {
AttachmentDraftType,
AttachmentType,
ThumbnailType,
} from './types/Attachment';
import { EmbeddedContactType } from './types/EmbeddedContact'; import { EmbeddedContactType } from './types/EmbeddedContact';
import { SignalService as Proto } from './protobuf'; import { SignalService as Proto } from './protobuf';
import { AvatarDataType } from './types/Avatar'; import { AvatarDataType } from './types/Avatar';
@ -223,7 +227,7 @@ export type ConversationAttributesType = {
customColorId?: string; customColorId?: string;
discoveredUnregisteredAt?: number; discoveredUnregisteredAt?: number;
draftChanged?: boolean; draftChanged?: boolean;
draftAttachments?: Array<AttachmentType>; draftAttachments?: Array<AttachmentDraftType>;
draftBodyRanges?: Array<BodyRangeType>; draftBodyRanges?: Array<BodyRangeType>;
draftTimestamp?: number | null; draftTimestamp?: number | null;
inbox_position: number; inbox_position: number;

View file

@ -4,7 +4,7 @@
import type { ThunkAction } from 'redux-thunk'; import type { ThunkAction } from 'redux-thunk';
import * as log from '../../logging/log'; import * as log from '../../logging/log';
import type { AttachmentType } from '../../types/Attachment'; import type { InMemoryAttachmentDraftType } from '../../types/Attachment';
import { SignalService as Proto } from '../../protobuf'; import { SignalService as Proto } from '../../protobuf';
import type { StateType as RootStateType } from '../reducer'; import type { StateType as RootStateType } from '../reducer';
import { fileToBytes } from '../../util/fileToBytes'; import { fileToBytes } from '../../util/fileToBytes';
@ -129,7 +129,7 @@ function completeRecordingAction(): CompleteRecordingAction {
function completeRecording( function completeRecording(
conversationId: string, conversationId: string,
onSendAudioRecording?: (rec: AttachmentType) => unknown onSendAudioRecording?: (rec: InMemoryAttachmentDraftType) => unknown
): ThunkAction< ): ThunkAction<
void, void,
RootStateType, RootStateType,
@ -158,7 +158,8 @@ function completeRecording(
} }
const data = await fileToBytes(blob); const data = await fileToBytes(blob);
const voiceNoteAttachment = { const voiceNoteAttachment: InMemoryAttachmentDraftType = {
pending: false,
contentType: stringToMIMEType(blob.type), contentType: stringToMIMEType(blob.type),
data, data,
size: data.byteLength, size: data.byteLength,

View file

@ -6,7 +6,10 @@ import type { ThunkAction } from 'redux-thunk';
import * as log from '../../logging/log'; import * as log from '../../logging/log';
import type { NoopActionType } from './noop'; import type { NoopActionType } from './noop';
import type { StateType as RootStateType } from '../reducer'; import type { StateType as RootStateType } from '../reducer';
import type { AttachmentType } from '../../types/Attachment'; import type {
AttachmentDraftType,
InMemoryAttachmentDraftType,
} from '../../types/Attachment';
import type { MessageAttributesType } from '../../model-types.d'; import type { MessageAttributesType } from '../../model-types.d';
import type { LinkPreviewWithDomain } from '../../types/LinkPreview'; import type { LinkPreviewWithDomain } from '../../types/LinkPreview';
import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation'; import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation';
@ -15,14 +18,14 @@ import { REMOVE_PREVIEW as REMOVE_LINK_PREVIEW } from './linkPreviews';
import { writeDraftAttachment } from '../../util/writeDraftAttachment'; import { writeDraftAttachment } from '../../util/writeDraftAttachment';
import { deleteDraftAttachment } from '../../util/deleteDraftAttachment'; import { deleteDraftAttachment } from '../../util/deleteDraftAttachment';
import { replaceIndex } from '../../util/replaceIndex'; import { replaceIndex } from '../../util/replaceIndex';
import { resolveAttachmentOnDisk } from '../../util/resolveAttachmentOnDisk'; import { resolveDraftAttachmentOnDisk } from '../../util/resolveDraftAttachmentOnDisk';
import type { HandleAttachmentsProcessingArgsType } from '../../util/handleAttachmentsProcessing'; import type { HandleAttachmentsProcessingArgsType } from '../../util/handleAttachmentsProcessing';
import { handleAttachmentsProcessing } from '../../util/handleAttachmentsProcessing'; import { handleAttachmentsProcessing } from '../../util/handleAttachmentsProcessing';
// State // State
export type ComposerStateType = { export type ComposerStateType = {
attachments: ReadonlyArray<AttachmentType>; attachments: ReadonlyArray<AttachmentDraftType>;
linkPreviewLoading: boolean; linkPreviewLoading: boolean;
linkPreviewResult?: LinkPreviewWithDomain; linkPreviewResult?: LinkPreviewWithDomain;
quotedMessage?: Pick<MessageAttributesType, 'conversationId' | 'quote'>; quotedMessage?: Pick<MessageAttributesType, 'conversationId' | 'quote'>;
@ -40,12 +43,12 @@ const SET_QUOTED_MESSAGE = 'composer/SET_QUOTED_MESSAGE';
type AddPendingAttachmentActionType = { type AddPendingAttachmentActionType = {
type: typeof ADD_PENDING_ATTACHMENT; type: typeof ADD_PENDING_ATTACHMENT;
payload: AttachmentType; payload: AttachmentDraftType;
}; };
type ReplaceAttachmentsActionType = { type ReplaceAttachmentsActionType = {
type: typeof REPLACE_ATTACHMENTS; type: typeof REPLACE_ATTACHMENTS;
payload: ReadonlyArray<AttachmentType>; payload: ReadonlyArray<AttachmentDraftType>;
}; };
type ResetComposerActionType = { type ResetComposerActionType = {
@ -99,14 +102,14 @@ export const actions = {
// next in-memory store. // next in-memory store.
function getAttachmentsFromConversationModel( function getAttachmentsFromConversationModel(
conversationId: string conversationId: string
): Array<AttachmentType> { ): Array<AttachmentDraftType> {
const conversation = window.ConversationController.get(conversationId); const conversation = window.ConversationController.get(conversationId);
return conversation?.get('draftAttachments') || []; return conversation?.get('draftAttachments') || [];
} }
function addAttachment( function addAttachment(
conversationId: string, conversationId: string,
attachment: AttachmentType attachment: InMemoryAttachmentDraftType
): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> { ): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
return async (dispatch, getState) => { return async (dispatch, getState) => {
// We do async operations first so multiple in-process addAttachments don't stomp on // We do async operations first so multiple in-process addAttachments don't stomp on
@ -161,7 +164,7 @@ function addAttachment(
function addPendingAttachment( function addPendingAttachment(
conversationId: string, conversationId: string,
pendingAttachment: AttachmentType pendingAttachment: AttachmentDraftType
): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> { ): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
return (dispatch, getState) => { return (dispatch, getState) => {
const isSelectedConversation = const isSelectedConversation =
@ -240,7 +243,7 @@ function removeAttachment(
function replaceAttachments( function replaceAttachments(
conversationId: string, conversationId: string,
attachments: ReadonlyArray<AttachmentType> attachments: ReadonlyArray<AttachmentDraftType>
): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> { ): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
return (dispatch, getState) => { return (dispatch, getState) => {
// If the call came from a conversation we are no longer in we do not // If the call came from a conversation we are no longer in we do not
@ -251,7 +254,7 @@ function replaceAttachments(
dispatch({ dispatch({
type: REPLACE_ATTACHMENTS, type: REPLACE_ATTACHMENTS,
payload: attachments.map(resolveAttachmentOnDisk), payload: attachments.map(resolveDraftAttachmentOnDisk),
}); });
}; };
} }

View file

@ -13,15 +13,15 @@ import { getLinkPreview } from '../selectors/linkPreviews';
import { getIntl, getTheme } from '../selectors/user'; import { getIntl, getTheme } from '../selectors/user';
import { getEmojiSkinTone } from '../selectors/items'; import { getEmojiSkinTone } from '../selectors/items';
import { selectRecentEmojis } from '../selectors/emojis'; import { selectRecentEmojis } from '../selectors/emojis';
import type { AttachmentType } from '../../types/Attachment'; import type { AttachmentDraftType } from '../../types/Attachment';
export type SmartForwardMessageModalProps = { export type SmartForwardMessageModalProps = {
attachments?: Array<AttachmentType>; attachments?: Array<AttachmentDraftType>;
conversationId: string; conversationId: string;
doForwardMessage: ( doForwardMessage: (
selectedContacts: Array<string>, selectedContacts: Array<string>,
messageBody?: string, messageBody?: string,
attachments?: Array<AttachmentType>, attachments?: Array<AttachmentDraftType>,
linkPreview?: LinkPreviewType linkPreview?: LinkPreviewType
) => void; ) => void;
isSticker: boolean; isSticker: boolean;

View file

@ -1,7 +1,10 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { AttachmentType } from '../../types/Attachment'; import type {
AttachmentType,
AttachmentDraftType,
} from '../../types/Attachment';
import { IMAGE_JPEG } from '../../types/MIME'; import { IMAGE_JPEG } from '../../types/MIME';
export const fakeAttachment = ( export const fakeAttachment = (
@ -13,3 +16,13 @@ export const fakeAttachment = (
size: 10304, size: 10304,
...overrides, ...overrides,
}); });
export const fakeDraftAttachment = (
overrides: Partial<AttachmentDraftType> = {}
): AttachmentDraftType => ({
pending: false,
contentType: IMAGE_JPEG,
path: 'file.jpg',
size: 10304,
...overrides,
});

View file

@ -9,8 +9,8 @@ import { noopAction } from '../../../state/ducks/noop';
import { reducer as rootReducer } from '../../../state/reducer'; import { reducer as rootReducer } from '../../../state/reducer';
import { IMAGE_JPEG } from '../../../types/MIME'; import { IMAGE_JPEG } from '../../../types/MIME';
import type { AttachmentType } from '../../../types/Attachment'; import type { AttachmentDraftType } from '../../../types/Attachment';
import { fakeAttachment } from '../../helpers/fakeAttachment'; import { fakeDraftAttachment } from '../../helpers/fakeAttachment';
describe('both/state/ducks/composer', () => { describe('both/state/ducks/composer', () => {
const QUOTED_MESSAGE = { const QUOTED_MESSAGE = {
@ -40,8 +40,13 @@ describe('both/state/ducks/composer', () => {
const { replaceAttachments } = actions; const { replaceAttachments } = actions;
const dispatch = sinon.spy(); const dispatch = sinon.spy();
const attachments: Array<AttachmentType> = [ const attachments: Array<AttachmentDraftType> = [
{ contentType: IMAGE_JPEG, pending: false, url: '', size: 2433 }, {
contentType: IMAGE_JPEG,
pending: true,
size: 2433,
path: 'image.jpg',
},
]; ];
replaceAttachments('123', attachments)( replaceAttachments('123', attachments)(
dispatch, dispatch,
@ -57,7 +62,7 @@ describe('both/state/ducks/composer', () => {
it('sets the high quality setting to false when there are no attachments', () => { it('sets the high quality setting to false when there are no attachments', () => {
const { replaceAttachments } = actions; const { replaceAttachments } = actions;
const dispatch = sinon.spy(); const dispatch = sinon.spy();
const attachments: Array<AttachmentType> = []; const attachments: Array<AttachmentDraftType> = [];
replaceAttachments('123', attachments)( replaceAttachments('123', attachments)(
dispatch, dispatch,
@ -83,7 +88,7 @@ describe('both/state/ducks/composer', () => {
const { replaceAttachments } = actions; const { replaceAttachments } = actions;
const dispatch = sinon.spy(); const dispatch = sinon.spy();
const attachments = [fakeAttachment()]; const attachments = [fakeDraftAttachment()];
replaceAttachments('123', attachments)( replaceAttachments('123', attachments)(
dispatch, dispatch,
getRootStateFunction('456'), getRootStateFunction('456'),

View file

@ -78,40 +78,46 @@ export type DownloadedAttachmentType = AttachmentType & {
export type BaseAttachmentDraftType = { export type BaseAttachmentDraftType = {
blurHash?: string; blurHash?: string;
contentType: MIME.MIMEType; contentType: MIME.MIMEType;
fileName: string;
path: string;
screenshotContentType?: string; screenshotContentType?: string;
screenshotSize?: number; screenshotSize?: number;
size: number; size: number;
flags?: number;
}; };
// An ephemeral attachment type, used between user's request to add the attachment as
// a draft and final save on disk and in conversation.draftAttachments.
export type InMemoryAttachmentDraftType = export type InMemoryAttachmentDraftType =
| ({ | ({
data?: Uint8Array; data: Uint8Array;
pending: false; pending: false;
screenshotData?: Uint8Array; screenshotData?: Uint8Array;
fileName?: string;
path?: string;
} & BaseAttachmentDraftType) } & BaseAttachmentDraftType)
| { | {
contentType: MIME.MIMEType; contentType: MIME.MIMEType;
fileName: string; fileName?: string;
path: string; path?: string;
pending: true; pending: true;
size: number; size: number;
}; };
// What's stored in conversation.draftAttachments
export type AttachmentDraftType = export type AttachmentDraftType =
| ({ | ({
url: string; url?: string;
screenshotPath?: string; screenshotPath?: string;
pending: false; pending: false;
// Old draft attachments may have a caption, though they are no longer editable // Old draft attachments may have a caption, though they are no longer editable
// because we removed the caption editor. // because we removed the caption editor.
caption?: string; caption?: string;
fileName?: string;
path: string;
} & BaseAttachmentDraftType) } & BaseAttachmentDraftType)
| { | {
contentType: MIME.MIMEType; contentType: MIME.MIMEType;
fileName: string; fileName?: string;
path: string; path?: string;
pending: true; pending: true;
size: number; size: number;
}; };
@ -614,6 +620,10 @@ export function getUrl(attachment: AttachmentType): string | undefined {
return attachment.screenshot.url; return attachment.screenshot.url;
} }
if (isVideoAttachment(attachment)) {
return undefined;
}
return attachment.url; return attachment.url;
} }

View file

@ -6,17 +6,20 @@ import {
preProcessAttachment, preProcessAttachment,
processAttachment, processAttachment,
} from './processAttachment'; } from './processAttachment';
import type { AttachmentType } from '../types/Attachment'; import type {
AttachmentDraftType,
InMemoryAttachmentDraftType,
} from '../types/Attachment';
import { AttachmentToastType } from '../types/AttachmentToastType'; import { AttachmentToastType } from '../types/AttachmentToastType';
import * as log from '../logging/log'; import * as log from '../logging/log';
export type AddAttachmentActionType = ( export type AddAttachmentActionType = (
conversationId: string, conversationId: string,
attachment: AttachmentType attachment: InMemoryAttachmentDraftType
) => unknown; ) => unknown;
export type AddPendingAttachmentActionType = ( export type AddPendingAttachmentActionType = (
conversationId: string, conversationId: string,
pendingAttachment: AttachmentType pendingAttachment: AttachmentDraftType
) => unknown; ) => unknown;
export type RemoveAttachmentActionType = ( export type RemoveAttachmentActionType = (
conversationId: string, conversationId: string,
@ -27,7 +30,7 @@ export type HandleAttachmentsProcessingArgsType = {
addAttachment: AddAttachmentActionType; addAttachment: AddAttachmentActionType;
addPendingAttachment: AddPendingAttachmentActionType; addPendingAttachment: AddPendingAttachmentActionType;
conversationId: string; conversationId: string;
draftAttachments: ReadonlyArray<AttachmentType>; draftAttachments: ReadonlyArray<AttachmentDraftType>;
files: ReadonlyArray<File>; files: ReadonlyArray<File>;
onShowToast: (toastType: AttachmentToastType) => unknown; onShowToast: (toastType: AttachmentToastType) => unknown;
removeAttachment: RemoveAttachmentActionType; removeAttachment: RemoveAttachmentActionType;

View file

@ -4,7 +4,10 @@
import path from 'path'; import path from 'path';
import * as log from '../logging/log'; import * as log from '../logging/log';
import type { AttachmentType } from '../types/Attachment'; import type {
AttachmentDraftType,
InMemoryAttachmentDraftType,
} from '../types/Attachment';
import { AttachmentToastType } from '../types/AttachmentToastType'; import { AttachmentToastType } from '../types/AttachmentToastType';
import { fileToBytes } from './fileToBytes'; import { fileToBytes } from './fileToBytes';
import { handleImageAttachment } from './handleImageAttachment'; import { handleImageAttachment } from './handleImageAttachment';
@ -14,7 +17,9 @@ import { isFileDangerous } from './isFileDangerous';
import { isHeic, isImage, stringToMIMEType } from '../types/MIME'; import { isHeic, isImage, stringToMIMEType } from '../types/MIME';
import { isImageTypeSupported, isVideoTypeSupported } from './GoogleChrome'; import { isImageTypeSupported, isVideoTypeSupported } from './GoogleChrome';
export function getPendingAttachment(file: File): AttachmentType | undefined { export function getPendingAttachment(
file: File
): AttachmentDraftType | undefined {
if (!file) { if (!file) {
return; return;
} }
@ -33,7 +38,7 @@ export function getPendingAttachment(file: File): AttachmentType | undefined {
export function preProcessAttachment( export function preProcessAttachment(
file: File, file: File,
draftAttachments: Array<AttachmentType> draftAttachments: Array<AttachmentDraftType>
): AttachmentToastType | undefined { ): AttachmentToastType | undefined {
if (!file) { if (!file) {
return; return;
@ -53,7 +58,7 @@ export function preProcessAttachment(
} }
const haveNonImage = draftAttachments.some( const haveNonImage = draftAttachments.some(
(attachment: AttachmentType) => !isImage(attachment.contentType) (attachment: AttachmentDraftType) => !isImage(attachment.contentType)
); );
// You can't add another attachment if you already have a non-image staged // You can't add another attachment if you already have a non-image staged
if (haveNonImage) { if (haveNonImage) {
@ -72,10 +77,10 @@ export function preProcessAttachment(
export async function processAttachment( export async function processAttachment(
file: File file: File
): Promise<AttachmentType | void> { ): Promise<InMemoryAttachmentDraftType | void> {
const fileType = stringToMIMEType(file.type); const fileType = stringToMIMEType(file.type);
let attachment: AttachmentType; let attachment: InMemoryAttachmentDraftType;
try { try {
if (isImageTypeSupported(fileType) || isHeic(fileType)) { if (isImageTypeSupported(fileType) || isHeic(fileType)) {
attachment = await handleImageAttachment(file); attachment = await handleImageAttachment(file);

View file

@ -4,11 +4,12 @@
import { pick } from 'lodash'; import { pick } from 'lodash';
import * as log from '../logging/log'; import * as log from '../logging/log';
import type { AttachmentType } from '../types/Attachment'; import type { AttachmentDraftType } from '../types/Attachment';
import { isVideoAttachment } from '../types/Attachment';
export function resolveAttachmentOnDisk( export function resolveDraftAttachmentOnDisk(
attachment: AttachmentType attachment: AttachmentDraftType
): AttachmentType { ): AttachmentDraftType {
let url = ''; let url = '';
if (attachment.pending) { if (attachment.pending) {
return attachment; return attachment;
@ -18,7 +19,7 @@ export function resolveAttachmentOnDisk(
url = window.Signal.Migrations.getAbsoluteDraftPath( url = window.Signal.Migrations.getAbsoluteDraftPath(
attachment.screenshotPath attachment.screenshotPath
); );
} else if (attachment.path) { } else if (!isVideoAttachment(attachment) && attachment.path) {
url = window.Signal.Migrations.getAbsoluteDraftPath(attachment.path); url = window.Signal.Migrations.getAbsoluteDraftPath(attachment.path);
} else { } else {
log.warn( log.warn(

View file

@ -2,28 +2,32 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { omit } from 'lodash'; import { omit } from 'lodash';
import type { AttachmentType } from '../types/Attachment'; import type {
InMemoryAttachmentDraftType,
AttachmentDraftType,
} from '../types/Attachment';
export async function writeDraftAttachment( export async function writeDraftAttachment(
attachment: AttachmentType attachment: InMemoryAttachmentDraftType
): Promise<AttachmentType> { ): Promise<AttachmentDraftType> {
if (attachment.pending) { if (attachment.pending) {
throw new Error('writeDraftAttachment: Cannot write pending attachment'); throw new Error('writeDraftAttachment: Cannot write pending attachment');
} }
const result: AttachmentType = { const path = await window.Signal.Migrations.writeNewDraftData(
attachment.data
);
const screenshotPath = attachment.screenshotData
? await window.Signal.Migrations.writeNewDraftData(
attachment.screenshotData
)
: undefined;
return {
...omit(attachment, ['data', 'screenshotData']), ...omit(attachment, ['data', 'screenshotData']),
path,
screenshotPath,
pending: false, pending: false,
}; };
if (attachment.data) {
result.path = await window.Signal.Migrations.writeNewDraftData(
attachment.data
);
}
if (attachment.screenshotData) {
result.screenshotPath = await window.Signal.Migrations.writeNewDraftData(
attachment.screenshotData
);
}
return result;
} }

View file

@ -7,7 +7,7 @@ import { batch as batchDispatch } from 'react-redux';
import { debounce, flatten, omit, throttle } from 'lodash'; import { debounce, flatten, omit, throttle } from 'lodash';
import { render } from 'mustache'; import { render } from 'mustache';
import type { AttachmentType } from '../types/Attachment'; import type { AttachmentDraftType, AttachmentType } from '../types/Attachment';
import { isGIF } from '../types/Attachment'; import { isGIF } from '../types/Attachment';
import * as Attachment from '../types/Attachment'; import * as Attachment from '../types/Attachment';
import type { StickerPackType as StickerPackDBType } from '../sql/Interface'; import type { StickerPackType as StickerPackDBType } from '../sql/Interface';
@ -1561,16 +1561,32 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
const attachments = getAttachmentsForMessage(message.attributes); const attachments = getAttachmentsForMessage(message.attributes);
const draftAttachments = attachments
.map((item: AttachmentType): AttachmentDraftType | null => {
const { path } = item;
if (!path) {
return null;
}
return {
...item,
path,
pending: false as const,
screenshotPath: item.screenshot?.path,
};
})
.filter(isNotNil);
this.forwardMessageModal = new Whisper.ReactWrapperView({ this.forwardMessageModal = new Whisper.ReactWrapperView({
JSX: window.Signal.State.Roots.createForwardMessageModal( JSX: window.Signal.State.Roots.createForwardMessageModal(
window.reduxStore, window.reduxStore,
{ {
attachments, attachments: draftAttachments,
conversationId: this.model.id, conversationId: this.model.id,
doForwardMessage: async ( doForwardMessage: async (
conversationIds: Array<string>, conversationIds: Array<string>,
messageBody?: string, messageBody?: string,
includedAttachments?: Array<AttachmentType>, includedAttachments?: Array<AttachmentDraftType>,
linkPreview?: LinkPreviewType linkPreview?: LinkPreviewType
) => { ) => {
try { try {
@ -1619,7 +1635,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
message: MessageModel, message: MessageModel,
conversationIds: Array<string>, conversationIds: Array<string>,
messageBody?: string, messageBody?: string,
attachments?: Array<AttachmentType>, attachments?: Array<AttachmentDraftType>,
linkPreview?: LinkPreviewType linkPreview?: LinkPreviewType
): Promise<boolean> { ): Promise<boolean> {
log.info(`maybeForwardMessage/${message.idForLogging()}: Starting...`); log.info(`maybeForwardMessage/${message.idForLogging()}: Starting...`);