Merge branch 'features/navigation-refactor-v2' of github.com:scinote-eln/scinote-web into features/navigation-refactor-v2

This commit is contained in:
Anton 2023-04-28 11:18:18 +02:00
commit 4c2c40340d
14 changed files with 216 additions and 190 deletions

View file

@ -652,6 +652,8 @@ var ProjectsIndex = (function() {
exportProjectsModalHeader = exportProjectsModal.find('.modal-title');
exportProjectsModalBody = exportProjectsModal.find('.modal-body');
window.initActionToolbar();
updateSelectedCards();
initNewProjectFolderModal();
initNewProjectModal();

View file

@ -1,7 +1,6 @@
/* global animateSpinner filterDropdown Sidebar Turbolinks HelperModule InfiniteScroll AsyncDropdown GLOBAL_CONSTANTS */
/* global animateSpinner filterDropdown Turbolinks HelperModule InfiniteScroll AsyncDropdown GLOBAL_CONSTANTS */
/* eslint-disable no-use-before-define */
(function() {
const PERMISSIONS = ['editable', 'archivable', 'restorable', 'moveable', 'duplicable'];
const pageSize = GLOBAL_CONSTANTS.DEFAULT_ELEMENTS_PER_PAGE;
var cardsWrapper = '#cardsWrapper';
var experimentsPage = '#projectShowWrapper';
@ -17,39 +16,9 @@
let archivedOnFromFilter;
let archivedOnToFilter;
function checkActionPermission(permission) {
return selectedExperiments.every(function(experimentId) {
return $(`.experiment-card[data-id="${experimentId}"]`).data(permission);
});
}
function updateExperimentsToolbar() {
let experimentsToolbar = $('#projectShowToolbar');
let toolbarVisible = false;
if (selectedExperiments.length === 0) {
experimentsToolbar.find('.single-object-action, .multiple-object-action').addClass('hidden');
}
if (selectedExperiments.length === 1) {
experimentsToolbar.find('.single-object-action, .multiple-object-action').removeClass('hidden');
} else if (selectedExperiments.length > 1) {
experimentsToolbar.find('.single-object-action').addClass('hidden');
experimentsToolbar.find('.multiple-object-action').removeClass('hidden');
}
PERMISSIONS.forEach((permission) => {
if (!checkActionPermission(permission)) {
experimentsToolbar.find(`.btn[data-for="${permission}"]`).addClass('hidden');
}
});
$.each($('#projectShowToolbar').find('.btn'), (i, btn) => {
if (window.getComputedStyle(btn).display !== 'none') {
toolbarVisible = true;
}
});
$(experimentsPage).attr('data-toolbar-visible', toolbarVisible);
window.actionToolbarComponent.fetchActions({ experiment_ids: selectedExperiments });
window.actionToolbarComponent.setReloadCallback(refreshCurrentView);
}
function initProjectsViewModeSwitch() {
@ -262,17 +231,7 @@
}
updateSelectAllCheckbox();
if (this.checked) {
$.get(card.data('permissions-url'), function(result) {
PERMISSIONS.forEach((permission) => {
card.data(permission, result[permission]);
});
updateExperimentsToolbar();
});
} else {
updateExperimentsToolbar();
}
updateExperimentsToolbar();
});
}
@ -285,29 +244,6 @@
});
}
function initArchiveRestoreToolbarButtons() {
$(experimentsPage)
.on('ajax:before', '.archive-experiments-form, .restore-experiments-form', function() {
let buttonForm = $(this);
buttonForm.find('input[name="experiments_ids[]"]').remove();
selectedExperiments.forEach(function(id) {
$('<input>').attr({
type: 'hidden',
name: 'experiments_ids[]',
value: id
}).appendTo(buttonForm);
});
})
.on('ajax:success', '.archive-experiments-form, .restore-experiments-form', function(ev, data) {
HelperModule.flashAlertMsg(data.message, 'success');
// Project saved, reload view
refreshCurrentView();
})
.on('ajax:error', '.archive-experiments-form, .restore-experiments-form', function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
}
function appendActionModal(modal) {
$('#projectShowWrapper').append(modal);
modal.modal('show');
@ -334,27 +270,6 @@
}
}
function initEditMoveDuplicateToolbarButton() {
let forms = '.edit-experiments-form, .move-experiments-form, .clone-experiments-form';
$(experimentsPage)
.on('ajax:before', forms, function() {
let buttonForm = $(this);
buttonForm.find('input[name="id"]').remove();
$('<input>').attr({
type: 'hidden',
name: 'id',
value: selectedExperiments[0]
}).appendTo(buttonForm);
})
.on('ajax:success', forms, function(ev, data) {
appendActionModal($(data.html));
})
.on('ajax:error', forms, function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
}
function initNewExperimentToolbarButton() {
let forms = '.new-experiment-form';
$(experimentsPage)
@ -412,13 +327,13 @@
$(this).renderFormErrors('experiment', data.responseJSON);
});
window.initActionToolbar();
initExperimentsFilters();
initSorting();
loadCardsView();
initProjectsViewModeSwitch();
initExperimentsSelector();
initArchiveRestoreToolbarButtons();
initEditMoveDuplicateToolbarButton();
initNewExperimentToolbarButton();
initSelectAllCheckbox();
AsyncDropdown.init($('#projectShowWrapper'));

