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"
|
:data-dir="isDir"
|
||||||
:aria-label="name"
|
:aria-label="name"
|
||||||
:aria-selected="isSelected"
|
:aria-selected="isSelected"
|
||||||
|
ref="item"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
|
@ -54,6 +55,7 @@ import filesize from "filesize";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
|
import { eventPosition } from "@/utils/event";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "item",
|
name: "item",
|
||||||
|
@ -117,8 +119,20 @@ export default {
|
||||||
return enableThumbs;
|
return enableThumbs;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$refs.item.addEventListener("contextmenu", this.contextMenu);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$refs.item.removeEventListener("contextmenu", this.contextMenu);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
|
...mapMutations([
|
||||||
|
"addSelected",
|
||||||
|
"removeSelected",
|
||||||
|
"resetSelected",
|
||||||
|
"showContextMenu",
|
||||||
|
"hideContextMenu",
|
||||||
|
]),
|
||||||
permissions() {
|
permissions() {
|
||||||
let s = "";
|
let s = "";
|
||||||
if (this.isSymlink) {
|
if (this.isSymlink) {
|
||||||
|
@ -283,6 +297,17 @@ export default {
|
||||||
open: function () {
|
open: function () {
|
||||||
this.$router.push({ path: this.url });
|
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>
|
</script>
|
||||||
|
|
|
@ -287,3 +287,23 @@
|
||||||
left: .9em;
|
left: .9em;
|
||||||
bottom: .4em;
|
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,
|
isLogged: (state) => state.user !== null,
|
||||||
isFiles: (state) => !state.loading && state.route.name === "Files",
|
isFiles: (state) => !state.loading && state.route.name === "Files",
|
||||||
isListing: (state, getters) => getters.isFiles && state.req.isDir,
|
isListing: (state, getters) => getters.isFiles && state.req.isDir,
|
||||||
|
isVisibleContext: (state, getters) =>
|
||||||
|
getters.isListing && state.contextMenu !== null,
|
||||||
selectedCount: (state) => state.selected.length,
|
selectedCount: (state) => state.selected.length,
|
||||||
progress: (state) => {
|
progress: (state) => {
|
||||||
if (state.upload.progress.length == 0) {
|
if (state.upload.progress.length == 0) {
|
||||||
|
@ -11,6 +13,20 @@ const getters = {
|
||||||
let sum = state.upload.progress.reduce((acc, val) => acc + val);
|
let sum = state.upload.progress.reduce((acc, val) => acc + val);
|
||||||
return Math.ceil((sum / state.upload.size) * 100);
|
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;
|
export default getters;
|
||||||
|
|
|
@ -23,6 +23,7 @@ const state = {
|
||||||
show: null,
|
show: null,
|
||||||
showShell: false,
|
showShell: false,
|
||||||
showConfirm: null,
|
showConfirm: null,
|
||||||
|
contextMenu: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
|
|
|
@ -82,6 +82,15 @@ const mutations = {
|
||||||
state.clipboard.key = "";
|
state.clipboard.key = "";
|
||||||
state.clipboard.items = [];
|
state.clipboard.items = [];
|
||||||
},
|
},
|
||||||
|
showContextMenu: (state, value) => {
|
||||||
|
state.contextMenu = {
|
||||||
|
x: value.x,
|
||||||
|
y: value.y,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
hideContextMenu: (state) => {
|
||||||
|
state.contextMenu = null;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default mutations;
|
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" />
|
<shell v-if="isExecEnabled && isLogged && user.perm.execute" />
|
||||||
</main>
|
</main>
|
||||||
<prompts></prompts>
|
<prompts></prompts>
|
||||||
|
<context-menu v-if="isVisibleContext"></context-menu>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
import { mapState, mapGetters } from "vuex";
|
import { mapState, mapGetters } from "vuex";
|
||||||
import Sidebar from "@/components/Sidebar";
|
import Sidebar from "@/components/Sidebar";
|
||||||
import Prompts from "@/components/prompts/Prompts";
|
import Prompts from "@/components/prompts/Prompts";
|
||||||
|
import ContextMenu from "@/components/files/ContextMenu";
|
||||||
import Shell from "@/components/Shell";
|
import Shell from "@/components/Shell";
|
||||||
import { enableExec } from "@/utils/constants";
|
import { enableExec } from "@/utils/constants";
|
||||||
|
|
||||||
|
@ -24,15 +26,17 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
Prompts,
|
Prompts,
|
||||||
|
ContextMenu,
|
||||||
Shell,
|
Shell,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["isLogged", "progress"]),
|
...mapGetters(["isLogged", "isVisibleContext", "progress"]),
|
||||||
...mapState(["user"]),
|
...mapState(["user"]),
|
||||||
isExecEnabled: () => enableExec,
|
isExecEnabled: () => enableExec,
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route: function () {
|
$route: function () {
|
||||||
|
this.$store.commit("hideContextMenu");
|
||||||
this.$store.commit("resetSelected");
|
this.$store.commit("resetSelected");
|
||||||
this.$store.commit("multiple", false);
|
this.$store.commit("multiple", false);
|
||||||
if (this.$store.state.show !== "success")
|
if (this.$store.state.show !== "success")
|
||||||
|
|
|
@ -325,7 +325,7 @@ export default {
|
||||||
"selected",
|
"selected",
|
||||||
"loading",
|
"loading",
|
||||||
]),
|
]),
|
||||||
...mapGetters(["selectedCount"]),
|
...mapGetters(["selectedCount", "onlyArchivesSelected"]),
|
||||||
nameSorted() {
|
nameSorted() {
|
||||||
return this.req.sorting.by === "name";
|
return this.req.sorting.by === "name";
|
||||||
},
|
},
|
||||||
|
@ -401,22 +401,6 @@ export default {
|
||||||
isMobile() {
|
isMobile() {
|
||||||
return this.width <= 736;
|
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: {
|
watch: {
|
||||||
req: function () {
|
req: function () {
|
||||||
|
@ -481,6 +465,7 @@ export default {
|
||||||
if (event.keyCode === 27) {
|
if (event.keyCode === 27) {
|
||||||
// Reset files selection.
|
// Reset files selection.
|
||||||
this.$store.commit("resetSelected");
|
this.$store.commit("resetSelected");
|
||||||
|
this.$store.commit("hideContextMenu");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Del!
|
// Del!
|
||||||
|
|
Loading…
Reference in New Issue