Store Table/Cards view setting per user

SCI-5450
This commit is contained in:
Urban Rotnik 2021-02-11 11:50:59 +01:00
parent a31ba4f369
commit 235f19cf25
14 changed files with 148 additions and 45 deletions

View file

@ -561,17 +561,16 @@ var ProjectsIndex = (function() {
function initProjectsViewModeSwitch() {
let projectsPageSelector = '.projects-index';
// list/cards switch
$(projectsPageSelector).on('click', '.cards-switch', function() {
let $btn = $(this);
$('.cards-switch').removeClass('active');
if ($btn.hasClass('view-switch-cards')) {
$(cardsWrapper).removeClass('list');
} else if ($btn.hasClass('view-switch-list')) {
$(cardsWrapper).addClass('list');
}
$btn.addClass('active');
});
$(projectsPageSelector)
.on('ajax:success', '.change-projects-view-type-form', function(ev, data) {
$(cardsWrapper).removeClass('list').addClass(data.cards_view_type_class);
$(projectsPageSelector).find('.cards-switch .button-to').removeClass('selected');
$(ev.target).find('.button-to').addClass('selected');
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
})
.on('ajax:error', '.change-projects-view-type-form', function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
});
// Active/Archived switch
// We have different sorting, filters for active/archived views.

View file

@ -51,16 +51,16 @@
}
function initProjectsViewModeSwitch() {
$(experimentsPage).on('click', '.cards-switch', function() {
let $btn = $(this);
$('.cards-switch').removeClass('active');
if ($btn.hasClass('view-switch-cards')) {
$(cardsWrapper).removeClass('list');
} else if ($btn.hasClass('view-switch-list')) {
$(cardsWrapper).addClass('list');
}
$btn.addClass('active');
});
$(experimentsPage)
.on('ajax:success', '.change-experiments-view-type-form', function(ev, data) {
$(cardsWrapper).removeClass('list').addClass(data.cards_view_type_class);
$(experimentsPage).find('.cards-switch .button-to').removeClass('selected');
$(ev.target).find('.button-to').addClass('selected');
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
})
.on('ajax:error', '.change-projects-view-type-form', function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
});
$(experimentsPage).on('click', '.archive-switch', function() {
Turbolinks.visit($(this).data('url'));

View file

@ -41,15 +41,40 @@
.dropdown-menu {
.form-dropdown-item {
padding: 0 !important;
button {
border-radius: 0;
color: $color-black !important;
padding-left: .9em;
text-align: left;
width: 100%;
}
&:hover {
background-color: $color-dd-hover !important;
.project-archive-restore-form {
.button-to {
&:hover {
background-color: $color-dd-hover !important;
}
}
}
.change-projects-view-type-form,
.change-experiments-view-type-form {
.button-to {
float: unset !important;
height: 48px;
margin: 0;
&:active {
background-color: inherit;
}
&.selected::after {
@include font-awesome;
content: $font-fas-check;
float: right;
}
}
}
}

View file

@ -5,20 +5,21 @@ class ProjectsController < ApplicationController
include TeamsHelper
include InputSanitizeHelper
include ProjectsHelper
include CardsViewHelper
include ExperimentsHelper
attr_reader :current_folder
helper_method :current_folder
before_action :switch_team_with_param, only: :index
before_action :load_vars, only: %i(show edit update notifications sidebar experiments_cards)
before_action :load_vars, only: %i(show edit update notifications sidebar experiments_cards view_type)
before_action :load_current_folder, only: %i(index cards new show)
before_action :check_view_permissions, only: %i(show notifications sidebar experiments_cards)
before_action :check_view_permissions, only: %i(show notifications sidebar experiments_cards view_type)
before_action :check_create_permissions, only: %i(new create)
before_action :check_manage_permissions, only: :edit
before_action :set_inline_name_editing, only: %i(show)
before_action :load_exp_sort_var, only: %i(show)
before_action :reset_invalid_view_state, only: %i(index cards)
before_action :load_exp_sort_var, only: :show
before_action :reset_invalid_view_state, only: %i(index cards show)
layout 'fluid'
@ -26,6 +27,7 @@ class ProjectsController < ApplicationController
if current_team
view_state = current_team.current_view_state(current_user)
@current_sort = view_state.state.dig('projects', projects_view_mode, 'sort') || 'atoz'
@current_view_type = view_state.state.dig('projects', 'view_type')
end
end
@ -261,6 +263,7 @@ class ProjectsController < ApplicationController
view_state = @project.current_view_state(current_user)
@current_sort = view_state.state.dig('experiments', experiments_view_mode(@project), 'sort') || 'atoz'
@current_view_type = view_state.state.dig('experiments', 'view_type')
end
def experiments_cards
@ -298,12 +301,24 @@ class ProjectsController < ApplicationController
render json: users, status: :ok
end
def view_type
view_state = @project.current_view_state(current_user)
view_state.state['experiments']['view_type'] = view_type_params
view_state.save!
render json: { cards_view_type_class: cards_view_type_class(view_type_params) }, status: :ok
end
private
def project_params
params.require(:project).permit(:name, :team_id, :visibility, :archived, :project_folder_id)
end
def view_type_params
params.require(:project).require(:view_type)
end
def load_vars
@project = Project.find_by(id: params[:id])
@ -357,7 +372,12 @@ class ProjectsController < ApplicationController
end
def reset_invalid_view_state
view_state = current_team.current_view_state(current_user)
view_state = if action_name == 'show'
@project.current_view_state(current_user)
else
current_team.current_view_state(current_user)
end
view_state.destroy unless view_state.valid?
end

View file

@ -2,13 +2,13 @@
class TeamsController < ApplicationController
include ProjectsHelper
include CardsViewHelper
attr_reader :current_folder
helper_method :current_folder
before_action :load_vars, only: %i(sidebar export_projects export_projects_modal)
before_action :load_current_folder, only: :sidebar
before_action :check_read_permissions
before_action :check_read_permissions, except: :view_type
before_action :check_export_projects_permissions, only: %i(export_projects_modal export_projects)
def sidebar
@ -77,6 +77,14 @@ class TeamsController < ApplicationController
redirect_to root_path
end
def view_type
view_state = current_team.current_view_state(current_user)
view_state.state['projects']['view_type'] = view_type_params
view_state.save!
render json: { cards_view_type_class: cards_view_type_class(view_type_params) }, status: :ok
end
private
def load_vars
@ -88,6 +96,10 @@ class TeamsController < ApplicationController
params.permit(:id, project_ids: [], project_folder_ids: [])
end
def view_type_params
params.require(:projects).require(:view_type)
end
def check_read_permissions
render_403 unless can_read_team?(@team)
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
module CardsViewHelper
def cards_view_type_class(view_type)
view_type == 'table' ? 'list' : ''
end
end

View file

@ -155,13 +155,15 @@ class Project < ApplicationRecord
{
experiments: {
active: { sort: 'new' },
archived: { sort: 'new' }
archived: { sort: 'new' },
view_type: 'cards'
}
}
end
def validate_view_state(view_state)
if %w(new old atoz ztoa).exclude?(view_state.state.dig('experiments', 'active', 'sort')) ||
if %w(cards table).exclude?(view_state.state.dig('experiments', 'view_type')) ||
%w(new old atoz ztoa).exclude?(view_state.state.dig('experiments', 'active', 'sort')) ||
%w(new old atoz ztoa archived_new archived_old).exclude?(view_state.state.dig('experiments', 'archived', 'sort'))
view_state.errors.add(:state, :wrong_state)
end

View file

@ -48,14 +48,16 @@ class Team < ApplicationRecord
{
projects: {
active: { sort: 'new' },
archived: { sort: 'new' }
archived: { sort: 'new' },
view_type: 'cards'
}
}
end
def validate_view_state(view_state)
if %w(new old atoz ztoa).exclude?(view_state.state.dig('projects', 'active', 'sort')) ||
%w(new old atoz ztoa archived_new archived_old).exclude?(view_state.state.dig('projects', 'archived', 'sort'))
%w(new old atoz ztoa archived_new archived_old).exclude?(view_state.state.dig('projects', 'archived', 'sort')) ||
%w(cards table).exclude?(view_state.state.dig('projects', 'view_type'))
view_state.errors.add(:state, :wrong_state)
end
end

View file

@ -17,7 +17,9 @@
<%= render partial: 'projects/index/modals/export_projects' %>
<div class="projects-container">
<div class="cards-wrapper" id="cardsWrapper" data-projects-cards-url="<%= @current_folder ? project_folder_cards_url(@current_folder) : cards_projects_url %>">
<div class="cards-wrapper <%= cards_view_type_class(@current_view_type) %>"
id="cardsWrapper"
data-projects-cards-url="<%= @current_folder ? project_folder_cards_url(@current_folder) : cards_projects_url %>">
<div class="table-header">
<div class="table-header-cell select-all-checkboxes">
<div class="sci-checkbox-container">

View file

@ -11,12 +11,28 @@
<div class="sci-btn-group header-actions">
<%= render layout: 'shared/view_switch', locals: { disabled: false } do %>
<li class="view-switch-cards cards-switch active">
<i class="fas fa-th-large button-icon"></i> <%= t('projects.index.header.cards') %>
<li class="view-switch-list cards-switch form-dropdown-item">
<%= button_to(view_type_projects_path(format: :json),
method: :put,
remote: true,
class: "btn btn-light button-to #{ @current_view_type == 'cards' ? 'selected' : ''}",
form_class: 'change-projects-view-type-form',
params: { projects: { view_type: 'cards' } }) do %>
<i class="fas fa-th-large"></i>
<%= t('projects.index.header.cards') %>
<% end %>
</li>
<li class="view-switch-list cards-switch ">
<i class="fas fa-list button-icon"></i> <%= t('projects.index.header.table') %>
<li class="view-switch-list cards-switch form-dropdown-item">
<%= button_to(view_type_projects_path(format: :json),
method: :put,
remote: true,
class: "btn btn-light button-to #{ @current_view_type == 'table' ? 'selected' : ''}",
form_class: 'change-projects-view-type-form',
params: { projects: { view_type: 'table' } }) do %>
<i class="fas fa-list"></i>
<%= t('projects.index.header.table') %>
<% end %>
</li>
<div role="separator" class="divider"></div>
<li class="view-switch-active archive-switch" data-view-mode="archived" data-url="<%= projects_path %>">

View file

@ -33,7 +33,7 @@
params: { project: { archived: true } }) do %>
<i class="fas fa-archive"></i>
<span><%= t('projects.index.archive_option') %></span>
<% end %>
<% end %>
</li>
<% elsif project.archived? && can_restore_project?(project) %>
<li class="form-dropdown-item">

View file

@ -11,7 +11,7 @@
<%= render partial: 'projects/show/header' %>
<div class="project-show-container">
<div class="cards-wrapper <%= 'readonly' unless can_manage_experiments?(@project) %>"
<div class="cards-wrapper <%= 'readonly' unless can_manage_experiments?(@project) %> <%= cards_view_type_class(@current_view_type) %>"
id="cardsWrapper"
data-experiments-cards-url="<%= experiments_cards_project_path(@project) %>">
<!-- list -->

View file

@ -18,12 +18,28 @@
<div class="sci-btn-group header-actions">
<!-- View switch dropdown -->
<%= render layout: 'shared/view_switch', locals: { disabled: false } do %>
<li class="view-switch-cards cards-switch active">
<i class="fas fa-th-large button-icon"></i> <%= t('experiments.header.cards') %>
<li class="view-switch-list cards-switch form-dropdown-item">
<%= button_to(view_type_project_path(@project, format: :json),
method: :put,
remote: true,
class: "btn btn-light button-to #{ @current_view_type == 'cards' ? 'selected' : ''}",
form_class: 'change-experiments-view-type-form',
params: { project: { view_type: 'cards', project_id: @project.id } }) do %>
<i class="fas fa-th-large"></i>
<%= t('projects.index.header.cards') %>
<% end %>
</li>
<li class="view-switch-list cards-switch ">
<i class="fas fa-list button-icon"></i> <%= t('experiments.header.table') %>
<li class="view-switch-list cards-switch form-dropdown-item">
<%= button_to(view_type_project_path(@project, format: :json),
method: :put,
remote: true,
class: "btn btn-light button-to #{ @current_view_type == 'table' ? 'selected' : ''}",
form_class: 'change-experiments-view-type-form',
params: { project: { view_type: 'table', project_id: @project.id } }) do %>
<i class="fas fa-list"></i>
<%= t('projects.index.header.table') %>
<% end %>
</li>
<div role="separator" class="divider"></div>

View file

@ -294,6 +294,7 @@ Rails.application.routes.draw do
get 'notifications'
get 'experiments_cards'
get 'sidebar'
put 'view_type'
end
collection do
@ -301,6 +302,7 @@ Rails.application.routes.draw do
get 'users_filter'
post 'archive_group'
post 'restore_group'
put 'view_type', to: 'teams#view_type'
end
end