View file

@ -9,8 +9,8 @@ class ExperimentsController < ApplicationController
include Breadcrumbs
before_action :load_project, only: %i(new create archive_group restore_group)
before_action :load_experiment, except: %i(new create archive_group restore_group)
before_action :check_read_permissions, except: %i(edit archive clone move new create archive_group restore_group)
before_action :load_experiment, except: %i(new create archive_group restore_group actions_toolbar)
before_action :check_read_permissions, except: %i(edit archive clone move new create archive_group restore_group actions_toolbar)
before_action :check_canvas_read_permissions, only: %i(canvas)
before_action :check_create_permissions, only: %i(new create)
before_action :check_manage_permissions, only: %i(edit batch_clone_my_modules)
@ -199,7 +199,7 @@ class ExperimentsController < ApplicationController
end
def archive_group
experiments = @project.experiments.active.where(id: params[:experiments_ids])
experiments = @project.experiments.active.where(id: params[:experiment_ids])
counter = 0
experiments.each do |experiment|
next unless can_archive_experiment?(experiment)
@ -222,7 +222,7 @@ class ExperimentsController < ApplicationController
end
def restore_group
experiments = @project.experiments.archived.where(id: params[:experiments_ids])
experiments = @project.experiments.archived.where(id: params[:experiment_ids])
counter = 0
experiments.each do |experiment|
next unless can_restore_experiment?(experiment)
@ -511,6 +511,16 @@ class ExperimentsController < ApplicationController
)
end
def actions_toolbar
render json: {
actions:
Toolbars::ExperimentsService.new(
current_user,
experiment_ids: params[:experiment_ids].split(',')
).actions
}
end
private
def load_experiment

View file

@ -6,11 +6,11 @@ import ActionToolbar from '../../vue/components/action_toolbar.vue';
Vue.use(TurbolinksAdapter);
window.addEventListener('turbolinks:load', () => {
window.initActionToolbar = () => {
new Vue({
el: '#actionToolbar',
components: {
ActionToolbar
}
});
});
}

View file

