From dc3b00dd3dbd23c9adc9bbf91cf04a89d2d2fa8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Mon, 24 Jul 2017 16:48:30 -0300 Subject: [PATCH] js: namespaces module refactoring --- app/assets/javascripts/main.js | 3 +- .../components/edit-namespace-form.js | 19 ++++ .../components/namespace-details.js | 39 +++++++ .../components/new-namespace-form.js | 33 ++++++ .../components/normal-namespaces-panel.js | 36 +++++++ .../javascripts/modules/namespaces/index.js | 20 ++++ .../modules/namespaces/pages/index.js | 19 ++++ .../modules/namespaces/pages/show.js | 19 ++++ app/assets/javascripts/modules/teams/index.js | 13 +++ .../javascripts/modules/teams/pages/show.js | 19 ++++ app/assets/javascripts/namespaces.js | 40 ------- app/views/namespaces/index.html.slim | 101 +++++++++--------- app/views/namespaces/show.html.slim | 7 +- app/views/namespaces/update.js.erb | 11 +- app/views/teams/show.html.slim | 99 ++++++++--------- spec/features/namespaces_spec.rb | 6 +- 16 files changed, 333 insertions(+), 151 deletions(-) create mode 100644 app/assets/javascripts/modules/namespaces/components/edit-namespace-form.js create mode 100644 app/assets/javascripts/modules/namespaces/components/namespace-details.js create mode 100644 app/assets/javascripts/modules/namespaces/components/new-namespace-form.js create mode 100644 app/assets/javascripts/modules/namespaces/components/normal-namespaces-panel.js create mode 100644 app/assets/javascripts/modules/namespaces/index.js create mode 100644 app/assets/javascripts/modules/namespaces/pages/index.js create mode 100644 app/assets/javascripts/modules/namespaces/pages/show.js create mode 100644 app/assets/javascripts/modules/teams/index.js create mode 100644 app/assets/javascripts/modules/teams/pages/show.js delete mode 100644 app/assets/javascripts/namespaces.js diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 1805e4863..03874fb72 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -18,7 +18,6 @@ import './vue-shared'; // Require tree. // NOTE: This should be moved into proper modules. import './bootstrap'; -import './namespaces'; import './repositories'; import './teams'; @@ -26,6 +25,8 @@ import './teams'; import './modules/users'; import './modules/dashboard'; import './modules/repositories'; +import './modules/namespaces'; +import './modules/teams'; import { setTimeOutAlertDelay, refreshFloatAlertPosition } from './utils/effects'; diff --git a/app/assets/javascripts/modules/namespaces/components/edit-namespace-form.js b/app/assets/javascripts/modules/namespaces/components/edit-namespace-form.js new file mode 100644 index 000000000..e20833c84 --- /dev/null +++ b/app/assets/javascripts/modules/namespaces/components/edit-namespace-form.js @@ -0,0 +1,19 @@ +import BaseComponent from '~/base/component'; + +import { setTypeahead } from '~/utils/typeahead'; + +const TYPEAHEAD_INPUT = '.remote .typeahead'; + +// EditNamespaceForm component refers to the namespace form +class EditNamespaceForm extends BaseComponent { + // eslint-disable-next-line class-methods-use-this + mounted() { + setTypeahead(TYPEAHEAD_INPUT, '/namespaces/typeahead/%QUERY'); + } + + toggle() { + this.$el.toggle(); + } +} + +export default EditNamespaceForm; diff --git a/app/assets/javascripts/modules/namespaces/components/namespace-details.js b/app/assets/javascripts/modules/namespaces/components/namespace-details.js new file mode 100644 index 000000000..4158b00af --- /dev/null +++ b/app/assets/javascripts/modules/namespaces/components/namespace-details.js @@ -0,0 +1,39 @@ +import BaseComponent from '~/base/component'; + +import { openCloseIcon } from '~/utils/effects'; + +import EditNamespaceForm from './edit-namespace-form'; + +const TOGGLE_LINK = '.edit-namespace-link'; +const TOGGLE_LINK_ICON = `${TOGGLE_LINK} i`; +const EDIT_NAMESPACE_FORM = '.edit-namespace-form'; +const DESCRIPTION = '.description'; + +// NamespaceDetails component handles details panel +// and edit namespace form +class NamespaceDetails extends BaseComponent { + elements() { + this.$toggle = this.$el.find(TOGGLE_LINK); + this.$toggleIcon = this.$el.find(TOGGLE_LINK_ICON); + this.$form = this.$el.find(EDIT_NAMESPACE_FORM); + this.$description = this.$el.find(DESCRIPTION); + } + + events() { + this.$toggle.on('click', () => this.onEditClick()); + } + + mount() { + this.form = new EditNamespaceForm(this.$form); + } + + onEditClick() { + openCloseIcon(this.$toggleIcon); + this.$description.toggle(); + this.form.toggle(); + + layout_resizer(); + } +} + +export default NamespaceDetails; diff --git a/app/assets/javascripts/modules/namespaces/components/new-namespace-form.js b/app/assets/javascripts/modules/namespaces/components/new-namespace-form.js new file mode 100644 index 000000000..fba929636 --- /dev/null +++ b/app/assets/javascripts/modules/namespaces/components/new-namespace-form.js @@ -0,0 +1,33 @@ +import BaseComponent from '~/base/component'; + +import { setTypeahead } from '~/utils/typeahead'; + +const TYPEAHEAD_INPUT = '.remote .typeahead'; +const NAMESPACE_FORM_FIELDS = '.form-control, textarea'; + +// NewNamespaceForm component refers to the new namespace form +class NewNamespaceForm extends BaseComponent { + elements() { + this.$fields = this.$el.find(NAMESPACE_FORM_FIELDS); + } + + // eslint-disable-next-line class-methods-use-this + mounted() { + setTypeahead(TYPEAHEAD_INPUT, '/namespaces/typeahead/%QUERY'); + } + + toggle() { + this.$el.toggle(400, 'swing', () => { + const visible = this.$el.is(':visible'); + + if (visible) { + this.$fields.first().focus(); + } + + this.$fields.val(''); + layout_resizer(); + }); + } +} + +export default NewNamespaceForm; diff --git a/app/assets/javascripts/modules/namespaces/components/normal-namespaces-panel.js b/app/assets/javascripts/modules/namespaces/components/normal-namespaces-panel.js new file mode 100644 index 000000000..a82b15153 --- /dev/null +++ b/app/assets/javascripts/modules/namespaces/components/normal-namespaces-panel.js @@ -0,0 +1,36 @@ +import BaseComponent from '~/base/component'; + +import NewNamespaceForm from './new-namespace-form'; + +const TOGGLE_LINK = '#add_namespace_btn'; +const TOGGLE_LINK_ICON = `${TOGGLE_LINK} i`; +const NEW_NAMESPACE_FORM = '#add_namespace_form'; + +// NormalNamespacesPanel component that lists normal namespaces +// and contains new namespace form. +class NormalNamespacesPanel extends BaseComponent { + elements() { + this.$toggle = this.$el.find(TOGGLE_LINK); + this.$toggleIcon = this.$el.find(TOGGLE_LINK_ICON); + this.$form = this.$el.find(NEW_NAMESPACE_FORM); + } + + events() { + this.$el.on('click', TOGGLE_LINK, e => this.onToggleLinkClick(e)); + } + + mount() { + this.newForm = new NewNamespaceForm(this.$form); + } + + // eslint-disable-next-line class-methods-use-this + onToggleLinkClick() { + const wasVisible = this.$form.is(':visible'); + + this.newForm.toggle(); + this.$toggleIcon.toggleClass('fa-minus-circle', !wasVisible); + this.$toggleIcon.toggleClass('fa-plus-circle', wasVisible); + } +} + +export default NormalNamespacesPanel; diff --git a/app/assets/javascripts/modules/namespaces/index.js b/app/assets/javascripts/modules/namespaces/index.js new file mode 100644 index 000000000..48c0b72c3 --- /dev/null +++ b/app/assets/javascripts/modules/namespaces/index.js @@ -0,0 +1,20 @@ +import NamespacesIndexPage from './pages/index'; +import NamespacesShowPage from './pages/show'; + +const NAMESPACES_INDEX_ROUTE = 'namespaces/index'; +const NAMESPACES_SHOW_ROUTE = 'namespaces/show'; + +$(() => { + const $body = $('body'); + const route = $body.data('route'); + + if (route === NAMESPACES_INDEX_ROUTE) { + // eslint-disable-next-line + new NamespacesIndexPage($body); + } + + if (route === NAMESPACES_SHOW_ROUTE) { + // eslint-disable-next-line + new NamespacesShowPage($body); + } +}); diff --git a/app/assets/javascripts/modules/namespaces/pages/index.js b/app/assets/javascripts/modules/namespaces/pages/index.js new file mode 100644 index 000000000..940078b63 --- /dev/null +++ b/app/assets/javascripts/modules/namespaces/pages/index.js @@ -0,0 +1,19 @@ +import BaseComponent from '~/base/component'; + +import NormalNamespacesPanel from '../components/normal-namespaces-panel'; + +const NORMAL_NAMESPACES_PANEL = '.normal-namespaces-wrapper'; + +// NamespacesIndexPage component responsible to instantiate +// the namespaces's index page components and handle interactions. +class NamespacesIndexPage extends BaseComponent { + elements() { + this.$normalNamespacesPanel = this.$el.find(NORMAL_NAMESPACES_PANEL); + } + + mount() { + this.normalNamespacesPanel = new NormalNamespacesPanel(this.$normalNamespacesPanel); + } +} + +export default NamespacesIndexPage; diff --git a/app/assets/javascripts/modules/namespaces/pages/show.js b/app/assets/javascripts/modules/namespaces/pages/show.js new file mode 100644 index 000000000..821459a77 --- /dev/null +++ b/app/assets/javascripts/modules/namespaces/pages/show.js @@ -0,0 +1,19 @@ +import BaseComponent from '~/base/component'; + +import NamespaceDetails from '../components/namespace-details'; + +const NAMESPACE_DETAILS = '.namespace-details'; + +// NamespaceShowPage component responsible to instantiate +// the user's edit page components and handle interactions. +class NamespaceShowPage extends BaseComponent { + elements() { + this.$details = this.$el.find(NAMESPACE_DETAILS); + } + + mount() { + this.details = new NamespaceDetails(this.$details); + } +} + +export default NamespaceShowPage; diff --git a/app/assets/javascripts/modules/teams/index.js b/app/assets/javascripts/modules/teams/index.js new file mode 100644 index 000000000..9a9155fe4 --- /dev/null +++ b/app/assets/javascripts/modules/teams/index.js @@ -0,0 +1,13 @@ +import TeamsShowPage from './pages/show'; + +const TEAMS_SHOW_ROUTE = 'teams/show'; + +$(() => { + const $body = $('body'); + const route = $body.data('route'); + + if (route === TEAMS_SHOW_ROUTE) { + // eslint-disable-next-line + new TeamsShowPage($body); + } +}); diff --git a/app/assets/javascripts/modules/teams/pages/show.js b/app/assets/javascripts/modules/teams/pages/show.js new file mode 100644 index 000000000..2f6a60cda --- /dev/null +++ b/app/assets/javascripts/modules/teams/pages/show.js @@ -0,0 +1,19 @@ +import BaseComponent from '~/base/component'; + +import NormalNamespacesPanel from '../../namespaces/components/normal-namespaces-panel'; + +const NORMAL_NAMESPACES_PANEL = '.normal-namespaces-wrapper'; + +// TeamsShowPage component responsible to instantiate +// the team's show page components and handle interactions. +class TeamsShowPage extends BaseComponent { + elements() { + this.$normalNamespacesPanel = this.$el.find(NORMAL_NAMESPACES_PANEL); + } + + mount() { + this.normalNamespacesPanel = new NormalNamespacesPanel(this.$normalNamespacesPanel); + } +} + +export default TeamsShowPage; diff --git a/app/assets/javascripts/namespaces.js b/app/assets/javascripts/namespaces.js deleted file mode 100644 index 41235ab30..000000000 --- a/app/assets/javascripts/namespaces.js +++ /dev/null @@ -1,40 +0,0 @@ -import { setTypeahead } from '~/utils/typeahead'; - -import { openCloseIcon } from '~/utils/effects'; - -jQuery(function ($) { - $('#edit_namespace').on('click', function (_event) { - setTypeahead('.remote .typeahead', '/teams/typeahead/%QUERY'); - }); - - $('#add_namespace_btn').unbind('click').on('click', function (_event) { - // clear only when creating a new namespace - if ($('#new-namespace-form').length) { - $('#namespace_team.typeahead').val(''); - $('#namespace_description').val(''); - $('#namespace_namespace').val(''); - } - - $('#add_namespace_form').toggle(400, 'swing', function () { - if ($('#add_namespace_form').is(':visible')) { - $('#add_namespace_btn i').addClass('fa-minus-circle'); - $('#add_namespace_btn i').removeClass('fa-plus-circle'); - $('#namespace_namespace').focus(); - } else { - $('#add_namespace_btn i').removeClass('fa-minus-circle'); - $('#add_namespace_btn i').addClass('fa-plus-circle'); - } - layout_resizer(); - }); - setTypeahead('.remote .typeahead', '/namespaces/typeahead/%QUERY'); - }); - - $('body').on('click', '.btn-edit-role', function (event) { - var el = $(this).find('i.fa'); - if ($(this).hasClass('button_namespace_description')) { - openCloseIcon(el); - $('.description').toggle(); - $('#change_description_namespace_' + event.currentTarget.value).toggle(); - } - }); -}); diff --git a/app/views/namespaces/index.html.slim b/app/views/namespaces/index.html.slim index e4ef8681a..9427c65d7 100644 --- a/app/views/namespaces/index.html.slim +++ b/app/views/namespaces/index.html.slim @@ -29,55 +29,56 @@ - @special_namespaces.each do |namespace| = render partial: 'namespaces/namespace', locals: {namespace: namespace} -- if Registry.any? - #add_namespace_form.collapse - = form_for :namespace, url: namespaces_path, remote: true, html: {id: 'new-namespace-form', class: 'form-horizontal', role: 'form'} do |f| - .form-group - = f.label :namespace, {class: 'control-label col-md-2'} - .col-md-7 - = f.text_field(:namespace, class: 'form-control', required: true, placeholder: "Name") - .form-group.has-feedback - = f.label :team, {class: 'control-label col-md-2'} - .col-md-7 - .remote - = f.text_field(:team, class: 'form-control typeahead', required: true, placeholder: "Name of the team") - span.fa.fa-search.form-control-feedback +.normal-namespaces-wrapper + - if Registry.any? + #add_namespace_form.collapse + = form_for :namespace, url: namespaces_path, remote: true, html: {id: 'new-namespace-form', class: 'form-horizontal', role: 'form'} do |f| + .form-group + = f.label :namespace, {class: 'control-label col-md-2'} + .col-md-7 + = f.text_field(:namespace, class: 'form-control', required: true, placeholder: "Name") + .form-group.has-feedback + = f.label :team, {class: 'control-label col-md-2'} + .col-md-7 + .remote + = f.text_field(:team, class: 'form-control typeahead', required: true, placeholder: "Name of the team") + span.fa.fa-search.form-control-feedback - .form-group - = f.label :description, {class: 'control-label col-md-2'} - .col-md-7 - = f.text_area(:description, class: 'form-control fixed-size', required: false, placeholder: "A short description of your namespace") - .form-group - .col-md-offset-2.col-md-7 - = f.submit('Create', class: 'btn btn-primary') + .form-group + = f.label :description, {class: 'control-label col-md-2'} + .col-md-7 + = f.text_area(:description, class: 'form-control fixed-size', required: false, placeholder: "A short description of your namespace") + .form-group + .col-md-offset-2.col-md-7 + = f.submit('Create', class: 'btn btn-primary') -.panel.panel-default - .panel-heading - .row - .col-sm-6 - h5 - ' Namespaces you have access to - .col-sm-6.text-right - - if Registry.any? && can_create_namespace? - a#add_namespace_btn.btn.btn-xs.btn-link.js-toggle-button[role="button"] - i.fa.fa-plus-circle - | Create new namespace - .panel-body - .table-responsive - table.table.table-stripped.table-hover - col.col-40 - col.col-15 - col.col-15 - col.col-20 - col.col-10 - thead - tr - th Name - th Repositories - th Webhooks - th Created At - th Visibility - tbody#accessible-namespaces - - @namespaces.each do |namespace| - = render partial: 'namespaces/namespace', locals: {namespace: namespace} - .panel-footer= paginate(@namespaces) + .panel.panel-default + .panel-heading + .row + .col-sm-6 + h5 + ' Namespaces you have access to + .col-sm-6.text-right + - if Registry.any? && can_create_namespace? + a#add_namespace_btn.btn.btn-xs.btn-link.js-toggle-button[role="button"] + i.fa.fa-plus-circle + | Create new namespace + .panel-body + .table-responsive + table.table.table-stripped.table-hover + col.col-40 + col.col-15 + col.col-15 + col.col-20 + col.col-10 + thead + tr + th Name + th Repositories + th Webhooks + th Created At + th Visibility + tbody#accessible-namespaces + - @namespaces.each do |namespace| + = render partial: 'namespaces/namespace', locals: {namespace: namespace} + .panel-footer= paginate(@namespaces) diff --git a/app/views/namespaces/show.html.slim b/app/views/namespaces/show.html.slim index a147a05d1..b8db68b55 100644 --- a/app/views/namespaces/show.html.slim +++ b/app/views/namespaces/show.html.slim @@ -1,4 +1,4 @@ -.panel.panel-default +.namespace-details.panel.panel-default .panel-heading .row .col-sm-6 @@ -16,8 +16,7 @@ ' namespace .col-sm-6.text-right - if can_manage_namespace?(@namespace) - button.btn.btn-link.btn-xs.btn-edit-role[ - value="#{@namespace.id}" id="edit_namespace" class="button_namespace_description"] + button.btn.btn-link.btn-xs.edit-namespace-link i.fa.fa-pencil | Edit namespace @@ -28,7 +27,7 @@ 'No description has been posted yet. - else = markdown(@namespace.description) - .collapse id="change_description_namespace_#{@namespace.id}" + .edit-namespace-form.collapse = form_for @namespace, remote: true, html: {role: 'form'} do |f| .form-group = f.label :team, "Team", {class: 'control-label col-md-2'} diff --git a/app/views/namespaces/update.js.erb b/app/views/namespaces/update.js.erb index 9e31a74e5..1290c25c5 100644 --- a/app/views/namespaces/update.js.erb +++ b/app/views/namespaces/update.js.erb @@ -1,10 +1,13 @@ <% if @namespace.errors.empty? %> - var el = $(".button_namespace_description i.fa"); - $("#change_description_namespace_<%= @namespace.id %>").fadeToggle(function() { + var $edit = $(".edit-namespace-link i.fa"); + + $edit.removeClass('fa-close'); + $edit.addClass('fa-pencil'); + + $(".edit-namespace-form").fadeToggle(function() { $(".description").fadeToggle(); }); - el.removeClass('fa-close'); - el.addClass('fa-pencil'); + <% if @namespace.description.blank? %> $('.description').html("No description has been posted yet"); <% else %> diff --git a/app/views/teams/show.html.slim b/app/views/teams/show.html.slim index 3a6cbdea6..dd2ab00a5 100644 --- a/app/views/teams/show.html.slim +++ b/app/views/teams/show.html.slim @@ -128,54 +128,55 @@ = render(tu) .panel-footer= paginate(@team_users, param_name: 'users_page') -- if can_manage_team?(@team) - #add_namespace_form.collapse - = form_for :namespace, url: namespaces_path, remote: true, html: {id: 'new-namespace-form', class: 'form-horizontal', role: 'form'} do |f| - = f.hidden_field(:team, value: @team.name) - .form-group - = f.label :namespace, {class: 'control-label col-md-2'} - .col-md-7 - = f.text_field(:namespace, class: 'form-control', placeholder: 'Name', required: true) - .form-group - = f.label :description, {class: 'control-label col-md-2'} - .col-md-7 - = f.text_area(:description, class: 'form-control', required: false, placeholder: "A short description of your namespace") - .form-group - .col-md-offset-2.col-md-7 - = f.submit('Add', class: 'btn btn-primary') +.normal-namespaces-wrapper + - if can_manage_team?(@team) + #add_namespace_form.collapse + = form_for :namespace, url: namespaces_path, remote: true, html: {id: 'new-namespace-form', class: 'form-horizontal', role: 'form'} do |f| + = f.hidden_field(:team, value: @team.name) + .form-group + = f.label :namespace, {class: 'control-label col-md-2'} + .col-md-7 + = f.text_field(:namespace, class: 'form-control', placeholder: 'Name', required: true) + .form-group + = f.label :description, {class: 'control-label col-md-2'} + .col-md-7 + = f.text_area(:description, class: 'form-control', required: false, placeholder: "A short description of your namespace") + .form-group + .col-md-offset-2.col-md-7 + = f.submit('Add', class: 'btn btn-primary') -.panel.panel-default - .panel-heading - .row - .col-sm-6 - h5 - small - a[data-placement="right" data-toggle="popover" data-container=".panel-heading" data-content='

