diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-04 03:09:26 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-04 03:09:26 +0300 |
commit | 65b1882ddddca5f86397f60dfbd28deb94ec7249 (patch) | |
tree | f6333826cf39f6c5064c22b6b17b938bb41f1523 /app/assets/javascripts/badges | |
parent | 01fa7c10d9c301ca09e40ce2b2e70514cd3f1053 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/badges')
5 files changed, 270 insertions, 136 deletions
diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue index 1a80030c7e6..23873922618 100644 --- a/app/assets/javascripts/badges/components/badge_form.vue +++ b/app/assets/javascripts/badges/components/badge_form.vue @@ -28,6 +28,11 @@ export default { type: Boolean, required: true, }, + inModal: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -119,16 +124,28 @@ export default { exampleUrl, }); }, + cancelButtonType() { + return this.isEditing ? 'button' : 'reset'; + }, + saveText() { + return this.isEditing ? s__('Badges|Save changes') : s__('Badges|Add badge'); + }, + }, + mounted() { + // declared here to make it cancel-able + this.debouncedPreview = debounce(function search() { + this.renderBadge(); + }, badgePreviewDelayInMilliseconds); }, methods: { ...mapActions(['addBadge', 'renderBadge', 'saveBadge', 'stopEditing', 'updateBadgeInForm']), - debouncedPreview: debounce(function preview() { - this.renderBadge(); - }, badgePreviewDelayInMilliseconds), - onCancel() { - this.stopEditing(); + updatePreview() { + this.debouncedPreview(); }, onSubmit() { + this.debouncedPreview.cancel(); + this.renderBadge(); + const form = this.$el; if (!form.checkValidity()) { this.wasValidated = true; @@ -161,6 +178,7 @@ export default { variant: VARIANT_INFO, }); this.wasValidated = false; + this.$emit('close-add-form'); }) .catch((error) => { createAlert({ @@ -171,6 +189,17 @@ export default { throw error; }); }, + closeForm() { + this.$refs.form.reset(); + this.$emit('close-add-form'); + }, + handleCancel() { + if (this.isEditing) { + this.stopEditing(); + } else { + this.closeForm(); + } + }, }, safeHtmlConfig: { ALLOW_TAGS: ['a', 'code'] }, }; @@ -178,6 +207,7 @@ export default { <template> <form + ref="form" :class="{ 'was-validated': wasValidated }" class="gl-mt-3 gl-mb-3 needs-validation" novalidate @@ -197,7 +227,7 @@ export default { type="URL" class="form-control gl-form-input" required - @input="debouncedPreview" + @input="updatePreview" /> <div class="invalid-feedback">{{ s__('Badges|Enter a valid URL') }}</div> <span class="form-text text-muted">{{ badgeLinkUrlExample }}</span> @@ -213,7 +243,7 @@ export default { type="URL" class="form-control gl-form-input" required - @input="debouncedPreview" + @input="updatePreview" /> <div class="invalid-feedback">{{ s__('Badges|Enter a valid URL') }}</div> <span class="form-text text-muted">{{ badgeImageUrlExample }}</span> @@ -235,29 +265,23 @@ export default { </p> </div> - <div v-if="isEditing" class="row-content-block"> - <gl-button class="btn-cancel gl-mr-4" data-testid="cancelEditing" @click="onCancel"> - {{ __('Cancel') }} - </gl-button> + <div v-if="!inModal" class="form-group" data-testid="action-buttons"> <gl-button :loading="isSaving" type="submit" variant="confirm" category="primary" - data-testid="saveEditing" + data-qa-selector="add_badge_button" + class="gl-mr-3" > - {{ s__('Badges|Save changes') }} + {{ saveText }} </gl-button> - </div> - <div v-else class="form-group"> <gl-button - :loading="isSaving" - type="submit" - variant="confirm" - category="primary" - data-qa-selector="add_badge_button" + :type="cancelButtonType" + data-qa-selector="cancel_badge_button" + @click="handleCancel" > - {{ s__('Badges|Add badge') }} + {{ __('Cancel') }} </gl-button> </div> </form> diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue index 76625fe9a60..dbb692a389e 100644 --- a/app/assets/javascripts/badges/components/badge_list.vue +++ b/app/assets/javascripts/badges/components/badge_list.vue @@ -1,15 +1,42 @@ <script> -import { GlLoadingIcon, GlBadge } from '@gitlab/ui'; -import { mapState } from 'vuex'; -import { GROUP_BADGE } from '../constants'; -import BadgeListRow from './badge_list_row.vue'; +import { + GlBadge, + GlLoadingIcon, + GlTable, + GlPagination, + GlButton, + GlModalDirective, +} from '@gitlab/ui'; +import { mapActions, mapState } from 'vuex'; +import { __, s__ } from '~/locale'; +import { GROUP_BADGE, PROJECT_BADGE, INITIAL_PAGE, PAGE_SIZE } from '../constants'; +import Badge from './badge.vue'; export default { + PAGE_SIZE, + INITIAL_PAGE, name: 'BadgeList', components: { - BadgeListRow, - GlLoadingIcon, + Badge, GlBadge, + GlLoadingIcon, + GlTable, + GlPagination, + GlButton, + }, + directives: { + GlModal: GlModalDirective, + }, + i18n: { + emptyGroupMessage: s__('Badges|This group has no badges, start by creating a new one above.'), + emptyProjectMessage: s__( + 'Badges|This project has no badges, start by creating a new one above.', + ), + }, + data() { + return { + currentPage: INITIAL_PAGE, + }; }, computed: { ...mapState(['badges', 'isLoading', 'kind']), @@ -19,28 +46,123 @@ export default { isGroupBadge() { return this.kind === GROUP_BADGE; }, + showPagination() { + return this.badges.length > PAGE_SIZE; + }, + emptyMessage() { + return this.isGroupBadge + ? this.$options.i18n.emptyGroupMessage + : this.$options.i18n.emptyProjectMessage; + }, + fields() { + return [ + { + key: 'name', + label: __('Name'), + tdClass: 'gl-vertical-align-middle!', + }, + { + key: 'badge', + label: __('Badge'), + tdClass: 'gl-vertical-align-middle!', + }, + { + key: 'url', + label: __('URL'), + tdClass: 'gl-vertical-align-middle!', + }, + { + key: 'actions', + label: __('Actions'), + thClass: 'gl-text-right', + tdClass: 'gl-text-right', + }, + ]; + }, + }, + methods: { + ...mapActions(['editBadge', 'updateBadgeInModal']), + badgeKindText(item) { + if (item.kind === PROJECT_BADGE) { + return s__('Badges|Project Badge'); + } + + return s__('Badges|Group Badge'); + }, + canEditBadge(item) { + return item.kind === this.kind; + }, }, }; </script> <template> - <div class="card"> - <div class="card-header"> - {{ s__('Badges|Your badges') }} - <gl-badge v-show="!isLoading" size="sm">{{ badges.length }}</gl-badge> - </div> - <gl-loading-icon v-show="isLoading" size="lg" class="card-body" /> - <div v-if="hasNoBadges" class="card-body"> - <span v-if="isGroupBadge">{{ s__('Badges|This group has no badges') }}</span> - <span v-else>{{ s__('Badges|This project has no badges') }}</span> - </div> - <div v-else class="card-body" data-qa-selector="badge_list_content"> - <badge-list-row - v-for="badge in badges" - :key="badge.id" - :badge="badge" - data-qa-selector="badge_list_row" - :data-qa-badge-name="badge.name" + <div> + <gl-loading-icon v-show="isLoading" size="md" /> + <div data-qa-selector="badge_list_content"> + <gl-table + :empty-text="emptyMessage" + :fields="fields" + :items="badges" + :per-page="$options.PAGE_SIZE" + :current-page="currentPage" + stacked="md" + show-empty + data-qa-selector="badge_list" + > + <template #cell(name)="{ item }"> + <label class="label-bold str-truncated mb-0">{{ item.name }}</label> + <gl-badge size="sm">{{ badgeKindText(item) }}</gl-badge> + </template> + + <template #cell(badge)="{ item }"> + <badge :image-url="item.renderedImageUrl" :link-url="item.renderedLinkUrl" /> + </template> + + <template #cell(url)="{ item }"> + {{ item.linkUrl }} + </template> + + <template #cell(actions)="{ item }"> + <div v-if="canEditBadge(item)" class="table-action-buttons" data-testid="badge-actions"> + <gl-button + v-gl-modal.edit-badge-modal + :disabled="item.isDeleting" + class="gl-mr-3" + variant="default" + icon="pencil" + size="medium" + :aria-label="__('Edit')" + data-testid="edit-badge-button" + @click="editBadge(item)" + /> + <gl-button + v-gl-modal.delete-badge-modal + :disabled="item.isDeleting" + category="secondary" + variant="danger" + icon="remove" + size="medium" + :aria-label="__('Delete')" + data-testid="delete-badge" + @click="updateBadgeInModal(item)" + /> + <gl-loading-icon v-show="item.isDeleting" size="sm" :inline="true" /> + </div> + </template> + </gl-table> + + <gl-pagination + v-if="showPagination" + v-model="currentPage" + :per-page="$options.PAGE_SIZE" + :total-items="badges.length" + :prev-text="__('Prev')" + :next-text="__('Next')" + :label-next-page="__('Go to next page')" + :label-prev-page="__('Go to previous page')" + align="center" + class="gl-mt-5" /> </div> </div> diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue deleted file mode 100644 index 4c2b700c7ff..00000000000 --- a/app/assets/javascripts/badges/components/badge_list_row.vue +++ /dev/null @@ -1,81 +0,0 @@ -<script> -import { GlLoadingIcon, GlButton, GlModalDirective, GlBadge } from '@gitlab/ui'; -import { mapActions, mapState } from 'vuex'; -import { s__ } from '~/locale'; -import { PROJECT_BADGE } from '../constants'; -import Badge from './badge.vue'; - -export default { - name: 'BadgeListRow', - components: { - Badge, - GlLoadingIcon, - GlButton, - GlBadge, - }, - directives: { - GlModal: GlModalDirective, - }, - props: { - badge: { - type: Object, - required: true, - }, - }, - computed: { - ...mapState(['kind']), - badgeKindText() { - if (this.badge.kind === PROJECT_BADGE) { - return s__('Badges|Project Badge'); - } - - return s__('Badges|Group Badge'); - }, - canEditBadge() { - return this.badge.kind === this.kind; - }, - }, - methods: { - ...mapActions(['editBadge', 'updateBadgeInModal']), - }, -}; -</script> - -<template> - <div class="gl-responsive-table-row-layout gl-responsive-table-row"> - <badge - :image-url="badge.renderedImageUrl" - :link-url="badge.renderedLinkUrl" - class="table-section section-30" - /> - <div class="table-section section-30"> - <label class="label-bold str-truncated mb-0">{{ badge.name }}</label> - <gl-badge size="sm">{{ badgeKindText }}</gl-badge> - </div> - <span class="table-section section-30 str-truncated">{{ badge.linkUrl }}</span> - <div class="table-section section-10 table-button-footer"> - <div v-if="canEditBadge" class="table-action-buttons"> - <gl-button - :disabled="badge.isDeleting" - class="gl-mr-3" - variant="default" - icon="pencil" - size="medium" - :aria-label="__('Edit')" - @click="editBadge(badge)" - /> - <gl-button - v-gl-modal.delete-badge-modal - :disabled="badge.isDeleting" - variant="danger" - icon="remove" - size="medium" - :aria-label="__('Delete')" - data-testid="delete-badge" - @click="updateBadgeInModal(badge)" - /> - <gl-loading-icon v-show="badge.isDeleting" size="sm" :inline="true" /> - </div> - </div> - </div> -</template> diff --git a/app/assets/javascripts/badges/components/badge_settings.vue b/app/assets/javascripts/badges/components/badge_settings.vue index 09f997d73aa..6fe772e717c 100644 --- a/app/assets/javascripts/badges/components/badge_settings.vue +++ b/app/assets/javascripts/badges/components/badge_settings.vue @@ -1,5 +1,5 @@ <script> -import { GlSprintf, GlModal } from '@gitlab/ui'; +import { GlButton, GlCard, GlModal, GlIcon, GlSprintf } from '@gitlab/ui'; import { mapState, mapActions } from 'vuex'; import { createAlert, VARIANT_INFO } from '~/alert'; import { __, s__ } from '~/locale'; @@ -13,17 +13,34 @@ export default { Badge, BadgeForm, BadgeList, + GlButton, + GlCard, GlModal, + GlIcon, GlSprintf, }, i18n: { + title: s__('Badges|Your badges'), + addButton: s__('Badges|Add badge'), + addFormTitle: s__('Badges|Add new badge'), deleteModalText: s__( 'Badges|You are going to delete this badge. Deleted badges %{strongStart}cannot%{strongEnd} be restored.', ), }, + data() { + return { + addFormVisible: false, + }; + }, computed: { - ...mapState(['badgeInModal', 'isEditing']), - primaryProps() { + ...mapState(['badges', 'badgeInModal', 'isEditing']), + saveProps() { + return { + text: __('Save changes'), + attributes: { category: 'primary', variant: 'confirm' }, + }; + }, + deleteProps() { return { text: __('Delete badge'), attributes: { category: 'primary', variant: 'danger' }, @@ -37,7 +54,16 @@ export default { }, methods: { ...mapActions(['deleteBadge']), - onSubmitModal() { + showAddForm() { + this.addFormVisible = !this.addFormVisible; + }, + closeAddForm() { + this.addFormVisible = false; + }, + onSubmitEditModal() { + this.$refs.editForm.onSubmit(); + }, + onSubmitDeleteModal() { this.deleteBadge(this.badgeInModal) .then(() => { createAlert({ @@ -58,12 +84,58 @@ export default { <template> <div class="badge-settings"> + <gl-card + class="gl-new-card" + header-class="gl-new-card-header" + body-class="gl-new-card-body gl-overflow-hidden gl-px-0" + > + <template #header> + <div class="gl-new-card-title-wrapper"> + <h3 class="gl-new-card-title">{{ $options.i18n.title }}</h3> + <span class="gl-new-card-count"> + <gl-icon name="labels" class="gl-mr-2" /> + {{ badges.length }} + </span> + </div> + <div class="gl-new-card-actions"> + <gl-button + v-if="!addFormVisible" + size="small" + data-testid="show-badge-add-form" + @click="showAddForm" + >{{ $options.i18n.addButton }}</gl-button + > + </div> + </template> + + <div v-if="addFormVisible" class="gl-new-card-add-form gl-m-5"> + <h4 class="gl-mt-0">{{ $options.i18n.addFormTitle }}</h4> + <badge-form + :is-editing="false" + data-testid="add-new-badge" + @close-add-form="closeAddForm" + /> + </div> + + <badge-list /> + </gl-card> + + <gl-modal + modal-id="edit-badge-modal" + :title="s__('Badges|Edit badge')" + :action-primary="saveProps" + :action-cancel="cancelProps" + @primary="onSubmitEditModal" + > + <badge-form ref="editForm" :is-editing="true" :in-modal="true" data-testid="edit-badge" /> + </gl-modal> + <gl-modal modal-id="delete-badge-modal" :title="s__('Badges|Delete badge?')" - :action-primary="primaryProps" + :action-primary="deleteProps" :action-cancel="cancelProps" - @primary="onSubmitModal" + @primary="onSubmitDeleteModal" > <div class="well"> <badge @@ -79,10 +151,5 @@ export default { </gl-sprintf> </p> </gl-modal> - - <badge-form v-show="isEditing" :is-editing="true" data-testid="edit-badge" /> - - <badge-form v-show="!isEditing" :is-editing="false" data-testid="add-new-badge" /> - <badge-list v-show="!isEditing" /> </div> </template> diff --git a/app/assets/javascripts/badges/constants.js b/app/assets/javascripts/badges/constants.js index 709436abca6..56b45abbe6b 100644 --- a/app/assets/javascripts/badges/constants.js +++ b/app/assets/javascripts/badges/constants.js @@ -8,3 +8,5 @@ export const PLACEHOLDERS = [ 'default_branch', 'commit_sha', ]; +export const INITIAL_PAGE = 1; +export const PAGE_SIZE = 10; |