Add insert step element dropdown [SCI-6758]

This commit is contained in:
Anton 2022-04-29 12:29:42 +02:00
parent 2515ed5a32
commit 6a827022f6
21 changed files with 300 additions and 11 deletions

View file

@ -53,6 +53,35 @@
.step-actions-container {
display: flex;
justify-content: flex-end;
.insert-button {
.caret {
margin-left: .5em;
}
}
.insert-element-dropdown {
@include font-button;
padding: 0;
li {
padding: .5em 1em;
&.action {
cursor: pointer;
&:hover {
background: $color-concrete;
}
}
&.title {
@include font-small;
color: $color-alto;
text-transform: uppercase;
}
}
}
}
}
}

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module StepComponents
class ChecklistsController < StepOrderableElementsController
private
def create_step_element
@step.checklists.create!(
name: t('protocols.steps.checklist.default_name', position: @step.step_tables.length + 1)
)
end
def element_params
params.require(:checklist).permit(:name)
end
end
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
module StepComponents
class TablesController < StepOrderableElementsController
private
def create_step_element
@step.step_tables.create!(table:
Table.create!(
name: t('protocols.steps.table.default_name', position: @step.step_tables.length + 1),
contents: '{"data":[["",""],["",""],["",""],["",""],["",""]]}',
created_by: current_user
))
end
def element_params
params.require(:table).permit(:name, :contents)
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
module StepComponents
class TextsController < StepOrderableElementsController
private
def create_step_element
@step.step_texts.create!
end
def element_params
params.require(:step_text).permit(:text)
end
end
end

View file

@ -0,0 +1,35 @@
# frozen_string_literal: true
class StepOrderableElementsController < ApplicationController
before_action :load_vars_nested
before_action :check_manage_permissions, only: :create
def create
ActiveRecord::Base.transaction do
element = @step.step_orderable_elements.create!(
position: @step.step_orderable_elements.length,
orderable: create_step_element
)
render json: element, serializer: StepOrderableElementSerializer
rescue ActiveRecord::RecordInvalid
render json: {}, status: :unprocessable_entity
end
end
private
def load_vars_nested
@step = Step.find_by(id: params[:step_id])
return render_404 unless @step
@protocol = @step.protocol
end
def check_view_permissions
render_403 unless can_read_protocol_in_module?(@protocol) || can_read_protocol_in_repository?(@protocol)
end
def check_manage_permissions
render_403 unless can_manage_step?(@step)
end
end

View file

@ -5,7 +5,7 @@ class StepsController < ApplicationController
include MarvinJsActions
before_action :load_vars, only: %i(edit update destroy show toggle_step_state checklistitem_state update_view_state
move_up move_down update_asset_view_mode)
move_up move_down update_asset_view_mode elements)
before_action :load_vars_nested, only: %i(new create index)
before_action :convert_table_contents_to_utf8, only: %i(create update)
@ -19,6 +19,10 @@ class StepsController < ApplicationController
render json: @protocol.steps.in_order, each_serializer: StepSerializer
end
def elements
render json: @step.step_orderable_elements.order(:position), each_serializer: StepOrderableElementSerializer
end
def new
@step = Step.new

View file

@ -14,7 +14,7 @@
</span>
</a>
<div class="sci-btn-group actions-block">
<a class="btn btn-primary" @click="addStep(steps.length - 1)">
<a class="btn btn-primary" @click="addStep(steps.length)">
<span class="fas fa-plus" aria-hidden="true"></span>
<span>New step</span>
</a>
@ -166,8 +166,8 @@
this.reorderSteps(unordered_steps)
},
updateStep(step) {
this.$set(this.steps, step.attributes.position, step)
updateStep(attributes) {
this.steps[attributes.position].attributes = attributes
},
reorderSteps(steps) {
this.steps = steps.sort((a, b) => a.attributes.position - b.attributes.position);

View file

@ -23,19 +23,50 @@
/>
</div>
<div class="step-actions-container">
<div class="dropdown">
<button class="btn btn-light dropdown-toggle insert-button" type="button" :id="'stepInserMenu_' + step.id" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{{ i18n.t('protocols.steps.insert.button') }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu insert-element-dropdown" :aria-labelledby="'stepInserMenu_' + step.id">
<li class="title">
{{ i18n.t('protocols.steps.insert.title') }}
</li>
<li class="action" @click="createElement('table')">
<i class="fas fa-table"></i>
{{ i18n.t('protocols.steps.insert.table') }}
</li>
<li class="action" @click="createElement('checklist')">
<i class="fas fa-list"></i>
{{ i18n.t('protocols.steps.insert.checklist') }}
</li>
<li class="action" @click="createElement('text')">
<i class="fas fa-font"></i>
{{ i18n.t('protocols.steps.insert.text') }}
</li>
</ul>
</div>
<button class="btn icon-btn btn-light" @click="deleteStep">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="collapse in" :id="'stepBody' + step.id">
Components here
<template v-for="(element, index) in elements">
<component
:is="elements[index].attributes.orderable_type"
:key="index"
:element.sync="elements[index]"/>
</template>
</div>
</div>
</template>
<script>
import InlineEdit from 'vue/shared/inline_edit.vue'
import StepTable from 'vue/protocol/step_components/table.vue'
import StepText from 'vue/protocol/step_components/text.vue'
import Checklist from 'vue/protocol/step_components/checklist.vue'
export default {
name: 'StepContainer',
@ -45,7 +76,22 @@
required: true
}
},
components: { InlineEdit },
data() {
return {
elements: []
}
},
components: {
InlineEdit,
StepTable,
StepText,
Checklist
},
created() {
$.get(this.step.attributes.urls.elements_url, (result) => {
this.elements = result.data
});
},
methods: {
deleteStep() {
$.ajax({
@ -62,7 +108,7 @@
},
changeState() {
$.post(this.step.attributes.urls.state_url, {completed: !this.step.attributes.completed}, (result) => {
this.$emit('step:update', result.data)
this.$emit('step:update', result.data.attributes)
})
},
updateName(newName) {
@ -71,9 +117,16 @@
type: 'PATCH',
data: {step: {name: newName}},
success: (result) => {
this.$emit('step:update', result.data)
this.$emit('step:update', result.data.attributes)
}
});
},
createElement(elementType) {
$.post(this.step.attributes.urls[`create_${elementType}_url`], (result) => {
this.elements.push(result.data)
}).error(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
})
}
}
}

