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 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 { ThemeType } from '../types/Util';
import { RecordingState } from '../state/ducks/audioRecorder';
@ -165,7 +165,7 @@ story.add('SMS-only', () => {
story.add('Attachments', () => {
const props = createProps({
draftAttachments: [
fakeAttachment({
fakeDraftAttachment({
contentType: IMAGE_JPEG,
url: landscapeGreenUrl,
}),

View file

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

View file

@ -4,7 +4,10 @@
import type { ChangeEventHandler } 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 type { LocalizerType } from '../types/Util';
@ -19,14 +22,14 @@ import type { HandleAttachmentsProcessingArgsType } from '../util/handleAttachme
export type PropsType = {
addAttachment: (
conversationId: string,
attachment: AttachmentType
attachment: InMemoryAttachmentDraftType
) => unknown;
addPendingAttachment: (
conversationId: string,
pendingAttachment: AttachmentType
pendingAttachment: AttachmentDraftType
) => unknown;
conversationId: string;
draftAttachments: ReadonlyArray<AttachmentType>;
draftAttachments: ReadonlyArray<AttachmentDraftType>;
i18n: LocalizerType;
processAttachments: (options: HandleAttachmentsProcessingArgsType) => 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 enMessages from '../../_locales/en/messages.json';
import type { AttachmentType } from '../types/Attachment';
import type { AttachmentDraftType } from '../types/Attachment';
import type { PropsType } from './ForwardMessageModal';
import { ForwardMessageModal } from './ForwardMessageModal';
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 { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
const createAttachment = (
props: Partial<AttachmentType> = {}
): AttachmentType => ({
const createDraftAttachment = (
props: Partial<AttachmentDraftType> = {}
): AttachmentDraftType => ({
pending: false,
path: 'fileName.jpg',
contentType: stringToMIMEType(
text('attachment contentType', props.contentType || '')
),
fileName: text('attachment fileName', props.fileName || ''),
screenshot: props.screenshot,
url: text('attachment url', props.url || ''),
screenshotPath: props.pending === false ? props.screenshotPath : undefined,
url: text('attachment url', props.pending === false ? props.url || '' : ''),
size: 3433,
});
@ -81,7 +83,7 @@ story.add('link preview', () => {
date: Date.now(),
domain: 'https://www.signal.org',
url: 'signal.org',
image: createAttachment({
image: createDraftAttachment({
url: '/fixtures/kitten-4-112-112.jpg',
contentType: IMAGE_JPEG,
}),
@ -99,22 +101,19 @@ story.add('media attachments', () => {
<ForwardMessageModal
{...useProps({
attachments: [
createAttachment({
createDraftAttachment({
pending: true,
}),
createDraftAttachment({
contentType: IMAGE_JPEG,
fileName: 'tina-rolf-269345-unsplash.jpg',
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
}),
createAttachment({
createDraftAttachment({
contentType: VIDEO_MP4,
fileName: 'pixabay-Soap-Bubble-7141.mp4',
url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
screenshot: {
height: 112,
width: 112,
url: '/fixtures/kitten-4-112-112.jpg',
contentType: IMAGE_JPEG,
path: 'originalPath',
},
screenshotPath: '/fixtures/kitten-4-112-112.jpg',
}),
],
messageBody: 'cats',

View file

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

View file

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

View file

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

View file

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