Implement moving items between folders

This commit is contained in:
Marcel Klehr 2019-08-25 17:08:20 +02:00
parent a13fc4bd5f
commit 5d6c5c3748
6 changed files with 308 additions and 20 deletions

View File

@ -32,6 +32,9 @@
<ActionButton icon="icon-rename" @click="onRename">{{
t('bookmarks', 'Rename')
}}</ActionButton>
<ActionButton icon="icon-category-files" @click="onMove">{{
t('bookmarks', 'Move')
}}</ActionButton>
<ActionButton icon="icon-delete" @click="onDelete">{{
t('bookmarks', 'Delete')
}}</ActionButton>
@ -52,7 +55,7 @@
</template>
<script>
import { Actions, ActionButton } from 'nextcloud-vue';
import { actions } from '../store';
import { actions, mutations } from '../store';
import TagLine from './TagLine';
export default {
@ -95,11 +98,19 @@ export default {
},
methods: {
onDelete() {
this.$store.dispatch(actions.DELETE_BOOKMARK, this.bookmark.id);
this.$store.dispatch(actions.DELETE_BOOKMARK, {
id: this.bookmark.id,
folder: this.$store.state.fetchState.query.folder
});
},
onDetails() {
this.$store.dispatch(actions.OPEN_BOOKMARK, this.bookmark.id);
},
onMove() {
this.$store.commit(mutations.RESET_SELECTION);
this.$store.commit(mutations.ADD_SELECTION_BOOKMARK, this.bookmark);
this.$store.commit(mutations.DISPLAY_MOVE_DIALOG, true);
},
onRename() {
this.renaming = true;
},

View File

@ -10,12 +10,12 @@
{{ folder.title }}
</h3>
<Actions class="Bookmarks__BookmarksList__Folder__Actions">
<ActionButton icon="icon-info" @click="onDetails">{{
t('bookmarks', 'Details')
}}</ActionButton>
<ActionButton icon="icon-rename" @click="onRename">{{
t('bookmarks', 'Rename')
}}</ActionButton>
<ActionButton icon="icon-category-folder" @click="onMove">{{
t('bookmarks', 'Move')
}}</ActionButton>
<ActionButton icon="icon-delete" @click="onDelete">{{
t('bookmarks', 'Delete')
}}</ActionButton>
@ -58,7 +58,11 @@ export default {
onDelete() {
this.$store.dispatch(actions.DELETE_FOLDER, this.folder.id);
},
onDetails() {},
onMove() {
this.$store.commit(mutations.RESET_SELECTION);
this.$store.commit(mutations.ADD_SELECTION_FOLDER, this.folder);
this.$store.commit(mutations.DISPLAY_MOVE_DIALOG, true);
},
onSelect() {
this.$router.push({ name: 'folder', params: { folder: this.folder.id } });
},

View File

@ -0,0 +1,84 @@
<template>
<Modal v-if="showModal" @close="onClose" :title="title">
<div class="Bookmarks__ModalMove">
<TreeFolder
:folder="{
title: t('bookmarks', 'Root folder'),
id: '-1',
children: allFolders
}"
@select="onSelect"
/>
</div>
</Modal>
</template>
<script>
import { Modal } from 'nextcloud-vue';
import { actions, mutations } from '../store';
import TreeFolder from './TreeFolder';
export default {
name: 'ModalMove',
components: {
Modal,
TreeFolder
},
created() {},
computed: {
showModal() {
return this.$store.state.displayMoveDialog;
},
selection() {
return this.$store.state.selection;
},
allFolders() {
return this.$store.state.folders;
},
title() {
if (this.selection.folders.length) {
if (this.selection.bookmarks.length) {
return n(
'bookmarks',
'Moving %n folder and some bookmarks',
'Moving %n folders and some bookmarks',
this.selection.folders.length
);
} else {
return n(
'bookmarks',
'Moving %n folder',
'Moving %n folders',
this.selection.folders.length
);
}
} else {
return n(
'bookmarks',
'Moving %n bookmark',
'Moving %n bookmarks',
this.selection.bookmarks.length
);
}
}
},
methods: {
async onSelect(folderId) {
await this.$store.dispatch(actions.MOVE_SELECTION, folderId);
this.$store.commit(mutations.RESET_SELECTION);
this.$store.commit(mutations.DISPLAY_MOVE_DIALOG, false);
this.$store.dispatch(actions.RELOAD_VIEW);
},
onClose() {
this.$store.commit(mutations.DISPLAY_MOVE_DIALOG, false);
}
}
};
</script>
<style>
.Bookmarks__ModalMove {
min-width: 300px;
height: 300px;
overflow-y: scroll;
padding: 10px;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<div class="Bookmarks__TreeFolder">
<div class="Bookmarks__TreeFolder__Title">
<h4 @click="showChildren = !showChildren">
<figure class="icon-folder"></figure>
{{ folder.title }}
</h4>
<button @click="$emit('select', folder.id)">
{{ t('bookmarks', 'Select') }}
</button>
</div>
<div class="Bookmarks__TreeFolder__Children" v-if="showChildren">
<TreeFolder
v-for="folder in folder.children"
:folder="folder"
@select="$emit('select', $event)"
/>
</div>
</div>
</template>
<script>
import { Modal } from 'nextcloud-vue';
import { actions, mutations } from '../store';
import TreeFolder from './TreeFolder';
export default {
name: 'TreeFolder',
components: {
Modal,
TreeFolder
},
props: {
folder: {
type: Object,
required: true
}
},
data() {
return { showChildren: false };
}
};
</script>
<style>
.Bookmarks__TreeFolder__Title {
display: flex;
align-items: center;
}
.Bookmarks__TreeFolder__Title > h4 {
flex: 1;
display: flex;
cursor: pointer;
}
.Bookmarks__TreeFolder__Title > h4 > figure {
margin: 0 5px;
}
.Bookmarks__TreeFolder__Title > button {
flex: 0;
}
.Bookmarks__TreeFolder__Children {
padding-left: 20px;
}
</style>

View File

@ -6,6 +6,7 @@
<BookmarksList :loading="loading.bookmarks" :bookmarks="bookmarks" />
</AppContent>
<SidebarBookmark />
<ModalMove />
</Content>
</template>
@ -15,6 +16,7 @@ import Navigation from './Navigation';
import BookmarksList from './BookmarksList';
import Breadcrumbs from './Breadcrumbs';
import SidebarBookmark from './SidebarBookmark';
import ModalMove from './ModalMove';
import { actions } from '../store';
export default {
@ -25,7 +27,8 @@ export default {
AppContent,
Breadcrumbs,
BookmarksList,
SidebarBookmark
SidebarBookmark,
ModalMove
},
data: function() {
return {

View File

@ -10,6 +10,12 @@ const BATCH_SIZE = 42;
export const mutations = {
DISPLAY_NEW_BOOKMARK: 'DISPLAY_NEW_BOOKMARK',
DISPLAY_NEW_FOLDER: 'DISPLAY_NEW_FOLDER',
DISPLAY_MOVE_DIALOG: 'DISPLAY_MOVE_DIALOG',
RESET_SELECTION: 'RESET_SELECTION',
REMOVE_SELECTION_BOOKMARK: 'REMOVE_SELECTION_BOOKMARK',
ADD_SELECTION_BOOKMARK: 'ADD_SELECTION_BOOKMARK',
REMOVE_SELECTION_FOLDER: 'REMOVE_SELECTION_FOLDER',
ADD_SELECTION_FOLDER: 'ADD_SELECTION_FOLDER',
ADD_BOOKMARK: 'ADD_BOOKMARK',
REMOVE_BOOKMARK: 'REMOVE_BOOKMARK',
REMOVE_ALL_BOOKMARK: 'REMOVE_ALL_BOOKMARK',
@ -33,6 +39,7 @@ export const actions = {
DELETE_BOOKMARK: 'DELETE_BOOKMARK',
OPEN_BOOKMARK: 'OPEN_BOOKMARK',
SAVE_BOOKMARK: 'SAVE_BOOKMARK',
MOVE_BOOKMARK: 'MOVE_BOOKMARK',
IMPORT_BOOKMARKS: 'IMPORT_BOOKMARKS',
DELETE_BOOKMARKS: 'IMPORT_BOOKMARKS',
@ -45,6 +52,10 @@ export const actions = {
SAVE_FOLDER: 'SAVE_FOLDER',
DELETE_FOLDER: 'DELETE_FOLDER',
MOVE_SELECTION: 'MOVE_SELECTION',
RELOAD_VIEW: 'RELOAD_VIEW',
NO_FILTER: 'NO_FILTER',
FILTER_BY_RECENT: 'FILTER_BY_RECENT',
FILTER_BY_UNTAGGED: 'FILTER_BY_UNTAGGED',
@ -84,8 +95,13 @@ export default new Vuex.Store({
tags: [],
folders: [],
foldersById: {},
selection: {
folders: [],
bookmarks: []
},
displayNewBookmark: false,
displayNewFolder: false,
displayMoveDialog: false,
sidebar: null,
viewMode: 'list'
},
@ -121,10 +137,36 @@ export default new Vuex.Store({
[mutations.DISPLAY_NEW_BOOKMARK](state, display) {
state.displayNewBookmark = display;
},
[mutations.DISPLAY_NEW_FOLDER](state, display) {
state.displayNewFolder = display;
},
[mutations.DISPLAY_MOVE_DIALOG](state, display) {
state.displayMoveDialog = display;
},
[mutations.RESET_SELECTION](state) {
state.selection = { folders: [], bookmarks: [] };
},
[mutations.ADD_SELECTION_BOOKMARK](state, item) {
state.selection.bookmarks.push(item);
},
[mutations.REMOVE_SELECTION_BOOKMARK](state, item) {
Vue.set(
state.selection,
'bookmarks',
state.selection.bookmarks.filter(s => !(s.id === item.id))
);
},
[mutations.ADD_SELECTION_FOLDER](state, item) {
state.selection.folders.push(item);
},
[mutations.REMOVE_SELECTION_FOLDER](state, item) {
Vue.set(
state.selection,
'folders',
state.selection.folders.filter(s => !(s.id === item.id))
);
},
[mutations.ADD_BOOKMARK](state, bookmark) {
const existingBookmark = state.bookmarksById[bookmark.id];
@ -232,29 +274,75 @@ export default new Vuex.Store({
commit(mutations.FETCH_END, 'saveBookmark');
});
},
async [actions.MOVE_BOOKMARK](
{ commit, dispatch, state },
{ bookmark, oldFolder, newFolder }
) {
commit(mutations.FETCH_START, 'moveBookmark');
try {
let response = await axios.post(
url(`/folder/${newFolder}/bookmarks/${bookmark}`)
);
if (response.data.status !== 'success') {
throw new Error(response.data);
}
let response2 = await axios.delete(
url(`/folder/${oldFolder}/bookmarks/${bookmark}`)
);
if (response2.data.status !== 'success') {
throw new Error(response2.data);
}
} catch (err) {
console.error(err);
commit(
mutations.SET_ERROR,
AppGlobal.methods.t('bookmarks', 'Failed to move bookmark')
);
throw err;
} finally {
commit(mutations.FETCH_END, 'moveBookmark');
}
},
[actions.OPEN_BOOKMARK]({ commit }, id) {
commit(mutations.SET_SIDEBAR, { type: 'bookmark', id });
},
[actions.DELETE_BOOKMARK]({ commit, dispatch, state }, id) {
return axios
.delete(url(`/bookmark/${id}`))
.then(response => {
const {
data: { status }
} = response;
if (status !== 'success') {
async [actions.DELETE_BOOKMARK](
{ commit, dispatch, state },
{ id, folder }
) {
if (folder) {
try {
const response = await axios.delete(
url(`/folder/${folder}/bookmarks/${id}`)
);
if (response.data.status !== 'success') {
throw new Error(response.data);
}
commit(mutations.REMOVE_BOOKMARK, id);
})
.catch(err => {
} catch (err) {
console.error(err);
commit(
mutations.SET_ERROR,
AppGlobal.methods.t('bookmarks', 'Failed to delete bookmark')
);
throw err;
});
}
return;
}
try {
const response = await axios.delete(url(`/bookmark/${id}`));
if (response.data.status !== 'success') {
throw new Error(response.data);
}
commit(mutations.REMOVE_BOOKMARK, id);
} catch (err) {
console.error(err);
commit(
mutations.SET_ERROR,
AppGlobal.methods.t('bookmarks', 'Failed to delete bookmark')
);
throw err;
}
},
[actions.IMPORT_BOOKMARKS]({ commit, dispatch, state }, file) {
var data = new FormData();
@ -453,7 +541,7 @@ export default new Vuex.Store({
},
[actions.SAVE_FOLDER]({ commit, dispatch, state }, id) {
const folder = this.getters.getFolder(id)[0];
commit(mutations.FETCH_END, 'saveFolder');
commit(mutations.FETCH_START, 'saveFolder');
return axios
.put(url(`/folder/${id}`), {
parent_folder: folder.parent_folder,
@ -480,6 +568,42 @@ export default new Vuex.Store({
});
},
async [actions.MOVE_SELECTION]({ commit, dispatch, state }, folderId) {
commit(mutations.FETCH_START, 'moveSelection');
try {
for (const folder of state.selection.folders) {
if (folderId === folder.id) {
throw new Error('Cannot move folder into itself');
}
folder.parent_folder = folderId;
await dispatch(actions.SAVE_FOLDER, folder.id);
}
for (const bookmark of state.selection.bookmarks) {
await dispatch(actions.MOVE_BOOKMARK, {
oldFolder: bookmark.folders[bookmark.folders.length - 1], // FIXME This is veeeery ugly and will cause issues. Inevitably.
newFolder: folderId,
bookmark: bookmark.id
});
}
} catch (err) {
console.error(err);
commit(
mutations.SET_ERROR,
AppGlobal.methods.t('bookmarks', 'Failed to move parts of selection')
);
throw err;
} finally {
commit(mutations.FETCH_END, 'moveSelection');
}
},
[actions.RELOAD_VIEW]({ state, dispatch, commit }) {
commit(mutations.SET_QUERY, state.fetchState.query);
dispatch(actions.LOAD_FOLDERS);
dispatch(actions.LOAD_TAGS);
},
[actions.NO_FILTER]({ dispatch, commit }) {
commit(mutations.SET_QUERY, {});
return dispatch(actions.FETCH_PAGE);