feat: listing context menu (#9)
* feat: listing context menu * fix: extract archive check methodpull/3756/head
parent
cfb4f8662e
commit
cff060225e
|
@ -0,0 +1,160 @@
|
|||
<template>
|
||||
<div
|
||||
id="context-menu"
|
||||
ref="contextMenu"
|
||||
class="card"
|
||||
:style="menuPosition"
|
||||
@click="close"
|
||||
>
|
||||
<p>
|
||||
<action icon="info" :label="$t('buttons.info')" show="info" />
|
||||
</p>
|
||||
<p v-if="options.share">
|
||||
<action icon="share" :label="$t('buttons.share')" show="share" />
|
||||
</p>
|
||||
<p v-if="options.rename">
|
||||
<action icon="mode_edit" :label="$t('buttons.rename')" show="rename" />
|
||||
</p>
|
||||
<p v-if="options.copy">
|
||||
<action
|
||||
id="copy-button"
|
||||
icon="content_copy"
|
||||
:label="$t('buttons.copyFile')"
|
||||
show="copy"
|
||||
/>
|
||||
</p>
|
||||
<p v-if="options.move">
|
||||
<action
|
||||
id="move-button"
|
||||
icon="forward"
|
||||
:label="$t('buttons.moveFile')"
|
||||
show="move"
|
||||
/>
|
||||
</p>
|
||||
<p v-if="options.permissions">
|
||||
<action
|
||||
id="permissions-button"
|
||||
icon="lock"
|
||||
:label="$t('buttons.permissions')"
|
||||
show="permissions"
|
||||
/>
|
||||
</p>
|
||||
<p v-if="options.archive">
|
||||
<action
|
||||
id="archive-button"
|
||||
icon="archive"
|
||||
:label="$t('buttons.archive')"
|
||||
show="archive"
|
||||
/>
|
||||
</p>
|
||||
<p v-if="options.unarchive">
|
||||
<action
|
||||
id="unarchive-button"
|
||||
icon="unarchive"
|
||||
:label="$t('buttons.unarchive')"
|
||||
show="unarchive"
|
||||
/>
|
||||
</p>
|
||||
<p v-if="options.download">
|
||||
<action
|
||||
icon="file_download"
|
||||
:label="$t('buttons.download')"
|
||||
@action="download"
|
||||
:counter="selectedCount"
|
||||
/>
|
||||
</p>
|
||||
<p v-if="options.delete">
|
||||
<action
|
||||
id="delete-button"
|
||||
icon="delete"
|
||||
:label="$t('buttons.delete')"
|
||||
show="delete"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import { files as api } from "@/api";
|
||||
import Action from "@/components/header/Action";
|
||||
|
||||
export default {
|
||||
name: "context-menu",
|
||||
components: { Action },
|
||||
computed: {
|
||||
...mapState(["req", "selected", "user", "selected", "contextMenu"]),
|
||||
...mapGetters(["selectedCount", "onlyArchivesSelected"]),
|
||||
menuPosition() {
|
||||
if (this.contextMenu === null) {
|
||||
return { left: "0px", right: "0px" };
|
||||
}
|
||||
|
||||
let style = {
|
||||
left: this.contextMenu.x + "px",
|
||||
top: this.contextMenu.y + "px",
|
||||
};
|
||||
|
||||
if (window.innerWidth - this.contextMenu.x < 150) {
|
||||
style.transform = "translateX(calc(-100% - 3px))";
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
options() {
|
||||
return {
|
||||
download: this.user.perm.download,
|
||||
delete: this.selectedCount > 0 && this.user.perm.delete,
|
||||
rename: this.selectedCount === 1 && this.user.perm.rename,
|
||||
share: this.selectedCount === 1 && this.user.perm.share,
|
||||
move: this.selectedCount > 0 && this.user.perm.rename,
|
||||
copy: this.selectedCount > 0 && this.user.perm.create,
|
||||
permissions: this.selectedCount === 1 && this.user.perm.modify,
|
||||
archive: this.selectedCount > 0 && this.user.perm.create,
|
||||
unarchive: this.selectedCount === 1 && this.onlyArchivesSelected,
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("mousedown", this.windowClick);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("mousedown", this.windowClick);
|
||||
},
|
||||
methods: {
|
||||
windowClick(event) {
|
||||
if (!this.$refs.contextMenu.contains(event.target)) {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$store.commit("hideContextMenu");
|
||||
},
|
||||
download() {
|
||||
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
||||
api.download(null, this.req.items[this.selected[0]].url);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "download",
|
||||
confirm: (format) => {
|
||||
this.$store.commit("closeHovers");
|
||||
|
||||
let files = [];
|
||||
|
||||
if (this.selectedCount > 0) {
|
||||
for (let i of this.selected) {
|
||||
files.push(this.req.items[i].url);
|
||||
}
|
||||
} else {
|
||||
files.push(this.$route.path);
|
||||
}
|
||||
|
||||
api.download(format, ...files);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -11,6 +11,7 @@
|
|||
:data-dir="isDir"
|
||||
:aria-label="name"
|
||||
:aria-selected="isSelected"
|
||||
ref="item"
|
||||
>
|
||||
<div>
|
||||
<img
|
||||
|
@ -54,6 +55,7 @@ import filesize from "filesize";
|
|||
import moment from "moment";
|
||||
import { files as api } from "@/api";
|
||||
import * as upload from "@/utils/upload";
|
||||
import { eventPosition } from "@/utils/event";
|
||||
|
||||
export default {
|
||||
name: "item",
|
||||
|
@ -117,8 +119,20 @@ export default {
|
|||
return enableThumbs;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.item.addEventListener("contextmenu", this.contextMenu);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.item.removeEventListener("contextmenu", this.contextMenu);
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
|
||||
...mapMutations([
|
||||
"addSelected",
|
||||
"removeSelected",
|
||||
"resetSelected",
|
||||
"showContextMenu",
|
||||
"hideContextMenu",
|
||||
]),
|
||||
permissions() {
|
||||
let s = "";
|
||||
if (this.isSymlink) {
|
||||
|
@ -283,6 +297,17 @@ export default {
|
|||
open: function () {
|
||||
this.$router.push({ path: this.url });
|
||||
},
|
||||
contextMenu(event) {
|
||||
event.preventDefault();
|
||||
this.hideContextMenu();
|
||||
if (this.$store.state.selected.indexOf(this.index) === -1) {
|
||||
this.resetSelected();
|
||||
this.addSelected(this.index);
|
||||
}
|
||||
let pos = eventPosition(event);
|
||||
pos.x += 2;
|
||||
this.showContextMenu(pos);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -286,4 +286,24 @@
|
|||
font-size: 1em;
|
||||
left: .9em;
|
||||
bottom: .4em;
|
||||
}
|
||||
|
||||
#context-menu {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#context-menu >p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#context-menu .action {
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
min-width: max-content;
|
||||
padding-right: .8em;
|
||||
}
|
||||
|
||||
#context-menu .action > * {
|
||||
vertical-align: middle;
|
||||
}
|
|
@ -2,6 +2,8 @@ const getters = {
|
|||
isLogged: (state) => state.user !== null,
|
||||
isFiles: (state) => !state.loading && state.route.name === "Files",
|
||||
isListing: (state, getters) => getters.isFiles && state.req.isDir,
|
||||
isVisibleContext: (state, getters) =>
|
||||
getters.isListing && state.contextMenu !== null,
|
||||
selectedCount: (state) => state.selected.length,
|
||||
progress: (state) => {
|
||||
if (state.upload.progress.length == 0) {
|
||||
|
@ -11,6 +13,20 @@ const getters = {
|
|||
let sum = state.upload.progress.reduce((acc, val) => acc + val);
|
||||
return Math.ceil((sum / state.upload.size) * 100);
|
||||
},
|
||||
onlyArchivesSelected: (state, getters) => {
|
||||
let extensions = [".zip", ".tar", ".gz", ".bz2", ".xz", ".lz4", ".sz"];
|
||||
let items = state.req.items;
|
||||
if (getters.selectedCount < 1) {
|
||||
return false;
|
||||
}
|
||||
for (const i of state.selected) {
|
||||
let item = items[i];
|
||||
if (item.isDir || !extensions.includes(item.extension)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
export default getters;
|
||||
|
|
|
@ -23,6 +23,7 @@ const state = {
|
|||
show: null,
|
||||
showShell: false,
|
||||
showConfirm: null,
|
||||
contextMenu: null,
|
||||
};
|
||||
|
||||
export default new Vuex.Store({
|
||||
|
|
|
@ -82,6 +82,15 @@ const mutations = {
|
|||
state.clipboard.key = "";
|
||||
state.clipboard.items = [];
|
||||
},
|
||||
showContextMenu: (state, value) => {
|
||||
state.contextMenu = {
|
||||
x: value.x,
|
||||
y: value.y,
|
||||
};
|
||||
},
|
||||
hideContextMenu: (state) => {
|
||||
state.contextMenu = null;
|
||||
},
|
||||
};
|
||||
|
||||
export default mutations;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
function eventPosition(event) {
|
||||
let posx = 0;
|
||||
let posy = 0;
|
||||
|
||||
if (event.pageX || event.pageY) {
|
||||
posx = event.pageX;
|
||||
posy = event.pageY;
|
||||
} else if (event.clientX || event.clientY) {
|
||||
posx =
|
||||
event.clientX +
|
||||
document.body.scrollLeft +
|
||||
document.documentElement.scrollLeft;
|
||||
posy =
|
||||
event.clientY +
|
||||
document.body.scrollTop +
|
||||
document.documentElement.scrollTop;
|
||||
}
|
||||
|
||||
return {
|
||||
x: posx,
|
||||
y: posy,
|
||||
};
|
||||
}
|
||||
|
||||
export { eventPosition };
|
|
@ -9,6 +9,7 @@
|
|||
<shell v-if="isExecEnabled && isLogged && user.perm.execute" />
|
||||
</main>
|
||||
<prompts></prompts>
|
||||
<context-menu v-if="isVisibleContext"></context-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -16,6 +17,7 @@
|
|||
import { mapState, mapGetters } from "vuex";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import Prompts from "@/components/prompts/Prompts";
|
||||
import ContextMenu from "@/components/files/ContextMenu";
|
||||
import Shell from "@/components/Shell";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
|
||||
|
@ -24,15 +26,17 @@ export default {
|
|||
components: {
|
||||
Sidebar,
|
||||
Prompts,
|
||||
ContextMenu,
|
||||
Shell,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["isLogged", "progress"]),
|
||||
...mapGetters(["isLogged", "isVisibleContext", "progress"]),
|
||||
...mapState(["user"]),
|
||||
isExecEnabled: () => enableExec,
|
||||
},
|
||||
watch: {
|
||||
$route: function () {
|
||||
this.$store.commit("hideContextMenu");
|
||||
this.$store.commit("resetSelected");
|
||||
this.$store.commit("multiple", false);
|
||||
if (this.$store.state.show !== "success")
|
||||
|
|
|
@ -325,7 +325,7 @@ export default {
|
|||
"selected",
|
||||
"loading",
|
||||
]),
|
||||
...mapGetters(["selectedCount"]),
|
||||
...mapGetters(["selectedCount", "onlyArchivesSelected"]),
|
||||
nameSorted() {
|
||||
return this.req.sorting.by === "name";
|
||||
},
|
||||
|
@ -401,22 +401,6 @@ export default {
|
|||
isMobile() {
|
||||
return this.width <= 736;
|
||||
},
|
||||
onlyArchivesSelected() {
|
||||
let extensions = [".zip", ".tar", ".gz", ".bz2", ".xz", ".lz4", ".sz"];
|
||||
|
||||
if (this.selectedCount < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const i of this.selected) {
|
||||
let item = this.req.items[i];
|
||||
if (item.isDir || !extensions.includes(item.extension)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
req: function () {
|
||||
|
@ -481,6 +465,7 @@ export default {
|
|||
if (event.keyCode === 27) {
|
||||
// Reset files selection.
|
||||
this.$store.commit("resetSelected");
|
||||
this.$store.commit("hideContextMenu");
|
||||
}
|
||||
|
||||
// Del!
|
||||
|
|
Loading…
Reference in New Issue