A team can own one or more namespaces. By default all the namespaces can be accessed only by the members of the team.

It is possible to add read only (pull) access to logged-in users or all Portus users by changing the visibility to "protected" or "public" respectively.

' data-original-title="What's this?" tabindex="0" data-html="true"] - i.fa.fa-info-circle - ' Namespaces owned - .col-sm-6.text-right - - if can_manage_team?(@team) && Registry.any? - a#add_namespace_btn.btn.btn-xs.btn-link.js-toggle-button role="button" - i.fa.fa-plus-circle - | Add namespace + .panel.panel-default + .panel-heading + .row + .col-sm-6 + h5 + small + a[data-placement="right" data-toggle="popover" data-container=".panel-heading" data-content='

A team can own one or more namespaces. By default all the namespaces can be accessed only by the members of the team.

It is possible to add read only (pull) access to logged-in users or all Portus users by changing the visibility to "protected" or "public" respectively.

' data-original-title="What's this?" tabindex="0" data-html="true"] + i.fa.fa-info-circle + ' Namespaces owned + .col-sm-6.text-right + - if can_manage_team?(@team) && Registry.any? + a#add_namespace_btn.btn.btn-xs.btn-link.js-toggle-button role="button" + i.fa.fa-plus-circle + | Add namespace - .panel-body - .table-responsive - table.table.table-striped.table-hover - colgroup - col.col-50 - col.col-15 - col.col-15 - col.col-20 - col.col-10 - thead - tr - th Namespace - th Repositories - th Webhooks - th Created At - th Visibility - tbody#namespaces - - @team_namespaces.each do |namespace| - = render(namespace) - .panel-footer= paginate(@team_namespaces, param_name: 'namespaces_page') + .panel-body + .table-responsive + table.table.table-striped.table-hover + colgroup + col.col-50 + col.col-15 + col.col-15 + col.col-20 + col.col-10 + thead + tr + th Namespace + th Repositories + th Webhooks + th Created At + th Visibility + tbody#namespaces + - @team_namespaces.each do |namespace| + = render(namespace) + .panel-footer= paginate(@team_namespaces, param_name: 'namespaces_page') diff --git a/spec/features/namespaces_spec.rb b/spec/features/namespaces_spec.rb index d9659d600..90b9043cd 100644 --- a/spec/features/namespaces_spec.rb +++ b/spec/features/namespaces_spec.rb @@ -154,13 +154,13 @@ describe "#update" do it "returns an error when trying to update the team to a non-existing one", js: true do visit namespace_path(namespace.id) - find("#edit_namespace").click + find(".edit-namespace-link").click wait_for_ajax fill_in "Team", with: "unknown" - find("#change_description_namespace_#{namespace.id} .btn").click - + find(".edit-namespace-form .btn").click wait_for_ajax + expect(page).to have_content("Team 'unknown' unknown") end end