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!