Merge pull request #4755 from aignatov-bio/ai-sci-7590-update-protocol-screen-draft

Update protocol view screen [SCI-7590][SCI-7617]
This commit is contained in:
aignatov-bio 2023-02-13 10:58:03 +01:00 committed by GitHub
commit 65bf9b2511
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 299 additions and 136 deletions

View file

@ -9,38 +9,96 @@
margin: 0 -16px;
min-height: calc(100vh - var(--navbar-height));
overflow: hidden;
padding: 36px 10px 10px;
padding: 0 0 10px;
width: calc(100% + 31px);
.content-header {
padding: 0 2em;
}
.protocol-position-container {
background-color: $color-white;
box-shadow: $flyout-shadow;
margin: 0 auto;
margin: 20px auto;
max-width: 900px;
padding: 10px 10px 10px 26px;
}
.protocol-section {
margin: 16px 0 16px -20px;
&.protocol-steps-section {
margin-left: 0;
}
.protocol-section-caret {
color: $color-volcano;
display: inline-block;
padding: .5em;
text-decoration: none;
.fas {
margin-right: 5px;
}
&:not(.collapsed) .fas {
@include rotate(90deg);
}
}
.protocol-section-title {
display: inline-block;
h2 {
display: inline-block;
margin: 10px 0;
}
.protocol-code {
color: $color-volcano;
margin-left: .5em;
}
}
.protocol-section-header {
align-items: center;
display: flex;
flex-wrap: wrap;
.actions-block {
display: flex;
flex-grow: 1;
justify-content: flex-end;
.caret {
margin-left: 25px;
}
}
}
.protocol-details {
padding-left: 20px;
}
}
.protocol-metadata {
margin-bottom: 2em;
.data-block {
margin-bottom: 16px;
}
.authors-data {
align-items: center;
display: flex;
margin-top: -12px;
.authors-list {
flex-basis: calc(100% - 90px);
flex-grow: 1;
margin-left: .75em;
&:hover:not(.editing) {
.sci-inline-edit__content {
border: $border-focus;
}
}
.sci-inline-edit__content {
padding-top: .35em;
}
line-height: 32px;
}
* {
@ -51,6 +109,7 @@
.keywords-data {
align-items: center;
display: flex;
margin-top: -12px;
.keywords-list {
flex-basis: calc(100% - 90px);

View file

@ -78,6 +78,7 @@ class ProtocolsController < ApplicationController
only: %i(protocolsio_import_create protocolsio_import_save)
before_action :set_importer, only: %i(load_from_file import)
before_action :set_inline_name_editing, only: :show
layout 'fluid'
@ -1103,6 +1104,18 @@ class ProtocolsController < ApplicationController
end
end
def set_inline_name_editing
return unless can_manage_protocol_in_repository?(@protocol)
@inline_editable_title_config = {
name: 'title',
params_group: 'protocol',
item_id: @protocol.id,
field_to_udpate: 'name',
path_to_update: name_protocol_path(@protocol)
}
end
def load_team_and_type
@current_team = current_team
# :public, :private or :archive

View file

@ -33,7 +33,7 @@
</div>
<div v-if="protocol.id" id="protocol-content" class="protocol-content collapse in" aria-expanded="true">
<div class="protocol-description">
<div class="protocol-name">
<div class="protocol-name" v-if="!inRepository">
<InlineEdit
v-if="urls.update_protocol_name_url"
:value="protocol.attributes.name"
@ -48,72 +48,100 @@
</span>
</div>
<ProtocolMetadata v-if="protocol.attributes && protocol.attributes.in_repository" :protocol="protocol" @update="updateProtocol"/>
<div v-if="urls.update_protocol_description_url">
<Tinymce
:value="protocol.attributes.description"
:value_html="protocol.attributes.description_view"
:placeholder="i18n.t('my_modules.protocols.protocol_status_bar.empty_description_edit_label')"
:updateUrl="urls.update_protocol_description_url"
:objectType="'Protocol'"
:objectId="parseInt(protocol.id)"
:fieldName="'protocol[description]'"
:lastUpdated="protocol.attributes.updated_at"
:characterLimit="100000"
@update="updateDescription"
/>
</div>
<div v-else-if="protocol.attributes.description_view" v-html="protocol.attributes.description_view"></div>
<div v-else class="empty-protocol-description">
{{ i18n.t("protocols.no_text_placeholder") }}
</div>
</div>
<a v-if="urls.add_step_url && protocol.attributes.in_repository" class="btn btn-primary repository-new-step" @click="addStep(steps.length)">
<span class="fas fa-plus" aria-hidden="true"></span>
<span>{{ i18n.t("protocols.steps.new_step") }}</span>
</a>
<div v-if="steps.length > 0" class="protocol-step-actions">
<button class="btn btn-light" @click="collapseSteps" tabindex="0">
<span class="fas fa-caret-up"></span>
{{ i18n.t("protocols.steps.collapse_label") }}
</button>
<button class="btn btn-light" @click="expandSteps" tabindex="0">
<span class="fas fa-caret-down"></span>
{{ i18n.t("protocols.steps.expand_label") }}
</button>
<a v-if="urls.reorder_steps_url"
class="btn btn-light"
data-toggle="modal"
@click="startStepReorder"
@keyup.enter="startStepReorder"
:class="{'disabled': steps.length == 1}"
tabindex="0" >
<i class="fas fas-rotated-90 fa-exchange-alt" aria-hidden="true"></i>
<span>{{ i18n.t("protocols.reorder_steps.button") }}</span>
</a>
</div>
<div class="protocol-steps">
<template v-for="(step, index) in steps">
<div class="step-block" :key="step.id">
<div v-if="index > 0 && urls.add_step_url" class="insert-step" @click="addStep(index)">
<i class="fas fa-plus"></i>
<div :class="inRepository ? 'protocol-section protocol-information' : ''">
<div v-if="inRepository" id="protocol-description" class="protocol-section-header">
<div class="protocol-description-container">
<a class="protocol-section-caret" role="button" data-toggle="collapse" href="#protocol-description-container" aria-expanded="false" aria-controls="protocol-description-container">
<i class="fas fa-caret-right"></i>
<span id="protocolDescriptionLabel" class="protocol-section-title">
<h2>
{{ i18n.t("protocols.header.protocol_description") }}
</h2>
</span>
</a>
</div>
<Step
:step.sync="steps[index]"
@reorder="startStepReorder"
:inRepository="inRepository"
@step:delete="updateStepsPosition"
@step:update="updateStep"
@stepUpdated="refreshProtocolStatus"
@step:insert="updateStepsPosition"
:reorderStepUrl="steps.length > 1 ? urls.reorder_steps_url : null"
/>
</div>
</template>
<div id="protocol-description-container" :class=" inRepository ? 'protocol-description collapse in' : ''" >
<div v-if="urls.update_protocol_description_url">
<Tinymce
:value="protocol.attributes.description"
:value_html="protocol.attributes.description_view"
:placeholder="i18n.t('my_modules.protocols.protocol_status_bar.empty_description_edit_label')"
:updateUrl="urls.update_protocol_description_url"
:objectType="'Protocol'"
:objectId="parseInt(protocol.id)"
:fieldName="'protocol[description]'"
:lastUpdated="protocol.attributes.updated_at"
:characterLimit="100000"
@update="updateDescription"
/>
</div>
<div v-else-if="protocol.attributes.description_view" v-html="protocol.attributes.description_view"></div>
<div v-else class="empty-protocol-description">
{{ i18n.t("protocols.no_text_placeholder") }}
</div>
</div>
</div>
</div>
<div :class="inRepository ? 'protocol-section protocol-steps-section protocol-information' : ''">
<div v-if="inRepository" id="protocol-steps" class="protocol-section-header">
<div class="protocol-steps-container">
<a class="protocol-section-caret" role="button" data-toggle="collapse" href="#protocol-steps-container" aria-expanded="false" aria-controls="protocol-steps-container">
<i class="fas fa-caret-right"></i>
<span id="protocolStepsLabel" class="protocol-section-title">
<h2>
{{ i18n.t("protocols.header.protocol_steps") }}
</h2>
</span>
</a>
</div>
</div>
<div id="protocol-steps-container" :class=" inRepository ? 'protocol-steps collapse in' : ''">
<div v-if="steps.length > 0" class="protocol-step-actions">
<button class="btn btn-light" @click="collapseSteps" tabindex="0">
<span class="fas fa-caret-up"></span>
{{ i18n.t("protocols.steps.collapse_label") }}
</button>
<button class="btn btn-light" @click="expandSteps" tabindex="0">
<span class="fas fa-caret-down"></span>
{{ i18n.t("protocols.steps.expand_label") }}
</button>
<a v-if="urls.reorder_steps_url"
class="btn btn-light"
data-toggle="modal"
@click="startStepReorder"
@keyup.enter="startStepReorder"
:class="{'disabled': steps.length == 1}"
tabindex="0" >
<i class="fas fas-rotated-90 fa-exchange-alt" aria-hidden="true"></i>
<span>{{ i18n.t("protocols.reorder_steps.button") }}</span>
</a>
</div>
<div class="protocol-steps">
<template v-for="(step, index) in steps">
<div class="step-block" :key="step.id">
<div v-if="index > 0 && urls.add_step_url" class="insert-step" @click="addStep(index)">
<i class="fas fa-plus"></i>
</div>
<Step
:step.sync="steps[index]"
@reorder="startStepReorder"
:inRepository="inRepository"
@step:delete="updateStepsPosition"
@step:update="updateStep"
@stepUpdated="refreshProtocolStatus"
@step:insert="updateStepsPosition"
:reorderStepUrl="steps.length > 1 ? urls.reorder_steps_url : null"
/>
</div>
</template>
</div>
<button v-if="(steps.length > 0 || inRepository) && urls.add_step_url" :class="!inRepository ? 'btn btn-primary' : 'btn btn-secondary'" @click="addStep(steps.length)">
<i class="fas fa-plus"></i>
{{ i18n.t("protocols.steps.new_step") }}
</button>
</div>
</div>
<button v-if="steps.length > 0 && urls.add_step_url" class="btn btn-primary" @click="addStep(steps.length)">
<i class="fas fa-plus"></i>
{{ i18n.t("protocols.steps.new_step") }}
</button>
</div>
<ProtocolModals/>
<ReorderableItemsModal v-if="reordering"

View file

@ -1,55 +1,79 @@
<template>
<div class="protocol-metadata">
<p class="data-block">
<span class="fas block-icon fa-calendar-alt fa-fw"></span>
{{ i18n.t("protocols.header.created_at") }}
<b>{{ protocol.attributes.created_at_formatted }}</b>
</p>
<p class="data-block">
<span class="fas block-icon fa-user fa-fw"></span>
{{ i18n.t("protocols.header.added_by") }}
<img :src="protocol.attributes.added_by.avatar"/>
{{ protocol.attributes.added_by.name }}
</p>
<p class="data-block">
<span class="fas block-icon fa-edit fa-fw"></span>
{{ i18n.t("protocols.header.updated_at") }}
<b>{{ protocol.attributes.updated_at_formatted }}</b>
</p>
<p class="data-block authors-data">
<span class="fas block-icon fa-graduation-cap fa-fw"></span>
<span>{{ i18n.t("protocols.header.authors") }}</span>
<span class="authors-list" v-if="protocol.attributes.urls.update_protocol_authors_url">
<InlineEdit
:value="protocol.attributes.authors"
:placeholder="i18n.t('protocols.header.add_authors')"
:allowBlank="true"
:attributeName="`${i18n.t('Protocol')} ${i18n.t('authors')}`"
@update="updateAuthors"
/>
</span>
<span class="authors-list" v-else>
{{ protocol.attributes.authors }}
</span>
</p>
<p class="data-block keywords-data">
<span class="fas block-icon fa-font fa-fw"></span>
<span>{{ i18n.t("protocols.header.keywords") }}</span>
<span class="keywords-list">
<DropdownSelector
:inputTagMode="true"
:options="this.protocol.attributes.keywords"
:placeholder="i18n.t('protocols.header.add_keywords')"
:selectorId="'keywordsSelector'"
:singleSelect="false"
:closeOnSelect="false"
:noEmptyOption="false"
:selectAppearance="'tag'"
:viewMode="protocol.attributes.urls.update_protocol_keywords_url == null"
@dropdown:changed="updateKeywords"
/>
</span>
</p>
<div class="protocol-section protocol-information">
<div id="protocol-details" class="protocol-section-header">
<div class="protocol-details-container">
<a class="protocol-section-caret" role="button" data-toggle="collapse" href="#details-container" aria-expanded="false" aria-controls="details-container">
<i class="fas fa-caret-right"></i>
<span id="protocolDetailsLabel" class="protocol-section-title">
<h2>
{{ i18n.t("protocols.header.details") }}
</h2>
<span class="protocol-code" >{{ protocol.attributes.code }}</span>
</span>
</a>
</div>
<div class="actions-block">
<a class="btn btn-light icon-btn pull-right" data-toggle="modal" data-target="#print-protocol-modal" tabindex="0">
<span class="fas fa-print" aria-hidden="true"></span>
</a>
<button class="btn btn-light">{{ i18n.t("protocols.header.versions") }}</button>
<button v-if="!protocol.attributes.published" class="btn btn-primary">{{ i18n.t("protocols.header.publish") }}</button>
<button v-else class="btn btn-secondary">{{ i18n.t("protocols.header.save_as_draft") }}</button>
</div>
</div>
<div id="details-container" class="protocol-details collapse in">
<div class="protocol-metadata">
<p class="data-block">
{{ i18n.t("protocols.header.version") }}
<b>{{ protocol.attributes.version }}</b>
</p>
<p class="data-block">
{{ i18n.t("protocols.header.updated_at") }}
<b>{{ protocol.attributes.updated_at_formatted }}</b>
</p>
<p class="data-block">
{{ i18n.t("protocols.header.created_at") }}
<b>{{ protocol.attributes.created_at_formatted }}</b>
</p>
<p class="data-block">
{{ i18n.t("protocols.header.added_by") }}
<img :src="protocol.attributes.added_by.avatar"/>
{{ protocol.attributes.added_by.name }}
</p>
<p class="data-block authors-data">
<span>{{ i18n.t("protocols.header.authors") }}</span>
<span class="authors-list" v-if="protocol.attributes.urls.update_protocol_authors_url">
<InlineEdit
:value="protocol.attributes.authors"
:placeholder="i18n.t('protocols.header.add_authors')"
:allowBlank="true"
:attributeName="`${i18n.t('Protocol')} ${i18n.t('authors')}`"
@update="updateAuthors"
/>
</span>
<span class="authors-list" v-else>
{{ protocol.attributes.authors }}
</span>
</p>
<p class="data-block keywords-data">
<span>{{ i18n.t("protocols.header.keywords") }}</span>
<span class="keywords-list">
<DropdownSelector
:inputTagMode="true"
:options="this.protocol.attributes.keywords"
:placeholder="i18n.t('protocols.header.add_keywords')"
:selectorId="'keywordsSelector'"
:singleSelect="false"
:closeOnSelect="false"
:noEmptyOption="false"
:selectAppearance="'tag'"
:viewMode="protocol.attributes.urls.update_protocol_keywords_url == null"
@dropdown:changed="updateKeywords"
/>
</span>
</p>
</div>
</div>
</div>
</template>
<script>

View file

@ -86,7 +86,8 @@ Canaid::Permissions.register_for(Protocol) do
# protocol in repository: update, create/update/delete/reorder step,
# toggle private/public visibility, archive
can :manage_protocol_in_repository do |user, protocol|
protocol.permission_granted?(user, ProtocolPermissions::MANAGE)
protocol.protocol_type.in_repository_draft? &&
protocol.permission_granted?(user, ProtocolPermissions::MANAGE)
end
can :manage_protocol_draft_in_repository do |user, protocol|

View file

@ -7,12 +7,21 @@ class ProtocolSerializer < ActiveModel::Serializer
include ActionView::Helpers::TextHelper
attributes :name, :id, :urls, :description, :description_view, :updated_at, :in_repository,
:created_at_formatted, :updated_at_formatted, :added_by, :authors, :keywords
:created_at_formatted, :updated_at_formatted, :added_by, :authors, :keywords, :version, :code,
:published
def updated_at
object.updated_at.to_i
end
def version
object.in_repository_draft? ? I18n.t('protocols.draft') : object.version_number
end
def published
object.in_repository_published?
end
def added_by
{
avatar: object.added_by&.avatar_url(:icon_small),

View file

@ -2,14 +2,34 @@
<%= content_for :sidebar do %>
<%= render partial: "/shared/sidebar/templates_sidebar", locals: {active: :protocol} %>
<% end %>
<% provide(:container_class, 'no-second-nav-container') %>
<div class="content-pane protocols-show" >
<div class="protocol-position-container">
<div class="protocol-actions">
<a class="btn btn-secondary pull-right" href="<%= print_protocol_path(@protocol) %>" target="_blank" tabindex="0">
<span class="fas fa-print" aria-hidden="true"></span>
<span><%= t("protocols.print.button") %></span>
</a>
<div class="content-header">
<div id="breadcrumbsWrapper">
<div class="breadcrumbs-container">
<a href="<%= protocols_path %>" class="breadcrumbs-link">
<%= t('sidebar.templates.protocol_templates') %>
</a>
<span class="delimiter">/</span>
</div>
</div>
<div class="title-row">
<h1>
<% if @inline_editable_title_config.present? %>
<%= render partial: "shared/inline_editing",
locals: {
initial_value: @protocol.name,
config: @inline_editable_title_config
} %>
<% else %>
<div class="name-readonly-placeholder">
<%= @protocol.name %>
</div>
<% end %>
</h1>
</div>
</div>
<div class="protocol-position-container">
<div
id="protocolContainer"
data-protocol-url="<%= protocol_path(@protocol) %>"

View file

@ -2427,6 +2427,7 @@ en:
unchangable_error_message: "Predefined roles can not be changed!"
protocols:
draft: "Draft"
no_text_placeholder: 'No protocol description'
reorder_steps:
button: "Rearrange steps"
@ -2530,6 +2531,14 @@ en:
row_success: "Exported"
row_failed: "Failed"
header:
details: 'Details'
versions: 'Versions'
protocol_description: 'Protocol description'
protocol_steps: 'Protocol steps'
publish: 'Publish'
save_as_draft: 'Save as draft'
version: "Version:"
draft: 'Draft'
created_at: "Created at:"
updated_at: "Last modified at:"
added_by: "Added by:"

View file

@ -556,7 +556,7 @@ Rails.application.routes.draw do
get 'versions_modal', to: 'protocols#versions_modal'
get 'preview', to: 'protocols#preview'
patch 'description', to: 'protocols#update_description'
patch 'name', to: 'protocols#update_name'
put 'name', to: 'protocols#update_name'
patch 'authors', to: 'protocols#update_authors'
patch 'keywords', to: 'protocols#update_keywords'
post 'clone', to: 'protocols#clone'