Composer: Support uploading folders (#1682)
This commit is contained in:
parent
80fb334d3d
commit
d2d70a1952
@ -254,7 +254,7 @@ const MiddleColumn: FC<StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { items } = e.dataTransfer || {};
|
const { items } = e.dataTransfer || {};
|
||||||
const shouldDrawQuick = items && Array.from(items)
|
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)
|
// Filter unnecessary element for drag and drop images in Firefox (https://github.com/Ajaxy/telegram-tt/issues/49)
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#image
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#image
|
||||||
.filter((item) => item.type !== 'text/uri-list')
|
.filter((item) => item.type !== 'text/uri-list')
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import React, {
|
|||||||
|
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
|
||||||
|
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
@ -38,13 +39,23 @@ const DropArea: FC<OwnProps> = ({
|
|||||||
|
|
||||||
useEffect(() => (isOpen ? captureEscKeyListener(onHide) : undefined), [isOpen, onHide]);
|
useEffect(() => (isOpen ? captureEscKeyListener(onHide) : undefined), [isOpen, onHide]);
|
||||||
|
|
||||||
const handleFilesDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleFilesDrop = useCallback(async (e: React.DragEvent<HTMLDivElement>) => {
|
||||||
const { dataTransfer: dt } = e;
|
const { dataTransfer: dt } = e;
|
||||||
|
let files: File[] = [];
|
||||||
|
|
||||||
|
if (dt.items && dt.items.length > 0) {
|
||||||
|
const folderFiles = await getFilesFromDataTransferItems(dt.items);
|
||||||
|
if (folderFiles.length) {
|
||||||
|
files = files.concat(folderFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (dt.files && dt.files.length > 0) {
|
if (dt.files && dt.files.length > 0) {
|
||||||
onHide();
|
files = files.concat(Array.from(dt.files));
|
||||||
onFileSelect(Array.from(dt.files), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onHide();
|
||||||
|
onFileSelect(files, false);
|
||||||
}, [onFileSelect, onHide]);
|
}, [onFileSelect, onHide]);
|
||||||
|
|
||||||
const handleQuickFilesDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleQuickFilesDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
export default async function getFilesFromDataTransferItems(dataTransferItems: DataTransferItemList) {
|
||||||
|
const files: File[] = [];
|
||||||
|
|
||||||
|
function traverseFileTreePromise(entry: FileSystemEntry | File) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (entry instanceof File) {
|
||||||
|
files.push(entry);
|
||||||
|
resolve(entry);
|
||||||
|
} else if (entry.isFile) {
|
||||||
|
(entry as FileSystemFileEntry).file((file) => {
|
||||||
|
files.push(file);
|
||||||
|
resolve(file);
|
||||||
|
});
|
||||||
|
} else if (entry.isDirectory) {
|
||||||
|
let dirReader = (entry as FileSystemDirectoryEntry).createReader();
|
||||||
|
dirReader.readEntries((entries) => {
|
||||||
|
let entriesPromises = [];
|
||||||
|
for (let entr of entries) {
|
||||||
|
entriesPromises.push(traverseFileTreePromise(entr));
|
||||||
|
}
|
||||||
|
resolve(Promise.all(entriesPromises));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let entriesPromises = [];
|
||||||
|
for (let item of dataTransferItems) {
|
||||||
|
if (item.kind === 'file') {
|
||||||
|
const entry = item.webkitGetAsEntry() || item.getAsFile();
|
||||||
|
if (entry) {
|
||||||
|
entriesPromises.push(traverseFileTreePromise(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(entriesPromises);
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
@ -3,6 +3,7 @@ import { ApiAttachment, ApiMessage } from '../../../../api/types';
|
|||||||
|
|
||||||
import buildAttachment from '../helpers/buildAttachment';
|
import buildAttachment from '../helpers/buildAttachment';
|
||||||
import { EDITABLE_INPUT_ID, EDITABLE_INPUT_MODAL_ID } from '../../../../config';
|
import { EDITABLE_INPUT_ID, EDITABLE_INPUT_MODAL_ID } from '../../../../config';
|
||||||
|
import getFilesFromDataTransferItems from '../helpers/getFilesFromDataTransferItems';
|
||||||
|
|
||||||
const CLIPBOARD_ACCEPTED_TYPES = ['image/png', 'image/jpeg', 'image/gif'];
|
const CLIPBOARD_ACCEPTED_TYPES = ['image/png', 'image/jpeg', 'image/gif'];
|
||||||
const MAX_MESSAGE_LENGTH = 4096;
|
const MAX_MESSAGE_LENGTH = 4096;
|
||||||
@ -23,24 +24,25 @@ const useClipboardPaste = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { items } = e.clipboardData;
|
|
||||||
const media = Array.from(items)
|
|
||||||
.find((item) => CLIPBOARD_ACCEPTED_TYPES.includes(item.type) && item.kind === 'file');
|
|
||||||
const file = media && media.getAsFile();
|
|
||||||
const pastedText = e.clipboardData.getData('text').substring(0, MAX_MESSAGE_LENGTH);
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!file && !pastedText) {
|
const { items } = e.clipboardData;
|
||||||
|
let files: File[] = [];
|
||||||
|
|
||||||
|
if (items.length > 0) {
|
||||||
|
files = await getFilesFromDataTransferItems(items);
|
||||||
|
}
|
||||||
|
const pastedText = e.clipboardData.getData('text').substring(0, MAX_MESSAGE_LENGTH);
|
||||||
|
|
||||||
|
if (files.length === 0 && !pastedText) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file && !editedMessage) {
|
if (files.length > 0 && !editedMessage) {
|
||||||
const attachment = await buildAttachment(file.name, file, true);
|
const newAttachments = await Promise.all(files.map((file) => {
|
||||||
setAttachments((attachments) => [
|
return buildAttachment(file.name, file, files.length === 1 && CLIPBOARD_ACCEPTED_TYPES.includes(file.type));
|
||||||
...attachments,
|
}));
|
||||||
attachment,
|
setAttachments((attachments) => attachments.concat(newAttachments));
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pastedText) {
|
if (pastedText) {
|
||||||
|
|||||||
@ -482,7 +482,7 @@
|
|||||||
&.is-reply .media-inner,
|
&.is-reply .media-inner,
|
||||||
&.force-sender-name .Album,
|
&.force-sender-name .Album,
|
||||||
&.is-reply .Album,
|
&.is-reply .Album,
|
||||||
.message-title ~ .media-inner:not(.RoundVideo) {
|
.message-title ~ .media-inner {
|
||||||
margin-top: 0.375rem;
|
margin-top: 0.375rem;
|
||||||
margin-bottom: -0.375rem;
|
margin-bottom: -0.375rem;
|
||||||
|
|
||||||
@ -491,6 +491,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.text) .RoundVideo {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Moved below .is-reply to overwrite its styles
|
// Moved below .is-reply to overwrite its styles
|
||||||
&.text .media-inner,
|
&.text .media-inner,
|
||||||
&.text .Album {
|
&.text .Album {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user