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 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 { ...@@ -151,8 +151,10 @@ export default class UserTabs {
loadTab(action, endpoint) { loadTab(action, endpoint) {
this.toggleLoading(true); this.toggleLoading(true);
const params = action === 'projects' ? { skip_namespace: true } : {};
return axios return axios
.get(endpoint) .get(endpoint, { params })
.then(({ data }) => { .then(({ data }) => {
const tabSelector = `div#${action}`; const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html); this.$parentEl.find(tabSelector).html(data.html);
...@@ -188,7 +190,7 @@ export default class UserTabs { ...@@ -188,7 +190,7 @@ export default class UserTabs {
requestParams: { limit: 10 }, requestParams: { limit: 10 },
}); });
UserTabs.renderMostRecentBlocks('#js-overview .projects-block', { 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; this.loaded.overview = true;
......
...@@ -5,11 +5,12 @@ import { spriteIcon } from './lib/utils/common_utils'; ...@@ -5,11 +5,12 @@ import { spriteIcon } from './lib/utils/common_utils';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
export default class Star { export default class Star {
constructor() { constructor(container = '.project-home-panel') {
$('.project-home-panel .toggle-star').on('click', function toggleStarClickCallback() { $(`${container} .toggle-star`).on('click', function toggleStarClickCallback() {
const $this = $(this); const $this = $(this);
const $starSpan = $this.find('span'); const $starSpan = $this.find('span');
const $startIcon = $this.find('svg'); const $starIcon = $this.find('svg');
const iconClasses = $starIcon.attr('class').split(' ');
axios axios
.post($this.data('endpoint')) .post($this.data('endpoint'))
...@@ -22,12 +23,12 @@ export default class Star { ...@@ -22,12 +23,12 @@ export default class Star {
if (isStarred) { if (isStarred) {
$starSpan.removeClass('starred').text(s__('StarProject|Star')); $starSpan.removeClass('starred').text(s__('StarProject|Star'));
$startIcon.remove(); $starIcon.remove();
$this.prepend(spriteIcon('star-o', 'icon')); $this.prepend(spriteIcon('star-o', iconClasses));
} else { } else {
$starSpan.addClass('starred').text(__('Unstar')); $starSpan.addClass('starred').text(__('Unstar'));
$startIcon.remove(); $starIcon.remove();
$this.prepend(spriteIcon('star', 'icon')); $this.prepend(spriteIcon('star', iconClasses));
} }
}) })
.catch(() => Flash('Star toggle failed. Try again later.')); .catch(() => Flash('Star toggle failed. Try again later.'));
......
...@@ -108,6 +108,7 @@ ...@@ -108,6 +108,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
text-decoration: none;
} }
.avatar { .avatar {
...@@ -120,6 +121,7 @@ ...@@ -120,6 +121,7 @@
} }
&.s40 { min-width: 40px; min-height: 40px; } &.s40 { min-width: 40px; min-height: 40px; }
&.s64 { min-width: 64px; min-height: 64px; }
} }
.avatar-counter { .avatar-counter {
......
...@@ -198,6 +198,7 @@ $well-light-text-color: #5b6169; ...@@ -198,6 +198,7 @@ $well-light-text-color: #5b6169;
$gl-font-size: 14px; $gl-font-size: 14px;
$gl-font-size-xs: 11px; $gl-font-size-xs: 11px;
$gl-font-size-small: 12px; $gl-font-size-small: 12px;
$gl-font-size-medium: 1.43rem;
$gl-font-size-large: 16px; $gl-font-size-large: 16px;
$gl-font-weight-normal: 400; $gl-font-weight-normal: 400;
$gl-font-weight-bold: 600; $gl-font-weight-bold: 600;
...@@ -276,6 +277,7 @@ $project-title-row-height: 64px; ...@@ -276,6 +277,7 @@ $project-title-row-height: 64px;
$project-avatar-mobile-size: 24px; $project-avatar-mobile-size: 24px;
$gl-line-height: 16px; $gl-line-height: 16px;
$gl-line-height-24: 24px; $gl-line-height-24: 24px;
$gl-line-height-14: 14px;
/* /*
* Common component specific colors * Common component specific colors
......
...@@ -969,34 +969,73 @@ pre.light-well { ...@@ -969,34 +969,73 @@ pre.light-well {
@include basic-list-stats; @include basic-list-stats;
display: flex; display: flex;
align-items: center; align-items: center;
color: $gl-text-color-secondary;
padding: $gl-padding 0;
@include media-breakpoint-up(lg) {
padding: $gl-padding-24 0;
} }
h3 { &.no-description {
font-size: $gl-font-size; @include media-breakpoint-up(sm) {
.avatar-container {
align-self: center;
} }
.avatar-container, .metadata-info {
.controls { margin-bottom: 0;
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 { .avatar-container {
flex: 0 0 auto;
align-self: flex-start; align-self: flex-start;
} }
.project-details { .project-details {
min-width: 0; 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, p,
.commit-row-message { .commit-row-message {
@include str-truncated(100%); @include str-truncated(100%);
margin-bottom: 0; margin-bottom: 0;
} }
.user-access-role {
margin: 0;
} }
.controls { @include media-breakpoint-up(md) {
margin-left: auto; .description {
text-align: right; color: $gl-text-color;
}
}
@include media-breakpoint-down(md) {
.user-access-role {
line-height: $gl-line-height-14;
}
}
} }
.ci-status-link { .ci-status-link {
...@@ -1008,6 +1047,149 @@ pre.light-well { ...@@ -1008,6 +1047,149 @@ pre.light-well {
text-decoration: none; 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 { .card .projects-list li {
......
...@@ -58,11 +58,13 @@ class UsersController < ApplicationController ...@@ -58,11 +58,13 @@ class UsersController < ApplicationController
load_projects load_projects
skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination]) 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| respond_to do |format|
format.html { render 'show' } format.html { render 'show' }
format.json do 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 end
end end
......
...@@ -515,6 +515,20 @@ module ProjectsHelper ...@@ -515,6 +515,20 @@ module ProjectsHelper
end end
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 def sidebar_projects_paths
%w[ %w[
projects#show projects#show
......
...@@ -2,24 +2,29 @@ ...@@ -2,24 +2,29 @@
- avatar = true unless local_assigns[:avatar] == false - avatar = true unless local_assigns[:avatar] == false
- use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true - use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true
- stars = true unless local_assigns[:stars] == 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
- ci = false unless local_assigns[:ci] == true - ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true - skip_namespace = false unless local_assigns[:skip_namespace] == true
- user = local_assigns[:user] - user = local_assigns[:user]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
- remote = false unless local_assigns[:remote] == true - remote = false unless local_assigns[:remote] == true
- skip_pagination = false unless local_assigns[:skip_pagination] == 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 .js-projects-list-holder
- if any_projects?(projects) - if any_projects?(projects)
- load_pipeline_status(projects) - load_pipeline_status(projects)
%ul.projects-list{ class: css_classes }
%ul.projects-list
- projects.each_with_index do |project, i| - projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil - css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace, = 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, 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 - if @private_forks_count && @private_forks_count > 0
%li.project-row.private-forks-notice %li.project-row.private-forks-notice
......
- avatar = true unless local_assigns[:avatar] == false - avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == 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 - skip_namespace = false unless local_assigns[:skip_namespace] == true
- access = max_project_member_access(project) - 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) - 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 - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- cache_key = project_list_cache_key(project) - cache_key = project_list_cache_key(project)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date) - 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 = cache(cache_key) do
- if avatar - if avatar
.avatar-container.s40 .avatar-container.s64.flex-grow-0.flex-shrink-0
= link_to project_path(project), class: dom_class(project) do = link_to project_path(project), class: dom_class(project) do
- if project.creator && use_creator_avatar - 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 - else
= project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40) = project_icon(project, alt: '', class: 'avatar project-avatar s64', width: 64, height: 64)
.project-details .project-details.flex-sm-fill{ class: css_details_class }
%h3.prepend-top-0.append-bottom-0 .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 = link_to project_path(project), class: 'text-plain' do
%span.project-full-name>< %span.project-full-name.append-right-8><
%span.namespace-name %span.namespace-name
- if project.namespace && !skip_namespace - if project.namespace && !skip_namespace
= project.namespace.human_name = project.namespace.human_name
...@@ -29,34 +37,71 @@ ...@@ -29,34 +37,71 @@
%span.project-name< %span.project-name<
= project.name = project.name
- if access&.nonzero? %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 -# haml-lint:disable UnnecessaryStringOutput
= ' ' # prevent haml from eating the space between elements = ' ' # prevent haml from eating the space between elements
%span.user-access-role= Gitlab::Access.human_access(access) .metadata-info.prepend-top-8
%span.user-access-role.d-block= Gitlab::Access.human_access(access)
- if show_last_commit_as_description - if show_last_commit_as_description
.description.prepend-top-5 .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") = link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
- elsif project.description.present? - elsif project.description.present?
.description.prepend-top-5 .description.d-none.d-sm-block.prepend-top-8.append-right-default
= markdown_field(project, :description) = markdown_field(project, :description)
.controls .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 }
.prepend-top-0 .icon-container.d-flex.align-items-center
- if project.archived - if project.archived
%span.prepend-left-10.badge.badge-warning archived %span.d-flex.icon-wrapper.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 - if stars
%span.prepend-left-10 %span.d-flex.align-items-center.icon-wrapper.stars.has-tooltip{ data: { container: 'body', placement: 'top' }, title: _('Stars') }
= icon('star') = sprite_icon('star', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.star_count) = number_with_delimiter(project.star_count)
%span.prepend-left-10.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project) } - if forks
= visibility_level_icon(project.visibility_level, fw: true) = link_to project_forks_path(project),
.prepend-top-0 class: "align-items-center icon-wrapper forks has-tooltip",
updated #{updated_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 "" ...@@ -3094,6 +3094,9 @@ msgstr ""
msgid "Forking in progress" msgid "Forking in progress"
msgstr "" msgstr ""
msgid "Forks"
msgstr ""
msgid "Format" msgid "Format"
msgstr "" msgstr ""
...@@ -6266,6 +6269,9 @@ msgstr "" ...@@ -6266,6 +6269,9 @@ msgstr ""
msgid "Starred projects" msgid "Starred projects"
msgstr "" msgstr ""
msgid "Stars"
msgstr ""
msgid "Start a %{new_merge_request} with these changes" msgid "Start a %{new_merge_request} with these changes"
msgstr "" 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