hardlounge/client/components/ContextMenu.vue

193 lines
4.7 KiB
Vue
Raw Normal View History

2019-11-09 22:21:34 +00:00
<template>
2019-11-18 20:05:47 +00:00
<div
v-if="isOpen"
id="context-menu-container"
@click="containerClick"
@contextmenu.prevent="containerClick"
@keydown.exact.up.prevent="navigateMenu(-1)"
@keydown.exact.down.prevent="navigateMenu(1)"
@keydown.exact.tab.prevent="navigateMenu(1)"
@keydown.shift.tab.prevent="navigateMenu(-1)"
2019-11-18 20:05:47 +00:00
>
<ul
id="context-menu"
ref="contextMenu"
role="menu"
:style="style"
tabindex="-1"
@mouseleave="activeItem = -1"
@keydown.enter.prevent="clickActiveItem"
>
<template v-for="(item, id) of items">
<li
:key="item.name"
:class="[
'context-menu-' + item.type,
item.class ? 'context-menu-' + item.class : null,
{active: id === activeItem},
]"
role="menuitem"
2020-01-27 09:44:36 +00:00
@mouseenter="hoverItem(id)"
2019-11-18 20:05:47 +00:00
@click="clickItem(item)"
>
{{ item.label }}
</li>
</template>
2019-11-09 22:21:34 +00:00
</ul>
</div>
</template>
<script>
2020-02-25 09:16:05 +00:00
import {generateUserContextMenu, generateChannelContextMenu} from "../js/helpers/contextMenu.js";
2019-11-09 22:21:34 +00:00
export default {
name: "ContextMenu",
props: {
message: Object,
},
data() {
return {
isOpen: false,
previousActiveElement: null,
items: [],
2019-11-18 20:05:47 +00:00
activeItem: -1,
2019-11-09 22:21:34 +00:00
style: {
left: 0,
top: 0,
},
};
},
mounted() {
2020-01-24 09:32:28 +00:00
this.$root.$on("escapekey", this.close);
2019-11-23 14:26:20 +00:00
this.$root.$on("contextmenu:user", this.openUserContextMenu);
this.$root.$on("contextmenu:channel", this.openChannelContextMenu);
2019-11-09 22:21:34 +00:00
},
2019-11-18 19:18:35 +00:00
destroyed() {
2020-01-24 09:32:28 +00:00
this.$root.$off("escapekey", this.close);
2019-11-23 14:26:20 +00:00
this.$root.$off("contextmenu:user", this.openUserContextMenu);
this.$root.$off("contextmenu:channel", this.openChannelContextMenu);
this.close();
2019-11-18 19:18:35 +00:00
},
2019-11-09 22:21:34 +00:00
methods: {
2019-11-23 14:26:20 +00:00
openChannelContextMenu(data) {
const items = generateChannelContextMenu(this.$root, data.channel, data.network);
this.open(data.event, items);
},
openUserContextMenu(data) {
2020-04-24 07:20:40 +00:00
const activeChannel = this.$store.state.activeChannel;
// If there's an active network and channel use them
let {network, channel} = activeChannel ? activeChannel : {network: null, channel: null};
2019-11-23 14:26:20 +00:00
2020-04-24 07:20:40 +00:00
// Use network and channel from event if specified
network = data.network ? data.network : network;
channel = data.channel ? data.channel : channel;
const defaultUser = {nick: data.user.nick};
let user = channel ? channel.users.find((u) => u.nick === data.user.nick) : defaultUser;
user = user ? user : defaultUser;
const items = generateUserContextMenu(this.$root, channel, network, user);
2019-11-23 14:26:20 +00:00
this.open(data.event, items);
},
2019-11-09 22:21:34 +00:00
open(event, items) {
2019-11-18 20:05:47 +00:00
event.preventDefault();
this.previousActiveElement = document.activeElement;
2019-11-09 22:21:34 +00:00
this.items = items;
2019-11-18 20:05:47 +00:00
this.activeItem = 0;
2019-11-09 22:21:34 +00:00
this.isOpen = true;
// Position the menu and set the focus on the first item after it's size has updated
this.$nextTick(() => {
const pos = this.positionContextMenu(event);
this.style.left = pos.left + "px";
this.style.top = pos.top + "px";
2019-11-18 20:05:47 +00:00
this.$refs.contextMenu.focus();
2019-11-09 22:21:34 +00:00
});
},
close() {
if (!this.isOpen) {
return;
}
this.isOpen = false;
2019-11-18 20:05:47 +00:00
this.items = [];
2019-11-09 22:21:34 +00:00
if (this.previousActiveElement) {
this.previousActiveElement.focus();
this.previousActiveElement = null;
}
},
2019-11-18 20:05:47 +00:00
hoverItem(id) {
this.activeItem = id;
},
2019-11-09 22:21:34 +00:00
clickItem(item) {
this.close();
2019-11-09 22:21:34 +00:00
if (item.action) {
item.action();
} else if (item.link) {
this.$router.push(item.link);
2019-11-18 20:05:47 +00:00
}
},
clickActiveItem() {
if (this.items[this.activeItem]) {
this.clickItem(this.items[this.activeItem]);
}
},
navigateMenu(direction) {
2019-11-18 20:05:47 +00:00
let currentIndex = this.activeItem;
currentIndex += direction;
const nextItem = this.items[currentIndex];
// If the next item we would select is a divider, skip over it
if (nextItem && nextItem.type === "divider") {
2019-11-18 20:05:47 +00:00
currentIndex += direction;
}
if (currentIndex < 0) {
currentIndex += this.items.length;
}
if (currentIndex > this.items.length - 1) {
currentIndex -= this.items.length;
}
this.activeItem = currentIndex;
},
containerClick(event) {
if (event.currentTarget === event.target) {
this.close();
2019-11-09 22:21:34 +00:00
}
},
positionContextMenu(event) {
const element = event.target;
const menuWidth = this.$refs.contextMenu.offsetWidth;
const menuHeight = this.$refs.contextMenu.offsetHeight;
if (element && element.classList.contains("menu")) {
2019-11-09 22:21:34 +00:00
return {
left: element.getBoundingClientRect().left - (menuWidth - element.offsetWidth),
top: element.getBoundingClientRect().top + element.offsetHeight,
};
}
const offset = {left: event.pageX, top: event.pageY};
if (window.innerWidth - offset.left < menuWidth) {
offset.left = window.innerWidth - menuWidth;
}
if (window.innerHeight - offset.top < menuHeight) {
offset.top = window.innerHeight - menuHeight;
}
return offset;
},
},
};
</script>