From b76567364df5f4c22db3c5508108926416725fca Mon Sep 17 00:00:00 2001 From: km0 Date: Tue, 18 Jul 2023 18:15:17 +0200 Subject: [PATCH 01/18] Multi-selection UI work in progress --- src/components/drive/DriveSelected.vue | 42 + src/components/drive/DriveTable.vue | 306 +- src/views/Drive.vue | 5121 ++++++++++++------------ 3 files changed, 2768 insertions(+), 2701 deletions(-) create mode 100644 src/components/drive/DriveSelected.vue diff --git a/src/components/drive/DriveSelected.vue b/src/components/drive/DriveSelected.vue new file mode 100644 index 00000000..6c076cb7 --- /dev/null +++ b/src/components/drive/DriveSelected.vue @@ -0,0 +1,42 @@ + + + diff --git a/src/components/drive/DriveTable.vue b/src/components/drive/DriveTable.vue index ead37f10..9eea914b 100644 --- a/src/components/drive/DriveTable.vue +++ b/src/components/drive/DriveTable.vue @@ -1,146 +1,160 @@ - - - - - \ No newline at end of file + + + + + diff --git a/src/views/Drive.vue b/src/views/Drive.vue index 8bc43feb..04880c29 100644 --- a/src/views/Drive.vue +++ b/src/views/Drive.vue @@ -1,2555 +1,2566 @@ - - - - - + + + + + From 6dd8403c7697fd69d2d543d2844724e68a2271c9 Mon Sep 17 00:00:00 2001 From: Francesco Luzzana Date: Wed, 19 Jul 2023 13:00:55 +0200 Subject: [PATCH 02/18] Drop down selected menu (wip) --- src/components/drive/DriveSelected.vue | 112 +++++++++++++++++++------ src/components/drive/DriveTable.vue | 3 +- src/views/Drive.vue | 12 +-- 3 files changed, 95 insertions(+), 32 deletions(-) diff --git a/src/components/drive/DriveSelected.vue b/src/components/drive/DriveSelected.vue index 6c076cb7..4fbc78d4 100644 --- a/src/components/drive/DriveSelected.vue +++ b/src/components/drive/DriveSelected.vue @@ -1,42 +1,104 @@ diff --git a/src/components/drive/DriveTable.vue b/src/components/drive/DriveTable.vue index 9eea914b..99ff059b 100644 --- a/src/components/drive/DriveTable.vue +++ b/src/components/drive/DriveTable.vue @@ -13,7 +13,8 @@ - + + Date: Wed, 19 Jul 2023 17:25:04 +0200 Subject: [PATCH 03/18] Grid & Table multi-select menu --- src/components/drive/DriveGridCard.vue | 32 +- src/components/drive/DriveSelected.vue | 91 +--- src/components/drive/DriveTable.vue | 9 +- src/views/Drive.vue | 657 +++++++++++++++---------- 4 files changed, 457 insertions(+), 332 deletions(-) diff --git a/src/components/drive/DriveGridCard.vue b/src/components/drive/DriveGridCard.vue index 19b04678..6f7a5846 100644 --- a/src/components/drive/DriveGridCard.vue +++ b/src/components/drive/DriveGridCard.vue @@ -1,6 +1,15 @@ From 01635191ec53e8a9923352c79615867b70a30010 Mon Sep 17 00:00:00 2001 From: kevodwyer Date: Fri, 21 Jul 2023 10:31:24 +0100 Subject: [PATCH 04/18] wire-up multi-select. WIP --- src/mixins/downloader/index.js | 16 +- src/views/Drive.vue | 462 +++++++++++++++++++++++++++++---- 2 files changed, 427 insertions(+), 51 deletions(-) diff --git a/src/mixins/downloader/index.js b/src/mixins/downloader/index.js index f56969cf..35ffb754 100644 --- a/src/mixins/downloader/index.js +++ b/src/mixins/downloader/index.js @@ -104,6 +104,7 @@ module.exports = { var that = this var resultingSize = this.getFileSize(props) let filename = fileLabel != null ? fileLabel : props.name; + let result = peergos.shared.util.Futures.incomplete(); var progress = { show: true, @@ -132,14 +133,13 @@ module.exports = { } } ) - .thenCompose(function (reader) { + .thenApply(function (reader) { if (that.supportsStreaming()) { var size = that.getFileSize(props) var maxBlockSize = 1024 * 1024 * 5 var blockSize = size > maxBlockSize ? maxBlockSize : size console.log('saving data of length ' + size + ' to ' + filename) - let result = peergos.shared.util.Futures.incomplete() let fileStream = streamSaver.createWriteStream( filename, props.mimeType, @@ -158,7 +158,6 @@ module.exports = { let pump = () => { if (blockSize == 0) { writer.close() - result.complete(true) } else { var data = convertToByteArray(new Uint8Array(blockSize)) reader @@ -169,18 +168,21 @@ module.exports = { writer.write(data).then(() => { setTimeout(pump) }) + if (size == 0) { + result.complete(true); + } }) } } pump() - return result } else { var size = that.getFileSize(props) var data = convertToByteArray(new Int8Array(size)) - return reader + reader .readIntoArray(data, 0, data.length) .thenApply(function (read) { - that.openItem(filename, data, props.mimeType) + that.openItem(filename, data, props.mimeType); + result.complete(true); }) } }) @@ -189,7 +191,9 @@ module.exports = { that.errorTitle = 'Error downloading file: ' + filename that.errorBody = throwable.getMessage() that.showError = true + result.complete(false); }) + return result; } } } diff --git a/src/views/Drive.vue b/src/views/Drive.vue index 01d304fa..d4027b91 100644 --- a/src/views/Drive.vue +++ b/src/views/Drive.vue @@ -66,12 +66,13 @@ > - -
  • Copy
  • -
  • Cut
  • -
  • Delete
  • -
  • Download
  • -
  • Zip
  • + +
  • Copy
  • +
  • Cut
  • +
  • Delete
  • +
  • Paste here
  • +
  • Download
  • +
  • Zip
  • Deselect
  • @@ -182,8 +183,8 @@
  • Delete
  • @@ -541,7 +542,8 @@ module.exports = { launcherApp: null, uploadProgressQueue: { entries:[]}, executingUploadProgressCommands: false, - progressBarUpdateFrequency: 50 + progressBarUpdateFrequency: 50, + zipAndDownloadFoldersCount: 0 }; }, mixins:[downloaderMixins, router, zipMixin, launcherMixin], @@ -827,6 +829,20 @@ module.exports = { } return true; }, + isPasteMultiSelectAvailable() { + if (this.currentDir == null) + return false; + + if (typeof (this.clipboardMultiSelect) == undefined || this.clipboardMultiSelect == null || + this.clipboardMultiSelect.op == null || typeof (this.clipboardMultiSelect.op) == "undefined") + return false; + + if (this.getPath == this.clipboardMultiSelect.path) { + return false; + } + let target = this.currentDir; + return target.isWritable() && target.isDirectory(); + }, isPasteAvailable() { if (this.currentDir == null) return false; @@ -1436,13 +1452,133 @@ module.exports = { showToastError: function(message) { this.$toast.error(message, {timeout:false}); }, - zipAndDownload() { - if (this.isStreamingAvailable) { - this.zipAndDownloadFolder(); - } else { - this.showToastError("Download as Zip only available where Streaming supported (like Chrome)"); + zipAndDownloadMultiSelect() { + if (this.currentDir == null) + return false; + if (this.isStreamingAvailable) { + this.zipAndDownloadFolders(); + } else { + this.showToastError("Download as Zip only available where Streaming supported (like Chrome)"); + } + }, + reduceTotalSize(index, path, files, accumTotalSize, stats, future) { + let that = this; + if (files.length == index) { + this.confirmZipAndDownloadOfFolders(files.length, stats, + () => { + that.showConfirm = false; + future.complete(true); + }, + () => { + that.showConfirm = false; + future.complete(false); + } + ); + } else { + let file = files[index]; + this.calculateTotalSize(file, path).thenApply(statistics => { + let updatedAccumTotalSize = statistics.actualSize + accumTotalSize; + stats.push(statistics); + if (file.isDirectory() && statistics.fileCount == 0) { + that.$toast('Folder:' + file.getName() + ' contains no files. Nothing to download'); + future.complete(false); + }else if (updatedAccumTotalSize > 1024 * 1024 * 1024 * 4) { //4GiB + that.$toast('Download of a Folder greater than 4GiB in size is not supported'); + future.complete(false); + } else { + that.reduceTotalSize(index + 1, path, files, updatedAccumTotalSize, stats, future); + } + }); + } + }, + reduceCollectFilesToZip(zipFilename, index, path, files, accumulatorList, progress, futureCollectFiles) { + let that = this; + if (files.length == index) { + futureCollectFiles.complete(true); + } else { + let file = files[index]; + let accumulator = {directoryMap: new Map(), files: []}; + let future = peergos.shared.util.Futures.incomplete(); + that.collectFilesToZip(path, file, path + file.getFileProperties().name, accumulator, future); + future.thenApply(allFiles => { + that.$toast({component: ProgressBar,props: progress} + , { icon: false , timeout:false, id: zipFilename}); + for(var i = 0; i < allFiles.files.length; i++) { + accumulatorList.push(allFiles.files[i]); + } + that.reduceCollectFilesToZip(zipFilename, index +1, path, files, accumulatorList, progress, futureCollectFiles); + }).exceptionally(function (throwable) { + that.$toast.error(throwable.getMessage()) + futureCollectFiles.complete(false); + }) + } + }, + confirmZipAndDownloadOfFolders(numberOfFoldersSelected, statisticsList, continueFunction, cancelFunction) { + var folderCount = numberOfFoldersSelected; + var fileCount = 0; + var actualSize = 0; + for(var i = 0; i < statisticsList.length; i++) { + folderCount = folderCount + statisticsList[i].folderCount; + fileCount = fileCount + statisticsList[i].fileCount; + actualSize = actualSize + statisticsList[i].actualSize; + } + this.confirm_message='Are you sure you want to download selected folders?'; + this.confirm_body='Folder(s): ' + folderCount + + ', File(s): ' + fileCount + + ', Total size: ' + helpers.convertBytesToHumanReadable(actualSize); + this.confirm_consumer_cancel_func = cancelFunction; + this.confirm_consumer_func = continueFunction; + this.showConfirm = true; + }, + zipAndDownloadFolders() { + this.showSpinner = true; + let that = this; + let futureTotalSize = peergos.shared.util.Futures.incomplete(); + let files = this.selectedFiles.slice(); + let path = this.getPath; + let statisticsList = []; + that.reduceTotalSize(0, path, files, 0, statisticsList, futureTotalSize); + futureTotalSize.thenApply(res => { + that.showSpinner = false; + if (res) { + that.showSpinner = true; + var actualSize = 0; + for(var i = 0; i < statisticsList.length; i++) { + actualSize = actualSize + statisticsList[i].actualSize; + } + let progress = { + show: true, + title: 'Downloading selected folders', + done: 0, + max: actualSize + } + let zipFilename = 'archive-' + that.zipAndDownloadFoldersCount + '.zip'; + that.zipAndDownloadFoldersCount = that.zipAndDownloadFoldersCount + 1; + let accumulator = {directoryMap: new Map(), files: []}; + let future = peergos.shared.util.Futures.incomplete(); + let allFilesList = []; + that.reduceCollectFilesToZip(zipFilename, 0, path, files, allFilesList, progress, future); + future.thenApply(res => { + that.showSpinner = false; + if (res) { + that.zipFiles(zipFilename, allFilesList, progress).thenApply(res => { + console.log('folders download complete'); + that.selectedFiles = []; + }).exceptionally(function (throwable) { + that.$toast.error(throwable.getMessage()) + }); + } + }); } - }, + }); + }, + zipAndDownload() { + if (this.isStreamingAvailable) { + this.zipAndDownloadFolder(); + } else { + this.showToastError("Download as Zip only available where Streaming supported (like Chrome)"); + } + }, zipAndDownloadFolder() { if (this.selectedFiles.length != 1) return; @@ -1458,7 +1594,7 @@ module.exports = { that.$toast('Download of a Folder greater than 4GiB in size is not supported'); } else { let filename = file.getName(); - this.confirmZipAndDownloadOfFolder(filename, statistics, + that.confirmZipAndDownloadOfFolder(filename, statistics, () => { that.showConfirm = false; var progress = { @@ -2028,33 +2164,181 @@ module.exports = { return null; }, - copy() { - if (this.selectedFiles.length != 1) + copyMultiSelect() { + if (this.selectedFiles.length < 1) return; - var file = this.selectedFiles[0]; - - this.clipboard = { - fileTreeNode: file, + let files = this.selectedFiles.slice(); + this.clipboardMultiSelect = { + fileTreeNodes: files, op: "copy", path: this.getPath }; - this.closeMenu(); }, - cut() { - if (this.selectedFiles.length != 1) + cutMultiSelect() { + if (this.selectedFiles.length < 1) return; - var file = this.selectedFiles[0]; - - this.clipboard = { + let files = this.selectedFiles.slice(); + this.clipboardMultiSelect = { parent: this.currentDir, - fileTreeNode: file, + fileTreeNodes: files, op: "cut", path: this.getPath }; - this.closeMenu(); }, + copy() { + if (this.selectedFiles.length != 1) + return; + var file = this.selectedFiles[0]; + this.clipboard = { + fileTreeNode: file, + op: "copy", + path: this.getPath + }; + this.closeMenu(); + }, + + cut() { + if (this.selectedFiles.length != 1) + return; + var file = this.selectedFiles[0]; + + this.clipboard = { + parent: this.currentDir, + fileTreeNode: file, + op: "cut", + path: this.getPath + }; + this.closeMenu(); + }, + + reduceMove(index, path, parent, fileTreeNodes, future) { + let that = this; + if (index == fileTreeNodes.length) { + future.complete(true); + } else { + let fileTreeNode = fileTreeNodes[index]; + let name = fileTreeNode.getFileProperties().name; + let filePath = peergos.client.PathUtils.toPath(path, name); + let target = this.currentDir; + parent.getLatest(this.context.network).thenApply(updatedParent => { + fileTreeNode.moveTo(target, updatedParent, filePath, that.context).thenApply(() => { + that.updateCurrentDirectory(null , () => + that.reduceMove(index + 1, path, parent, fileTreeNodes, future) + ); + }).exceptionally(function (throwable) { + that.errorTitle = 'Error moving file: ' + name; + that.errorBody = throwable.getMessage(); + that.showError = true; + future.complete(false); + }); + }); + } + }, + reduceCopy(index, fileTreeNodes, future) { + let that = this; + if (index == fileTreeNodes.length) { + future.complete(true); + } else { + let fileTreeNode = fileTreeNodes[index]; + let target = this.currentDir; + fileTreeNode.copyTo(target, that.context).thenApply(function () { + that.updateUsage(usageBytes => { + that.updateCurrentDirectory(null , () => + that.reduceCopy(index + 1, fileTreeNodes, future) + ); + }); + }).exceptionally(function (throwable) { + that.errorTitle = 'Error copying file: ' + fileTreeNode.getFileProperties().name; + that.errorBody = throwable.getMessage(); + that.showError = true; + future.complete(false); + }); + } + }, + reduceSizeCalculation(index, path, fileTreeNodes, accumApparentSize, sizeFuture) { + let that = this; + if (index == fileTreeNodes.length) { + sizeFuture.complete(true); + } else { + let fileTreeNode = fileTreeNodes[index]; + this.calculateTotalSize(fileTreeNode, path).thenApply(statistics => { + let updatedAccumApparentSize = accumApparentSize + statistics.apparentSize; + if (Number(that.quotaBytes.toString()) < updatedAccumApparentSize) { + let errMsg = "File copy operation exceeds total space\n" + "Please upgrade to get more space"; + that.$toast.error(errMsg, {timeout:false}); + future.complete(false); + } else { + let spaceAfterOperation = that.checkAvailableSpace(updatedAccumApparentSize); + if (spaceAfterOperation < 0) { + let errMsg = "File copy operation exceeds available space\n" + "Please free up " + helpers.convertBytesToHumanReadable('' + -spaceAfterOperation) + " and try again"; + that.$toast.error(errMsg, {timeout:false}) + that.showSpinner = false; + future.complete(false); + } else { + that.reduceSizeCalculation(index + 1, path, fileTreeNodes, updatedAccumApparentSize, sizeFuture); + } + } + }); + } + }, + pasteMultiSelect(e, retrying) { + var target = this.currentDir; + var that = this; + if (!target.isDirectory()) { + return; + } + let clipboard = this.clipboardMultiSelect; + if (typeof (clipboard) == undefined || typeof (clipboard.op) == "undefined") + return; + for(var i=0; i < clipboard.fileTreeNodes.length; i++) { + let fileTreeNode = clipboard.fileTreeNodes[i]; + if (fileTreeNode.samePointer(target)) { + that.$toast.error('Destination folder is same as Source folder', {timeout:false}) + return; + } + } + that.showSpinner = true; + if (clipboard.op == "cut") { + let future = peergos.shared.util.Futures.incomplete(); + this.reduceMove(0, that.path, clipboard.parent, clipboard.fileTreeNodes, future); + future.thenApply(res => { + that.showSpinner = false; + clipboard.op = null; + that.selectedFiles = []; + }); + } else if (clipboard.op == "copy") { + if (this.quotaBytes.toString() == '0') { + if (retrying == null) { + this.updateQuota(quotaBytes => { + if (quotaBytes != null) { + that.updateUsage(usageBytes => { + that.pasteMultiSelect(e, true); + }); + } else { + that.pasteMultiSelect(e, true); + } + }); + } else { + this.$toast.error("Client Offline!", {timeout:false}); + this.showSpinner = false; + } + } else { + let sizeFuture = peergos.shared.util.Futures.incomplete(); + this.reduceSizeCalculation(0, clipboard.path, clipboard.fileTreeNodes, 0, sizeFuture); + sizeFuture.thenApply(res => { + let copyFuture = peergos.shared.util.Futures.incomplete(); + that.reduceCopy(0, clipboard.fileTreeNodes, copyFuture); + copyFuture.thenApply(res => { + that.showSpinner = false; + clipboard.op = null; + that.selectedFiles = []; + }); + }); + } + } + }, paste(e, retrying) { if (this.selectedFiles.length > 1) return; @@ -2103,7 +2387,7 @@ module.exports = { } }); } else { - this.$toast.error("Client Offline!", {timeout:false, id: 'upload'}); + this.$toast.error("Client Offline!", {timeout:false}); this.showSpinner = false; } } else { @@ -2240,6 +2524,45 @@ module.exports = { this.showSpinner = true; this.updateHistory("Drive", path, {filename:""}); }, + reduceDownload(index, files, future) { + let that = this; + if (index == files.length) { + future.complete(true); + } else { + let file = files[index]; + that.downloadFile(file).thenApply(res => { + that.reduceDownload(index + 1, files, future); + }); + } + }, + downloadAllMultiSelect() { + if (this.currentDir == null) + return false; + if (this.selectedFiles.length == 0) + return; + if (!this.isStreamingAvailable) { + this.showToastError("Downloading multiple files only available where Streaming supported (like Chrome)"); + return; + } + let foundFolder = false; + for (var i = 0; i < this.selectedFiles.length; i++) { + let file = this.selectedFiles[i]; + if (file.isDirectory()) { + foundFolder = true; + } + } + let that = this; + if (foundFolder) { + that.zipAndDownloadMultiSelect(); + } else { + let files = this.selectedFiles.slice(); + let future = peergos.shared.util.Futures.incomplete(); + this.reduceDownload(0,files, future); + future.thenApply(res => { + that.selectedFiles = []; + }); + } + }, downloadAll() { if (this.selectedFiles.length == 0) return; @@ -2616,27 +2939,76 @@ module.exports = { this.showPrompt = true; }, + reduceDelete(index, path, parent, selectedFilesForDeletion, future) { + let that = this; + if (index == selectedFilesForDeletion.length) { + future.complete(true); + } else { + let file = selectedFilesForDeletion[index]; + let name = file.getFileProperties().name; + let filePath = peergos.client.PathUtils.toPath(path, name); + parent.getLatest(this.context.network).thenApply(updatedParent => { + file.remove(updatedParent, filePath, that.context).thenApply(function (b) { + that.updateUsage(usageBytes => { + that.updateCurrentDirectory(null , () => + that.reduceDelete(index + 1, path, parent, selectedFilesForDeletion, future) + ); + }); + }).exceptionally(function (throwable) { + that.errorTitle = 'Error deleting file: ' + file.getFileProperties().name; + that.errorBody = throwable.getMessage(); + that.showError = true; + future.complete(false); + }); + }); + } + }, - deleteFiles() { - // console.log('deleteFiles:',this.selectedFiles.length ) + deleteFilesMultiSelect() { var selectedCount = this.selectedFiles.length; if (selectedCount == 0) return; + var that = this; + this.confirmDeleteMultiSelect(selectedCount, (prompt_result) => { + that.showPrompt = false; + if (prompt_result != null) { + that.showSpinner = true; + let parent = that.currentDir; + let filesToDelete = that.selectedFiles.slice(); + let future = peergos.shared.util.Futures.incomplete(); + that.reduceDelete(0, that.path, parent, filesToDelete, future); + future.thenApply(res => { + that.showSpinner = false; + that.selectedFiles = []; + }); + } + }); + }, - this.closeMenu(); + confirmDeleteMultiSelect(fileCount, deleteFn) { + this.prompt_placeholder = null; + this.prompt_message = `Are you sure you want to delete ${fileCount} items?`; + this.prompt_value = ''; + this.prompt_consumer_func = deleteFn; + // this.prompt_action = 'Delete' + this.showPrompt = true; + }, - for (var i = 0; i < selectedCount; i++) { - var file = this.selectedFiles[i]; - var that = this; - var parent = this.currentDir; + deleteFile() { + var selectedCount = this.selectedFiles.length; + if (selectedCount == 0) + return; - // console.log('deleteFiles:',file, parent, this.context ) - this.confirmDelete(file, (prompt_result) => { - if (prompt_result != null) { - that.deleteOne(file, parent, this.context); - } - }); - } + this.closeMenu(); + var file = this.selectedFiles[0]; + var that = this; + var parent = this.currentDir; + + this.confirmDelete(file, (prompt_result) => { + if (prompt_result != null) { + that.deleteOne(file, parent, this.context); + } + }); }, deleteOne(file, parent, context) { From 422faff9e7ec9270efe346bf802207420974f9ec Mon Sep 17 00:00:00 2001 From: kevodwyer Date: Fri, 21 Jul 2023 13:28:52 +0100 Subject: [PATCH 05/18] fixes for multi-select --- src/views/Drive.vue | 122 ++++++++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 50 deletions(-) diff --git a/src/views/Drive.vue b/src/views/Drive.vue index d4027b91..7df330d7 100644 --- a/src/views/Drive.vue +++ b/src/views/Drive.vue @@ -66,11 +66,10 @@ > - +
  • Copy
  • Cut
  • Delete
  • -
  • Paste here
  • Download
  • Zip
  • @@ -78,6 +77,14 @@
  • + + +
  • + Paste +
  • +
    +
    +
    f.getName() == selectedFilename); that.openFile(); + } else { + that.selectedFiles = []; + that.multiSelectTargetFolder = null; } if (callback != null) { callback(); @@ -2213,7 +2210,7 @@ module.exports = { this.closeMenu(); }, - reduceMove(index, path, parent, fileTreeNodes, future) { + reduceMove(index, path, parent, target, fileTreeNodes, future) { let that = this; if (index == fileTreeNodes.length) { future.complete(true); @@ -2221,39 +2218,41 @@ module.exports = { let fileTreeNode = fileTreeNodes[index]; let name = fileTreeNode.getFileProperties().name; let filePath = peergos.client.PathUtils.toPath(path, name); - let target = this.currentDir; - parent.getLatest(this.context.network).thenApply(updatedParent => { - fileTreeNode.moveTo(target, updatedParent, filePath, that.context).thenApply(() => { - that.updateCurrentDirectory(null , () => - that.reduceMove(index + 1, path, parent, fileTreeNodes, future) - ); - }).exceptionally(function (throwable) { - that.errorTitle = 'Error moving file: ' + name; - that.errorBody = throwable.getMessage(); - that.showError = true; - future.complete(false); + target.getLatest(this.context.network).thenApply(updatedTarget => { + parent.getLatest(that.context.network).thenApply(updatedParent => { + fileTreeNode.moveTo(updatedTarget, updatedParent, filePath, that.context).thenApply(() => { + that.updateCurrentDirectory(null , () => + that.reduceMove(index + 1, path, updatedParent, updatedTarget, fileTreeNodes, future) + ); + }).exceptionally(function (throwable) { + that.errorTitle = 'Error moving file: ' + name; + that.errorBody = throwable.getMessage(); + that.showError = true; + future.complete(false); + }); }); }); } }, - reduceCopy(index, fileTreeNodes, future) { + reduceCopy(index, fileTreeNodes, target, future) { let that = this; if (index == fileTreeNodes.length) { future.complete(true); } else { let fileTreeNode = fileTreeNodes[index]; - let target = this.currentDir; - fileTreeNode.copyTo(target, that.context).thenApply(function () { - that.updateUsage(usageBytes => { - that.updateCurrentDirectory(null , () => - that.reduceCopy(index + 1, fileTreeNodes, future) - ); + target.getLatest(this.context.network).thenApply(updatedTarget => { + fileTreeNode.copyTo(updatedTarget, that.context).thenApply(function () { + that.updateUsage(usageBytes => { + that.updateCurrentDirectory(null , () => + that.reduceCopy(index + 1, fileTreeNodes, updatedTarget, future) + ); + }); + }).exceptionally(function (throwable) { + that.errorTitle = 'Error copying file: ' + fileTreeNode.getFileProperties().name; + that.errorBody = throwable.getMessage(); + that.showError = true; + future.complete(false); }); - }).exceptionally(function (throwable) { - that.errorTitle = 'Error copying file: ' + fileTreeNode.getFileProperties().name; - that.errorBody = throwable.getMessage(); - that.showError = true; - future.complete(false); }); } }, @@ -2284,7 +2283,10 @@ module.exports = { } }, pasteMultiSelect(e, retrying) { - var target = this.currentDir; + var target = this.multiSelectTargetFolder; + if (target == null) { + return; + } var that = this; if (!target.isDirectory()) { return; @@ -2299,10 +2301,11 @@ module.exports = { return; } } + this.closePasteMenu(); that.showSpinner = true; if (clipboard.op == "cut") { let future = peergos.shared.util.Futures.incomplete(); - this.reduceMove(0, that.path, clipboard.parent, clipboard.fileTreeNodes, future); + this.reduceMove(0, that.path, clipboard.parent, target, clipboard.fileTreeNodes, future); future.thenApply(res => { that.showSpinner = false; clipboard.op = null; @@ -2329,7 +2332,7 @@ module.exports = { this.reduceSizeCalculation(0, clipboard.path, clipboard.fileTreeNodes, 0, sizeFuture); sizeFuture.thenApply(res => { let copyFuture = peergos.shared.util.Futures.incomplete(); - that.reduceCopy(0, clipboard.fileTreeNodes, copyFuture); + that.reduceCopy(0, clipboard.fileTreeNodes, target, copyFuture); copyFuture.thenApply(res => { that.showSpinner = false; clipboard.op = null; @@ -2724,16 +2727,33 @@ module.exports = { } }, + isPasteToFolderMultiSelectAvailable(target) { + if (this.currentDir == null) + return false; + + if (typeof (this.clipboardMultiSelect) == undefined || this.clipboardMultiSelect == null || + this.clipboardMultiSelect.op == null || typeof (this.clipboardMultiSelect.op) == "undefined") + return false; + if (target == null) + return false; + return target.isWritable() && target.isDirectory(); + }, + openMenu(file) { // console.log(file) - if (file) { - this.selectedFiles = [file]; + if (this.selectedFiles.length > 1 && this.isPasteToFolderMultiSelectAvailable(file)) { + this.multiSelectTargetFolder = file; + this.viewPasteMenu = true } else { - this.selectedFiles = [this.currentDir]; - } - - this.viewMenu = true + this.multiSelectTargetFolder = null; + if (file) { + this.selectedFiles = [file]; + } else { + this.selectedFiles = [this.currentDir]; + } + this.viewMenu = true + } Vue.nextTick(() => { this.$refs.driveMenu.$el.focus() }); @@ -3051,7 +3071,9 @@ module.exports = { closeMenu() { this.viewMenu = false }, - + closePasteMenu() { + this.viewPasteMenu = false + }, isSelected(file) { return this.selectedFiles.findIndex(selected => selected == file) > -1 }, From 3404e3e7fa115727807062d12b8e42b6697ce585 Mon Sep 17 00:00:00 2001 From: kevodwyer Date: Fri, 21 Jul 2023 14:09:14 +0100 Subject: [PATCH 06/18] fix logic to display paste context menu item --- src/views/Drive.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/Drive.vue b/src/views/Drive.vue index 7df330d7..be9abfca 100644 --- a/src/views/Drive.vue +++ b/src/views/Drive.vue @@ -2741,7 +2741,7 @@ module.exports = { openMenu(file) { // console.log(file) - if (this.selectedFiles.length > 1 && this.isPasteToFolderMultiSelectAvailable(file)) { + if (this.isPasteToFolderMultiSelectAvailable(file)) { this.multiSelectTargetFolder = file; this.viewPasteMenu = true } else { From 7a5ea87102d032676ca0592c798a1008d8351ee5 Mon Sep 17 00:00:00 2001 From: kevodwyer Date: Fri, 21 Jul 2023 14:34:25 +0100 Subject: [PATCH 07/18] fix zip download for files --- src/mixins/zip/index.js | 6 +++--- src/views/Drive.vue | 34 +++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/mixins/zip/index.js b/src/mixins/zip/index.js index ee3fe1e4..58d5bcc9 100644 --- a/src/mixins/zip/index.js +++ b/src/mixins/zip/index.js @@ -136,7 +136,7 @@ module.exports = { zipFile(fileEntry, progress, writer, zipFilename, state) { let future = peergos.shared.util.Futures.incomplete(); let file = fileEntry.file; - let path = fileEntry.path; + let path = fileEntry.path == '' ? '' : fileEntry.path + '/'; var props = file.getFileProperties() var that = this; file.getInputStream(this.context.network, this.context.crypto, props.sizeHigh(), props.sizeLow(), @@ -149,9 +149,9 @@ module.exports = { } } ).thenApply(function (reader) { - console.log('zipping: ' + path + '/' + file.getName()); + console.log('zipping: ' + path + file.getName()); var crc = -1; // Begin with all bits set ( 0xffffffff ) - let fileEncodedName = new TextEncoder().encode(path + '/' + file.getName()); + let fileEncodedName = new TextEncoder().encode(path + file.getName()); let header = that.createZipFileHeader(props, fileEncodedName); let fileSize = that.getFileSize(props); writer.write(header).then(() => { diff --git a/src/views/Drive.vue b/src/views/Drive.vue index be9abfca..d0397030 100644 --- a/src/views/Drive.vue +++ b/src/views/Drive.vue @@ -1488,26 +1488,29 @@ module.exports = { }); } }, - reduceCollectFilesToZip(zipFilename, index, path, files, accumulatorList, progress, futureCollectFiles) { + reduceCollectFilesToZip(index, path, files, accumulatorList, futureCollectFiles) { let that = this; if (files.length == index) { futureCollectFiles.complete(true); } else { let file = files[index]; let accumulator = {directoryMap: new Map(), files: []}; - let future = peergos.shared.util.Futures.incomplete(); - that.collectFilesToZip(path, file, path + file.getFileProperties().name, accumulator, future); - future.thenApply(allFiles => { - that.$toast({component: ProgressBar,props: progress} - , { icon: false , timeout:false, id: zipFilename}); - for(var i = 0; i < allFiles.files.length; i++) { - accumulatorList.push(allFiles.files[i]); - } - that.reduceCollectFilesToZip(zipFilename, index +1, path, files, accumulatorList, progress, futureCollectFiles); - }).exceptionally(function (throwable) { - that.$toast.error(throwable.getMessage()) - futureCollectFiles.complete(false); - }) + if (file.isDirectory()) { + let future = peergos.shared.util.Futures.incomplete(); + that.collectFilesToZip(path, file, path + file.getFileProperties().name, accumulator, future); + future.thenApply(allFiles => { + for(var i = 0; i < allFiles.files.length; i++) { + accumulatorList.push(allFiles.files[i]); + } + that.reduceCollectFilesToZip(index +1, path, files, accumulatorList, futureCollectFiles); + }).exceptionally(function (throwable) { + that.$toast.error(throwable.getMessage()) + futureCollectFiles.complete(false); + }) + } else { + accumulatorList.push({path: '', file: file}); + that.reduceCollectFilesToZip(index +1, path, files, accumulatorList, futureCollectFiles); + } } }, confirmZipAndDownloadOfFolders(numberOfFoldersSelected, statisticsList, continueFunction, cancelFunction) { @@ -1554,7 +1557,8 @@ module.exports = { let accumulator = {directoryMap: new Map(), files: []}; let future = peergos.shared.util.Futures.incomplete(); let allFilesList = []; - that.reduceCollectFilesToZip(zipFilename, 0, path, files, allFilesList, progress, future); + that.$toast({component: ProgressBar,props: progress}, { icon: false , timeout:false, id: zipFilename}); + that.reduceCollectFilesToZip(0, path, files, allFilesList, future); future.thenApply(res => { that.showSpinner = false; if (res) { From 6fa611e9546a0c9b7fa9b9032e008ea185015da3 Mon Sep 17 00:00:00 2001 From: kevodwyer Date: Fri, 21 Jul 2023 15:10:31 +0100 Subject: [PATCH 08/18] allow multi-select paste to current folder --- src/views/Drive.vue | 62 +++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/src/views/Drive.vue b/src/views/Drive.vue index d0397030..d5b8fedf 100644 --- a/src/views/Drive.vue +++ b/src/views/Drive.vue @@ -25,7 +25,7 @@ 1) - return false; - var target = this.selectedFiles.length == 1 ? this.selectedFiles[0] : this.currentDir; - - if (target == null) { - return false; - } - - if (this.clipboard.fileTreeNode != null && this.clipboard.fileTreeNode.samePointer(target)) { - return false; - } - - return target.isWritable() && target.isDirectory(); + return this.isPasteToFolderAvailable(); }, }, @@ -1313,7 +1303,6 @@ module.exports = { that.openFile(); } else { that.selectedFiles = []; - that.multiSelectTargetFolder = null; } if (callback != null) { callback(); @@ -2285,6 +2274,35 @@ module.exports = { } }); } + }, + isPasteToFolderAvailable() { + if (this.currentDir == null) + return false; + + if (typeof (this.clipboard) == undefined || this.clipboard == null || this.clipboard.op == null || typeof (this.clipboard.op) == "undefined") + return false; + + if (this.selectedFiles.length > 1) + return false; + var target = this.selectedFiles.length == 1 ? this.selectedFiles[0] : this.currentDir; + + if (target == null) { + return false; + } + + if (this.clipboard.fileTreeNode != null && this.clipboard.fileTreeNode.samePointer(target)) { + return false; + } + + return target.isWritable() && target.isDirectory(); + }, + pasteToFolder(e) { + var target = this.multiSelectTargetFolder; + if (target == null) { + this.paste(e); + } else { + this.pasteMultiSelect(e); + } }, pasteMultiSelect(e, retrying) { var target = this.multiSelectTargetFolder; From d64b6762a858147088935c6ea0ae17e2f17265dc Mon Sep 17 00:00:00 2001 From: kevodwyer Date: Sat, 22 Jul 2023 07:24:08 +0100 Subject: [PATCH 09/18] fix context menu focus --- src/views/Drive.vue | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/views/Drive.vue b/src/views/Drive.vue index d5b8fedf..1fe5c4b4 100644 --- a/src/views/Drive.vue +++ b/src/views/Drive.vue @@ -2766,6 +2766,9 @@ module.exports = { if (this.isPasteToFolderMultiSelectAvailable(file)) { this.multiSelectTargetFolder = file; this.viewPasteMenu = true + Vue.nextTick(() => { + this.$refs.drivePasteMenu.$el.focus() + }); } else { this.multiSelectTargetFolder = null; if (file) { @@ -2775,10 +2778,10 @@ module.exports = { } this.viewMenu = true + Vue.nextTick(() => { + this.$refs.driveMenu.$el.focus() + }); } - Vue.nextTick(() => { - this.$refs.driveMenu.$el.focus() - }); }, createNewApp() { From e9b409725e840600c5096600d9b4b0159cb8858a Mon Sep 17 00:00:00 2001 From: kevodwyer Date: Sun, 23 Jul 2023 13:55:42 +0100 Subject: [PATCH 10/18] testing multi-select --- src/views/Drive.vue | 52 +++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/views/Drive.vue b/src/views/Drive.vue index 1fe5c4b4..84592de3 100644 --- a/src/views/Drive.vue +++ b/src/views/Drive.vue @@ -1551,7 +1551,7 @@ module.exports = { future.thenApply(res => { that.showSpinner = false; if (res) { - that.zipFiles(zipFilename, allFilesList, progress).thenApply(res => { + that.zipFiles(zipFilename, allFilesList, progress).thenApply(res2 => { console.log('folders download complete'); that.selectedFiles = []; }).exceptionally(function (throwable) { @@ -2218,10 +2218,12 @@ module.exports = { that.reduceMove(index + 1, path, updatedParent, updatedTarget, fileTreeNodes, future) ); }).exceptionally(function (throwable) { - that.errorTitle = 'Error moving file: ' + name; - that.errorBody = throwable.getMessage(); - that.showError = true; - future.complete(false); + that.updateCurrentDirectory(null , () => { + that.errorTitle = 'Error moving file: ' + name; + that.errorBody = throwable.getMessage(); + that.showError = true; + future.complete(false); + }); }); }); }); @@ -2241,10 +2243,12 @@ module.exports = { ); }); }).exceptionally(function (throwable) { - that.errorTitle = 'Error copying file: ' + fileTreeNode.getFileProperties().name; - that.errorBody = throwable.getMessage(); - that.showError = true; - future.complete(false); + that.updateCurrentDirectory(null , () => { + that.errorTitle = 'Error copying file: ' + fileTreeNode.getFileProperties().name; + that.errorBody = throwable.getMessage(); + that.showError = true; + future.complete(false); + }); }); }); } @@ -2260,14 +2264,14 @@ module.exports = { if (Number(that.quotaBytes.toString()) < updatedAccumApparentSize) { let errMsg = "File copy operation exceeds total space\n" + "Please upgrade to get more space"; that.$toast.error(errMsg, {timeout:false}); - future.complete(false); + sizeFuture.complete(false); } else { let spaceAfterOperation = that.checkAvailableSpace(updatedAccumApparentSize); if (spaceAfterOperation < 0) { let errMsg = "File copy operation exceeds available space\n" + "Please free up " + helpers.convertBytesToHumanReadable('' + -spaceAfterOperation) + " and try again"; that.$toast.error(errMsg, {timeout:false}) that.showSpinner = false; - future.complete(false); + sizeFuture.complete(false); } else { that.reduceSizeCalculation(index + 1, path, fileTreeNodes, updatedAccumApparentSize, sizeFuture); } @@ -2329,9 +2333,11 @@ module.exports = { let future = peergos.shared.util.Futures.incomplete(); this.reduceMove(0, that.path, clipboard.parent, target, clipboard.fileTreeNodes, future); future.thenApply(res => { - that.showSpinner = false; - clipboard.op = null; - that.selectedFiles = []; + if (res) { + that.showSpinner = false; + clipboard.op = null; + that.selectedFiles = []; + } }); } else if (clipboard.op == "copy") { if (this.quotaBytes.toString() == '0') { @@ -2353,13 +2359,17 @@ module.exports = { let sizeFuture = peergos.shared.util.Futures.incomplete(); this.reduceSizeCalculation(0, clipboard.path, clipboard.fileTreeNodes, 0, sizeFuture); sizeFuture.thenApply(res => { - let copyFuture = peergos.shared.util.Futures.incomplete(); - that.reduceCopy(0, clipboard.fileTreeNodes, target, copyFuture); - copyFuture.thenApply(res => { - that.showSpinner = false; - clipboard.op = null; - that.selectedFiles = []; - }); + if (res) { + let copyFuture = peergos.shared.util.Futures.incomplete(); + that.reduceCopy(0, clipboard.fileTreeNodes, target, copyFuture); + copyFuture.thenApply(res2 => { + if (res2) { + that.showSpinner = false; + clipboard.op = null; + that.selectedFiles = []; + } + }); + } }); } } From e139c2e68d13f5461bcc2d4d70d4b6d20347990c Mon Sep 17 00:00:00 2001 From: Francesco Luzzana Date: Mon, 24 Jul 2023 15:34:04 +0200 Subject: [PATCH 11/18] checkboxes, focus and long press select wip --- src/components/drive/DriveGridCard.vue | 75 ++++++++++++++++++++++++-- src/components/drive/DriveTable.vue | 12 ++++- src/views/Drive.vue | 3 +- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/components/drive/DriveGridCard.vue b/src/components/drive/DriveGridCard.vue index 6f7a5846..3fa57640 100644 --- a/src/components/drive/DriveGridCard.vue +++ b/src/components/drive/DriveGridCard.vue @@ -1,5 +1,7 @@