Implement bottom action toolbar for repository items [SCI-8300]

Implement bottom action toolbar for repository items [SCI-8300]
This commit is contained in:
artoscinote 2023-05-17 11:27:55 +02:00 committed by GitHub
parent ea0d4cf210
commit aeb84e18d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 236 additions and 59 deletions

View file

@ -52,6 +52,10 @@ var RepositoryDatatable = (function(global) {
// Enable/disable edit button // Enable/disable edit button
function updateButtons() { function updateButtons() {
if (window.actionToolbarComponent) {
window.actionToolbarComponent.fetchActions({ repository_row_ids: rowsSelected });
}
if (currentMode === 'viewMode') { if (currentMode === 'viewMode') {
$(TABLE_WRAPPER_ID).removeClass('editing'); $(TABLE_WRAPPER_ID).removeClass('editing');
$('.repository-save-changes-link').off('click'); $('.repository-save-changes-link').off('click');
@ -73,22 +77,9 @@ var RepositoryDatatable = (function(global) {
} }
$('#hideRepositoryReminders').show(); $('#hideRepositoryReminders').show();
$('#importRecordsButton').show(); $('#importRecordsButton').show();
if (rowsSelected.length === 0) {
$('#exportRepositoriesButton').addClass('disabled'); if (rowsSelected.length !== 0) {
$('#copyRepositoryRecords').prop('disabled', true);
$('#editRepositoryRecord').prop('disabled', true);
$('#archiveRepositoryRecordsButton').prop('disabled', true);
$('#restoreRepositoryRecords').prop('disabled', true);
$('#deleteRepositoryRecords').prop('disabled', true);
$('#editDeleteCopy').hide();
$('#toolbarPrintLabel').hide();
} else {
$('#editRepositoryRecord').prop('disabled', !allSelectedRowsAreOnPage()); $('#editRepositoryRecord').prop('disabled', !allSelectedRowsAreOnPage());
$('#exportRepositoriesButton').removeClass('disabled');
$('#archiveRepositoryRecordsButton').prop('disabled', false);
$('#copyRepositoryRecords').prop('disabled', false);
$('#restoreRepositoryRecords').prop('disabled', false);
$('#deleteRepositoryRecords').prop('disabled', false);
$('#importRecordsButton').hide(); $('#importRecordsButton').hide();
if (rowsSelected.some(r=> rowsLocked.indexOf(r) >= 0)) { // Some selected rows is rowsLocked if (rowsSelected.some(r=> rowsLocked.indexOf(r) >= 0)) { // Some selected rows is rowsLocked
@ -119,15 +110,12 @@ var RepositoryDatatable = (function(global) {
$('#repository-acitons-dropdown').prop('disabled', true); $('#repository-acitons-dropdown').prop('disabled', true);
$('.dataTables_length select').prop('disabled', true); $('.dataTables_length select').prop('disabled', true);
$('#addRepositoryRecord').prop('disabled', true); $('#addRepositoryRecord').prop('disabled', true);
$('#editRepositoryRecord').prop('disabled', true);
$('#archiveRepositoryRecordsButton').prop('disabled', true);
$('#assignRepositoryRecords').prop('disabled', true); $('#assignRepositoryRecords').prop('disabled', true);
$('#unassignRepositoryRecords').prop('disabled', true); $('#unassignRepositoryRecords').prop('disabled', true);
$('#repository-columns-dropdown').find('.dropdown-toggle').prop('disabled', true); $('#repository-columns-dropdown').find('.dropdown-toggle').prop('disabled', true);
$('th').addClass('disable-click'); $('th').addClass('disable-click');
$('.repository-row-selector').prop('disabled', true); $('.repository-row-selector').prop('disabled', true);
$('.dataTables_filter input').prop('disabled', true); $('.dataTables_filter input').prop('disabled', true);
$('#toolbarPrintLabel').hide();
$('.repository-edit-overlay').show(); $('.repository-edit-overlay').show();
$('#team-switch').css({ 'pointer-events': 'none', opacity: 0.6 }); $('#team-switch').css({ 'pointer-events': 'none', opacity: 0.6 });
$('#navigationGoBtn').prop('disabled', true); $('#navigationGoBtn').prop('disabled', true);
@ -398,11 +386,13 @@ var RepositoryDatatable = (function(global) {
} }
function initExportActions() { function initExportActions() {
$('#exportRepositoriesButton').on('click', function() { $(document).on('click', '#exportRepositoriesButton', function(e) {
e.preventDefault();
e.stopPropagation();
$('#exportRepositoryModal').modal('show'); $('#exportRepositoryModal').modal('show');
}); });
$('form#form-export').off().submit(function() { $('form#form-export').off().submit(function() {
var form = this; var form = this;
if (currentMode === 'viewMode') { if (currentMode === 'viewMode') {
@ -661,6 +651,8 @@ var RepositoryDatatable = (function(global) {
}); });
}, },
fnInitComplete: function() { fnInitComplete: function() {
window.initActionToolbar();
initHeaderTooltip(); initHeaderTooltip();
disableCheckboxToggleOnCheckboxPreview(); disableCheckboxToggleOnCheckboxPreview();
@ -777,7 +769,10 @@ var RepositoryDatatable = (function(global) {
changeToEditMode(); changeToEditMode();
$('.tooltip').remove(); $('.tooltip').remove();
}) })
.on('click', '#copyRepositoryRecords', function() { .on('click', '#copyRepositoryRecords', function(e) {
e.preventDefault();
e.stopPropagation();
animateSpinner(); animateSpinner();
$.ajax({ $.ajax({
url: $('table' + TABLE_ID).data('copy-records'), url: $('table' + TABLE_ID).data('copy-records'),
@ -799,7 +794,10 @@ var RepositoryDatatable = (function(global) {
} }
}); });
}) })
.on('click', '#archiveRepositoryRecordsButton', function() { .on('click', '#archiveRepositoryRecordsButton', function(e) {
e.preventDefault();
e.stopPropagation();
animateSpinner(); animateSpinner();
$.ajax({ $.ajax({
url: $('table' + TABLE_ID).data('archive-records'), url: $('table' + TABLE_ID).data('archive-records'),
@ -825,7 +823,10 @@ var RepositoryDatatable = (function(global) {
} }
}); });
}) })
.on('click', '#restoreRepositoryRecords', function() { .on('click', '#restoreRepositoryRecords', function(e) {
e.preventDefault();
e.stopPropagation();
animateSpinner(); animateSpinner();
$.ajax({ $.ajax({
url: $('table' + TABLE_ID).data('restore-records'), url: $('table' + TABLE_ID).data('restore-records'),
@ -851,7 +852,10 @@ var RepositoryDatatable = (function(global) {
} }
}); });
}) })
.on('click', '#editRepositoryRecord', function() { .on('click', '#editRepositoryRecord', function(e) {
e.preventDefault();
e.stopPropagation();
checkAvailableColumns(); checkAvailableColumns();
$(TABLE_ID).find('.repository-row-edit-icon').remove(); $(TABLE_ID).find('.repository-row-edit-icon').remove();
@ -863,14 +867,20 @@ var RepositoryDatatable = (function(global) {
changeToEditMode(); changeToEditMode();
// adjustTableHeader(); // adjustTableHeader();
}) })
.on('click', '#deleteRepositoryRecords', function() { .on('click', '#deleteRepositoryRecords', function(e) {
e.preventDefault();
e.stopPropagation();
$('#deleteRepositoryRecord').modal('show'); $('#deleteRepositoryRecord').modal('show');
}) })
.on('click', '#hideRepositoryReminders', function() { .on('click', '#hideRepositoryReminders', function(e) {
var visibleReminderRepositoryRowIds = $('.row-reminders-dropdown').map( var visibleReminderRepositoryRowIds = $('.row-reminders-dropdown').map(
function() { return $(this).closest('[role=row]').attr('id'); } function() { return $(this).closest('[role=row]').attr('id'); }
).toArray(); ).toArray();
e.preventDefault();
e.stopPropagation();
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: $(this).data('hideRemindersUrl'), url: $(this).data('hideRemindersUrl'),

View file

@ -57,11 +57,15 @@
return false; return false;
}); });
$(document).on('click', '.print-label-button', function() { $(document).on('click', '.print-label-button', function(e) {
var selectedRows = $(this).data('rows'); var selectedRows = $(this).data('rows');
e.preventDefault();
e.stopPropagation();
if (typeof PrintModalComponent !== 'undefined') { if (typeof PrintModalComponent !== 'undefined') {
PrintModalComponent.showModal = true; PrintModalComponent.showModal = true;
if (selectedRows.length) { if (selectedRows && selectedRows.length) {
$('#modal-info-repository-row').modal('hide'); $('#modal-info-repository-row').modal('hide');
PrintModalComponent.row_ids = selectedRows; PrintModalComponent.row_ids = selectedRows;
} else { } else {

View file

@ -1,6 +1,6 @@
.sn-action-toolbar { .sn-action-toolbar {
background: $color-concrete; background: $color-concrete;
z-index: 2; z-index: 100;
.btn.btn-light:hover { .btn.btn-light:hover {
background: $color-white; background: $color-white;

View file

@ -5,13 +5,15 @@ class RepositoryRowsController < ApplicationController
include MyModulesHelper include MyModulesHelper
MAX_PRINTABLE_ITEM_NAME_LENGTH = 64 MAX_PRINTABLE_ITEM_NAME_LENGTH = 64
before_action :load_repository, except: %i(show print rows_to_print print_zpl validate_label_template_columns) before_action :load_repository, except: %i(show print rows_to_print print_zpl
validate_label_template_columns actions_toolbar)
before_action :load_repository_row_print, only: %i(print rows_to_print print_zpl validate_label_template_columns) before_action :load_repository_row_print, only: %i(print rows_to_print print_zpl validate_label_template_columns)
before_action :load_repository_or_snapshot, only: %i(print rows_to_print print_zpl validate_label_template_columns) before_action :load_repository_or_snapshot, only: %i(print rows_to_print print_zpl validate_label_template_columns)
before_action :load_repository_row, only: %i(update assigned_task_list active_reminder_repository_cells) before_action :load_repository_row, only: %i(update assigned_task_list active_reminder_repository_cells)
before_action :check_read_permissions, except: %i(show create update delete_records before_action :check_read_permissions, except: %i(show create update delete_records
copy_records reminder_repository_cells copy_records reminder_repository_cells
delete_records archive_records restore_records) delete_records archive_records restore_records
actions_toolbar)
before_action :check_snapshotting_status, only: %i(create update delete_records copy_records) before_action :check_snapshotting_status, only: %i(create update delete_records copy_records)
before_action :check_create_permissions, only: :create before_action :check_create_permissions, only: :create
before_action :check_delete_permissions, only: %i(delete_records archive_records restore_records) before_action :check_delete_permissions, only: %i(delete_records archive_records restore_records)
@ -288,6 +290,16 @@ class RepositoryRowsController < ApplicationController
} }
end end
def actions_toolbar
render json: {
actions:
Toolbars::RepositoryRowsService.new(
current_user,
repository_row_ids: params[:repository_row_ids].split(',')
).actions
}
end
private private
include StringUtility include StringUtility

View file

@ -1,6 +1,9 @@
<template> <template>
<div v-if="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`">
<div class="sn-action-toolbar__actions flex"> <div class="sn-action-toolbar__actions flex">
<div v-if="loading && !actions.length" class="sn-action-toolbar__action">
<a class="btn btn-light"></a>
</div>
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action"> <div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action">
<a :class="`btn btn-light ${action.button_class}`" <a :class="`btn btn-light ${action.button_class}`"
:href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'" :href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'"
@ -19,6 +22,8 @@
</template> </template>
<script> <script>
import {debounce} from '../shared/debounce.js';
export default { export default {
name: 'ActionToolbar', name: 'ActionToolbar',
props: { props: {
@ -31,12 +36,22 @@
multiple: false, multiple: false,
params: {}, params: {},
reloadCallback: null, reloadCallback: null,
loading: false,
width: 0 width: 0
} }
}, },
created() { created() {
window.actionToolbarComponent = this; window.actionToolbarComponent = this;
window.onresize = this.setWidth; window.onresize = this.setWidth;
this.debouncedFetchActions = debounce((params) => {
this.params = params;
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => {
this.actions = data.actions;
this.loading = false;
});
}, 200);
}, },
mounted() { mounted() {
this.setWidth(); this.setWidth();
@ -49,11 +64,8 @@
this.width = $(this.$el).parent().width(); this.width = $(this.$el).parent().width();
}, },
fetchActions(params) { fetchActions(params) {
this.params = params; this.loading = true;
this.debouncedFetchActions(params);
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => {
this.actions = data.actions;
});
}, },
setReloadCallback(func) { setReloadCallback(func) {
this.reloadCallback = func; this.reloadCallback = func;

View file

@ -0,0 +1,140 @@
# frozen_string_literal: true
class RepositoryMismatchError < StandardError; end
module Toolbars
class RepositoryRowsService
attr_reader :current_user
include Canaid::Helpers::PermissionsHelper
include Rails.application.routes.url_helpers
def initialize(current_user, repository_row_ids: [])
@current_user = current_user
@repository_rows = RepositoryRow.where(id: repository_row_ids)
return if @repository_rows.none?
if @repository_rows.pluck(:repository_id).uniq.size != 1
raise RepositoryMismatchError, 'Items are not from the same repository!'
end
@repository = @repository_rows.first.repository
@single = @repository_rows.length == 1
end
def actions
return [] if @repository_rows.none?
[
restore_action,
edit_action,
duplicate_action,
export_action,
print_label_action,
archive_action,
delete_action
].compact
end
private
def restore_action
return unless can_manage_repository_rows?(@repository)
return unless @repository_rows.all?(&:archived?)
{
name: 'restore',
label: I18n.t('repositories.restore_record'),
icon: 'fas fa-undo',
button_class: 'resotre-repository-row-btn',
button_id: 'restoreRepositoryRecords',
type: :legacy
}
end
def edit_action
return unless can_manage_repository_rows?(@repository)
{
name: 'edit',
label: I18n.t('repositories.edit_record'),
icon: 'fas fa-pencil-alt',
button_class: 'edit-repository-row-btn',
button_id: 'editRepositoryRecord',
type: :legacy
}
end
def duplicate_action
return unless can_create_repository_rows?(@repository)
{
name: 'duplicate',
label: I18n.t('repositories.copy_record'),
icon: 'fas fa-copy',
button_class: 'copy-repository-row-btn',
button_id: 'copyRepositoryRecords',
type: :legacy
}
end
def export_action
return unless can_read_repository?(@repository)
{
name: 'export',
label: I18n.t('repositories.export_record'),
icon: 'fas fa-file-export',
button_class: 'export-repository-row-btn',
button_id: 'exportRepositoriesButton',
type: :legacy
}
end
def print_label_action
return unless can_read_repository?(@repository)
{
name: 'print_label',
label: I18n.t('repositories.print_label'),
icon: 'fas fa-print',
button_class: 'print-label-button',
button_id: 'toolbarPrintLabel',
type: :legacy
}
end
def archive_action
return unless can_manage_repository_rows?(@repository)
return unless @repository_rows.all?(&:active?)
{
name: 'archive',
label: I18n.t('repositories.archive_record'),
icon: 'fas fa-archive',
button_class: 'resotre-repository-row-btn',
button_id: 'archiveRepositoryRecordsButton',
type: :legacy
}
end
def delete_action
return unless can_delete_repository_rows?(@repository)
return unless @repository_rows.all?(&:archived?)
{
name: 'delete',
label: I18n.t('repositories.delete_record'),
icon: 'fas fa-trash',
button_class: 'resotre-repository-row-btn',
button_id: 'deleteRepositoryRecords',
type: :legacy
}
end
end
end

View file

@ -25,30 +25,18 @@
<span class="button-text"><%= t('repositories.index.options_dropdown.import_items') %></span> <span class="button-text"><%= t('repositories.index.options_dropdown.import_items') %></span>
</button> </button>
<% end %> <% end %>
<!--
<%= render partial: 'repositories/toolbar/row_actions' %> <span id="saveCancel" data-toggle="buttons" style="display:none">
<% if @repository.repository_rows.with_active_reminders(current_user).any? %> <button type="button" class="btn btn-success prevent-shrink" id="saveRecord" data-view-mode="active">
<button type="button" data-toggle="tooltip" data-placement="bottom" title="<%= t("repositories.hide_reminders") %>" <span class="fas fa-save"></span>
class="btn btn-light auto-shrink-button" <%= t("repositories.save_record") %>
id="hideRepositoryReminders"
data-view-mode="active"
data-hide-reminders-url="<%= team_repository_hide_reminders_url(@current_team, @repository) %>">
<span class="fas fa-bell-slash"></span>
<span class="button-text"><%= t("repositories.hide_reminders") %></span>
</button> </button>
<button type="button" class="btn btn-light prevent-shrink" id="cancelSave" data-view-mode="active">
<span class="fas fa-times-circle"></span>
<%= t("repositories.cancel_save") %>
</button>
</span>
<% end %> <% end %>
<% else %>
<%= render partial: 'repositories/toolbar/print_label_button' %>
<% end %>
<div class="archived-label" data-view-mode="archived">
<%= render partial: 'repositories/toolbar/archive_label' %>
</div>
<% if can_manage_repository_filters?(@repository) %>
<div class="toolbar-save-filters">
<%= render partial: 'repositories/toolbar/save_filters' %>
</div>
<% end %>
-->
</div> </div>
<div class="toolbar-middle-block"> <div class="toolbar-middle-block">

View file

@ -67,6 +67,10 @@
repository_index_link: repository_table_index_path(@repository) repository_index_link: repository_table_index_path(@repository)
} }
%> %>
<div id="actionToolbar" data-behaviour="vue">
<action-toolbar actions-url="<%= actions_toolbar_repository_rows_url %>" />
</div>
</div> </div>
<%= render partial: 'repositories/import_repository_records_modal', <%= render partial: 'repositories/import_repository_records_modal',
@ -89,6 +93,7 @@
<%= render partial: 'save_repository_filter_modal' %> <%= render partial: 'save_repository_filter_modal' %>
<% end %> <% end %>
<%= javascript_include_tag 'vue_components_action_toolbar' %>
<%= javascript_include_tag 'vue_repository_search' %> <%= javascript_include_tag 'vue_repository_search' %>
<%= javascript_include_tag 'repositories/edit', nonce: true %> <%= javascript_include_tag 'repositories/edit', nonce: true %>
<%= javascript_include_tag 'repositories/repository_datatable', nonce: true %> <%= javascript_include_tag 'repositories/repository_datatable', nonce: true %>

View file

@ -1960,6 +1960,8 @@ en:
delete_record: "Delete" delete_record: "Delete"
archive_record: "Archive" archive_record: "Archive"
restore_record: "Restore" restore_record: "Restore"
print_label: "Print label"
export_record: "Export"
save_record: "Save" save_record: "Save"
cancel_save: "Cancel" cancel_save: "Cancel"
hide_reminders: "Clear all reminders" hide_reminders: "Clear all reminders"

View file

@ -664,6 +664,10 @@ Rails.application.routes.draw do
get :print_zpl get :print_zpl
post :validate_label_template_columns post :validate_label_template_columns
end end
collection do
get :actions_toolbar
end
end end
resources :repositories do resources :repositories do