@@ -0,0 +1,206 @@
|
||||
type TreeItem = { [key: string]: File | TreeItem };
|
||||
|
||||
export type FileTreeObject = {
|
||||
tree: TreeItem;
|
||||
documentsCount: number;
|
||||
totalSize: number;
|
||||
};
|
||||
|
||||
export const getFilesTree = (
|
||||
event: Event & { dataTransfer: DataTransfer },
|
||||
fcb?: (tree: any, documentsCount: number, totalSize: number) => void,
|
||||
): Promise<FileTreeObject> => {
|
||||
return new Promise<FileTreeObject>(function (resolve) {
|
||||
function newDirectoryApi(input: DataTransfer, cb: (files?: File[], paths?: string[]) => void) {
|
||||
const fd: any[] = [],
|
||||
files: any[] = [];
|
||||
const iterate = function (entries: any[], path: string, resolve: (v: any[]) => void) {
|
||||
const promises: any[] = [];
|
||||
entries.forEach(function (entry: any) {
|
||||
promises.push(
|
||||
new Promise(function (resolve) {
|
||||
if ('getFilesAndDirectories' in entry) {
|
||||
entry.getFilesAndDirectories().then(function (entries: any[]) {
|
||||
iterate(entries, entry.path + '/', resolve);
|
||||
});
|
||||
} else {
|
||||
if (entry.name) {
|
||||
const p = (path + entry.name).replace(/^[/\\]/, '');
|
||||
fd.push(entry);
|
||||
files.push(p);
|
||||
|
||||
if (files.length > 1000000) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
resolve(true);
|
||||
}
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
if (files.length > 1000000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Promise.all(promises).then(resolve);
|
||||
};
|
||||
(input as any).getFilesAndDirectories().then(function (entries: any) {
|
||||
new Promise(function (resolve) {
|
||||
iterate(entries, '/', resolve);
|
||||
}).then(cb.bind(null, fd, files));
|
||||
});
|
||||
}
|
||||
|
||||
// old prefixed API implemented in Chrome 11+ as well as array fallback
|
||||
function arrayApi(input: DataTransfer, cb: (files?: File[], paths?: string[]) => void) {
|
||||
const fd: any[] = [],
|
||||
files: any[] = [];
|
||||
[].slice.call(input.files).forEach(function (file: File) {
|
||||
fd.push(file);
|
||||
files.push(file.webkitRelativePath || file.name);
|
||||
|
||||
if (files.length > 1000000) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (files.length > 1000000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cb(fd, files);
|
||||
}
|
||||
|
||||
// old drag and drop API implemented in Chrome 11+
|
||||
function entriesApi(
|
||||
items: DataTransferItemList,
|
||||
cb: (files?: File[], paths?: string[]) => void,
|
||||
) {
|
||||
const fd: any[] = [],
|
||||
files: any[] = [],
|
||||
rootPromises: any[] = [];
|
||||
|
||||
function readEntries(entry: any, reader: any, oldEntries: any, cb: any) {
|
||||
const dirReader = reader || entry.createReader();
|
||||
dirReader.readEntries(function (entries: any) {
|
||||
const newEntries = oldEntries ? oldEntries.concat(entries) : entries;
|
||||
if (entries.length) {
|
||||
setTimeout(readEntries.bind(null, entry, dirReader, newEntries, cb), 0);
|
||||
} else {
|
||||
cb(newEntries);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function readDirectory(entry: any, path: null | string, resolve: (v: any) => void) {
|
||||
if (!path) path = entry.name;
|
||||
readEntries(entry, 0, 0, function (entries: any[]) {
|
||||
const promises: Promise<any>[] = [];
|
||||
entries.forEach(function (entry: any) {
|
||||
promises.push(
|
||||
new Promise(function (resolve) {
|
||||
if (entry.isFile) {
|
||||
entry.file(function (file: File) {
|
||||
const p = path + '/' + file.name;
|
||||
fd.push(file);
|
||||
files.push(p);
|
||||
if (files.length > 1000000) {
|
||||
return false;
|
||||
}
|
||||
resolve(true);
|
||||
}, resolve.bind(null, true));
|
||||
} else readDirectory(entry, path + '/' + entry.name, resolve);
|
||||
}),
|
||||
);
|
||||
});
|
||||
Promise.all(promises).then(resolve.bind(null, true));
|
||||
});
|
||||
}
|
||||
|
||||
[].slice.call(items).forEach(function (entry: any) {
|
||||
entry = entry.webkitGetAsEntry();
|
||||
if (entry) {
|
||||
rootPromises.push(
|
||||
new Promise(function (resolve) {
|
||||
if (entry.isFile) {
|
||||
entry.file(function (file: File) {
|
||||
fd.push(file);
|
||||
files.push(file.name);
|
||||
if (files.length > 1000000) {
|
||||
return false;
|
||||
}
|
||||
resolve(true);
|
||||
}, resolve.bind(null, true));
|
||||
} else if (entry.isDirectory) {
|
||||
readDirectory(entry, null, resolve);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (files.length > 1000000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Promise.all(rootPromises).then(cb.bind(null, fd, files));
|
||||
}
|
||||
|
||||
const cb = function (event: Event, files: File[], paths?: string[]) {
|
||||
const documents_number = paths ? paths.length : 0;
|
||||
let total_size = 0;
|
||||
const tree: any = {};
|
||||
(paths || []).forEach(function (path, file_index) {
|
||||
let dirs = tree;
|
||||
const real_file = files[file_index];
|
||||
|
||||
total_size += real_file.size;
|
||||
|
||||
path.split('/').forEach(function (dir, dir_index) {
|
||||
if (dir.indexOf('.') === 0) {
|
||||
return;
|
||||
}
|
||||
if (dir_index === path.split('/').length - 1) {
|
||||
dirs[dir] = real_file;
|
||||
} else {
|
||||
if (!dirs[dir]) {
|
||||
dirs[dir] = {};
|
||||
}
|
||||
dirs = dirs[dir];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
fcb && fcb(tree, documents_number, total_size);
|
||||
resolve({ tree, documentsCount: documents_number, totalSize: total_size });
|
||||
};
|
||||
|
||||
if (event.dataTransfer) {
|
||||
const dt = event.dataTransfer;
|
||||
if (dt.items && dt.items.length && 'webkitGetAsEntry' in dt.items[0]) {
|
||||
entriesApi(dt.items, (files, paths) => cb(event, files || [], paths));
|
||||
} else if ('getFilesAndDirectories' in dt) {
|
||||
newDirectoryApi(dt, (files, paths) => cb(event, files || [], paths));
|
||||
} else if (dt.files) {
|
||||
arrayApi(dt, (files, paths) => cb(event, files || [], paths));
|
||||
} else cb(event, [], []);
|
||||
} else if (event.target) {
|
||||
const t = event.target as any;
|
||||
if (t.files && t.files.length) {
|
||||
arrayApi(t, (files, paths) => cb(event, files || [], paths));
|
||||
} else if ('getFilesAndDirectories' in t) {
|
||||
newDirectoryApi(t, (files, paths) => cb(event, files || [], paths));
|
||||
} else {
|
||||
cb(event, [], []);
|
||||
}
|
||||
} else {
|
||||
fcb && fcb([(event.target as any).files[0]], 1, (event.target as any).files[0].size);
|
||||
resolve({
|
||||
tree: (event.target as any).files[0],
|
||||
documentsCount: 1,
|
||||
totalSize: (event.target as any).files[0].size,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,329 @@
|
||||
import Observable from 'app/deprecated/CollectionsV1/observable.js';
|
||||
import Number from 'app/features/global/utils/Numbers';
|
||||
import DriveService from 'app/deprecated/Apps/Drive/Drive.js';
|
||||
import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js';
|
||||
import Resumable from 'app/features/files/utils/resumable.js';
|
||||
import Globals from 'app/features/global/services/globals-tdrive-app-service';
|
||||
import Api from 'app/features/global/framework/api-service';
|
||||
import JWTStorage from 'app/features/auth/jwt-storage-service';
|
||||
|
||||
class UploadManager extends Observable {
|
||||
constructor() {
|
||||
super();
|
||||
this.setObservableName('upload_manager');
|
||||
this.reinit();
|
||||
|
||||
window.uploadManager = this;
|
||||
}
|
||||
|
||||
reinit() {
|
||||
if (this.reinitTimeout) clearTimeout(this.reinitTimeout);
|
||||
if (this.reinitTimeoutBefore) clearTimeout(this.reinitTimeoutBefore);
|
||||
this.currentUploadTotalSize = 0;
|
||||
this.currentUploadTotalNumber = 0;
|
||||
this.currentUploadedTotalSize = 0;
|
||||
this.currentUploadedFilesNumber = 0;
|
||||
this.currentUploadingFilesNumber = 0;
|
||||
this.currentCancelledFilesNumber = 0;
|
||||
this.currentWaitingFilesNumber = 0;
|
||||
this.currentErrorFilesNumber = 0;
|
||||
this.currentUploadFiles = [];
|
||||
this.currentUploadStartTime = new Date();
|
||||
this.will_close = false;
|
||||
this.notify();
|
||||
}
|
||||
|
||||
reinitAfterDelay() {
|
||||
if (this.reinitTimeout) clearTimeout(this.reinitTimeout);
|
||||
if (this.reinitTimeoutBefore) clearTimeout(this.reinitTimeoutBefore);
|
||||
this.reinitTimeoutBefore = setTimeout(() => {
|
||||
this.will_close = true;
|
||||
this.notify();
|
||||
this.reinitTimeout = setTimeout(() => {
|
||||
this.reinit();
|
||||
}, 500);
|
||||
}, 800);
|
||||
}
|
||||
|
||||
startUpload(
|
||||
elements,
|
||||
total_number,
|
||||
total_size,
|
||||
drive_parent,
|
||||
upload_options,
|
||||
driveCollectionKey,
|
||||
callback,
|
||||
) {
|
||||
if (this.addElementsRecursive(elements, '')) {
|
||||
this.uploadRecursive(elements, drive_parent, upload_options, driveCollectionKey, callback);
|
||||
} else {
|
||||
this.currentUploadFiles = [];
|
||||
this.currentUploadTotalNumber = 0;
|
||||
this.currentUploadTotalSize = 0;
|
||||
this.currentWaitingFilesNumber = 0;
|
||||
this.currentUploadStartTime = new Date();
|
||||
this.reinit();
|
||||
}
|
||||
}
|
||||
|
||||
addElementsRecursive(elements, path) {
|
||||
return Object.keys(elements).every(name => {
|
||||
var element = elements[name];
|
||||
if (element.size) {
|
||||
//File
|
||||
this.currentUploadTotalSize += element.size;
|
||||
this.currentUploadTotalNumber++;
|
||||
this.currentUploadFiles.push({
|
||||
unid: Number.unid(),
|
||||
progress: 0,
|
||||
cancelled: false,
|
||||
error: false,
|
||||
name: name,
|
||||
path: path,
|
||||
xhr: null,
|
||||
file: element,
|
||||
});
|
||||
} else {
|
||||
//Directory
|
||||
if (!this.addElementsRecursive(element, path + '/' + name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
uploadRecursive(elements, drive_parent, upload_options, driveCollectionKey, callback) {
|
||||
Object.keys(elements).forEach(name => {
|
||||
var element = elements[name];
|
||||
if (element.size) {
|
||||
//File
|
||||
this.uploadFile(element, drive_parent, upload_options, driveCollectionKey, callback);
|
||||
} else {
|
||||
//Directory
|
||||
this.createDirectory(name, drive_parent, upload_options, driveCollectionKey, directory => {
|
||||
if (callback) callback(directory);
|
||||
if (directory) {
|
||||
this.uploadRecursive(element, directory, upload_options, driveCollectionKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createDirectory(name, parent, upload_options, driveCollectionKey, callback) {
|
||||
if (this.currentUploadingFilesNumber > 4) {
|
||||
setTimeout(() => {
|
||||
this.createDirectory(name, parent, upload_options, driveCollectionKey, callback);
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentUploadingFilesNumber++;
|
||||
|
||||
DriveService.createDirectory(
|
||||
upload_options.workspace_id,
|
||||
name,
|
||||
parent,
|
||||
driveCollectionKey,
|
||||
dir => {
|
||||
this.currentUploadingFilesNumber--;
|
||||
if (callback) callback(dir);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
abort(elements) {
|
||||
var that = this;
|
||||
|
||||
if (elements.length === undefined) {
|
||||
elements = [elements];
|
||||
}
|
||||
|
||||
elements.forEach(element => {
|
||||
if (element.resumable) element.resumable.cancel();
|
||||
element.xhr_cancelled = true;
|
||||
element.cancelled = true;
|
||||
that.currentCancelledFilesNumber++;
|
||||
that.currentUploadingFilesNumber--;
|
||||
|
||||
if (
|
||||
that.currentUploadedFilesNumber +
|
||||
that.currentCancelledFilesNumber +
|
||||
that.currentErrorFilesNumber >=
|
||||
that.currentUploadTotalNumber
|
||||
) {
|
||||
that.reinitAfterDelay();
|
||||
}
|
||||
});
|
||||
|
||||
that.notify();
|
||||
}
|
||||
|
||||
uploadFile(element, drive_parent, upload_options, driveCollectionKey, callback, timeout_count) {
|
||||
this.currentWaitingFilesNumber++;
|
||||
|
||||
if (!timeout_count) {
|
||||
timeout_count = 0;
|
||||
}
|
||||
|
||||
if (this.currentUploadingFilesNumber > 4) {
|
||||
setTimeout(() => {
|
||||
this.currentWaitingFilesNumber--;
|
||||
this.uploadFile(
|
||||
element,
|
||||
drive_parent,
|
||||
upload_options,
|
||||
driveCollectionKey,
|
||||
callback,
|
||||
timeout_count + 1,
|
||||
);
|
||||
}, Math.max(30000, 500 * (this.currentWaitingFilesNumber + 1) + Math.random() * 1000));
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentWaitingFilesNumber--;
|
||||
this.currentUploadingFilesNumber++;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var listIndex = 0;
|
||||
var current_item = 0;
|
||||
this.currentUploadFiles.forEach((item, i) => {
|
||||
if (item.file === element) {
|
||||
listIndex = i;
|
||||
current_item = item;
|
||||
}
|
||||
});
|
||||
|
||||
var that = this;
|
||||
|
||||
if (current_item.xhr_cancelled === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
var extension = element.name.split('.');
|
||||
var r_data = {
|
||||
workspace_id: upload_options.workspace_id,
|
||||
name: element.name,
|
||||
extension: extension[extension.length - 1],
|
||||
};
|
||||
|
||||
//Use Resumable.js for upload by chunk
|
||||
var r = new Resumable({
|
||||
target: `${Globals.api_root_url}/ajax/driveupload/upload`,
|
||||
chunkSize: 50000000,
|
||||
testChunks: false,
|
||||
simultaneousUploads: 5,
|
||||
withCredentials: true,
|
||||
maxChunkRetries: 2,
|
||||
headers: {
|
||||
Authorization: JWTStorage.getAutorizationHeader(),
|
||||
},
|
||||
query: {
|
||||
object: JSON.stringify({
|
||||
id: upload_options.new_version ? upload_options.file_id : null,
|
||||
_once_new_version: upload_options.new_version,
|
||||
front_id: Number.unid(),
|
||||
is_directory: false,
|
||||
name: element.name,
|
||||
parent_id: drive_parent ? drive_parent.id : null,
|
||||
workspace_id: upload_options.workspace_id,
|
||||
detached: upload_options.detached,
|
||||
}),
|
||||
},
|
||||
generateUniqueIdentifier: (file, event) => {
|
||||
return 'no_id';
|
||||
},
|
||||
});
|
||||
|
||||
current_item.resumable = r;
|
||||
|
||||
current_item.timeout = setTimeout(() => {
|
||||
this.currentUploadingFilesNumber--;
|
||||
}, 60000);
|
||||
|
||||
r.on('fileSuccess', function (file, message) {
|
||||
clearTimeout(current_item.timeout);
|
||||
|
||||
that.currentUploadedFilesNumber++;
|
||||
that.currentUploadingFilesNumber--;
|
||||
current_item.progress = 1;
|
||||
|
||||
if (
|
||||
that.currentUploadedFilesNumber +
|
||||
that.currentCancelledFilesNumber +
|
||||
that.currentErrorFilesNumber >=
|
||||
that.currentUploadTotalNumber
|
||||
) {
|
||||
that.reinitAfterDelay();
|
||||
}
|
||||
|
||||
var resp = JSON.parse(message);
|
||||
|
||||
if (resp.data && resp.data.object) {
|
||||
Collections.get('drive').updateObject(resp.data.object);
|
||||
Collections.get('drive').share(resp.data.object);
|
||||
|
||||
if (callback) callback(Collections.get('drive').find(resp.data.object.id));
|
||||
|
||||
var parent = Collections.get('drive').find(resp.data.object.parent_id);
|
||||
if (parent) {
|
||||
parent.size = parseInt(parent.size) + parseInt(resp.data.object.size || 0);
|
||||
Collections.get('drive').updateObject(parent);
|
||||
}
|
||||
} else {
|
||||
current_item.error = 1;
|
||||
}
|
||||
|
||||
that.notify();
|
||||
});
|
||||
r.on('progress', function (file, message) {
|
||||
current_item.progress = Math.min(r.progress(), 0.99);
|
||||
that.notify();
|
||||
|
||||
clearTimeout(current_item.timeout);
|
||||
current_item.timeout = setTimeout(() => {
|
||||
this.currentUploadingFilesNumber--;
|
||||
}, 60000);
|
||||
});
|
||||
r.on('fileError', function (file, message) {
|
||||
clearTimeout(current_item.timeout);
|
||||
|
||||
that.currentErrorFilesNumber++;
|
||||
that.currentUploadingFilesNumber--;
|
||||
current_item.error = true;
|
||||
|
||||
if (
|
||||
that.currentUploadedFilesNumber +
|
||||
that.currentCancelledFilesNumber +
|
||||
that.currentErrorFilesNumber >=
|
||||
that.currentUploadTotalNumber
|
||||
) {
|
||||
that.reinitAfterDelay();
|
||||
}
|
||||
|
||||
that.notify();
|
||||
});
|
||||
|
||||
r.addFile(element);
|
||||
|
||||
Api.post(
|
||||
'/ajax/driveupload/preprocess',
|
||||
{
|
||||
workspace_id: r_data.workspace_id,
|
||||
name: r_data.name,
|
||||
identifier: r_data.identifier,
|
||||
extension: r_data.extension,
|
||||
},
|
||||
res => {
|
||||
var identifier = res.identifier;
|
||||
var file = r.getFromUniqueIdentifier('no_id');
|
||||
file.uniqueIdentifier = identifier;
|
||||
r.upload();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const service = new UploadManager();
|
||||
export default service;
|
||||
@@ -0,0 +1,170 @@
|
||||
import React from 'react';
|
||||
|
||||
import UploadManager from './upload-manager.js';
|
||||
import CloseIcon from '@material-ui/icons/CloseOutlined';
|
||||
import './uploads.scss';
|
||||
import moment from 'moment';
|
||||
import Languages from 'app/features/global/services/languages-service';
|
||||
|
||||
export default class UploadViewer extends React.Component {
|
||||
constructor(props) {
|
||||
super();
|
||||
this.state = {
|
||||
upload_manager: UploadManager,
|
||||
};
|
||||
UploadManager.addListener(this);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
UploadManager.removeListener(this);
|
||||
}
|
||||
render() {
|
||||
if (this.state.upload_manager.currentUploadTotalNumber <= 0) {
|
||||
// eslint-disable-next-line react/no-direct-mutation-state
|
||||
this.state.large = true;
|
||||
return '';
|
||||
}
|
||||
|
||||
var documents = this.state.upload_manager.currentUploadFiles.filter(d => !d.path.substr(1));
|
||||
var folders = {};
|
||||
var folders_content = {};
|
||||
this.state.upload_manager.currentUploadFiles
|
||||
.filter(d => d.path.substr(1))
|
||||
.forEach(d => {
|
||||
var folder = d.path.substr(1).split('/')[0];
|
||||
if (!folders[folder]) {
|
||||
folders[folder] = {
|
||||
total: 0,
|
||||
total_progress: 0,
|
||||
total_uploaded: 0,
|
||||
error: true,
|
||||
cancelled: true,
|
||||
};
|
||||
folders_content[folder] = [];
|
||||
}
|
||||
|
||||
if (!d.cancelled) {
|
||||
folders[folder].cancelled = false;
|
||||
}
|
||||
|
||||
if (!d.error) {
|
||||
folders[folder].error = false;
|
||||
}
|
||||
|
||||
if (!d.error && !d.cancelled) {
|
||||
folders[folder].total++;
|
||||
folders[folder].total_progress += d.progress;
|
||||
if (d.progress === 1) {
|
||||
folders[folder].total_uploaded += 1;
|
||||
}
|
||||
|
||||
folders_content[folder].push(d);
|
||||
}
|
||||
});
|
||||
Object.keys(folders).forEach(name => {
|
||||
documents.push({
|
||||
progress: folders[name].total_progress / folders[name].total,
|
||||
name: name,
|
||||
cancelled: folders[name].cancelled,
|
||||
error: folders[name].error,
|
||||
folder_total: folders[name].total,
|
||||
folder_total_uploaded: folders[name].total_uploaded,
|
||||
all_files: folders_content[name],
|
||||
});
|
||||
});
|
||||
|
||||
var total_finished =
|
||||
this.state.upload_manager.currentUploadedFilesNumber +
|
||||
this.state.upload_manager.currentCancelledFilesNumber +
|
||||
this.state.upload_manager.currentErrorFilesNumber;
|
||||
var todo = this.state.upload_manager.currentUploadTotalNumber;
|
||||
|
||||
var total_finished_size = this.state.upload_manager.currentUploadFiles
|
||||
.map(a => {
|
||||
if (a.error || a.cancelled) {
|
||||
return (a.file || {}).size || 0;
|
||||
}
|
||||
if (a.progress > 0) {
|
||||
return ((a.file || {}).size || 0) * a.progress;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
.reduce((a, b) => {
|
||||
return a + b;
|
||||
});
|
||||
var todo_size = this.state.upload_manager.currentUploadFiles
|
||||
.map(a => {
|
||||
return (a.file || {}).size || 0;
|
||||
})
|
||||
.reduce((a, b) => {
|
||||
return a + b;
|
||||
});
|
||||
|
||||
var remaining_time = 0;
|
||||
if (total_finished_size > 0) {
|
||||
remaining_time =
|
||||
((todo_size - total_finished_size) / 1000000) *
|
||||
((new Date().getTime() - this.state.upload_manager.currentUploadStartTime) /
|
||||
(total_finished_size / 1000000));
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
'upload_viewer ' +
|
||||
(this.state.upload_manager.will_close ? 'fade_out ' : 'skew_in_left_nobounce ')
|
||||
}
|
||||
>
|
||||
<div className="title" onClick={() => this.setState({ large: !this.state.large })}>
|
||||
{Languages.t('general.uploading')} {total_finished}/{todo}
|
||||
</div>
|
||||
{remaining_time > 0 && (
|
||||
<div className="subtitle">
|
||||
Will end {moment(new Date().getTime() + remaining_time).fromNow()}
|
||||
</div>
|
||||
)}
|
||||
<div className="uploads" style={{ display: this.state.large ? 'block' : 'none' }}>
|
||||
{documents
|
||||
.sort((a, b) => (a.progress === 1) - (b.progress === 1))
|
||||
.map(item => {
|
||||
return (
|
||||
<div
|
||||
key={item.unid}
|
||||
className={
|
||||
'uploadingFile ' +
|
||||
(item.cancelled || item.error ? 'stopped ' : '') +
|
||||
(item.progress === 1 && !item.error ? 'done ' : '') +
|
||||
(item.progress < 1 && !item.error && !item.cancelled ? 'progress ' : '')
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="progress_bar"
|
||||
style={{ width: parseInt(item.progress * 100) + '%' }}
|
||||
/>
|
||||
<div className="name">
|
||||
{item.name} {item.folder_total !== undefined && '(Folder)'}
|
||||
</div>
|
||||
{item.path && item.path.substr(1) && (
|
||||
<div className="path">{item.path.substr(1)}</div>
|
||||
)}
|
||||
{item.folder_total !== undefined && (
|
||||
<div className="path">
|
||||
{item.folder_total_uploaded}/{item.folder_total}
|
||||
</div>
|
||||
)}
|
||||
<div className="progress">{parseInt((item.progress || 0) * 100)}%</div>
|
||||
<div
|
||||
className="cancel"
|
||||
onClick={() => {
|
||||
UploadManager.abort(item.all_files || item);
|
||||
}}
|
||||
>
|
||||
<CloseIcon className="m-icon-small" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React from 'react';
|
||||
|
||||
import UploadManager from './upload-manager';
|
||||
import Languages from 'app/features/global/services/languages-service';
|
||||
import { Upload } from 'react-feather';
|
||||
import classNames from 'classnames';
|
||||
import './uploads.scss';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
type PropsType = {
|
||||
[key: string]: any;
|
||||
onAddFiles: (files: File[], event: Event & { dataTransfer: DataTransfer }) => void;
|
||||
};
|
||||
|
||||
type StateType = { [key: string]: any };
|
||||
|
||||
type FileInputType = any;
|
||||
|
||||
type FileObjectType = { [key: string]: any };
|
||||
|
||||
let sharedFileInput: any = null;
|
||||
let sharedFolderInput: any = null;
|
||||
|
||||
export default class UploadZone extends React.Component<PropsType, StateType> {
|
||||
file_input: FileInputType = {};
|
||||
stopHoverTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
node: HTMLDivElement | null = null;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.state = {
|
||||
upload_manager: UploadManager,
|
||||
};
|
||||
UploadManager.addListener(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
UploadManager.removeListener(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.node && this.watch(this.node, document.body);
|
||||
|
||||
if (
|
||||
(this.props.directory && !sharedFolderInput) ||
|
||||
(!this.props.directory && !sharedFileInput)
|
||||
) {
|
||||
this.file_input = document.createElement('input');
|
||||
this.file_input.type = 'file';
|
||||
this.file_input.style.position = 'absolute';
|
||||
this.file_input.style.top = '-10000px';
|
||||
this.file_input.style.left = '-10000px';
|
||||
this.file_input.style.width = '100px';
|
||||
this.file_input.multiple = this.props.multiple ? true : false;
|
||||
this.file_input.directory = this.props.directory ? true : false;
|
||||
this.file_input.webkitdirectory = this.props.directory ? true : false;
|
||||
|
||||
this.setCallback();
|
||||
|
||||
document.body.appendChild(this.file_input);
|
||||
|
||||
if (this.props.directory) {
|
||||
sharedFolderInput = this.file_input;
|
||||
} else {
|
||||
sharedFileInput = this.file_input;
|
||||
}
|
||||
} else {
|
||||
this.file_input = sharedFileInput;
|
||||
}
|
||||
}
|
||||
|
||||
setCallback() {
|
||||
this.file_input.onchange = (e: any) => {
|
||||
this.change(e);
|
||||
};
|
||||
}
|
||||
|
||||
open() {
|
||||
if (this.props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setCallback();
|
||||
|
||||
this.file_input.click();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param tree
|
||||
* @param nb
|
||||
* @param totalSize
|
||||
*/
|
||||
upload(tree: any, nb?: number, totalSize?: number) {
|
||||
if (this.props.multiple === false) {
|
||||
nb = 1;
|
||||
let file: any = null;
|
||||
Object.keys(tree).every(i => {
|
||||
const element = tree[i];
|
||||
if (element.size) {
|
||||
file = {};
|
||||
file[i] = element;
|
||||
totalSize = element.size;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
UploadManager.startUpload(
|
||||
tree,
|
||||
nb,
|
||||
totalSize,
|
||||
this.props.parent,
|
||||
this.props.uploadOptions,
|
||||
this.props.driveCollectionKey,
|
||||
this.props.onUploaded,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
change(event: any) {
|
||||
if (this.props.disabled) return;
|
||||
event.preventDefault();
|
||||
this.hover(false);
|
||||
|
||||
const files = event.target.files || event.dataTransfer.files || [];
|
||||
if (this.props.onAddFiles && files.length > 0) return this.props.onAddFiles([...files], event);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param currentNode
|
||||
* @param body
|
||||
*/
|
||||
watch(currentNode: HTMLElement, body: HTMLElement) {
|
||||
/**
|
||||
* DRAGOVER EVENT
|
||||
*/
|
||||
currentNode.addEventListener('dragover', () => currentNode.classList.add('input-drag-focus'));
|
||||
|
||||
body.addEventListener('dragover', (e: DragEvent) => {
|
||||
body.classList.add('body-drag-focus');
|
||||
this.hover(true, e);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
/**
|
||||
* DRAGLEAVE EVENT
|
||||
*/
|
||||
currentNode.addEventListener('dragleave', () =>
|
||||
currentNode.classList.remove('input-drag-focus'),
|
||||
);
|
||||
|
||||
body.addEventListener('dragleave', (e: DragEvent) => {
|
||||
body.classList.remove('body-drag-focus');
|
||||
|
||||
if (this.props.onDragLeave) {
|
||||
this.props.onDragLeave();
|
||||
}
|
||||
|
||||
this.hover(false, e);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
/**
|
||||
* DROP EVENT
|
||||
*/
|
||||
|
||||
currentNode.addEventListener('drop', (e: DragEvent) => {
|
||||
currentNode.classList.contains('input-drag-focus') && this.change(e);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
body.addEventListener('drop', (e: DragEvent) => {
|
||||
this.hover(false, e);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
/**
|
||||
* DRAGENTER EVENT
|
||||
*/
|
||||
body.addEventListener('dragenter', (e: DragEvent) => {
|
||||
if (!this.props.disabled && this.props.onDragEnter) {
|
||||
this.props.onDragEnter();
|
||||
}
|
||||
|
||||
this.hover(true, e);
|
||||
e.preventDefault();
|
||||
|
||||
this.setCallback();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Blob[]} files
|
||||
* @returns
|
||||
*/
|
||||
uploadFiles(files: any = []) {
|
||||
if (!this.props.allowPaste || !files.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filesToUpload: any = {};
|
||||
|
||||
files.forEach((file: FileObjectType, index: number) => {
|
||||
const filename = file.name
|
||||
? file.name.replace(/\.(png|jpeg|jpg|tiff|gif)$/i, '')
|
||||
: `file-${index}`;
|
||||
filesToUpload[filename] = file;
|
||||
});
|
||||
|
||||
this.upload(filesToUpload);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param state
|
||||
* @param event
|
||||
*/
|
||||
hover(state: any, event?: any) {
|
||||
if (
|
||||
!this.state.dragover &&
|
||||
(!event || !event.dataTransfer || (event.dataTransfer.types || []).indexOf('Files') < 0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!state) {
|
||||
this.stopHoverTimeout = setTimeout(() => {
|
||||
this.setState({ dragover: false });
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
if (this.stopHoverTimeout) clearTimeout(this.stopHoverTimeout);
|
||||
if (this.state.dragover !== state) {
|
||||
this.setState({ dragover: state });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
ref={node => node && (this.node = node)}
|
||||
style={this.props.style}
|
||||
className={classNames('upload_drop_zone', this.props.className)}
|
||||
onClick={() => {
|
||||
if (!this.props.disableClick) {
|
||||
this.open();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!this.props.disabled && (
|
||||
<div
|
||||
className={classNames('on_drag_over_background', {
|
||||
dragover: this.state.dragover,
|
||||
})}
|
||||
>
|
||||
<div className={'dashed ' + this.props.overClassName}>
|
||||
<div
|
||||
className={classNames('centered', { skew_in_top_nobounce: !!this.state.dragover })}
|
||||
>
|
||||
<div className="subtitle">
|
||||
<Upload size={18} className="small-right-margin" />
|
||||
<Typography.Text strong style={{ color: 'var(--primary)' }}>
|
||||
{Languages.t('components.upload.drop_files')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
.upload_viewer {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
box-shadow: var(--box-shadow-base);
|
||||
border-radius: var(--border-radius-base);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 100;
|
||||
|
||||
.title {
|
||||
background: var(--secondary);
|
||||
color: #eeeeee;
|
||||
font-size: 14px;
|
||||
padding: 8px;
|
||||
margin-bottom: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
background: var(--primary-background);
|
||||
color: var(--grey-dark);
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.uploads {
|
||||
background: #fff;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
max-height: 70vh;
|
||||
width: 300px;
|
||||
max-width: 50vw;
|
||||
|
||||
.uploadingFile {
|
||||
position: relative;
|
||||
padding: 4px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
padding: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.progress {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.path {
|
||||
background: var(--secondary);
|
||||
padding: 4px;
|
||||
color: #fff;
|
||||
border-radius: var(--border-radius-base);
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.name,
|
||||
.path,
|
||||
.progress {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.done {
|
||||
border-bottom: 1px solid #a8efa8;
|
||||
.progress_bar {
|
||||
width: 100% !important;
|
||||
background: #b8ffb8;
|
||||
}
|
||||
}
|
||||
|
||||
&.stopped {
|
||||
border-bottom: 1px solid #efa8a8;
|
||||
.progress_bar {
|
||||
width: 100% !important;
|
||||
background: #ffb8b8;
|
||||
}
|
||||
}
|
||||
|
||||
.progress_bar {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
background: #e5f3fd;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: width 0.2s;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
background: #f35e5e;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
margin-left: 10px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 3px;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 3px;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #e34e4e;
|
||||
}
|
||||
}
|
||||
|
||||
&.progress:hover {
|
||||
.cancel {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
.progress {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload_drop_zone {
|
||||
|
||||
&.input-drag-focus > .on_drag_over_background.dragover > * {
|
||||
background: var(--primary-background);
|
||||
}
|
||||
|
||||
.on_drag_over_background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--white);
|
||||
z-index: 100;
|
||||
transition: opacity 0.5s;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
|
||||
&.dragover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dashed {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
transition: opacity 0.5s;
|
||||
border: 2px dashed var(--primary-background);
|
||||
border-radius: var(--border-radius-base);
|
||||
|
||||
.centered {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.subtitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user