Implement protocol options dropdown [SCI-6750, SCI-6751] (#4066)

* Implement protocol options dropdown [SCI-6750, SCI-6751]

* Implement simple API error handling for step status update [SCI-6354]
This commit is contained in:
artoscinote 2022-05-03 15:18:48 +02:00 committed by GitHub
parent ccc668ce5c
commit 6352a4dd04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 176 additions and 48 deletions

View file

@ -27,4 +27,14 @@
} }
} }
} }
.protocol-options-dropdown {
a {
cursor: pointer;
&.disabled {
cursor: default;
pointer-events: none;
}
}
}
} }

View file

@ -263,14 +263,14 @@ class MyModulesController < ApplicationController
end end
def protocol def protocol
render json: @my_module.protocol render json: @my_module.protocol, serializer: ProtocolSerializer
end end
def update_protocol def update_protocol
protocol = @my_module.protocol protocol = @my_module.protocol
protocol.update!(protocol_params) protocol.update!(protocol_params)
render json: protocol render json: protocol, serializer: ProtocolSerializer
end end
def results def results

View file

@ -9,6 +9,7 @@ Vue.prototype.i18n = window.I18n;
window.initProtocolComponent = () => { window.initProtocolComponent = () => {
Vue.prototype.dateFormat = $('#protocolContainer').data('date-format'); Vue.prototype.dateFormat = $('#protocolContainer').data('date-format');
new Vue({ new Vue({
el: '#protocolContainer', el: '#protocolContainer',
components: { components: {

View file

@ -6,8 +6,8 @@
<div class="task-section-title"> <div class="task-section-title">
<h2>{{ i18n.t('Protocol') }}</h2> <h2>{{ i18n.t('Protocol') }}</h2>
</div> </div>
<span v-if="protocol.linked" class="status-label linked"> <span v-if="protocol.attributes.linked" class="status-label linked">
[{{ protocol.name }}] [{{ protocol.attributes.name }}]
</span> </span>
<span class="status-label" v-else> <span class="status-label" v-else>
[{{ i18n.t('my_modules.protocols.protocol_status_bar.unlinked') }}] [{{ i18n.t('my_modules.protocols.protocol_status_bar.unlinked') }}]
@ -16,52 +16,20 @@
<div class="sci-btn-group actions-block"> <div class="sci-btn-group actions-block">
<a class="btn btn-primary" @click="addStep(steps.length)"> <a class="btn btn-primary" @click="addStep(steps.length)">
<span class="fas fa-plus" aria-hidden="true"></span> <span class="fas fa-plus" aria-hidden="true"></span>
<span>New step</span> <span>{{ i18n.t("protocols.steps.new_step") }}</span>
</a> </a>
<a class="btn btn-default" data-toggle="modal" data-target="#print-protocol-modal"> <a class="btn btn-default" data-toggle="modal" data-target="#print-protocol-modal">
<span class="fas fa-print" aria-hidden="true"></span> <span class="fas fa-print" aria-hidden="true"></span>
<span>Print</span> <span>{{ i18n.t("protocols.print.button") }}</span>
</a> </a>
<div class="dropdown sci-dropdown"> <ProtocolOptions v-if="protocol.attributes && protocol.attributes.urls" :protocol="protocol" />
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownProtocolOptions" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span class="fas fa-cog"></span>
<span>Protocol options</span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownProtocolOptions">
<li>
<a>
<span class="fas fa-edit"></span>
<span>Load from repository</span>
</a>
</li>
<li>
<a>
<span class="fas fa-download"></span>
<span>Import protocol</span>
</a>
</li>
<li>
<a>
<span class="fas fa-upload"></span>
<span>Export protocol</span>
</a>
</li>
<li>
<a>
<span class="fas fa-save"></span>
<span>Save to repository</span>
</a>
</li>
</ul>
</div>
</div> </div>
</div> </div>
<div v-if="protocol.id" id="protocol-content" class="protocol-content collapse in" aria-expanded="true"> <div v-if="protocol.id" id="protocol-content" class="protocol-content collapse in" aria-expanded="true">
<div class="protocol-description"> <div class="protocol-description">
<div class="protocol-name"> <div class="protocol-name">
<InlineEdit <InlineEdit
:value="protocol.name" :value="protocol.attributes.name"
:characterLimit="255" :characterLimit="255"
:placeholder="i18n.t('my_modules.protocols.protocol_status_bar.enter_name')" :placeholder="i18n.t('my_modules.protocols.protocol_status_bar.enter_name')"
:allowBlank="true" :allowBlank="true"
@ -95,6 +63,7 @@
<script> <script>
import InlineEdit from 'vue/shared/inline_edit.vue' import InlineEdit from 'vue/shared/inline_edit.vue'
import Step from 'vue/protocol/step' import Step from 'vue/protocol/step'
import ProtocolOptions from 'vue/protocol/protocolOptions'
export default { export default {
name: 'ProtocolContainer', name: 'ProtocolContainer',
@ -116,16 +85,18 @@
required: true required: true
} }
}, },
components: { Step, InlineEdit }, components: { Step, InlineEdit, ProtocolOptions },
data() { data() {
return { return {
protocol: {}, protocol: {
attributes: {}
},
steps: {} steps: {}
} }
}, },
created() { created() {
$.get(this.protocolUrl, (data) => { $.get(this.protocolUrl, (result) => {
this.protocol = data; this.protocol = result.data;
}); });
$.get(this.stepsUrl, (result) => { $.get(this.stepsUrl, (result) => {
this.steps = result.data this.steps = result.data
@ -133,7 +104,7 @@
}, },
methods: { methods: {
updateName(newName) { updateName(newName) {
this.protocol.name = newName; this.protocol.attributes.name = newName;
$.ajax({ $.ajax({
type: 'PATCH', type: 'PATCH',
url: this.protocolUrl, url: this.protocolUrl,

View file

@ -0,0 +1,98 @@
<template>
<div class="dropdown sci-dropdown protocol-options-dropdown">
<button
class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownProtocolOptions"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="true"
>
<span class="fas fa-cog"></span>
<span>{{ i18n.t("my_modules.protocol.options_dropdown.title") }}</span>
<span class="caret"></span>
</button>
<ul
class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownProtocolOptions"
>
<li>
<a
ref="loadProtocol"
data-action="load-from-repository"
@click="loadProtocol"
:class="{ disabled: !protocol.attributes.urls.load_from_repo_url }"
>
<span class="fas fa-edit"></span>
<span>{{ i18n.t("my_modules.protocol.options_dropdown.load_from_repo") }}</span>
</a>
</li>
<li>
<a
ref="saveProtocol"
data-action="copy-to-repository"
@click="saveProtocol"
:class="{ disabled: !protocol.attributes.urls.save_to_repo_url }"
>
<span class="fas fa-check"></span>
<span>{{
i18n.t("my_modules.protocol.options_dropdown.save_to_repo")
}}</span>
</a>
</li>
<li>
<a data-action="load-from-file"
class="btn-open-file"
:class="{ disabled: !protocol.attributes.urls.import_url }">
<span class="fas fa-download"></span>
<span>{{ i18n.t("my_modules.protocol.options_dropdown.import") }}</span>
<input type="file" value="" accept=".eln" data-turbolinks="false">
</a>
</li>
<li>
<a
data-turbolinks="false"
:href="protocol.attributes.urls.export_url"
:class="{ disabled: !protocol.attributes.urls.export_url }"
>
<span class="fas fa-upload"></span>
<span>{{
i18n.t("my_modules.protocol.options_dropdown.export")
}}</span>
</a>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "ProtocolOptions",
props: {
protocol: {
type: Object,
required: true,
},
},
mounted() {
// Legacy global functions from app/assets/javascripts/my_modules/protocols.js
initLoadFromRepository();
initCopyToRepository();
initImport();
},
methods: {
loadProtocol() {
$.get(
this.protocol.attributes.urls.load_from_repo_url + "?type=recent"
).success((data) => {
$(this.$refs.loadProtocol).trigger("ajax:success", data);
});
},
saveProtocol() {
$.get(this.protocol.attributes.urls.save_to_repo_url).success((data) => {
$(this.$refs.saveProtocol).trigger("ajax:success", data);
});
}
},
};
</script>

View file

@ -107,8 +107,12 @@
}); });
}, },
changeState() { changeState() {
$.post(this.step.attributes.urls.state_url, {completed: !this.step.attributes.completed}, (result) => { this.step.attributes.completed = !this.step.attributes.completed;
this.$emit('step:update', result.data.attributes) this.$emit('step:update', this.step)
$.post(this.step.attributes.urls.state_url, {completed: this.step.attributes.completed}).error(() => {
this.step.attributes.completed = !this.step.attributes.completed;
this.$emit('step:update', this.step)
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
}) })
}, },
updateName(newName) { updateName(newName) {

View file

@ -0,0 +1,43 @@
# frozen_string_literal: true
class ProtocolSerializer < ActiveModel::Serializer
include Canaid::Helpers::PermissionsHelper
include Rails.application.routes.url_helpers
attributes :name, :id, :urls
def urls
{
load_from_repo_url: load_from_repo_url,
save_to_repo_url: save_to_repo_url,
export_url: export_url,
import_url: import_url
}
end
private
def load_from_repo_url
return unless can_manage_protocol_in_module?(object)
load_from_repository_modal_protocol_path(object, format: :json)
end
def save_to_repo_url
return unless can_read_protocol_in_module?(object) && can_create_protocols_in_repository?(object.team)
copy_to_repository_modal_protocol_path(object, format: :json)
end
def import_url
return unless can_manage_protocol_in_module?(object)
load_from_file_protocol_path(object, format: :json)
end
def export_url
return unless can_read_protocol_in_module?(object)
export_protocols_path(protocol_ids: object.id, my_module_id: object.my_module.id)
end
end

View file

@ -178,7 +178,8 @@ en:
attributes: attributes:
output_id: output_id:
creates_cycle: "mustn't create cycle" creates_cycle: "mustn't create cycle"
errors:
general: "Something went wrong."
helpers: helpers:
label: label:
team: team: