From 01933bfcb1d605d79a7957ed513478397316bb38 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 24 May 2021 13:10:03 +0300 Subject: [PATCH] Attacment Modal: Fix multiline height and better support for drag-n-drop (#1113) --- .../middle/composer/AttachmentModal.scss | 54 ++++++ .../middle/composer/AttachmentModal.tsx | 170 ++++++++++++------ src/components/middle/composer/Composer.tsx | 8 + .../middle/composer/DropTarget.scss | 2 +- .../middle/composer/MessageInput.tsx | 4 + src/config.ts | 4 +- 6 files changed, 187 insertions(+), 55 deletions(-) diff --git a/src/components/middle/composer/AttachmentModal.scss b/src/components/middle/composer/AttachmentModal.scss index 52185daa5..efa1e2332 100644 --- a/src/components/middle/composer/AttachmentModal.scss +++ b/src/components/middle/composer/AttachmentModal.scss @@ -74,4 +74,58 @@ z-index: 0; } } + + .drop-target { + position: relative; + + &::before, + &::after { + content: ''; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + border-radius: var(--border-radius-default); + pointer-events: none; + + opacity: 0; + transition: 250ms opacity; + z-index: 1; + } + + &::before { + background-image: var(--drag-target-border-hovered); + background-color: var(--color-background); + } + + &::after { + content: attr(data-attach-description); + display: flex; + justify-content: center; + align-items: center; + color: var(--color-primary); + } + } + + &.hovered { + .drop-target::before { + opacity: .95; + } + + .drop-target::after { + opacity: 1; + } + + .attachment-caption-wrapper, + .document-wrapper, + .media-wrapper { + pointer-events: none; + } + + .document-wrapper, + .media-wrapper { + border-radius: var(--border-radius-default); + } + } } diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index 538106ba6..f44e6e100 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -1,9 +1,9 @@ import React, { - FC, memo, useCallback, useEffect, + FC, memo, useCallback, useEffect, useRef, } from '../../../lib/teact/teact'; import { ApiAttachment, ApiChatMember, ApiUser } from '../../../api/types'; -import { EDITABLE_INPUT_MODAL_ID } from '../../../config'; +import { CONTENT_TYPES_FOR_QUICK_UPLOAD, EDITABLE_INPUT_MODAL_ID } from '../../../config'; import { getFileExtension } from '../../common/helpers/documentInfo'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; @@ -11,6 +11,7 @@ import usePrevious from '../../../hooks/usePrevious'; import useMentionTooltip from './hooks/useMentionTooltip'; import useEmojiTooltip from './hooks/useEmojiTooltip'; import useLang from '../../../hooks/useLang'; +import useFlag from '../../../hooks/useFlag'; import Button from '../../ui/Button'; import Modal from '../../ui/Modal'; @@ -33,9 +34,12 @@ export type OwnProps = { addRecentEmoji: AnyToVoidFunction; onCaptionUpdate: (html: string) => void; onSend: () => void; + onFileAppend: (files: File[], isQuick: boolean) => void; onClear: () => void; }; +const DROP_LEAVE_TIMEOUT_MS = 150; + const AttachmentModal: FC = ({ attachments, caption, @@ -47,11 +51,17 @@ const AttachmentModal: FC = ({ onCaptionUpdate, addRecentEmoji, onSend, + onFileAppend, onClear, }) => { + // eslint-disable-next-line no-null/no-null + const hideTimeoutRef = useRef(null); const prevAttachments = usePrevious(attachments); const renderingAttachments = attachments.length ? attachments : prevAttachments; const isOpen = Boolean(attachments.length); + const [isHovered, markHovered, unmarkHovered] = useFlag(); + const isQuick = renderingAttachments && renderingAttachments.every((a) => a.quick); + const lang = useLang(); const { isMentionTooltipOpen, mentionFilter, @@ -84,7 +94,51 @@ const AttachmentModal: FC = ({ } }, [isOpen, onSend]); - const lang = useLang(); + const handleDragLeave = (e: React.DragEvent) => { + const { relatedTarget: toTarget, target: fromTarget } = e; + + // Esc button pressed during drag event + if ((fromTarget as HTMLDivElement).matches('.drop-target') && !toTarget) { + hideTimeoutRef.current = window.setTimeout(unmarkHovered, DROP_LEAVE_TIMEOUT_MS); + } + + // Prevent DragLeave event from firing when the pointer moves inside the AttachmentModal drop target + if (fromTarget && (fromTarget as HTMLElement).closest('.AttachmentModal.hovered')) { + return; + } + + if (toTarget) { + e.stopPropagation(); + } + + unmarkHovered(); + }; + + const handleFilesDrop = useCallback((e: React.DragEvent) => { + e.preventDefault(); + unmarkHovered(); + + const { dataTransfer: { files } } = e; + + if (files && files.length) { + const newFiles = isQuick + ? Array.from(files).filter((file) => { + return file.type && CONTENT_TYPES_FOR_QUICK_UPLOAD.includes(file.type); + }) + : Array.from(files); + + onFileAppend(newFiles, false); + } + }, [isQuick, onFileAppend, unmarkHovered]); + + function handleDragOver(e: React.MouseEvent) { + e.preventDefault(); + e.stopPropagation(); + + if (hideTimeoutRef.current) { + window.clearTimeout(hideTimeoutRef.current); + } + } if (!renderingAttachments) { return undefined; @@ -102,8 +156,6 @@ const AttachmentModal: FC = ({ title = renderingAttachments.length === 1 ? 'Send File' : `Send ${renderingAttachments.length} Files`; } - const isQuick = renderingAttachments.every((a) => a.quick); - function renderHeader() { if (!renderingAttachments) { return undefined; @@ -128,54 +180,68 @@ const AttachmentModal: FC = ({ } return ( - - {isQuick ? ( -
- {renderingAttachments.map((attachment) => ( - attachment.mimeType.startsWith('image/') - ? - :
- ) : ( -
- {renderingAttachments.map((attachment) => ( - - ))} -
- )} + +
+ {isQuick ? ( +
+ {renderingAttachments.map((attachment) => ( + attachment.mimeType.startsWith('image/') + ? + :
+ ) : ( +
+ {renderingAttachments.map((attachment) => ( + + ))} +
+ )} -
- - - +
+ + + +
); diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx index f5f9c8796..b4ec7fd44 100644 --- a/src/components/middle/composer/Composer.tsx +++ b/src/components/middle/composer/Composer.tsx @@ -384,6 +384,13 @@ const Composer: FC = ({ setAttachments(await Promise.all(files.map((file) => buildAttachment(file.name, file, isQuick)))); }, []); + const handleAppendFiles = useCallback(async (files: File[], isQuick: boolean) => { + setAttachments([ + ...attachments, + ...await Promise.all(files.map((file) => buildAttachment(file.name, file, isQuick))), + ]); + }, [attachments]); + const handleClearAttachment = useCallback(() => { setAttachments([]); }, []); @@ -683,6 +690,7 @@ const Composer: FC = ({ onCaptionUpdate={setHtml} addRecentEmoji={addRecentEmoji} onSend={shouldSchedule ? openCalendar : handleSend} + onFileAppend={handleAppendFiles} onClear={handleClearAttachment} /> = ({ const [textFormatterAnchorPosition, setTextFormatterAnchorPosition] = useState(); const [selectedRange, setSelectedRange] = useState(); + useEffect(() => { + updateInputHeight(false); + }, []); + useLayoutEffectWithPrevDeps(([prevHtml]) => { if (html !== inputRef.current!.innerHTML) { inputRef.current!.innerHTML = html; diff --git a/src/config.ts b/src/config.ts index 90a036db7..cb20f8a3b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -28,7 +28,7 @@ export const MEDIA_PROGRESSIVE_CACHE_DISABLED = false; export const MEDIA_PROGRESSIVE_CACHE_NAME = 'tt-media-progressive'; export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg'; -export const LANG_CACHE_NAME = 'tt-lang-packs-v2'; +export const LANG_CACHE_NAME = 'tt-lang-packs-v3'; export const ASSET_CACHE_NAME = 'tt-assets'; export const API_UPDATE_THROTTLE = 300; @@ -124,7 +124,7 @@ export const MAX_MEDIA_FILES_FOR_ALBUM = 10; export const MAX_ACTIVE_PINNED_CHATS = 5; export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE; export const DEFAULT_LANG_PACK = 'android'; -export const LANG_PACKS = ['android', 'ios', 'tdesktop']; +export const LANG_PACKS = ['android', 'ios', 'tdesktop', 'macos']; export const TIPS_USERNAME = 'TelegramTips'; export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time'; export const DARK_THEME_BG_COLOR = '#0F0F0F';