diff --git a/.hound.yml b/.hound.yml index 079bcf510..9101758bf 100644 --- a/.hound.yml +++ b/.hound.yml @@ -3,7 +3,7 @@ ruby: eslint: enabled: true - config_file: .eslintrc.json + config_file: app/assets/.eslintrc.json scss: config_file: .scss-lint.yml diff --git a/app/assets/javascripts/projects/index.js b/app/assets/javascripts/projects/index.js index 394b4c81c..20e95bebf 100644 --- a/app/assets/javascripts/projects/index.js +++ b/app/assets/javascripts/projects/index.js @@ -7,7 +7,7 @@ // - refresh project users tab after manage user modal is closed // - refactor view handling using library, ex. backbone.js -/* global Comments CounterBadge animateSpinner */ +/* global Comments CounterBadge animateSpinner initFormSubmitLinks HelperModule */ //= require comments (function() { @@ -28,28 +28,7 @@ var projectsViewMode = 'cards'; var projectsViewFilter = $('.projects-view-filter.active').data('filter'); - - function initProjectsViewFilter() { - $('.projects-view-filter').click(function(event) { - event.preventDefault(); - event.stopPropagation(); - $('.projects-view-filter').removeClass('active'); - $(this).addClass('active'); - if ($(this).data('filter') === projectsViewFilter) { - return; - } - projectsViewFilter = $(this).data('filter'); - }); - } - - function initProjectsViewModeSwitch() { - $('input[name=projects-view-mode-selector]').on('change', function() { - if ($(this).val() === projectsViewMode) { - return; - } - projectsViewMode = $(this).val(); - }); - } + var projectsViewSort = 'new'; /** * Initialize the JS for new project modal to work. @@ -129,20 +108,16 @@ animateSpinner(this); }) .on('ajax:success', function(ev2, data2) { - // Project saved, replace changed project's title - var responseHtml = $(data2.html); - var id = responseHtml.attr('data-id'); - var newTitle = responseHtml.find('.panel-title'); - var existingTitle = $(".panel-project[data-id='" + id + "'] .panel-title"); - - existingTitle.after(newTitle); - existingTitle.remove(); - // Hide modal editProjectModal.modal('hide'); + + HelperModule.flashAlertMsg(data2.message, 'success'); + + // Project saved, reload cards view + loadCardsView(); }) .on('ajax:error', function(ev2, data2) { - $(this).renderFormErrors('project', data2.responseJSON); + $(this).renderFormErrors('project', data2.responseJSON.errors); }) .on('ajax:complete', function() { animateSpinner(this, false); @@ -276,8 +251,6 @@ projectActionsModalBody = projectActionsModal.find('.modal-body'); projectActionsModalFooter = projectActionsModal.find('.modal-footer'); - initProjectsViewFilter(); - initProjectsViewModeSwitch(); initNewProjectModal(); initEditProjectModal(); initManageUsersModal(); @@ -285,6 +258,31 @@ Comments.initEditComments('.panel-project .tab-content'); Comments.initDeleteComments('.panel-project .tab-content'); + $('.project-card-selector').click(function() { + if (this.checked) { + $(this).closest('.panel-project').addClass('selected'); + } else { + $(this).closest('.panel-project').removeClass('selected'); + } + }); + + // init project archive/restore function + $('.panel-project .panel-heading form') + .on('ajax:beforeSend', function() { + animateSpinner($('#projects-cards-view')); + }) + .on('ajax:success', function(ev, data) { + HelperModule.flashAlertMsg(data.message, 'success'); + // Project saved, reload cards view + loadCardsView(); + }) + .on('ajax:error', function(ev, data) { + HelperModule.flashAlertMsg(data.responseJSON.message, 'danger'); + }) + .on('ajax:complete', function() { + animateSpinner($('#projects-cards-view'), false); + }); + // initialize project tab remote loading $('.panel-project .active').removeClass('active'); $('.panel-project .panel-footer [role=tab]') @@ -324,5 +322,70 @@ }); } - init(); + function loadCardsView() { + // Load HTML with projects list + var viewContainer = $('#projects-cards-view'); + animateSpinner(viewContainer, true); + $.ajax({ + url: $('#projects-cards-view').data('projects-url'), + type: 'GET', + dataType: 'json', + data: { + filter: projectsViewFilter, + sort: projectsViewSort + }, + success: function(data) { + viewContainer.html(data.html); + initFormSubmitLinks(viewContainer); + init(); + }, + error: function() { + viewContainer.html('Error loading project list'); + } + }); + } + + function initProjectsViewFilter() { + $('.projects-view-filter').click(function(event) { + event.preventDefault(); + event.stopPropagation(); + $('.projects-view-filter').removeClass('active'); + $(this).addClass('active'); + if ($(this).data('filter') === projectsViewFilter) { + return; + } + projectsViewFilter = $(this).data('filter'); + if ($('#projects-cards-view').hasClass('active')) { + loadCardsView(); + } + }); + } + + function initProjectsViewModeSwitch() { + $('input[name=projects-view-mode-selector]').on('change', function() { + if ($(this).val() === projectsViewMode) { + return; + } + projectsViewMode = $(this).val(); + }); + } + + function initSorting() { + $('#sortMenuDropdown a').click(function(event) { + event.preventDefault(); + event.stopPropagation(); + if (projectsViewSort !== $(this).data('sort')) { + $('#sortMenuDropdown a').removeClass('disabled'); + projectsViewSort = $(this).data('sort'); + loadCardsView(); + $(this).addClass('disabled'); + $('#sortMenu').dropdown('toggle'); + } + }); + } + + initProjectsViewFilter(); + initProjectsViewModeSwitch(); + initSorting(); + loadCardsView(); }()); diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss index 1e440c934..68cb915ad 100644 --- a/app/assets/stylesheets/themes/scinote.scss +++ b/app/assets/stylesheets/themes/scinote.scss @@ -623,8 +623,6 @@ ul.double-line > li { color: $brand-primary; } -#projects-index, -#project-archive, #project-show, #experiment-archive, #module-archive, @@ -651,9 +649,76 @@ ul.double-line > li { } .panel-project { + box-shadow: 0 3px 6px $color-alto; + color: $color-silver-chalice; + + &:not(.selected) .panel-heading .project-card-selector, + &:not(.selected) .panel-heading .dropdown { + display: none; + } + + &:not(.selected):hover .project-card-selector, + &:not(.selected):hover .dropdown { + display: block; + } + + .nav .btn-link { + padding: 10px 5px; + } + + &.selected { + border-color: $brand-primary; + box-shadow: 0 3px 10px $brand-primary; + } + + &.archived { + &, + .panel-heading, + .panel-body, + .panel-footer-scinote { + background-color: $color-concrete; + background-image: none; + } + } + .panel-heading { - background-color: $brand-primary; - color: $color-white; + background-color: $color-white; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + color: $color-silver-chalice; + + .fas { + margin-right: 10px; + } + } + + .panel-title { + color: $brand-primary; + + .fas { + color: $color-silver-chalice; + } + } + + .panel-body { + padding: 10px 15px; + + .row { + padding: 2px; + } + } + + .panel-footer { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + + .nav { + padding: 0 10px; + } + + .nav-tabs { + border-bottom: 0; + } } } diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 975ab6cb9..3d3e59ff7 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -31,6 +31,12 @@ class ProjectsController < ApplicationController @current_team ||= current_user.teams.first @projects = ProjectsOverviewService.new(@current_team, current_user) .project_cards(params) + render json: { + html: render_to_string( + partial: 'projects/index/team_projects.html.erb', + locals: { projects: @projects } + ) + } end format.html do current_team_switch(Team.find_by_id(params[:team])) if params[:team] @@ -128,8 +134,9 @@ class ProjectsController < ApplicationController (project_params[:archived] == 'false' && !can_restore_project?(@project)) return_error = true - is_archive = URI(request.referer).path == projects_archive_path ? "restore" : "archive" - flash_error = t("projects.#{is_archive}.error_flash", name: @project.name) + is_archive = project_params[:archived] == 'true' ? 'archive' : 'restore' + flash_error = + t("projects.#{is_archive}.error_flash", name: @project.name) end elsif !can_manage_project?(@project) render_403 && return @@ -137,19 +144,19 @@ class ProjectsController < ApplicationController message_renamed = nil message_visibility = nil - if project_params.include? :name and - project_params[:name] != @project.name then + if (project_params.include? :name) && + (project_params[:name] != @project.name) message_renamed = t( - "activities.rename_project", + 'activities.rename_project', user: current_user.full_name, project_old: @project.name, project_new: project_params[:name] ) end - if project_params.include? :visibility and - project_params[:visibility] != @project.visibility then + if (project_params.include? :visibility) && + (project_params[:visibility] != @project.visibility) message_visibility = t( - "activities.change_project_visibility", + 'activities.change_project_visibility', user: current_user.full_name, project: @project.name, visibility: project_params[:visibility] == "visible" ? @@ -159,7 +166,7 @@ class ProjectsController < ApplicationController end @project.last_modified_by = current_user - if @project.update(project_params) + if !return_error && @project.update(project_params) # Add activities if needed if message_visibility.present? Activity.create( @@ -179,10 +186,15 @@ class ProjectsController < ApplicationController end flash_success = t('projects.update.success_flash', name: @project.name) + if project_params[:archived] == 'true' + flash_success = t('projects.archive.success_flash', name: @project.name) + elsif project_params[:archived] == 'false' + flash_success = t('projects.restore.success_flash', name: @project.name) + end respond_to do |format| - format.html { + format.html do # Redirect URL for archive view is different as for other views. - if URI(request.referer).path == projects_archive_path + if project_params[:archived] == 'false' # The project should be restored unless @project.archived @project.restore(current_user) @@ -193,56 +205,45 @@ class ProjectsController < ApplicationController user: current_user, project: @project, message: t( - "activities.restore_project", + 'activities.restore_project', user: current_user.full_name, project: @project.name ) ) - - flash_success = t('projects.restore.success_flash', - name: @project.name) end - redirect_to projects_archive_path - else + elsif @project.archived # The project should be archived - if @project.archived - @project.archive(current_user) + @project.archive(current_user) - # "Archive project" activity - Activity.create( - type_of: :archive_project, - user: current_user, - project: @project, - message: t( - "activities.archive_project", - user: current_user.full_name, - project: @project.name - ) + # "Archive project" activity + Activity.create( + type_of: :archive_project, + user: current_user, + project: @project, + message: t( + 'activities.archive_project', + user: current_user.full_name, + project: @project.name ) - - flash_success = t('projects.archive.success_flash', name: @project.name) - end - redirect_to projects_path + ) end + redirect_to projects_path flash[:success] = flash_success - } - format.json { + end + format.json do render json: { status: :ok, - html: render_to_string({ - partial: "projects/index/project.html.erb", - locals: { project: @project } - }) + message: flash_success } - } + end end else return_error = true end - if return_error then + if return_error respond_to do |format| - format.html { + format.html do flash[:error] = flash_error # Redirect URL for archive view is different as for other views. if URI(request.referer).path == projects_archive_path @@ -250,11 +251,11 @@ class ProjectsController < ApplicationController else redirect_to projects_path end - } - format.json { - render json: @project.errors, - status: :unprocessable_entity - } + end + format.json do + render json: { message: flash_error, errors: @project.errors }, + status: :unprocessable_entity + end end end end diff --git a/app/models/experiment.rb b/app/models/experiment.rb index fec46ca43..59a66108e 100644 --- a/app/models/experiment.rb +++ b/app/models/experiment.rb @@ -2,7 +2,7 @@ class Experiment < ApplicationRecord include ArchivableModel include SearchableModel - belongs_to :project, inverse_of: :experiments, optional: true + belongs_to :project, inverse_of: :experiments, touch: true, optional: true belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User', diff --git a/app/models/my_module.rb b/app/models/my_module.rb index 012899d3e..c96de8944 100644 --- a/app/models/my_module.rb +++ b/app/models/my_module.rb @@ -31,7 +31,7 @@ class MyModule < ApplicationRecord foreign_key: 'restored_by_id', class_name: 'User', optional: true - belongs_to :experiment, inverse_of: :my_modules, optional: true + belongs_to :experiment, inverse_of: :my_modules, touch: true, optional: true belongs_to :my_module_group, inverse_of: :my_modules, optional: true has_many :results, inverse_of: :my_module, dependent: :destroy has_many :my_module_tags, inverse_of: :my_module, dependent: :destroy diff --git a/app/models/project_comment.rb b/app/models/project_comment.rb index c625176b0..682025769 100644 --- a/app/models/project_comment.rb +++ b/app/models/project_comment.rb @@ -2,6 +2,7 @@ class ProjectComment < Comment belongs_to :project, foreign_key: :associated_id, inverse_of: :project_comments, + touch: true, optional: true validates :project, presence: true diff --git a/app/models/user_my_module.rb b/app/models/user_my_module.rb index 3fd70fe1d..5b53a81a0 100644 --- a/app/models/user_my_module.rb +++ b/app/models/user_my_module.rb @@ -6,5 +6,7 @@ class UserMyModule < ApplicationRecord foreign_key: 'assigned_by_id', class_name: 'User', optional: true - belongs_to :my_module, inverse_of: :user_my_modules, optional: true + belongs_to :my_module, inverse_of: :user_my_modules, + touch: true, + optional: true end diff --git a/app/models/user_project.rb b/app/models/user_project.rb index 5ad20ab01..05c551fef 100644 --- a/app/models/user_project.rb +++ b/app/models/user_project.rb @@ -10,7 +10,7 @@ class UserProject < ApplicationRecord foreign_key: 'assigned_by_id', class_name: 'User', optional: true - belongs_to :project, inverse_of: :user_projects, optional: true + belongs_to :project, inverse_of: :user_projects, touch: true, optional: true before_destroy :destroy_associations diff --git a/app/services/projects_overview_service.rb b/app/services/projects_overview_service.rb index 5223d1cd1..921d0bb52 100644 --- a/app/services/projects_overview_service.rb +++ b/app/services/projects_overview_service.rb @@ -8,8 +8,8 @@ class ProjectsOverviewService def project_cards(params) records = fetch_records - records = records.where(archived: true) if params[:archived] == 'true' - records = records.where(archived: false) if params[:archived] == 'false' + records = records.where(archived: true) if params[:filter] == 'archived' + records = records.where(archived: false) if params[:filter] == 'active' return records unless params[:sort] case params[:sort] when 'new' @@ -29,8 +29,8 @@ class ProjectsOverviewService per_page = params[:length] == '-1' ? 10 : params[:length].to_i page = params[:start] ? (params[:start].to_i / per_page) + 1 : 1 records = fetch_dt_records - records = records.where(archived: true) if params[:archived] == 'true' - records = records.where(archived: false) if params[:archived] == 'false' + records = records.where(archived: true) if params[:filter] == 'archived' + records = records.where(archived: false) if params[:filter] == 'active' search_value = params.dig(:search, :value) records = search(records, search_value) if search_value.present? sort(records, params).page(page).per(per_page) diff --git a/app/views/projects/_edit.html.erb b/app/views/projects/_edit.html.erb index 9960ba8a6..42d70af20 100644 --- a/app/views/projects/_edit.html.erb +++ b/app/views/projects/_edit.html.erb @@ -6,4 +6,4 @@ <%= f.enum_btn_group :visibility, label: t("projects.index.modal_new_project.visibility"), btn_names: { hidden: t("projects.index.modal_new_project.visibility_hidden"), visible: t("projects.index.modal_new_project.visibility_visible") } %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb index 577d4486f..324b5eed4 100644 --- a/app/views/projects/index.html.erb +++ b/app/views/projects/index.html.erb @@ -98,12 +98,18 @@ -