From e9174ad15893b09b230e2411ce2bf35c2ea981b6 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 21 Aug 2023 09:22:56 +0200 Subject: [PATCH] Add element movements between step [SCI-9024] --- .../result_elements/base_controller.rb | 4 + .../result_elements/tables_controller.rb | 15 +++- .../result_elements/texts_controller.rb | 14 +++- app/controllers/results_controller.rb | 2 - .../step_elements/base_controller.rb | 5 ++ .../step_elements/checklists_controller.rb | 14 +++- .../step_elements/tables_controller.rb | 15 +++- .../step_elements/texts_controller.rb | 14 +++- app/javascript/packs/custom_axios.js | 22 +++++ app/javascript/packs/vue/protocol.js | 2 + app/javascript/packs/vue/results.js | 2 + app/javascript/vue/protocol/container.vue | 9 +- app/javascript/vue/protocol/step.vue | 26 +++++- app/javascript/vue/results/result.vue | 25 +++++- app/javascript/vue/results/results.vue | 15 +++- .../vue/shared/content/checklist.vue | 13 ++- .../vue/shared/content/mixins/move.js | 25 ++++++ .../vue/shared/content/modal/move.vue | 83 +++++++++++++++++++ app/javascript/vue/shared/content/table.vue | 13 ++- app/javascript/vue/shared/content/text.vue | 13 ++- app/models/result.rb | 6 ++ app/models/step.rb | 6 ++ app/models/step_orderable_element.rb | 8 +- app/serializers/checklist_serializer.rb | 10 ++- app/serializers/result_table_serializer.rb | 13 +-- app/serializers/result_text_serializer.rb | 10 ++- app/serializers/step_text_serializer.rb | 10 ++- app/serializers/table_serializer.rb | 10 ++- config/locales/en.yml | 7 ++ config/routes.rb | 10 +++ 30 files changed, 381 insertions(+), 40 deletions(-) create mode 100644 app/javascript/packs/custom_axios.js create mode 100644 app/javascript/vue/shared/content/mixins/move.js create mode 100644 app/javascript/vue/shared/content/modal/move.vue diff --git a/app/controllers/result_elements/base_controller.rb b/app/controllers/result_elements/base_controller.rb index 5ed0694f8..e3b72ef13 100644 --- a/app/controllers/result_elements/base_controller.rb +++ b/app/controllers/result_elements/base_controller.rb @@ -5,6 +5,10 @@ module ResultElements before_action :load_result_and_my_module before_action :check_manage_permissions + def move_targets + render json: { targets: @my_module.results.where.not(id: @result.id).map{ |i| [i.id, i.name] } } + end + private def load_result_and_my_module diff --git a/app/controllers/result_elements/tables_controller.rb b/app/controllers/result_elements/tables_controller.rb index 09b3d247f..691b284af 100644 --- a/app/controllers/result_elements/tables_controller.rb +++ b/app/controllers/result_elements/tables_controller.rb @@ -2,7 +2,7 @@ module ResultElements class TablesController < BaseController - before_action :load_table, only: %i(update destroy duplicate) + before_action :load_table, only: %i(update destroy duplicate move) def create predefined_table_dimensions = create_table_params[:tableDimensions].map(&:to_i) @@ -57,6 +57,19 @@ module ResultElements head :unprocessable_entity end + def move + target = @my_module.results.find_by(id: params[:target_id]) + result_table = @table.result_table + ActiveRecord::Base.transaction do + result_table.update!(result: target) + result_table.result_orderable_element.update!(result: target, position: target.result_orderable_elements.size) + @result.normalize_elements_position + render json: @table, serializer: ResultTableSerializer, user: current_user + rescue ActiveRecord::RecordInvalid + render json: result_table.errors, status: :unprocessable_entity + end + end + def destroy if @table.destroy #log_step_activity(:table_deleted, { table_name: @table.name }) diff --git a/app/controllers/result_elements/texts_controller.rb b/app/controllers/result_elements/texts_controller.rb index 09f75c7d3..5e3b54f87 100644 --- a/app/controllers/result_elements/texts_controller.rb +++ b/app/controllers/result_elements/texts_controller.rb @@ -7,7 +7,7 @@ module ResultElements include InputSanitizeHelper include Rails.application.routes.url_helpers - before_action :load_result_text, only: %i(update destroy duplicate) + before_action :load_result_text, only: %i(update destroy duplicate move) def create result_text = @result.result_texts.build @@ -36,6 +36,18 @@ module ResultElements render json: @result_text.errors, status: :unprocessable_entity end + def move + target = @my_module.results.find_by(id: params[:target_id]) + ActiveRecord::Base.transaction do + @result_text.update!(result: target) + @result_text.result_orderable_element.update!(result: target, position: target.result_orderable_elements.size) + @result.normalize_elements_position + render json: @result_text, serializer: ResultTextSerializer, user: current_user + rescue ActiveRecord::RecordInvalid + render json: @result_text.errors, status: :unprocessable_entity + end + end + def destroy if @result_text.destroy log_step_activity(:text_deleted, { text_name: @result_text.name }) diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index 5bdbdae80..c953ce15a 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class ResultsController < ApplicationController - skip_before_action :verify_authenticity_token, only: %i(create update destroy) - before_action :load_my_module before_action :load_vars, only: %i(destroy elements assets upload_attachment update_view_state update_asset_view_mode update) before_action :check_destroy_permissions, only: :destroy diff --git a/app/controllers/step_elements/base_controller.rb b/app/controllers/step_elements/base_controller.rb index 4ceed0c1d..37595157d 100644 --- a/app/controllers/step_elements/base_controller.rb +++ b/app/controllers/step_elements/base_controller.rb @@ -5,6 +5,11 @@ module StepElements before_action :load_step_and_protocol before_action :check_manage_permissions + + def move_targets + render json: { targets: @protocol.steps.order(:position).where.not(id: @step.id).map{|i| [i.id, i.name] } } + end + private def load_step_and_protocol diff --git a/app/controllers/step_elements/checklists_controller.rb b/app/controllers/step_elements/checklists_controller.rb index aa6ee30e8..54bc8705b 100644 --- a/app/controllers/step_elements/checklists_controller.rb +++ b/app/controllers/step_elements/checklists_controller.rb @@ -4,7 +4,7 @@ module StepElements class ChecklistsController < BaseController include ApplicationHelper include StepsActions - before_action :load_checklist, only: %i(update destroy duplicate) + before_action :load_checklist, only: %i(update destroy duplicate move) def create checklist = @step.checklists.build( name: t('protocols.steps.checklist.default_name', position: @step.checklists.length + 1) @@ -32,6 +32,18 @@ module StepElements head :unprocessable_entity end + def move + target = @protocol.steps.find_by(id: params[:target_id]) + ActiveRecord::Base.transaction do + @checklist.update!(step: target) + @checklist.step_orderable_element.update!(step: target, position: target.step_orderable_elements.size) + @step.normalize_elements_position + render json: @checklist, serializer: ChecklistSerializer, user: current_user + rescue ActiveRecord::RecordInvalid + render json: @checklist.errors, status: :unprocessable_entity + end + end + def destroy if @checklist.destroy log_step_activity(:checklist_deleted, { checklist_name: @checklist.name }) diff --git a/app/controllers/step_elements/tables_controller.rb b/app/controllers/step_elements/tables_controller.rb index 167acc604..29bcc8d79 100644 --- a/app/controllers/step_elements/tables_controller.rb +++ b/app/controllers/step_elements/tables_controller.rb @@ -2,7 +2,7 @@ module StepElements class TablesController < BaseController - before_action :load_table, only: %i(update destroy duplicate) + before_action :load_table, only: %i(update destroy duplicate move) def create predefined_table_dimensions = create_table_params[:tableDimensions].map(&:to_i) @@ -57,6 +57,19 @@ module StepElements head :unprocessable_entity end + def move + target = @protocol.steps.find_by(id: params[:target_id]) + step_table = @table.step_table + ActiveRecord::Base.transaction do + step_table.update!(step: target) + step_table.step_orderable_element.update!(step: target, position: target.step_orderable_elements.size) + @step.normalize_elements_position + render json: @table, serializer: TableSerializer, user: current_user + rescue ActiveRecord::RecordInvalid + render json: step_table.errors, status: :unprocessable_entity + end + end + def destroy if @table.destroy log_step_activity(:table_deleted, { table_name: @table.name }) diff --git a/app/controllers/step_elements/texts_controller.rb b/app/controllers/step_elements/texts_controller.rb index 7377a3782..1a988a09e 100644 --- a/app/controllers/step_elements/texts_controller.rb +++ b/app/controllers/step_elements/texts_controller.rb @@ -5,7 +5,7 @@ module StepElements include ApplicationHelper include StepsActions - before_action :load_step_text, only: %i(update destroy duplicate) + before_action :load_step_text, only: %i(update destroy duplicate move) def create step_text = @step.step_texts.build @@ -34,6 +34,18 @@ module StepElements render json: @step_text.errors, status: :unprocessable_entity end + def move + target = @protocol.steps.find_by(id: params[:target_id]) + ActiveRecord::Base.transaction do + @step_text.update!(step: target) + @step_text.step_orderable_element.update!(step: target, position: target.step_orderable_elements.size) + @step.normalize_elements_position + render json: @step_text, serializer: StepTextSerializer, user: current_user + rescue ActiveRecord::RecordInvalid + render json: @step_text.errors, status: :unprocessable_entity + end + end + def destroy if @step_text.destroy log_step_activity(:text_deleted, { text_name: @step_text.name }) diff --git a/app/javascript/packs/custom_axios.js b/app/javascript/packs/custom_axios.js new file mode 100644 index 000000000..368fb8f50 --- /dev/null +++ b/app/javascript/packs/custom_axios.js @@ -0,0 +1,22 @@ +import axios from "axios"; + +const instance = axios.create({ + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, +}); + +instance.interceptors.request.use( + function (config) { + const csrfToken = document.querySelector('[name=csrf-token]').content; + config.headers["X-CSRF-Token"] = csrfToken; + + return config; + }, + function (error) { + return Promise.reject(error); + } +); + +export default instance; diff --git a/app/javascript/packs/vue/protocol.js b/app/javascript/packs/vue/protocol.js index 0edee59d7..ee4063cf2 100644 --- a/app/javascript/packs/vue/protocol.js +++ b/app/javascript/packs/vue/protocol.js @@ -3,7 +3,9 @@ import TurbolinksAdapter from 'vue-turbolinks'; import Vue from 'vue/dist/vue.esm'; import ProtocolContainer from '../../vue/protocol/container.vue'; +import PerfectScrollbar from 'vue2-perfect-scrollbar'; +Vue.use(PerfectScrollbar); Vue.use(TurbolinksAdapter); Vue.prototype.i18n = window.I18n; Vue.prototype.inlineEditing = window.inlineEditing; diff --git a/app/javascript/packs/vue/results.js b/app/javascript/packs/vue/results.js index 4ee45cd90..e9f92ede5 100644 --- a/app/javascript/packs/vue/results.js +++ b/app/javascript/packs/vue/results.js @@ -1,7 +1,9 @@ import TurbolinksAdapter from 'vue-turbolinks'; import Vue from 'vue/dist/vue.esm'; import Results from '../../vue/results/results.vue'; +import PerfectScrollbar from 'vue2-perfect-scrollbar'; +Vue.use(PerfectScrollbar); Vue.use(TurbolinksAdapter); Vue.prototype.i18n = window.I18n; Vue.prototype.ActiveStoragePreviews = window.ActiveStoragePreviews; diff --git a/app/javascript/vue/protocol/container.vue b/app/javascript/vue/protocol/container.vue index 59fcd179a..55448d123 100644 --- a/app/javascript/vue/protocol/container.vue +++ b/app/javascript/vue/protocol/container.vue @@ -129,10 +129,13 @@ :step.sync="steps[index]" @reorder="startStepReorder" :inRepository="inRepository" + :stepToReload="stepToReload" @step:delete="updateStepsPosition" @step:update="updateStep" @stepUpdated="refreshProtocolStatus" @step:insert="updateStepsPosition" + @step:elements:loaded="stepToReload = null" + @step:move_element="reloadStep" :reorderStepUrl="steps.length > 1 ? urls.reorder_steps_url : null" :assignableMyModuleId="protocol.attributes.assignable_my_module_id" /> @@ -200,7 +203,8 @@ }, steps: [], reordering: false, - publishing: false + publishing: false, + stepToReload: null, } }, created() { @@ -215,6 +219,9 @@ }); }, methods: { + reloadStep(step) { + this.stepToReload = step; + }, collapseSteps() { $('.step-container .collapse').collapse('hide') }, diff --git a/app/javascript/vue/protocol/step.vue b/app/javascript/vue/protocol/step.vue index d40210e3d..b7bd05c65 100644 --- a/app/javascript/vue/protocol/step.vue +++ b/app/javascript/vue/protocol/step.vue @@ -175,6 +175,7 @@ @update="updateElement" @reorder="openReorderModal" @component:insert="insertElement" + @moved="moveElement" /> { this.elements = result.data + this.$emit('step:elements:loaded'); }); }, showStorageUsage() { @@ -522,6 +535,17 @@ }) this.elements.push(element); }, + 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('stepUpdated') + this.$emit('step:move_element', target_id) + }, duplicateStep() { $.post(this.urls.duplicate_step_url, (result) => { this.$emit('step:insert', result.data); diff --git a/app/javascript/vue/results/result.vue b/app/javascript/vue/results/result.vue index 18b105fb4..1a6233dc0 100644 --- a/app/javascript/vue/results/result.vue +++ b/app/javascript/vue/results/result.vue @@ -138,6 +138,7 @@ @update="updateElement" @reorder="openReorderModal" @component:insert="insertElement" + @moved="moveElement" /> diff --git a/app/javascript/vue/shared/content/table.vue b/app/javascript/vue/shared/content/table.vue index 3b38ef358..4db39ad43 100644 --- a/app/javascript/vue/shared/content/table.vue +++ b/app/javascript/vue/shared/content/table.vue @@ -28,6 +28,9 @@ + @@ -59,20 +62,26 @@ +