scinote-web/app/javascript/vue/results/result.vue
2023-09-06 15:06:27 +02:00

386 lines
15 KiB
Vue

<template>
<div class="result-wrapper bg-white px-4 py-2 mb-4">
<div class="result-header flex justify-between py-2">
<div class="result-head-left">
<InlineEdit
:value="result.attributes.name"
:class="{ 'pointer-events-none': !urls.update_url }"
:characterLimit="255"
:allowBlank="false"
:attributeName="`${i18n.t('Result')} ${i18n.t('name')}`"
:autofocus="editingName"
:placeholder="i18n.t('my_modules.results.placeholder')"
:defaultValue="i18n.t('my_modules.results.default_name')"
@editingEnabled="editingName = true"
@editingDisabled="editingName = false"
:editOnload="result.newResult == true"
@update="updateName"
/>
</div>
<div class="result-head-right flex elements-actions-container">
<input type="file" class="hidden" ref="fileSelector" @change="loadFromComputer" multiple />
<div ref="elementsDropdownButton" v-if="urls.update_url" class="dropdown">
<button class="btn btn-light dropdown-toggle insert-button" type="button" :id="'resultInsertMenu_' + result.attributes.id" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{{ i18n.t('my_modules.results.insert.button') }}
<span class="sn-icon sn-icon-down"></span>
</button>
<ul ref="elementsDropdown" class="dropdown-menu insert-element-dropdown dropdown-menu-right" :aria-labelledby="'resultInsertMenu_' + result.attributes.id">
<li class="title">
{{ i18n.t('my_modules.results.insert.title') }}
</li>
<li class="action" @click="createElement('table')">
<i class="sn-icon sn-icon-tables"></i>
{{ i18n.t('my_modules.results.insert.table') }}
</li>
<li class="action dropdown-submenu-item">
<i class="sn-icon sn-icon-tables"></i>
{{ i18n.t('my_modules.results.insert.well_plate') }}
<span class="caret"></span>
<ul class="dropdown-submenu">
<li v-for="option in wellPlateOptions" :key="option.dimensions.toString()" class="action" @click="createElement('table', option.dimensions, true)">
{{ i18n.t(option.label) }}
</li>
</ul>
</li>
<li class="action" @click="createElement('text')">
<i class="sn-icon sn-icon-result-text"></i>
{{ i18n.t('my_modules.results.insert.text') }}
</li>
<li class="action dropdown-submenu-item">
<i class="sn-icon sn-icon-files"></i>
{{ i18n.t('my_modules.results.insert.attachment') }}
<span class="caret"></span>
<ul class="dropdown-submenu">
<li class="action" @click="openLoadFromComputer">
{{ i18n.t('my_modules.results.insert.add_file') }}
</li>
<li class="action" v-if="result.attributes.wopi_enabled" @click="openWopiFileModal">
{{ i18n.t('assets.create_wopi_file.button_text') }}
</li>
<li class="action" v-if="result.attributes.marvinjs_enabled" @click="openMarvinJsModal($refs.marvinJsButton)">
<span
class="new-marvinjs-upload-button text-sn-black text-decoration-none"
:data-object-id="result.attributes.id"
ref="marvinJsButton"
:data-marvin-url="result.attributes.marvinjs_context.marvin_js_asset_url"
:data-object-type="result.attributes.type"
tabindex="0"
>
{{ i18n.t('marvinjs.new_button') }}
</span>
</li>
</ul>
</li>
</ul>
</div>
<a href="#"
ref="comments"
class="open-comments-sidebar btn icon-btn btn-light"
data-turbolinks="false"
data-object-type="Result"
:data-object-id="result.attributes.id">
<i class="sn-icon sn-icon-comments"></i>
</a>
<div ref="actionsDropdownButton" class="dropdown">
<button class="btn btn-light icon-btn dropdown-toggle insert-button" type="button" :id="'resultOptionsMenu_' + result.attributes.id" data-toggle="dropdown" data-display="static" aria-haspopup="true" aria-expanded="true">
<i class="sn-icon sn-icon-more-hori"></i>
</button>
<ul ref="actionsDropdown" class="dropdown-menu dropdown-menu-right insert-element-dropdown" :aria-labelledby="'resultOptionsMenu_' + result.attributes.id">
<li class="action" @click="openReorderModal" v-if="urls.reorder_elements_url">
{{ i18n.t('my_modules.results.actions.rearrange') }}
</li>
<li class="action" @click="duplicateResult" v-if="urls.duplicate_url && !result.attributes.archived">
{{ i18n.t('my_modules.results.actions.duplicate') }}
</li>
<li class="action" @click="archiveResult" v-if="urls.archive_url">
{{ i18n.t('my_modules.results.actions.archive') }}
</li>
<li class="action" @click="restoreResult" v-if="urls.restore_url">
{{ i18n.t('my_modules.results.actions.restore') }}
</li>
<li class="action" @click="deleteResult" v-if="urls.delete_url">
{{ i18n.t('my_modules.results.actions.delete') }}
</li>
</ul>
</div>
</div>
</div>
<div class="relative ml-1 bottom-17 w-356 h-15 font-normal font-hairline text-xs leading-4">
{{ i18n.t('protocols.steps.timestamp', {date: result.attributes.created_at, user: result.attributes.created_by }) }}
</div>
<ReorderableItemsModal v-if="reordering"
:title="i18n.t('my_modules.modals.reorder_results.title')"
:items="reorderableElements"
@reorder="updateElementOrder"
@close="closeReorderModal"
/>
<div>
<div v-for="(element, index) in orderedElements" :key="index" class="pt-6 border-0 border-t border-dotted border-sn-sleepy-grey mt-6">
<component
:is="elements[index].attributes.orderable_type"
:element.sync="elements[index]"
:inRepository="false"
:reorderElementUrl="elements.length > 1 ? urls.reorder_elements_url : ''"
:assignableMyModuleId="result.attributes.my_module_id"
:isNew="element.isNew"
@component:delete="deleteElement"
@update="updateElement"
@reorder="openReorderModal"
@component:insert="insertElement"
@moved="moveElement"
/>
</div>
<Attachments v-if="attachments.length"
class="pt-6 border-0 border-t border-dotted border-sn-sleepy-grey mt-6"
:parent="result"
:attachments="attachments"
:attachmentsReady="attachmentsReady"
@attachments:openFileModal="showFileModal = true"
@attachment:deleted="attachmentDeleted"
@attachment:uploaded="loadAttachments"
@attachment:moved="moveAttachment"
@attachments:order="changeAttachmentsOrder"
@attachments:viewMode="changeAttachmentsViewMode"
@attachment:viewMode="updateAttachmentViewMode"/>
</div>
</div>
</template>
<script>
import axios from '../../packs/custom_axios.js';
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue';
import ResultTable from '../shared/content/table.vue';
import ResultText from '../shared/content/text.vue';
import Attachments from '../shared/content/attachments.vue';
import InlineEdit from '../shared/inline_edit.vue'
import AttachmentsMixin from '../shared/content/mixins/attachments.js'
import WopiFileModal from '../shared/content/attachments/mixins/wopi_file_modal.js'
import UtilsMixin from '../mixins/utils.js'
export default {
name: 'Results',
props: {
result: { type: Object, required: true },
resultToReload: { type: Number, required: false }
},
data() {
return {
reordering: false,
elements: [],
attachments: [],
attachmentsReady: false,
showFileModal: false,
wellPlateOptions: [
{ label: 'my_modules.results.insert.well_plate_options.32_x_48', dimensions: [32, 48] },
{ label: 'my_modules.results.insert.well_plate_options.16_x_24', dimensions: [16, 24] },
{ label: 'my_modules.results.insert.well_plate_options.8_x_12', dimensions: [8, 12] },
{ label: 'my_modules.results.insert.well_plate_options.6_x_8', dimensions: [6, 8] },
{ label: 'my_modules.results.insert.well_plate_options.6_x_4', dimensions: [6, 4] },
{ label: 'my_modules.results.insert.well_plate_options.2_x_3', dimensions: [2, 3] }
],
editingName: false
}
},
mixins: [UtilsMixin, AttachmentsMixin, WopiFileModal],
components: {
ReorderableItemsModal,
ResultTable,
ResultText,
Attachments,
InlineEdit
},
watch: {
resultToReload() {
if (this.resultToReload == this.result.attributes.id) {
this.loadElements();
this.loadAttachments();
}
}
},
computed: {
reorderableElements() {
return this.orderedElements.map((e) => { return { id: e.id, attributes: e.attributes.orderable } })
},
orderedElements() {
return this.elements.sort((a, b) => a.attributes.position - b.attributes.position);
},
urls() {
return this.result.attributes.urls || {}
}
},
created() {
this.loadAttachments();
this.loadElements();
},
methods: {
openReorderModal() {
this.reordering = true;
},
closeReorderModal() {
this.reordering = false;
},
updateElementOrder(orderedElements) {
orderedElements.forEach((element, position) => {
let index = this.elements.findIndex((e) => e.id === element.id);
this.elements[index].attributes.position = position;
});
let elementPositions = {
result_orderable_element_positions: this.elements.map(
(element) => [element.id, element.attributes.position]
)
};
axios.post(this.urls.reorder_elements_url, elementPositions, {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then(() => {
this.$emit('resultUpdated');
})
.catch(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
});
},
deleteElement(position) {
this.elements.splice(position, 1)
let unorderedElements = this.elements.map( e => {
if (e.attributes.position >= position) {
e.attributes.position -= 1;
}
return e;
})
this.$emit('resultUpdated')
},
updateElement(element, skipRequest=false, callback) {
let index = this.elements.findIndex((e) => e.id === element.id);
this.elements[index].isNew = false;
if (skipRequest) {
this.elements[index].attributes.orderable = element.attributes.orderable;
this.$emit('resultUpdated');
} else {
$.ajax({
url: element.attributes.orderable.urls.update_url,
method: 'PUT',
data: element.attributes.orderable,
success: (result) => {
this.elements[index].attributes.orderable = result.data.attributes;
this.$emit('resultUpdated');
// optional callback after successful update
if(typeof callback === 'function') {
callback();
}
}
}).fail(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
})
}
},
insertElement(element) {
let position = element.attributes.position;
this.elements = this.elements.map( s => {
if (s.attributes.position >= position) {
s.attributes.position += 1;
}
return s;
})
this.elements.push(element);
},
loadElements() {
$.get(this.urls.elements_url, (result) => {
this.elements = result.data
this.$emit('result:elements:loaded');
});
},
loadAttachments() {
this.attachmentsReady = false
$.get(this.urls.attachments_url, (result) => {
this.attachments = result.data
this.$emit('result:attachments:loaded');
if (this.attachments.findIndex((e) => e.attributes.attached === false) >= 0) {
setTimeout(() => {
this.loadAttachments()
}, 10000)
} else {
this.attachmentsReady = true
}
});
this.showFileModal = false;
},
attachmentDeleted(id) {
this.attachments = this.attachments.filter((a) => a.id !== id );
this.$emit('resultUpdated');
},
createElement(elementType, tableDimensions = [5,5], plateTemplate = false) {
$.post(this.urls[`create_${elementType}_url`], { tableDimensions: tableDimensions, plateTemplate: plateTemplate }, (result) => {
result.data.isNew = true;
this.elements.push(result.data)
this.$emit('resultUpdated')
}).fail(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
}).done(() => {
this.$parent.$nextTick(() => {
const children = this.$children
const lastChild = children[children.length - 1]
lastChild.$el.scrollIntoView(false)
window.scrollBy({
top: 200,
behavior: 'smooth'
});
})
});
},
archiveResult() {
axios.post(this.urls.archive_url).then((response) => {
this.$emit('result:archived', this.result.attributes.id);
});
},
restoreResult() {
axios.post(this.urls.restore_url).then((response) => {
this.$emit('result:restored', this.result.attributes.id);
});
},
deleteResult() {
axios.delete(this.urls.delete_url).then((response) => {
this.$emit('result:deleted', this.result.attributes.id);
});
},
duplicateResult() {
axios.post(this.urls.duplicate_url).then((_) => {
this.$emit('result:duplicated');
});
},
moveElement(position, target_id) {
this.elements.splice(position, 1)
let unorderedElements = this.elements.map( e => {
if (e.attributes.position >= position) {
e.attributes.position -= 1;
}
return e;
})
this.$emit('resultUpdated')
this.$emit('result:move_element', target_id)
},
moveAttachment(id, target_id) {
this.attachments = this.attachments.filter((a) => a.id !== id );
this.$emit('resultUpdated')
this.$emit('result:move_attachment', target_id)
},
updateName(name) {
axios.patch(this.urls.update_url, { result: { name: name } }).then((_) => {
this.$emit('updated');
});
}
}
}
</script>