@ -3,11 +3,12 @@
<div class="sn-action-toolbar__actions flex">
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action">
<a :class="`btn btn-light ${action.button_class}`"
:href="action.type === 'link' ? action.path : '#'"
:href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'"
:id="action.button_id"
:data-url="action.path"
:data-object-type="action.item_type"
:data-object-id="action.item_id"
:data-action="action.type"
@click="doAction(action)">
<i :class="action.icon"></i>
{{ action.label }}
@ -63,7 +64,10 @@
// do nothing, this is handled by legacy code based on the button class
break;
case 'link':
// already handled by href
// do nothing, already handled by href
break;
case 'remote-modal':
// do nothing, handled by the data-action="remote-modal" binding
break;
case 'request':
$.ajax({
@ -71,9 +75,9 @@
url: action.path,
data: this.params
}).done((data) => {
HelperModule.flashAlertMsg(data.message, 'success');
HelperModule.flashAlertMsg(data.responseJSON && data.responseJSON.message || data.message, 'success');
}).fail((data) => {
HelperModule.flashAlertMsg(data.message, 'danger');
HelperModule.flashAlertMsg(data.responseJSON && data.responseJSON.message || data.message, 'danger');
}).complete(() => {
if (this.reloadCallback) this.reloadCallback();
});

View file

@ -0,0 +1,138 @@
# frozen_string_literal: true
module Toolbars
class ExperimentsService
attr_reader :current_user
include Canaid::Helpers::PermissionsHelper
include Rails.application.routes.url_helpers
def initialize(current_user, experiment_ids: [])
@current_user = current_user
@experiments = Experiment.joins(:project)
.where(projects: { team_id: current_user.current_team_id })
.readable_by_user(current_user)
.where(id: experiment_ids)
@single = @experiments.length == 1
end
def actions
return [] if @experiments.none?
[
restore_action,
edit_action,
access_action,
move_action,
duplicate_action,
archive_action
].compact
end
private
def restore_action
return unless @experiments.all? { |experiment| can_restore_experiment?(experiment) }
{
name: 'restore',
label: I18n.t('experiments.toolbar.restore_button'),
icon: 'fas fa-undo',
button_class: 'restore-experiments-btn',
path: restore_group_project_experiments_path(project_id: @experiments.first.project_id),
type: :request,
request_method: :post
}
end
def edit_action
return unless @single
experiment = @experiments.first
return unless can_manage_experiment?(experiment)
{
name: 'edit',
label: I18n.t('experiments.index.edit_option'),
icon: 'fa fa-pen',
button_class: 'edit-btn',
path: edit_experiment_path(experiment),
type: 'remote-modal'
}
end
def access_action
return unless @single
experiment = @experiments.first
return unless can_read_experiment?(experiment)
path = if can_manage_experiment_users?(experiment)
edit_access_permissions_experiment_path(experiment)
else
access_permissions_experiment_path(experiment)
end
{
name: 'access',
label: I18n.t('general.access'),
icon: 'fa fa-door-open',
button_class: 'access-btn',
path: path,
type: 'remote-modal'
}
end
def move_action
return unless @single
experiment = @experiments.first
return unless can_move_experiment?(experiment)
{
name: 'move',
label: I18n.t('experiments.toolbar.move_button'),
icon: 'fas fa-arrow-right',
button_class: 'move-experiments-btn',
path: move_modal_experiments_path(id: experiment.id),
type: 'remote-modal'
}
end
def duplicate_action
return unless @single
experiment = @experiments.first
return unless can_clone_experiment?(experiment)
{
name: 'duplicate',
label: I18n.t('experiments.toolbar.duplicate_button'),
icon: 'fas fa-copy',
button_class: 'clone-experiment-btn',
path: clone_modal_experiments_path(id: experiment.id),
type: 'remote-modal',
request_method: :get
}
end
def archive_action
return unless @experiments.all? { |experiment| can_archive_experiment?(experiment) }
{
name: 'archive',
label: I18n.t('experiments.toolbar.archive_button'),
icon: 'fas fa-archive',
button_class: 'archive-experiments-btn',
path: archive_group_project_experiments_path(project_id: @experiments.first.project_id),
type: :request,
request_method: :post
}
end
end
end

View file

