Commit d7619fd6 authored by Sean McGivern's avatar Sean McGivern

Merge branch '51944-redesign-project-lists-ui' into 'master'

Resolve "Redesign project lists UI"

Closes #51944

See merge request gitlab-org/gitlab-ce!22682
parents b96150d8 388d4170
import ProjectsList from '~/projects_list';
import Star from '../../../star';
document.addEventListener('DOMContentLoaded', () => new ProjectsList());
document.addEventListener('DOMContentLoaded', () => {
new ProjectsList(); // eslint-disable-line no-new
new Star('.project-row'); // eslint-disable-line no-new
});
// if the "projects dashboard" is a user's default dashboard, when they visit the
// instance root index, the dashboard will be served by the root controller instead
// of a dashboard controller. The root index redirects for all other default dashboards.
import '../dashboard/projects/index';
......@@ -151,8 +151,10 @@ export default class UserTabs {
loadTab(action, endpoint) {
this.toggleLoading(true);
const params = action === 'projects' ? { skip_namespace: true } : {};
return axios
.get(endpoint)
.get(endpoint, { params })
.then(({ data }) => {
const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html);
......@@ -188,7 +190,7 @@ export default class UserTabs {
requestParams: { limit: 10 },
});
UserTabs.renderMostRecentBlocks('#js-overview .projects-block', {
requestParams: { limit: 10, skip_pagination: true },
requestParams: { limit: 10, skip_pagination: true, skip_namespace: true, compact_mode: true },
});
this.loaded.overview = true;
......
......@@ -5,11 +5,12 @@ import { spriteIcon } from './lib/utils/common_utils';
import axios from './lib/utils/axios_utils';
export default class Star {
constructor() {
$('.project-home-panel .toggle-star').on('click', function toggleStarClickCallback() {
constructor(container = '.project-home-panel') {
$(`${container} .toggle-star`).on('click', function toggleStarClickCallback() {
const $this = $(this);
const $starSpan = $this.find('span');
const $startIcon = $this.find('svg');
const $starIcon = $this.find('svg');
const iconClasses = $starIcon.attr('class').split(' ');
axios
.post($this.data('endpoint'))
......@@ -22,12 +23,12 @@ export default class Star {
if (isStarred) {
$starSpan.removeClass('starred').text(s__('StarProject|Star'));
$startIcon.remove();
$this.prepend(spriteIcon('star-o', 'icon'));
$starIcon.remove();
$this.prepend(spriteIcon('star-o', iconClasses));
} else {
$starSpan.addClass('starred').text(__('Unstar'));
$startIcon.remove();
$this.prepend(spriteIcon('star', 'icon'));
$starIcon.remove();
$this.prepend(spriteIcon('star', iconClasses));
}
})
.catch(() => Flash('Star toggle failed. Try again later.'));
......
......@@ -108,6 +108,7 @@
width: 100%;
height: 100%;
display: flex;
text-decoration: none;
}
.avatar {
......@@ -120,6 +121,7 @@
}
&.s40 { min-width: 40px; min-height: 40px; }
&.s64 { min-width: 64px; min-height: 64px; }
}
.avatar-counter {
......
......@@ -198,6 +198,7 @@ $well-light-text-color: #5b6169;
$gl-font-size: 14px;
$gl-font-size-xs: 11px;
$gl-font-size-small: 12px;
$gl-font-size-medium: 1.43rem;
$gl-font-size-large: 16px;
$gl-font-weight-normal: 400;
$gl-font-weight-bold: 600;
......@@ -276,6 +277,7 @@ $project-title-row-height: 64px;
$project-avatar-mobile-size: 24px;
$gl-line-height: 16px;
$gl-line-height-24: 24px;
$gl-line-height-14: 14px;
/*
* Common component specific colors
......
......@@ -969,34 +969,73 @@ pre.light-well {
@include basic-list-stats;
display: flex;
align-items: center;
}
color: $gl-text-color-secondary;
padding: $gl-padding 0;
h3 {
font-size: $gl-font-size;
@include media-breakpoint-up(lg) {
padding: $gl-padding-24 0;
}
&.no-description {
@include media-breakpoint-up(sm) {
.avatar-container {
align-self: center;
}
.metadata-info {
margin-bottom: 0;
}
}
}
}
.avatar-container,
.controls {
flex: 0 0 auto;
h2 {
font-size: $gl-font-size-medium;
font-weight: $gl-font-weight-bold;
margin-bottom: 0;
@include media-breakpoint-up(sm) {
.namespace-name {
font-weight: $gl-font-weight-normal;
}
}
}
.avatar-container {
flex: 0 0 auto;
align-self: flex-start;
}
.project-details {
min-width: 0;
line-height: $gl-line-height;
.flex-wrapper {
min-width: 0;
margin-top: -$gl-padding-8; // negative margin required for flex-wrap
}
p,
.commit-row-message {
@include str-truncated(100%);
margin-bottom: 0;
}
}
.controls {
margin-left: auto;
text-align: right;
.user-access-role {
margin: 0;
}
@include media-breakpoint-up(md) {
.description {
color: $gl-text-color;
}
}
@include media-breakpoint-down(md) {
.user-access-role {
line-height: $gl-line-height-14;
}
}
}
.ci-status-link {
......@@ -1008,6 +1047,149 @@ pre.light-well {
text-decoration: none;
}
}
.controls {
margin-top: $gl-padding;
@include media-breakpoint-down(md) {
margin-top: 0;
}
@include media-breakpoint-down(xs) {
margin-top: $gl-padding-8;
}
.icon-wrapper {
color: inherit;
margin-right: $gl-padding;
@include media-breakpoint-down(md) {
margin-right: 0;
margin-left: $gl-padding-8;
}
@include media-breakpoint-down(xs) {
&:first-child {
margin-left: 0;
}
}
}
.ci-status-link {
display: inline-flex;
}
}
.star-button {
.icon {
top: 0;
}
}
.icon-container {
@include media-breakpoint-down(xs) {
margin-right: $gl-padding-8;
}
}
&.compact {
.project-row {
padding: $gl-padding 0;
}
h2 {
font-size: $gl-font-size;
}
.avatar-container {
@include avatar-size(40px, 10px);
min-height: 40px;
min-width: 40px;
.identicon.s64 {
font-size: 16px;
}
}
.controls {
@include media-breakpoint-up(sm) {
margin-top: 0;
}
}
.updated-note {
@include media-breakpoint-up(sm) {
margin-top: $gl-padding-8;
}
}
.icon-wrapper {
margin-left: $gl-padding-8;
margin-right: 0;
@include media-breakpoint-down(xs) {
&:first-child {
margin-left: 0;
}
}
}
.user-access-role {
line-height: $gl-line-height-14;
}
}
@include media-breakpoint-down(md) {
h2 {
font-size: $gl-font-size;
}
.avatar-container {
@include avatar-size(40px, 10px);
min-height: 40px;
min-width: 40px;
.identicon.s64 {
font-size: 16px;
}
}
}
@include media-breakpoint-down(md) {
.updated-note {
margin-top: $gl-padding-8;
text-align: right;
}
}
.forks,
.pipeline-status,
.updated-note {
display: flex;
}
@include media-breakpoint-down(md) {
&:not(.explore) {
.forks {
display: none;
}
}
&.explore {
.pipeline-status,
.updated-note {
display: none !important;
}
}
}
@include media-breakpoint-down(xs) {
.updated-note {
margin-top: 0;
text-align: left;
}
}
}
.card .projects-list li {
......
......@@ -58,11 +58,13 @@ class UsersController < ApplicationController
load_projects
skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination])
skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace])
compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode])
respond_to do |format|
format.html { render 'show' }
format.json do
pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination)
pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode)
end
end
end
......
......@@ -515,6 +515,20 @@ module ProjectsHelper
end
end
def explore_projects_tab?
current_page?(explore_projects_path) ||
current_page?(trending_explore_projects_path) ||
current_page?(starred_explore_projects_path)
end
def show_merge_request_count?(merge_requests, compact_mode)
merge_requests && !compact_mode && Feature.enabled?(:project_list_show_mr_count, default_enabled: true)
end
def show_issue_count?(issues, compact_mode)
issues && !compact_mode && Feature.enabled?(:project_list_show_issue_count, default_enabled: true)
end
def sidebar_projects_paths
%w[
projects#show
......
......@@ -2,24 +2,29 @@
- avatar = true unless local_assigns[:avatar] == false
- use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true
- stars = true unless local_assigns[:stars] == false
- forks = false unless local_assigns[:forks] == true
- forks = true unless local_assigns[:forks] == false
- merge_requests = true unless local_assigns[:merge_requests] == false
- issues = true unless local_assigns[:issues] == false
- pipeline_status = true unless local_assigns[:pipeline_status] == false
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- user = local_assigns[:user]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
- remote = false unless local_assigns[:remote] == true
- skip_pagination = false unless local_assigns[:skip_pagination] == true
- compact_mode = false unless local_assigns[:compact_mode] == true
- css_classes = "#{'compact' if compact_mode} #{'explore' if explore_projects_tab?}"
.js-projects-list-holder
- if any_projects?(projects)
- load_pipeline_status(projects)
%ul.projects-list
%ul.projects-list{ class: css_classes }
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user
forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user, merge_requests: merge_requests,
issues: issues, pipeline_status: pipeline_status, compact_mode: compact_mode
- if @private_forks_count && @private_forks_count > 0
%li.project-row.private-forks-notice
......
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
- forks = false unless local_assigns[:forks] == true
- forks = true unless local_assigns[:forks] == false
- merge_requests = true unless local_assigns[:merge_requests] == false
- issues = true unless local_assigns[:issues] == false
- pipeline_status = true unless local_assigns[:pipeline_status] == false
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- access = max_project_member_access(project)
- css_class = '' unless local_assigns[:css_class]
- compact_mode = false unless local_assigns[:compact_mode] == true
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project)
- css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- cache_key = project_list_cache_key(project)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
- css_details_class = compact_mode ? "d-flex flex-column flex-sm-row flex-md-row align-items-sm-center" : "align-items-center flex-md-fill flex-lg-column d-sm-flex d-lg-block"
- css_controls_class = compact_mode ? "" : "align-items-md-end align-items-lg-center flex-lg-row"
%li.project-row{ class: css_class }
%li.project-row.d-flex{ class: css_class }
= cache(cache_key) do
- if avatar
.avatar-container.s40
.avatar-container.s64.flex-grow-0.flex-shrink-0
= link_to project_path(project), class: dom_class(project) do
- if project.creator && use_creator_avatar
= image_tag avatar_icon_for_user(project.creator, 40), class: "avatar s40", alt:''
= image_tag avatar_icon_for_user(project.creator, 64), class: "avatar s65", alt:''
- else
= project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40)
.project-details
%h3.prepend-top-0.append-bottom-0
= link_to project_path(project), class: 'text-plain' do
%span.project-full-name><
%span.namespace-name
- if project.namespace && !skip_namespace
= project.namespace.human_name
\/
%span.project-name<
= project.name
- if access&.nonzero?
-# haml-lint:disable UnnecessaryStringOutput
= ' ' # prevent haml from eating the space between elements
%span.user-access-role= Gitlab::Access.human_access(access)
- if show_last_commit_as_description
.description.prepend-top-5
= link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
- elsif project.description.present?
.description.prepend-top-5
= markdown_field(project, :description)
.controls
.prepend-top-0
- if project.archived
%span.prepend-left-10.badge.badge-warning archived
- if can?(current_user, :read_cross_project) && project.pipeline_status.has_status?
%span.prepend-left-10
= render_project_pipeline_status(project.pipeline_status)
- if forks
%span.prepend-left-10
= sprite_icon('fork', size: 12)
= number_with_delimiter(project.forks_count)
- if stars
%span.prepend-left-10
= icon('star')
= number_with_delimiter(project.star_count)
%span.prepend-left-10.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project) }
= visibility_level_icon(project.visibility_level, fw: true)
.prepend-top-0
updated #{updated_tooltip}
= project_icon(project, alt: '', class: 'avatar project-avatar s64', width: 64, height: 64)
.project-details.flex-sm-fill{ class: css_details_class }
.flex-wrapper.flex-fill
.d-flex.align-items-center.flex-wrap
%h2.d-flex.prepend-top-8
= link_to project_path(project), class: 'text-plain' do
%span.project-full-name.append-right-8><
%span.namespace-name
- if project.namespace && !skip_namespace
= project.namespace.human_name
\/
%span.project-name<
= project.name
%span.metadata-info.visibility-icon.append-right-10.prepend-top-8.has-tooltip{ data: { container: 'body', placement: 'top' }, title: visibility_icon_description(project) }
= visibility_level_icon(project.visibility_level, fw: true)
- if explore_projects_tab? && project.repository.license
%span.metadata-info.d-inline-flex.align-items-center.append-right-10.prepend-top-8
= sprite_icon('scale', size: 14, css_class: 'append-right-4')
= project.repository.license.name
- if !explore_projects_tab? && access&.nonzero?
-# haml-lint:disable UnnecessaryStringOutput
= ' ' # prevent haml from eating the space between elements
.metadata-info.prepend-top-8
%span.user-access-role.d-block= Gitlab::Access.human_access(access)
- if show_last_commit_as_description
.description.d-none.d-sm-block.prepend-top-8.append-right-default
= link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
- elsif project.description.present?
.description.d-none.d-sm-block.prepend-top-8.append-right-default
= markdown_field(project, :description)
.controls.d-flex.flex-row.flex-sm-column.flex-md-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class }
.icon-container.d-flex.align-items-center
- if project.archived
%span.d-flex.icon-wrapper.badge.badge-warning archived
- if stars
%span.d-flex.align-items-center.icon-wrapper.stars.has-tooltip{ data: { container: 'body', placement: 'top' }, title: _('Stars') }
= sprite_icon('star', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.star_count)
- if forks
= link_to project_forks_path(project),
class: "align-items-center icon-wrapper forks has-tooltip",
title: _('Forks'), data: { container: 'body', placement: 'top' } do
= sprite_icon('fork', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.forks_count)
- if show_merge_request_count?(merge_requests, compact_mode)
= link_to project_merge_requests_path(project),
class: "d-none d-lg-flex align-items-center icon-wrapper merge-requests has-tooltip",
title: _('Merge Requests'), data: { container: 'body', placement: 'top' } do
= sprite_icon('git-merge', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.open_merge_requests_count)
- if show_issue_count?(issues, compact_mode)
= link_to project_issues_path(project),
class: "d-none d-lg-flex align-items-center icon-wrapper issues has-tooltip",
title: _('Issues'), data: { container: 'body', placement: 'top' } do
= sprite_icon('issues', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.open_issues_count)
- if pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status?
%span.icon-wrapper.pipeline-status
= render_project_pipeline_status(project.pipeline_status, tooltip_placement: 'top')
.updated-note
%span Updated #{updated_tooltip}
.d-none.d-lg-flex.align-item-stretch
- unless compact_mode
- if current_user
%button.star-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(project, :json) } }
- if current_user.starred?(project)
= sprite_icon('star', { css_class: 'icon' })
%span.starred= s_('ProjectOverview|Unstar')
- else
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
- else
= link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
---
title: Redesign project lists UI
merge_request: 22682
author:
type: other
......@@ -3094,6 +3094,9 @@ msgstr ""
msgid "Forking in progress"
msgstr ""
msgid "Forks"
msgstr ""
msgid "Format"
msgstr ""
......@@ -6266,6 +6269,9 @@ msgstr ""
msgid "Starred projects"
msgstr ""
msgid "Stars"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment