mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Add element movements between step [SCI-9024]
This commit is contained in:
parent
0780ed17b0
commit
e9174ad158
|
@ -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
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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 })
|
||||
|
|
22
app/javascript/packs/custom_axios.js
Normal file
22
app/javascript/packs/custom_axios.js
Normal file
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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')
|
||||
},
|
||||
|
|
|
@ -175,6 +175,7 @@
|
|||
@update="updateElement"
|
||||
@reorder="openReorderModal"
|
||||
@component:insert="insertElement"
|
||||
@moved="moveElement"
|
||||
/>
|
||||
</template>
|
||||
<Attachments v-if="attachments.length"
|
||||
|
@ -243,7 +244,11 @@
|
|||
assignableMyModuleId: {
|
||||
type: Number,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
stepToReload: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -284,6 +289,13 @@
|
|||
this.loadAttachments();
|
||||
this.loadElements();
|
||||
},
|
||||
watch: {
|
||||
stepToReload() {
|
||||
if (this.stepToReload == this.step.id) {
|
||||
this.loadElements();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
$(this.$refs.comments).data('closeCallback', this.closeCommentsSidebar);
|
||||
$(this.$refs.comments).data('openCallback', this.closeCommentsSidebar);
|
||||
|
@ -335,6 +347,7 @@
|
|||
loadElements() {
|
||||
$.get(this.urls.elements_url, (result) => {
|
||||
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);
|
||||
|
|
|
@ -138,6 +138,7 @@
|
|||
@update="updateElement"
|
||||
@reorder="openReorderModal"
|
||||
@component:insert="insertElement"
|
||||
@moved="moveElement"
|
||||
/>
|
||||
</template>
|
||||
<Attachments v-if="attachments.length"
|
||||
|
@ -156,7 +157,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue';
|
||||
import ResultTable from '../shared/content/table.vue';
|
||||
import ResultText from '../shared/content/text.vue';
|
||||
|
@ -170,7 +171,8 @@
|
|||
export default {
|
||||
name: 'Results',
|
||||
props: {
|
||||
result: { type: Object, required: true }
|
||||
result: { type: Object, required: true },
|
||||
resultToReload: { type: Number, required: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -198,6 +200,13 @@
|
|||
Attachments,
|
||||
InlineEdit
|
||||
},
|
||||
watch: {
|
||||
resultToReload() {
|
||||
if (this.resultToReload == this.result.id) {
|
||||
this.loadElements();
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
reorderableElements() {
|
||||
return this.orderedElements.map((e) => { return { id: e.id, attributes: e.attributes.orderable } })
|
||||
|
@ -294,6 +303,7 @@
|
|||
loadElements() {
|
||||
$.get(this.urls.elements_url, (result) => {
|
||||
this.elements = result.data
|
||||
this.$emit('result:elements:loaded');
|
||||
});
|
||||
},
|
||||
loadAttachments() {
|
||||
|
@ -340,6 +350,17 @@
|
|||
},
|
||||
duplicateResult() {
|
||||
|
||||
},
|
||||
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('resultUpdated')
|
||||
this.$emit('result:move_element', target_id)
|
||||
},
|
||||
updateName(name) {
|
||||
axios.patch(this.urls.update_url, { result: { name: name } }, (_) => {
|
||||
|
|
|
@ -2,13 +2,18 @@
|
|||
<div class="results-wrapper">
|
||||
<ResultsToolbar :sort="sort" @setSort="setSort" @newResult="createResult" @expandAll="expandAll" @collapseAll="collapseAll" class="mb-3" />
|
||||
<div class="results-list">
|
||||
<Result v-for="result in results" :key="result.id" :result="result" />
|
||||
<Result v-for="result in results" :key="result.id"
|
||||
:result="result"
|
||||
:resultToReload="resultToReload"
|
||||
@result:elements:loaded="resultToReload = null"
|
||||
@result:move_element="reloadResult"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
import ResultsToolbar from './results_toolbar.vue';
|
||||
import Result from './result.vue';
|
||||
|
||||
|
@ -21,13 +26,17 @@
|
|||
data() {
|
||||
return {
|
||||
results: [],
|
||||
sort: 'created_at_desc'
|
||||
sort: 'created_at_desc',
|
||||
resultToReload: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadResults();
|
||||
},
|
||||
methods: {
|
||||
reloadResult(result) {
|
||||
this.resultToReload = result;
|
||||
},
|
||||
loadResults() {
|
||||
axios.get(
|
||||
`${this.url}?sort=${this.sort}`,
|
||||
|
|
|
@ -29,6 +29,9 @@
|
|||
<button v-if="element.attributes.orderable.urls.duplicate_url" class="btn icon-btn btn-light btn-sm" tabindex="0" @click="duplicateElement">
|
||||
<i class="sn-icon sn-icon-duplicate"></i>
|
||||
</button>
|
||||
<button v-if="element.attributes.orderable.urls.move_targets_url" class="btn btn-light btn-sm" tabindex="0" @click="showMoveModal">
|
||||
Move
|
||||
</button>
|
||||
<button v-if="element.attributes.orderable.urls.delete_url" class="btn icon-btn btn-light btn-sm" @click="showDeleteModal" tabindex="0">
|
||||
<i class="sn-icon sn-icon-delete"></i>
|
||||
</button>
|
||||
|
@ -76,21 +79,27 @@
|
|||
{{ i18n.t("protocols.steps.checklist.empty_checklist") }}
|
||||
</div>
|
||||
<deleteElementModal v-if="confirmingDelete" @confirm="deleteElement" @cancel="closeDeleteModal"/>
|
||||
<moveElementModal v-if="movingElement"
|
||||
:parent_type="element.attributes.orderable.parent_type"
|
||||
:targets_url="element.attributes.orderable.urls.move_targets_url"
|
||||
@confirm="moveElement($event)" @cancel="closeMoveModal"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DeleteMixin from './mixins/delete.js'
|
||||
import MoveMixin from './mixins/move.js'
|
||||
import DuplicateMixin from './mixins/duplicate.js'
|
||||
import deleteElementModal from './modal/delete.vue'
|
||||
import InlineEdit from '../inline_edit.vue'
|
||||
import ChecklistItem from './checklistItem.vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
import moveElementModal from './modal/move.vue'
|
||||
|
||||
export default {
|
||||
name: 'Checklist',
|
||||
components: { deleteElementModal, InlineEdit, ChecklistItem, Draggable },
|
||||
mixins: [DeleteMixin, DuplicateMixin],
|
||||
components: { deleteElementModal, InlineEdit, ChecklistItem, Draggable, moveElementModal },
|
||||
mixins: [DeleteMixin, DuplicateMixin, MoveMixin],
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
|
|
25
app/javascript/vue/shared/content/mixins/move.js
Normal file
25
app/javascript/vue/shared/content/mixins/move.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import axios from '../../../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
movingElement: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
showMoveModal(event) {
|
||||
event.stopPropagation();
|
||||
this.movingElement = true;
|
||||
},
|
||||
closeMoveModal() {
|
||||
this.movingElement = false;
|
||||
},
|
||||
moveElement(target_id) {
|
||||
axios.post(this.element.attributes.orderable.urls.move_url, { target_id: target_id }).
|
||||
then(() => {
|
||||
this.movingElement = false;
|
||||
this.$emit('moved', this.element.attributes.position, target_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
83
app/javascript/vue/shared/content/modal/move.vue
Normal file
83
app/javascript/vue/shared/content/modal/move.vue
Normal file
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div ref="modal" @keydown.esc="cancel" class="modal" id="modalDestroyProtocolContent" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title" id="modal-destroy-team-label">
|
||||
{{ i18n.t(`protocols.steps.modals.move_element.${parent_type}.title`) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label>
|
||||
{{ i18n.t(`protocols.steps.modals.move_element.${parent_type}.targets_label`) }}
|
||||
</label>
|
||||
<div class="w-full">
|
||||
<Select
|
||||
:value="target"
|
||||
:options="targets"
|
||||
v-bind:disabled="false"
|
||||
@change="setTarget"
|
||||
></Select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @click="cancel">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="confirm">{{ i18n.t('general.save')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import axios from '../../../../packs/custom_axios.js';
|
||||
import Select from "../../select.vue";
|
||||
|
||||
export default {
|
||||
name: 'moveElementModal',
|
||||
props: {
|
||||
parent_type: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
targets_url: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
target: null,
|
||||
targets: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Select
|
||||
},
|
||||
mounted() {
|
||||
$(this.$refs.modal).modal('show');
|
||||
$(this.$refs.modal).on('hidden.bs.modal', () => {
|
||||
this.$emit('cancel');
|
||||
});
|
||||
this.fetchTargets();
|
||||
},
|
||||
methods: {
|
||||
setTarget(target) {
|
||||
this.target = target;
|
||||
},
|
||||
fetchTargets() {
|
||||
axios.get(this.targets_url)
|
||||
.then(response => {
|
||||
this.targets = response.data.targets;
|
||||
})
|
||||
},
|
||||
confirm() {
|
||||
$(this.$refs.modal).modal('hide');
|
||||
this.$emit('confirm', this.target);
|
||||
},
|
||||
cancel() {
|
||||
$(this.$refs.modal).modal('hide');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -28,6 +28,9 @@
|
|||
<button v-if="element.attributes.orderable.urls.duplicate_url" class="btn icon-btn btn-light" tabindex="0" @click="duplicateElement">
|
||||
<i class="sn-icon sn-icon-duplicate"></i>
|
||||
</button>
|
||||
<button v-if="element.attributes.orderable.urls.move_targets_url" class="btn btn-light btn-sm" tabindex="0" @click="showMoveModal">
|
||||
Move
|
||||
</button>
|
||||
<button v-if="element.attributes.orderable.urls.delete_url" class="btn icon-btn btn-light" @click="showDeleteModal" tabindex="0">
|
||||
<i class="sn-icon sn-icon-delete"></i>
|
||||
</button>
|
||||
|
@ -59,20 +62,26 @@
|
|||
</div>
|
||||
<deleteElementModal v-if="confirmingDelete" @confirm="deleteElement" @cancel="closeDeleteModal"/>
|
||||
<tableNameModal v-if="nameModalOpen" :element="element" @update="updateEmptyName" @cancel="nameModalOpen = false" />
|
||||
<moveElementModal v-if="movingElement"
|
||||
:parent_type="element.attributes.orderable.parent_type"
|
||||
:targets_url="element.attributes.orderable.urls.move_targets_url"
|
||||
@confirm="moveElement($event)" @cancel="closeMoveModal"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DeleteMixin from './mixins/delete.js'
|
||||
import MoveMixin from './mixins/move.js'
|
||||
import DuplicateMixin from './mixins/duplicate.js'
|
||||
import deleteElementModal from './modal/delete.vue'
|
||||
import InlineEdit from '../inline_edit.vue'
|
||||
import TableNameModal from './modal/table_name.vue'
|
||||
import moveElementModal from './modal/move.vue'
|
||||
|
||||
export default {
|
||||
name: 'ContentTable',
|
||||
components: { deleteElementModal, InlineEdit, TableNameModal },
|
||||
mixins: [DeleteMixin, DuplicateMixin],
|
||||
components: { deleteElementModal, InlineEdit, TableNameModal, moveElementModal },
|
||||
mixins: [DeleteMixin, DuplicateMixin, MoveMixin],
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
<button v-if="element.attributes.orderable.urls.duplicate_url" class="btn icon-btn btn-light btn-sm" tabindex="0" @click="duplicateElement">
|
||||
<i class="sn-icon sn-icon-duplicate"></i>
|
||||
</button>
|
||||
<button v-if="element.attributes.orderable.urls.move_targets_url" class="btn btn-light btn-sm" tabindex="0" @click="showMoveModal">
|
||||
Move
|
||||
</button>
|
||||
<button v-if="element.attributes.orderable.urls.delete_url" class="btn icon-btn btn-light btn-sm" @click="showDeleteModal" tabindex="0">
|
||||
<i class="sn-icon sn-icon-delete"></i>
|
||||
</button>
|
||||
|
@ -41,19 +44,25 @@
|
|||
{{ i18n.t("protocols.steps.text.empty_text") }}
|
||||
</div>
|
||||
<deleteElementModal v-if="confirmingDelete" @confirm="deleteElement($event)" @cancel="closeDeleteModal"/>
|
||||
<moveElementModal v-if="movingElement"
|
||||
:parent_type="element.attributes.orderable.parent_type"
|
||||
:targets_url="element.attributes.orderable.urls.move_targets_url"
|
||||
@confirm="moveElement($event)" @cancel="closeMoveModal"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DeleteMixin from './mixins/delete.js'
|
||||
import MoveMixin from './mixins/move.js'
|
||||
import DuplicateMixin from './mixins/duplicate.js'
|
||||
import deleteElementModal from './modal/delete.vue'
|
||||
import moveElementModal from './modal/move.vue'
|
||||
import Tinymce from '../tinymce.vue'
|
||||
|
||||
export default {
|
||||
name: 'TextContent',
|
||||
components: { deleteElementModal, Tinymce },
|
||||
mixins: [DeleteMixin, DuplicateMixin],
|
||||
components: { deleteElementModal, Tinymce, moveElementModal },
|
||||
mixins: [DeleteMixin, DuplicateMixin, MoveMixin],
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
|
|
|
@ -111,4 +111,10 @@ class Result < ApplicationRecord
|
|||
def comments
|
||||
result_comments
|
||||
end
|
||||
|
||||
def normalize_elements_position
|
||||
result_orderable_elements.order(:position).each_with_index do |element, index|
|
||||
element.update!(position: index) unless element.position == index
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -180,6 +180,12 @@ class Step < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def normalize_elements_position
|
||||
step_orderable_elements.order(:position).each_with_index do |element, index|
|
||||
element.update!(position: index) unless element.position == index
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def touch_protocol
|
||||
|
|
|
@ -23,13 +23,7 @@ class StepOrderableElement < ApplicationRecord
|
|||
def decrement_following_elements_positions
|
||||
step.with_lock do
|
||||
yield
|
||||
step.step_orderable_elements
|
||||
.where('position > ?', position)
|
||||
.order(position: :asc).each do |step_orderable_element|
|
||||
# find_each ignore any ordering
|
||||
step_orderable_element.position -= 1
|
||||
step_orderable_element.save!
|
||||
end
|
||||
step.normalize_elements_position
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,12 +6,16 @@ class ChecklistSerializer < ActiveModel::Serializer
|
|||
include ApplicationHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
|
||||
attributes :id, :name, :urls, :icon, :sa_name, :checklist_items
|
||||
attributes :id, :name, :urls, :icon, :sa_name, :checklist_items, :parent_type
|
||||
|
||||
def icon
|
||||
'fa-list-ul'
|
||||
end
|
||||
|
||||
def parent_type
|
||||
:step
|
||||
end
|
||||
|
||||
def checklist_items
|
||||
object.checklist_items.map do |item|
|
||||
ChecklistItemSerializer.new(item, scope: { user: scope[:user] || @instance_options[:user] }).as_json
|
||||
|
@ -34,7 +38,9 @@ class ChecklistSerializer < ActiveModel::Serializer
|
|||
delete_url: step_checklist_path(object.step, object),
|
||||
update_url: step_checklist_path(object.step, object),
|
||||
reorder_url: reorder_step_checklist_checklist_items_path(object.step, object),
|
||||
create_item_url: step_checklist_checklist_items_path(object.step, object)
|
||||
create_item_url: step_checklist_checklist_items_path(object.step, object),
|
||||
move_targets_url: move_targets_step_checklist_path(object.step, object),
|
||||
move_url: move_step_checklist_path(object.step, object)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ class ResultTableSerializer < ActiveModel::Serializer
|
|||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :name, :contents, :urls, :icon, :metadata
|
||||
attributes :name, :contents, :urls, :icon, :metadata, :parent_type
|
||||
|
||||
def contents
|
||||
object.contents_utf_8
|
||||
|
@ -14,20 +14,23 @@ class ResultTableSerializer < ActiveModel::Serializer
|
|||
'fa-table'
|
||||
end
|
||||
|
||||
def parent_type
|
||||
:result
|
||||
end
|
||||
|
||||
def urls
|
||||
return if object.destroyed?
|
||||
|
||||
object.reload unless object.result
|
||||
|
||||
p object.result
|
||||
p scope[:user] || @instance_options[:user]
|
||||
p can_manage_result?(scope[:user] || @instance_options[:user], object.result)
|
||||
return {} unless can_manage_result?(scope[:user] || @instance_options[:user], object.result)
|
||||
|
||||
{
|
||||
duplicate_url: duplicate_my_module_result_table_path(object.result.my_module, object.result, object),
|
||||
delete_url: my_module_result_table_path(object.result.my_module, object.result, object),
|
||||
update_url: my_module_result_table_path(object.result.my_module, object.result, object)
|
||||
update_url: my_module_result_table_path(object.result.my_module, object.result, object),
|
||||
move_targets_url: move_targets_my_module_result_table_path(object.result.my_module, object.result, object),
|
||||
move_url: move_my_module_result_table_path(object.result.my_module, object.result, object)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ class ResultTextSerializer < ActiveModel::Serializer
|
|||
include ApplicationHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
|
||||
attributes :id, :text, :urls, :text_view, :icon, :placeholder, :name
|
||||
attributes :id, :text, :urls, :text_view, :icon, :placeholder, :name, :parent_type
|
||||
|
||||
def updated_at
|
||||
object.updated_at.to_i
|
||||
|
@ -16,6 +16,10 @@ class ResultTextSerializer < ActiveModel::Serializer
|
|||
I18n.t('protocols.steps.text.placeholder')
|
||||
end
|
||||
|
||||
def parent_type
|
||||
:result
|
||||
end
|
||||
|
||||
def text_view
|
||||
@user = scope[:user]
|
||||
custom_auto_link(object.tinymce_render('text'),
|
||||
|
@ -40,7 +44,9 @@ class ResultTextSerializer < ActiveModel::Serializer
|
|||
{
|
||||
duplicate_url: duplicate_my_module_result_text_path(result.my_module, result, object),
|
||||
delete_url: my_module_result_text_path(result.my_module, result, object),
|
||||
update_url: my_module_result_text_path(result.my_module, result, object)
|
||||
update_url: my_module_result_text_path(result.my_module, result, object),
|
||||
move_targets_url: move_targets_my_module_result_text_path(result.my_module, result, object),
|
||||
move_url: move_my_module_result_text_path(result.my_module, result, object)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,12 +6,16 @@ class StepTextSerializer < ActiveModel::Serializer
|
|||
include ApplicationHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
|
||||
attributes :id, :text, :urls, :text_view, :updated_at, :icon, :name, :placeholder
|
||||
attributes :id, :text, :urls, :text_view, :updated_at, :icon, :name, :placeholder, :parent_type
|
||||
|
||||
def updated_at
|
||||
object.updated_at.to_i
|
||||
end
|
||||
|
||||
def parent_type
|
||||
:step
|
||||
end
|
||||
|
||||
def placeholder
|
||||
I18n.t('protocols.steps.text.placeholder')
|
||||
end
|
||||
|
@ -38,7 +42,9 @@ class StepTextSerializer < ActiveModel::Serializer
|
|||
{
|
||||
duplicate_url: duplicate_step_text_path(object.step, object),
|
||||
delete_url: step_text_path(object.step, object),
|
||||
update_url: step_text_path(object.step, object)
|
||||
update_url: step_text_path(object.step, object),
|
||||
move_url: move_step_text_path(object.step, object),
|
||||
move_targets_url: move_targets_step_text_path(object.step, object)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ class TableSerializer < ActiveModel::Serializer
|
|||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :name, :contents, :urls, :icon, :metadata
|
||||
attributes :name, :contents, :urls, :icon, :metadata, :parent_type
|
||||
|
||||
def contents
|
||||
object.contents_utf_8
|
||||
|
@ -14,6 +14,10 @@ class TableSerializer < ActiveModel::Serializer
|
|||
'fa-table'
|
||||
end
|
||||
|
||||
def parent_type
|
||||
:step
|
||||
end
|
||||
|
||||
def urls
|
||||
return if object.destroyed?
|
||||
|
||||
|
@ -24,7 +28,9 @@ class TableSerializer < ActiveModel::Serializer
|
|||
{
|
||||
duplicate_url: duplicate_step_table_path(object.step, object),
|
||||
delete_url: step_table_path(object.step, object),
|
||||
update_url: step_table_path(object.step, object)
|
||||
update_url: step_table_path(object.step, object),
|
||||
move_targets_url: move_targets_step_table_path(object.step, object),
|
||||
move_url: move_step_table_path(object.step, object)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3099,6 +3099,13 @@ en:
|
|||
description_1: 'You’re about to delete a content block from your protocol. It might contain data you don’t want to lose. You won’t be able to get it back.'
|
||||
description_2: 'Are you sure you want to delete it?'
|
||||
confirm: 'Delete forever'
|
||||
move_element:
|
||||
step:
|
||||
title: Move to different step
|
||||
targets_label: Select step
|
||||
result:
|
||||
title: Move to different result
|
||||
targets_label: Select result
|
||||
delete_step:
|
||||
title: 'Delete step'
|
||||
description_1: 'You’re about to delete a whole step from your protocol. It might contain data you don’t want to lose. You won’t be able to get it back.'
|
||||
|
|
|
@ -542,11 +542,15 @@ Rails.application.routes.draw do
|
|||
|
||||
resources :tables, controller: 'result_elements/tables', only: %i(create destroy update) do
|
||||
member do
|
||||
get :move_targets
|
||||
post :move
|
||||
post :duplicate
|
||||
end
|
||||
end
|
||||
resources :texts, controller: 'result_elements/texts', only: %i(create destroy update) do
|
||||
member do
|
||||
get :move_targets
|
||||
post :move
|
||||
post :duplicate
|
||||
end
|
||||
end
|
||||
|
@ -562,16 +566,22 @@ Rails.application.routes.draw do
|
|||
only: %i(create index update destroy)
|
||||
resources :tables, controller: 'step_elements/tables', only: %i(create destroy update) do
|
||||
member do
|
||||
get :move_targets
|
||||
post :move
|
||||
post :duplicate
|
||||
end
|
||||
end
|
||||
resources :texts, controller: 'step_elements/texts', only: %i(create destroy update) do
|
||||
member do
|
||||
get :move_targets
|
||||
post :move
|
||||
post :duplicate
|
||||
end
|
||||
end
|
||||
resources :checklists, controller: 'step_elements/checklists', only: %i(create destroy update) do
|
||||
member do
|
||||
get :move_targets
|
||||
post :move
|
||||
post :duplicate
|
||||
end
|
||||
resources :checklist_items, controller: 'step_elements/checklist_items', only: %i(create update destroy) do
|
||||
|
|
Loading…
Reference in a new issue