feat: added right-click context-menu
This commit is contained in:
parent
cfafefa35a
commit
924bac6667
@ -184,6 +184,12 @@ table th {
|
|||||||
border-top: 1px solid var(--divider);
|
border-top: 1px solid var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.context-menu {
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
border: 1px solid var(--divider);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
#editor-container {
|
#editor-container {
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
}
|
}
|
||||||
|
|||||||
48
frontend/src/components/ContextMenu.vue
Normal file
48
frontend/src/components/ContextMenu.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="context-menu"
|
||||||
|
ref="contextMenu"
|
||||||
|
v-show="show"
|
||||||
|
:style="{
|
||||||
|
top: `${top}px`,
|
||||||
|
left: `${left}px`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "context-menu",
|
||||||
|
props: ["show", "pos"],
|
||||||
|
computed: {
|
||||||
|
top() {
|
||||||
|
return Math.min(
|
||||||
|
this.pos.y,
|
||||||
|
window.innerHeight - this.$refs.contextMenu?.clientHeight ?? 0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
left() {
|
||||||
|
return Math.min(
|
||||||
|
this.pos.x,
|
||||||
|
window.innerWidth - this.$refs.contextMenu?.clientWidth ?? 0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hideContextMenu() {
|
||||||
|
this.$emit("hide");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show: function (val) {
|
||||||
|
if (val) {
|
||||||
|
document.addEventListener("click", this.hideContextMenu);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener("click", this.hideContextMenu);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -8,6 +8,7 @@
|
|||||||
@dragover="dragOver"
|
@dragover="dragOver"
|
||||||
@drop="drop"
|
@drop="drop"
|
||||||
@click="itemClick"
|
@click="itemClick"
|
||||||
|
@contextmenu="contextMenu"
|
||||||
:data-dir="isDir"
|
:data-dir="isDir"
|
||||||
:data-type="type"
|
:data-type="type"
|
||||||
:aria-label="name"
|
:aria-label="name"
|
||||||
@ -194,6 +195,25 @@ export default {
|
|||||||
if (this.singleClick && !this.$store.state.multiple) this.open();
|
if (this.singleClick && !this.$store.state.multiple) this.open();
|
||||||
else this.click(event);
|
else this.click(event);
|
||||||
},
|
},
|
||||||
|
contextMenu: function (event) {
|
||||||
|
const to = setTimeout(() => {
|
||||||
|
this.touches = 0;
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
this.touches++;
|
||||||
|
if (this.touches > 1) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
if (
|
||||||
|
this.selected.length < 2 ||
|
||||||
|
event.ctrlKey ||
|
||||||
|
this.$store.state.selected.indexOf(this.index) === -1
|
||||||
|
) {
|
||||||
|
this.touches--;
|
||||||
|
clearTimeout(to);
|
||||||
|
this.click(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
click: function (event) {
|
click: function (event) {
|
||||||
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault();
|
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault();
|
||||||
|
|
||||||
|
|||||||
16
frontend/src/css/context-menu.css
Normal file
16
frontend/src/css/context-menu.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.context-menu {
|
||||||
|
position: fixed;
|
||||||
|
background: #ffffff;
|
||||||
|
min-width: 180px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu .action {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@
|
|||||||
@import "./dashboard.css";
|
@import "./dashboard.css";
|
||||||
@import "./login.css";
|
@import "./login.css";
|
||||||
@import "./mobile.css";
|
@import "./mobile.css";
|
||||||
|
@import "./context-menu.css";
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
|
|||||||
@ -198,6 +198,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<span @contextmenu="showContextMenu">
|
||||||
<h2 v-if="req.numDirs > 0">{{ $t("files.folders") }}</h2>
|
<h2 v-if="req.numDirs > 0">{{ $t("files.folders") }}</h2>
|
||||||
<div v-if="req.numDirs > 0">
|
<div v-if="req.numDirs > 0">
|
||||||
<item
|
<item
|
||||||
@ -231,6 +232,54 @@
|
|||||||
>
|
>
|
||||||
</item>
|
</item>
|
||||||
</div>
|
</div>
|
||||||
|
<context-menu
|
||||||
|
:show="isContextMenuVisible"
|
||||||
|
:pos="contextMenuPos"
|
||||||
|
@hide="hideContextMenu"
|
||||||
|
>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.share"
|
||||||
|
icon="share"
|
||||||
|
:label="$t('buttons.share')"
|
||||||
|
show="share"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.rename"
|
||||||
|
icon="mode_edit"
|
||||||
|
:label="$t('buttons.rename')"
|
||||||
|
show="rename"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.copy"
|
||||||
|
id="copy-button"
|
||||||
|
icon="content_copy"
|
||||||
|
:label="$t('buttons.copyFile')"
|
||||||
|
show="copy"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.move"
|
||||||
|
id="move-button"
|
||||||
|
icon="forward"
|
||||||
|
:label="$t('buttons.moveFile')"
|
||||||
|
show="move"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.delete"
|
||||||
|
id="delete-button"
|
||||||
|
icon="delete"
|
||||||
|
:label="$t('buttons.delete')"
|
||||||
|
show="delete"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.download"
|
||||||
|
icon="file_download"
|
||||||
|
:label="$t('buttons.download')"
|
||||||
|
@action="download"
|
||||||
|
:counter="selectedCount"
|
||||||
|
/>
|
||||||
|
<action icon="info" :label="$t('buttons.info')" show="info" />
|
||||||
|
</context-menu>
|
||||||
|
</span>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
style="display: none"
|
style="display: none"
|
||||||
@ -278,6 +327,7 @@ import throttle from "lodash.throttle";
|
|||||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||||
import Action from "@/components/header/Action.vue";
|
import Action from "@/components/header/Action.vue";
|
||||||
import Search from "@/components/Search.vue";
|
import Search from "@/components/Search.vue";
|
||||||
|
import ContextMenu from "@/components/ContextMenu.vue";
|
||||||
import Item from "@/components/files/ListingItem.vue";
|
import Item from "@/components/files/ListingItem.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -287,6 +337,7 @@ export default {
|
|||||||
Action,
|
Action,
|
||||||
Search,
|
Search,
|
||||||
Item,
|
Item,
|
||||||
|
ContextMenu,
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
@ -295,10 +346,12 @@ export default {
|
|||||||
dragCounter: 0,
|
dragCounter: 0,
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
itemWeight: 0,
|
itemWeight: 0,
|
||||||
|
isContextMenuVisible: false,
|
||||||
|
contextMenuPos: { x: 0, y: 0 },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "selected", "user", "multiple", "selected", "loading"]),
|
...mapState(["req", "user", "multiple", "selected", "loading"]),
|
||||||
...mapGetters(["selectedCount", "currentPrompt"]),
|
...mapGetters(["selectedCount", "currentPrompt"]),
|
||||||
nameSorted() {
|
nameSorted() {
|
||||||
return this.req.sorting.by === "name";
|
return this.req.sorting.by === "name";
|
||||||
@ -398,7 +451,7 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
// Check the columns size for the first time.
|
// Check the columns size for the first time.
|
||||||
this.colunmsResize();
|
this.columnsResize();
|
||||||
|
|
||||||
// How much every listing item affects the window height
|
// How much every listing item affects the window height
|
||||||
this.setItemWeight();
|
this.setItemWeight();
|
||||||
@ -597,7 +650,7 @@ export default {
|
|||||||
|
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
},
|
},
|
||||||
colunmsResize() {
|
columnsResize() {
|
||||||
// Update the columns size based on the window width.
|
// Update the columns size based on the window width.
|
||||||
let items = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
|
let items = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
|
||||||
if (!items) return;
|
if (!items) return;
|
||||||
@ -788,7 +841,7 @@ export default {
|
|||||||
this.$store.commit("closeHovers");
|
this.$store.commit("closeHovers");
|
||||||
},
|
},
|
||||||
windowsResize: throttle(function () {
|
windowsResize: throttle(function () {
|
||||||
this.colunmsResize();
|
this.columnsResize();
|
||||||
this.width = window.innerWidth;
|
this.width = window.innerWidth;
|
||||||
|
|
||||||
// Listing element is not displayed
|
// Listing element is not displayed
|
||||||
@ -886,6 +939,16 @@ export default {
|
|||||||
// Set the number of displayed items
|
// Set the number of displayed items
|
||||||
this.showLimit = showQuantity > totalItems ? totalItems : showQuantity;
|
this.showLimit = showQuantity > totalItems ? totalItems : showQuantity;
|
||||||
},
|
},
|
||||||
|
showContextMenu(event) {
|
||||||
|
this.isContextMenuVisible = true;
|
||||||
|
this.contextMenuPos = {
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
hideContextMenu() {
|
||||||
|
this.isContextMenuVisible = false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user