base_ui/components/global/RightClickComponent.vue
2025-02-01 13:04:55 +03:30

202 lines
5.6 KiB
Vue

<template>
<div
class="ctx-menu"
:style="style"
:hidden="!ctxMenuData"
v-click-outside="resetCtx"
>
<!-- Check if there are options data -->
<div v-if="ctxMenuData">
<!-- Use template tag to loop through the options -->
<div v-for="(item, index) in ctxMenuData">
<!--
Then check opton type default is undeifined then if the type is not divider
-->
<!-- Make sure to create a unique ref is will be needed later -->
<div
v-if="item.type !== 'divider'"
:key="index"
:ref="'ctx_' + index"
:id="'ctx_' + index"
>
<span class="ctx-menu-option">
<svg class="nav-icon-container" :class="'icon icon-' + item.icon">
<use :xlink:href="'#icon-' + item.icon"></use>
</svg>
{{ item.title }}
</span>
</div>
<div v-else :key="'else-' + index" class="ctx-menu-divider">
<span></span>
</div>
</div>
</div>
</div>
</template>
<script>
// Access the eventBus instance
export default {
data: () => ({
style: null,
ctxMenuData: null,
// the options schema
// [
// {
// title: string,
// type: string, // default is undefined
// handler: function
// }
// ]
ctxMenuRect: null,
// {
// y:number,
// x:number
// }
}),
methods: {
resetCtx() {
this.ctxMenuData = null;
this.ctxMenuRect = null;
},
onContextMenu(ev, ctxMenuData) {
// prevent default behaviours
ev.preventDefault();
ev.stopPropagation();
this.ctxMenuData = ctxMenuData;
this.ctxMenuRect = {
x: ev.x,
y: ev.y,
};
// then reevaluate and set context-menu position
this.reevaluatePosition();
// populate the option
this.$nextTick(() => {
this.onData();
});
},
async reevaluatePosition() {
if (this.ctxMenuRect) {
// using $nextTick to daley and make sure that the context-menu
// options are fully rendered which will help us
// to get the accurate height
await this.$nextTick();
await this.$nextTick();
let { x, y } = this.ctxMenuRect;
// get the window current inner height and width
let innerHeight = window?.innerHeight;
let innerWidth = window?.innerWidth;
// get the component height and width through element.getClientRects
let height = 0,
width = 0;
let clientRect = this.$el.getClientRects();
if (clientRect && clientRect.length) {
height = clientRect[0].height;
width = clientRect[0].width;
}
// then subtract window inner height and width with
// context-menu event source points (x, y)
let dY = innerHeight - y;
let dX = innerWidth - x;
// check if the context-menu height is not
// longer than the available
if (dY < height) {
y = y - height;
}
if (dX < width) {
x = x - width;
}
// set the position
this.style = { left: x + "px", top: y + "px" };
}
},
async onData() {
const vm = this;
// validate if the ctxMenuData is an array and the lenght is not less then 1
if (Array.isArray(vm.ctxMenuData) && vm.ctxMenuData.length) {
// loop through the options
vm.ctxMenuData.forEach((item, index) => {
// if vm option type is equal's to divider and the handler property value is a function
if (item.type !== "divider" && typeof item.handler === "function") {
// select the option element with the help of the refs id
let refName = `ctx_${index}`;
let refs = vm.$refs[refName];
// accessing $refs prooerty with object square bracket notation alwasys returns arrays of
// HTML Elements of Vue components instance
// so you have to validate
if (Array.isArray(refs) && refs.length) {
let el = refs[0];
// then attach click event and pass an arrow function as a the
// event handler callback
el.addEventListener(
"click",
() => {
// then on click on the option
// envoke the handler
// and reset the the ctxMenuData to hide the context-menu
item.handler();
vm.resetCtx();
},
false
);
}
}
});
}
},
},
mounted() {
// Listen on contextmenu event through the $root instance
const { $eventBus } = useNuxtApp();
$eventBus.emit("contextmenu", (data) => {
// if the data is null reset and handler the action
if (data === null) this.resetCtx();
else this.onContextMenu(data.event, data.ctxMenuData);
});
},
beforeDestroy() {
// this.$root.$off("contextmenu", () => {});
},
};
</script>
<style scoped>
.ctx-menu {
min-width: 150px;
height: fit-content;
padding: 10px 0;
position: fixed;
background-color: #fff;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26), 0 2px 10px 0 rgba(0, 0, 0, 0.16);
border-radius: 3px;
z-index: 1111111;
}
.ctx-menu-option {
display: flex;
align-items: center;
height: 35px;
padding: 0 10px;
cursor: pointer;
}
.ctx-menu-option:hover {
background-color: #f3f9ff;
}
.ctx-menu-divider {
height: 16px;
display: flex;
justify-content: center;
align-items: center;
}
.ctx-menu-divider > span {
width: 50px;
height: 1px;
background-color: #dcdfe6;
}
</style>