Explorer context menu
This commit is contained in:
parent
4b5cd7aef7
commit
b06a6a37eb
@ -4,6 +4,7 @@
|
|||||||
<layout v-else></layout>
|
<layout v-else></layout>
|
||||||
<modal v-if="showModal"></modal>
|
<modal v-if="showModal"></modal>
|
||||||
<notification></notification>
|
<notification></notification>
|
||||||
|
<context-menu></context-menu>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ import Vue from 'vue';
|
|||||||
import Layout from './Layout';
|
import Layout from './Layout';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import Notification from './Notification';
|
import Notification from './Notification';
|
||||||
|
import ContextMenu from './ContextMenu';
|
||||||
import SplashScreen from './SplashScreen';
|
import SplashScreen from './SplashScreen';
|
||||||
import syncSvc from '../services/syncSvc';
|
import syncSvc from '../services/syncSvc';
|
||||||
import networkSvc from '../services/networkSvc';
|
import networkSvc from '../services/networkSvc';
|
||||||
@ -71,6 +73,7 @@ export default {
|
|||||||
Layout,
|
Layout,
|
||||||
Modal,
|
Modal,
|
||||||
Notification,
|
Notification,
|
||||||
|
ContextMenu,
|
||||||
SplashScreen,
|
SplashScreen,
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
80
src/components/ContextMenu.vue
Normal file
80
src/components/ContextMenu.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="context-menu" v-if="items.length" @click="close()" @contextmenu.prevent="close()">
|
||||||
|
<div class="context-menu__inner flex flex--column" :style="{ left: coordinates.left + 'px', top: coordinates.top + 'px' }" @click.stop>
|
||||||
|
<div v-for="(item, idx) in items" :key="idx">
|
||||||
|
<div class="context-menu__separator" v-if="item.type === 'separator'"></div>
|
||||||
|
<div class="context-menu__item context-menu__item--disabled" v-else-if="item.disabled">{{item.name}}</div>
|
||||||
|
<a class="context-menu__item" href="javascript:void(0)" v-else @click.stop="close(item)">{{item.name}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
...mapState('contextMenu', [
|
||||||
|
'coordinates',
|
||||||
|
'items',
|
||||||
|
'resolve',
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close(item) {
|
||||||
|
if (item) {
|
||||||
|
this.resolve(item);
|
||||||
|
}
|
||||||
|
this.$store.dispatch('contextMenu/close');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.context-menu {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
$padding: 5px;
|
||||||
|
|
||||||
|
.context-menu__inner {
|
||||||
|
position: absolute;
|
||||||
|
background-color: #ebebeb;
|
||||||
|
border-radius: $padding;
|
||||||
|
padding: $padding 0;
|
||||||
|
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.16), 0 3px 10px 1px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu__item {
|
||||||
|
display: block;
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.context-menu__item {
|
||||||
|
&:active,
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
background-color: #338dfc;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu__item--disabled {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu__separator {
|
||||||
|
border-top: 2px solid #dcdcdd;
|
||||||
|
margin: $padding 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -8,7 +8,7 @@
|
|||||||
<button class="side-title__button button" @click="newItem(true)" v-title="'New folder'">
|
<button class="side-title__button button" @click="newItem(true)" v-title="'New folder'">
|
||||||
<icon-folder-plus></icon-folder-plus>
|
<icon-folder-plus></icon-folder-plus>
|
||||||
</button>
|
</button>
|
||||||
<button class="side-title__button button" @click="deleteItem()" v-title="'Remove'">
|
<button class="side-title__button button" @click="deleteItem()" v-title="'Delete'">
|
||||||
<icon-delete></icon-delete>
|
<icon-delete></icon-delete>
|
||||||
</button>
|
</button>
|
||||||
<button class="side-title__button button" @click="editItem()" v-title="'Rename'">
|
<button class="side-title__button button" @click="editItem()" v-title="'Rename'">
|
||||||
@ -39,63 +39,21 @@ export default {
|
|||||||
]),
|
]),
|
||||||
...mapGetters('explorer', [
|
...mapGetters('explorer', [
|
||||||
'rootNode',
|
'rootNode',
|
||||||
|
'selectedNode',
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('data', [
|
...mapActions('data', [
|
||||||
'toggleExplorer',
|
'toggleExplorer',
|
||||||
]),
|
]),
|
||||||
newItem(isFolder) {
|
...mapActions('explorer', [
|
||||||
let parentId = this.$store.getters['explorer/selectedNodeFolder'].item.id;
|
'newItem',
|
||||||
if (parentId === 'trash') {
|
'deleteItem',
|
||||||
parentId = null;
|
]),
|
||||||
}
|
|
||||||
this.$store.dispatch('explorer/openNode', parentId);
|
|
||||||
this.$store.commit('explorer/setNewItem', {
|
|
||||||
type: isFolder ? 'folder' : 'file',
|
|
||||||
parentId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
editItem() {
|
editItem() {
|
||||||
const selectedNode = this.$store.getters['explorer/selectedNode'];
|
const node = this.selectedNode;
|
||||||
if (selectedNode.item.id !== 'trash') {
|
if (!node.isTrash) {
|
||||||
this.$store.commit('explorer/setEditingId', selectedNode.item.id);
|
this.$store.commit('explorer/setEditingId', node.item.id);
|
||||||
}
|
|
||||||
},
|
|
||||||
deleteItem() {
|
|
||||||
const selectedNode = this.$store.getters['explorer/selectedNode'];
|
|
||||||
if (!selectedNode.isNil) {
|
|
||||||
if (selectedNode.item.id === 'trash' || selectedNode.item.parentId === 'trash') {
|
|
||||||
this.$store.dispatch('modal/trashDeletion');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$store.dispatch(selectedNode.isFolder
|
|
||||||
? 'modal/folderDeletion'
|
|
||||||
: 'modal/fileDeletion',
|
|
||||||
selectedNode.item)
|
|
||||||
.then(() => {
|
|
||||||
if (selectedNode === this.$store.getters['explorer/selectedNode']) {
|
|
||||||
if (selectedNode.isFolder) {
|
|
||||||
const recursiveMoveToTrash = (folderNode) => {
|
|
||||||
folderNode.folders.forEach(recursiveMoveToTrash);
|
|
||||||
folderNode.files.forEach((fileNode) => {
|
|
||||||
this.$store.commit('file/patchItem', {
|
|
||||||
id: fileNode.item.id,
|
|
||||||
parentId: 'trash',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.$store.commit('folder/deleteItem', folderNode.item.id);
|
|
||||||
};
|
|
||||||
recursiveMoveToTrash(selectedNode);
|
|
||||||
} else {
|
|
||||||
this.$store.commit('file/patchItem', {
|
|
||||||
id: selectedNode.item.id,
|
|
||||||
parentId: 'trash',
|
|
||||||
});
|
|
||||||
this.$store.commit('file/setCurrentId', this.$store.getters['data/lastOpenedIds'][1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--open': isOpen, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="setDragTarget(node.item.id)" @dragleave.stop="isDragTarget && setDragTargetId()" @drop.prevent.stop="onDrop">
|
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--open': isOpen, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="setDragTarget(node.item.id)" @dragleave.stop="isDragTarget && setDragTargetId()" @drop.prevent.stop="onDrop" @contextmenu="onContextMenu">
|
||||||
<div class="explorer-node__item-editor" v-if="isEditing" :class="['explorer-node__item-editor--' + node.item.type]" :style="{paddingLeft: leftPadding}" draggable="true" @dragstart.stop.prevent>
|
<div class="explorer-node__item-editor" v-if="isEditing" :class="['explorer-node__item-editor--' + node.item.type]" :style="{paddingLeft: leftPadding}" draggable="true" @dragstart.stop.prevent>
|
||||||
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keyup.enter="submitEdit()" @keyup.esc="submitEdit(true)" v-model="editingNodeName">
|
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keyup.enter="submitEdit()" @keyup.esc="submitEdit(true)" v-model="editingNodeName">
|
||||||
</div>
|
</div>
|
||||||
<div class="explorer-node__item" v-else :class="['explorer-node__item--' + node.item.type]" :style="{paddingLeft: leftPadding}" @click="select(node.item.id)" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTargetId()">
|
<div class="explorer-node__item" v-else :class="['explorer-node__item--' + node.item.type]" :style="{paddingLeft: leftPadding}" @click="select()" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTargetId()">
|
||||||
{{node.item.name}}
|
{{node.item.name}}
|
||||||
<icon-provider class="explorer-node__location" v-for="location in node.locations" :key="location.id" :provider-id="location.providerId"></icon-provider>
|
<icon-provider class="explorer-node__location" v-for="location in node.locations" :key="location.id" :provider-id="location.providerId"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
@ -73,23 +73,30 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
...mapMutations('explorer', [
|
...mapMutations('explorer', [
|
||||||
'setDragTargetId',
|
'setDragTargetId',
|
||||||
|
'setEditingId',
|
||||||
]),
|
]),
|
||||||
...mapActions('explorer', [
|
...mapActions('explorer', [
|
||||||
'setDragTarget',
|
'setDragTarget',
|
||||||
|
'newItem',
|
||||||
|
'deleteItem',
|
||||||
]),
|
]),
|
||||||
select(id) {
|
select(id = this.node.item.id, doOpen = true) {
|
||||||
const node = this.$store.getters['explorer/nodeMap'][id];
|
const node = this.$store.getters['explorer/nodeMap'][id];
|
||||||
if (node) {
|
if (!node) {
|
||||||
this.$store.commit('explorer/setSelectedId', id);
|
return false;
|
||||||
if (node.isFolder) {
|
|
||||||
this.$store.commit('explorer/toggleOpenNode', id);
|
|
||||||
} else {
|
|
||||||
// Prevent from freezing the UI while loading the file
|
|
||||||
setTimeout(() => {
|
|
||||||
this.$store.commit('file/setCurrentId', id);
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.$store.commit('explorer/setSelectedId', id);
|
||||||
|
if (doOpen) {
|
||||||
|
// Prevent from freezing the UI while loading the file
|
||||||
|
setTimeout(() => {
|
||||||
|
if (node.isFolder) {
|
||||||
|
this.$store.commit('explorer/toggleOpenNode', id);
|
||||||
|
} else {
|
||||||
|
this.$store.commit('file/setCurrentId', id);
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
submitNewChild(cancel) {
|
submitNewChild(cancel) {
|
||||||
const newChildNode = this.$store.state.explorer.newChildNode;
|
const newChildNode = this.$store.state.explorer.newChildNode;
|
||||||
@ -119,7 +126,7 @@ export default {
|
|||||||
name: utils.sanitizeName(value),
|
name: utils.sanitizeName(value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.$store.commit('explorer/setEditingId', null);
|
this.setEditingId(null);
|
||||||
},
|
},
|
||||||
setDragSourceId(evt) {
|
setDragSourceId(evt) {
|
||||||
if (this.node.noDrag) {
|
if (this.node.noDrag) {
|
||||||
@ -147,6 +154,38 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onContextMenu(evt) {
|
||||||
|
if (this.select(undefined, false)) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
this.$store.dispatch('contextMenu/open', {
|
||||||
|
coordinates: {
|
||||||
|
left: evt.clientX,
|
||||||
|
top: evt.clientY,
|
||||||
|
},
|
||||||
|
items: [{
|
||||||
|
name: 'New file',
|
||||||
|
disabled: !this.node.isFolder || this.node.isTrash,
|
||||||
|
perform: () => this.newItem(false),
|
||||||
|
}, {
|
||||||
|
name: 'New folder',
|
||||||
|
disabled: !this.node.isFolder || this.node.isTrash,
|
||||||
|
perform: () => this.newItem(true),
|
||||||
|
}, {
|
||||||
|
type: 'separator',
|
||||||
|
}, {
|
||||||
|
name: 'Rename',
|
||||||
|
disabled: this.node.isTrash,
|
||||||
|
perform: () => this.setEditingId(this.node.item.id),
|
||||||
|
}, {
|
||||||
|
name: 'Delete',
|
||||||
|
disabled: this.node.isTrash || this.node.item.parentId === 'trash',
|
||||||
|
perform: () => this.deleteItem(),
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
.then(item => item.perform());
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -159,6 +198,7 @@ $item-font-size: 14px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.explorer-node__item {
|
.explorer-node__item {
|
||||||
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: $item-font-size;
|
font-size: $item-font-size;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -226,6 +226,7 @@ img {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
display: none;
|
display: none;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
|
height: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
5
src/icons/Magnify.vue
Normal file
5
src/icons/Magnify.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
|
||||||
|
<path d="M 9.5,3C 13.0899,3 16,5.91015 16,9.5C 16,11.1149 15.411,12.5923 14.4362,13.7291L 14.7071,14L 15.5,14L 20.5,19L 19,20.5L 14,15.5L 14,14.7071L 13.7291,14.4362C 12.5923,15.411 11.1149,16 9.5,16C 5.91015,16 3,13.0899 3,9.5C 3,5.91015 5.91015,3 9.5,3 Z M 9.5,5.00001C 7.01472,5.00001 5,7.01473 5,9.50001C 5,11.9853 7.01472,14 9.5,14C 11.9853,14 14,11.9853 14,9.50001C 14,7.01473 11.9853,5.00001 9.5,5.00001 Z "/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
@ -48,6 +48,7 @@ import ContentSave from './ContentSave';
|
|||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
import History from './History';
|
import History from './History';
|
||||||
import Database from './Database';
|
import Database from './Database';
|
||||||
|
import Magnify from './Magnify';
|
||||||
|
|
||||||
Vue.component('iconProvider', Provider);
|
Vue.component('iconProvider', Provider);
|
||||||
Vue.component('iconFormatBold', FormatBold);
|
Vue.component('iconFormatBold', FormatBold);
|
||||||
@ -98,3 +99,4 @@ Vue.component('iconContentSave', ContentSave);
|
|||||||
Vue.component('iconMessage', Message);
|
Vue.component('iconMessage', Message);
|
||||||
Vue.component('iconHistory', History);
|
Vue.component('iconHistory', History);
|
||||||
Vue.component('iconDatabase', Database);
|
Vue.component('iconDatabase', Database);
|
||||||
|
Vue.component('iconMagnify', Magnify);
|
||||||
|
53
src/store/contextMenu.js
Normal file
53
src/store/contextMenu.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
const setter = propertyName => (state, value) => {
|
||||||
|
state[propertyName] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
coordinates: {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
},
|
||||||
|
items: [],
|
||||||
|
resolve: () => {},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setCoordinates: setter('coordinates'),
|
||||||
|
setItems: setter('items'),
|
||||||
|
setResolve: setter('resolve'),
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
open({ commit, rootState }, { coordinates, items }) {
|
||||||
|
commit('setItems', items);
|
||||||
|
// Place the context menu outside the screen
|
||||||
|
commit('setCoordinates', { top: 0, left: -9999 });
|
||||||
|
// Let the UI refresh itself
|
||||||
|
setTimeout(() => {
|
||||||
|
// Take the size of the context menu and place it
|
||||||
|
const elt = document.querySelector('.context-menu__inner');
|
||||||
|
const height = elt.offsetHeight;
|
||||||
|
if (coordinates.top + height > rootState.layout.bodyHeight) {
|
||||||
|
coordinates.top -= height;
|
||||||
|
}
|
||||||
|
if (coordinates.top < 0) {
|
||||||
|
coordinates.top = 0;
|
||||||
|
}
|
||||||
|
const width = elt.offsetWidth;
|
||||||
|
if (coordinates.left + width > rootState.layout.bodyWidth) {
|
||||||
|
coordinates.left -= width;
|
||||||
|
}
|
||||||
|
if (coordinates.left < 0) {
|
||||||
|
coordinates.left = 0;
|
||||||
|
}
|
||||||
|
commit('setCoordinates', coordinates);
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
return new Promise(resolve => commit('setResolve', resolve));
|
||||||
|
},
|
||||||
|
close({ commit }) {
|
||||||
|
commit('setItems', []);
|
||||||
|
commit('setResolve', () => {});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -72,6 +72,7 @@ export default {
|
|||||||
const trashFolderNode = new Node(emptyFolder(), [], true);
|
const trashFolderNode = new Node(emptyFolder(), [], true);
|
||||||
trashFolderNode.item.id = 'trash';
|
trashFolderNode.item.id = 'trash';
|
||||||
trashFolderNode.item.name = 'Trash';
|
trashFolderNode.item.name = 'Trash';
|
||||||
|
trashFolderNode.isTrash = true;
|
||||||
trashFolderNode.noDrag = true;
|
trashFolderNode.noDrag = true;
|
||||||
const nodeMap = {
|
const nodeMap = {
|
||||||
trash: trashFolderNode,
|
trash: trashFolderNode,
|
||||||
@ -79,18 +80,20 @@ export default {
|
|||||||
rootGetters['folder/items'].forEach((item) => {
|
rootGetters['folder/items'].forEach((item) => {
|
||||||
nodeMap[item.id] = new Node(item, [], true);
|
nodeMap[item.id] = new Node(item, [], true);
|
||||||
});
|
});
|
||||||
|
const syncLocationsByFileId = rootGetters['syncLocation/groupedByFileId'];
|
||||||
|
const publishLocationsByFileId = rootGetters['publishLocation/groupedByFileId'];
|
||||||
rootGetters['file/items'].forEach((item) => {
|
rootGetters['file/items'].forEach((item) => {
|
||||||
const locations = [
|
const locations = [
|
||||||
...rootGetters['syncLocation/groupedByFileId'][item.id] || [],
|
...syncLocationsByFileId[item.id] || [],
|
||||||
...rootGetters['publishLocation/groupedByFileId'][item.id] || [],
|
...publishLocationsByFileId[item.id] || [],
|
||||||
];
|
];
|
||||||
nodeMap[item.id] = new Node(item, locations);
|
nodeMap[item.id] = new Node(item, locations);
|
||||||
});
|
});
|
||||||
const rootNode = new Node(emptyFolder(), [], true, true);
|
const rootNode = new Node(emptyFolder(), [], true, true);
|
||||||
Object.entries(nodeMap).forEach(([id, node]) => {
|
Object.entries(nodeMap).forEach(([, node]) => {
|
||||||
let parentNode = nodeMap[node.item.parentId];
|
let parentNode = nodeMap[node.item.parentId];
|
||||||
if (!parentNode || !parentNode.isFolder) {
|
if (!parentNode || !parentNode.isFolder) {
|
||||||
if (id === 'trash') {
|
if (node.isTrash) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
parentNode = rootNode;
|
parentNode = rootNode;
|
||||||
@ -105,7 +108,7 @@ export default {
|
|||||||
if (trashFolderNode.files.length) {
|
if (trashFolderNode.files.length) {
|
||||||
rootNode.folders.unshift(trashFolderNode);
|
rootNode.folders.unshift(trashFolderNode);
|
||||||
}
|
}
|
||||||
// Add a fake file at the end of the root folder to always allow drag and drop into it.
|
// Add a fake file at the end of the root folder to allow drag and drop into it.
|
||||||
rootNode.files.push(fakeFileNode);
|
rootNode.files.push(fakeFileNode);
|
||||||
return {
|
return {
|
||||||
nodeMap,
|
nodeMap,
|
||||||
@ -164,5 +167,67 @@ export default {
|
|||||||
commit('setDragTargetId', id);
|
commit('setDragTargetId', id);
|
||||||
dispatch('openDragTarget');
|
dispatch('openDragTarget');
|
||||||
},
|
},
|
||||||
|
newItem({ getters, commit, dispatch }, isFolder) {
|
||||||
|
let parentId = getters.selectedNodeFolder.item.id;
|
||||||
|
if (parentId === 'trash') {
|
||||||
|
parentId = null;
|
||||||
|
}
|
||||||
|
dispatch('openNode', parentId);
|
||||||
|
commit('setNewItem', {
|
||||||
|
type: isFolder ? 'folder' : 'file',
|
||||||
|
parentId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteItem({ rootState, getters, rootGetters, commit, dispatch }) {
|
||||||
|
const selectedNode = getters.selectedNode;
|
||||||
|
if (selectedNode.isNil) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
if (selectedNode.isTrash || selectedNode.item.parentId === 'trash') {
|
||||||
|
return dispatch('modal/trashDeletion', null, { root: true });
|
||||||
|
}
|
||||||
|
return dispatch(selectedNode.isFolder
|
||||||
|
? 'modal/folderDeletion'
|
||||||
|
: 'modal/fileDeletion',
|
||||||
|
selectedNode.item,
|
||||||
|
{ root: true },
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
if (selectedNode === getters.selectedNode) {
|
||||||
|
const currentFileId = rootGetters['file/current'].id;
|
||||||
|
let doClose = selectedNode.item.id === currentFileId;
|
||||||
|
if (selectedNode.isFolder) {
|
||||||
|
const recursiveMoveToTrash = (folderNode) => {
|
||||||
|
folderNode.folders.forEach(recursiveMoveToTrash);
|
||||||
|
folderNode.files.forEach((fileNode) => {
|
||||||
|
commit('file/patchItem', {
|
||||||
|
id: fileNode.item.id,
|
||||||
|
parentId: 'trash',
|
||||||
|
}, { root: true });
|
||||||
|
doClose = doClose || fileNode.item.id === currentFileId;
|
||||||
|
});
|
||||||
|
commit('folder/deleteItem', folderNode.item.id, { root: true });
|
||||||
|
};
|
||||||
|
recursiveMoveToTrash(selectedNode);
|
||||||
|
} else {
|
||||||
|
commit('file/patchItem', {
|
||||||
|
id: selectedNode.item.id,
|
||||||
|
parentId: 'trash',
|
||||||
|
}, { root: true });
|
||||||
|
}
|
||||||
|
if (doClose) {
|
||||||
|
// Close the current file by opening the last opened, not deleted one
|
||||||
|
rootGetters['data/lastOpenedIds'].some((id) => {
|
||||||
|
const file = rootState.file.itemMap[id];
|
||||||
|
if (file.parentId === 'trash') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
commit('file/setCurrentId', id, { root: true });
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, () => {}); // Cancel
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -2,21 +2,22 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import utils from '../services/utils';
|
import utils from '../services/utils';
|
||||||
import contentState from './contentState';
|
|
||||||
import syncedContent from './syncedContent';
|
|
||||||
import content from './content';
|
import content from './content';
|
||||||
|
import contentState from './contentState';
|
||||||
|
import contextMenu from './contextMenu';
|
||||||
|
import data from './data';
|
||||||
|
import discussion from './discussion';
|
||||||
|
import explorer from './explorer';
|
||||||
import file from './file';
|
import file from './file';
|
||||||
import findReplace from './findReplace';
|
import findReplace from './findReplace';
|
||||||
import folder from './folder';
|
import folder from './folder';
|
||||||
import publishLocation from './publishLocation';
|
|
||||||
import syncLocation from './syncLocation';
|
|
||||||
import data from './data';
|
|
||||||
import discussion from './discussion';
|
|
||||||
import layout from './layout';
|
import layout from './layout';
|
||||||
import explorer from './explorer';
|
|
||||||
import modal from './modal';
|
import modal from './modal';
|
||||||
import notification from './notification';
|
import notification from './notification';
|
||||||
|
import publishLocation from './publishLocation';
|
||||||
import queue from './queue';
|
import queue from './queue';
|
||||||
|
import syncedContent from './syncedContent';
|
||||||
|
import syncLocation from './syncLocation';
|
||||||
import userInfo from './userInfo';
|
import userInfo from './userInfo';
|
||||||
import workspace from './workspace';
|
import workspace from './workspace';
|
||||||
|
|
||||||
@ -26,21 +27,22 @@ const debug = NODE_ENV !== 'production';
|
|||||||
|
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
contentState,
|
|
||||||
syncedContent,
|
|
||||||
content,
|
content,
|
||||||
|
contentState,
|
||||||
|
contextMenu,
|
||||||
|
data,
|
||||||
discussion,
|
discussion,
|
||||||
|
explorer,
|
||||||
file,
|
file,
|
||||||
findReplace,
|
findReplace,
|
||||||
folder,
|
folder,
|
||||||
publishLocation,
|
|
||||||
syncLocation,
|
|
||||||
data,
|
|
||||||
layout,
|
layout,
|
||||||
explorer,
|
|
||||||
modal,
|
modal,
|
||||||
notification,
|
notification,
|
||||||
|
publishLocation,
|
||||||
queue,
|
queue,
|
||||||
|
syncedContent,
|
||||||
|
syncLocation,
|
||||||
userInfo,
|
userInfo,
|
||||||
workspace,
|
workspace,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user