Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Tang <dennis@dennistang.net>2018-09-07 09:09:13 +0300
committerMike Greiling <mike@pixelcog.com>2018-09-07 09:09:13 +0300
commit5b74a1aebcc1712316b8269c415e83e9d59750d5 (patch)
treea398af6332ae6fd8165981c63aecd7602a338560 /app/assets
parent53fae9ad84361bbf3f9fb3db446c9bb22772fb64 (diff)
Resolve "Improve handling of projects shared with a group"
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/groups/components/app.vue78
-rw-r--r--app/assets/javascripts/groups/components/group_folder.vue7
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue14
-rw-r--r--app/assets/javascripts/groups/components/groups.vue68
-rw-r--r--app/assets/javascripts/groups/components/item_actions.vue7
-rw-r--r--app/assets/javascripts/groups/constants.js24
-rw-r--r--app/assets/javascripts/groups/groups_filterable_list.js64
-rw-r--r--app/assets/javascripts/groups/index.js31
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/groups/index/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/show/group_tabs.js136
-rw-r--r--app/assets/javascripts/pages/groups/show/index.js12
-rw-r--r--app/assets/stylesheets/pages/groups.scss54
13 files changed, 372 insertions, 131 deletions
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index b0765747a36..69f192ac75e 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -2,14 +2,15 @@
/* global Flash */
import $ from 'jquery';
-import { s__ } from '~/locale';
+import { s__, sprintf } from '~/locale';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import { HIDDEN_CLASS } from '~/lib/utils/constants';
import { getParameterByName } from '~/lib/utils/common_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
-import { COMMON_STR } from '../constants';
+import { COMMON_STR, CONTENT_LIST_CLASS } from '../constants';
import groupsComponent from './groups.vue';
export default {
@@ -19,6 +20,16 @@ export default {
groupsComponent,
},
props: {
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ containerId: {
+ type: String,
+ required: false,
+ default: '',
+ },
store: {
type: Object,
required: true,
@@ -56,31 +67,28 @@ export default {
? COMMON_STR.GROUP_SEARCH_EMPTY
: COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
- eventHub.$on('fetchPage', this.fetchPage);
- eventHub.$on('toggleChildren', this.toggleChildren);
- eventHub.$on('showLeaveGroupModal', this.showLeaveGroupModal);
- eventHub.$on('updatePagination', this.updatePagination);
- eventHub.$on('updateGroups', this.updateGroups);
+ eventHub.$on(`${this.action}fetchPage`, this.fetchPage);
+ eventHub.$on(`${this.action}toggleChildren`, this.toggleChildren);
+ eventHub.$on(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
+ eventHub.$on(`${this.action}updatePagination`, this.updatePagination);
+ eventHub.$on(`${this.action}updateGroups`, this.updateGroups);
},
mounted() {
this.fetchAllGroups();
+
+ if (this.containerId) {
+ this.containerEl = document.getElementById(this.containerId);
+ }
},
beforeDestroy() {
- eventHub.$off('fetchPage', this.fetchPage);
- eventHub.$off('toggleChildren', this.toggleChildren);
- eventHub.$off('showLeaveGroupModal', this.showLeaveGroupModal);
- eventHub.$off('updatePagination', this.updatePagination);
- eventHub.$off('updateGroups', this.updateGroups);
+ eventHub.$off(`${this.action}fetchPage`, this.fetchPage);
+ eventHub.$off(`${this.action}toggleChildren`, this.toggleChildren);
+ eventHub.$off(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
+ eventHub.$off(`${this.action}updatePagination`, this.updatePagination);
+ eventHub.$off(`${this.action}updateGroups`, this.updateGroups);
},
methods: {
- fetchGroups({
- parentId,
- page,
- filterGroupsBy,
- sortBy,
- archived,
- updatePagination,
- }) {
+ fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
return this.service
.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
.then(res => {
@@ -165,13 +173,13 @@ export default {
}
},
showLeaveGroupModal(group, parentGroup) {
+ const { fullName } = group;
this.targetGroup = group;
this.targetParentGroup = parentGroup;
this.showModal = true;
- this.groupLeaveConfirmationMessage = s__(
- `GroupsTree|Are you sure you want to leave the "${
- group.fullName
- }" group?`,
+ this.groupLeaveConfirmationMessage = sprintf(
+ s__('GroupsTree|Are you sure you want to leave the "%{fullName}" group?'),
+ { fullName },
);
},
hideLeaveGroupModal() {
@@ -197,16 +205,35 @@ export default {
this.targetGroup.isBeingRemoved = false;
});
},
+ showEmptyState() {
+ const { containerEl } = this;
+ const contentListEl = containerEl.querySelector(CONTENT_LIST_CLASS);
+ const emptyStateEl = containerEl.querySelector('.empty-state');
+
+ if (contentListEl) {
+ contentListEl.remove();
+ }
+
+ if (emptyStateEl) {
+ emptyStateEl.classList.remove(HIDDEN_CLASS);
+ }
+ },
updatePagination(headers) {
this.store.setPaginationInfo(headers);
},
updateGroups(groups, fromSearch) {
- this.isSearchEmpty = groups ? groups.length === 0 : false;
+ const hasGroups = groups && groups.length > 0;
+ this.isSearchEmpty = !hasGroups;
+
if (fromSearch) {
this.store.setSearchedGroups(groups);
} else {
this.store.setGroups(groups);
}
+
+ if (this.action && !hasGroups && !fromSearch) {
+ this.showEmptyState();
+ }
},
},
};
@@ -226,6 +253,7 @@ export default {
:search-empty="isSearchEmpty"
:search-empty-message="searchEmptyMessage"
:page-info="pageInfo"
+ :action="action"
/>
<deprecated-modal
v-show="showModal"
diff --git a/app/assets/javascripts/groups/components/group_folder.vue b/app/assets/javascripts/groups/components/group_folder.vue
index 647c9d0046d..bcc7a638346 100644
--- a/app/assets/javascripts/groups/components/group_folder.vue
+++ b/app/assets/javascripts/groups/components/group_folder.vue
@@ -11,8 +11,12 @@ export default {
},
groups: {
type: Array,
+ required: true,
+ },
+ action: {
+ type: String,
required: false,
- default: () => ([]),
+ default: '',
},
},
computed: {
@@ -37,6 +41,7 @@ export default {
:key="index"
:group="group"
:parent-group="parentGroup"
+ :action="action"
/>
<li
v-if="hasMoreChildren"
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 2b9e2a929fc..154ad2ea607 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -30,6 +30,11 @@ export default {
type: Object,
required: true,
},
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
groupDomId() {
@@ -56,10 +61,12 @@ export default {
methods: {
onClickRowGroup(e) {
const NO_EXPAND_CLS = 'no-expand';
- if (!(e.target.classList.contains(NO_EXPAND_CLS) ||
- e.target.parentElement.classList.contains(NO_EXPAND_CLS))) {
+ const targetClasses = e.target.classList;
+ const parentElClasses = e.target.parentElement.classList;
+
+ if (!(targetClasses.contains(NO_EXPAND_CLS) || parentElClasses.contains(NO_EXPAND_CLS))) {
if (this.hasChildren) {
- eventHub.$emit('toggleChildren', this.group);
+ eventHub.$emit(`${this.action}toggleChildren`, this.group);
} else {
visitUrl(this.group.relativePath);
}
@@ -158,6 +165,7 @@ export default {
v-if="group.isOpen && hasChildren"
:parent-group="group"
:groups="group.children"
+ :action="action"
/>
</li>
</template>
diff --git a/app/assets/javascripts/groups/components/groups.vue b/app/assets/javascripts/groups/components/groups.vue
index 73ae928b0d9..a1beb222950 100644
--- a/app/assets/javascripts/groups/components/groups.vue
+++ b/app/assets/javascripts/groups/components/groups.vue
@@ -1,39 +1,44 @@
<script>
- import tablePagination from '~/vue_shared/components/table_pagination.vue';
- import eventHub from '../event_hub';
- import { getParameterByName } from '../../lib/utils/common_utils';
+import tablePagination from '~/vue_shared/components/table_pagination.vue';
+import eventHub from '../event_hub';
+import { getParameterByName } from '../../lib/utils/common_utils';
- export default {
- components: {
- tablePagination,
+export default {
+ components: {
+ tablePagination,
+ },
+ props: {
+ groups: {
+ type: Array,
+ required: true,
},
- props: {
- groups: {
- type: Array,
- required: true,
- },
- pageInfo: {
- type: Object,
- required: true,
- },
- searchEmpty: {
- type: Boolean,
- required: true,
- },
- searchEmptyMessage: {
- type: String,
- required: true,
- },
+ pageInfo: {
+ type: Object,
+ required: true,
},
- methods: {
- change(page) {
- const filterGroupsParam = getParameterByName('filter_groups');
- const sortParam = getParameterByName('sort');
- const archivedParam = getParameterByName('archived');
- eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
- },
+ searchEmpty: {
+ type: Boolean,
+ required: true,
},
- };
+ searchEmptyMessage: {
+ type: String,
+ required: true,
+ },
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ methods: {
+ change(page) {
+ const filterGroupsParam = getParameterByName('filter_groups');
+ const sortParam = getParameterByName('sort');
+ const archivedParam = getParameterByName('archived');
+ eventHub.$emit(`${this.action}fetchPage`, page, filterGroupsParam, sortParam, archivedParam);
+ },
+ },
+};
</script>
<template>
@@ -47,6 +52,7 @@
<group-folder
v-if="!searchEmpty"
:groups="groups"
+ :action="action"
/>
<table-pagination
v-if="!searchEmpty"
diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue
index 24eec4901ec..6e700b8bf8a 100644
--- a/app/assets/javascripts/groups/components/item_actions.vue
+++ b/app/assets/javascripts/groups/components/item_actions.vue
@@ -21,6 +21,11 @@ export default {
type: Object,
required: true,
},
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
leaveBtnTitle() {
@@ -32,7 +37,7 @@ export default {
},
methods: {
onLeaveGroup() {
- eventHub.$emit('showLeaveGroupModal', this.group, this.parentGroup);
+ eventHub.$emit(`${this.action}showLeaveGroupModal`, this.group, this.parentGroup);
},
},
};
diff --git a/app/assets/javascripts/groups/constants.js b/app/assets/javascripts/groups/constants.js
index b8baed682f5..9c246cf3ba6 100644
--- a/app/assets/javascripts/groups/constants.js
+++ b/app/assets/javascripts/groups/constants.js
@@ -2,13 +2,23 @@ import { __, s__ } from '../locale';
export const MAX_CHILDREN_COUNT = 20;
+export const ACTIVE_TAB_SUBGROUPS_AND_PROJECTS = 'subgroups_and_projects';
+export const ACTIVE_TAB_SHARED = 'shared';
+export const ACTIVE_TAB_ARCHIVED = 'archived';
+
+export const GROUPS_LIST_HOLDER_CLASS = '.js-groups-list-holder';
+export const GROUPS_FILTER_FORM_CLASS = '.js-group-filter-form';
+export const CONTENT_LIST_CLASS = '.content-list';
+
export const COMMON_STR = {
FAILURE: __('An error occurred. Please try again.'),
- LEAVE_FORBIDDEN: s__('GroupsTree|Failed to leave the group. Please make sure you are not the only owner.'),
+ LEAVE_FORBIDDEN: s__(
+ 'GroupsTree|Failed to leave the group. Please make sure you are not the only owner.',
+ ),
LEAVE_BTN_TITLE: s__('GroupsTree|Leave this group'),
EDIT_BTN_TITLE: s__('GroupsTree|Edit group'),
- GROUP_SEARCH_EMPTY: s__('GroupsTree|Sorry, no groups matched your search'),
- GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|Sorry, no groups or projects matched your search'),
+ GROUP_SEARCH_EMPTY: s__('GroupsTree|No groups matched your search'),
+ GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|No groups or projects matched your search'),
};
export const ITEM_TYPE = {
@@ -17,8 +27,12 @@ export const ITEM_TYPE = {
};
export const GROUP_VISIBILITY_TYPE = {
- public: __('Public - The group and any public projects can be viewed without any authentication.'),
- internal: __('Internal - The group and any internal projects can be viewed by any logged in user.'),
+ public: __(
+ 'Public - The group and any public projects can be viewed without any authentication.',
+ ),
+ internal: __(
+ 'Internal - The group and any internal projects can be viewed by any logged in user.',
+ ),
private: __('Private - The group and its projects can only be viewed by members.'),
};
diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js
index e6db1746487..693519729ac 100644
--- a/app/assets/javascripts/groups/groups_filterable_list.js
+++ b/app/assets/javascripts/groups/groups_filterable_list.js
@@ -4,13 +4,23 @@ import eventHub from './event_hub';
import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
export default class GroupFilterableList extends FilterableList {
- constructor({ form, filter, holder, filterEndpoint, pagePath, dropdownSel, filterInputField }) {
+ constructor({
+ form,
+ filter,
+ holder,
+ filterEndpoint,
+ pagePath,
+ dropdownSel,
+ filterInputField,
+ action,
+ }) {
super(form, filter, holder, filterInputField);
this.form = form;
this.filterEndpoint = filterEndpoint;
this.pagePath = pagePath;
this.filterInputField = filterInputField;
this.$dropdown = $(dropdownSel);
+ this.action = action;
}
getFilterEndpoint() {
@@ -20,15 +30,16 @@ export default class GroupFilterableList extends FilterableList {
getPagePath(queryData) {
const params = queryData ? $.param(queryData) : '';
const queryString = params ? `?${params}` : '';
- return `${this.pagePath}${queryString}`;
+ const path = this.pagePath || window.location.pathname;
+ return `${path}${queryString}`;
}
bindEvents() {
super.bindEvents();
- this.onFilterOptionClikWrapper = this.onOptionClick.bind(this);
+ this.onFilterOptionClickWrapper = this.onOptionClick.bind(this);
- this.$dropdown.on('click', 'a', this.onFilterOptionClikWrapper);
+ this.$dropdown.on('click', 'a', this.onFilterOptionClickWrapper);
}
onFilterInput() {
@@ -53,7 +64,12 @@ export default class GroupFilterableList extends FilterableList {
}
setDefaultFilterOption() {
- const defaultOption = $.trim(this.$dropdown.find('.dropdown-menu li.js-filter-sort-order a').first().text());
+ const defaultOption = $.trim(
+ this.$dropdown
+ .find('.dropdown-menu li.js-filter-sort-order a')
+ .first()
+ .text(),
+ );
this.$dropdown.find('.dropdown-label').text(defaultOption);
}
@@ -65,11 +81,19 @@ export default class GroupFilterableList extends FilterableList {
// Get type of option selected from dropdown
const currentTargetClassList = e.currentTarget.parentElement.classList;
const isOptionFilterBySort = currentTargetClassList.contains('js-filter-sort-order');
- const isOptionFilterByArchivedProjects = currentTargetClassList.contains('js-filter-archived-projects');
+ const isOptionFilterByArchivedProjects = currentTargetClassList.contains(
+ 'js-filter-archived-projects',
+ );
// Get option query param, also preserve currently applied query param
- const sortParam = getParameterByName('sort', isOptionFilterBySort ? e.currentTarget.href : window.location.href);
- const archivedParam = getParameterByName('archived', isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href);
+ const sortParam = getParameterByName(
+ 'sort',
+ isOptionFilterBySort ? e.currentTarget.href : window.location.href,
+ );
+ const archivedParam = getParameterByName(
+ 'archived',
+ isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href,
+ );
if (sortParam) {
queryData.sort = sortParam;
@@ -86,7 +110,9 @@ export default class GroupFilterableList extends FilterableList {
this.$dropdown.find('.dropdown-label').text($.trim(e.currentTarget.text));
this.$dropdown.find('.dropdown-menu li.js-filter-sort-order a').removeClass('is-active');
} else if (isOptionFilterByArchivedProjects) {
- this.$dropdown.find('.dropdown-menu li.js-filter-archived-projects a').removeClass('is-active');
+ this.$dropdown
+ .find('.dropdown-menu li.js-filter-archived-projects a')
+ .removeClass('is-active');
}
$(e.target).addClass('is-active');
@@ -98,11 +124,19 @@ export default class GroupFilterableList extends FilterableList {
onFilterSuccess(res, queryData) {
const currentPath = this.getPagePath(queryData);
- window.history.replaceState({
- page: currentPath,
- }, document.title, currentPath);
-
- eventHub.$emit('updateGroups', res.data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
- eventHub.$emit('updatePagination', normalizeHeaders(res.headers));
+ window.history.replaceState(
+ {
+ page: currentPath,
+ },
+ document.title,
+ currentPath,
+ );
+
+ eventHub.$emit(
+ `${this.action}updateGroups`,
+ res.data,
+ Object.prototype.hasOwnProperty.call(queryData, this.filterInputField),
+ );
+ eventHub.$emit(`${this.action}updatePagination`, normalizeHeaders(res.headers));
}
}
diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js
index 83a9008a94b..0f68f05b523 100644
--- a/app/assets/javascripts/groups/index.js
+++ b/app/assets/javascripts/groups/index.js
@@ -7,18 +7,26 @@ import GroupsService from './service/groups_service';
import groupsApp from './components/app.vue';
import groupFolderComponent from './components/group_folder.vue';
import groupItemComponent from './components/group_item.vue';
+import { GROUPS_LIST_HOLDER_CLASS, CONTENT_LIST_CLASS } from './constants';
Vue.use(Translate);
-export default () => {
- const el = document.getElementById('js-groups-tree');
+export default (containerId = 'js-groups-tree', endpoint, action = '') => {
+ const containerEl = document.getElementById(containerId);
+ let dataEl;
// Don't do anything if element doesn't exist (No groups)
// This is for when the user enters directly to the page via URL
- if (!el) {
+ if (!containerEl) {
return;
}
+ const el = action ? containerEl.querySelector(GROUPS_LIST_HOLDER_CLASS) : containerEl;
+
+ if (action) {
+ dataEl = containerEl.querySelector(CONTENT_LIST_CLASS);
+ }
+
Vue.component('group-folder', groupFolderComponent);
Vue.component('group-item', groupItemComponent);
@@ -29,20 +37,26 @@ export default () => {
groupsApp,
},
data() {
- const { dataset } = this.$options.el;
+ const { dataset } = dataEl || this.$options.el;
const hideProjects = dataset.hideProjects === 'true';
+ const service = new GroupsService(endpoint || dataset.endpoint);
const store = new GroupsStore(hideProjects);
- const service = new GroupsService(dataset.endpoint);
return {
+ action,
store,
service,
hideProjects,
loading: true,
+ containerId,
};
},
beforeMount() {
- const { dataset } = this.$options.el;
+ if (this.action) {
+ return;
+ }
+
+ const { dataset } = dataEl || this.$options.el;
let groupFilterList = null;
const form = document.querySelector(dataset.formSel);
const filter = document.querySelector(dataset.filterSel);
@@ -52,10 +66,11 @@ export default () => {
form,
filter,
holder,
- filterEndpoint: dataset.endpoint,
+ filterEndpoint: endpoint || dataset.endpoint,
pagePath: dataset.path,
dropdownSel: dataset.dropdownSel,
filterInputField: 'filter',
+ action: this.action,
};
groupFilterList = new GroupFilterableList(opts);
@@ -64,9 +79,11 @@ export default () => {
render(createElement) {
return createElement('groups-app', {
props: {
+ action: this.action,
store: this.store,
service: this.service,
hideProjects: this.hideProjects,
+ containerId: this.containerId,
},
});
},
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 72b72f4247d..a282c2df441 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -47,9 +47,9 @@ export function removeParamQueryString(url, param) {
return urlVariables.filter(variable => variable.indexOf(param) === -1).join('&');
}
-export function removeParams(params) {
+export function removeParams(params, source = window.location.href) {
const url = document.createElement('a');
- url.href = window.location.href;
+ url.href = source;
params.forEach(param => {
url.search = removeParamQueryString(url.search, param);
diff --git a/app/assets/javascripts/pages/dashboard/groups/index/index.js b/app/assets/javascripts/pages/dashboard/groups/index/index.js
index 79987642796..b9277106a71 100644
--- a/app/assets/javascripts/pages/dashboard/groups/index/index.js
+++ b/app/assets/javascripts/pages/dashboard/groups/index/index.js
@@ -1,3 +1,5 @@
import initGroupsList from '~/groups';
-document.addEventListener('DOMContentLoaded', initGroupsList);
+document.addEventListener('DOMContentLoaded', () => {
+ initGroupsList();
+});
diff --git a/app/assets/javascripts/pages/groups/show/group_tabs.js b/app/assets/javascripts/pages/groups/show/group_tabs.js
new file mode 100644
index 00000000000..c6fe61d2bd9
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/show/group_tabs.js
@@ -0,0 +1,136 @@
+import $ from 'jquery';
+import { removeParams } from '~/lib/utils/url_utility';
+import createGroupTree from '~/groups';
+import {
+ ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
+ ACTIVE_TAB_SHARED,
+ ACTIVE_TAB_ARCHIVED,
+ CONTENT_LIST_CLASS,
+ GROUPS_LIST_HOLDER_CLASS,
+ GROUPS_FILTER_FORM_CLASS,
+} from '~/groups/constants';
+import UserTabs from '~/pages/users/user_tabs';
+import GroupFilterableList from '~/groups/groups_filterable_list';
+
+export default class GroupTabs extends UserTabs {
+ constructor({ defaultAction = 'subgroups_and_projects', action, parentEl }) {
+ super({ defaultAction, action, parentEl });
+ }
+
+ bindEvents() {
+ 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));
+ }
+
+ tabShown(event) {
+ const $target = $(event.target);
+ const action = $target.data('action') || $target.data('targetSection');
+ const source = $target.attr('href') || $target.data('targetPath');
+
+ document.querySelector(GROUPS_FILTER_FORM_CLASS).action = source;
+
+ this.setTab(action);
+ return this.setCurrentAction(source);
+ }
+
+ setTab(action) {
+ const loadableActions = [
+ ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
+ ACTIVE_TAB_SHARED,
+ ACTIVE_TAB_ARCHIVED,
+ ];
+ this.enableSearchBar(action);
+ this.action = action;
+
+ if (this.loaded[action]) {
+ return;
+ }
+
+ if (loadableActions.includes(action)) {
+ this.cleanFilterState();
+ this.loadTab(action);
+ }
+ }
+
+ loadTab(action) {
+ const elId = `js-groups-${action}-tree`;
+ const endpoint = this.getEndpoint(action);
+
+ this.toggleLoading(true);
+
+ createGroupTree(elId, endpoint, action);
+ this.loaded[action] = true;
+
+ this.toggleLoading(false);
+ }
+
+ getEndpoint(action) {
+ const { endpointsDefault, endpointsShared } = this.$parentEl.data();
+ let endpoint;
+
+ switch (action) {
+ case ACTIVE_TAB_ARCHIVED:
+ endpoint = `${endpointsDefault}?archived=only`;
+ break;
+ case ACTIVE_TAB_SHARED:
+ endpoint = endpointsShared;
+ break;
+ default:
+ // ACTIVE_TAB_SUBGROUPS_AND_PROJECTS
+ endpoint = endpointsDefault;
+ break;
+ }
+
+ return endpoint;
+ }
+
+ enableSearchBar(action) {
+ const containerEl = document.getElementById(action);
+ const form = document.querySelector(GROUPS_FILTER_FORM_CLASS);
+ const filter = form.querySelector('.js-groups-list-filter');
+ const holder = containerEl.querySelector(GROUPS_LIST_HOLDER_CLASS);
+ const dataEl = containerEl.querySelector(CONTENT_LIST_CLASS);
+ const endpoint = this.getEndpoint(action);
+
+ if (!dataEl) {
+ return;
+ }
+
+ const { dataset } = dataEl;
+ const opts = {
+ form,
+ filter,
+ holder,
+ filterEndpoint: endpoint || dataset.endpoint,
+ pagePath: null,
+ dropdownSel: '.js-group-filter-dropdown-wrap',
+ filterInputField: 'filter',
+ action,
+ };
+
+ if (!this.loaded[action]) {
+ const filterableList = new GroupFilterableList(opts);
+ filterableList.initSearch();
+ }
+ }
+
+ cleanFilterState() {
+ const values = Object.values(this.loaded);
+ const loadedTabs = values.filter(e => e === true);
+
+ if (!loadedTabs.length) {
+ return;
+ }
+
+ const newState = removeParams(['page'], window.location.search);
+
+ window.history.replaceState(
+ {
+ url: newState,
+ },
+ document.title,
+ newState,
+ );
+ }
+}
diff --git a/app/assets/javascripts/pages/groups/show/index.js b/app/assets/javascripts/pages/groups/show/index.js
index d7b35d2b26b..5b8c2ae7e81 100644
--- a/app/assets/javascripts/pages/groups/show/index.js
+++ b/app/assets/javascripts/pages/groups/show/index.js
@@ -1,14 +1,22 @@
/* eslint-disable no-new */
+import { getPagePath } from '~/lib/utils/common_utils';
+import { ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED } from '~/groups/constants';
import NewGroupChild from '~/groups/new_group_child';
import notificationsDropdown from '~/notifications_dropdown';
import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/shortcuts_navigation';
-import initGroupsList from '~/groups';
+import GroupTabs from './group_tabs';
document.addEventListener('DOMContentLoaded', () => {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
+ const loadableActions = [ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED];
+ const paths = window.location.pathname.split('/');
+ const subpath = paths[paths.length - 1];
+ const action = loadableActions.includes(subpath) ? subpath : getPagePath(1);
+
+ new GroupTabs({ parentEl: '.groups-listing', action });
new ShortcutsNavigation();
new NotificationsForm();
notificationsDropdown();
@@ -17,6 +25,4 @@ document.addEventListener('DOMContentLoaded', () => {
if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper);
}
-
- initGroupsList();
});
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 60b4d39bb1a..9ff62e58681 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -3,7 +3,6 @@
}
.dashboard .side .card .card-header .input-group {
-
.form-control {
height: 42px;
}
@@ -30,14 +29,15 @@
}
}
+.group-nav-container .group-search,
.group-nav-container .nav-controls {
display: flex;
align-items: flex-start;
- padding: $gl-padding-top 0;
- border-bottom: 1px solid $border-color;
+ padding: $gl-padding-top 0 0;
.group-filter-form {
- flex: 1;
+ flex: 1 1 auto;
+ margin-right: $gl-padding-8;
}
.dropdown-menu-right {
@@ -136,6 +136,10 @@
flex: 1;
}
+ .dropdown-toggle {
+ width: auto;
+ }
+
.dropdown-menu {
width: 100%;
max-width: inherit;
@@ -145,38 +149,14 @@
}
}
-.groups-empty-state {
- padding: 50px 100px;
- overflow: hidden;
-
- @include media-breakpoint-down(sm) {
- padding: 50px 0;
- }
-
- svg {
- float: right;
-
- @include media-breakpoint-down(sm) {
- float: none;
- display: block;
- width: 250px;
- position: relative;
- left: 50%;
- margin-left: -125px;
- }
- }
-
- .text-content {
- float: left;
- width: 460px;
- margin-top: 120px;
+.group-nav-container .group-search {
+ padding: $gl-padding 0;
+ border-bottom: 1px solid $border-color;
+}
- @include media-breakpoint-down(sm) {
- float: none;
- margin-top: 60px;
- width: auto;
- text-align: center;
- }
+.groups-listing {
+ .group-list-tree .group-row:first-child {
+ border-top: 0;
}
}
@@ -278,7 +258,7 @@
}
&::after {
- content: "";
+ content: '';
position: absolute;
height: 100%;
width: 100%;
@@ -346,7 +326,7 @@
position: relative;
&::before {
- content: "";
+ content: '';
display: block;
width: 10px;
height: 0;