Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(upload): handle uploading folders and multiple files #44

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
133 changes: 126 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ import {
listView
} from '@osjs/gui';


/**
* flag indicating whether uploading folders is supported
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you capitalize this comment? Other than that, looks great!

*/
const supportsUploadingFolders = core =>
core.config('filemanager.disableMultiUpload', false) !== true
&& !!(window.DataTransferItem && DataTransferItem.prototype.webkitGetAsEntry);

/**
* Creates default settings
*/
Expand All @@ -66,7 +74,8 @@ const createWindowOptions = (core, proc, title) => ({
attributes: {
mediaQueries: {
small: 'screen and (max-width: 400px)'
}
},
droppable: {dataTransferProperty: supportsUploadingFolders(core) ? 'items' : 'files'},
},
dimension: Object.assign({
width: 400,
Expand Down Expand Up @@ -284,7 +293,7 @@ const vfsActionFactory = (core, proc, win, dialog, state) => {
};

const writeRelative = f => {
const d = dialog('progress', f);
const d = dialog('progress', f.name);

return vfs.writefile({
path: pathJoin(state.currentPath.path, f.name)
Expand All @@ -300,12 +309,122 @@ const vfsActionFactory = (core, proc, win, dialog, state) => {
});
};

const uploadBrowserFiles = (files) => {
const legacyUploadBrowserFiles = (files) => {
Promise.all(files.map(writeRelative))
.then(() => refresh(files[0].name)) // FIXME: Select all ?
.catch(error => dialog('error', error, __('MSG_UPLOAD_ERROR')));
};

const getUploadList = async (items) => {
/*
type: [{dirPath: string, file?: any}]
Directories do not have the `file` property.
They only have their relative path stored in `dirPath`.
Files store their containing folders path as their `dirPath`.
*/
const uploadList = [];

const getDirectoryEntries = (directory) => {
const reader = directory.createReader();
return new Promise(resolve => {
reader.readEntries(async (entries) => {
resolve(entries);
});
});
};

const getFileFromEntry = (entry) => {
return new Promise((resolve, reject) => {
entry.file((file) => {
resolve(file);
}, (error) => {
reject(error);
});
});
};

const checkDirectory = async (directory, dirPath) => {
uploadList.push({dirPath});
const entries = await getDirectoryEntries(directory);
for (let entry of entries) {
if (entry.isFile) {
const file = await getFileFromEntry(entry);
uploadList.push({dirPath, file});
} else if (entry.isDirectory) {
const subDirPath = dirPath + '/' + entry.name;
await checkDirectory(entry, subDirPath);
}
}
};

try {
for (let item of items) {
const entry = item.webkitGetAsEntry();
andersevenrud marked this conversation as resolved.
Show resolved Hide resolved
if (entry.isFile) {
const file = item.getAsFile();
const dirPath = '';
uploadList.push({dirPath, file});
} else if (entry.isDirectory) {
await checkDirectory(entry, entry.name);
}
}
} catch (error) {
console.warn(error);
}

return uploadList;
};

const uploadFileAndFolderList = async (list) => {
const files = list.map(({file}) => file).filter((file) => file);
const totalSize = files.reduce((sum, {size}) => sum + size, 0);
const dialogTitle = files.length === 1 ? files[0].name : 'multiple files';
const abortController = new AbortController();
let isCancelled = false;
const onCancel = () => {
isCancelled = false;
abortController.abort();
};
const d = dialog('progress', dialogTitle, onCancel);
try {
let uploaded = 0;
for (let {dirPath, file} of list) {
if (isCancelled) {
return;
}
if (file) {
// upload file
await vfs.writefile({
path: pathJoin(state.currentPath.path, dirPath, file.name)
}, file, {
signal: abortController.signal,
pid: proc.pid,
onProgress: (ev, progress) => {
d.setProgress(Math.round((uploaded + progress * file.size / 100) * 100 / totalSize));
}
});
uploaded += file.size;
} else {
// create folder
await vfs.mkdir({path: pathJoin(state.currentPath.path, dirPath)}, {pid: proc.pid});
}
}
} catch (error) {
dialog('error', error, __('MSG_UPLOAD_ERROR'));
}
d.destroy();
};

const uploadBrowserFiles = async (items) => {
if (!supportsUploadingFolders(core)) {
return legacyUploadBrowserFiles(items);
}

const uploadList = await getUploadList(items);
await uploadFileAndFolderList(uploadList);
refresh(items[0].name); // FIXME: Select all ?
};

const uploadVirtualFile = (data) => {
const dest = {path: pathJoin(state.currentPath.path, data.filename)};
if (dest.path !== data.path) {
Expand Down Expand Up @@ -449,10 +568,10 @@ const dialogFactory = (core, proc, win) => {
action(() => vfs.unlink(file, {pid: proc.pid}), true, __('MSG_DELETE_ERROR'));
}));

const progressDialog = (file) => dialog('progress', {
message: __('DIALOG_PROGRESS_MESSAGE', file.name),
buttons: []
}, () => {}, false);
const progressDialog = (name, cb = (() => {})) => dialog('progress', {
message: __('DIALOG_PROGRESS_MESSAGE', name),
buttons: ['Cancel']
}, cb, false);

const errorDialog = (error, message) => dialog('alert', {
type: 'error',
Expand Down