diff --git a/frontend/src/components/files/ContextMenu.vue b/frontend/src/components/files/ContextMenu.vue
new file mode 100644
index 00000000..22687d85
--- /dev/null
+++ b/frontend/src/components/files/ContextMenu.vue
@@ -0,0 +1,160 @@
+
+
+
+
+
diff --git a/frontend/src/components/files/ListingItem.vue b/frontend/src/components/files/ListingItem.vue
index 65aaac22..9b2f367e 100644
--- a/frontend/src/components/files/ListingItem.vue
+++ b/frontend/src/components/files/ListingItem.vue
@@ -11,6 +11,7 @@
:data-dir="isDir"
:aria-label="name"
:aria-selected="isSelected"
+ ref="item"
>
![]()
diff --git a/frontend/src/css/listing.css b/frontend/src/css/listing.css
index 910e4892..f7cc9211 100644
--- a/frontend/src/css/listing.css
+++ b/frontend/src/css/listing.css
@@ -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;
}
\ No newline at end of file
diff --git a/frontend/src/store/getters.js b/frontend/src/store/getters.js
index 8f1a8a4c..8a08e392 100644
--- a/frontend/src/store/getters.js
+++ b/frontend/src/store/getters.js
@@ -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;
diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js
index 1da37f64..fb8c0cb6 100644
--- a/frontend/src/store/index.js
+++ b/frontend/src/store/index.js
@@ -23,6 +23,7 @@ const state = {
show: null,
showShell: false,
showConfirm: null,
+ contextMenu: null,
};
export default new Vuex.Store({
diff --git a/frontend/src/store/mutations.js b/frontend/src/store/mutations.js
index b60973b7..3d252744 100644
--- a/frontend/src/store/mutations.js
+++ b/frontend/src/store/mutations.js
@@ -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;
diff --git a/frontend/src/utils/event.js b/frontend/src/utils/event.js
new file mode 100644
index 00000000..10227a31
--- /dev/null
+++ b/frontend/src/utils/event.js
@@ -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 };
diff --git a/frontend/src/views/Layout.vue b/frontend/src/views/Layout.vue
index e9f306dc..5afe58cf 100644
--- a/frontend/src/views/Layout.vue
+++ b/frontend/src/views/Layout.vue
@@ -9,6 +9,7 @@
+
@@ -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")
diff --git a/frontend/src/views/files/Listing.vue b/frontend/src/views/files/Listing.vue
index 8f25ccc0..1b8edd04 100644
--- a/frontend/src/views/files/Listing.vue
+++ b/frontend/src/views/files/Listing.vue
@@ -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!