From ee8ee264b5685e2fbbb5ba56f861b8b430e3f9e8 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Sun, 6 Nov 2016 00:44:32 +0000 Subject: [PATCH 1/4] Added snippets empty state to project snippets and dashboard snippets Separated filtered and empty states Added to user page and added absolute empty state to dashboard Review changes Added padding and tests --- app/assets/javascripts/user_tabs.js.es6 | 14 ++++ app/assets/stylesheets/framework/blocks.scss | 6 ++ app/controllers/users_controller.rb | 4 +- app/views/dashboard/snippets/index.html.haml | 75 ++++++++++--------- app/views/projects/snippets/index.html.haml | 17 +++-- .../shared/empty_states/_snippets.html.haml | 15 ++++ .../shared/empty_states/icons/_snippets.svg | 1 + app/views/snippets/_snippets.html.haml | 18 ++--- app/views/users/show.html.haml | 2 + ...d-better-empty-state-for-snippets-view.yml | 4 + 10 files changed, 101 insertions(+), 55 deletions(-) create mode 100644 app/views/shared/empty_states/_snippets.html.haml create mode 100644 app/views/shared/empty_states/icons/_snippets.svg create mode 100644 changelogs/unreleased/20847-getting-started-better-empty-state-for-snippets-view.yml diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index 5a625611987a..753228f42c5e 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -66,6 +66,7 @@ content on the Users#show page. this.action = action || this.defaultAction; this.$parentEl = $(parentEl) || $(document); this._location = window.location; + this.initEmptyStates(); this.$parentEl.find('.nav-links a') .each((i, navLink) => { this.loaded[$(navLink).attr('data-action')] = false; @@ -85,6 +86,12 @@ content on the Users#show page. .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); } + initEmptyStates() { + this.emptyStates = { + snippets: document.querySelector('#js-user-snippets-empty-state'), + }; + } + tabShown(event) { const $target = $(event.target); const action = $target.data('action'); @@ -113,6 +120,7 @@ content on the Users#show page. } loadTab(source, action) { + if (this.emptyStates[action]) this.emptyStates[action].classList.add('hidden'); return $.ajax({ beforeSend: () => this.toggleLoading(true), complete: () => this.toggleLoading(false), @@ -121,6 +129,7 @@ content on the Users#show page. url: `${source}.json`, success: (data) => { const tabSelector = `div#${action}`; + if (!data.html) return this.showEmptyState(tabSelector, action); this.$parentEl.find(tabSelector).html(data.html); this.loaded[action] = true; return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); @@ -128,6 +137,11 @@ content on the Users#show page. }); } + showEmptyState(tabSelector, action) { + if (this.emptyStates[action]) this.emptyStates[action].classList.remove('hidden'); + this.loaded[action] = true; + } + loadActivities(source) { if (this.loaded['activity']) { return; diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 77ae9e9a6e7d..9e6c27925b44 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -282,4 +282,10 @@ width: 100%; } } + + @media(min-width: $screen-xs-max) { + &.snippets .text-content { + margin-top: 100px; + } + } } diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6e29f1e8a655..504e79574bd4 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -64,9 +64,7 @@ def snippets respond_to do |format| format.html { render 'show' } format.json do - render json: { - html: view_to_html_string("snippets/_snippets", collection: @snippets, remote: true) - } + render json: { html: @snippets.blank? ? nil : view_to_html_string("snippets/_snippets", collection: @snippets, remote: true) } end end end diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index b2af438ea576..cba9824202de 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -1,42 +1,45 @@ - page_title "Snippets" - header_title "Snippets", dashboard_snippets_path -= render 'dashboard/snippets_head' - -.nav-block - .controls.hidden-xs - = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do - = icon('plus') - New snippet - - .nav-links.snippet-scope-menu - %li{ class: ("active" unless params[:scope]) } - = link_to dashboard_snippets_path do - All - %span.badge - = current_user.snippets.count - - %li{ class: ("active" if params[:scope] == "are_private") } - = link_to dashboard_snippets_path(scope: 'are_private') do - Private - %span.badge - = current_user.snippets.are_private.count - - %li{ class: ("active" if params[:scope] == "are_internal") } - = link_to dashboard_snippets_path(scope: 'are_internal') do - Internal - %span.badge - = current_user.snippets.are_internal.count - - %li{ class: ("active" if params[:scope] == "are_public") } - = link_to dashboard_snippets_path(scope: 'are_public') do - Public - %span.badge - = current_user.snippets.are_public.count - - .visible-xs - = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do +- if current_user.snippets.exists? + = render 'dashboard/snippets_head' + + .nav-block + .controls.hidden-xs + = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do = icon('plus') New snippet -= render 'snippets/snippets' + .nav-links.snippet-scope-menu + %li{ class: ("active" unless params[:scope]) } + = link_to dashboard_snippets_path do + All + %span.badge + = current_user.snippets.count + + %li{ class: ("active" if params[:scope] == "are_private") } + = link_to dashboard_snippets_path(scope: 'are_private') do + Private + %span.badge + = current_user.snippets.are_private.count + + %li{ class: ("active" if params[:scope] == "are_internal") } + = link_to dashboard_snippets_path(scope: 'are_internal') do + Internal + %span.badge + = current_user.snippets.are_internal.count + + %li{ class: ("active" if params[:scope] == "are_public") } + = link_to dashboard_snippets_path(scope: 'are_public') do + Public + %span.badge + = current_user.snippets.are_public.count + + .visible-xs + = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do + = icon('plus') + New snippet + + = render 'snippets/snippets' +- else + = render 'shared/empty_states/snippets', button_path: new_snippet_path diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index e77e1b026f6e..8b255f8a8634 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,11 +1,14 @@ - page_title "Snippets" -.sub-header-block - - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do - New snippet +- if @snippets.exists? + .sub-header-block + - if can?(current_user, :create_project_snippet, @project) + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do + New snippet - .oneline - Share code pastes with others out of git repository + .oneline + Share code pastes with others out of git repository -= render 'snippets/snippets' + = render 'snippets/snippets' +- else + = render 'shared/empty_states/snippets', button_path: new_namespace_project_snippet_path(@project.namespace, @project) diff --git a/app/views/shared/empty_states/_snippets.html.haml b/app/views/shared/empty_states/_snippets.html.haml new file mode 100644 index 000000000000..4c289923a97d --- /dev/null +++ b/app/views/shared/empty_states/_snippets.html.haml @@ -0,0 +1,15 @@ +- button_path = local_assigns.fetch(:button_path, false) + +.row.empty-state.snippets + .col-xs-12.pull-right{ class: "#{'col-sm-6' if button_path}" } + .svg-content + = render 'shared/empty_states/icons/snippets.svg' + .col-xs-12{ class: "#{'col-sm-6' if button_path}" } + .text-content + - if button_path + %h4 + Snippets are small pieces of code or notes that you want to keep. + They can be either public or private. + = link_to 'New snippet', button_path, class: 'btn btn-new', title: 'New snippet', id: 'new_snippet_link' + - else + %h4.text-center There are no snippets to show. diff --git a/app/views/shared/empty_states/icons/_snippets.svg b/app/views/shared/empty_states/icons/_snippets.svg new file mode 100644 index 000000000000..105f25931a83 --- /dev/null +++ b/app/views/shared/empty_states/icons/_snippets.svg @@ -0,0 +1 @@ + diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index 77b66ca74b63..7483b2b752a0 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -1,13 +1,13 @@ - remote = local_assigns.fetch(:remote, false) -.snippets-list-holder - %ul.content-list - = render partial: 'shared/snippets/snippet', collection: @snippets - - if @snippets.empty? - %li - .nothing-here-block Nothing here. +- if @snippets.exists? + .snippets-list-holder + %ul.content-list + = render partial: 'shared/snippets/snippet', collection: @snippets - = paginate @snippets, theme: 'gitlab', remote: remote + = paginate @snippets, theme: 'gitlab', remote: remote -:javascript - gl.SnippetsList(); + :javascript + gl.SnippetsList(); +- else + = render 'shared/empty_states/snippets' diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 1e0752bd3c39..e39a0cc80336 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -120,6 +120,8 @@ #snippets.tab-pane - # This tab is always loaded via AJAX + #js-user-snippets-empty-state.hidden + = render 'shared/empty_states/snippets', button_path: new_snippet_path .loading-status = spinner diff --git a/changelogs/unreleased/20847-getting-started-better-empty-state-for-snippets-view.yml b/changelogs/unreleased/20847-getting-started-better-empty-state-for-snippets-view.yml new file mode 100644 index 000000000000..3d34762a0e0f --- /dev/null +++ b/changelogs/unreleased/20847-getting-started-better-empty-state-for-snippets-view.yml @@ -0,0 +1,4 @@ +--- +title: Added snippets empty state +merge_request: 7645 +author: -- GitLab From d0b488ea5c0d40bd0c062cbf835b2a90a31ee554 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 21 Apr 2017 13:06:38 +0100 Subject: [PATCH 2/4] Fixed haml_lint error --- app/views/users/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index f701324446ae..8427cec9ef6c 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -125,7 +125,7 @@ -# This tab is always loaded via AJAX #snippets.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #js-user-snippets-empty-state.hidden = render 'shared/empty_states/snippets', button_path: new_snippet_path -- GitLab From 1cad726b746461eebd8c95539325e96d1d3d00f9 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 21 Apr 2017 15:41:21 +0100 Subject: [PATCH 3/4] Removed user_tabs es6 file --- app/assets/javascripts/user_tabs.js.es6 | 172 ---------------------- spec/features/dashboard/shortcuts_spec.rb | 2 +- 2 files changed, 1 insertion(+), 173 deletions(-) delete mode 100644 app/assets/javascripts/user_tabs.js.es6 diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 deleted file mode 100644 index 753228f42c5e..000000000000 --- a/app/assets/javascripts/user_tabs.js.es6 +++ /dev/null @@ -1,172 +0,0 @@ -/* eslint-disable */ -/* -UserTabs - -Handles persisting and restoring the current tab selection and lazily-loading -content on the Users#show page. - -### Example Markup - - - -
-
- Activity Content -
-
- Groups Content -
-
- Contributed projects content -
-
- Projects content -
-
- Snippets content -
-
- -
-
- Loading Animation -
-
-*/ -((global) => { - class UserTabs { - constructor ({ defaultAction, action, parentEl }) { - this.loaded = {}; - this.defaultAction = defaultAction || 'activity'; - this.action = action || this.defaultAction; - this.$parentEl = $(parentEl) || $(document); - this._location = window.location; - this.initEmptyStates(); - this.$parentEl.find('.nav-links a') - .each((i, navLink) => { - this.loaded[$(navLink).attr('data-action')] = false; - }); - this.actions = Object.keys(this.loaded); - this.bindEvents(); - - if (this.action === 'show') { - this.action = this.defaultAction; - } - - this.activateTab(this.action); - } - - bindEvents() { - return this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') - .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); - } - - initEmptyStates() { - this.emptyStates = { - snippets: document.querySelector('#js-user-snippets-empty-state'), - }; - } - - tabShown(event) { - const $target = $(event.target); - const action = $target.data('action'); - const source = $target.attr('href'); - this.setTab(source, action); - return this.setCurrentAction(source, action); - } - - activateTab(action) { - return this.$parentEl.find(`.nav-links .js-${action}-tab a`) - .tab('show'); - } - - setTab(source, action) { - if (this.loaded[action]) { - return; - } - if (action === 'activity') { - this.loadActivities(source); - } - - const loadableActions = [ 'groups', 'contributed', 'projects', 'snippets' ]; - if (loadableActions.indexOf(action) > -1) { - return this.loadTab(source, action); - } - } - - loadTab(source, action) { - if (this.emptyStates[action]) this.emptyStates[action].classList.add('hidden'); - return $.ajax({ - beforeSend: () => this.toggleLoading(true), - complete: () => this.toggleLoading(false), - dataType: 'json', - type: 'GET', - url: `${source}.json`, - success: (data) => { - const tabSelector = `div#${action}`; - if (!data.html) return this.showEmptyState(tabSelector, action); - this.$parentEl.find(tabSelector).html(data.html); - this.loaded[action] = true; - return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); - } - }); - } - - showEmptyState(tabSelector, action) { - if (this.emptyStates[action]) this.emptyStates[action].classList.remove('hidden'); - this.loaded[action] = true; - } - - loadActivities(source) { - if (this.loaded['activity']) { - return; - } - const $calendarWrap = this.$parentEl.find('.user-calendar'); - $calendarWrap.load($calendarWrap.data('href')); - new gl.Activities(); - return this.loaded['activity'] = true; - } - - toggleLoading(status) { - return this.$parentEl.find('.loading-status .loading') - .toggle(status); - } - - setCurrentAction(source, action) { - let new_state = source - new_state = new_state.replace(/\/+$/, ''); - new_state += this._location.search + this._location.hash; - history.replaceState({ - turbolinks: true, - url: new_state - }, document.title, new_state); - return new_state; - } - } - global.UserTabs = UserTabs; -})(window.gl || (window.gl = {})); diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index 4c9adcabe343..1d4d453f1dc0 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -42,7 +42,7 @@ find('body').native.send_keys([:shift, 'S']) - expect(page).to have_selector('.snippets-list-holder') + expect(page).to have_content('There are no snippets to show.') end end -- GitLab From 31f1f662e32b05be3738539f32340e2660b42f75 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 21 Apr 2017 18:43:53 +0100 Subject: [PATCH 4/4] Corrected emptyState state functionality --- app/assets/javascripts/user_tabs.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js index 20e45b25cdd2..f098de573eb9 100644 --- a/app/assets/javascripts/user_tabs.js +++ b/app/assets/javascripts/user_tabs.js @@ -99,7 +99,7 @@ content on the Users#show page. }; } - showEmptyState(tabSelector, action) { + showEmptyState(action) { if (this.emptyStates[action]) this.emptyStates[action].classList.remove('hidden'); this.loaded[action] = true; } @@ -141,7 +141,7 @@ content on the Users#show page. } loadTab(action, endpoint) { - if (this.emptyStates[endpoint]) this.emptyStates[endpoint].classList.add('hidden'); + if (this.emptyStates[action]) this.emptyStates[action].classList.add('hidden'); return $.ajax({ beforeSend: () => this.toggleLoading(true), @@ -152,7 +152,7 @@ content on the Users#show page. success: (data) => { const tabSelector = `div#${action}`; - if (!data.html) return this.showEmptyState(tabSelector, action); + if (!data.html) return this.showEmptyState(action); this.$parentEl.find(tabSelector).html(data.html); this.loaded[action] = true; -- GitLab