mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-06 03:46:39 +08:00
Add flyout menu to step/results elements [SCI-9262]
This commit is contained in:
parent
7bb5305b14
commit
3bee8feb7d
10 changed files with 196 additions and 51 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -5,8 +5,7 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
showDeleteModal(event) {
|
||||
event.stopPropagation();
|
||||
showDeleteModal() {
|
||||
this.confirmingDelete = true;
|
||||
},
|
||||
closeDeleteModal() {
|
||||
|
|
|
@ -7,8 +7,7 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
showMoveModal(event) {
|
||||
event.stopPropagation();
|
||||
showMoveModal() {
|
||||
this.movingElement = true;
|
||||
},
|
||||
closeMoveModal() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
60
app/javascript/vue/shared/menu_dropdown.vue
Normal file
60
app/javascript/vue/shared/menu_dropdown.vue
Normal 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>
|
|
@ -3587,6 +3587,7 @@ en:
|
|||
edit: "Edit"
|
||||
delete: "Delete"
|
||||
cancel: "Cancel"
|
||||
duplicate: "Duplicate"
|
||||
okay: "Okay"
|
||||
back: "Back"
|
||||
close: "Close"
|
||||
|
|
Loading…
Add table
Reference in a new issue