mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-01-27 01:59:28 +08:00
Add insert step element dropdown [SCI-6758]
This commit is contained in:
parent
2515ed5a32
commit
6a827022f6
21 changed files with 300 additions and 11 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
17
app/controllers/step_components/checklists_controller.rb
Normal file
17
app/controllers/step_components/checklists_controller.rb
Normal 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
|
20
app/controllers/step_components/tables_controller.rb
Normal file
20
app/controllers/step_components/tables_controller.rb
Normal 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
|
15
app/controllers/step_components/texts_controller.rb
Normal file
15
app/controllers/step_components/texts_controller.rb
Normal 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
|
35
app/controllers/step_orderable_elements_controller.rb
Normal file
35
app/controllers/step_orderable_elements_controller.rb
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
11
app/javascript/vue/protocol/step_components/checklist.vue
Normal file
11
app/javascript/vue/protocol/step_components/checklist.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div class="step-checklist-container">
|
||||
Checklist
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Checklist'
|
||||
}
|
||||
</script>
|
11
app/javascript/vue/protocol/step_components/table.vue
Normal file
11
app/javascript/vue/protocol/step_components/table.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div class="step-table-container">
|
||||
Table
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'StepTable'
|
||||
}
|
||||
</script>
|
11
app/javascript/vue/protocol/step_components/text.vue
Normal file
11
app/javascript/vue/protocol/step_components/text.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div class="step-text-container">
|
||||
Text
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'StepText'
|
||||
}
|
||||
</script>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
5
app/serializers/checklist_serializer.rb
Normal file
5
app/serializers/checklist_serializer.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChecklistSerializer < ActiveModel::Serializer
|
||||
attributes :name
|
||||
end
|
16
app/serializers/step_orderable_element_serializer.rb
Normal file
16
app/serializers/step_orderable_element_serializer.rb
Normal 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
|
|
@ -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
|
||||
|
|
5
app/serializers/step_table_serializer.rb
Normal file
5
app/serializers/step_table_serializer.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StepTableSerializer < ActiveModel::Serializer
|
||||
attributes :name
|
||||
end
|
5
app/serializers/step_text_serializer.rb
Normal file
5
app/serializers/step_text_serializer.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StepTextSerializer < ActiveModel::Serializer
|
||||
attributes :text
|
||||
end
|
|
@ -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"
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue