From 2a8aaed7d1badf5b842c065ad81a14fdac0c49d6 Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 6 Dec 2022 15:54:50 +0100 Subject: [PATCH] Add actions toolbar to protocol repository [SCI-7521] --- app/assets/images/icon/versions.svg | 5 ++ app/assets/javascripts/protocols/index.js | 65 +++++++++++++++++-- app/assets/stylesheets/protocols/index.scss | 3 +- app/assets/stylesheets/shared/datatable.scss | 45 +++++++++++++ .../shared_styles/constants/transition.scss | 9 +++ .../shared_styles/elements/buttons.scss | 4 ++ app/controllers/protocols_controller.rb | 11 ++++ app/datatables/protocols_datatable.rb | 15 ++--- app/views/protocols/index.html.erb | 2 +- .../protocols/index/_action_toolbar.html.erb | 26 ++++++++ config/locales/en.yml | 7 ++ config/routes.rb | 1 + 12 files changed, 175 insertions(+), 18 deletions(-) create mode 100644 app/assets/images/icon/versions.svg create mode 100644 app/views/protocols/index/_action_toolbar.html.erb diff --git a/app/assets/images/icon/versions.svg b/app/assets/images/icon/versions.svg new file mode 100644 index 000000000..a9e0a8896 --- /dev/null +++ b/app/assets/images/icon/versions.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/assets/javascripts/protocols/index.js b/app/assets/javascripts/protocols/index.js index dc58cc5ec..93ce008d6 100644 --- a/app/assets/javascripts/protocols/index.js +++ b/app/assets/javascripts/protocols/index.js @@ -1,7 +1,9 @@ //= require protocols/import_export/import +/* eslint-disable no-use-before-define */ /* global ProtocolRepositoryHeader PdfPreview DataTableHelpers */ // Global variables +var PERMISSIONS = ['archivable', 'restorable', 'copyable']; var rowsSelected = []; var protocolsTableEl = null; var protocolsDatatable = null; @@ -27,7 +29,7 @@ function initProtocolsTable() { protocolsDatatable = protocolsTableEl.DataTable({ order: [[1, "asc"]], - dom: "R<'main-actions hidden'<'toolbar'><'filter-container'f>>t<'pagination-row hidden'<'pagination-info'li><'pagination-actions'p>>", + dom: "R<'main-actions hidden'<'toolbar'><'filter-container'f>>t<'pagination-row hidden'<'actions-toolbar'><'pagination-info'li><'pagination-actions'p>>", stateSave: true, sScrollX: '100%', sScrollXInner: '100%', @@ -115,6 +117,9 @@ function initProtocolsTable() { DataTableHelpers.initLengthAppearance(dataTableWrapper); DataTableHelpers.initSearchField(dataTableWrapper, 'Enter...'); dataTableWrapper.find('.main-actions, .pagination-row').removeClass('hidden'); + + let actionToolBar = $($('#protocolActionToolbar').html()); + $('.protocols-container .actions-toolbar').html(actionToolBar); }, stateLoadCallback: function (settings) { // Load the table state for the current team @@ -137,6 +142,26 @@ function initProtocolsTable() { }); } +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'); @@ -163,15 +188,16 @@ function initRowSelection() { rowsSelected.splice(index, 1); } + updateDataTableSelectAllCheckbox(); if (this.checked) { - row.addClass("selected"); + loadPermission(rowId); + row.addClass('selected'); } else { - row.removeClass("selected"); + row.removeClass('selected'); + updateButtons(); } - updateDataTableSelectAllCheckbox(); e.stopPropagation(); - updateButtons(); }); // Handle click on "Select all" control @@ -390,6 +416,34 @@ function updateDataTableSelectAllCheckbox() { } } +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'); + actionToolbar.find('.btn').removeClass('notransition'); +} + +/* function updateButtons() { var cloneBtn = $("[data-action='clone']"); var makePrivateBtn = $("[data-action='make-private']"); @@ -506,6 +560,7 @@ function updateButtons() { } } } +*/ function exportProtocols(ids) { if (ids.length > 0) { diff --git a/app/assets/stylesheets/protocols/index.scss b/app/assets/stylesheets/protocols/index.scss index 246462aa1..6dbde103f 100644 --- a/app/assets/stylesheets/protocols/index.scss +++ b/app/assets/stylesheets/protocols/index.scss @@ -3,6 +3,7 @@ .protocols-index { .protocols-datatable { --content-header-size: 5em; + --protocol-toolbar-size: 4em; height: calc(100vh - var(--navbar-height) - var(--content-header-size)); #protocols-table_wrapper { @@ -16,7 +17,7 @@ display: flex; flex-direction: column; flex-grow: 1; - height: calc(100% - var(--datatable-pagination-row)); + height: calc(100% - var(--datatable-pagination-row) - var(--protocol-toolbar-size)); } .dataTables_scrollBody { diff --git a/app/assets/stylesheets/shared/datatable.scss b/app/assets/stylesheets/shared/datatable.scss index 6c249b965..6b5ea33c4 100644 --- a/app/assets/stylesheets/shared/datatable.scss +++ b/app/assets/stylesheets/shared/datatable.scss @@ -41,8 +41,26 @@ display: flex; flex-wrap: wrap; min-height: var(--datatable-pagination-row); + position: relative; width: 100%; + .actions-toolbar { + align-items: center; + background-color: $color-concrete; + border-bottom: 1px solid $color-alto; + display: none; + height: 70px; + overflow-x: auto; + padding: 0 1em; + position: absolute; + top: 0; + width: 100%; + + .btn { + margin-right: .25em; + } + } + .pagination-info, .pagination-actions { flex-grow: 1; @@ -107,4 +125,31 @@ table > tbody > tr:first-child > td { border-top: 0; } + + &.show-actions { + --datatable-pagination-row: 139px; + + .pagination-row { + padding-top: 71px; + + .actions-toolbar { + display: flex; + } + } + } + + @media (max-width: 1000px) { + .pagination-row .actions-toolbar { + .btn { + .button-text { + display: none; + } + + .fas, + img { + margin: 0; + } + } + } + } } diff --git a/app/assets/stylesheets/shared_styles/constants/transition.scss b/app/assets/stylesheets/shared_styles/constants/transition.scss index fbcc31451..6d9f81417 100644 --- a/app/assets/stylesheets/shared_styles/constants/transition.scss +++ b/app/assets/stylesheets/shared_styles/constants/transition.scss @@ -1,3 +1,5 @@ +// scss-lint:disable ImportantRule VendorPrefix + $timing-function-deceleration: cubic-bezier(0, 0, .2, 1) !default; $timing-function-standard: cubic-bezier(.4, 0, .2, 1) !default; $timing-function-acceleration: cubic-bezier(.4, 0, 1, 1) !default; @@ -13,3 +15,10 @@ $timing-function-sharp: cubic-bezier(.4, 0, .6, 1) !default; animation: pulse 1s infinite; animation-timing-function: ease-in-out; } + +.notransition { + -moz-transition: none !important; + -o-transition: none !important; + -webkit-transition: none !important; + transition: none !important; +} diff --git a/app/assets/stylesheets/shared_styles/elements/buttons.scss b/app/assets/stylesheets/shared_styles/elements/buttons.scss index 7b30cdab1..608087c51 100644 --- a/app/assets/stylesheets/shared_styles/elements/buttons.scss +++ b/app/assets/stylesheets/shared_styles/elements/buttons.scss @@ -22,6 +22,10 @@ margin-right: .25em; } + img { + padding-bottom: 3px; + } + &:hover { text-decoration: none; } diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb index 04aaa2e02..7b2342f27 100644 --- a/app/controllers/protocols_controller.rb +++ b/app/controllers/protocols_controller.rb @@ -21,6 +21,7 @@ class ProtocolsController < ApplicationController preview linked_children linked_children_datatable + permissions ) before_action :switch_team_with_param, only: :index before_action :check_view_all_permissions, only: %i( @@ -1024,6 +1025,16 @@ class ProtocolsController < ApplicationController end end + def permissions + if stale?(@protocol) + render json: { + copyable: can_clone_protocol_in_repository?(@protocol), + archivable: can_manage_protocol_in_repository?(@protocol), + restorable: can_restore_protocol_in_repository?(@protocol) + } + end + end + private def set_importer diff --git a/app/datatables/protocols_datatable.rb b/app/datatables/protocols_datatable.rb index 1f20bba18..b385c421b 100644 --- a/app/datatables/protocols_datatable.rb +++ b/app/datatables/protocols_datatable.rb @@ -2,6 +2,7 @@ class ProtocolsDatatable < CustomDatatable # Needed for sanitize_sql_like method include ActiveRecord::Sanitization::ClassMethods include InputSanitizeHelper + include Rails.application.routes.url_helpers def_delegator :@view, :can_read_protocol_in_repository? def_delegator :@view, :can_manage_protocol_in_repository? @@ -81,17 +82,9 @@ class ProtocolsDatatable < CustomDatatable protocol = Protocol.find(record.id) result_data << { 'DT_RowId': record.id, - 'DT_CanClone': can_clone_protocol_in_repository?(protocol), - 'DT_CloneUrl': if can_clone_protocol_in_repository?(protocol) - clone_protocol_path(protocol, - team: @team, - type: @type) - end, - 'DT_CanMakePrivate': can_manage_protocol_in_repository?(protocol), - 'DT_CanPublish': can_manage_protocol_in_repository?(protocol), - 'DT_CanArchive': can_manage_protocol_in_repository?(protocol), - 'DT_CanRestore': can_restore_protocol_in_repository?(protocol), - 'DT_CanExport': can_read_protocol_in_repository?(protocol), + 'DT_RowAttr': { + 'data-permissions-url': permissions_protocol_path(protocol) + }, '1': if protocol.in_repository_archived? escape_input(record.name) else diff --git a/app/views/protocols/index.html.erb b/app/views/protocols/index.html.erb index bcd5e32c3..147127f07 100644 --- a/app/views/protocols/index.html.erb +++ b/app/views/protocols/index.html.erb @@ -96,7 +96,7 @@
<%= render partial: "protocols/import_export/import_json_protocol_modal.html.erb" %> - +<%= render partial: "protocols/index/action_toolbar.html.erb" %> <%= render partial: "protocols/index/make_private_results_modal.html.erb" %> <%= render partial: "protocols/index/publish_results_modal.html.erb" %> <%= render partial: "protocols/index/confirm_archive_modal.html.erb" %> diff --git a/app/views/protocols/index/_action_toolbar.html.erb b/app/views/protocols/index/_action_toolbar.html.erb new file mode 100644 index 000000000..d471011e0 --- /dev/null +++ b/app/views/protocols/index/_action_toolbar.html.erb @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index daf944969..8f61ce85d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2555,6 +2555,13 @@ en: private: "My protocols" external_protocols: "External protocols" archive: "Archive" + action_toolbar: + versions: "Versions" + access: "Manage Access" + duplicate: "Duplicate" + export: "Export" + archive: "Archive" + restore: "Restore" public_description: "Team protocols are visible and can be used by everyone from the team." private_description: "My protocols are only visible to you." create_new: "New" diff --git a/config/routes.rb b/config/routes.rb index a3cc2fe37..e15f99b96 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -581,6 +581,7 @@ Rails.application.routes.draw do get 'edit_authors_modal', to: 'protocols#edit_authors_modal' get 'edit_description_modal', to: 'protocols#edit_description_modal' post 'delete_steps' + get :permissions end collection do post 'datatable', to: 'protocols#datatable'