View file

@ -0,0 +1,11 @@
<template>
<div class="step-checklist-container">
Checklist
</div>
</template>
<script>
export default {
name: 'Checklist'
}
</script>

View file

@ -0,0 +1,11 @@
<template>
<div class="step-table-container">
Table
</div>
</template>
<script>
export default {
name: 'StepTable'
}
</script>

View file

@ -0,0 +1,11 @@
<template>
<div class="step-text-container">
Text
</div>
</template>
<script>
export default {
name: 'StepText'
}
</script>

View file

@ -6,7 +6,7 @@ class StepOrderableElement < ApplicationRecord
around_destroy :decrement_following_elements_positions
belongs_to :step, inverse_of: :step_oerderable_elements, touch: true
belongs_to :step, inverse_of: :step_orderable_elements, touch: true
belongs_to :orderable, polymorphic: true, inverse_of: :step_orderable_elements
private

View file

@ -4,7 +4,6 @@ class StepText < ApplicationRecord
include TinyMceImages
auto_strip_attributes :text, nullify: false
validates :text, presence: true
validates :text, length: { maximum: Constants::RICH_TEXT_MAX_LENGTH }
belongs_to :step, inverse_of: :step_texts, touch: true

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class ChecklistSerializer < ActiveModel::Serializer
attributes :name
end

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
class StepOrderableElementSerializer < ActiveModel::Serializer
attributes :position, :element, :orderable_type
def element
case object.orderable_type
when 'Checklist'
ChecklistSerializer
when 'StepTable'
StepTableSerializer
when 'StepText'
StepTextSerializer
end
end
end

View file

@ -7,7 +7,11 @@ class StepSerializer < ActiveModel::Serializer
{
delete_url: step_path(object),
state_url: toggle_step_state_step_path(object),
update_url: step_path(object)
update_url: step_path(object),
elements_url: elements_step_path(object),
create_table_url: step_tables_path(object),
create_text_url: step_texts_path(object),
create_checklist_url: step_checklists_path(object)
}
end
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class StepTableSerializer < ActiveModel::Serializer
attributes :name
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class StepTextSerializer < ActiveModel::Serializer
attributes :text
end

View file

@ -2512,6 +2512,17 @@ en:
comments: "Comments"
empty_checklist: "No items"
comment_title: "%{user} at %{time}:"
insert:
button: 'Insert'
title: 'insert content'
table: 'Add table'
text: 'Add text'
checklist: 'Add checklist'
table:
default_name: 'Table %{position}'
checklist:
default_name: 'Checklist %{position}'
options:
up_arrow_title: "Move step up"
down_arrow_title: "Move step down"

View file

@ -448,7 +448,12 @@ Rails.application.routes.draw do
resources :step_comments,
path: '/comments',
only: %i(create index update destroy)
resources :tables, controller: 'step_components/tables', only: :create
resources :texts, controller: 'step_components/texts', only: :create
resources :checklists, controller: 'step_components/checklists', only: :create
member do
get 'elements'
post 'checklistitem_state'
post 'toggle_step_state'
put 'move_down'

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
require File.expand_path('app/helpers/database_helper')
class GenerateStepOrderableRelation < ActiveRecord::Migration[6.1]
include DatabaseHelper
def up
Step.find_in_batches(batch_size: 100) do |steps|
steps.each do |step|
position = 0
orderable_elements = []
step.step_texts.each do |text|
orderable_elements << step.step_orderable_elements.new(orderable: text, position: position)
position += 1
end
step.step_tables.each do |table|
orderable_elements << step.step_orderable_elements.new(orderable: table, position: position)
position += 1
end
step.checklists.each do |checklist|
orderable_elements << step.step_orderable_elements.new(orderable: checklist, position: position)
position += 1
end
StepOrderableElement.import(orderable_elements)
end
end
end
def down
StepOrderableElement.destroy_all
end
end