mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-08 14:15:35 +08:00
Implement new toolbar for protocols [SCI-8399]
This commit is contained in:
parent
a52ee99781
commit
eb5fb16b1b
9 changed files with 179 additions and 94 deletions
|
@ -6,7 +6,6 @@
|
|||
// Global variables
|
||||
var ProtocolsIndex = (function() {
|
||||
const ALL_VERSIONS_VALUE = 'All';
|
||||
var PERMISSIONS = ['archivable', 'restorable', 'copyable', 'readable'];
|
||||
var rowsSelected = [];
|
||||
var protocolsTableEl = null;
|
||||
var protocolsDatatable = null;
|
||||
|
@ -28,6 +27,10 @@ var ProtocolsIndex = (function() {
|
|||
* Initializes page
|
||||
*/
|
||||
function init() {
|
||||
window.initActionToolbar();
|
||||
window.actionToolbarComponent.setReloadCallback(reloadTable);
|
||||
// make room for pagination
|
||||
window.actionToolbarComponent.setBottomOffset(75);
|
||||
updateButtons();
|
||||
initProtocolsTable();
|
||||
initKeywordFiltering();
|
||||
|
@ -256,9 +259,7 @@ var ProtocolsIndex = (function() {
|
|||
DataTableHelpers.initSearchField(dataTableWrapper, I18n.t('protocols.index.search_bar_placeholder'));
|
||||
dataTableWrapper.find('.main-actions, .pagination-row').removeClass('hidden');
|
||||
|
||||
let actionToolBar = $($('#protocolActionToolbar').html());
|
||||
let generalToolbar = $($('#protocolGeneralToolbar').html());
|
||||
$('.protocols-container .actions-toolbar').html(actionToolBar);
|
||||
$('.protocols-container .toolbar').html(generalToolbar);
|
||||
|
||||
let protocolFilters = $($('#protocolFilters').html());
|
||||
|
@ -285,26 +286,6 @@ var ProtocolsIndex = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
function checkActionPermission(permission) {
|
||||
let allProtocols;
|
||||
|
||||
allProtocols = rowsSelected.every((id) => {
|
||||
return protocolsTableEl.find(`tbody tr#${id}`).data(permission);
|
||||
});
|
||||
|
||||
return allProtocols;
|
||||
}
|
||||
|
||||
function loadPermission(id) {
|
||||
let row = protocolsTableEl.find(`tbody tr#${id}`);
|
||||
$.get(row.data('permissions-url'), (result) => {
|
||||
PERMISSIONS.forEach((permission) => {
|
||||
row.data(permission, result[permission]);
|
||||
});
|
||||
updateButtons();
|
||||
});
|
||||
}
|
||||
|
||||
function initRowSelection() {
|
||||
let protocolsTableScrollHead = protocolsTableEl.closest('.dataTables_scroll').find('.dataTables_scrollHead');
|
||||
|
||||
|
@ -335,13 +316,12 @@ var ProtocolsIndex = (function() {
|
|||
|
||||
updateDataTableSelectAllCheckbox();
|
||||
if (this.checked) {
|
||||
loadPermission(rowId);
|
||||
row.addClass('selected');
|
||||
} else {
|
||||
row.removeClass('selected');
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
|
@ -435,7 +415,8 @@ var ProtocolsIndex = (function() {
|
|||
e.preventDefault();
|
||||
});
|
||||
|
||||
$(protocolsContainer).on('click', '#protocolVersions', function() {
|
||||
$(protocolsContainer).on('click', '#protocolVersions', function(e) {
|
||||
e.stopPropagation();
|
||||
loadVersionModal($(`tr[data-row-id=${rowsSelected[0]}]`).data('versions-url'));
|
||||
});
|
||||
}
|
||||
|
@ -663,34 +644,7 @@ var ProtocolsIndex = (function() {
|
|||
}
|
||||
|
||||
function updateButtons() {
|
||||
let actionToolbar = $('.protocols-container .actions-toolbar');
|
||||
$('.dataTables_wrapper').addClass('show-actions');
|
||||
|
||||
if (rowsSelected.length === 0) {
|
||||
$('.dataTables_wrapper').removeClass('show-actions');
|
||||
} else if (rowsSelected.length === 1) {
|
||||
actionToolbar.find('.single-object-action, .multiple-object-action').removeClass('hidden');
|
||||
} else {
|
||||
actionToolbar.find('.single-object-action').addClass('hidden');
|
||||
actionToolbar.find('.multiple-object-action').removeClass('hidden');
|
||||
}
|
||||
|
||||
PERMISSIONS.forEach((permission) => {
|
||||
if (!checkActionPermission(permission)) {
|
||||
actionToolbar.find(`.btn[data-for="${permission}"]`).addClass('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
if (protocolsDatatable) protocolsDatatable.columns.adjust();
|
||||
|
||||
actionToolbar.find('.btn').addClass('notransition');
|
||||
actionToolbar.find('.btn').removeClass('btn-primary').addClass('btn-light');
|
||||
actionToolbar.find('.btn:visible').first().addClass('btn-primary').removeClass('btn-light');
|
||||
setTimeout(function() {
|
||||
actionToolbar.find('.btn').removeClass('notransition');
|
||||
}, 500);
|
||||
|
||||
actionToolbar.find('.emptyPlaceholder').toggleClass('hidden', actionToolbar.find('.btn:visible').length > 0);
|
||||
window.actionToolbarComponent.fetchActions({ protocol_ids: rowsSelected.join(',') });
|
||||
}
|
||||
|
||||
function initLocalFileImport() {
|
||||
|
|
|
@ -5,4 +5,20 @@
|
|||
.btn.btn-light:hover {
|
||||
background: $color-white;
|
||||
}
|
||||
|
||||
.icon-versions {
|
||||
display: inline-block;
|
||||
height: 14px;
|
||||
background-image: image-url("icon_small/versions-black.svg");
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
margin-bottom: -2px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.sn-action-toolbar__button-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -921,6 +921,16 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def actions_toolbar
|
||||
render json: {
|
||||
actions:
|
||||
Toolbars::ProtocolsService.new(
|
||||
current_user,
|
||||
protocol_ids: params[:protocol_ids].split(',')
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_importer
|
||||
|
@ -1001,7 +1011,7 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
def check_clone_permissions
|
||||
load_team_and_type
|
||||
protocol = Protocol.find_by(id: params[:ids][0])
|
||||
protocol = Protocol.find_by(id: params[:protocol_ids].split(','))
|
||||
@original = protocol.latest_published_version_or_self
|
||||
|
||||
if @original.blank? ||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-if="loading || actions.length" class="sn-action-toolbar p-4 w-full fixed bottom-0 rounded-t-md shadow-[0_-12px_24px_-12px_rgba(35,31,32,0.2)]" :style="`width: ${width}px`">
|
||||
<div v-if="loading || actions.length" class="sn-action-toolbar p-4 w-full fixed bottom-0 rounded-t-md shadow-[0_-12px_24px_-12px_rgba(35,31,32,0.2)]" :style="`width: ${width}px; bottom: ${bottom}px;`">
|
||||
<div class="sn-action-toolbar__actions flex">
|
||||
<div v-if="loading && !actions.length" class="sn-action-toolbar__action">
|
||||
<a class="btn btn-light"></a>
|
||||
|
@ -12,9 +12,9 @@
|
|||
:data-object-type="action.item_type"
|
||||
:data-object-id="action.item_id"
|
||||
:data-action="action.type"
|
||||
@click="doAction(action)">
|
||||
@click="doAction(action, $event)">
|
||||
<i class="mr-1" :class="action.icon"></i>
|
||||
{{ action.label }}
|
||||
<span class="sn-action-toolbar__button-text">{{ action.label }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -37,7 +37,8 @@
|
|||
params: {},
|
||||
reloadCallback: null,
|
||||
loading: false,
|
||||
width: 0
|
||||
width: 0,
|
||||
bottom: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -51,7 +52,7 @@
|
|||
this.actions = data.actions;
|
||||
this.loading = false;
|
||||
});
|
||||
}, 200);
|
||||
}, 10);
|
||||
},
|
||||
mounted() {
|
||||
this.setWidth();
|
||||
|
@ -63,6 +64,9 @@
|
|||
setWidth() {
|
||||
this.width = $(this.$el).parent().width();
|
||||
},
|
||||
setBottomOffset(pixels) {
|
||||
this.bottom = pixels;
|
||||
},
|
||||
fetchActions(params) {
|
||||
this.loading = true;
|
||||
this.debouncedFetchActions(params);
|
||||
|
@ -70,7 +74,7 @@
|
|||
setReloadCallback(func) {
|
||||
this.reloadCallback = func;
|
||||
},
|
||||
doAction(action) {
|
||||
doAction(action, event) {
|
||||
switch(action.type) {
|
||||
case 'legacy':
|
||||
// do nothing, this is handled by legacy code based on the button class
|
||||
|
@ -81,7 +85,13 @@
|
|||
case 'remote-modal':
|
||||
// do nothing, handled by the data-action="remote-modal" binding
|
||||
break;
|
||||
case 'download':
|
||||
event.stopPropagation();
|
||||
window.location.href = action.path;
|
||||
break;
|
||||
case 'request':
|
||||
event.stopPropagation();
|
||||
|
||||
$.ajax({
|
||||
type: action.request_method,
|
||||
url: action.path,
|
||||
|
|
120
app/services/toolbars/protocols_service.rb
Normal file
120
app/services/toolbars/protocols_service.rb
Normal file
|
@ -0,0 +1,120 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Toolbars
|
||||
class ProtocolsService
|
||||
attr_reader :current_user
|
||||
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
include ActionView::Helpers::AssetUrlHelper
|
||||
|
||||
def initialize(current_user, protocol_ids: [])
|
||||
@current_user = current_user
|
||||
@protocols = Protocol.where(id: protocol_ids)
|
||||
|
||||
@single = @protocols.length == 1
|
||||
end
|
||||
|
||||
def actions
|
||||
return [] if @protocols.none?
|
||||
|
||||
[
|
||||
restore_action,
|
||||
versions_action,
|
||||
duplicate_action,
|
||||
access_action,
|
||||
export_action,
|
||||
archive_action
|
||||
].compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def versions_action
|
||||
return unless @single
|
||||
|
||||
{
|
||||
name: 'versions',
|
||||
label: I18n.t('protocols.index.toolbar.versions'),
|
||||
icon: 'icon-versions',
|
||||
button_id: 'protocolVersions',
|
||||
type: :legacy
|
||||
}
|
||||
end
|
||||
|
||||
def duplicate_action
|
||||
return unless @single
|
||||
|
||||
protocol = @protocols.first.latest_published_version_or_self
|
||||
return unless can_clone_protocol_in_repository?(protocol)
|
||||
|
||||
{
|
||||
name: 'duplicate',
|
||||
label: I18n.t('protocols.index.toolbar.duplicate'),
|
||||
icon: 'fas fa-copy',
|
||||
path: clone_protocols_path,
|
||||
type: :request,
|
||||
request_method: :post
|
||||
}
|
||||
end
|
||||
|
||||
def access_action
|
||||
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',
|
||||
label: I18n.t('protocols.index.toolbar.access'),
|
||||
icon: 'fas fa-door-open',
|
||||
button_class: 'access-btn',
|
||||
path: path,
|
||||
type: 'remote-modal'
|
||||
}
|
||||
end
|
||||
|
||||
def export_action
|
||||
return unless @single
|
||||
|
||||
{
|
||||
name: 'export',
|
||||
label: I18n.t('protocols.index.toolbar.export'),
|
||||
icon: 'fas fa-download',
|
||||
path: export_protocols_path(protocol_ids: @protocols.pluck(:id)),
|
||||
type: :download
|
||||
}
|
||||
end
|
||||
|
||||
def archive_action
|
||||
return unless @protocols.all? { |p| can_archive_protocol_in_repository?(p) }
|
||||
|
||||
{
|
||||
name: 'archive',
|
||||
label: I18n.t('protocols.index.toolbar.archive'),
|
||||
icon: 'fas fa-archive',
|
||||
path: archive_protocols_path,
|
||||
type: :request,
|
||||
request_method: :post
|
||||
}
|
||||
end
|
||||
|
||||
def restore_action
|
||||
return unless @protocols.all? { |p| can_restore_protocol_in_repository?(p) }
|
||||
|
||||
{
|
||||
name: 'archive',
|
||||
label: I18n.t('protocols.index.toolbar.restore'),
|
||||
icon: 'fas fa-undo',
|
||||
path: restore_protocols_path,
|
||||
type: :request,
|
||||
request_method: :post
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -26,13 +26,16 @@
|
|||
</div>
|
||||
<div class="protocols-container">
|
||||
<%= render partial: "protocols/index/protocols_datatable.html.erb" %>
|
||||
|
||||
<div id="actionToolbar" data-behaviour="vue">
|
||||
<action-toolbar actions-url="<%= actions_toolbar_protocols_url %>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div id="protocolsio-preview-modal-target"></div>
|
||||
<%= render partial: "protocols/index/general_toolbar.html.erb" %>
|
||||
<%= render partial: "protocols/index/action_toolbar.html.erb" %>
|
||||
<%= render partial: "protocols/index/protocol_filters.html.erb" %>
|
||||
<%= render partial: "protocols/index/delete_draft_modal.html.erb" %>
|
||||
<%= render partial: "protocols/index/linked_children_modal.html.erb" %>
|
||||
|
@ -41,7 +44,7 @@
|
|||
<%= render partial: "protocols/index/new_protocol_modal.html.erb", locals: {type: 'new'} %>
|
||||
|
||||
<%= render partial: "protocols/import_export/import_elements.html.erb" %>
|
||||
|
||||
<%= javascript_include_tag "vue_components_action_toolbar" %>
|
||||
<%= javascript_include_tag "handsontable.full" %>
|
||||
|
||||
<!-- Libraries for formulas -->
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<template id="protocolActionToolbar">
|
||||
<button id="restoreProtocol" class="btn btn-light multiple-object-action hidden only-archive" data-url="<%= restore_protocols_path %>" data-for="restorable">
|
||||
<i class="fas fa-undo"></i>
|
||||
<span class="button-text"><%= t("protocols.index.action_toolbar.restore") %></span>
|
||||
</button>
|
||||
<button id="protocolVersions" class="btn btn-light single-object-action hidden only-active" data-for="readable">
|
||||
<%= image_tag 'icon/versions.svg' %>
|
||||
<span class="button-text"><%= t("protocols.index.action_toolbar.versions") %></span>
|
||||
</button>
|
||||
<button id="manageProtocolAccess" class="btn btn-light single-object-action hidden">
|
||||
<i class="fas fa-door-open"></i>
|
||||
<span class="button-text"><%= t("protocols.index.action_toolbar.access") %></span>
|
||||
</button>
|
||||
<button id="duplicateProtocol" class="btn btn-light single-object-action hidden only-active" data-url="<%= clone_protocols_path %>" data-for="copyable">
|
||||
<i class="fas fa-copy"></i>
|
||||
<span class="button-text"><%= t("protocols.index.action_toolbar.duplicate") %></span>
|
||||
</button>
|
||||
<button id="exportProtocol" class="btn btn-light single-object-action hidden only-active" data-url="<%= export_protocols_path %>" data-for="readable">
|
||||
<i class="fas fa-download"></i>
|
||||
<span class="button-text"><%= t("protocols.index.action_toolbar.export") %></span>
|
||||
</button>
|
||||
<button id="archiveProtocol" class="btn btn-light multiple-object-action hidden only-active" data-url="<%= archive_protocols_path %>" data-for="archivable">
|
||||
<i class="fas fa-archive"></i>
|
||||
<span class="button-text"><%= t("protocols.index.action_toolbar.archive") %></span>
|
||||
</button>
|
||||
<div class="emptyPlaceholder hidden">
|
||||
<%= t("protocols.index.action_toolbar.empty_placeholder") %>
|
||||
</div>
|
||||
</template>
|
|
@ -2789,9 +2789,9 @@ en:
|
|||
search_bar_placeholder: 'Filter protocols'
|
||||
navigation:
|
||||
archive: "Archive"
|
||||
action_toolbar:
|
||||
toolbar:
|
||||
versions: "Versions"
|
||||
access: "Manage Access"
|
||||
access: "Access"
|
||||
duplicate: "Duplicate"
|
||||
export: "Export"
|
||||
archive: "Archive"
|
||||
|
|
|
@ -655,6 +655,7 @@ Rails.application.routes.draw do
|
|||
post 'protocolsio_import_save', to: 'protocols#protocolsio_import_save'
|
||||
get 'export', to: 'protocols#export'
|
||||
get 'protocolsio', to: 'protocols#protocolsio_index'
|
||||
get 'actions_toolbar', to: 'protocols#actions_toolbar'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue