From a9dd1221fbed42e29c35d082f7c98b100dfafcbc Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Sun, 22 Jan 2023 18:12:07 +0100 Subject: [PATCH] Attachment Modal: Redesign (#2253) --- src/api/gramjs/apiBuilders/messages.ts | 84 ++--- src/api/gramjs/methods/messages.ts | 65 ++-- src/api/types/misc.ts | 4 + src/assets/fonts/icomoon.woff | Bin 50980 -> 53644 bytes src/assets/fonts/icomoon.woff2 | Bin 23744 -> 24892 bytes src/components/common/File.tsx | 2 +- .../settings/SettingsGeneralBackground.tsx | 6 +- .../middle/MessageSelectToolbar.tsx | 2 +- src/components/middle/MiddleColumn.tsx | 5 - src/components/middle/composer/AttachMenu.tsx | 10 +- .../middle/composer/AttachmentModal.scss | 99 +++--- .../middle/composer/AttachmentModal.tsx | 321 +++++++++++++----- .../composer/AttachmentModalItem.module.scss | 79 +++++ .../middle/composer/AttachmentModalItem.tsx | 118 +++++++ src/components/middle/composer/Composer.scss | 15 +- src/components/middle/composer/Composer.tsx | 254 ++++++++------ src/components/middle/composer/DropArea.tsx | 14 +- src/components/middle/composer/DropTarget.tsx | 5 +- .../composer/helpers/buildAttachment.ts | 46 +-- .../helpers/getFilesFromDataTransferItems.ts | 13 +- .../composer/hooks/useAttachmentModal.ts | 66 ++++ .../composer/hooks/useClipboardPaste.ts | 9 +- src/components/ui/Menu.scss | 2 +- src/components/ui/Modal.scss | 4 + src/components/ui/Modal.tsx | 4 +- src/global/actions/api/messages.ts | 73 +++- src/global/actions/ui/misc.ts | 14 + src/global/cache.ts | 1 + src/global/initialState.ts | 5 + src/global/types.ts | 12 + src/styles/Telegram T.json | 306 +++++++++++------ src/styles/icons.scss | 12 + src/util/files.ts | 16 + src/util/setupServiceWorker.ts | 3 +- 34 files changed, 1174 insertions(+), 495 deletions(-) create mode 100644 src/components/middle/composer/AttachmentModalItem.module.scss create mode 100644 src/components/middle/composer/AttachmentModalItem.tsx create mode 100644 src/components/middle/composer/hooks/useAttachmentModal.ts diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index 11b784607..d5b6b180a 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -1393,62 +1393,66 @@ function buildUploadingMedia( previewBlobUrl, mimeType, size, + audio, + shouldSendAsFile, } = attachment; - if (attachment.quick) { - if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) { - const { width, height } = attachment.quick; + if (!shouldSendAsFile) { + if (attachment.quick) { + if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) { + const { width, height } = attachment.quick; + return { + photo: { + id: LOCAL_MEDIA_UPLOADING_TEMP_ID, + sizes: [], + thumbnail: { width, height, dataUri: '' }, // Used only for dimensions + blobUrl, + }, + }; + } + if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) { + const { width, height, duration } = attachment.quick; + return { + video: { + id: LOCAL_MEDIA_UPLOADING_TEMP_ID, + mimeType, + duration: duration || 0, + fileName, + width, + height, + blobUrl, + ...(previewBlobUrl && { thumbnail: { width, height, dataUri: previewBlobUrl } }), + size, + }, + }; + } + } + if (attachment.voice) { + const { duration, waveform } = attachment.voice; + const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH); return { - photo: { + voice: { id: LOCAL_MEDIA_UPLOADING_TEMP_ID, - sizes: [], - thumbnail: { width, height, dataUri: '' }, // Used only for dimensions - blobUrl, + duration, + waveform: inputWaveform, }, }; } - if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) { - const { width, height, duration } = attachment.quick; + if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) { + const { duration, performer, title } = audio || {}; return { - video: { + audio: { id: LOCAL_MEDIA_UPLOADING_TEMP_ID, mimeType, - duration: duration || 0, fileName, - width, - height, - blobUrl, - ...(previewBlobUrl && { thumbnail: { width, height, dataUri: previewBlobUrl } }), size, + duration: duration || 0, + title, + performer, }, }; } } - if (attachment.voice) { - const { duration, waveform } = attachment.voice; - const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH); - return { - voice: { - id: LOCAL_MEDIA_UPLOADING_TEMP_ID, - duration, - waveform: inputWaveform, - }, - }; - } - if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) { - const { duration, performer, title } = attachment.audio || {}; - return { - audio: { - id: LOCAL_MEDIA_UPLOADING_TEMP_ID, - mimeType, - fileName, - size, - duration: duration || 0, - title, - performer, - }, - }; - } return { document: { mimeType, diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index 3ea3b5615..6ddaf328a 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -566,7 +566,7 @@ export async function rescheduleMessage({ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, onProgress: ApiOnProgress) { const { - filename, blobUrl, mimeType, quick, voice, audio, previewBlobUrl, + filename, blobUrl, mimeType, quick, voice, audio, previewBlobUrl, shouldSendAsFile, } = attachment; const patchedOnProgress: ApiOnProgress = (progress) => { @@ -584,41 +584,43 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, const thumb = thumbFile ? await uploadFile(thumbFile) : undefined; const attributes: GramJs.TypeDocumentAttribute[] = [new GramJs.DocumentAttributeFilename({ fileName: filename })]; - if (quick) { - if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) { - return new GramJs.InputMediaUploadedPhoto({ file: inputFile }); - } + if (!shouldSendAsFile) { + if (quick) { + if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) { + return new GramJs.InputMediaUploadedPhoto({ file: inputFile }); + } - if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) { - const { width, height, duration } = quick; - if (duration !== undefined) { - attributes.push(new GramJs.DocumentAttributeVideo({ - duration, - w: width, - h: height, - supportsStreaming: true, - })); + if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) { + const { width, height, duration } = quick; + if (duration !== undefined) { + attributes.push(new GramJs.DocumentAttributeVideo({ + duration, + w: width, + h: height, + supportsStreaming: true, + })); + } } } - } - if (audio) { - const { duration, title, performer } = audio; - attributes.push(new GramJs.DocumentAttributeAudio({ - duration, - title, - performer, - })); - } + if (audio) { + const { duration, title, performer } = audio; + attributes.push(new GramJs.DocumentAttributeAudio({ + duration, + title, + performer, + })); + } - if (voice) { - const { duration, waveform } = voice; - const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH); - attributes.push(new GramJs.DocumentAttributeAudio({ - voice: true, - duration, - waveform: Buffer.from(inputWaveform), - })); + if (voice) { + const { duration, waveform } = voice; + const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH); + attributes.push(new GramJs.DocumentAttributeAudio({ + voice: true, + duration, + waveform: Buffer.from(inputWaveform), + })); + } } return new GramJs.InputMediaUploadedDocument({ @@ -626,6 +628,7 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, mimeType, attributes, thumb, + forceFile: shouldSendAsFile || undefined, }); } diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index fa7bb2d68..177407e52 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -45,6 +45,10 @@ export interface ApiAttachment { performer?: string; }; previewBlobUrl?: string; + + shouldSendAsFile?: boolean; + + uniqueId?: string; } export interface ApiWallpaper { diff --git a/src/assets/fonts/icomoon.woff b/src/assets/fonts/icomoon.woff index a9fcf97cd4ce94ab0630b35e79351fa8db833c03..7d6f7cbec11ad6158bc2b523eb025ceb6dba3ead 100644 GIT binary patch delta 3435 zcmZ`+du&tJ89(RV>wC}5z4o0c+C;{VGlS`E%z7wm(0y4WS$V2AVAGvBxoC zJ1ipf>o%Cz(k@JM2e$9rI|*yRpZO>4fBNId;TqhqZe^^Ih8Cd`kR$*Gqf$f<8N5d%Ox?JU)Vl@x=>o;J0E{4o#pR z0hO*9W*8Tnky?y{I0RjAm0J^zC6X<;*b?bJ+>1N=%WX04!5ujMBvBYgFmT2#xCu9w zxdG=8*06EB>#dN*q>G$efs9^&Kf#z0h@9{%HC}IH;0S|xhZt0ePt`KDGqo8_^$|5F zI(W=1*lHjy2Yzu+Frm2I4q4?{4r8Kg@9LT({|B`p*c$6L%?35xrc{Y=5>AU%iq#fY z8%(o1c6Z?}!*NcrDyUvk!yZ?}&7vcLCXctq?MujnV1{>*`nohoB6>q22a*6g%yVMM zD}tEmx|!Ovruj%=6ZZ0;6XsZ6l^t%ED_Oml*FY|v;Qjp$TMiJT(KNc_v+*v|Y*fRo zZm|ju0^2Il-AXsju6Uy(*x>aF|MSs&svm` zW^}0Yr?UoIaiXppydSsWWVWR<1>kh_;KlY8O<1rIrw4I+5mnh#5s&Sr!3%t%01iG8 z2R>~);76gj3S*mzW#}4D5jGFNOfSx~)1P#<)1=tNiF7CM#D)fknb-Y_ru*HBzupW+ zRbP;cUiJl}ni&k<3Mi@(P?SLagsM|2eTq`TxJ8vERh8VzK1GM2=8|jaN0VgLswFa@ z!s(p*EY@pkO{$qx`bbC<{HmIb4XV`vB{w@Lc?4p*)n-#MiQthPg5Xwah(lI537|?i zo+FYx&N;c#F_2?0)@hpcYNSPxMS;yR9}|VlAxV-GG7BG~uuUMIPXbqM91L zP8Fe(bU|{%$S(s`0|CYDXO;(S#RGR`wV=;kO>rwQ^5qZNE1b%7IEmnN3dGsZiA1XN z^9s_~(kjo*%CRSv(Xge6(y2oZFPNMXgH!~{YVhw2wNjv)}OCs@L9JT<6O zaJPiDq-k~nzB^4d>dws_;5kqsDakS@l#~bqISg@V3<>Ocb`@Fz@ZE=w0BlS70eo-! znEIc#B4{+jvawPC1LUDp)&>#yzYq80I#>tD+cW^N5U%mC6iB6PRiLx6QbwkyE(nRS zcvazQPmRu=cM_!~qSk{Zu?N&Q&D1v*5xI(uyYo&XPJzuI8U%ZRRY(J2h5XHxx%nx_ z^Bh-Gf(d?k!K{HfYI~kI!K`p;bIE*WQVX}Zsf&tMwF+i^Mc~*N&641vrMPx(4P|F% z&G`K|h~Oin0o*t@c0Zm`Avp;R06N?NkBS4#&ah{}>^*2VI)Gk=oPp}7y)#MA|1(vx z()^}Xjz!!2Ni*4xA?+fcaR1sDdU*`L5Yw@Zm!| z_u%jOn`Pf|{4*EE@}C!ts(}Ev!KgVmM4D}Xv*Z_^%ei&V$=ZOw4}dQ_k`p+;ECb&4 zxz+$-cn*g6`Yb2p`>0bi`9ba!ZmIC(2t4=6pU*Ey%~66}h?m5@<%{3!zu0|H=d9B%d;pHY2N${=e+DFI#6Tq@w{^$lR=u!RqmNb@H>{=#_5K5F@UDF?dLPkywd+>c8Jz&DBpI9yP|0xeD!^~|SAnjf+Cm6=(pHr08~C|ZXNQBF|X z6e=d-)3kK$43tw0hj(~_lBDyZ$m^06^bjR3ibN5-e!@E##=#T6_arN@g_5htq^hr6 zA5n;$J7}ur%ma*jYi{+dDw@1=lnVUr2hv2tvXwMF<2i8g!YEs z3HOJmBTQshDGa^gY8}ITiS1=ys1;EpLeu$SZC6%^x^d7%%;p#c0=~3*@vCIox8h8 z*E?PJy1m_}dv^7lS-hbK6!hE7q^L_0+LpdAMyjH#|N(Ju))#cx8I!_Lcv5`rImG)uYuztEa!V zYYnsJJ8S+jw03gs^g34zWH{_q0eAf5u#_``i=X-Adrp8TY lH-|T$*rH!}bL delta 764 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 diff --git a/src/components/common/File.tsx b/src/components/common/File.tsx index d619e6fe2..6b1fe4ff6 100644 --- a/src/components/common/File.tsx +++ b/src/components/common/File.tsx @@ -140,7 +140,7 @@ const File: FC = ({ )}
-
{renderText(name)}
+
{renderText(name)}
{isTransferring && transferProgress ? `${Math.round(transferProgress * 100)}%` : sizeString} diff --git a/src/components/left/settings/SettingsGeneralBackground.tsx b/src/components/left/settings/SettingsGeneralBackground.tsx index 6260e6d8a..47a3bfe44 100644 --- a/src/components/left/settings/SettingsGeneralBackground.tsx +++ b/src/components/left/settings/SettingsGeneralBackground.tsx @@ -10,6 +10,7 @@ import type { ApiWallpaper } from '../../../api/types'; import { DARK_THEME_PATTERN_COLOR, DEFAULT_PATTERN_COLOR } from '../../../config'; import { throttle } from '../../../util/schedulers'; +import { validateFiles } from '../../../util/files'; import { openSystemFilesDialog } from '../../../util/systemFilesDialog'; import { getAverageColor, getPatternColor, rgb2hex } from '../../../util/colors'; import { selectTheme } from '../../../global/selectors'; @@ -68,8 +69,9 @@ const SettingsGeneralBackground: FC = ({ const handleFileSelect = useCallback((e: Event) => { const { files } = e.target as HTMLInputElement; - if (files && files.length > 0) { - uploadWallpaper(files[0]); + const validatedFiles = validateFiles(files); + if (validatedFiles?.length) { + uploadWallpaper(validatedFiles[0]); } }, [uploadWallpaper]); diff --git a/src/components/middle/MessageSelectToolbar.tsx b/src/components/middle/MessageSelectToolbar.tsx index a2985d1c5..7cf957f3b 100644 --- a/src/components/middle/MessageSelectToolbar.tsx +++ b/src/components/middle/MessageSelectToolbar.tsx @@ -188,7 +188,7 @@ export default memo(withGlobal( const canDownload = selectCanDownloadSelectedMessages(global); const { messageIds: selectedMessageIds } = global.selectedMessages || {}; const hasProtectedMessage = chatId ? selectHasProtectedMessage(global, chatId, selectedMessageIds) : false; - const canForward = chatId ? selectCanForwardMessages(global, chatId, selectedMessageIds) : false; + const canForward = !isSchedule && chatId ? selectCanForwardMessages(global, chatId, selectedMessageIds) : false; const isForwardModalOpen = global.forwardMessages.isModalShown; const isAnyModalOpen = Boolean(isForwardModalOpen || global.requestedDraft || global.requestedAttachBotInChat || global.requestedAttachBotInstall); diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index 7b7a0da9e..9a9b44e1b 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -28,7 +28,6 @@ import { import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT, - IS_TOUCH_ENV, MASK_IMAGE_DISABLED, } from '../../util/environment'; import { DropAreaState } from './composer/DropArea'; @@ -285,10 +284,6 @@ const MiddleColumn: FC = ({ }, [shouldLoadFullChat, chatId, isReady, loadFullChat]); const handleDragEnter = useCallback((e: React.DragEvent) => { - if (IS_TOUCH_ENV) { - return; - } - const { items } = e.dataTransfer || {}; const shouldDrawQuick = items && items.length > 0 && Array.from(items) // Filter unnecessary element for drag and drop images in Firefox (https://github.com/Ajaxy/telegram-tt/issues/49) diff --git a/src/components/middle/composer/AttachMenu.tsx b/src/components/middle/composer/AttachMenu.tsx index 0cf7626a5..745e46686 100644 --- a/src/components/middle/composer/AttachMenu.tsx +++ b/src/components/middle/composer/AttachMenu.tsx @@ -10,6 +10,7 @@ import type { ISettings } from '../../../types'; import { CONTENT_TYPES_WITH_PREVIEW } from '../../../config'; import { IS_TOUCH_ENV } from '../../../util/environment'; import { openSystemFilesDialog } from '../../../util/systemFilesDialog'; +import { validateFiles } from '../../../util/files'; import useMouseInside from '../../../hooks/useMouseInside'; import useLang from '../../../hooks/useLang'; @@ -31,7 +32,7 @@ export type OwnProps = { isScheduled?: boolean; attachBots: GlobalState['attachMenu']['bots']; peerType?: ApiAttachMenuPeerType; - onFileSelect: (files: File[], isQuick: boolean) => void; + onFileSelect: (files: File[], shouldSuggestCompression?: boolean) => void; onPollCreate: () => void; theme: ISettings['theme']; }; @@ -67,11 +68,12 @@ const AttachMenu: FC = ({ } }, [isAttachMenuOpen, openAttachMenu, closeAttachMenu]); - const handleFileSelect = useCallback((e: Event, isQuick: boolean) => { + const handleFileSelect = useCallback((e: Event, shouldSuggestCompression?: boolean) => { const { files } = e.target as HTMLInputElement; + const validatedFiles = validateFiles(files); - if (files && files.length > 0) { - onFileSelect(Array.from(files), isQuick); + if (validatedFiles?.length) { + onFileSelect(validatedFiles, shouldSuggestCompression); } }, [onFileSelect]); diff --git a/src/components/middle/composer/AttachmentModal.scss b/src/components/middle/composer/AttachmentModal.scss index 7dbd4643e..599b777f7 100644 --- a/src/components/middle/composer/AttachmentModal.scss +++ b/src/components/middle/composer/AttachmentModal.scss @@ -5,65 +5,49 @@ max-width: 26.25rem; @media (max-width: 600px) { max-height: 100%; - padding-bottom: 1.5rem; } } + .modal-header-condensed { + padding: 0.3125rem 0.5rem !important; + border-bottom: 1px solid var(--color-borders); + } + .modal-content { - padding: 0.5rem 1.25rem 1.875rem; + padding: 0; max-height: calc(100vh - 3.25rem); - @media (max-width: 600px) { - padding-bottom: 0.25rem; - } + + overflow-x: auto; } - .media-wrapper { + .attachments-wrapper { max-height: 26rem; overflow: auto; + flex-shrink: 0; + display: flex; flex-wrap: wrap; - margin-bottom: 1.5rem; + gap: 0.5rem; + margin: 0 0.25rem; + padding: 0.5rem 0.25rem; - video, - img { - flex: 1; - width: calc(50% - 0.15rem); - height: 12rem; - margin-bottom: 0.3125rem; - border-radius: var(--border-radius-default); - object-fit: cover; - - &:only-child { - height: auto; - max-height: 25rem; - margin-bottom: 0; - } - - &:nth-child(even) { - margin-left: 0.3125rem; - } - } - } - - .document-wrapper { - max-height: 25rem; - overflow: auto; - flex-shrink: 0; - display: flex; - flex-direction: column; - margin: 0.75rem 0 1.75rem; - - .File:not(:last-child) { - margin-bottom: 1.5rem; - } - - .file-icon { - cursor: default !important; + @media (max-width: 600px) { + max-height: 80vh; } } .attachment-caption-wrapper { position: relative; + margin: 0 0.5rem; + + &::before { + content: ''; + position: absolute; + left: -0.5rem; + top: 0; + right: -0.5rem; + border-top: 1px solid var(--color-borders); + } .form-control { background: var(--color-background); @@ -75,6 +59,15 @@ } } + .attachment-caption { + display: flex; + gap: 0.5rem; + } + + .attachment-checkbox { + margin-left: -1rem; + } + .drop-target { position: relative; overflow: hidden; @@ -119,9 +112,8 @@ } .attachment-caption-wrapper, - .document-wrapper, - .media-wrapper, - .form-control { + .attachments-wrapper, + .input-scroller { pointer-events: none; } @@ -136,13 +128,24 @@ } .CustomSendMenu { - bottom: auto; + bottom: 2.25rem; .is-pointer-env & > .backdrop { width: 100%; - top: -2.25rem; + top: -2rem; bottom: auto; - height: 2.75rem; + height: 3.5rem; } } + + .AttachmentModal--send-wrapper { + align-self: flex-end; + margin-right: 0.25rem; + } + + .AttachmentModal--send { + height: 2.5rem; + padding: 0 1rem; + margin-bottom: 0.25rem; + } } diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index 70f04f4bc..72665db44 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -1,22 +1,28 @@ import React, { - memo, useCallback, useEffect, useMemo, useRef, + memo, useCallback, useEffect, useMemo, useRef, useState, } from '../../../lib/teact/teact'; -import { getActions } from '../../../global'; +import { getActions, withGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; import type { ApiAttachment, ApiChatMember, ApiSticker } from '../../../api/types'; +import type { GlobalState } from '../../../global/types'; import { + BASE_EMOJI_KEYWORD_LANG, EDITABLE_INPUT_MODAL_ID, SUPPORTED_AUDIO_CONTENT_TYPES, SUPPORTED_IMAGE_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES, } from '../../../config'; -import { getFileExtension } from '../../common/helpers/documentInfo'; +import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems'; -import { hasPreview } from '../../../util/files'; import { getHtmlTextLength } from './helpers/getHtmlTextLength'; +import { selectChat, selectIsChatWithSelf } from '../../../global/selectors'; +import { selectCurrentLimit } from '../../../global/selectors/limits'; +import { openSystemFilesDialog } from '../../../util/systemFilesDialog'; +import buildClassName from '../../../util/buildClassName'; +import { validateFiles } from '../../../util/files'; import usePrevious from '../../../hooks/usePrevious'; import useMentionTooltip from './hooks/useMentionTooltip'; @@ -29,12 +35,14 @@ import useCustomEmojiTooltip from './hooks/useCustomEmojiTooltip'; import Button from '../../ui/Button'; import Modal from '../../ui/Modal'; -import File from '../../common/File'; import MessageInput from './MessageInput'; import MentionTooltip from './MentionTooltip'; import EmojiTooltip from './EmojiTooltip.async'; import CustomSendMenu from './CustomSendMenu.async'; import CustomEmojiTooltip from './CustomEmojiTooltip.async'; +import AttachmentModalItem from './AttachmentModalItem'; +import DropdownMenu from '../../ui/DropdownMenu'; +import MenuItem from '../../ui/MenuItem'; import './AttachmentModal.scss'; @@ -45,28 +53,34 @@ export type OwnProps = { caption: string; canShowCustomSendMenu?: boolean; isReady?: boolean; + shouldSchedule?: boolean; + shouldSuggestCompression?: boolean; + onCaptionUpdate: (html: string) => void; + onSend: (sendCompressed: boolean, sendGrouped: boolean) => void; + onFileAppend: (files: File[]) => void; + onDelete: (attachmentIndex: number) => void; + onClear: () => void; + onSendSilent: (sendCompressed: boolean, sendGrouped: boolean) => void; + onSendScheduled: (sendCompressed: boolean, sendGrouped: boolean) => void; +}; + +type StateProps = { isChatWithSelf?: boolean; currentUserId?: string; groupChatMembers?: ApiChatMember[]; recentEmojis: string[]; baseEmojiKeywords?: Record; emojiKeywords?: Record; - shouldSchedule?: boolean; shouldSuggestCustomEmoji?: boolean; customEmojiForEmoji?: ApiSticker[]; captionLimit: number; - onCaptionUpdate: (html: string) => void; - onSend: () => void; - onFileAppend: (files: File[], isQuick: boolean) => void; - onClear: () => void; - onSendSilent: () => void; - onSendScheduled: () => void; + attachmentSettings: GlobalState['attachmentSettings']; }; const DROP_LEAVE_TIMEOUT_MS = 150; const CAPTION_SYMBOLS_LEFT_THRESHOLD = 100; -const AttachmentModal: FC = ({ +const AttachmentModal: FC = ({ chatId, threadId, attachments, @@ -83,24 +97,39 @@ const AttachmentModal: FC = ({ shouldSchedule, shouldSuggestCustomEmoji, customEmojiForEmoji, + attachmentSettings, + shouldSuggestCompression, onCaptionUpdate, onSend, onFileAppend, + onDelete, onClear, onSendSilent, onSendScheduled, }) => { - const { addRecentCustomEmoji, addRecentEmoji } = getActions(); + const { addRecentCustomEmoji, addRecentEmoji, updateAttachmentSettings } = getActions(); + const lang = useLang(); const captionRef = useStateRef(caption); // eslint-disable-next-line no-null/no-null const mainButtonRef = useStateRef(null); const hideTimeoutRef = useRef(); const prevAttachments = usePrevious(attachments); const renderingAttachments = attachments.length ? attachments : prevAttachments; + + const [shouldSendCompressed, setShouldSendCompressed] = useState( + shouldSuggestCompression ?? attachmentSettings.shouldCompress, + ); + const [shouldSendGrouped, setShouldSendGrouped] = useState(attachmentSettings.shouldSendGrouped); + const isOpen = Boolean(attachments.length); const [isHovered, markHovered, unmarkHovered] = useFlag(); - const isQuick = Boolean(renderingAttachments && renderingAttachments.every((a) => a.quick)); - const lang = useLang(); + + const [hasMedia, hasOnlyMedia] = useMemo(() => { + const onlyMedia = Boolean(renderingAttachments?.every((a) => a.quick || a.audio)); + if (onlyMedia) return [true, true]; + const oneMedia = Boolean(renderingAttachments?.some((a) => a.quick || a.audio)); + return [oneMedia, false]; + }, [renderingAttachments]); const { isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers, @@ -142,6 +171,13 @@ const AttachmentModal: FC = ({ useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]); + useEffect(() => { + if (isOpen) { + setShouldSendCompressed(shouldSuggestCompression ?? attachmentSettings.shouldCompress); + setShouldSendGrouped(attachmentSettings.shouldSendGrouped); + } + }, [attachmentSettings, isOpen, shouldSuggestCompression]); + const { isContextMenuOpen: isCustomSendMenuOpen, handleContextMenu, @@ -149,15 +185,32 @@ const AttachmentModal: FC = ({ handleContextMenuHide, } = useContextMenuHandlers(mainButtonRef, !canShowCustomSendMenu || !isOpen); - const sendAttachments = useCallback(() => { + const sendAttachments = useCallback((isSilent?: boolean, shouldSendScheduled?: boolean) => { if (isOpen) { - if (shouldSchedule) { - onSendScheduled(); - } else { - onSend(); - } + const send = (shouldSchedule || shouldSendScheduled) ? onSendScheduled + : isSilent ? onSendSilent : onSend; + send(shouldSendCompressed, shouldSendGrouped); + updateAttachmentSettings({ + shouldCompress: shouldSendCompressed, + shouldSendGrouped, + }); } - }, [isOpen, onSendScheduled, onSend, shouldSchedule]); + }, [ + isOpen, shouldSchedule, onSendScheduled, onSend, updateAttachmentSettings, shouldSendCompressed, shouldSendGrouped, + onSendSilent, + ]); + + const handleSendSilent = useCallback(() => { + sendAttachments(true); + }, [sendAttachments]); + + const handleSendClick = useCallback(() => { + sendAttachments(); + }, [sendAttachments]); + + const handleScheduleClick = useCallback(() => { + sendAttachments(false, true); + }, [sendAttachments]); const handleDragLeave = (e: React.DragEvent) => { const { relatedTarget: toTarget, target: fromTarget } = e; @@ -187,11 +240,9 @@ const AttachmentModal: FC = ({ const files = await getFilesFromDataTransferItems(dataTransfer.items); if (files?.length) { - const newFiles = Array.from(files).filter((file) => !isQuick || hasPreview(file)); - - onFileAppend(newFiles, isQuick); + onFileAppend(files); } - }, [isQuick, onFileAppend, unmarkHovered]); + }, [onFileAppend, unmarkHovered]); function handleDragOver(e: React.MouseEvent) { e.preventDefault(); @@ -202,18 +253,55 @@ const AttachmentModal: FC = ({ } } + const handleFileSelect = useCallback((e: Event) => { + const { files } = e.target as HTMLInputElement; + const validatedFiles = validateFiles(files); + + if (validatedFiles?.length) { + onFileAppend(validatedFiles); + } + }, [onFileAppend]); + + const handleDocumentSelect = useCallback(() => { + openSystemFilesDialog('*', (e) => handleFileSelect(e)); + }, [handleFileSelect]); + + const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => { + return ({ onTrigger, isOpen: isMenuOpen }) => ( + + ); + }, []); + const leftChars = useMemo(() => { const captionLeftBeforeLimit = captionLimit - getHtmlTextLength(caption); return captionLeftBeforeLimit <= CAPTION_SYMBOLS_LEFT_THRESHOLD ? captionLeftBeforeLimit : undefined; }, [caption, captionLimit]); + const isQuickGallery = shouldSendCompressed && hasOnlyMedia; + + const [areAllPhotos, areAllVideos, areAllAudios] = useMemo(() => { + if (!isQuickGallery || !renderingAttachments) return [false, false, false]; + const everyPhoto = renderingAttachments.every((a) => SUPPORTED_IMAGE_CONTENT_TYPES.has(a.mimeType)); + const everyVideo = renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType)); + const everyAudio = renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType)); + return [everyPhoto, everyVideo, everyAudio]; + }, [renderingAttachments, isQuickGallery]); + if (!renderingAttachments) { return undefined; } - const areAllPhotos = renderingAttachments.every((a) => SUPPORTED_IMAGE_CONTENT_TYPES.has(a.mimeType)); - const areAllVideos = renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType)); - const areAllAudios = renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType)); + const isMultiple = renderingAttachments.length > 1; let title = ''; if (areAllPhotos) { @@ -237,29 +325,42 @@ const AttachmentModal: FC = ({
{title}
-
- - {canShowCustomSendMenu && ( - + + {lang('Add')} + {hasMedia && ( + shouldSendCompressed ? ( + // eslint-disable-next-line react/jsx-no-bind + setShouldSendCompressed(false)}> + {lang(isMultiple ? 'Attachment.SendAsFiles' : 'Attachment.SendAsFile')} + + ) : ( + // eslint-disable-next-line react/jsx-no-bind + setShouldSendCompressed(true)}> + {isMultiple ? 'Send All as Media' : 'Send as Media'} + + ) )} -
+ {isMultiple && ( + shouldSendGrouped ? ( + setShouldSendGrouped(false)} + > + Ungroup All Media + + ) : ( + // eslint-disable-next-line react/jsx-no-bind + setShouldSendGrouped(true)}> + Group All Media + + ) + )} +
); } @@ -270,6 +371,7 @@ const AttachmentModal: FC = ({ onClose={onClear} header={renderHeader()} className={`AttachmentModal ${isHovered ? 'hovered' : ''}`} + noBackdropClose >
= ({ onDrop={handleFilesDrop} onDragOver={handleDragOver} onDragLeave={handleDragLeave} + onClick={unmarkHovered} data-attach-description={lang('Preview.Dragging.AddItems', 10)} data-dropzone > - {isQuick ? ( -
- {renderingAttachments.map((attachment) => ( - attachment.mimeType.startsWith('image/') - ? - :
- ) : ( -
- {renderingAttachments.map((attachment) => ( - - ))} -
- )} - +
+ {renderingAttachments.map((attachment, i) => ( + + ))} +
= ({ onCustomEmojiSelect={insertCustomEmoji} addRecentCustomEmoji={addRecentCustomEmoji} /> - +
+ +
+ + {canShowCustomSendMenu && ( + + )} +
+
); }; -export default memo(AttachmentModal); +export default memo(withGlobal( + (global, { chatId }): StateProps => { + const { + currentUserId, + recentEmojis, + customEmojis, + attachmentSettings, + } = global; + + const chat = selectChat(global, chatId); + const isChatWithSelf = selectIsChatWithSelf(global, chatId); + const { language, shouldSuggestCustomEmoji } = global.settings.byKey; + const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG]; + const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined; + + return { + isChatWithSelf, + currentUserId, + groupChatMembers: chat?.fullInfo?.members, + recentEmojis, + baseEmojiKeywords: baseEmojiKeywords?.keywords, + emojiKeywords: emojiKeywords?.keywords, + shouldSuggestCustomEmoji, + customEmojiForEmoji: customEmojis.forEmoji.stickers, + captionLimit: selectCurrentLimit(global, 'captionLength'), + attachmentSettings, + }; + }, +)(AttachmentModal)); diff --git a/src/components/middle/composer/AttachmentModalItem.module.scss b/src/components/middle/composer/AttachmentModalItem.module.scss new file mode 100644 index 000000000..b7a046bba --- /dev/null +++ b/src/components/middle/composer/AttachmentModalItem.module.scss @@ -0,0 +1,79 @@ +.root { + flex: 1 calc(50% - 0.5rem); + min-width: 0; + position: relative; + + display: flex; + align-items: center; +} + +.preview { + width: 100%; + height: 12rem; + object-fit: cover; + border-radius: var(--border-radius-default); +} + +.duration { + background: rgba(0, 0, 0, 0.25); + color: #fff; + font-size: 0.75rem; + position: absolute; + left: 0.1875rem; + top: 0.1875rem; + z-index: 1; + padding: 0 0.375rem; + border-radius: 0.75rem; + line-height: 1.125rem; + -webkit-user-select: none; + user-select: none; +} + +.single .preview { + height: auto; + max-height: 25rem; +} + +.no-grouping { + flex-basis: 100%; +} + +.file { + margin: 0.5rem; + flex-grow: 1; + min-width: 0; +} + +.overlay { + position: absolute; + bottom: 0.5rem; + right: 0.5rem; + background-color: rgba(0, 0, 0, 0.25); + border-radius: var(--border-radius-default); + + overflow: hidden; + + backdrop-filter: blur(10px); +} + +.action-item { + display: block; + padding: 0.3125rem; + color: white; + border-radius: var(--border-radius-messages-small); + + transition: 0.2s background-color ease-in-out; + + cursor: pointer; + + &:hover { + background-color: rgba(0, 0, 0, 0.15); + } +} + +.delete-file { + font-size: 1.5rem; + color: var(--color-text-secondary); + + margin-right: 1rem; +} diff --git a/src/components/middle/composer/AttachmentModalItem.tsx b/src/components/middle/composer/AttachmentModalItem.tsx new file mode 100644 index 000000000..876592b6d --- /dev/null +++ b/src/components/middle/composer/AttachmentModalItem.tsx @@ -0,0 +1,118 @@ +import React, { memo, useMemo } from '../../../lib/teact/teact'; + +import type { FC } from '../../../lib/teact/teact'; +import type { ApiAttachment } from '../../../api/types'; + +import { SUPPORTED_IMAGE_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES } from '../../../config'; +import { getFileExtension } from '../../common/helpers/documentInfo'; +import buildClassName from '../../../util/buildClassName'; +import { formatMediaDuration } from '../../../util/dateFormat'; + +import File from '../../common/File'; + +import styles from './AttachmentModalItem.module.scss'; + +type OwnProps = { + attachment: ApiAttachment; + className?: string; + shouldDisplayCompressed?: boolean; + shouldDisplayGrouped?: boolean; + isSingle?: boolean; + index: number; + onDelete?: (index: number) => void; +}; + +const AttachmentModalItem: FC = ({ + attachment, + className, + isSingle, + shouldDisplayCompressed, + shouldDisplayGrouped, + index, + onDelete, +}) => { + const displayType = getDisplayType(attachment, shouldDisplayCompressed); + + const content = useMemo(() => { + switch (displayType) { + case 'image': + return ( + + ); + case 'video': + return ( + <> + {Boolean(attachment.quick?.duration) && ( +
{formatMediaDuration(attachment.quick!.duration)}
+ )} +