@ -1,7 +1,7 @@
<div class="modal" id="edit-experiment-modal-<%= @experiment.id %>" tabindex="-1" role="dialog" aria-labelledby="edit-experiment-modal-label">
<div class="modal-dialog" role="document">
<div class="modal-content">
<%= bootstrap_form_for [@project, @experiment], remote: true, html: { class: 'experiment-action-form' } do |f| %>
<%= bootstrap_form_for [@project, @experiment], html: { class: 'experiment-action-form' } do |f| %>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="<%= t('general.close') %>"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="edit-eperiment-modal-label">

View file

@ -62,6 +62,6 @@
</div>
</template>
<%= javascript_include_tag "vue_components_action_toolbar" %>
<%= javascript_include_tag "projects/index" %>
<%= javascript_include_tag "vue_components_action_toolbar" %>

View file

@ -33,6 +33,9 @@
</div>
</div>
</div>
<div id="actionToolbar" data-behaviour="vue">
<action-toolbar actions-url="<%= actions_toolbar_experiments_url(project_id: params[:project_id]) %>" />
</div>
</div>
<template id="experimentPlaceholder">
@ -59,5 +62,7 @@
</div>
</template>
<%= javascript_include_tag "vue_components_action_toolbar" %>
<%= javascript_include_tag("projects/show") %>
<i data-hook="project-show-js"></i>

View file

@ -1,4 +1,17 @@
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="experimentActionsDropdown">
<% if experiment.archived? && can_restore_experiment?(experiment) %>
<li class="form-dropdown-item">
<%= button_to(experiment_path(experiment, format: :json),
method: :put,
remote: true,
class: 'btn btn-light',
form_class: 'experiment-action-form',
params: { experiment: { archived: false } }) do %>
<i class="fas fa-undo"></i>
<span><%= t('experiments.index.restore_option') %></span>
<% end %>
</li>
<% end %>
<!-- Edit experiment -->
<% if experiment.active? && can_manage_experiment?(experiment) %>
<li>
@ -10,28 +23,6 @@
<% end %>
</li>
<% end %>
<!-- Duplicate experiment -->
<% if can_clone_experiment?(experiment) %>
<li>
<%= link_to clone_modal_experiment_url(experiment),
remote: true, type: 'button',
class: 'clone-experiment experiment-action-link' do %>
<i class="fas fa-copy"></i>
<span><%= t('experiments.index.clone_option') %></span>
<% end %>
</li>
<% end %>
<!-- Move experiment -->
<% if can_move_experiment?(experiment) %>
<li>
<%= link_to move_modal_experiment_url(experiment),
remote: true,
class: 'move-experiment experiment-action-link' do %>
<i class="fas fa-arrow-right"></i>
<span><%= t('experiments.index.move_option') %></span>
<% end %>
</li>
<% end %>
<!-- Set or view user experiment assignments -->
<% if can_manage_experiment_users?(experiment) %>
<li class="form-dropdown-item">
@ -48,6 +39,28 @@
<% end %>
</li>
<% end %>
<!-- Move experiment -->
<% if can_move_experiment?(experiment) %>
<li>
<%= link_to move_modal_experiment_url(experiment),
remote: true,
class: 'move-experiment experiment-action-link' do %>
<i class="fas fa-arrow-right"></i>
<span><%= t('experiments.index.move_option') %></span>
<% end %>
</li>
<% end %>
<!-- Duplicate experiment -->
<% if can_clone_experiment?(experiment) %>
<li>
<%= link_to clone_modal_experiment_url(experiment),
remote: true, type: 'button',
class: 'clone-experiment experiment-action-link' do %>
<i class="fas fa-copy"></i>
<span><%= t('experiments.index.clone_option') %></span>
<% end %>
</li>
<% end %>
<!-- Archive/restore experiment -->
<% if experiment.active? && can_archive_experiment?(experiment) %>
<li class="form-dropdown-item">
@ -61,18 +74,6 @@
<span><%= t('experiments.index.archive_option') %></span>
<% end %>
</li>
<% elsif experiment.archived? && can_restore_experiment?(experiment) %>
<li class="form-dropdown-item">
<%= button_to(experiment_path(experiment, format: :json),
method: :put,
remote: true,
class: 'btn btn-light',
form_class: 'experiment-action-form',
params: { experiment: { archived: false } }) do %>
<i class="fas fa-undo"></i>
<span><%= t('experiments.index.restore_option') %></span>
<% end %>
</li>
<% end %>
<li class="form-dropdown-item">
<div class="form-dropdown-item-info">

