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;
|
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, {
|
import React, {
|
||||||
FC, memo, useCallback, useEffect,
|
FC, memo, useCallback, useEffect, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import { ApiAttachment, ApiChatMember, ApiUser } from '../../../api/types';
|
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 { getFileExtension } from '../../common/helpers/documentInfo';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
@ -11,6 +11,7 @@ import usePrevious from '../../../hooks/usePrevious';
|
|||||||
import useMentionTooltip from './hooks/useMentionTooltip';
|
import useMentionTooltip from './hooks/useMentionTooltip';
|
||||||
import useEmojiTooltip from './hooks/useEmojiTooltip';
|
import useEmojiTooltip from './hooks/useEmojiTooltip';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import Modal from '../../ui/Modal';
|
import Modal from '../../ui/Modal';
|
||||||
@ -33,9 +34,12 @@ export type OwnProps = {
|
|||||||
addRecentEmoji: AnyToVoidFunction;
|
addRecentEmoji: AnyToVoidFunction;
|
||||||
onCaptionUpdate: (html: string) => void;
|
onCaptionUpdate: (html: string) => void;
|
||||||
onSend: () => void;
|
onSend: () => void;
|
||||||
|
onFileAppend: (files: File[], isQuick: boolean) => void;
|
||||||
onClear: () => void;
|
onClear: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DROP_LEAVE_TIMEOUT_MS = 150;
|
||||||
|
|
||||||
const AttachmentModal: FC<OwnProps> = ({
|
const AttachmentModal: FC<OwnProps> = ({
|
||||||
attachments,
|
attachments,
|
||||||
caption,
|
caption,
|
||||||
@ -47,11 +51,17 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
onCaptionUpdate,
|
onCaptionUpdate,
|
||||||
addRecentEmoji,
|
addRecentEmoji,
|
||||||
onSend,
|
onSend,
|
||||||
|
onFileAppend,
|
||||||
onClear,
|
onClear,
|
||||||
}) => {
|
}) => {
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const hideTimeoutRef = useRef<number>(null);
|
||||||
const prevAttachments = usePrevious(attachments);
|
const prevAttachments = usePrevious(attachments);
|
||||||
const renderingAttachments = attachments.length ? attachments : prevAttachments;
|
const renderingAttachments = attachments.length ? attachments : prevAttachments;
|
||||||
const isOpen = Boolean(attachments.length);
|
const isOpen = Boolean(attachments.length);
|
||||||
|
const [isHovered, markHovered, unmarkHovered] = useFlag();
|
||||||
|
const isQuick = renderingAttachments && renderingAttachments.every((a) => a.quick);
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isMentionTooltipOpen, mentionFilter,
|
isMentionTooltipOpen, mentionFilter,
|
||||||
@ -84,7 +94,51 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isOpen, onSend]);
|
}, [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) {
|
if (!renderingAttachments) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -102,8 +156,6 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
title = renderingAttachments.length === 1 ? 'Send File' : `Send ${renderingAttachments.length} Files`;
|
title = renderingAttachments.length === 1 ? 'Send File' : `Send ${renderingAttachments.length} Files`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isQuick = renderingAttachments.every((a) => a.quick);
|
|
||||||
|
|
||||||
function renderHeader() {
|
function renderHeader() {
|
||||||
if (!renderingAttachments) {
|
if (!renderingAttachments) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -128,54 +180,68 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClose={onClear} header={renderHeader()} className="AttachmentModal">
|
<Modal
|
||||||
{isQuick ? (
|
isOpen={isOpen}
|
||||||
<div className="media-wrapper custom-scroll">
|
onClose={onClear}
|
||||||
{renderingAttachments.map((attachment) => (
|
header={renderHeader()}
|
||||||
attachment.mimeType.startsWith('image/')
|
className={`AttachmentModal ${isHovered ? 'hovered' : ''}`}
|
||||||
? <img src={attachment.blobUrl} alt="" />
|
>
|
||||||
: <video src={attachment.blobUrl} autoPlay muted loop />
|
<div
|
||||||
))}
|
className="drop-target"
|
||||||
</div>
|
onDragEnter={markHovered}
|
||||||
) : (
|
onDrop={handleFilesDrop}
|
||||||
<div className="document-wrapper custom-scroll">
|
onDragOver={handleDragOver}
|
||||||
{renderingAttachments.map((attachment) => (
|
onDragLeave={handleDragLeave}
|
||||||
<File
|
data-attach-description={lang('Preview.Dragging.AddItems', 10)}
|
||||||
name={attachment.filename}
|
>
|
||||||
extension={getFileExtension(attachment.filename, attachment.mimeType)}
|
{isQuick ? (
|
||||||
previewData={attachment.previewBlobUrl}
|
<div className="media-wrapper custom-scroll">
|
||||||
size={attachment.size}
|
{renderingAttachments.map((attachment) => (
|
||||||
smaller
|
attachment.mimeType.startsWith('image/')
|
||||||
/>
|
? <img src={attachment.blobUrl} alt="" />
|
||||||
))}
|
: <video src={attachment.blobUrl} autoPlay muted loop />
|
||||||
</div>
|
))}
|
||||||
)}
|
</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">
|
<div className="attachment-caption-wrapper">
|
||||||
<MentionTooltip
|
<MentionTooltip
|
||||||
isOpen={isMentionTooltipOpen}
|
isOpen={isMentionTooltipOpen}
|
||||||
onClose={closeMentionTooltip}
|
onClose={closeMentionTooltip}
|
||||||
filter={mentionFilter}
|
filter={mentionFilter}
|
||||||
onInsertUserName={insertMention}
|
onInsertUserName={insertMention}
|
||||||
filteredChatMembers={mentionFilteredMembers}
|
filteredChatMembers={mentionFilteredMembers}
|
||||||
usersById={usersById}
|
usersById={usersById}
|
||||||
/>
|
/>
|
||||||
<EmojiTooltip
|
<EmojiTooltip
|
||||||
isOpen={isEmojiTooltipOpen}
|
isOpen={isEmojiTooltipOpen}
|
||||||
emojis={filteredEmojis}
|
emojis={filteredEmojis}
|
||||||
onClose={closeEmojiTooltip}
|
onClose={closeEmojiTooltip}
|
||||||
onEmojiSelect={insertEmoji}
|
onEmojiSelect={insertEmoji}
|
||||||
addRecentEmoji={addRecentEmoji}
|
addRecentEmoji={addRecentEmoji}
|
||||||
/>
|
/>
|
||||||
<MessageInput
|
<MessageInput
|
||||||
id="caption-input-text"
|
id="caption-input-text"
|
||||||
html={caption}
|
html={caption}
|
||||||
editableInputId={EDITABLE_INPUT_MODAL_ID}
|
editableInputId={EDITABLE_INPUT_MODAL_ID}
|
||||||
placeholder={lang('Caption')}
|
placeholder={lang('Caption')}
|
||||||
onUpdate={onCaptionUpdate}
|
onUpdate={onCaptionUpdate}
|
||||||
onSend={onSend}
|
onSend={onSend}
|
||||||
shouldSetFocus={isOpen}
|
shouldSetFocus={isOpen}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -384,6 +384,13 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
setAttachments(await Promise.all(files.map((file) => buildAttachment(file.name, file, isQuick))));
|
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(() => {
|
const handleClearAttachment = useCallback(() => {
|
||||||
setAttachments([]);
|
setAttachments([]);
|
||||||
}, []);
|
}, []);
|
||||||
@ -683,6 +690,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
onCaptionUpdate={setHtml}
|
onCaptionUpdate={setHtml}
|
||||||
addRecentEmoji={addRecentEmoji}
|
addRecentEmoji={addRecentEmoji}
|
||||||
onSend={shouldSchedule ? openCalendar : handleSend}
|
onSend={shouldSchedule ? openCalendar : handleSend}
|
||||||
|
onFileAppend={handleAppendFiles}
|
||||||
onClear={handleClearAttachment}
|
onClear={handleClearAttachment}
|
||||||
/>
|
/>
|
||||||
<PollModal
|
<PollModal
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.hovered .target-content {
|
&.hovered .target-content {
|
||||||
color: #63A2E3;
|
color: var(--color-primary);
|
||||||
background-image: var(--drag-target-border-hovered);
|
background-image: var(--drag-target-border-hovered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -97,6 +97,10 @@ const MessageInput: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const [textFormatterAnchorPosition, setTextFormatterAnchorPosition] = useState<IAnchorPosition>();
|
const [textFormatterAnchorPosition, setTextFormatterAnchorPosition] = useState<IAnchorPosition>();
|
||||||
const [selectedRange, setSelectedRange] = useState<Range>();
|
const [selectedRange, setSelectedRange] = useState<Range>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateInputHeight(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useLayoutEffectWithPrevDeps(([prevHtml]) => {
|
useLayoutEffectWithPrevDeps(([prevHtml]) => {
|
||||||
if (html !== inputRef.current!.innerHTML) {
|
if (html !== inputRef.current!.innerHTML) {
|
||||||
inputRef.current!.innerHTML = html;
|
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_PROGRESSIVE_CACHE_NAME = 'tt-media-progressive';
|
||||||
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
||||||
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
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 ASSET_CACHE_NAME = 'tt-assets';
|
||||||
|
|
||||||
export const API_UPDATE_THROTTLE = 300;
|
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 MAX_ACTIVE_PINNED_CHATS = 5;
|
||||||
export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE;
|
export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE;
|
||||||
export const DEFAULT_LANG_PACK = 'android';
|
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 TIPS_USERNAME = 'TelegramTips';
|
||||||
export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time';
|
export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time';
|
||||||
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user