mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-12-19 07:08:48 +08:00
Merge pull request #6864 from aignatov-bio/ai-sci-9802-migrate-protocols-table
Migrate protocols table [SCI-9802]
This commit is contained in:
commit
e93635f061
27 changed files with 1321 additions and 187 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
//= require protocols/import_export/import
|
//= require protocols/import_export/import
|
||||||
/* eslint-disable no-use-before-define, no-underscore-dangle, max-len, no-param-reassign */
|
/* eslint-disable no-use-before-define, no-underscore-dangle, max-len, no-param-reassign */
|
||||||
/* global ProtocolRepositoryHeader PdfPreview DataTableHelpers importProtocolFromFile _ PerfectSb protocolsIO
|
/* global ProtocolRepositoryHeader PdfPreview DataTableHelpers importProtocolFromFile
|
||||||
protocolSteps dropdownSelector filterDropdown I18n animateSpinner initHandsOnTable inlineEditing HelperModule */
|
protocolFileImportModal PerfectSb protocolsIO
|
||||||
|
protocolSteps dropdownSelector filterDropdown I18n animateSpinner initHandsOnTable inlineEditing HelperModule */
|
||||||
|
|
||||||
// Global variables
|
// Global variables
|
||||||
var ProtocolsIndex = (function() {
|
var ProtocolsIndex = (function() {
|
||||||
|
|
@ -27,10 +28,10 @@ var ProtocolsIndex = (function() {
|
||||||
* Initializes page
|
* Initializes page
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
window.initActionToolbar();
|
// window.initActionToolbar();
|
||||||
window.actionToolbarComponent.setReloadCallback(reloadTable);
|
// window.actionToolbarComponent.setReloadCallback(reloadTable);
|
||||||
// make room for pagination
|
// make room for pagination
|
||||||
window.actionToolbarComponent.setBottomOffset(68);
|
// window.actionToolbarComponent.setBottomOffset(68);
|
||||||
updateButtons();
|
updateButtons();
|
||||||
initProtocolsTable();
|
initProtocolsTable();
|
||||||
initKeywordFiltering();
|
initKeywordFiltering();
|
||||||
|
|
@ -38,6 +39,7 @@ var ProtocolsIndex = (function() {
|
||||||
initLinkedChildrenModal();
|
initLinkedChildrenModal();
|
||||||
initModals();
|
initModals();
|
||||||
initVersionsModal();
|
initVersionsModal();
|
||||||
|
initLocalFileImport();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadTable() {
|
function reloadTable() {
|
||||||
|
|
@ -267,7 +269,6 @@ var ProtocolsIndex = (function() {
|
||||||
let protocolFilters = $($('#protocolFilters').html());
|
let protocolFilters = $($('#protocolFilters').html());
|
||||||
$(protocolFilters).appendTo('.protocols-container .protocol-filters');
|
$(protocolFilters).appendTo('.protocols-container .protocol-filters');
|
||||||
|
|
||||||
initLocalFileImport();
|
|
||||||
initProtocolsFilters();
|
initProtocolsFilters();
|
||||||
initRowSelection();
|
initRowSelection();
|
||||||
},
|
},
|
||||||
|
|
@ -649,8 +650,6 @@ var ProtocolsIndex = (function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateButtons() {
|
function updateButtons() {
|
||||||
window.actionToolbarComponent.fetchActions({ protocol_ids: rowsSelected });
|
|
||||||
$('.dataTables_scrollBody').css('margin-bottom', `${rowsSelected.length > 0 ? 46 : 0}px`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initLocalFileImport() {
|
function initLocalFileImport() {
|
||||||
|
|
@ -673,7 +672,6 @@ var ProtocolsIndex = (function() {
|
||||||
var importUrl = fileInput.attr('data-import-url');
|
var importUrl = fileInput.attr('data-import-url');
|
||||||
var teamId = fileInput.attr('data-team-id');
|
var teamId = fileInput.attr('data-team-id');
|
||||||
var type = fileInput.attr('data-type');
|
var type = fileInput.attr('data-type');
|
||||||
|
|
||||||
if(ev.target.files[0].name.split('.').pop() === 'eln') {
|
if(ev.target.files[0].name.split('.').pop() === 'eln') {
|
||||||
importProtocolFromFile(
|
importProtocolFromFile(
|
||||||
ev.target.files[0],
|
ev.target.files[0],
|
||||||
|
|
@ -691,14 +689,14 @@ var ProtocolsIndex = (function() {
|
||||||
|
|
||||||
if (nrSuccessful) {
|
if (nrSuccessful) {
|
||||||
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_ok_html', { count: nrSuccessful }), 'success');
|
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_ok_html', { count: nrSuccessful }), 'success');
|
||||||
reloadTable();
|
window.protocolsTable.$refs.table.updateTable();
|
||||||
} else {
|
} else {
|
||||||
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_failed'), 'danger');
|
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_failed'), 'danger');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
protocolFileImportModal.init(ev.target.files, reloadTable);
|
protocolFileImportModal.init(ev.target.files, window.protocolsTable.$refs.table.updateTable());
|
||||||
}
|
}
|
||||||
// $(this).val('');
|
// $(this).val('');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,7 @@ var protocolsIO = function() {
|
||||||
animateSpinner(modal, false);
|
animateSpinner(modal, false);
|
||||||
modal.modal('hide');
|
modal.modal('hide');
|
||||||
HelperModule.flashAlertMsg(data.message, 'success');
|
HelperModule.flashAlertMsg(data.message, 'success');
|
||||||
ProtocolsIndex.reloadTable();
|
window.protocolsTable.$refs.table.updateTable();
|
||||||
},
|
},
|
||||||
error: function(data) {
|
error: function(data) {
|
||||||
showFormErrors(modal, data.responseJSON.validation_errors);
|
showFormErrors(modal, data.responseJSON.validation_errors);
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,20 @@
|
||||||
|
|
||||||
module AccessPermissions
|
module AccessPermissions
|
||||||
class ProtocolsController < ApplicationController
|
class ProtocolsController < ApplicationController
|
||||||
|
include InputSanitizeHelper
|
||||||
|
|
||||||
before_action :set_protocol
|
before_action :set_protocol
|
||||||
before_action :check_read_permissions, only: %i(show)
|
before_action :check_read_permissions, only: %i(show)
|
||||||
before_action :check_manage_permissions, except: %i(show)
|
before_action :check_manage_permissions, except: %i(show)
|
||||||
|
before_action :available_users, only: %i(new create)
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
render json: @protocol.user_assignments.includes(:user_role, :user).order('users.full_name ASC'),
|
||||||
|
each_serializer: UserAssignmentSerializer
|
||||||
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@user_assignment = UserAssignment.new(
|
render json: @available_users, each_serializer: UserSerializer
|
||||||
assignable: @protocol,
|
|
||||||
assigned_by: current_user,
|
|
||||||
team: current_team
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit; end
|
def edit; end
|
||||||
|
|
@ -21,38 +23,34 @@ module AccessPermissions
|
||||||
def create
|
def create
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
created_count = 0
|
created_count = 0
|
||||||
permitted_create_params[:resource_members].each do |_k, user_assignment_params|
|
if permitted_create_params[:user_id] == 'all'
|
||||||
next unless user_assignment_params[:assign] == '1'
|
@protocol.update!(visibility: :visible, default_public_user_role_id: permitted_create_params[:user_role_id])
|
||||||
|
log_activity(:protocol_template_access_granted_all_team_members,
|
||||||
if user_assignment_params[:user_id] == 'all'
|
|
||||||
@protocol.update!(default_public_user_role_id: user_assignment_params[:user_role_id])
|
|
||||||
log_activity(:protocol_template_access_granted_all_team_members,
|
|
||||||
{ team: @protocol.team.id, role: @protocol.default_public_user_role&.name })
|
{ team: @protocol.team.id, role: @protocol.default_public_user_role&.name })
|
||||||
else
|
else
|
||||||
user_assignment = UserAssignment.find_or_initialize_by(
|
user_assignment = UserAssignment.find_or_initialize_by(
|
||||||
assignable: @protocol,
|
assignable: @protocol,
|
||||||
user_id: user_assignment_params[:user_id],
|
user_id: permitted_create_params[:user_id],
|
||||||
team: current_team
|
team: current_team
|
||||||
)
|
)
|
||||||
|
|
||||||
user_assignment.update!(
|
user_assignment.update!(
|
||||||
user_role_id: user_assignment_params[:user_role_id],
|
user_role_id: permitted_create_params[:user_role_id],
|
||||||
assigned_by: current_user,
|
assigned_by: current_user,
|
||||||
assigned: :manually
|
assigned: :manually
|
||||||
)
|
)
|
||||||
|
|
||||||
created_count += 1
|
log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id,
|
||||||
log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id,
|
|
||||||
role: user_assignment.user_role.name })
|
role: user_assignment.user_role.name })
|
||||||
end
|
created_count += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@message = if created_count.zero?
|
@message = if created_count.zero?
|
||||||
t('access_permissions.create.success', count: t('access_permissions.all_team'))
|
t('access_permissions.create.success', member_name: t('access_permissions.all_team'))
|
||||||
else
|
else
|
||||||
t('access_permissions.create.success', count: created_count)
|
t('access_permissions.create.success', member_name: escape_input(user_assignment.user.name))
|
||||||
end
|
end
|
||||||
render :edit
|
render json: { message: @message }
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
Rails.logger.error e.message
|
Rails.logger.error e.message
|
||||||
errors = @protocol.errors.present? ? @protocol.errors&.map(&:message)&.join(',') : e.message
|
errors = @protocol.errors.present? ? @protocol.errors&.map(&:message)&.join(',') : e.message
|
||||||
|
|
@ -146,8 +144,17 @@ module AccessPermissions
|
||||||
end
|
end
|
||||||
|
|
||||||
def permitted_create_params
|
def permitted_create_params
|
||||||
params.require(:access_permissions_new_user_form)
|
params.require(:user_assignment)
|
||||||
.permit(resource_members: %i(assign user_id user_role_id))
|
.permit(%i(user_id user_role_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def available_users
|
||||||
|
# automatically assigned or not assigned to project
|
||||||
|
@available_users = current_team.users.where(
|
||||||
|
id: @protocol.user_assignments.automatically_assigned.select(:user_id)
|
||||||
|
).or(
|
||||||
|
current_team.users.where.not(id: @protocol.users.select(:id))
|
||||||
|
).order('users.full_name ASC')
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_protocol
|
def set_protocol
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class ProtocolsController < ApplicationController
|
||||||
protocol_status_bar
|
protocol_status_bar
|
||||||
linked_children
|
linked_children
|
||||||
linked_children_datatable
|
linked_children_datatable
|
||||||
|
versions_list
|
||||||
permissions
|
permissions
|
||||||
)
|
)
|
||||||
before_action :switch_team_with_param, only: %i(index protocolsio_index)
|
before_action :switch_team_with_param, only: %i(index protocolsio_index)
|
||||||
|
|
@ -75,7 +76,20 @@ class ProtocolsController < ApplicationController
|
||||||
|
|
||||||
layout 'fluid'
|
layout 'fluid'
|
||||||
|
|
||||||
def index; end
|
def index
|
||||||
|
respond_to do |format|
|
||||||
|
format.json do
|
||||||
|
protocols = Lists::ProtocolsService.new(Protocol.latest_available_versions(@current_team), params).call
|
||||||
|
render json: protocols,
|
||||||
|
each_serializer: Lists::ProtocolSerializer,
|
||||||
|
user: current_user,
|
||||||
|
meta: pagination_dict(protocols)
|
||||||
|
end
|
||||||
|
format.html do
|
||||||
|
render 'index'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def datatable
|
def datatable
|
||||||
render json: ::ProtocolsDatatable.new(
|
render json: ::ProtocolsDatatable.new(
|
||||||
|
|
@ -90,8 +104,17 @@ class ProtocolsController < ApplicationController
|
||||||
return render_403 unless @protocol.in_repository_published_original? || @protocol.initial_draft?
|
return render_403 unless @protocol.in_repository_published_original? || @protocol.initial_draft?
|
||||||
|
|
||||||
@published_versions = @protocol.published_versions_with_original.order(version_number: :desc)
|
@published_versions = @protocol.published_versions_with_original.order(version_number: :desc)
|
||||||
|
|
||||||
|
if @protocol.draft.present?
|
||||||
|
draft = @protocol.initial_draft? ? @protocol : @protocol.draft
|
||||||
|
draft_hash = ProtocolDraftSerializer.new(draft, scope: current_user).as_json
|
||||||
|
end
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
html: render_to_string(partial: 'protocols/index/protocol_versions_modal')
|
draft: draft_hash,
|
||||||
|
versions: @published_versions.map do |version|
|
||||||
|
ProtocolVersionSerializer.new(version, scope: current_user).as_json
|
||||||
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -100,14 +123,35 @@ class ProtocolsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def linked_children
|
def linked_children
|
||||||
render json: {
|
if params[:version].present?
|
||||||
title: I18n.t('protocols.index.linked_children.title',
|
records = @protocol.published_versions_with_original
|
||||||
protocol: escape_input(@protocol.name)),
|
.find_by!(version_number: params[:version])
|
||||||
html: render_to_string(partial: 'protocols/index/linked_children_modal_body',
|
.linked_children
|
||||||
locals: { protocol: @protocol })
|
else
|
||||||
|
records = Protocol.where(protocol_type: Protocol.protocol_types[:linked])
|
||||||
|
records = records.where(parent_id: @protocol.published_versions)
|
||||||
|
.or(records.where(parent_id: @protocol.id))
|
||||||
|
end
|
||||||
|
records.preload(my_module: { experiment: :project }).distinct
|
||||||
|
|
||||||
|
render json: records.map { |record|
|
||||||
|
{
|
||||||
|
my_module_name: record.my_module.name,
|
||||||
|
experiment_name: record.my_module.experiment.name,
|
||||||
|
project_name: record.my_module.experiment.project.name,
|
||||||
|
my_module_url: protocols_my_module_path(record.my_module),
|
||||||
|
experiment_url: my_modules_path(experiment_id: record.my_module.experiment.id),
|
||||||
|
project_url: experiments_path(project_id: record.my_module.experiment.project.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def versions_list
|
||||||
|
render json: { versions: (@protocol.parent || @protocol).published_versions_with_original
|
||||||
|
.order(version_number: :desc)
|
||||||
|
.map(&:version_number) }
|
||||||
|
end
|
||||||
|
|
||||||
def linked_children_datatable
|
def linked_children_datatable
|
||||||
render json: ::ProtocolLinkedChildrenDatatable.new(
|
render json: ::ProtocolLinkedChildrenDatatable.new(
|
||||||
view_context,
|
view_context,
|
||||||
|
|
@ -155,8 +199,12 @@ class ProtocolsController < ApplicationController
|
||||||
nil,
|
nil,
|
||||||
protocol: @protocol.id)
|
protocol: @protocol.id)
|
||||||
|
|
||||||
flash[:success] = I18n.t('protocols.delete_draft_modal.success')
|
if params[:version_modal]
|
||||||
redirect_to protocols_path
|
render json: { message: I18n.t('protocols.delete_draft_modal.success') }
|
||||||
|
else
|
||||||
|
flash[:success] = I18n.t('protocols.delete_draft_modal.success')
|
||||||
|
redirect_to protocols_path
|
||||||
|
end
|
||||||
rescue ActiveRecord::RecordNotDestroyed => e
|
rescue ActiveRecord::RecordNotDestroyed => e
|
||||||
Rails.logger.error e.message
|
Rails.logger.error e.message
|
||||||
render json: { message: e.message }, status: :unprocessable_entity
|
render json: { message: e.message }, status: :unprocessable_entity
|
||||||
|
|
@ -328,17 +376,15 @@ class ProtocolsController < ApplicationController
|
||||||
draft = @protocol.save_as_draft(current_user)
|
draft = @protocol.save_as_draft(current_user)
|
||||||
|
|
||||||
if draft.invalid?
|
if draft.invalid?
|
||||||
flash[:error] = draft.errors.full_messages.join(', ')
|
render json: { error: draft.errors.messages.map { |_, value| value }.join(' ') }, status: :unprocessable_entity
|
||||||
redirect_to protocols_path
|
|
||||||
else
|
else
|
||||||
log_activity(:protocol_template_draft_created, nil, protocol: @protocol.id)
|
log_activity(:protocol_template_draft_created, nil, protocol: @protocol.id)
|
||||||
redirect_to protocol_path(draft)
|
render json: { url: protocol_path(draft) }
|
||||||
end
|
end
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rails.logger.error(e.message)
|
Rails.logger.error(e.message)
|
||||||
Rails.logger.error(e.backtrace.join("\n"))
|
Rails.logger.error(e.backtrace.join("\n"))
|
||||||
flash[:error] = I18n.t('errors.general')
|
render json: { error: I18n.t('errors.general') }, status: :unprocessable_entity
|
||||||
redirect_to protocols_path
|
|
||||||
raise ActiveRecord::Rollback
|
raise ActiveRecord::Rollback
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -823,7 +869,7 @@ class ProtocolsController < ApplicationController
|
||||||
actions:
|
actions:
|
||||||
Toolbars::ProtocolsService.new(
|
Toolbars::ProtocolsService.new(
|
||||||
current_user,
|
current_user,
|
||||||
protocol_ids: params[:protocol_ids].split(',')
|
protocol_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||||
).actions
|
).actions
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
12
app/javascript/packs/vue/protocols_list.js
Normal file
12
app/javascript/packs/vue/protocols_list.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||||
|
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||||
|
import ProtocolsTable from '../../vue/protocols/table.vue';
|
||||||
|
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||||
|
|
||||||
|
const app = createApp();
|
||||||
|
app.component('ProtocolsTable', ProtocolsTable);
|
||||||
|
app.config.globalProperties.i18n = window.I18n;
|
||||||
|
app.use(PerfectScrollbar);
|
||||||
|
window.protocolsTable = mountWithTurbolinks(app, '#ProtocolsTable', () => {
|
||||||
|
delete window.protocolsTable;
|
||||||
|
});
|
||||||
|
|
@ -61,8 +61,8 @@
|
||||||
py-2.5 hover:bg-sn-super-light-grey"
|
py-2.5 hover:bg-sn-super-light-grey"
|
||||||
@click="createTag()"
|
@click="createTag()"
|
||||||
>
|
>
|
||||||
<div class="h-8 w-8 rounded relative border-sn-gray border-solid">
|
<div class="h-8 w-8 rounded relative border-sn-grey border-solid">
|
||||||
<div class="absolute top-1 left-1 rounded-full w-1 h-1 bg-white border-sn-gray border-solid"></div>
|
<div class="absolute top-1 left-1 rounded-full w-1 h-1 bg-white border-sn-grey border-solid"></div>
|
||||||
</div>
|
</div>
|
||||||
<div>{{ i18n.t('experiments.canvas.modal_manage_tags.create_new') }}</div>
|
<div>{{ i18n.t('experiments.canvas.modal_manage_tags.create_new') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -332,8 +332,8 @@ export default {
|
||||||
},
|
},
|
||||||
move(event, rows) {
|
move(event, rows) {
|
||||||
this.objectsToMove = rows;
|
this.objectsToMove = rows;
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
<div class="protocol-section protocol-information mb-4">
|
<div class="protocol-section protocol-information mb-4">
|
||||||
<div id="protocol-details" class="protocol-section-header">
|
<div id="protocol-details" class="protocol-section-header">
|
||||||
<div class="protocol-details-container">
|
<div class="protocol-details-container">
|
||||||
<a class="protocol-section-caret" role="button" data-toggle="collapse" href="#details-container" aria-expanded="false" aria-controls="details-container">
|
<a class="protocol-section-caret" role="button" data-toggle="collapse"
|
||||||
|
href="#details-container" aria-expanded="false" aria-controls="details-container">
|
||||||
<i class="sn-icon sn-icon-right"></i>
|
<i class="sn-icon sn-icon-right"></i>
|
||||||
<span id="protocolDetailsLabel" class="protocol-section-title">
|
<span id="protocolDetailsLabel" class="protocol-section-title">
|
||||||
<h2>
|
<h2>
|
||||||
|
|
@ -13,12 +14,21 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions-block">
|
<div class="actions-block">
|
||||||
<a class="btn btn-light icon-btn pull-right" :href="protocol.attributes.urls.print_protocol_url" target="_blank">
|
<a class="btn btn-light icon-btn pull-right"
|
||||||
|
:href="protocol.attributes.urls.print_protocol_url" target="_blank">
|
||||||
<span class="sn-icon sn-icon-printer" aria-hidden="true"></span>
|
<span class="sn-icon sn-icon-printer" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
<button class="btn btn-light" @click="openVersionsModal">{{ i18n.t("protocols.header.versions") }}</button>
|
<button class="btn btn-light" @click="openVersionsModal">
|
||||||
<button v-if="protocol.attributes.urls.publish_url" @click="$emit('publish')" class="btn btn-primary">{{ i18n.t("protocols.header.publish") }}</button>
|
{{ i18n.t("protocols.header.versions") }}
|
||||||
<button v-if="protocol.attributes.urls.save_as_draft_url" v-bind:disabled="protocol.attributes.has_draft" @click="saveAsdraft" class="btn btn-secondary">{{ i18n.t("protocols.header.save_as_draft") }}</button>
|
</button>
|
||||||
|
<button v-if="protocol.attributes.urls.publish_url"
|
||||||
|
@click="$emit('publish')" class="btn btn-primary">
|
||||||
|
{{ i18n.t("protocols.header.publish") }}</button>
|
||||||
|
<button v-if="protocol.attributes.urls.save_as_draft_url"
|
||||||
|
v-bind:disabled="protocol.attributes.has_draft"
|
||||||
|
@click="saveAsdraft" class="btn btn-secondary">
|
||||||
|
{{ i18n.t("protocols.header.save_as_draft") }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="details-container" class="protocol-details collapse in">
|
<div id="details-container" class="protocol-details collapse in">
|
||||||
|
|
@ -87,77 +97,95 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Teleport to="body">
|
||||||
|
<VersionsModal v-if="VersionsModalObject" :protocol="VersionsModalObject"
|
||||||
|
@close="VersionsModalObject = null"
|
||||||
|
@reloadPage="reloadPage"
|
||||||
|
@redirectToProtocols="redirectToProtocols"/>
|
||||||
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
/* global HelperModule */
|
||||||
|
import InlineEdit from '../shared/inline_edit.vue';
|
||||||
|
import DropdownSelector from '../shared/legacy/dropdown_selector.vue';
|
||||||
|
import VersionsModal from '../protocols/modals/versions.vue';
|
||||||
|
|
||||||
import InlineEdit from '../shared/inline_edit.vue'
|
export default {
|
||||||
import DropdownSelector from '../shared/legacy/dropdown_selector.vue'
|
name: 'ProtocolMetadata',
|
||||||
|
components: { InlineEdit, DropdownSelector, VersionsModal },
|
||||||
export default {
|
props: {
|
||||||
name: 'ProtocolMetadata',
|
protocol: {
|
||||||
components: { InlineEdit, DropdownSelector },
|
type: Object,
|
||||||
props: {
|
required: true
|
||||||
protocol: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
},
|
||||||
titleVersion() {
|
data() {
|
||||||
const createdFromVersion = this.protocol.attributes.created_from_version;
|
return {
|
||||||
|
VersionsModalObject: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
titleVersion() {
|
||||||
|
const createdFromVersion = this.protocol.attributes.created_from_version;
|
||||||
|
|
||||||
if (this.protocol.attributes.published) {
|
if (this.protocol.attributes.published) {
|
||||||
return this.protocol.attributes.version;
|
return this.protocol.attributes.version;
|
||||||
}
|
|
||||||
|
|
||||||
if (!createdFromVersion) {
|
|
||||||
return this.i18n.t('protocols.draft');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.i18n.t('protocols.header.draft_with_from_version', {nr: createdFromVersion});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!createdFromVersion) {
|
||||||
|
return this.i18n.t('protocols.draft');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.i18n.t('protocols.header.draft_with_from_version', { nr: createdFromVersion });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
saveAsdraft() {
|
||||||
|
$.post(this.protocol.attributes.urls.save_as_draft_url);
|
||||||
},
|
},
|
||||||
methods: {
|
updateAuthors(authors) {
|
||||||
saveAsdraft() {
|
$.ajax({
|
||||||
$.post(this.protocol.attributes.urls.save_as_draft_url)
|
type: 'PATCH',
|
||||||
},
|
url: this.protocol.attributes.urls.update_protocol_authors_url,
|
||||||
updateAuthors(authors) {
|
data: { protocol: { authors } },
|
||||||
$.ajax({
|
success: (result) => {
|
||||||
type: 'PATCH',
|
this.$emit('update', result.data.attributes);
|
||||||
url: this.protocol.attributes.urls.update_protocol_authors_url,
|
},
|
||||||
data: { protocol: { authors: authors } },
|
error: (data) => {
|
||||||
success: (result) => {
|
let message;
|
||||||
this.$emit('update', result.data.attributes)
|
if (data.responseJSON) {
|
||||||
},
|
message = Object.values(data.responseJSON).join(', ');
|
||||||
error: (data) => {
|
} else {
|
||||||
HelperModule.flashAlertMsg(data.responseJSON ? Object.values(data.responseJSON).join(', ') : I18n.t('errors.general'), 'danger');
|
message = this.i18n.t('errors.general');
|
||||||
}
|
}
|
||||||
});
|
HelperModule.flashAlertMsg(message);
|
||||||
},
|
}
|
||||||
updateKeywords(keywords) {
|
});
|
||||||
$.ajax({
|
},
|
||||||
type: 'PATCH',
|
updateKeywords(keywords) {
|
||||||
url: this.protocol.attributes.urls.update_protocol_keywords_url,
|
$.ajax({
|
||||||
data: { keywords: keywords },
|
type: 'PATCH',
|
||||||
success: (result) => {
|
url: this.protocol.attributes.urls.update_protocol_keywords_url,
|
||||||
this.$emit('update', result.data.attributes)
|
data: { keywords },
|
||||||
}
|
success: (result) => {
|
||||||
});
|
this.$emit('update', result.data.attributes);
|
||||||
},
|
}
|
||||||
openVersionsModal() {
|
});
|
||||||
$.get(this.protocol.attributes.urls.versions_modal_url, (data) => {
|
},
|
||||||
let versionsModal = '#protocol-versions-modal'
|
openVersionsModal() {
|
||||||
$('.protocols-show').append($.parseHTML(data.html));
|
this.VersionsModalObject = {
|
||||||
$(versionsModal).modal('show');
|
id: this.protocol.id,
|
||||||
inlineEditing.init();
|
urls: {
|
||||||
$(versionsModal).find('[data-toggle="tooltip"]').tooltip();
|
versions_modal: this.protocol.attributes.urls.versions_modal
|
||||||
|
}
|
||||||
// Remove modal when it gets closed
|
};
|
||||||
$(versionsModal).on('hidden.bs.modal', () => {
|
},
|
||||||
$(versionsModal).remove();
|
reloadPage() {
|
||||||
});
|
window.location.reload();
|
||||||
});
|
},
|
||||||
}
|
redirectToProtocols() {
|
||||||
|
window.location.href = this.protocol.attributes.urls.redirect_to_protocols;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
104
app/javascript/vue/protocols/modals/linked_my_modules.vue
Normal file
104
app/javascript/vue/protocols/modals/linked_my_modules.vue
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
<template>
|
||||||
|
<div ref="modal" class="modal" 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 truncate !block">
|
||||||
|
{{ i18n.t('protocols.index.linked_children.title', { protocol: protocol.name }) }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="max-h-96 overflow-y-auto">
|
||||||
|
<div v-for="myModule in linkedMyModules" class="flex items-center gap-2 px-3 py-2">
|
||||||
|
<a :href="myModule.project_url"
|
||||||
|
:title="myModule.project_name"
|
||||||
|
class="hover:no-underline flex items-center gap-1 shrink-0">
|
||||||
|
<i class="sn-icon sn-icon-projects"></i>
|
||||||
|
<span class="truncate max-w-[160px]">{{ myModule.project_name }}</span>
|
||||||
|
</a>
|
||||||
|
<span>/</span>
|
||||||
|
<a :href="myModule.experiment_url"
|
||||||
|
:title="myModule.experiment_name"
|
||||||
|
class="hover:no-underline flex items-center gap-1 shrink-0">
|
||||||
|
<i class="sn-icon sn-icon-experiment"></i>
|
||||||
|
<span class="truncate max-w-[160px]">{{ myModule.experiment_name }}</span>
|
||||||
|
</a>
|
||||||
|
<span>/</span>
|
||||||
|
<a :href="myModule.my_module_url"
|
||||||
|
:title="myModule.my_module_name"
|
||||||
|
class="hover:no-underline flex items-center gap-1 shrink-0">
|
||||||
|
<i class="sn-icon sn-icon-task"></i>
|
||||||
|
<span class="truncate max-w-[160px]">{{ myModule.my_module_name }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer items-center">
|
||||||
|
{{ i18n.t("protocols.index.linked_children.show_version") }}
|
||||||
|
<div class="w-48 mr-auto">
|
||||||
|
<SelectDropdown
|
||||||
|
:options="versionsList"
|
||||||
|
:value="selectedVersion"
|
||||||
|
@change="changeSelectedVersion"
|
||||||
|
></SelectDropdown>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||||
|
import axios from '../../../packs/custom_axios.js';
|
||||||
|
import modalMixin from '../../shared/modal_mixin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NewProtocolModal',
|
||||||
|
props: {
|
||||||
|
protocol: Object
|
||||||
|
},
|
||||||
|
mixins: [modalMixin],
|
||||||
|
components: {
|
||||||
|
SelectDropdown
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
linkedMyModules: [],
|
||||||
|
versionsList: [],
|
||||||
|
selectedVersion: 'All'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadLinkedMyModules();
|
||||||
|
this.loadVersions();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadLinkedMyModules() {
|
||||||
|
const urlParams = {};
|
||||||
|
if (this.selectedVersion !== 'All') {
|
||||||
|
urlParams.version = this.selectedVersion;
|
||||||
|
}
|
||||||
|
axios.get(this.protocol.urls.linked_my_modules, { params: urlParams })
|
||||||
|
.then((response) => {
|
||||||
|
this.linkedMyModules = response.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadVersions() {
|
||||||
|
axios.get(this.protocol.urls.versions_list)
|
||||||
|
.then((response) => {
|
||||||
|
this.versionsList = [['All', 'All']].concat(response.data.versions.map((version) => [version, version]));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
changeSelectedVersion(version) {
|
||||||
|
this.selectedVersion = version;
|
||||||
|
this.loadLinkedMyModules();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
90
app/javascript/vue/protocols/modals/new.vue
Normal file
90
app/javascript/vue/protocols/modals/new.vue
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<div ref="modal" class="modal" 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 truncate !block" id="edit-project-modal-label">
|
||||||
|
{{ i18n.t("protocols.new_protocol_modal.title_new") }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-6">
|
||||||
|
<label class="sci-label">{{ i18n.t("protocols.new_protocol_modal.name_label") }}</label>
|
||||||
|
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||||
|
<input type="text" v-model="name"
|
||||||
|
class="sci-input-field"
|
||||||
|
autofocus="true"
|
||||||
|
:placeholder="i18n.t('protocols.new_protocol_modal.name_placeholder')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 text-xs items-center">
|
||||||
|
<div class="sci-checkbox-container">
|
||||||
|
<input type="checkbox" class="sci-checkbox" v-model="visible" value="visible"/>
|
||||||
|
<span class="sci-checkbox-label"></span>
|
||||||
|
</div>
|
||||||
|
<span v-html="i18n.t('protocols.new_protocol_modal.access_label')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6" :class="{'hidden': !visible}">
|
||||||
|
<label class="sci-label">{{ i18n.t("protocols.new_protocol_modal.role_label") }}</label>
|
||||||
|
<SelectDropdown :optionsUrl="userRolesUrl" :value="defaultRole" @change="changeRole" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||||
|
<button class="btn btn-primary" @click="submit" type="submit">
|
||||||
|
{{ i18n.t('protocols.new_protocol_modal.create_new') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||||
|
import axios from '../../../packs/custom_axios.js';
|
||||||
|
import modalMixin from '../../shared/modal_mixin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NewProtocolModal',
|
||||||
|
props: {
|
||||||
|
createUrl: String,
|
||||||
|
userRolesUrl: String
|
||||||
|
},
|
||||||
|
mixins: [modalMixin],
|
||||||
|
components: {
|
||||||
|
SelectDropdown
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
visible: false,
|
||||||
|
defaultRole: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
axios.post(this.createUrl, {
|
||||||
|
protocol: {
|
||||||
|
name: this.name,
|
||||||
|
visibility: (this.visible ? 'visible' : 'hidden'),
|
||||||
|
default_public_user_role_id: this.defaultRole
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
this.error = null;
|
||||||
|
this.$emit('create');
|
||||||
|
}).catch((error) => {
|
||||||
|
this.error = error.response.data.name;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
changeRole(role) {
|
||||||
|
this.defaultRole = role;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
181
app/javascript/vue/protocols/modals/versions.vue
Normal file
181
app/javascript/vue/protocols/modals/versions.vue
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
<template>
|
||||||
|
<div ref="modal" class="modal" 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 truncate !block">
|
||||||
|
{{ i18n.t('protocols.index.versions.title', { protocol: protocol.name }) }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="max-h-[400px] overflow-y-auto">
|
||||||
|
<div v-if="draft">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<a :href="draft.urls.show" class="hover:no-underline cursor-pointer shrink-0">
|
||||||
|
<span v-if="draft.previous_number"
|
||||||
|
v-html="i18n.t('protocols.index.versions.draft_html', {
|
||||||
|
parent_version: draft.previous_number
|
||||||
|
})"
|
||||||
|
></span>
|
||||||
|
<span v-else v-html="i18n.t('protocols.index.versions.first_draft_html')"></span>
|
||||||
|
</a>
|
||||||
|
<span class="text-xs" v-if="draft.modified_by">
|
||||||
|
{{
|
||||||
|
i18n.t('protocols.index.versions.draft_full_modification_info', {
|
||||||
|
modified_on: draft.modified_on,
|
||||||
|
modified_by: draft.modified_by
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs" v-else>
|
||||||
|
{{
|
||||||
|
i18n.t('protocols.index.versions.draft_update_modification_info', {
|
||||||
|
modified_on: draft.modified_on
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<div class="flex items-center gap-2 ml-auto">
|
||||||
|
<button v-if="draft.urls.publish" class="btn btn-light" @click="publishDraft">
|
||||||
|
{{ i18n.t('protocols.index.versions.publish') }}
|
||||||
|
</button>
|
||||||
|
<button v-if="draft.urls.destroy" @click="destroyDraft" class="btn btn-light icon-btn">
|
||||||
|
<i class="sn-icon sn-icon-delete"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<InlineEdit
|
||||||
|
:class="{ 'pointer-events-none': !draft.urls.comment }"
|
||||||
|
class="mb-4"
|
||||||
|
:value="draft.comment"
|
||||||
|
:characterLimit="10000"
|
||||||
|
:placeholder="i18n.t('protocols.index.versions.comment_placeholder')"
|
||||||
|
:allowBlank="true"
|
||||||
|
:singleLine="false"
|
||||||
|
:attributeName="`${i18n.t('Draft')} ${i18n.t('comment')}`"
|
||||||
|
@update="updateComment"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-for="version in publishedVersions" :key="version.number">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<a :href="version.urls.show" class="hover:no-underline cursor-pointer shrink-0">
|
||||||
|
<b>
|
||||||
|
{{ i18n.t('protocols.index.versions.revision', { version: version.number }) }}
|
||||||
|
</b>
|
||||||
|
</a>
|
||||||
|
<span class="text-xs">
|
||||||
|
{{
|
||||||
|
i18n.t('protocols.index.versions.revision_publishing_info', {
|
||||||
|
published_on: version.published_on,
|
||||||
|
published_by: version.published_by
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-if="version.urls.save_as_draft"
|
||||||
|
class="btn btn-light icon-btn ml-auto"
|
||||||
|
:title="i18n.t('protocols.index.versions.save_as_draft')"
|
||||||
|
@click="saveAsDraft(version.urls.save_as_draft)"
|
||||||
|
:disabled="draft"
|
||||||
|
>
|
||||||
|
<i class="sn-icon sn-icon-duplicate"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
{{ version.comment }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ConfirmationModal
|
||||||
|
:title="i18n.t('protocols.delete_draft_modal.title')"
|
||||||
|
:description="`
|
||||||
|
<p>${i18n.t('protocols.delete_draft_modal.description_1')}</p>
|
||||||
|
<p><b>${i18n.t('protocols.delete_draft_modal.description_2')}</b></p>
|
||||||
|
`"
|
||||||
|
:confirmClass="'btn btn-danger'"
|
||||||
|
:confirmText="i18n.t('protocols.delete_draft_modal.confirm')"
|
||||||
|
ref="destroyModal"
|
||||||
|
></ConfirmationModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
/* global HelperModule */
|
||||||
|
|
||||||
|
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||||
|
import InlineEdit from '../../shared/inline_edit.vue';
|
||||||
|
import axios from '../../../packs/custom_axios.js';
|
||||||
|
import modalMixin from '../../shared/modal_mixin';
|
||||||
|
import ConfirmationModal from '../../shared/confirmation_modal.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VersionsModal',
|
||||||
|
props: {
|
||||||
|
protocol: Object
|
||||||
|
},
|
||||||
|
emits: ['refresh', 'close'],
|
||||||
|
mixins: [modalMixin],
|
||||||
|
components: {
|
||||||
|
SelectDropdown,
|
||||||
|
InlineEdit,
|
||||||
|
ConfirmationModal
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
draft: null,
|
||||||
|
publishedVersions: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadModalData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadModalData() {
|
||||||
|
axios.get(this.protocol.urls.versions_modal).then((response) => {
|
||||||
|
this.publishedVersions = response.data.versions;
|
||||||
|
this.draft = response.data.draft;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateComment(comment) {
|
||||||
|
axios.put(this.draft.urls.comment, { protocol: { version_comment: comment } }).then(() => {
|
||||||
|
this.draft.comment = comment;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async destroyDraft() {
|
||||||
|
const ok = await this.$refs.destroyModal.show();
|
||||||
|
if (ok) {
|
||||||
|
axios.post(this.draft.urls.destroy, {
|
||||||
|
version_modal: true
|
||||||
|
}).then((response) => {
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
this.$emit('refresh');
|
||||||
|
this.$emit('redirectToProtocols');
|
||||||
|
this.loadModalData();
|
||||||
|
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveAsDraft(url) {
|
||||||
|
axios.post(url).then((response) => {
|
||||||
|
window.location.href = response.data.url;
|
||||||
|
}).catch((error) => {
|
||||||
|
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
publishDraft() {
|
||||||
|
axios.post(this.draft.urls.publish).then(() => {
|
||||||
|
this.loadModalData();
|
||||||
|
this.$emit('reloadPage');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
23
app/javascript/vue/protocols/renderers/keywords.vue
Normal file
23
app/javascript/vue/protocols/renderers/keywords.vue
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center gap-2 truncate h-full">
|
||||||
|
<div v-for="keyword in params.data.keywords" :key="keyword.id"
|
||||||
|
@click="params.dtComponent.setSearchValue(keyword)"
|
||||||
|
class="px-2 py-1 rounded-sm bg-sn-super-light-grey gap-1 leading-5 cursor-pointer hover:bg-sn-light-grey">
|
||||||
|
{{ keyword }}
|
||||||
|
</div>
|
||||||
|
<div v-if="params.data.keywords.length === 0">
|
||||||
|
—
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'KeywordsRenderer',
|
||||||
|
props: {
|
||||||
|
params: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
22
app/javascript/vue/protocols/renderers/linked_my_modules.vue
Normal file
22
app/javascript/vue/protocols/renderers/linked_my_modules.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="params.data.linked_tasks > 0"
|
||||||
|
@click.stop="params.dtComponent.$emit('linked_my_modules', {}, [params.data])"
|
||||||
|
class="cursor-pointer text-sn-blue"
|
||||||
|
>
|
||||||
|
{{ params.data.linked_tasks }}
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
0
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'LinkedMyModulesRenderer',
|
||||||
|
props: {
|
||||||
|
params: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
33
app/javascript/vue/protocols/renderers/versions.vue
Normal file
33
app/javascript/vue/protocols/renderers/versions.vue
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="params.data.urls.show" class="flex items-center gap-2 text-sn-blue">
|
||||||
|
<span class="cursor-pointer"
|
||||||
|
@click.stop="params.dtComponent.$emit('versions', {}, [params.data])"
|
||||||
|
v-if="params.data.nr_of_versions > 0">
|
||||||
|
{{ params.data.nr_of_versions }}
|
||||||
|
</span>
|
||||||
|
<span v-if="params.data.nr_of_versions > 0 && params.data.has_draft">/</span>
|
||||||
|
<a v-if="params.data.has_draft" :href="params.data.urls.show_draft" class="hover:no-underline">
|
||||||
|
{{ i18n.t("protocols.index.table.draft") }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex items-center gap-2 text-sn-grey">
|
||||||
|
<span v-if="params.data.nr_of_versions > 0">
|
||||||
|
{{ params.data.nr_of_versions }}
|
||||||
|
</span>
|
||||||
|
<span v-if="params.data.nr_of_versions > 0 && params.data.has_draft">/</span>
|
||||||
|
<span v-if="params.data.has_draft" class="hover:no-underline ">
|
||||||
|
{{ i18n.t("protocols.index.table.draft") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'VersionsRenderer',
|
||||||
|
props: {
|
||||||
|
params: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
298
app/javascript/vue/protocols/table.vue
Normal file
298
app/javascript/vue/protocols/table.vue
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full">
|
||||||
|
<DataTable :columnDefs="columnDefs"
|
||||||
|
:tableId="'protocolTemplates'"
|
||||||
|
:dataUrl="dataSource"
|
||||||
|
:reloadingTable="reloadingTable"
|
||||||
|
:currentViewMode="currentViewMode"
|
||||||
|
:toolbarActions="toolbarActions"
|
||||||
|
:activePageUrl="activePageUrl"
|
||||||
|
:archivedPageUrl="archivedPageUrl"
|
||||||
|
:actionsUrl="actionsUrl"
|
||||||
|
@create="create"
|
||||||
|
@archive="archive"
|
||||||
|
@restore="restore"
|
||||||
|
@export="exportProtocol"
|
||||||
|
@duplicate="duplicate"
|
||||||
|
@versions="versions"
|
||||||
|
@tableReloaded="reloadingTable = false"
|
||||||
|
@import_file="importFile"
|
||||||
|
@import_protocols_io="importProtocolsIo"
|
||||||
|
@import_docx="importDocx"
|
||||||
|
@access="access"
|
||||||
|
@linked_my_modules="linkedMyModules"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<NewProtocolModal v-if="newProtocol" :createUrl="createUrl"
|
||||||
|
:userRolesUrl="userRolesUrl"
|
||||||
|
@close="newProtocol = false" @create="updateTable" />
|
||||||
|
<AccessModal v-if="accessModalParams" :params="accessModalParams"
|
||||||
|
@close="accessModalParams = null" @refresh="this.reloadingTable = true" />
|
||||||
|
<LinkedMyModulesModal v-if="linkedMyModulesModalObject" :protocol="linkedMyModulesModalObject"
|
||||||
|
@close="linkedMyModulesModalObject = null"/>
|
||||||
|
<VersionsModal v-if="VersionsModalObject" :protocol="VersionsModalObject"
|
||||||
|
@close="VersionsModalObject = null" @refresh="this.reloadingTable = true"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/* global HelperModule */
|
||||||
|
|
||||||
|
import axios from '../../packs/custom_axios.js';
|
||||||
|
|
||||||
|
import DataTable from '../shared/datatable/table.vue';
|
||||||
|
import UsersRenderer from '../projects/renderers/users.vue';
|
||||||
|
import NewProtocolModal from './modals/new.vue';
|
||||||
|
import AccessModal from '../shared/access_modal/modal.vue';
|
||||||
|
import KeywordsRenderer from './renderers/keywords.vue';
|
||||||
|
import LinkedMyModulesRenderer from './renderers/linked_my_modules.vue';
|
||||||
|
import LinkedMyModulesModal from './modals/linked_my_modules.vue';
|
||||||
|
import VersionsRenderer from './renderers/versions.vue';
|
||||||
|
import VersionsModal from './modals/versions.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'LabelTemplatesTable',
|
||||||
|
components: {
|
||||||
|
DataTable,
|
||||||
|
UsersRenderer,
|
||||||
|
NewProtocolModal,
|
||||||
|
AccessModal,
|
||||||
|
KeywordsRenderer,
|
||||||
|
LinkedMyModulesRenderer,
|
||||||
|
LinkedMyModulesModal,
|
||||||
|
VersionsRenderer,
|
||||||
|
VersionsModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
dataSource: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
actionsUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
createUrl: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
currentViewMode: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
docxParserEnabled: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
activePageUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
archivedPageUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
userRolesUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
reloadingTable: false,
|
||||||
|
newProtocol: false,
|
||||||
|
accessModalParams: null,
|
||||||
|
linkedMyModulesModalObject: null,
|
||||||
|
VersionsModalObject: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
columnDefs() {
|
||||||
|
const columns = [{
|
||||||
|
field: 'name',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.name'),
|
||||||
|
sortable: true,
|
||||||
|
notSelectable: true,
|
||||||
|
cellRenderer: this.nameRenderer
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'code',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.id'),
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'versions',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.versions'),
|
||||||
|
sortable: true,
|
||||||
|
cellRenderer: 'VersionsRenderer',
|
||||||
|
notSelectable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'keywords',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.keywords'),
|
||||||
|
sortable: true,
|
||||||
|
cellRenderer: 'KeywordsRenderer',
|
||||||
|
notSelectable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'linked_tasks',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.nr_of_linked_children'),
|
||||||
|
sortable: true,
|
||||||
|
cellRenderer: 'LinkedMyModulesRenderer'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'assigned_users',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.access'),
|
||||||
|
sortable: true,
|
||||||
|
cellRenderer: 'UsersRenderer',
|
||||||
|
minWidth: 210,
|
||||||
|
notSelectable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'published_by',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.published_by'),
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'published_on',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.published_on'),
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'updated_at',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.updated_at'),
|
||||||
|
sortable: true
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (this.currentViewMode === 'archived') {
|
||||||
|
columns.push({
|
||||||
|
field: 'archived_by',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.archived_by'),
|
||||||
|
sortable: true
|
||||||
|
});
|
||||||
|
columns.push({
|
||||||
|
field: 'archived_on',
|
||||||
|
headerName: this.i18n.t('protocols.index.thead.archived_on'),
|
||||||
|
sortable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
},
|
||||||
|
toolbarActions() {
|
||||||
|
const left = [];
|
||||||
|
if (this.createUrl) {
|
||||||
|
left.push({
|
||||||
|
name: 'create',
|
||||||
|
icon: 'sn-icon sn-icon-new-task',
|
||||||
|
label: this.i18n.t('protocols.index.create_new'),
|
||||||
|
type: 'emit',
|
||||||
|
path: this.createUrl,
|
||||||
|
buttonStyle: 'btn btn-primary'
|
||||||
|
});
|
||||||
|
const importMenu = {
|
||||||
|
name: 'import',
|
||||||
|
icon: 'sn-icon sn-icon-import',
|
||||||
|
label: this.i18n.t('protocols.index.import'),
|
||||||
|
type: 'menu',
|
||||||
|
path: this.createUrl,
|
||||||
|
buttonStyle: 'btn btn-light',
|
||||||
|
menuItems: [
|
||||||
|
{
|
||||||
|
emit: 'import_file',
|
||||||
|
text: this.i18n.t('protocols.index.import_eln')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.docxParserEnabled) {
|
||||||
|
importMenu.menuItems.push({
|
||||||
|
emit: 'import_docx',
|
||||||
|
text: this.i18n.t('protocols.index.import_docx')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
importMenu.menuItems.push({
|
||||||
|
emit: 'import_protocols_io',
|
||||||
|
text: this.i18n.t('protocols.index.import_protocols_io')
|
||||||
|
});
|
||||||
|
|
||||||
|
left.push(importMenu);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
left,
|
||||||
|
right: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateTable() {
|
||||||
|
this.newProtocol = false;
|
||||||
|
this.reloadingTable = true;
|
||||||
|
},
|
||||||
|
create() {
|
||||||
|
this.newProtocol = true;
|
||||||
|
},
|
||||||
|
duplicate(event, rows) {
|
||||||
|
axios.post(event.path, { protocol_ids: rows.map((row) => row.id) }).then((response) => {
|
||||||
|
this.updateTable();
|
||||||
|
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||||
|
}).catch((error) => {
|
||||||
|
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
versions(_event, rows) {
|
||||||
|
[this.VersionsModalObject] = rows;
|
||||||
|
},
|
||||||
|
archive(event, rows) {
|
||||||
|
axios.post(event.path, { protocol_ids: rows.map((row) => row.id) }).then((response) => {
|
||||||
|
this.updateTable();
|
||||||
|
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||||
|
}).catch((error) => {
|
||||||
|
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
restore(event, rows) {
|
||||||
|
axios.post(event.path, { protocol_ids: rows.map((row) => row.id) }).then((response) => {
|
||||||
|
this.updateTable();
|
||||||
|
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||||
|
}).catch((error) => {
|
||||||
|
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
exportProtocol(event) {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = event.path;
|
||||||
|
link.click();
|
||||||
|
},
|
||||||
|
importFile() {
|
||||||
|
const fileInput = document.querySelector('#importFileInput');
|
||||||
|
fileInput.click();
|
||||||
|
},
|
||||||
|
importProtocolsIo() {
|
||||||
|
const protocolIoButton = document.querySelector('#importProtocolsIo');
|
||||||
|
protocolIoButton.click();
|
||||||
|
},
|
||||||
|
importDocx() {
|
||||||
|
const docxButton = document.querySelector('#importDocx');
|
||||||
|
docxButton.click();
|
||||||
|
},
|
||||||
|
access(_event, rows) {
|
||||||
|
this.accessModalParams = {
|
||||||
|
object: rows[0],
|
||||||
|
roles_path: this.userRolesUrl
|
||||||
|
};
|
||||||
|
},
|
||||||
|
linkedMyModules(_event, rows) {
|
||||||
|
[this.linkedMyModulesModalObject] = rows;
|
||||||
|
},
|
||||||
|
// renderers
|
||||||
|
nameRenderer(params) {
|
||||||
|
const { urls, name } = params.data;
|
||||||
|
if (urls.show) {
|
||||||
|
return `<a href="${urls.show}">${name}</a>`;
|
||||||
|
}
|
||||||
|
return `<span class="text-sn-grey">${name}</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
@ -1,18 +1,30 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex py-4 items-center justify-between">
|
<div class="flex py-4 items-center justify-between">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<a v-for="action in toolbarActions.left" :key="action.label"
|
<template v-for="action in toolbarActions.left" :key="action.label">
|
||||||
:class="action.buttonStyle"
|
<a v-if="action.type === 'emit'"
|
||||||
:href="action.path"
|
:class="action.buttonStyle"
|
||||||
@click="doAction(action, $event)">
|
:href="action.path"
|
||||||
<i :class="action.icon"></i>
|
@click="doAction(action, $event)">
|
||||||
{{ action.label }}
|
<i :class="action.icon"></i>
|
||||||
</a>
|
{{ action.label }}
|
||||||
|
</a>
|
||||||
|
<MenuDropdown
|
||||||
|
v-if="action.type === 'menu'"
|
||||||
|
:listItems="action.menuItems"
|
||||||
|
:btnClasses="action.buttonStyle"
|
||||||
|
:btnText="action.label"
|
||||||
|
:btnIcon="action.icon"
|
||||||
|
:caret="true"
|
||||||
|
:position="'right'"
|
||||||
|
@dtEvent="handleEvent"
|
||||||
|
></MenuDropdown>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<MenuDropdown
|
<MenuDropdown
|
||||||
v-if="archivedPageUrl"
|
v-if="viewRenders"
|
||||||
:listItems="this.viewRendersMenu"
|
:listItems="this.viewRendersMenu"
|
||||||
:btnClasses="'btn btn-light icon-btn'"
|
:btnClasses="'btn btn-light icon-btn'"
|
||||||
:btnText="i18n.t(`toolbar.${currentViewRender}_view`)"
|
:btnText="i18n.t(`toolbar.${currentViewRender}_view`)"
|
||||||
|
|
@ -59,7 +71,9 @@
|
||||||
:placeholder="'Search...'"
|
:placeholder="'Search...'"
|
||||||
@change="$emit('search:change', $event.target.value)"
|
@change="$emit('search:change', $event.target.value)"
|
||||||
/>
|
/>
|
||||||
<i class="sn-icon sn-icon-search !m-2.5 !ml-auto right-0"></i>
|
<i v-if="searchValue.length === 0" class="sn-icon sn-icon-search !m-2.5 !ml-auto right-0"></i>
|
||||||
|
<i v-else class="sn-icon sn-icon-close !m-2.5 !ml-auto right-0 cursor-pointer z-10"
|
||||||
|
@click="$emit('search:change', '')"></i>
|
||||||
</div>
|
</div>
|
||||||
<FilterDropdown v-else :filters="filters" @applyFilters="applyFilters" />
|
<FilterDropdown v-else :filters="filters" @applyFilters="applyFilters" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -105,7 +119,6 @@ export default {
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
viewRenders: {
|
viewRenders: {
|
||||||
type: Array,
|
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
currentViewRender: {
|
currentViewRender: {
|
||||||
|
|
@ -184,6 +197,9 @@ export default {
|
||||||
},
|
},
|
||||||
applyFilters(filters) {
|
applyFilters(filters) {
|
||||||
this.$emit('applyFilters', filters);
|
this.$emit('applyFilters', filters);
|
||||||
|
},
|
||||||
|
handleEvent(event) {
|
||||||
|
this.$emit('toolbar:action', { name: event });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ class Protocol < ApplicationRecord
|
||||||
dependent: :destroy
|
dependent: :destroy
|
||||||
has_many :protocol_keywords, through: :protocol_protocol_keywords
|
has_many :protocol_keywords, through: :protocol_protocol_keywords
|
||||||
has_many :steps, inverse_of: :protocol, dependent: :destroy
|
has_many :steps, inverse_of: :protocol, dependent: :destroy
|
||||||
|
has_many :users, through: :user_assignments
|
||||||
|
|
||||||
def self.search(user,
|
def self.search(user,
|
||||||
include_archived,
|
include_archived,
|
||||||
|
|
|
||||||
104
app/serializers/lists/protocol_serializer.rb
Normal file
104
app/serializers/lists/protocol_serializer.rb
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Lists
|
||||||
|
class ProtocolSerializer < ActiveModel::Serializer
|
||||||
|
include Canaid::Helpers::PermissionsHelper
|
||||||
|
include Rails.application.routes.url_helpers
|
||||||
|
|
||||||
|
attributes :name, :code, :keywords, :linked_tasks, :nr_of_versions, :assigned_users, :published_by,
|
||||||
|
:published_on, :updated_at, :archived_by, :archived_on, :urls, :default_public_user_role_id,
|
||||||
|
:hidden, :top_level_assignable, :has_draft, :team
|
||||||
|
|
||||||
|
def keywords
|
||||||
|
object.protocol_keywords.map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def team
|
||||||
|
object.team.name
|
||||||
|
end
|
||||||
|
|
||||||
|
def linked_tasks
|
||||||
|
object.nr_of_linked_tasks
|
||||||
|
end
|
||||||
|
|
||||||
|
def assigned_users
|
||||||
|
object.user_assignments.map do |ua|
|
||||||
|
{
|
||||||
|
avatar: avatar_path(ua.user, :icon_small),
|
||||||
|
full_name: ua.user_name_with_role
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_draft
|
||||||
|
if object.in_repository_published_original? || object.in_repository_published_version?
|
||||||
|
parent = object.parent || object
|
||||||
|
parent.draft.present?
|
||||||
|
else
|
||||||
|
object.in_repository_draft?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def published_by
|
||||||
|
object.published_by&.full_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def published_on
|
||||||
|
I18n.l(object.published_on, format: :full) if object.published_on
|
||||||
|
end
|
||||||
|
|
||||||
|
def updated_at
|
||||||
|
I18n.l(object.updated_at, format: :full) if object.updated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def archived_by
|
||||||
|
object.archived_by&.full_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def archived_on
|
||||||
|
I18n.l(object.archived_on, format: :full) if object.archived_on
|
||||||
|
end
|
||||||
|
|
||||||
|
delegate :default_public_user_role_id, to: :object
|
||||||
|
|
||||||
|
def hidden
|
||||||
|
object.hidden?
|
||||||
|
end
|
||||||
|
|
||||||
|
def top_level_assignable
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def urls
|
||||||
|
urls_list = {
|
||||||
|
show_access: access_permissions_protocol_path(object),
|
||||||
|
versions_list: versions_list_protocol_path(object),
|
||||||
|
linked_my_modules: linked_children_protocol_path(object.parent || object),
|
||||||
|
versions_modal: versions_modal_protocol_path(object.parent || object)
|
||||||
|
}
|
||||||
|
|
||||||
|
if can_read_protocol_in_repository?(object)
|
||||||
|
urls_list[:show] = if object.in_repository_published_original? && object.latest_published_version.present?
|
||||||
|
protocol_path(object.latest_published_version)
|
||||||
|
else
|
||||||
|
protocol_path(object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if has_draft
|
||||||
|
object.initial_draft? ? object : object.draft
|
||||||
|
urls_list[:show_draft] = protocol_path(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
if can_manage_protocol_users?(object)
|
||||||
|
urls_list[:update_access] = access_permissions_protocol_path(object)
|
||||||
|
urls_list[:new_access] = new_access_permissions_protocol_path(id: object.id)
|
||||||
|
urls_list[:create_access] = access_permissions_protocols_path(id: object.id)
|
||||||
|
urls_list[:default_public_user_role_path] =
|
||||||
|
update_default_public_user_role_access_permissions_protocol_path(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
urls_list
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
43
app/serializers/protocol_draft_serializer.rb
Normal file
43
app/serializers/protocol_draft_serializer.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ProtocolDraftSerializer < ActiveModel::Serializer
|
||||||
|
include Rails.application.routes.url_helpers
|
||||||
|
include Canaid::Helpers::PermissionsHelper
|
||||||
|
|
||||||
|
attributes :id, :previous_number, :modified_on, :modified_by, :comment, :urls
|
||||||
|
|
||||||
|
def previous_number
|
||||||
|
object.previous_version&.version_number
|
||||||
|
end
|
||||||
|
|
||||||
|
def modified_on
|
||||||
|
I18n.l(object.updated_at, format: :full_date) if object.updated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def modified_by
|
||||||
|
object.last_modified_by&.full_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def comment
|
||||||
|
object.version_comment
|
||||||
|
end
|
||||||
|
|
||||||
|
def urls
|
||||||
|
current_user = scope
|
||||||
|
urls_list = {
|
||||||
|
show: protocol_path(object)
|
||||||
|
}
|
||||||
|
|
||||||
|
urls_list[:publish] = publish_protocol_path(object) if can_publish_protocol_in_repository?(current_user, object)
|
||||||
|
if can_delete_protocol_draft_in_repository?(current_user, object)
|
||||||
|
urls_list[:destroy] = destroy_draft_protocol_path(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
if can_manage_protocol_draft_in_repository?(current_user, object) &&
|
||||||
|
can_publish_protocol_in_repository?(current_user, object)
|
||||||
|
urls_list[:comment] = update_version_comment_protocol_path(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
urls_list
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -96,7 +96,9 @@ class ProtocolSerializer < ActiveModel::Serializer
|
||||||
save_as_draft_url: save_as_draft_url,
|
save_as_draft_url: save_as_draft_url,
|
||||||
versions_modal_url: versions_modal_url,
|
versions_modal_url: versions_modal_url,
|
||||||
version_comment_url: version_comment_url,
|
version_comment_url: version_comment_url,
|
||||||
print_protocol_url: print_protocol_url
|
print_protocol_url: print_protocol_url,
|
||||||
|
versions_modal: versions_modal_protocol_path(object.parent || object),
|
||||||
|
redirect_to_protocols: protocols_path
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
36
app/serializers/protocol_version_serializer.rb
Normal file
36
app/serializers/protocol_version_serializer.rb
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ProtocolVersionSerializer < ActiveModel::Serializer
|
||||||
|
include Rails.application.routes.url_helpers
|
||||||
|
include Canaid::Helpers::PermissionsHelper
|
||||||
|
|
||||||
|
attributes :id, :number, :published_on, :published_by, :comment, :urls
|
||||||
|
|
||||||
|
def urls
|
||||||
|
current_user = scope
|
||||||
|
urls_list = {
|
||||||
|
show: protocol_path(object)
|
||||||
|
}
|
||||||
|
if can_save_protocol_version_as_draft?(current_user, object)
|
||||||
|
urls_list[:save_as_draft] = save_as_draft_protocol_path(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
urls_list
|
||||||
|
end
|
||||||
|
|
||||||
|
def number
|
||||||
|
object.version_number
|
||||||
|
end
|
||||||
|
|
||||||
|
def published_on
|
||||||
|
I18n.l(object.published_on, format: :full_date) if object.published_on
|
||||||
|
end
|
||||||
|
|
||||||
|
def published_by
|
||||||
|
object.published_by&.full_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def comment
|
||||||
|
object.version_comment
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -47,6 +47,8 @@ class UserAssignmentSerializer < ActiveModel::Serializer
|
||||||
parent.user_assignments.find_by(user: object.user)
|
parent.user_assignments.find_by(user: object.user)
|
||||||
when Experiment
|
when Experiment
|
||||||
parent_assignment(parent.permission_parent)
|
parent_assignment(parent.permission_parent)
|
||||||
|
when Team
|
||||||
|
object
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
88
app/services/lists/protocols_service.rb
Normal file
88
app/services/lists/protocols_service.rb
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Lists
|
||||||
|
class ProtocolsService < BaseService
|
||||||
|
PREFIXED_ID_SQL = "('#{Protocol::ID_PREFIX}' || COALESCE(\"protocols\".\"parent_id\", \"protocols\".\"id\"))".freeze
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fetch_records
|
||||||
|
@records = @raw_data.preload(:parent, :latest_published_version, :draft,
|
||||||
|
:protocol_keywords, user_assignments: %i(user user_role))
|
||||||
|
.joins("LEFT OUTER JOIN protocols protocol_versions " \
|
||||||
|
"ON protocol_versions.protocol_type = #{
|
||||||
|
Protocol.protocol_types[:in_repository_published_version]} " \
|
||||||
|
"AND protocol_versions.parent_id = protocols.parent_id")
|
||||||
|
.joins("LEFT OUTER JOIN protocols self_linked_task_protocols " \
|
||||||
|
"ON self_linked_task_protocols.protocol_type = #{
|
||||||
|
Protocol.protocol_types[:linked]} " \
|
||||||
|
"AND self_linked_task_protocols.parent_id = protocols.id")
|
||||||
|
.joins("LEFT OUTER JOIN protocols parent_linked_task_protocols " \
|
||||||
|
"ON parent_linked_task_protocols.protocol_type = #{
|
||||||
|
Protocol.protocol_types[:linked]} " \
|
||||||
|
"AND parent_linked_task_protocols.parent_id = protocols.parent_id")
|
||||||
|
.joins("LEFT OUTER JOIN protocols version_linked_task_protocols " \
|
||||||
|
"ON version_linked_task_protocols.protocol_type = #{
|
||||||
|
Protocol.protocol_types[:linked]} " \
|
||||||
|
"AND version_linked_task_protocols.parent_id = protocol_versions.id " \
|
||||||
|
"AND version_linked_task_protocols.parent_id != protocols.id")
|
||||||
|
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" ' \
|
||||||
|
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
|
||||||
|
.joins('LEFT OUTER JOIN "protocol_keywords" ' \
|
||||||
|
'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
|
||||||
|
.joins('LEFT OUTER JOIN "users" "archived_users"
|
||||||
|
ON "archived_users"."id" = "protocols"."archived_by_id"')
|
||||||
|
.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."published_by_id"')
|
||||||
|
.joins('LEFT OUTER JOIN "user_assignments" "all_user_assignments" ' \
|
||||||
|
'ON "all_user_assignments"."assignable_type" = \'Protocol\' ' \
|
||||||
|
'AND "all_user_assignments"."assignable_id" = "protocols"."id"')
|
||||||
|
.group('"protocols"."id"')
|
||||||
|
.select(
|
||||||
|
'"protocols".*',
|
||||||
|
'COALESCE("protocols"."parent_id", "protocols"."id") AS adjusted_parent_id',
|
||||||
|
'STRING_AGG(DISTINCT("protocol_keywords"."name"), \', \') AS "protocol_keywords_str"',
|
||||||
|
"CASE WHEN protocols.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} " \
|
||||||
|
"THEN 0 ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \
|
||||||
|
"END AS nr_of_versions",
|
||||||
|
'(COUNT(DISTINCT("self_linked_task_protocols"."id")) + ' \
|
||||||
|
'COUNT(DISTINCT("parent_linked_task_protocols"."id")) + ' \
|
||||||
|
'COUNT(DISTINCT("version_linked_task_protocols"."id"))) AS nr_of_linked_tasks',
|
||||||
|
'COUNT(DISTINCT("all_user_assignments"."id")) AS "nr_of_assigned_users"',
|
||||||
|
'MAX("users"."full_name") AS "full_username_str"', # "Hack" to get single username
|
||||||
|
'MAX("archived_users"."full_name") AS "archived_full_username_str"'
|
||||||
|
)
|
||||||
|
|
||||||
|
view_mode = @params[:view_mode] || 'active'
|
||||||
|
|
||||||
|
@records = @records.archived if view_mode == 'archived'
|
||||||
|
@records = @records.active if view_mode == 'active'
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_records
|
||||||
|
return if @params[:search].blank?
|
||||||
|
|
||||||
|
@records = @records.where(
|
||||||
|
"LOWER(\"protocols\".\"name\") LIKE :search OR
|
||||||
|
LOWER(\"protocol_keywords\".\"name\") LIKE :search OR
|
||||||
|
LOWER(#{PREFIXED_ID_SQL}) LIKE :search",
|
||||||
|
search: "%#{@params[:search].to_s.downcase}%"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sortable_columns
|
||||||
|
@sortable_columns ||= {
|
||||||
|
name: 'name',
|
||||||
|
parent_id: 'adjusted_parent_id',
|
||||||
|
versions: 'nr_of_versions',
|
||||||
|
keywords: 'protocol_keywords_str',
|
||||||
|
linked_tasks: 'nr_of_linked_tasks',
|
||||||
|
assigned_users: 'nr_of_assigned_users',
|
||||||
|
published_by: 'full_username_str',
|
||||||
|
published_on: 'published_on',
|
||||||
|
udpated_at: 'updated_at',
|
||||||
|
archived_by: 'archived_full_username_str',
|
||||||
|
archived_on: 'archived_on'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -39,8 +39,7 @@ module Toolbars
|
||||||
name: 'versions',
|
name: 'versions',
|
||||||
label: I18n.t('protocols.index.toolbar.versions'),
|
label: I18n.t('protocols.index.toolbar.versions'),
|
||||||
icon: 'sn-icon sn-icon-versions',
|
icon: 'sn-icon sn-icon-versions',
|
||||||
button_id: 'protocolVersions',
|
type: :emit
|
||||||
type: :legacy
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -55,29 +54,18 @@ module Toolbars
|
||||||
label: I18n.t('protocols.index.toolbar.duplicate'),
|
label: I18n.t('protocols.index.toolbar.duplicate'),
|
||||||
icon: 'sn-icon sn-icon-duplicate',
|
icon: 'sn-icon sn-icon-duplicate',
|
||||||
path: clone_protocols_path,
|
path: clone_protocols_path,
|
||||||
type: :request,
|
type: :emit
|
||||||
request_method: :post
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def access_action
|
def access_action
|
||||||
return unless @single
|
return unless @single
|
||||||
|
|
||||||
protocol = @protocols.first
|
|
||||||
|
|
||||||
path = if can_manage_protocol_users?(protocol)
|
|
||||||
edit_access_permissions_protocol_path(protocol)
|
|
||||||
else
|
|
||||||
access_permissions_protocol_path(protocol)
|
|
||||||
end
|
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'access',
|
name: 'access',
|
||||||
label: I18n.t('protocols.index.toolbar.access'),
|
label: I18n.t('protocols.index.toolbar.access'),
|
||||||
icon: 'sn-icon sn-icon-project-member-access',
|
icon: 'sn-icon sn-icon-project-member-access',
|
||||||
button_class: 'access-btn',
|
type: :emit
|
||||||
path: path,
|
|
||||||
type: 'remote-modal'
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -91,7 +79,7 @@ module Toolbars
|
||||||
label: I18n.t('protocols.index.toolbar.export'),
|
label: I18n.t('protocols.index.toolbar.export'),
|
||||||
icon: 'sn-icon sn-icon-export',
|
icon: 'sn-icon sn-icon-export',
|
||||||
path: export_protocols_path(protocol_ids: @protocols.pluck(:id)),
|
path: export_protocols_path(protocol_ids: @protocols.pluck(:id)),
|
||||||
type: :download
|
type: :emit
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -103,8 +91,7 @@ module Toolbars
|
||||||
label: I18n.t('protocols.index.toolbar.archive'),
|
label: I18n.t('protocols.index.toolbar.archive'),
|
||||||
icon: 'sn-icon sn-icon-archive',
|
icon: 'sn-icon sn-icon-archive',
|
||||||
path: archive_protocols_path,
|
path: archive_protocols_path,
|
||||||
type: :request,
|
type: :emit
|
||||||
request_method: :post
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -112,12 +99,11 @@ module Toolbars
|
||||||
return unless @protocols.all? { |p| can_restore_protocol_in_repository?(p) }
|
return unless @protocols.all? { |p| can_restore_protocol_in_repository?(p) }
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'archive',
|
name: 'restore',
|
||||||
label: I18n.t('protocols.index.toolbar.restore'),
|
label: I18n.t('protocols.index.toolbar.restore'),
|
||||||
icon: 'sn-icon sn-icon-restore',
|
icon: 'sn-icon sn-icon-restore',
|
||||||
path: restore_protocols_path,
|
path: restore_protocols_path,
|
||||||
type: :request,
|
type: :emit
|
||||||
request_method: :post
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -25,42 +25,54 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="protocols-container">
|
<div class="protocols-container">
|
||||||
<%= render partial: "protocols/index/protocols_datatable" %>
|
<div id="ProtocolsTable" class="fixed-content-body">
|
||||||
|
<protocols-table
|
||||||
<div id="actionToolbar" data-behaviour="vue">
|
ref="table"
|
||||||
<action-toolbar actions-url="<%= actions_toolbar_protocols_url %>" />
|
:actions-url="'<%= actions_toolbar_protocols_url %>'"
|
||||||
|
:data-source="'<%= protocols_path(format: :json) %>'"
|
||||||
|
:active-page-url="'<%= protocols_path %>'"
|
||||||
|
:archived-page-url="'<%= protocols_path(view_mode: :archived) %>'"
|
||||||
|
current-view-mode="<%= params[:view_mode] || :active %>"
|
||||||
|
:docx-parser-enabled="<%= Protocol.docx_parser_enabled? %>"
|
||||||
|
user-roles-url="<%= user_roles_projects_path %>"
|
||||||
|
:create-url="'<%= protocols_path if can_create_protocols_in_repository?(current_team) %>'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<%= javascript_include_tag 'vue_protocols_list' %>
|
||||||
|
|
||||||
|
<!-- Legacy code -->
|
||||||
|
<a class="btn-open-file tw-hidden" data-action='import' >
|
||||||
|
<input type="file" ref="importFileBtn" value="" id="importFileInput" accept=".eln" data-role="import-file-input"
|
||||||
|
data-team-id="<%= current_team.id %>" data-import-url="<%= import_protocols_path %>">
|
||||||
|
</a>
|
||||||
|
<a class="btn-open-file tw-hidden" data-action='import' >
|
||||||
|
<input type="file" value="" id="importDocx" accept=".docx" data-role="import-file-input"
|
||||||
|
data-team-id="<%= @current_team.id %>" data-import-url="<%= import_protocols_path %>">
|
||||||
|
</a>
|
||||||
|
<%= link_to t("protocols.index.import_protocols_io"), '',
|
||||||
|
id: "importProtocolsIo",
|
||||||
|
class: 'tw-hidden',
|
||||||
|
data: { target: '#protocolsioModal', toggle: 'modal' } %>
|
||||||
|
|
||||||
|
<%= render partial: "protocols/index/protocolsio_modal" %>
|
||||||
|
<div id="protocolsio-preview-modal-target"></div>
|
||||||
|
<%= javascript_include_tag "protocols/index" %>
|
||||||
|
<%= javascript_include_tag "handsontable.full" %>
|
||||||
|
<%= render partial: "shared/formulas_libraries" %>
|
||||||
|
|
||||||
<div id="protocolFileImportModal">
|
<div id="protocolFileImportModal">
|
||||||
<protocol-file-import-modal
|
<protocol-file-import-modal
|
||||||
import-url="<%= import_docx_protocols_path %>"
|
import-url="<%= import_docx_protocols_path %>"
|
||||||
protocol-template-table-url="<%= protocols_path %>"
|
protocol-template-table-url="<%= protocols_path %>"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<%= javascript_include_tag 'vue_protocol_file_import_modal' %>
|
|
||||||
|
|
||||||
<div id="protocolsio-preview-modal-target"></div>
|
|
||||||
<%= render partial: "protocols/index/general_toolbar" %>
|
|
||||||
<%= render partial: "protocols/index/protocol_filters" %>
|
|
||||||
<%= render partial: "protocols/index/delete_draft_modal" %>
|
|
||||||
<%= render partial: "protocols/index/linked_children_modal" %>
|
|
||||||
<%= render partial: "protocols/index/protocol_preview_modal" %>
|
|
||||||
<%= render partial: "protocols/index/protocolsio_modal" %>
|
|
||||||
<%= render partial: "protocols/index/new_protocol_modal", locals: {type: 'new'} %>
|
|
||||||
|
|
||||||
<%= render partial: "protocols/import_export/import_elements" %>
|
<%= render partial: "protocols/import_export/import_elements" %>
|
||||||
<%= javascript_include_tag "vue_components_action_toolbar" %>
|
<%= javascript_include_tag 'vue_protocol_file_import_modal' %>
|
||||||
<%= javascript_include_tag "handsontable.full" %>
|
|
||||||
|
|
||||||
<!-- Libraries for formulas -->
|
|
||||||
<%= render partial: "shared/formulas_libraries" %>
|
|
||||||
|
|
||||||
<%= stylesheet_link_tag 'datatables' %>
|
|
||||||
<%= javascript_include_tag "assets/wopi/create_wopi_file" %>
|
<%= javascript_include_tag "assets/wopi/create_wopi_file" %>
|
||||||
<%= javascript_include_tag "protocols/index" %>
|
|
||||||
<%= javascript_include_tag "protocols/new_protocol" %>
|
|
||||||
<%= javascript_include_tag 'pdf_js' %>
|
<%= javascript_include_tag 'pdf_js' %>
|
||||||
<%= stylesheet_link_tag 'pdf_js_styles' %>
|
<%= stylesheet_link_tag 'pdf_js_styles' %>
|
||||||
|
|
|
||||||
|
|
@ -633,6 +633,7 @@ Rails.application.routes.draw do
|
||||||
post :publish
|
post :publish
|
||||||
post :destroy_draft
|
post :destroy_draft
|
||||||
post :save_as_draft
|
post :save_as_draft
|
||||||
|
get :versions_list
|
||||||
get 'version_comment', to: 'protocols#version_comment'
|
get 'version_comment', to: 'protocols#version_comment'
|
||||||
get 'print', to: 'protocols#print'
|
get 'print', to: 'protocols#print'
|
||||||
get 'linked_children', to: 'protocols#linked_children'
|
get 'linked_children', to: 'protocols#linked_children'
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,9 @@ const entryList = {
|
||||||
vue_projects_list: './app/javascript/packs/vue/projects_list.js',
|
vue_projects_list: './app/javascript/packs/vue/projects_list.js',
|
||||||
vue_experiments_list: './app/javascript/packs/vue/experiments_list.js',
|
vue_experiments_list: './app/javascript/packs/vue/experiments_list.js',
|
||||||
vue_my_modules_list: './app/javascript/packs/vue/my_modules_list.js',
|
vue_my_modules_list: './app/javascript/packs/vue/my_modules_list.js',
|
||||||
vue_design_system_select: './app/javascript/packs/vue/design_system/select.js'
|
vue_design_system_select: './app/javascript/packs/vue/design_system/select.js',
|
||||||
}
|
vue_protocols_list: './app/javascript/packs/vue/protocols_list.js'
|
||||||
|
};
|
||||||
|
|
||||||
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
||||||
// Get paths to all engines' folders
|
// Get paths to all engines' folders
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue