diff options
Diffstat (limited to 'app/assets/javascripts/groups')
9 files changed, 227 insertions, 112 deletions
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index cd5521c599e..0bd7371d39b 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -17,11 +17,6 @@ export default { GlLoadingIcon, EmptyState, }, - inject: { - renderEmptyState: { - default: false, - }, - }, props: { action: { type: String, @@ -45,6 +40,11 @@ export default { type: Boolean, required: true, }, + renderEmptyState: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -224,6 +224,9 @@ export default { }, showLegacyEmptyState() { const { containerEl } = this; + + if (!containerEl) return; + const contentListEl = containerEl.querySelector(CONTENT_LIST_CLASS); const emptyStateEl = containerEl.querySelector('.empty-state'); diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index 2f182b86d2c..961af800971 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -16,15 +16,15 @@ import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge. import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; import { helpPagePath } from '~/helpers/help_page_helper'; import { __ } from '~/locale'; -import { VISIBILITY_LEVELS_ENUM } from '~/visibility_level/constants'; +import { VISIBILITY_LEVELS_STRING_TO_INTEGER } from '~/visibility_level/constants'; import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, ITEM_TYPE } from '../constants'; import eventHub from '../event_hub'; -import itemActions from './item_actions.vue'; -import itemCaret from './item_caret.vue'; -import itemStats from './item_stats.vue'; -import itemTypeIcon from './item_type_icon.vue'; +import ItemActions from './item_actions.vue'; +import ItemCaret from './item_caret.vue'; +import ItemStats from './item_stats.vue'; +import ItemTypeIcon from './item_type_icon.vue'; export default { directives: { @@ -41,10 +41,10 @@ export default { GlPopover, GlLink, UserAccessRoleBadge, - itemCaret, - itemTypeIcon, - itemActions, - itemStats, + ItemCaret, + ItemTypeIcon, + ItemActions, + ItemStats, }, inject: ['currentGroupVisibility'], props: { @@ -111,8 +111,8 @@ export default { shouldShowVisibilityWarning() { return ( this.action === 'shared' && - VISIBILITY_LEVELS_ENUM[this.group.visibility] > - VISIBILITY_LEVELS_ENUM[this.currentGroupVisibility] + VISIBILITY_LEVELS_STRING_TO_INTEGER[this.group.visibility] > + VISIBILITY_LEVELS_STRING_TO_INTEGER[this.currentGroupVisibility] ); }, }, diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue index 2aa812250a0..a4c163b0a81 100644 --- a/app/assets/javascripts/groups/components/item_stats.vue +++ b/app/assets/javascripts/groups/components/item_stats.vue @@ -1,19 +1,19 @@ <script> import { GlBadge } from '@gitlab/ui'; import isProjectPendingRemoval from 'ee_else_ce/groups/mixins/is_project_pending_removal'; -import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE, } from '../constants'; -import itemStatsValue from './item_stats_value.vue'; +import ItemStatsValue from './item_stats_value.vue'; export default { components: { - timeAgoTooltip, - itemStatsValue, + TimeAgoTooltip, + ItemStatsValue, GlBadge, }, mixins: [isProjectPendingRemoval], diff --git a/app/assets/javascripts/groups/components/overview_tabs.vue b/app/assets/javascripts/groups/components/overview_tabs.vue new file mode 100644 index 00000000000..325e42af0f8 --- /dev/null +++ b/app/assets/javascripts/groups/components/overview_tabs.vue @@ -0,0 +1,103 @@ +<script> +import { GlTabs, GlTab } from '@gitlab/ui'; +import { isString } from 'lodash'; +import { __ } from '~/locale'; +import GroupsStore from '../store/groups_store'; +import GroupsService from '../service/groups_service'; +import { + ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, + ACTIVE_TAB_SHARED, + ACTIVE_TAB_ARCHIVED, +} from '../constants'; +import GroupsApp from './app.vue'; + +export default { + components: { GlTabs, GlTab, GroupsApp }, + inject: ['endpoints'], + data() { + return { + tabs: [ + { + title: this.$options.i18n[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS], + key: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, + renderEmptyState: true, + lazy: false, + service: new GroupsService(this.endpoints[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]), + store: new GroupsStore({ showSchemaMarkup: true }), + }, + { + title: this.$options.i18n[ACTIVE_TAB_SHARED], + key: ACTIVE_TAB_SHARED, + renderEmptyState: false, + lazy: true, + service: new GroupsService(this.endpoints[ACTIVE_TAB_SHARED]), + store: new GroupsStore(), + }, + { + title: this.$options.i18n[ACTIVE_TAB_ARCHIVED], + key: ACTIVE_TAB_ARCHIVED, + renderEmptyState: false, + lazy: true, + service: new GroupsService(this.endpoints[ACTIVE_TAB_ARCHIVED]), + store: new GroupsStore(), + }, + ], + activeTabIndex: 0, + }; + }, + mounted() { + const activeTabIndex = this.tabs.findIndex((tab) => tab.key === this.$route.name); + + if (activeTabIndex === -1) { + return; + } + + this.activeTabIndex = activeTabIndex; + }, + methods: { + handleTabInput(tabIndex) { + if (tabIndex === this.activeTabIndex) { + return; + } + + this.activeTabIndex = tabIndex; + + const tab = this.tabs[tabIndex]; + tab.lazy = false; + + // Vue router will convert `/` to `%2F` if you pass a string as a param + // If you pass an array as a param it will concatenate them with a `/` + // This makes sure we are always passing an array for the group param + const groupParam = isString(this.$route.params.group) + ? this.$route.params.group.split('/') + : this.$route.params.group; + + this.$router.push({ name: tab.key, params: { group: groupParam } }); + }, + }, + i18n: { + [ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]: __('Subgroups and projects'), + [ACTIVE_TAB_SHARED]: __('Shared projects'), + [ACTIVE_TAB_ARCHIVED]: __('Archived projects'), + }, +}; +</script> + +<template> + <gl-tabs content-class="gl-pt-0" :value="activeTabIndex" @input="handleTabInput"> + <gl-tab + v-for="{ key, title, renderEmptyState, lazy, service, store } in tabs" + :key="key" + :title="title" + :lazy="lazy" + > + <groups-app + :action="key" + :service="service" + :store="store" + :hide-projects="false" + :render-empty-state="renderEmptyState" + /> + </gl-tab> + </gl-tabs> +</template> diff --git a/app/assets/javascripts/groups/components/visibility_level_dropdown.vue b/app/assets/javascripts/groups/components/visibility_level_dropdown.vue deleted file mode 100644 index 0933045fc38..00000000000 --- a/app/assets/javascripts/groups/components/visibility_level_dropdown.vue +++ /dev/null @@ -1,48 +0,0 @@ -<script> -import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; - -export default { - components: { - GlDropdown, - GlDropdownItem, - }, - props: { - visibilityLevelOptions: { - type: Array, - required: true, - }, - defaultLevel: { - type: Number, - required: true, - }, - }, - data() { - return { - selectedOption: this.getDefaultOption(), - }; - }, - methods: { - getDefaultOption() { - return this.visibilityLevelOptions.find((option) => option.level === this.defaultLevel); - }, - onClick(option) { - this.selectedOption = option; - }, - }, -}; -</script> -<template> - <div> - <input type="hidden" name="group[visibility_level]" :value="selectedOption.level" /> - <gl-dropdown :text="selectedOption.label" class="gl-w-full" menu-class="gl-w-full! gl-mb-0"> - <gl-dropdown-item - v-for="option in visibilityLevelOptions" - :key="option.level" - :secondary-text="option.description" - @click="onClick(option)" - > - <div class="gl-font-weight-bold gl-mb-1">{{ option.label }}</div> - </gl-dropdown-item> - </gl-dropdown> - </div> -</template> diff --git a/app/assets/javascripts/groups/constants.js b/app/assets/javascripts/groups/constants.js index 0d09ad9442b..223c2975c11 100644 --- a/app/assets/javascripts/groups/constants.js +++ b/app/assets/javascripts/groups/constants.js @@ -1,8 +1,8 @@ import { __, s__ } from '~/locale'; import { - VISIBILITY_LEVEL_PRIVATE, - VISIBILITY_LEVEL_INTERNAL, - VISIBILITY_LEVEL_PUBLIC, + VISIBILITY_LEVEL_PRIVATE_STRING, + VISIBILITY_LEVEL_INTERNAL_STRING, + VISIBILITY_LEVEL_PUBLIC_STRING, } from '~/visibility_level/constants'; export const MAX_CHILDREN_COUNT = 20; @@ -34,29 +34,31 @@ export const ITEM_TYPE = { }; export const GROUP_VISIBILITY_TYPE = { - [VISIBILITY_LEVEL_PUBLIC]: __( + [VISIBILITY_LEVEL_PUBLIC_STRING]: __( 'Public - The group and any public projects can be viewed without any authentication.', ), - [VISIBILITY_LEVEL_INTERNAL]: __( + [VISIBILITY_LEVEL_INTERNAL_STRING]: __( 'Internal - The group and any internal projects can be viewed by any logged in user except external users.', ), - [VISIBILITY_LEVEL_PRIVATE]: __( + [VISIBILITY_LEVEL_PRIVATE_STRING]: __( 'Private - The group and its projects can only be viewed by members.', ), }; export const PROJECT_VISIBILITY_TYPE = { - [VISIBILITY_LEVEL_PUBLIC]: __('Public - The project can be accessed without any authentication.'), - [VISIBILITY_LEVEL_INTERNAL]: __( + [VISIBILITY_LEVEL_PUBLIC_STRING]: __( + 'Public - The project can be accessed without any authentication.', + ), + [VISIBILITY_LEVEL_INTERNAL_STRING]: __( 'Internal - The project can be accessed by any logged in user except external users.', ), - [VISIBILITY_LEVEL_PRIVATE]: __( + [VISIBILITY_LEVEL_PRIVATE_STRING]: __( 'Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.', ), }; export const VISIBILITY_TYPE_ICON = { - [VISIBILITY_LEVEL_PUBLIC]: 'earth', - [VISIBILITY_LEVEL_INTERNAL]: 'shield', - [VISIBILITY_LEVEL_PRIVATE]: 'lock', + [VISIBILITY_LEVEL_PUBLIC_STRING]: 'earth', + [VISIBILITY_LEVEL_INTERNAL_STRING]: 'shield', + [VISIBILITY_LEVEL_PRIVATE_STRING]: 'lock', }; diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js index a502fcd31ad..c3bf3f28509 100644 --- a/app/assets/javascripts/groups/index.js +++ b/app/assets/javascripts/groups/index.js @@ -4,9 +4,9 @@ import { parseBoolean } from '~/lib/utils/common_utils'; import UserCallout from '~/user_callout'; import Translate from '../vue_shared/translate'; -import groupsApp from './components/app.vue'; -import groupFolderComponent from './components/group_folder.vue'; -import groupItemComponent from './components/group_item.vue'; +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'; import GroupFilterableList from './groups_filterable_list'; import GroupsService from './service/groups_service'; @@ -33,8 +33,8 @@ export default (containerId = 'js-groups-tree', endpoint, action = '') => { dataEl = containerEl.querySelector(CONTENT_LIST_CLASS); } - Vue.component('GroupFolder', groupFolderComponent); - Vue.component('GroupItem', groupItemComponent); + Vue.component('GroupFolder', GroupFolderComponent); + Vue.component('GroupItem', GroupItemComponent); Vue.use(GlToast); @@ -42,7 +42,7 @@ export default (containerId = 'js-groups-tree', endpoint, action = '') => { new Vue({ el, components: { - groupsApp, + GroupsApp, }, provide() { const { @@ -52,7 +52,6 @@ export default (containerId = 'js-groups-tree', endpoint, action = '') => { newSubgroupIllustration, newProjectIllustration, emptySubgroupIllustration, - renderEmptyState, canCreateSubgroups, canCreateProjects, currentGroupVisibility, @@ -65,7 +64,6 @@ export default (containerId = 'js-groups-tree', endpoint, action = '') => { newSubgroupIllustration, newProjectIllustration, emptySubgroupIllustration, - renderEmptyState: parseBoolean(renderEmptyState), canCreateSubgroups: parseBoolean(canCreateSubgroups), canCreateProjects: parseBoolean(canCreateProjects), currentGroupVisibility, @@ -75,6 +73,7 @@ export default (containerId = 'js-groups-tree', endpoint, action = '') => { const { dataset } = dataEl || this.$options.el; const hideProjects = parseBoolean(dataset.hideProjects); const showSchemaMarkup = parseBoolean(dataset.showSchemaMarkup); + const renderEmptyState = parseBoolean(dataset.renderEmptyState); const service = new GroupsService(endpoint || dataset.endpoint); const store = new GroupsStore({ hideProjects, showSchemaMarkup }); @@ -83,6 +82,7 @@ export default (containerId = 'js-groups-tree', endpoint, action = '') => { store, service, hideProjects, + renderEmptyState, loading: true, containerId, }; @@ -119,6 +119,7 @@ export default (containerId = 'js-groups-tree', endpoint, action = '') => { store: this.store, service: this.service, hideProjects: this.hideProjects, + renderEmptyState: this.renderEmptyState, containerId: this.containerId, }, }); diff --git a/app/assets/javascripts/groups/init_overview_tabs.js b/app/assets/javascripts/groups/init_overview_tabs.js new file mode 100644 index 00000000000..4fa3682c729 --- /dev/null +++ b/app/assets/javascripts/groups/init_overview_tabs.js @@ -0,0 +1,78 @@ +import Vue from 'vue'; +import VueRouter from 'vue-router'; +import { GlToast } from '@gitlab/ui'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import GroupFolder from './components/group_folder.vue'; +import GroupItem from './components/group_item.vue'; +import { + ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, + ACTIVE_TAB_SHARED, + ACTIVE_TAB_ARCHIVED, +} from './constants'; +import OverviewTabs from './components/overview_tabs.vue'; + +export const createRouter = () => { + const routes = [ + { name: ACTIVE_TAB_SHARED, path: '/groups/:group*/-/shared' }, + { name: ACTIVE_TAB_ARCHIVED, path: '/groups/:group*/-/archived' }, + { name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, path: '/:group*' }, + ]; + + const router = new VueRouter({ + routes, + mode: 'history', + base: '/', + }); + + return router; +}; + +export const initGroupOverviewTabs = () => { + const el = document.getElementById('js-group-overview-tabs'); + + if (!el) return false; + + Vue.component('GroupFolder', GroupFolder); + Vue.component('GroupItem', GroupItem); + Vue.use(GlToast); + Vue.use(VueRouter); + + const router = createRouter(); + + const { + newSubgroupPath, + newProjectPath, + newSubgroupIllustration, + newProjectIllustration, + emptySubgroupIllustration, + canCreateSubgroups, + canCreateProjects, + currentGroupVisibility, + subgroupsAndProjectsEndpoint, + sharedProjectsEndpoint, + archivedProjectsEndpoint, + } = el.dataset; + + return new Vue({ + el, + router, + provide: { + newSubgroupPath, + newProjectPath, + newSubgroupIllustration, + newProjectIllustration, + emptySubgroupIllustration, + canCreateSubgroups: parseBoolean(canCreateSubgroups), + canCreateProjects: parseBoolean(canCreateProjects), + currentGroupVisibility, + endpoints: { + [ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]: subgroupsAndProjectsEndpoint, + [ACTIVE_TAB_SHARED]: sharedProjectsEndpoint, + [ACTIVE_TAB_ARCHIVED]: archivedProjectsEndpoint, + }, + }, + render(createElement) { + return createElement(OverviewTabs); + }, + }); +}; diff --git a/app/assets/javascripts/groups/visibility_level.js b/app/assets/javascripts/groups/visibility_level.js deleted file mode 100644 index d570b5e65ac..00000000000 --- a/app/assets/javascripts/groups/visibility_level.js +++ /dev/null @@ -1,24 +0,0 @@ -import Vue from 'vue'; -import VisibilityLevelDropdown from './components/visibility_level_dropdown.vue'; - -export default () => { - const el = document.querySelector('.js-visibility-level-dropdown'); - - if (!el) { - return null; - } - - const { visibilityLevelOptions, defaultLevel } = el.dataset; - - return new Vue({ - el, - render(createElement) { - return createElement(VisibilityLevelDropdown, { - props: { - visibilityLevelOptions: JSON.parse(visibilityLevelOptions), - defaultLevel: Number(defaultLevel), - }, - }); - }, - }); -}; |