Attacment Modal: Fix multiline height and better support for drag-n-drop (#1113)
This commit is contained in:
parent
800a23f731
commit
01933bfcb1
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<OwnProps> = ({
|
||||
attachments,
|
||||
caption,
|
||||
@ -47,11 +51,17 @@ const AttachmentModal: FC<OwnProps> = ({
|
||||
onCaptionUpdate,
|
||||
addRecentEmoji,
|
||||
onSend,
|
||||
onFileAppend,
|
||||
onClear,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const hideTimeoutRef = useRef<number>(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<OwnProps> = ({
|
||||
}
|
||||
}, [isOpen, onSend]);
|
||||
|
||||
const lang = useLang();
|
||||
const handleDragLeave = (e: React.DragEvent<HTMLElement>) => {
|
||||
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<HTMLDivElement>) => {
|
||||
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<HTMLDivElement, MouseEvent>) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (hideTimeoutRef.current) {
|
||||
window.clearTimeout(hideTimeoutRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
if (!renderingAttachments) {
|
||||
return undefined;
|
||||
@ -102,8 +156,6 @@ const AttachmentModal: FC<OwnProps> = ({
|
||||
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<OwnProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClear} header={renderHeader()} className="AttachmentModal">
|
||||
{isQuick ? (
|
||||
<div className="media-wrapper custom-scroll">
|
||||
{renderingAttachments.map((attachment) => (
|
||||
attachment.mimeType.startsWith('image/')
|
||||
? <img src={attachment.blobUrl} alt="" />
|
||||
: <video src={attachment.blobUrl} autoPlay muted loop />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="document-wrapper custom-scroll">
|
||||
{renderingAttachments.map((attachment) => (
|
||||
<File
|
||||
name={attachment.filename}
|
||||
extension={getFileExtension(attachment.filename, attachment.mimeType)}
|
||||
previewData={attachment.previewBlobUrl}
|
||||
size={attachment.size}
|
||||
smaller
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClear}
|
||||
header={renderHeader()}
|
||||
className={`AttachmentModal ${isHovered ? 'hovered' : ''}`}
|
||||
>
|
||||
<div
|
||||
className="drop-target"
|
||||
onDragEnter={markHovered}
|
||||
onDrop={handleFilesDrop}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
data-attach-description={lang('Preview.Dragging.AddItems', 10)}
|
||||
>
|
||||
{isQuick ? (
|
||||
<div className="media-wrapper custom-scroll">
|
||||
{renderingAttachments.map((attachment) => (
|
||||
attachment.mimeType.startsWith('image/')
|
||||
? <img src={attachment.blobUrl} alt="" />
|
||||
: <video src={attachment.blobUrl} autoPlay muted loop />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="document-wrapper custom-scroll">
|
||||
{renderingAttachments.map((attachment) => (
|
||||
<File
|
||||
name={attachment.filename}
|
||||
extension={getFileExtension(attachment.filename, attachment.mimeType)}
|
||||
previewData={attachment.previewBlobUrl}
|
||||
size={attachment.size}
|
||||
smaller
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="attachment-caption-wrapper">
|
||||
<MentionTooltip
|
||||
isOpen={isMentionTooltipOpen}
|
||||
onClose={closeMentionTooltip}
|
||||
filter={mentionFilter}
|
||||
onInsertUserName={insertMention}
|
||||
filteredChatMembers={mentionFilteredMembers}
|
||||
usersById={usersById}
|
||||
/>
|
||||
<EmojiTooltip
|
||||
isOpen={isEmojiTooltipOpen}
|
||||
emojis={filteredEmojis}
|
||||
onClose={closeEmojiTooltip}
|
||||
onEmojiSelect={insertEmoji}
|
||||
addRecentEmoji={addRecentEmoji}
|
||||
/>
|
||||
<MessageInput
|
||||
id="caption-input-text"
|
||||
html={caption}
|
||||
editableInputId={EDITABLE_INPUT_MODAL_ID}
|
||||
placeholder={lang('Caption')}
|
||||
onUpdate={onCaptionUpdate}
|
||||
onSend={onSend}
|
||||
shouldSetFocus={isOpen}
|
||||
/>
|
||||
<div className="attachment-caption-wrapper">
|
||||
<MentionTooltip
|
||||
isOpen={isMentionTooltipOpen}
|
||||
onClose={closeMentionTooltip}
|
||||
filter={mentionFilter}
|
||||
onInsertUserName={insertMention}
|
||||
filteredChatMembers={mentionFilteredMembers}
|
||||
usersById={usersById}
|
||||
/>
|
||||
<EmojiTooltip
|
||||
isOpen={isEmojiTooltipOpen}
|
||||
emojis={filteredEmojis}
|
||||
onClose={closeEmojiTooltip}
|
||||
onEmojiSelect={insertEmoji}
|
||||
addRecentEmoji={addRecentEmoji}
|
||||
/>
|
||||
<MessageInput
|
||||
id="caption-input-text"
|
||||
html={caption}
|
||||
editableInputId={EDITABLE_INPUT_MODAL_ID}
|
||||
placeholder={lang('Caption')}
|
||||
onUpdate={onCaptionUpdate}
|
||||
onSend={onSend}
|
||||
shouldSetFocus={isOpen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@ -384,6 +384,13 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
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<OwnProps & StateProps & DispatchProps> = ({
|
||||
onCaptionUpdate={setHtml}
|
||||
addRecentEmoji={addRecentEmoji}
|
||||
onSend={shouldSchedule ? openCalendar : handleSend}
|
||||
onFileAppend={handleAppendFiles}
|
||||
onClear={handleClearAttachment}
|
||||
/>
|
||||
<PollModal
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
&.hovered .target-content {
|
||||
color: #63A2E3;
|
||||
color: var(--color-primary);
|
||||
background-image: var(--drag-target-border-hovered);
|
||||
}
|
||||
|
||||
|
||||
@ -97,6 +97,10 @@ const MessageInput: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const [textFormatterAnchorPosition, setTextFormatterAnchorPosition] = useState<IAnchorPosition>();
|
||||
const [selectedRange, setSelectedRange] = useState<Range>();
|
||||
|
||||
useEffect(() => {
|
||||
updateInputHeight(false);
|
||||
}, []);
|
||||
|
||||
useLayoutEffectWithPrevDeps(([prevHtml]) => {
|
||||
if (html !== inputRef.current!.innerHTML) {
|
||||
inputRef.current!.innerHTML = html;
|
||||
|
||||
@ -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';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user