feat: listing context menu (#9)

* feat: listing context menu

* fix: extract archive check method
pull/3756/head
Laurynas Gadliauskas 2021-06-07 11:38:20 +03:00 committed by GitHub
parent cfb4f8662e
commit cff060225e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 264 additions and 19 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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;

View File

@ -23,6 +23,7 @@ const state = {
show: null,
showShell: false,
showConfirm: null,
contextMenu: null,
};
export default new Vuex.Store({

View File

@ -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;

View File

@ -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 };

View File

@ -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")

View File

@ -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!