import React, { memo, useCallback, useEffect, useRef, } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact'; import type { ApiAttachment, ApiChatMember } from '../../../api/types'; import { CONTENT_TYPES_WITH_PREVIEW, 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 captureEscKeyListener from '../../../util/captureEscKeyListener'; import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems'; 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 useContextMenuHandlers from '../../../hooks/useContextMenuHandlers'; import { useStateRef } from '../../../hooks/useStateRef'; 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 './AttachmentModal.scss'; export type OwnProps = { chatId: string; threadId: number; attachments: ApiAttachment[]; caption: string; canShowCustomSendMenu?: boolean; isReady?: boolean; isChatWithSelf?: boolean; currentUserId?: string; groupChatMembers?: ApiChatMember[]; recentEmojis: string[]; baseEmojiKeywords?: Record; emojiKeywords?: Record; shouldSchedule?: boolean; captionLimit: number; addRecentEmoji: AnyToVoidFunction; onCaptionUpdate: (html: string) => void; onSend: () => void; onFileAppend: (files: File[], isQuick: boolean) => void; onClear: () => void; onSendSilent: () => void; onSendScheduled: () => void; }; const DROP_LEAVE_TIMEOUT_MS = 150; const AttachmentModal: FC = ({ chatId, threadId, attachments, caption, canShowCustomSendMenu, captionLimit, isReady, isChatWithSelf, currentUserId, groupChatMembers, recentEmojis, baseEmojiKeywords, emojiKeywords, shouldSchedule, addRecentEmoji, onCaptionUpdate, onSend, onFileAppend, onClear, onSendSilent, onSendScheduled, }) => { 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 isOpen = Boolean(attachments.length); const [isHovered, markHovered, unmarkHovered] = useFlag(); const isQuick = Boolean(renderingAttachments && renderingAttachments.every((a) => a.quick)); const lang = useLang(); const { isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers, } = useMentionTooltip( isOpen, captionRef, onCaptionUpdate, EDITABLE_INPUT_MODAL_ID, groupChatMembers, undefined, currentUserId, ); const { isEmojiTooltipOpen, closeEmojiTooltip, filteredEmojis, insertEmoji, } = useEmojiTooltip( isOpen, captionRef, recentEmojis, EDITABLE_INPUT_MODAL_ID, onCaptionUpdate, baseEmojiKeywords, emojiKeywords, !isReady, ); useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]); const { isContextMenuOpen: isCustomSendMenuOpen, handleContextMenu, handleContextMenuClose, handleContextMenuHide, } = useContextMenuHandlers(mainButtonRef, !canShowCustomSendMenu || !isOpen); const sendAttachments = useCallback(() => { if (isOpen) { if (shouldSchedule) { onSendScheduled(); } else { onSend(); } } }, [isOpen, onSendScheduled, onSend, shouldSchedule]); 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(async (e: React.DragEvent) => { e.preventDefault(); unmarkHovered(); const { dataTransfer } = e; const files = await getFilesFromDataTransferItems(dataTransfer.items); if (files?.length) { const newFiles = isQuick ? Array.from(files).filter((file) => { return file.type && CONTENT_TYPES_WITH_PREVIEW.has(file.type); }) : Array.from(files); onFileAppend(newFiles, isQuick); } }, [isQuick, onFileAppend, unmarkHovered]); function handleDragOver(e: React.MouseEvent) { e.preventDefault(); if (hideTimeoutRef.current) { window.clearTimeout(hideTimeoutRef.current); hideTimeoutRef.current = undefined; } } 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)); let title = ''; if (areAllPhotos) { title = lang('PreviewSender.SendPhoto', renderingAttachments.length, 'i'); } else if (areAllVideos) { title = lang('PreviewSender.SendVideo', renderingAttachments.length, 'i'); } else if (areAllAudios) { title = lang('PreviewSender.SendAudio', renderingAttachments.length, 'i'); } else { title = lang('PreviewSender.SendFile', renderingAttachments.length, 'i'); } function renderHeader() { if (!renderingAttachments) { return undefined; } return (
{title}
{canShowCustomSendMenu && ( )}
); } const leftChars = (captionLimit - caption.length) <= 100 ? captionLimit - caption.length : undefined; return (
{isQuick ? (
{renderingAttachments.map((attachment) => ( attachment.mimeType.startsWith('image/') ? :
) : (
{renderingAttachments.map((attachment) => ( ))}
)}
); }; export default memo(AttachmentModal);