dashboard/components/ActionMenu.vue

171 lines
3.6 KiB
Vue

<script>
import { mapGetters } from 'vuex';
import $ from 'jquery';
import { AUTO, CENTER, fitOnScreen } from '@/utils/position';
import { isAlternate } from '@/utils/platform';
const HIDDEN = 'hide';
const CALC = 'calculate';
const SHOW = 'show';
export default {
data() {
return {
phase: HIDDEN,
style: {}
};
},
computed: {
...mapGetters({
targetElem: 'action-menu/elem',
targetEvent: 'action-menu/event',
shouldShow: 'action-menu/showing',
options: 'action-menu/options'
}),
showing() {
return this.phase !== HIDDEN;
},
},
watch: {
shouldShow: {
handler(show) {
if ( show ) {
this.phase = CALC;
this.updateStyle();
this.$nextTick(() => {
if ( this.phase === CALC ) {
this.phase = SHOW;
this.updateStyle();
}
});
} else {
this.phase = HIDDEN;
}
},
},
'$route.path'(val, old) {
this.hide();
}
},
methods: {
hide() {
this.$store.commit('action-menu/hide');
},
updateStyle() {
if ( this.phase === SHOW ) {
const menu = $('.menu', this.$el)[0];
const event = this.targetEvent;
const elem = this.targetElem;
this.style = fitOnScreen(menu, event || elem, {
overlapX: true,
fudgeX: elem ? 4 : 0,
fudgeY: elem ? 4 : 0,
positionX: (elem ? AUTO : CENTER),
positionY: AUTO,
});
this.style.visibility = 'visible';
} else {
this.style = {};
}
},
execute(action, event, args) {
const opts = { alt: isAlternate(event) };
this.$store.dispatch('action-menu/execute', {
action, args, opts
});
this.hide();
},
hasOptions(options) {
return options.length !== undefined ? options.length : Object.keys(options).length > 0;
}
},
};
</script>
<template>
<div v-if="showing">
<div class="background" @click="hide" @contextmenu.prevent></div>
<ul class="list-unstyled menu" :style="style">
<li v-for="opt in options" :key="opt.action" :class="{divider: opt.divider}" @click="execute(opt, $event)">
<i v-if="opt.icon" :class="{icon: true, [opt.icon]: true}" />
<span v-html="opt.label" />
</li>
<li v-if="!hasOptions(options)" class="no-actions">
<span v-t="'sortableTable.noActions'" />
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.root {
position: absolute;
}
.menu {
position: absolute;
visibility: hidden;
top: 0;
left: 0;
z-index: z-index('dropdownContent');
color: var(--dropdown-text);
background-color: var(--dropdown-bg);
border: 1px solid var(--dropdown-border);
border-radius: 5px;
box-shadow: 0 5px 20px var(--shadow);
LI {
padding: 10px;
margin: 0;
&.divider {
padding: 0;
border-bottom: 1px solid var(--dropdown-divider);
}
&:not(.divider):hover {
background-color: var(--dropdown-hover-bg);
color: var(--dropdown-hover-text);
cursor: pointer;
}
.icon {
display: unset;
}
&.no-actions {
color: var(--disabled-text);
}
&.no-actions:hover {
background-color: initial;
color: var(--disabled-text);
cursor: default;
}
}
}
.background {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
z-index: z-index('dropdownOverlay');
}
</style>