Allow stage and send of video, even if we can't get screenshot
This commit is contained in:
parent
117cb074c7
commit
a024ee4b96
21 changed files with 224 additions and 143 deletions
|
@ -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,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
8
ts/model-types.d.ts
vendored
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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...`);
|
||||||
|
|
Loading…
Reference in a new issue