diff --git a/app/assets/stylesheets/navigation/general.scss b/app/assets/stylesheets/navigation/general.scss index 97d6518f4..ea5f162f9 100644 --- a/app/assets/stylesheets/navigation/general.scss +++ b/app/assets/stylesheets/navigation/general.scss @@ -51,9 +51,6 @@ body.navigator-collapsed { .sci--layout-navigation-top { grid-area: top; height: var(--top-navigation-height); - position: sticky; - position: -webkit-sticky; - top: 0; width: 100%; z-index: 610; } @@ -69,13 +66,14 @@ body.navigator-collapsed { } .sci--layout-navigation-navigator { + --navigator-top-margin: 0px; background-color: var(--sn-white); grid-area: navigator; - height: calc(100vh - var(--top-navigation-height) - var(--breadcrumbs-navigation-height) - 1rem); + height: calc(100vh - var(--navbar-height) - 1rem - var(--navigator-top-margin)); position: sticky; position: -webkit-sticky; - top: calc(var(--top-navigation-height) + var(--breadcrumbs-navigation-height)); - transition: .4s $timing-function-sharp; + top: 1rem; + transition: width .4s $timing-function-sharp; width: var(--navigator-navigation-width); z-index: 550; } @@ -84,9 +82,6 @@ body.navigator-collapsed { background-color: var(--sn-white); grid-area: breadcrumbs; height: var(--breadcrumbs-navigation-height); - position: sticky; - position: -webkit-sticky; - top: var(--top-navigation-height); transition: .4s $timing-function-sharp; width: 100%; z-index: 600; @@ -104,6 +99,8 @@ body.navigator-collapsed { } &[data-grey-background="true"] { + background-color: var(--sn-super-light-grey); + .sci--layout-content, .sci--layout-navigation-breadcrumbs, .sci--layout-navigation-navigator { diff --git a/app/assets/stylesheets/shared/content_pane.scss b/app/assets/stylesheets/shared/content_pane.scss index 663b8159d..d59380123 100644 --- a/app/assets/stylesheets/shared/content_pane.scss +++ b/app/assets/stylesheets/shared/content_pane.scss @@ -19,7 +19,7 @@ background-color: inherit; position: sticky; position: -webkit-sticky; - top: var(--navbar-height); + top: 0; z-index: 250; } 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/result_oderable_elements_controller.rb b/app/controllers/result_orderable_elements_controller.rb similarity index 100% rename from app/controllers/result_oderable_elements_controller.rb rename to app/controllers/result_orderable_elements_controller.rb diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index 8b05fd9d5..a5491ed3d 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/application.js b/app/javascript/packs/application.js index fb8434ead..20869421a 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -37,6 +37,24 @@ $(document).on('click', '.btn', function() { $(this).blur(); }); +$(document).on('turbolinks:load', () => { + $(window).on('scroll', () => { + const navbarHeight = 116; + let scrollPosition = $(window).scrollTop(); + if (scrollPosition > navbarHeight) { + scrollPosition = navbarHeight; + } + $('.sci--layout').css( + '--navbar-height', + `calc(var(--top-navigation-height) + var(--breadcrumbs-navigation-height) - ${scrollPosition}px)` + ); + $('.sci--layout-navigation-navigator').css( + '--navigator-top-margin', + ((scrollPosition / navbarHeight) * 16) + 'px' + ); + }); +}); + // Needed to support Turbolinks redirect_to responses as unsafe-inline is blocked by the CSP $.ajaxSetup({ converters: { 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 @@ +