diff --git a/_locales/en/messages.json b/_locales/en/messages.json index cee5811fd..3b6d14f14 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -557,12 +557,8 @@ "message": "Submit log", "description": "Label for the decryption error toast button" }, - "oneNonImageAtATimeToast": { - "message": "When including a non-image attachment, the limit is one attachment per message.", - "description": "An error popup when the user has attempted to add an attachment" - }, - "cannotMixImageAndNonImageAttachments": { - "message": "You cannot mix non-image and image attachments in one message.", + "cannotSelectPhotosAndVideosAlongWithFiles": { + "message": "You can't select photos and videos along with files.", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { @@ -2623,8 +2619,8 @@ "message": "Close Popup", "description": "Used as alt text for any button closing a popup" }, - "add-image-attachment": { - "message": "Add image attachment", + "addImageOrVideoattachment": { + "message": "Add image or video attachment", "description": "Used in draft attachment list for the big 'add new attachment' button" }, "remove-attachment": { diff --git a/ts/components/CompositionUpload.tsx b/ts/components/CompositionUpload.tsx index 1be58d7d4..a127b7941 100644 --- a/ts/components/CompositionUpload.tsx +++ b/ts/components/CompositionUpload.tsx @@ -8,16 +8,21 @@ import type { InMemoryAttachmentDraftType, AttachmentDraftType, } from '../types/Attachment'; +import { isVideoAttachment, isImageAttachment } from '../types/Attachment'; import { AttachmentToastType } from '../types/AttachmentToastType'; import type { LocalizerType } from '../types/Util'; -import { ToastCannotMixImageAndNonImageAttachments } from './ToastCannotMixImageAndNonImageAttachments'; +import { ToastCannotMixMultiAndNonMultiAttachments } from './ToastCannotMixMultiAndNonMultiAttachments'; import { ToastDangerousFileType } from './ToastDangerousFileType'; import { ToastFileSize } from './ToastFileSize'; import { ToastMaxAttachments } from './ToastMaxAttachments'; -import { ToastOneNonImageAtATime } from './ToastOneNonImageAtATime'; +import { ToastUnsupportedMultiAttachment } from './ToastUnsupportedMultiAttachment'; import { ToastUnableToLoadAttachment } from './ToastUnableToLoadAttachment'; import type { HandleAttachmentsProcessingArgsType } from '../util/handleAttachmentsProcessing'; +import { + getSupportedImageTypes, + getSupportedVideoTypes, +} from '../util/GoogleChrome'; export type PropsType = { addAttachment: ( @@ -87,14 +92,18 @@ export const CompositionUpload = forwardRef( toast = ; } else if (toastType === AttachmentToastType.ToastMaxAttachments) { toast = ; - } else if (toastType === AttachmentToastType.ToastOneNonImageAtATime) { - toast = ; } else if ( - toastType === - AttachmentToastType.ToastCannotMixImageAndNonImageAttachments + toastType === AttachmentToastType.ToastUnsupportedMultiAttachment ) { toast = ( - + ); + } else if ( + toastType === + AttachmentToastType.ToastCannotMixMultiAndNonMultiAttachments + ) { + toast = ( + @@ -103,6 +112,14 @@ export const CompositionUpload = forwardRef( toast = ; } + const anyVideoOrImageAttachments = draftAttachments.some(attachment => { + return isImageAttachment(attachment) || isVideoAttachment(attachment); + }); + + const acceptContentTypes = anyVideoOrImageAttachments + ? [...getSupportedImageTypes(), ...getSupportedVideoTypes()] + : null; + return ( <> {toast} @@ -112,6 +129,7 @@ export const CompositionUpload = forwardRef( onChange={onFileInputChange} ref={ref} type="file" + accept={acceptContentTypes?.join(',')} /> ); diff --git a/ts/components/ToastCannotMixImageAndNonImageAttachments.stories.tsx b/ts/components/ToastCannotMixMultiAndNonMultiAttachments.stories.tsx similarity index 51% rename from ts/components/ToastCannotMixImageAndNonImageAttachments.stories.tsx rename to ts/components/ToastCannotMixMultiAndNonMultiAttachments.stories.tsx index c617f0d3c..1c4b13549 100644 --- a/ts/components/ToastCannotMixImageAndNonImageAttachments.stories.tsx +++ b/ts/components/ToastCannotMixMultiAndNonMultiAttachments.stories.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; -import { ToastCannotMixImageAndNonImageAttachments } from './ToastCannotMixImageAndNonImageAttachments'; +import { ToastCannotMixMultiAndNonMultiAttachments } from './ToastCannotMixMultiAndNonMultiAttachments'; import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -16,13 +16,13 @@ const defaultProps = { }; export default { - title: 'Components/ToastCannotMixImageAndNonImageAttachments', + title: 'Components/ToastCannotMixMultiAndNonMultiAttachments', }; -export const _ToastCannotMixImageAndNonImageAttachments = (): JSX.Element => ( - +export const _ToastCannotMixMultiAndNonMultiAttachments = (): JSX.Element => ( + ); -_ToastCannotMixImageAndNonImageAttachments.story = { - name: 'ToastCannotMixImageAndNonImageAttachments', +_ToastCannotMixMultiAndNonMultiAttachments.story = { + name: 'ToastCannotMixMultiAndNonMultiAttachments', }; diff --git a/ts/components/ToastOneNonImageAtATime.tsx b/ts/components/ToastCannotMixMultiAndNonMultiAttachments.tsx similarity index 64% rename from ts/components/ToastOneNonImageAtATime.tsx rename to ts/components/ToastCannotMixMultiAndNonMultiAttachments.tsx index f68dfb313..2f4182f6e 100644 --- a/ts/components/ToastOneNonImageAtATime.tsx +++ b/ts/components/ToastCannotMixMultiAndNonMultiAttachments.tsx @@ -10,9 +10,13 @@ type PropsType = { onClose: () => unknown; }; -export const ToastOneNonImageAtATime = ({ +export const ToastCannotMixMultiAndNonMultiAttachments = ({ i18n, onClose, }: PropsType): JSX.Element => { - return {i18n('oneNonImageAtATimeToast')}; + return ( + + {i18n('cannotSelectPhotosAndVideosAlongWithFiles')} + + ); }; diff --git a/ts/components/ToastOneNonImageAtATime.stories.tsx b/ts/components/ToastUnsupportedMultiAttachment.stories.tsx similarity index 53% rename from ts/components/ToastOneNonImageAtATime.stories.tsx rename to ts/components/ToastUnsupportedMultiAttachment.stories.tsx index 5d0e6138a..b0f07d381 100644 --- a/ts/components/ToastOneNonImageAtATime.stories.tsx +++ b/ts/components/ToastUnsupportedMultiAttachment.stories.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; -import { ToastOneNonImageAtATime } from './ToastOneNonImageAtATime'; +import { ToastUnsupportedMultiAttachment } from './ToastUnsupportedMultiAttachment'; import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -16,13 +16,13 @@ const defaultProps = { }; export default { - title: 'Components/ToastOneNonImageAtATime', + title: 'Components/ToastUnsupportedMultiAttachment', }; -export const _ToastOneNonImageAtATime = (): JSX.Element => ( - +export const _ToastUnsupportedMultiAttachment = (): JSX.Element => ( + ); -_ToastOneNonImageAtATime.story = { - name: 'ToastOneNonImageAtATime', +_ToastUnsupportedMultiAttachment.story = { + name: 'ToastUnsupportedMultiAttachment', }; diff --git a/ts/components/ToastCannotMixImageAndNonImageAttachments.tsx b/ts/components/ToastUnsupportedMultiAttachment.tsx similarity index 77% rename from ts/components/ToastCannotMixImageAndNonImageAttachments.tsx rename to ts/components/ToastUnsupportedMultiAttachment.tsx index 649088016..4c08c3707 100644 --- a/ts/components/ToastCannotMixImageAndNonImageAttachments.tsx +++ b/ts/components/ToastUnsupportedMultiAttachment.tsx @@ -10,13 +10,13 @@ type PropsType = { onClose: () => unknown; }; -export const ToastCannotMixImageAndNonImageAttachments = ({ +export const ToastUnsupportedMultiAttachment = ({ i18n, onClose, }: PropsType): JSX.Element => { return ( - {i18n('cannotMixImageAndNonImageAttachments')} + {i18n('cannotSelectPhotosAndVideosAlongWithFiles')} ); }; diff --git a/ts/components/conversation/StagedPlaceholderAttachment.tsx b/ts/components/conversation/StagedPlaceholderAttachment.tsx index fe9006817..2d0dcce08 100644 --- a/ts/components/conversation/StagedPlaceholderAttachment.tsx +++ b/ts/components/conversation/StagedPlaceholderAttachment.tsx @@ -17,7 +17,7 @@ export const StagedPlaceholderAttachment = ({ type="button" className="module-staged-placeholder-attachment" onClick={onClick} - title={i18n('add-image-attachment')} + title={i18n('addImageOrVideoattachment')} >
diff --git a/ts/types/AttachmentToastType.ts b/ts/types/AttachmentToastType.ts index d4d15eb93..f378dfae7 100644 --- a/ts/types/AttachmentToastType.ts +++ b/ts/types/AttachmentToastType.ts @@ -2,10 +2,10 @@ // SPDX-License-Identifier: AGPL-3.0-only export enum AttachmentToastType { - ToastCannotMixImageAndNonImageAttachments, + ToastCannotMixMultiAndNonMultiAttachments, ToastDangerousFileType, ToastFileSize, ToastMaxAttachments, - ToastOneNonImageAtATime, + ToastUnsupportedMultiAttachment, ToastUnableToLoadAttachment, } diff --git a/ts/util/GoogleChrome.ts b/ts/util/GoogleChrome.ts index d098580dc..f9a4edecc 100644 --- a/ts/util/GoogleChrome.ts +++ b/ts/util/GoogleChrome.ts @@ -36,3 +36,13 @@ const SUPPORTED_VIDEO_MIME_TYPES: MIMETypeSupportMap = { // See: https://www.chromium.org/audio-video export const isVideoTypeSupported = (mimeType: MIME.MIMEType): boolean => SUPPORTED_VIDEO_MIME_TYPES[mimeType] === true; + +export const getSupportedImageTypes = (): Array => { + const keys = Object.keys(SUPPORTED_IMAGE_MIME_TYPES) as Array; + return keys.filter(contentType => SUPPORTED_IMAGE_MIME_TYPES[contentType]); +}; + +export const getSupportedVideoTypes = (): Array => { + const keys = Object.keys(SUPPORTED_VIDEO_MIME_TYPES) as Array; + return keys.filter(contentType => SUPPORTED_VIDEO_MIME_TYPES[contentType]); +}; diff --git a/ts/util/handleAttachmentsProcessing.ts b/ts/util/handleAttachmentsProcessing.ts index 3ca2b3f21..8b16ab583 100644 --- a/ts/util/handleAttachmentsProcessing.ts +++ b/ts/util/handleAttachmentsProcessing.ts @@ -54,7 +54,7 @@ export async function handleAttachmentsProcessing({ for (let i = 0; i < files.length; i += 1) { const file = files[i]; const processingResult = preProcessAttachment(file, nextDraftAttachments); - if (processingResult) { + if (processingResult != null) { onShowToast(processingResult); } else { const pendingAttachment = getPendingAttachment(file); diff --git a/ts/util/processAttachment.ts b/ts/util/processAttachment.ts index 5b687222c..bda5406b2 100644 --- a/ts/util/processAttachment.ts +++ b/ts/util/processAttachment.ts @@ -15,7 +15,7 @@ import { handleImageAttachment } from './handleImageAttachment'; import { handleVideoAttachment } from './handleVideoAttachment'; import { isAttachmentSizeOkay } from './isAttachmentSizeOkay'; import { isFileDangerous } from './isFileDangerous'; -import { isHeic, isImage, stringToMIMEType } from '../types/MIME'; +import { isHeic, isImage, isVideo, stringToMIMEType } from '../types/MIME'; import { isImageTypeSupported, isVideoTypeSupported } from './GoogleChrome'; export function getPendingAttachment( @@ -57,19 +57,24 @@ export function preProcessAttachment( return AttachmentToastType.ToastMaxAttachments; } - const haveNonImage = draftAttachments.some( - (attachment: AttachmentDraftType) => !isImage(attachment.contentType) + const haveNonImageOrVideo = draftAttachments.some( + (attachment: AttachmentDraftType) => { + return ( + !isImage(attachment.contentType) && !isVideo(attachment.contentType) + ); + } ); // You can't add another attachment if you already have a non-image staged - if (haveNonImage) { - return AttachmentToastType.ToastOneNonImageAtATime; + if (haveNonImageOrVideo) { + return AttachmentToastType.ToastUnsupportedMultiAttachment; } const fileType = stringToMIMEType(file.type); + const imageOrVideo = isImage(fileType) || isVideo(fileType); // You can't add a non-image attachment if you already have attachments staged - if (!isImage(fileType) && draftAttachments.length > 0) { - return AttachmentToastType.ToastCannotMixImageAndNonImageAttachments; + if (!imageOrVideo && draftAttachments.length > 0) { + return AttachmentToastType.ToastCannotMixMultiAndNonMultiAttachments; } return undefined; diff --git a/ts/util/showToast.tsx b/ts/util/showToast.tsx index 762dbd03a..866b65c20 100644 --- a/ts/util/showToast.tsx +++ b/ts/util/showToast.tsx @@ -8,7 +8,7 @@ import type { ToastAlreadyGroupMember } from '../components/ToastAlreadyGroupMem import type { ToastAlreadyRequestedToJoin } from '../components/ToastAlreadyRequestedToJoin'; import type { ToastBlocked } from '../components/ToastBlocked'; import type { ToastBlockedGroup } from '../components/ToastBlockedGroup'; -import type { ToastCannotMixImageAndNonImageAttachments } from '../components/ToastCannotMixImageAndNonImageAttachments'; +import type { ToastUnsupportedMultiAttachment } from '../components/ToastUnsupportedMultiAttachment'; import type { ToastCannotOpenGiftBadge, ToastPropsType as ToastCannotOpenGiftBadgePropsType, @@ -44,7 +44,7 @@ import type { ToastLinkCopied } from '../components/ToastLinkCopied'; import type { ToastLoadingFullLogs } from '../components/ToastLoadingFullLogs'; import type { ToastMaxAttachments } from '../components/ToastMaxAttachments'; import type { ToastMessageBodyTooLong } from '../components/ToastMessageBodyTooLong'; -import type { ToastOneNonImageAtATime } from '../components/ToastOneNonImageAtATime'; + import type { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound'; import type { ToastPinnedConversationsFull } from '../components/ToastPinnedConversationsFull'; import type { ToastReactionFailed } from '../components/ToastReactionFailed'; @@ -60,9 +60,7 @@ export function showToast(Toast: typeof ToastAlreadyGroupMember): void; export function showToast(Toast: typeof ToastAlreadyRequestedToJoin): void; export function showToast(Toast: typeof ToastBlocked): void; export function showToast(Toast: typeof ToastBlockedGroup): void; -export function showToast( - Toast: typeof ToastCannotMixImageAndNonImageAttachments -): void; +export function showToast(Toast: typeof ToastUnsupportedMultiAttachment): void; export function showToast(Toast: typeof ToastCannotStartGroupCall): void; export function showToast( Toast: typeof ToastCannotOpenGiftBadge, @@ -98,7 +96,7 @@ export function showToast(Toast: typeof ToastLinkCopied): void; export function showToast(Toast: typeof ToastLoadingFullLogs): void; export function showToast(Toast: typeof ToastMaxAttachments): void; export function showToast(Toast: typeof ToastMessageBodyTooLong): void; -export function showToast(Toast: typeof ToastOneNonImageAtATime): void; +export function showToast(Toast: typeof ToastUnsupportedMultiAttachment): void; export function showToast(Toast: typeof ToastOriginalMessageNotFound): void; export function showToast(Toast: typeof ToastPinnedConversationsFull): void; export function showToast(Toast: typeof ToastReactionFailed): void; diff --git a/ts/views/conversation_view.tsx b/ts/views/conversation_view.tsx index cc5076ccb..8071f4bd5 100644 --- a/ts/views/conversation_view.tsx +++ b/ts/views/conversation_view.tsx @@ -60,7 +60,7 @@ import { ReadStatus } from '../messages/MessageReadStatus'; import { SignalService as Proto } from '../protobuf'; import { ToastBlocked } from '../components/ToastBlocked'; import { ToastBlockedGroup } from '../components/ToastBlockedGroup'; -import { ToastCannotMixImageAndNonImageAttachments } from '../components/ToastCannotMixImageAndNonImageAttachments'; +import { ToastCannotMixMultiAndNonMultiAttachments } from '../components/ToastCannotMixMultiAndNonMultiAttachments'; import { ToastCannotStartGroupCall } from '../components/ToastCannotStartGroupCall'; import { ToastConversationArchived } from '../components/ToastConversationArchived'; import { ToastConversationMarkedUnread } from '../components/ToastConversationMarkedUnread'; @@ -73,7 +73,7 @@ import { ToastInvalidConversation } from '../components/ToastInvalidConversation import { ToastLeftGroup } from '../components/ToastLeftGroup'; import { ToastMaxAttachments } from '../components/ToastMaxAttachments'; import { ToastMessageBodyTooLong } from '../components/ToastMessageBodyTooLong'; -import { ToastOneNonImageAtATime } from '../components/ToastOneNonImageAtATime'; +import { ToastUnsupportedMultiAttachment } from '../components/ToastUnsupportedMultiAttachment'; import { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound'; import { ToastPinnedConversationsFull } from '../components/ToastPinnedConversationsFull'; import { ToastReactionFailed } from '../components/ToastReactionFailed'; @@ -1028,13 +1028,15 @@ export class ConversationView extends window.Backbone.View { showToast(ToastDangerousFileType); } else if (toastType === AttachmentToastType.ToastMaxAttachments) { showToast(ToastMaxAttachments); - } else if (toastType === AttachmentToastType.ToastOneNonImageAtATime) { - showToast(ToastOneNonImageAtATime); + } else if ( + toastType === AttachmentToastType.ToastUnsupportedMultiAttachment + ) { + showToast(ToastUnsupportedMultiAttachment); } else if ( toastType === - AttachmentToastType.ToastCannotMixImageAndNonImageAttachments + AttachmentToastType.ToastCannotMixMultiAndNonMultiAttachments ) { - showToast(ToastCannotMixImageAndNonImageAttachments); + showToast(ToastCannotMixMultiAndNonMultiAttachments); } else if ( toastType === AttachmentToastType.ToastUnableToLoadAttachment ) {