View file

@ -89,56 +89,4 @@
</ul>
</div>
</span>
<!-- temporarily disabling other actions -->
<% button_to edit_experiments_path(),
class: 'btn btn-light edit-experiment-btn single-object-action hidden',
form_class: 'edit-experiments-form',
data: { for: :editable, view_mode: 'active' },
remote: true,
method: :get do %>
<span class="fas fa-pencil-alt" aria-hidden="true"></span>
<span class="hidden-xs"><%= t('experiments.toolbar.edit_button') %></span>
<% end %>
<% button_to clone_modal_experiments_path(),
class: 'btn btn-light clone-experiment-btn single-object-action hidden',
form_class: 'clone-experiments-form',
data: { for: :duplicable, view_mode: 'active' },
remote: true,
method: :get do %>
<span class="fas fa-copy" aria-hidden="true"></span>
<span class="hidden-xs"><%= t('experiments.toolbar.duplicate_button') %></span>
<% end %>
<% button_to move_modal_experiments_path(),
class: 'btn btn-light move-experiment-btn single-object-action hidden',
form_class: 'move-experiments-form',
data: { for: :moveable },
remote: true,
method: :get do %>
<span class="fas fa-arrow-right" aria-hidden="true"></span>
<span class="hidden-xs"><%= t('experiments.toolbar.move_button') %></span>
<% end %>
<% button_to archive_group_project_experiments_path(@project),
class: 'btn btn-light archive-experiments-btn multiple-object-action hidden',
form_class: 'archive-experiments-form',
data: { for: :archivable, view_mode: 'active' },
remote: true,
method: :post do %>
<span class="fas fa-archive" aria-hidden="true"></span>
<span class="hidden-xs"><%= t('projects.index.archive_button') %></span>
<% end %>
<% button_to restore_group_project_experiments_path(@project),
class: 'btn btn-light restore-experiments-btn multiple-object-action hidden',
form_class: 'restore-experiments-form',
data: { for: :restorable, view_mode: 'archived' },
remote: true,
method: :post do %>
<span class="fas fa-undo" aria-hidden="true"></span>
<span class="hidden-xs"><%= t('experiments.toolbar.restore_button') %></span>
<% end %>
</div>

View file

@ -3370,6 +3370,7 @@ en:
remove: "Remove"
clone_label: "Clone"
download: "Download"
access: "Access"
# In order to use the strings 'yes' and 'no' as keys, you need to wrap them with quotes
'yes': "Yes"
'no': "No"

View file

@ -384,6 +384,7 @@ Rails.application.routes.draw do
get 'edit', action: :edit
get 'clone_modal', action: :clone_modal
get 'move_modal', action: :move_modal
get 'actions_toolbar'
end
member do
get 'permissions'

View file

@ -32,7 +32,8 @@ const entryList = {
vue_repository_filter: './app/javascript/packs/vue/repository_filter.js',
vue_repository_print_modal: './app/javascript/packs/vue/repository_print_modal.js',
vue_navigation_top_menu: './app/javascript/packs/vue/navigation/top_menu.js',
vue_navigation_navigator: './app/javascript/packs/vue/navigation/navigator.js'
vue_navigation_navigator: './app/javascript/packs/vue/navigation/navigator.js',
vue_components_action_toolbar: './app/javascript/packs/vue/action_toolbar.js'
}
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949