Skip to content
Snippets Groups Projects
Commit 0341cfd6 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

UI: added methods for copying nodes and handles copy/move of multiple nodes at the same time

parent 2b1dfb56
Branches
No related tags found
No related merge requests found
...@@ -199,8 +199,9 @@ export default { ...@@ -199,8 +199,9 @@ export default {
data data
}, true, true); }, true, true);
}, },
getNodesForMove(data) { getNodesForMoveOrCopy(data) {
let url = BASE_API_URL + 'nodesForMove?path=' + escapePath(data.path) + '&nodeToMove=' + data.nodeToMove; let url = BASE_API_URL + 'nodesForMoveOrCopy?path=' + escapePath(data.path) +
'&' + data.targets.map(t => 'target=' + escapePath(t)).join('&');
return apiRequest({ return apiRequest({
method: 'GET', method: 'GET',
url: url, url: url,
...@@ -210,8 +211,8 @@ export default { ...@@ -210,8 +211,8 @@ export default {
} }
}, true, true); }, true, true);
}, },
moveNode(data) { moveOrCopyNodes(data) {
let url = BASE_API_URL + 'move'; let url = BASE_API_URL + 'moveOrCopy';
return apiRequest({ return apiRequest({
method: 'POST', method: 'POST',
url: url, url: url,
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
<b-dropdown variant="primary" text="Actions" v-if="actionsEnabled"> <b-dropdown variant="primary" text="Actions" v-if="actionsEnabled">
<b-dropdown-item :disabled="!asyncButtonEnabled" @click="startAsyncRecallJob">Async recall</b-dropdown-item> <b-dropdown-item :disabled="!asyncButtonEnabled" @click="startAsyncRecallJob">Async recall</b-dropdown-item>
<b-dropdown-item :disabled="!deleteButtonEnabled" @click="deleteNodes">Delete</b-dropdown-item> <b-dropdown-item :disabled="!deleteButtonEnabled" @click="deleteNodes">Delete</b-dropdown-item>
<b-dropdown-item :disabled="!moveButtonEnabled" @click="moveNodes">Move</b-dropdown-item>
<b-dropdown-item :disabled="!copyButtonEnabled" @click="copyNodes">Copy</b-dropdown-item>
<b-dropdown-item :disabled="!archiveButtonEnabled" @click="createArchive('zip')">Create zip archive</b-dropdown-item> <b-dropdown-item :disabled="!archiveButtonEnabled" @click="createArchive('zip')">Create zip archive</b-dropdown-item>
<b-dropdown-item :disabled="!archiveButtonEnabled" @click="createArchive('tar')">Create tar archive</b-dropdown-item> <b-dropdown-item :disabled="!archiveButtonEnabled" @click="createArchive('tar')">Create tar archive</b-dropdown-item>
</b-dropdown> </b-dropdown>
...@@ -44,7 +46,7 @@ ...@@ -44,7 +46,7 @@
<ConfirmDeleteModal /> <ConfirmDeleteModal />
<ShareModal /> <ShareModal />
<RenameModal /> <RenameModal />
<MoveModal /> <MoveOrCopyModal />
<ConfirmArchiveModal /> <ConfirmArchiveModal />
</div> </div>
</template> </template>
...@@ -56,7 +58,7 @@ import UploadFilesModal from './modal/UploadFilesModal.vue' ...@@ -56,7 +58,7 @@ import UploadFilesModal from './modal/UploadFilesModal.vue'
import ConfirmDeleteModal from './modal/ConfirmDeleteModal.vue' import ConfirmDeleteModal from './modal/ConfirmDeleteModal.vue'
import ShareModal from './modal/ShareModal.vue' import ShareModal from './modal/ShareModal.vue'
import RenameModal from './modal/RenameModal.vue' import RenameModal from './modal/RenameModal.vue'
import MoveModal from './modal/MoveModal.vue' import MoveOrCopyModal from './modal/MoveOrCopyModal.vue'
import ConfirmArchiveModal from './modal/ConfirmArchiveModal.vue' import ConfirmArchiveModal from './modal/ConfirmArchiveModal.vue'
export default { export default {
...@@ -68,7 +70,7 @@ export default { ...@@ -68,7 +70,7 @@ export default {
ConfirmDeleteModal, ConfirmDeleteModal,
ShareModal, ShareModal,
RenameModal, RenameModal,
MoveModal, MoveOrCopyModal,
ConfirmArchiveModal ConfirmArchiveModal
}, },
computed: { computed: {
...@@ -98,9 +100,15 @@ export default { ...@@ -98,9 +100,15 @@ export default {
deleteButtonEnabled() { deleteButtonEnabled() {
return this.$store.state.deleteButtonEnabled; return this.$store.state.deleteButtonEnabled;
}, },
moveButtonEnabled() {
return this.$store.state.moveButtonEnabled;
},
archiveButtonEnabled() { archiveButtonEnabled() {
return this.$store.state.archiveButtonEnabled; return this.$store.state.archiveButtonEnabled;
}, },
copyButtonEnabled() {
return this.$store.state.copyButtonEnabled;
},
writable() { writable() {
return this.$store.state.writable; return this.$store.state.writable;
} }
...@@ -147,6 +155,42 @@ export default { ...@@ -147,6 +155,42 @@ export default {
this.$store.commit('setSelectedUndeletableNodes', unDeletablePaths); this.$store.commit('setSelectedUndeletableNodes', unDeletablePaths);
this.$bvModal.show('confirm-delete-modal'); this.$bvModal.show('confirm-delete-modal');
}, },
moveNodes() {
let selectedNodesCheckboxes = document.querySelectorAll('#nodes input:checked');
let paths = [];
let unDeletablePaths = [];
for (let i = 0; i < selectedNodesCheckboxes.length; i++) {
let checkbox = selectedNodesCheckboxes[i];
let dataNode = checkbox.getAttribute('data-node');
if (checkbox.classList.contains('deletable')) {
paths.push(dataNode);
} else {
unDeletablePaths.push(dataNode);
}
}
this.$store.commit('setNodesToMoveOrCopy', paths);
this.$store.commit('setMoveOrCopy', 'move');
this.$store.commit('setSelectedNotMovableNodes', unDeletablePaths);
this.$bvModal.show('move-or-copy-modal');
},
copyNodes() {
let selectedNodesCheckboxes = document.querySelectorAll('#nodes input:checked');
let paths = [];
let unCopiablePaths = [];
for (let i = 0; i < selectedNodesCheckboxes.length; i++) {
let checkbox = selectedNodesCheckboxes[i];
let dataNode = checkbox.getAttribute('data-node');
if (checkbox.classList.contains('async')) {
unCopiablePaths.push(dataNode);
} else {
paths.push(dataNode);
}
}
this.$store.commit('setNodesToMoveOrCopy', paths);
this.$store.commit('setMoveOrCopy', 'copy');
this.$store.commit('setSelectedNotCopiableNodes', unCopiablePaths);
this.$bvModal.show('move-or-copy-modal');
},
createArchive(type) { createArchive(type) {
let selectedNodesCheckboxes = document.querySelectorAll('#nodes input:checked'); let selectedNodesCheckboxes = document.querySelectorAll('#nodes input:checked');
let nodesToArchive = []; let nodesToArchive = [];
......
...@@ -4,26 +4,56 @@ ...@@ -4,26 +4,56 @@
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
--> -->
<template> <template>
<b-modal id="move-modal" :title="'Move ' + nodeToMove + ' to ' + destinationPath" okTitle="Move node" @show="afterShow" @ok.prevent="moveNode" :ok-disabled="!writable" size="lg"> <b-modal id="move-or-copy-modal" :title="title" :okTitle="okTitle" @show="afterShow" @ok.prevent="moveOrCopyNodes" :ok-disabled="!writable" size="lg">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item" v-for="(item, i) in breadcrumbs" :key="i" :class="{ 'active' : item.active }"> <li class="breadcrumb-item" v-for="(item, i) in breadcrumbs" :key="i" :class="{ 'active' : item.active }">
<a href="#" @click.stop.prevent="breadcrumbClick(i)" v-if="!item.active">{{item.text}}</a> <a href="#" @click.stop.prevent="breadcrumbClick(i)" v-if="!item.active">{{item.text}}</a>
<span v-if="item.active">{{item.text}}</span> <span v-if="item.active">{{item.text}}</span>
</li> </li>
</ol> </ol>
<div id="move-nodes-wrapper"> <div id="move-or-copy-nodes-wrapper">
<div id="move-nodes"></div> <div id="move-or-copy-nodes"></div>
</div>
<div v-if="selectedNotCopiableNodes.length > 0" class="mt-3">
<p><strong>Warning</strong>: following selected nodes can't be copied and will be ignored:</p>
<p>
<ul>
<li v-for="node in selectedNotCopiableNodes" :key="node">{{node}}</li>
</ul>
</p>
</div>
<div v-if="selectedNotMovableNodes.length > 0" class="mt-3">
<p><strong>Warning</strong>: following selected nodes can't be moved and will be ignored:</p>
<p>
<ul>
<li v-for="node in selectedNotMovableNodes" :key="node">{{node}}</li>
</ul>
</p>
</div> </div>
</b-modal> </b-modal>
</template> </template>
<script> <script>
export default { export default {
name: 'MoveModal', name: 'MoveOrCopyModal',
computed: { computed: {
nodeToMove() { return this.$store.state.nodeToMove }, nodesToMoveOrCopy() { return this.$store.state.nodesToMoveOrCopy },
destinationPath() { return this.$store.state.nodeToMoveDestination }, destinationPath() { return this.$store.state.moveOrCopyDestination },
writable() { return this.$store.state.nodeToMoveDestinationWritable }, writable() { return this.$store.state.moveOrCopyDestinationWritable },
moveOrCopy() { return this.$store.state.moveOrCopy },
selectedNotMovableNodes() { return this.$store.state.selectedNotMovableNodes },
selectedNotCopiableNodes() { return this.$store.state.selectedNotCopiableNodes },
title() {
let text = this.moveOrCopy === 'move' ? 'Move ' : 'Copy ';
if (this.nodesToMoveOrCopy.length === 1) {
text += this.nodesToMoveOrCopy[0];
} else {
text += this.nodesToMoveOrCopy.length + ' nodes';
}
text += ' to ' + this.destinationPath;
return text;
},
okTitle() { return (this.moveOrCopy === 'move' ? 'Move nodes' : 'Copy nodes') },
breadcrumbs() { breadcrumbs() {
let items = []; let items = [];
if (this.destinationPath !== null) { if (this.destinationPath !== null) {
...@@ -41,7 +71,8 @@ export default { ...@@ -41,7 +71,8 @@ export default {
methods: { methods: {
afterShow() { afterShow() {
// starts from parent path // starts from parent path
this.$store.dispatch('openNodeInMoveModal', this.nodeToMove.substring(0, this.nodeToMove.lastIndexOf('/'))); let firstSelectedNode = this.nodesToMoveOrCopy[0];
this.$store.dispatch('openNodeInMoveOrCopyModal', firstSelectedNode.substring(0, firstSelectedNode.lastIndexOf('/')));
}, },
breadcrumbClick(i) { breadcrumbClick(i) {
let pathSplit = this.destinationPath.split('/'); let pathSplit = this.destinationPath.split('/');
...@@ -49,16 +80,17 @@ export default { ...@@ -49,16 +80,17 @@ export default {
if (path === '') { if (path === '') {
path = '/'; path = '/';
} }
this.$store.commit('setNodeToMoveDestination', path); this.$store.commit('setMoveOrCopyDestination', path);
window.openNodeInMoveModal(event, path); window.openNodeInMoveOrCopyModal(event, path);
}, },
moveNode() { moveOrCopyNodes() {
this.$store.dispatch('moveNode', { this.$store.dispatch('moveOrCopyNodes', {
target: this.nodeToMove, targets: this.nodesToMoveOrCopy,
direction: this.destinationPath direction: this.destinationPath,
keepBytes: this.moveOrCopy === 'copy'
}) })
.then(() => { .then(() => {
this.$bvModal.hide('move-modal'); this.$bvModal.hide('move-or-copy-modal');
}) })
} }
} }
...@@ -66,13 +98,13 @@ export default { ...@@ -66,13 +98,13 @@ export default {
</script> </script>
<style> <style>
#move-nodes .list-group-item { #move-or-copy-nodes .list-group-item {
/* reduced padding */ /* reduced padding */
padding-top: .35rem; padding-top: .35rem;
padding-bottom: .35rem; padding-bottom: .35rem;
} }
#move-nodes-wrapper { #move-or-copy-nodes-wrapper {
max-height: 300px; max-height: 300px;
overflow-y: auto; overflow-y: auto;
} }
......
...@@ -54,9 +54,10 @@ export default { ...@@ -54,9 +54,10 @@ export default {
this.newNameError = "Name contains an illegal character. Following characters are not allowed: < > ? \" : \\ / | ' * `"; this.newNameError = "Name contains an illegal character. Following characters are not allowed: < > ? \" : \\ / | ' * `";
} else { } else {
let parentPath = this.nodeToRename.substring(0, this.nodeToRename.lastIndexOf('/') + 1); let parentPath = this.nodeToRename.substring(0, this.nodeToRename.lastIndexOf('/') + 1);
this.$store.dispatch('moveNode', { this.$store.dispatch('moveOrCopyNodes', {
target: parentPath + this.oldName, targets: [parentPath + this.oldName],
direction: parentPath + this.newName direction: parentPath + this.newName,
keepBytes: false
}) })
.then(() => { .then(() => {
this.$bvModal.hide('rename-modal'); this.$bvModal.hide('rename-modal');
......
...@@ -36,13 +36,23 @@ window.renameNode = function(path) { ...@@ -36,13 +36,23 @@ window.renameNode = function(path) {
vm.$bvModal.show('rename-modal'); vm.$bvModal.show('rename-modal');
} }
window.moveNode = function(path) { window.moveNode = function(path) {
store.commit('setNodeToMove', path); moveOrCopy(path, 'move');
vm.$bvModal.show('move-modal');
} }
window.openNodeInMoveModal = function(event, path) { window.copyNode = function(path) {
moveOrCopy(path, 'copy');
}
window.openNodeInMoveOrCopyModal = function(event, path) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
store.dispatch('openNodeInMoveModal', path); store.dispatch('openNodeInMoveOrCopyModal', path);
}
function moveOrCopy(path, moveOrCopy) {
store.commit('setNodesToMoveOrCopy', [path]);
store.commit('setMoveOrCopy', moveOrCopy);
store.commit('setSelectedNotMovableNodes', []);
store.commit('setSelectedNotCopiableNodes', []);
vm.$bvModal.show('move-or-copy-modal');
} }
export default { export default {
......
...@@ -29,7 +29,9 @@ export default new Vuex.Store({ ...@@ -29,7 +29,9 @@ export default new Vuex.Store({
nodesLoading: false, nodesLoading: false,
asyncButtonEnabled: false, asyncButtonEnabled: false,
deleteButtonEnabled: false, deleteButtonEnabled: false,
moveButtonEnabled: false,
archiveButtonEnabled: false, archiveButtonEnabled: false,
copyButtonEnabled: false,
jobs: [], jobs: [],
jobsLoading: true, jobsLoading: true,
lastJobsCheckTime: null, lastJobsCheckTime: null,
...@@ -43,9 +45,12 @@ export default new Vuex.Store({ ...@@ -43,9 +45,12 @@ export default new Vuex.Store({
groupWrite: null groupWrite: null
}, },
nodeToRename: null, nodeToRename: null,
nodeToMove: null, moveOrCopy: 'move',
nodeToMoveDestination: null, nodesToMoveOrCopy: [],
nodeToMoveDestinationWritable: false, selectedNotMovableNodes: [],
selectedNotCopiableNodes: [],
moveOrCopyDestination: null,
moveOrCopyDestinationWritable: false,
archiveType: null, archiveType: null,
nodesToArchive: [], nodesToArchive: [],
selectedNotArchivableNodes: [] selectedNotArchivableNodes: []
...@@ -72,9 +77,15 @@ export default new Vuex.Store({ ...@@ -72,9 +77,15 @@ export default new Vuex.Store({
setDeleteButtonEnabled(state, value) { setDeleteButtonEnabled(state, value) {
state.deleteButtonEnabled = value; state.deleteButtonEnabled = value;
}, },
setMoveButtonEnabled(state, value) {
state.moveButtonEnabled = value;
},
setArchiveButtonEnabled(state, value) { setArchiveButtonEnabled(state, value) {
state.archiveButtonEnabled = value; state.archiveButtonEnabled = value;
}, },
setCopyButtonEnabled(state, value) {
state.copyButtonEnabled = value;
},
setJobs(state, jobs) { setJobs(state, jobs) {
updateArray(state.jobs, jobs); updateArray(state.jobs, jobs);
}, },
...@@ -93,6 +104,9 @@ export default new Vuex.Store({ ...@@ -93,6 +104,9 @@ export default new Vuex.Store({
setSelectedUndeletableNodes(state, paths) { setSelectedUndeletableNodes(state, paths) {
updateArray(state.selectedUndeletableNodes, paths); updateArray(state.selectedUndeletableNodes, paths);
}, },
setSelectedNotCopiableNodes(state, paths) {
updateArray(state.selectedNotCopiableNodes, paths);
},
setWritable(state, value) { setWritable(state, value) {
state.writable = value; state.writable = value;
}, },
...@@ -104,14 +118,20 @@ export default new Vuex.Store({ ...@@ -104,14 +118,20 @@ export default new Vuex.Store({
setNodeToRename(state, path) { setNodeToRename(state, path) {
state.nodeToRename = path; state.nodeToRename = path;
}, },
setNodeToMove(state, path) { setMoveOrCopy(state, value) {
state.nodeToMove = path; state.moveOrCopy = value;
},
setNodesToMoveOrCopy(state, paths) {
updateArray(state.nodesToMoveOrCopy, paths);
}, },
setNodeToMoveDestination(state, path) { setMoveOrCopyDestination(state, path) {
state.nodeToMoveDestination = path; state.moveOrCopyDestination = path;
}, },
setNodeToMoveDestinationWritable(state, value) { setMoveOrCopyDestinationWritable(state, value) {
state.nodeToMoveDestinationWritable = value; state.moveOrCopyDestinationWritable = value;
},
setSelectedNotMovableNodes(state, paths) {
updateArray(state.selectedNotMovableNodes, paths);
}, },
setArchiveType(state, type) { setArchiveType(state, type) {
state.archiveType = type; state.archiveType = type;
...@@ -147,7 +167,9 @@ export default new Vuex.Store({ ...@@ -147,7 +167,9 @@ export default new Vuex.Store({
computeButtonsVisibility({ commit }) { computeButtonsVisibility({ commit }) {
commit('setAsyncButtonEnabled', document.querySelectorAll('#nodes input.async:checked').length > 0); commit('setAsyncButtonEnabled', document.querySelectorAll('#nodes input.async:checked').length > 0);
commit('setDeleteButtonEnabled', document.querySelectorAll('#nodes input.deletable:checked').length > 0); commit('setDeleteButtonEnabled', document.querySelectorAll('#nodes input.deletable:checked').length > 0);
commit('setMoveButtonEnabled', document.querySelectorAll('#nodes input.deletable:checked').length > 0);
commit('setArchiveButtonEnabled', document.querySelectorAll('#nodes input:not(.async):checked').length > 0); commit('setArchiveButtonEnabled', document.querySelectorAll('#nodes input:not(.async):checked').length > 0);
commit('setCopyButtonEnabled', document.querySelectorAll('#nodes input:not(.async):checked').length > 0);
}, },
startAsyncRecallJob({ state, commit, dispatch }) { startAsyncRecallJob({ state, commit, dispatch }) {
let asyncCheckboxes = document.querySelectorAll('#nodes input.async:checked'); let asyncCheckboxes = document.querySelectorAll('#nodes input.async:checked');
...@@ -215,24 +237,27 @@ export default new Vuex.Store({ ...@@ -215,24 +237,27 @@ export default new Vuex.Store({
dispatch('setPath', state.path); dispatch('setPath', state.path);
}); });
}, },
moveNode({ state, commit, dispatch }, data) { moveOrCopyNodes({ state, commit, dispatch }, data) {
client.moveNode(data) client.moveOrCopyNodes(data)
.then(job => { .then(jobs => {
if (job.phase === 'COMPLETED') { let uncompletedJobs = jobs.filter(j => j.phase !== 'COMPLETED');
if (uncompletedJobs.length === 0) {
// Reload current node // Reload current node
dispatch('setPath', state.path); dispatch('setPath', state.path);
} else { } else {
main.showInfo('Move operation is taking some time and will be handled in background'); main.showInfo('The operation is taking some time and will be handled in background');
for (let job of uncompletedJobs) {
commit('addJob', job); commit('addJob', job);
} }
}
}); });
}, },
openNodeInMoveModal({ state, commit }, path) { openNodeInMoveOrCopyModal({ state, commit }, path) {
commit('setNodeToMoveDestination', path); commit('setMoveOrCopyDestination', path);
client.getNodesForMove({ path, nodeToMove: state.nodeToMove }) client.getNodesForMoveOrCopy({ path, targets: state.nodesToMoveOrCopy })
.then(res => { .then(res => {
commit('setNodeToMoveDestinationWritable', res.writable); commit('setMoveOrCopyDestinationWritable', res.writable);
document.getElementById('move-nodes').outerHTML = res.html; document.getElementById('move-or-copy-nodes').outerHTML = res.html;
}); });
}, },
createArchive({ state, commit }) { createArchive({ state, commit }) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment