Add flyout menu to step/results elements [SCI-9262]

This commit is contained in:
Anton 2023-09-13 12:12:36 +02:00
parent 7bb5305b14
commit 3bee8feb7d
10 changed files with 196 additions and 51 deletions

View file

@ -4,9 +4,12 @@ import TurbolinksAdapter from 'vue-turbolinks';
import Vue from 'vue/dist/vue.esm';
import ProtocolContainer from '../../vue/protocol/container.vue';
import PerfectScrollbar from 'vue2-perfect-scrollbar';
import outsideClick from './directives/outside_click';
Vue.use(PerfectScrollbar);
Vue.use(TurbolinksAdapter);
Vue.directive('click-outside', outsideClick);
Vue.prototype.i18n = window.I18n;
Vue.prototype.inlineEditing = window.inlineEditing;
Vue.prototype.ActiveStoragePreviews = window.ActiveStoragePreviews;

View file

@ -2,9 +2,12 @@ import TurbolinksAdapter from 'vue-turbolinks';
import Vue from 'vue/dist/vue.esm';
import Results from '../../vue/results/results.vue';
import PerfectScrollbar from 'vue2-perfect-scrollbar';
import outsideClick from './directives/outside_click';
Vue.use(PerfectScrollbar);
Vue.use(TurbolinksAdapter);
Vue.directive('click-outside', outsideClick);
Vue.prototype.i18n = window.I18n;
Vue.prototype.ActiveStoragePreviews = window.ActiveStoragePreviews;

View file

@ -22,20 +22,17 @@
@update="updateName"
/>
</div>
<div class="tw-hidden group-hover/checklist-header:flex items-center gap-2 ml-auto">
<button v-if="element.attributes.orderable.urls.update_url" class="btn icon-btn btn-light btn-sm" @click="editingName = true" tabindex="0">
<i class="sn-icon sn-icon-edit"></i>
</button>
<button v-if="element.attributes.orderable.urls.duplicate_url" class="btn icon-btn btn-light btn-sm" tabindex="0" @click="duplicateElement">
<i class="sn-icon sn-icon-duplicate"></i>
</button>
<button v-if="element.attributes.orderable.urls.move_targets_url" class="btn btn-light btn-sm" tabindex="0" @click="showMoveModal">
Move
</button>
<button v-if="element.attributes.orderable.urls.delete_url" class="btn icon-btn btn-light btn-sm" @click="showDeleteModal" tabindex="0">
<i class="sn-icon sn-icon-delete"></i>
</button>
</div>
<MenuDropdown
class="ml-auto"
:listItems="this.actionMenu"
:btnClasses="'btn btn-light icon-btn'"
:position="'right'"
:btnIcon="'sn-icon sn-icon-more-hori'"
@edit="editingName = true"
@duplicate="duplicateElement"
@move="showMoveModal"
@delete="showDeleteModal"
></MenuDropdown>
</div>
<div v-if="element.attributes.orderable.urls.create_item_url || orderedChecklistItems.length > 0">
<Draggable
@ -95,10 +92,11 @@
import ChecklistItem from './checklistItem.vue'
import Draggable from 'vuedraggable'
import moveElementModal from './modal/move.vue'
import MenuDropdown from '../menu_dropdown.vue'
export default {
name: 'Checklist',
components: { deleteElementModal, InlineEdit, ChecklistItem, Draggable, moveElementModal },
components: { deleteElementModal, InlineEdit, ChecklistItem, Draggable, moveElementModal, MenuDropdown },
mixins: [DeleteMixin, DuplicateMixin, MoveMixin],
props: {
element: {
@ -153,6 +151,34 @@
},
locked() {
return this.reordering || this.editingName || !this.element.attributes.orderable.urls.update_url
},
actionMenu() {
let menu = [];
if (this.element.attributes.orderable.urls.update_url) {
menu.push({
text: I18n.t('general.edit'),
emit: 'edit'
});
}
if (this.element.attributes.orderable.urls.duplicate_url) {
menu.push({
text: I18n.t('general.duplicate'),
emit: 'duplicate'
});
}
if (this.element.attributes.orderable.urls.move_targets_url) {
menu.push({
text: I18n.t('general.move'),
emit: 'move'
});
}
if (this.element.attributes.orderable.urls.delete_url) {
menu.push({
text: I18n.t('general.delete'),
emit: 'delete'
});
}
return menu;
}
},
methods: {

View file

@ -18,7 +18,7 @@
</span>
</div>
<div v-else class="w-6"></div>
<div class="whitespace-nowrap text-ellipsis overflow-hidden grow" :class="{ 'pointer-events-none': !checklistItem.attributes.isNew && !updateUrl }">
<div class="grow" :class="{ 'pointer-events-none': !checklistItem.attributes.isNew && !updateUrl }">
<InlineEdit
:value="checklistItem.attributes.text"
:sa_value="checklistItem.attributes.sa_text"

View file

@ -5,8 +5,7 @@ export default {
};
},
methods: {
showDeleteModal(event) {
event.stopPropagation();
showDeleteModal() {
this.confirmingDelete = true;
},
closeDeleteModal() {

View file

@ -7,8 +7,7 @@ export default {
};
},
methods: {
showMoveModal(event) {
event.stopPropagation();
showMoveModal() {
this.movingElement = true;
},
closeMoveModal() {

View file

@ -23,26 +23,23 @@
@update="updateName"
/>
</div>
<div class="tw-hidden group-hover/table-header:flex items-center gap-2 ml-auto">
<button v-if="element.attributes.orderable.urls.update_url" class="btn icon-btn btn-light" @click="enableNameEdit" tabindex="0">
<i class="sn-icon sn-icon-edit"></i>
</button>
<button v-if="element.attributes.orderable.urls.duplicate_url" class="btn icon-btn btn-light" tabindex="0" @click="duplicateElement">
<i class="sn-icon sn-icon-duplicate"></i>
</button>
<button v-if="element.attributes.orderable.urls.move_targets_url" class="btn btn-light btn-sm" tabindex="0" @click="showMoveModal">
Move
</button>
<button v-if="element.attributes.orderable.urls.delete_url" class="btn icon-btn btn-light" @click="showDeleteModal" tabindex="0">
<i class="sn-icon sn-icon-delete"></i>
</button>
</div>
<MenuDropdown
class="ml-auto"
:listItems="this.actionMenu"
:btnClasses="'btn btn-light icon-btn'"
:position="'right'"
:btnIcon="'sn-icon sn-icon-more-hori'"
@edit="enableNameEdit"
@duplicate="duplicateElement"
@move="showMoveModal"
@delete="showDeleteModal"
></MenuDropdown>
</div>
<div class="table-body ml-8 group/table-body relative p-1 border-solid border-transparent"
:class="{'edit border-sn-light-grey': editingTable, 'view': !editingTable, 'locked': !element.attributes.orderable.urls.update_url}"
tabindex="0"
@keyup.enter="!editingTable && enableTableEdit()">
<div class="absolute right-0 top-0 z-[200]" v-if="!editingTable && element.attributes.orderable.urls.update_url" @click="enableTableEdit">
<div class="absolute right-0 top-0 z-40" v-if="!editingTable && element.attributes.orderable.urls.update_url" @click="enableTableEdit">
<div class="bg-sn-light-grey rounded tw-hidden group-hover/table-body:flex cursor-pointer p-1">
<i class="sn-icon sn-icon-edit"></i>
@ -79,10 +76,11 @@
import InlineEdit from '../inline_edit.vue'
import TableNameModal from './modal/table_name.vue'
import moveElementModal from './modal/move.vue'
import MenuDropdown from '../menu_dropdown.vue'
export default {
name: 'ContentTable',
components: { deleteElementModal, InlineEdit, TableNameModal, moveElementModal },
components: { deleteElementModal, InlineEdit, TableNameModal, moveElementModal, MenuDropdown },
mixins: [DeleteMixin, DuplicateMixin, MoveMixin],
props: {
element: {
@ -117,6 +115,34 @@
computed: {
locked() {
return !this.element.attributes.orderable.urls.update_url
},
actionMenu() {
let menu = [];
if (this.element.attributes.orderable.urls.update_url) {
menu.push({
text: I18n.t('general.edit'),
emit: 'edit'
});
}
if (this.element.attributes.orderable.urls.duplicate_url) {
menu.push({
text: I18n.t('general.duplicate'),
emit: 'duplicate'
});
}
if (this.element.attributes.orderable.urls.move_targets_url) {
menu.push({
text: I18n.t('general.move'),
emit: 'move'
});
}
if (this.element.attributes.orderable.urls.delete_url) {
menu.push({
text: I18n.t('general.delete'),
emit: 'delete'
});
}
return menu;
}
},
updated() {

View file

@ -24,20 +24,17 @@
@update="updateName"
/>
</div>
<div class="tw-hidden group-hover/text-header:flex items-center gap-2 ml-auto" >
<button v-if="element.attributes.orderable.urls.update_url" class="btn icon-btn btn-light" @click="enableNameEdit" tabindex="0">
<i class="sn-icon sn-icon-edit"></i>
</button>
<button v-if="element.attributes.orderable.urls.duplicate_url" class="btn icon-btn btn-light" tabindex="0" @click="duplicateElement">
<i class="sn-icon sn-icon-duplicate"></i>
</button>
<button v-if="element.attributes.orderable.urls.move_targets_url" class="btn btn-light btn-sm" tabindex="0" @click="showMoveModal">
Move
</button>
<button v-if="element.attributes.orderable.urls.delete_url" class="btn icon-btn btn-light" @click="showDeleteModal" tabindex="0">
<i class="sn-icon sn-icon-delete"></i>
</button>
</div>
<MenuDropdown
class="ml-auto"
:listItems="this.actionMenu"
:btnClasses="'btn btn-light icon-btn'"
:position="'right'"
:btnIcon="'sn-icon sn-icon-more-hori'"
@edit="enableNameEdit"
@duplicate="duplicateElement"
@move="showMoveModal"
@delete="showDeleteModal"
></MenuDropdown>
</div>
<div class="flex rounded ml-8 pl-1 min-h-[2.25rem] mb-4 relative w-full group/text_container content__text-body" :class="{ 'edit': inEditMode, 'component__element--locked': !element.attributes.orderable.urls.update_url }" @keyup.enter="enableEditMode($event)" tabindex="0">
<Tinymce
@ -78,10 +75,11 @@
import moveElementModal from './modal/move.vue'
import InlineEdit from '../inline_edit.vue'
import Tinymce from '../tinymce.vue'
import MenuDropdown from '../menu_dropdown.vue'
export default {
name: 'TextContent',
components: { deleteElementModal, Tinymce, moveElementModal, InlineEdit },
components: { deleteElementModal, Tinymce, moveElementModal, InlineEdit, MenuDropdown },
mixins: [DeleteMixin, DuplicateMixin, MoveMixin],
props: {
element: {
@ -115,6 +113,36 @@
this.enableEditMode();
}
},
computed: {
actionMenu() {
let menu = [];
if (this.element.attributes.orderable.urls.update_url) {
menu.push({
text: I18n.t('general.edit'),
emit: 'edit'
});
}
if (this.element.attributes.orderable.urls.duplicate_url) {
menu.push({
text: I18n.t('general.duplicate'),
emit: 'duplicate'
});
}
if (this.element.attributes.orderable.urls.move_targets_url) {
menu.push({
text: I18n.t('general.move'),
emit: 'move'
});
}
if (this.element.attributes.orderable.urls.delete_url) {
menu.push({
text: I18n.t('general.delete'),
emit: 'delete'
});
}
return menu;
}
},
methods: {
enableEditMode() {
if (!this.element.attributes.orderable.urls.update_url) return

View file

@ -0,0 +1,60 @@
<template>
<div class="relative" v-if="listItems.length > 0" >
<button ref="openBtn" :class="btnClasses" @click="showMenu = !showMenu">
<i v-if="btnIcon" :class="btnIcon"></i>
{{ btnText }}
<i v-if="caret && showMenu" class="fas fa-caret-up"></i>
<i v-else-if="caret" class="fas fa-caret-down"></i>
</button>
<div ref="flyout"
class="absolute z-50 bg-sn-white rounded p-2.5 sn-shadow-menu-sm"
:class="{'right-0': position === 'right', 'left-0': position === 'left'}"
v-if="showMenu"
v-click-outside="{handler: 'closeMenu', exclude: ['openBtn', 'flyout']}">
<a v-for="(item, i) in listItems"
:key="i"
:href="item.url"
class="block whitespace-nowrap px-3 py-2.5 hover:no-underline cursor-pointer hover:bg-sn-super-light-blue"
@click="handleClick($event, item)"
>
{{ item.text }}
</a>
</div>
</div>
</template>
<script>
export default {
name: 'DropdownMenu',
props: {
listItems: { type: Array, required: true },
position: { type: String, default: 'left' },
btnClasses: { type: String, default: 'btn btn-light' },
btnText: { type: String, required: false },
btnIcon: { type: String, required: false },
caret: { type: Boolean, default: false },
},
data() {
return {
showMenu: false,
}
},
methods: {
closeMenu() {
this.showMenu = false;
},
handleClick(event, item) {
if (!item.url) {
event.preventDefault();
}
if (item.emit) {
this.$emit(item.emit)
}
this.showMenu = false;
}
}
}
</script>

View file

@ -3587,6 +3587,7 @@ en:
edit: "Edit"
delete: "Delete"
cancel: "Cancel"
duplicate: "Duplicate"
okay: "Okay"
back: "Back"
close: "Close"