2017-07-31 09:04:01 +00:00
|
|
|
<template>
|
2018-08-19 13:04:43 +00:00
|
|
|
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--folder': node.isFolder, 'explorer-node--open': isOpen, 'explorer-node--trash': node.isTrash, 'explorer-node--temp': node.isTemp, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="node.noDrop || setDragTarget(node)" @dragleave.stop="isDragTarget && setDragTarget()" @drop.prevent.stop="onDrop" @contextmenu="onContextMenu">
|
2018-06-21 19:16:33 +00:00
|
|
|
<div class="explorer-node__item-editor" v-if="isEditing" :style="{paddingLeft: leftPadding}" draggable="true" @dragstart.stop.prevent>
|
2018-03-18 15:48:29 +00:00
|
|
|
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keydown.stop @keydown.enter="submitEdit()" @keydown.esc="submitEdit(true)" v-model="editingNodeName">
|
2017-07-31 09:04:01 +00:00
|
|
|
</div>
|
2018-08-19 13:04:43 +00:00
|
|
|
<div class="explorer-node__item" v-else :style="{paddingLeft: leftPadding}" @click="select()" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTarget()">
|
2017-07-31 09:04:01 +00:00
|
|
|
{{node.item.name}}
|
2017-09-23 19:01:50 +00:00
|
|
|
<icon-provider class="explorer-node__location" v-for="location in node.locations" :key="location.id" :provider-id="location.providerId"></icon-provider>
|
2017-07-31 09:04:01 +00:00
|
|
|
</div>
|
2017-08-03 17:08:12 +00:00
|
|
|
<div class="explorer-node__children" v-if="node.isFolder && isOpen">
|
2017-07-31 09:04:01 +00:00
|
|
|
<explorer-node v-for="node in node.folders" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
2018-06-21 19:16:33 +00:00
|
|
|
<div v-if="newChild" class="explorer-node__new-child" :class="{'explorer-node__new-child--folder': newChild.isFolder}" :style="{paddingLeft: childLeftPadding}">
|
2018-03-18 15:48:29 +00:00
|
|
|
<input type="text" class="text-input" v-focus @blur="submitNewChild()" @keydown.stop @keydown.enter="submitNewChild()" @keydown.esc="submitNewChild(true)" v-model.trim="newChildName">
|
2017-07-31 09:04:01 +00:00
|
|
|
</div>
|
|
|
|
<explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
2017-08-03 17:08:12 +00:00
|
|
|
import { mapMutations, mapActions } from 'vuex';
|
2018-07-03 23:41:24 +00:00
|
|
|
import workspaceSvc from '../services/workspaceSvc';
|
2018-05-04 18:07:28 +00:00
|
|
|
import explorerSvc from '../services/explorerSvc';
|
2018-09-19 08:59:22 +00:00
|
|
|
import store from '../store';
|
2017-07-31 09:04:01 +00:00
|
|
|
|
|
|
|
export default {
|
2017-11-04 16:59:48 +00:00
|
|
|
name: 'explorer-node', // Required for recursivity
|
2017-07-31 09:04:01 +00:00
|
|
|
props: ['node', 'depth'],
|
|
|
|
data: () => ({
|
|
|
|
editingValue: '',
|
|
|
|
}),
|
|
|
|
computed: {
|
|
|
|
leftPadding() {
|
|
|
|
return `${this.depth * 15}px`;
|
|
|
|
},
|
|
|
|
childLeftPadding() {
|
|
|
|
return `${(this.depth + 1) * 15}px`;
|
|
|
|
},
|
2017-08-03 17:08:12 +00:00
|
|
|
isSelected() {
|
2018-09-19 08:59:22 +00:00
|
|
|
return store.getters['explorer/selectedNode'] === this.node;
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
2017-08-03 17:08:12 +00:00
|
|
|
isEditing() {
|
2018-09-19 08:59:22 +00:00
|
|
|
return store.getters['explorer/editingNode'] === this.node;
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
2017-08-03 17:08:12 +00:00
|
|
|
isDragTarget() {
|
2018-09-19 08:59:22 +00:00
|
|
|
return store.getters['explorer/dragTargetNode'] === this.node;
|
2017-08-03 17:08:12 +00:00
|
|
|
},
|
|
|
|
isDragTargetFolder() {
|
2018-09-19 08:59:22 +00:00
|
|
|
return store.getters['explorer/dragTargetNodeFolder'] === this.node;
|
2017-08-03 17:08:12 +00:00
|
|
|
},
|
|
|
|
isOpen() {
|
2018-09-19 08:59:22 +00:00
|
|
|
return store.state.explorer.openNodes[this.node.item.id] || this.node.isRoot;
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
|
|
|
newChild() {
|
2018-09-19 08:59:22 +00:00
|
|
|
return store.getters['explorer/newChildNodeParent'] === this.node
|
|
|
|
&& store.state.explorer.newChildNode;
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
|
|
|
newChildName: {
|
|
|
|
get() {
|
2018-09-19 08:59:22 +00:00
|
|
|
return store.state.explorer.newChildNode.item.name;
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
|
|
|
set(value) {
|
2018-09-19 08:59:22 +00:00
|
|
|
store.commit('explorer/setNewItemName', value);
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
editingNodeName: {
|
|
|
|
get() {
|
2018-09-19 08:59:22 +00:00
|
|
|
return store.getters['explorer/editingNode'].item.name;
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
|
|
|
set(value) {
|
|
|
|
this.editingValue = value.trim();
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
methods: {
|
2017-08-03 17:08:12 +00:00
|
|
|
...mapMutations('explorer', [
|
2018-03-09 21:18:20 +00:00
|
|
|
'setEditingId',
|
2017-08-03 17:08:12 +00:00
|
|
|
]),
|
|
|
|
...mapActions('explorer', [
|
|
|
|
'setDragTarget',
|
|
|
|
]),
|
2018-03-09 21:18:20 +00:00
|
|
|
select(id = this.node.item.id, doOpen = true) {
|
2018-09-19 08:59:22 +00:00
|
|
|
const node = store.getters['explorer/nodeMap'][id];
|
2018-03-09 21:18:20 +00:00
|
|
|
if (!node) {
|
|
|
|
return false;
|
|
|
|
}
|
2018-09-19 08:59:22 +00:00
|
|
|
store.commit('explorer/setSelectedId', id);
|
2018-03-09 21:18:20 +00:00
|
|
|
if (doOpen) {
|
|
|
|
// Prevent from freezing the UI while loading the file
|
|
|
|
setTimeout(() => {
|
|
|
|
if (node.isFolder) {
|
2018-09-19 08:59:22 +00:00
|
|
|
store.commit('explorer/toggleOpenNode', id);
|
2018-03-09 21:18:20 +00:00
|
|
|
} else {
|
2018-09-19 08:59:22 +00:00
|
|
|
store.commit('file/setCurrentId', id);
|
2018-03-09 21:18:20 +00:00
|
|
|
}
|
|
|
|
}, 10);
|
2017-07-31 09:04:01 +00:00
|
|
|
}
|
2018-03-09 21:18:20 +00:00
|
|
|
return true;
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
2018-05-13 13:27:33 +00:00
|
|
|
async submitNewChild(cancel) {
|
2018-09-19 08:59:22 +00:00
|
|
|
const { newChildNode } = store.state.explorer;
|
2017-08-03 17:08:12 +00:00
|
|
|
if (!cancel && !newChildNode.isNil && newChildNode.item.name) {
|
2018-05-13 13:27:33 +00:00
|
|
|
try {
|
|
|
|
if (newChildNode.isFolder) {
|
2018-07-03 23:41:24 +00:00
|
|
|
const item = await workspaceSvc.storeItem(newChildNode.item);
|
2018-05-13 13:27:33 +00:00
|
|
|
this.select(item.id);
|
|
|
|
} else {
|
2018-07-03 23:41:24 +00:00
|
|
|
const item = await workspaceSvc.createFile(newChildNode.item);
|
2018-05-13 13:27:33 +00:00
|
|
|
this.select(item.id);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// Cancel
|
2017-07-31 09:04:01 +00:00
|
|
|
}
|
|
|
|
}
|
2018-09-19 08:59:22 +00:00
|
|
|
store.commit('explorer/setNewItem', null);
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
2018-05-13 13:27:33 +00:00
|
|
|
async submitEdit(cancel) {
|
2018-09-19 08:59:22 +00:00
|
|
|
const { item } = store.getters['explorer/editingNode'];
|
2017-07-31 09:04:01 +00:00
|
|
|
const value = this.editingValue;
|
2018-03-09 21:18:20 +00:00
|
|
|
this.setEditingId(null);
|
2018-05-04 18:07:28 +00:00
|
|
|
if (!cancel && item.id && value) {
|
2018-05-13 13:27:33 +00:00
|
|
|
try {
|
2018-07-03 23:41:24 +00:00
|
|
|
await workspaceSvc.storeItem({
|
2018-05-13 13:27:33 +00:00
|
|
|
...item,
|
|
|
|
name: value,
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
// Cancel
|
|
|
|
}
|
2018-05-04 18:07:28 +00:00
|
|
|
}
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
2017-08-03 17:08:12 +00:00
|
|
|
setDragSourceId(evt) {
|
2017-10-05 07:16:35 +00:00
|
|
|
if (this.node.noDrag) {
|
2017-08-03 17:08:12 +00:00
|
|
|
evt.preventDefault();
|
|
|
|
return;
|
|
|
|
}
|
2018-09-19 08:59:22 +00:00
|
|
|
store.commit('explorer/setDragSourceId', this.node.item.id);
|
2018-03-24 17:03:35 +00:00
|
|
|
// Fix for Firefox
|
|
|
|
// See https://stackoverflow.com/a/3977637/1333165
|
2018-03-25 00:48:18 +00:00
|
|
|
evt.dataTransfer.setData('Text', '');
|
2017-08-03 17:08:12 +00:00
|
|
|
},
|
|
|
|
onDrop() {
|
2018-09-19 08:59:22 +00:00
|
|
|
const sourceNode = store.getters['explorer/dragSourceNode'];
|
|
|
|
const targetNode = store.getters['explorer/dragTargetNodeFolder'];
|
2018-08-19 13:04:43 +00:00
|
|
|
this.setDragTarget();
|
2017-08-03 17:08:12 +00:00
|
|
|
if (!sourceNode.isNil
|
|
|
|
&& !targetNode.isNil
|
|
|
|
&& sourceNode.item.id !== targetNode.item.id
|
|
|
|
) {
|
2018-07-03 23:41:24 +00:00
|
|
|
workspaceSvc.storeItem({
|
2018-05-13 13:27:33 +00:00
|
|
|
...sourceNode.item,
|
2017-08-03 17:08:12 +00:00
|
|
|
parentId: targetNode.item.id,
|
2018-05-13 13:27:33 +00:00
|
|
|
});
|
2017-08-03 17:08:12 +00:00
|
|
|
}
|
|
|
|
},
|
2018-05-13 13:27:33 +00:00
|
|
|
async onContextMenu(evt) {
|
2018-03-09 21:18:20 +00:00
|
|
|
if (this.select(undefined, false)) {
|
|
|
|
evt.preventDefault();
|
|
|
|
evt.stopPropagation();
|
2018-09-19 08:59:22 +00:00
|
|
|
const item = await store.dispatch('contextMenu/open', {
|
2018-03-09 21:18:20 +00:00
|
|
|
coordinates: {
|
|
|
|
left: evt.clientX,
|
|
|
|
top: evt.clientY,
|
|
|
|
},
|
|
|
|
items: [{
|
|
|
|
name: 'New file',
|
|
|
|
disabled: !this.node.isFolder || this.node.isTrash,
|
2018-05-04 18:07:28 +00:00
|
|
|
perform: () => explorerSvc.newItem(false),
|
2018-03-09 21:18:20 +00:00
|
|
|
}, {
|
|
|
|
name: 'New folder',
|
2018-03-14 00:42:26 +00:00
|
|
|
disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp,
|
2018-05-04 18:07:28 +00:00
|
|
|
perform: () => explorerSvc.newItem(true),
|
2018-03-09 21:18:20 +00:00
|
|
|
}, {
|
|
|
|
type: 'separator',
|
|
|
|
}, {
|
|
|
|
name: 'Rename',
|
2018-03-24 17:03:35 +00:00
|
|
|
disabled: this.node.isTrash || this.node.isTemp,
|
2018-03-09 21:18:20 +00:00
|
|
|
perform: () => this.setEditingId(this.node.item.id),
|
|
|
|
}, {
|
|
|
|
name: 'Delete',
|
2018-05-04 18:07:28 +00:00
|
|
|
perform: () => explorerSvc.deleteItem(),
|
2018-03-09 21:18:20 +00:00
|
|
|
}],
|
2018-05-13 13:27:33 +00:00
|
|
|
});
|
2018-06-07 23:56:11 +00:00
|
|
|
if (item) {
|
|
|
|
item.perform();
|
|
|
|
}
|
2018-03-09 21:18:20 +00:00
|
|
|
}
|
|
|
|
},
|
2017-07-31 09:04:01 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
$item-font-size: 14px;
|
|
|
|
|
2017-08-03 17:08:12 +00:00
|
|
|
.explorer-node--drag-target {
|
|
|
|
background-color: rgba(0, 128, 255, 0.2);
|
|
|
|
}
|
|
|
|
|
2017-07-31 09:04:01 +00:00
|
|
|
.explorer-node__item {
|
2018-03-09 21:18:20 +00:00
|
|
|
position: relative;
|
2017-07-31 09:04:01 +00:00
|
|
|
cursor: pointer;
|
|
|
|
font-size: $item-font-size;
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
2017-09-23 19:01:50 +00:00
|
|
|
padding-right: 5px;
|
2017-07-31 09:04:01 +00:00
|
|
|
|
|
|
|
.explorer-node--selected > & {
|
|
|
|
background-color: rgba(0, 0, 0, 0.2);
|
|
|
|
|
|
|
|
.explorer__tree:focus & {
|
|
|
|
background-color: #39f;
|
|
|
|
color: #fff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.explorer__tree--new-item & {
|
|
|
|
opacity: 0.33;
|
|
|
|
}
|
2017-09-23 19:01:50 +00:00
|
|
|
|
|
|
|
.explorer-node__location {
|
|
|
|
float: right;
|
|
|
|
width: 18px;
|
|
|
|
height: 18px;
|
|
|
|
margin: 2px 1px;
|
|
|
|
}
|
2017-07-31 09:04:01 +00:00
|
|
|
}
|
|
|
|
|
2018-06-21 19:16:33 +00:00
|
|
|
.explorer-node--trash,
|
|
|
|
.explorer-node--temp {
|
|
|
|
color: rgba(0, 0, 0, 0.5);
|
|
|
|
}
|
|
|
|
|
|
|
|
.explorer-node--folder > .explorer-node__item,
|
|
|
|
.explorer-node--folder > .explorer-node__item-editor,
|
2017-07-31 09:04:01 +00:00
|
|
|
.explorer-node__new-child--folder {
|
|
|
|
&::before {
|
2018-03-18 15:52:33 +00:00
|
|
|
content: '▹';
|
2017-07-31 09:04:01 +00:00
|
|
|
position: absolute;
|
|
|
|
margin-left: -13px;
|
2018-06-21 19:16:33 +00:00
|
|
|
}
|
|
|
|
}
|
2017-07-31 09:04:01 +00:00
|
|
|
|
2018-06-21 19:16:33 +00:00
|
|
|
.explorer-node--folder.explorer-node--open > .explorer-node__item,
|
|
|
|
.explorer-node--folder.explorer-node--open > .explorer-node__item-editor {
|
|
|
|
&::before {
|
|
|
|
content: '▾';
|
2017-07-31 09:04:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$new-child-height: 25px;
|
|
|
|
|
|
|
|
.explorer-node__item-editor,
|
|
|
|
.explorer-node__new-child {
|
|
|
|
padding: 1px 10px;
|
|
|
|
|
|
|
|
.text-input {
|
|
|
|
font-size: $item-font-size;
|
|
|
|
padding: 2px;
|
|
|
|
height: $new-child-height;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|