diff options
Diffstat (limited to 'app/assets/javascripts/frequent_items/components')
3 files changed, 89 insertions, 33 deletions
diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue index 8ad9eeaa266..a4e883c96b5 100644 --- a/app/assets/javascripts/frequent_items/components/app.vue +++ b/app/assets/javascripts/frequent_items/components/app.vue @@ -1,5 +1,5 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import AccessorUtilities from '~/lib/utils/accessor'; import { mapVuexModuleState, @@ -18,6 +18,11 @@ export default { FrequentItemsSearchInput, FrequentItemsList, GlLoadingIcon, + GlButton, + GlIcon, + }, + directives: { + GlTooltip: GlTooltipDirective, }, mixins: [frequentItemsMixin], inject: ['vuexModule'], @@ -40,12 +45,14 @@ export default { ...mapVuexModuleState((vm) => vm.vuexModule, [ 'searchQuery', 'isLoadingItems', + 'isItemsListEditable', 'isFetchFailed', + 'isItemRemovalFailed', 'items', ]), ...mapVuexModuleGetters((vm) => vm.vuexModule, ['hasSearchQuery']), translations() { - return this.getTranslations(['loadingMessage', 'header']); + return this.getTranslations(['loadingMessage', 'header', 'headerEditToggle']); }, }, created() { @@ -74,6 +81,7 @@ export default { ...mapVuexModuleActions((vm) => vm.vuexModule, [ 'setNamespace', 'setStorageKey', + 'toggleItemsListEditablity', 'fetchFrequentItems', ]), dropdownOpenHandler() { @@ -132,8 +140,25 @@ export default { class="loading-animation prepend-top-20" data-testid="loading" /> - <div v-if="!isLoadingItems && !hasSearchQuery" class="section-header" data-testid="header"> - {{ translations.header }} + <div + v-if="!isLoadingItems && !hasSearchQuery" + class="section-header gl-display-flex" + data-testid="header" + > + <span class="gl-flex-grow-1">{{ translations.header }}</span> + <gl-button + v-if="items.length" + v-gl-tooltip.left + size="small" + category="tertiary" + :aria-label="translations.headerEditToggle" + :title="translations.headerEditToggle" + :class="{ 'gl-bg-gray-100!': isItemsListEditable }" + class="gl-p-2!" + @click="toggleItemsListEditablity" + > + <gl-icon name="pencil" :class="{ 'gl-text-gray-900!': isItemsListEditable }" /> + </gl-button> </div> <frequent-items-list v-if="!isLoadingItems" @@ -141,6 +166,7 @@ export default { :namespace="namespace" :has-search-query="hasSearchQuery" :is-fetch-failed="isFetchFailed" + :is-item-removal-failed="isItemRemovalFailed" :matcher="searchQuery" /> </div> diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list.vue index c0bfcf9c4a9..da1d3bedaf4 100644 --- a/app/assets/javascripts/frequent_items/components/frequent_items_list.vue +++ b/app/assets/javascripts/frequent_items/components/frequent_items_list.vue @@ -21,6 +21,10 @@ export default { type: Boolean, required: true, }, + isItemRemovalFailed: { + type: Boolean, + required: true, + }, matcher: { type: String, required: true, @@ -38,6 +42,9 @@ export default { isListEmpty() { return this.items.length === 0; }, + showListEmptyMessage() { + return this.isListEmpty || this.isItemRemovalFailed; + }, listEmptyMessage() { if (this.hasSearchQuery) { return this.isFetchFailed @@ -45,7 +52,7 @@ export default { : this.translations.searchListEmptyMessage; } - return this.isFetchFailed + return this.isFetchFailed || this.isItemRemovalFailed ? this.translations.itemListErrorMessage : this.translations.itemListEmptyMessage; }, @@ -60,9 +67,10 @@ export default { <div class="frequent-items-list-container"> <ul data-testid="frequent-items-list" class="list-unstyled"> <li - v-if="isListEmpty" + v-if="showListEmptyMessage" :class="{ 'section-failure': isFetchFailed }" class="section-empty gl-mb-3" + data-testid="frequent-items-list-empty" > {{ listEmptyMessage }} </li> diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue index 89b6885091c..75ea9beb5cf 100644 --- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue +++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue @@ -1,10 +1,10 @@ <script> -import { GlButton } from '@gitlab/ui'; +import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui'; import { snakeCase } from 'lodash'; import SafeHtml from '~/vue_shared/directives/safe_html'; import highlight from '~/lib/utils/highlight'; import { truncateNamespace } from '~/lib/utils/text_utility'; -import { mapVuexModuleState } from '~/lib/utils/vuex_module_mappers'; +import { mapVuexModuleState, mapVuexModuleActions } from '~/lib/utils/vuex_module_mappers'; import Tracking from '~/tracking'; import ProjectAvatar from '~/vue_shared/components/project_avatar.vue'; @@ -12,11 +12,13 @@ const trackingMixin = Tracking.mixin(); export default { components: { + GlIcon, GlButton, ProjectAvatar, }, directives: { SafeHtml, + GlTooltip: GlTooltipDirective, }, mixins: [trackingMixin], inject: ['vuexModule'], @@ -51,7 +53,7 @@ export default { }, }, computed: { - ...mapVuexModuleState((vm) => vm.vuexModule, ['dropdownType']), + ...mapVuexModuleState((vm) => vm.vuexModule, ['dropdownType', 'isItemsListEditable']), truncatedNamespace() { return truncateNamespace(this.namespace); }, @@ -62,43 +64,63 @@ export default { return `${this.dropdownType}_dropdown_frequent_items_list_item_${snakeCase(this.itemName)}`; }, }, + methods: { + ...mapVuexModuleActions((vm) => vm.vuexModule, ['removeFrequentItem']), + }, }; </script> <template> - <li class="frequent-items-list-item-container"> + <li class="frequent-items-list-item-container gl-relative"> <gl-button category="tertiary" :href="webUrl" - class="gl-text-left gl-justify-content-start!" + class="gl-text-left gl-w-full" + button-text-classes="gl-display-flex gl-w-full" + data-testid="frequent-item-link" @click="track('click_link', { label: itemTrackingLabel })" > - <project-avatar - class="gl-float-left gl-mr-3" - :project-avatar-url="avatarUrl" - :project-id="itemId" - :project-name="itemName" - aria-hidden="true" - /> - <div - data-testid="frequent-items-item-metadata-container" - class="frequent-items-item-metadata-container" - > - <div - v-safe-html="highlightedItemName" - data-testid="frequent-items-item-title" - :title="itemName" - class="frequent-items-item-title" - ></div> + <div class="gl-flex-grow-1"> + <project-avatar + class="gl-float-left gl-mr-3" + :project-avatar-url="avatarUrl" + :project-id="itemId" + :project-name="itemName" + aria-hidden="true" + /> <div - v-if="namespace" - data-testid="frequent-items-item-namespace" - :title="namespace" - class="frequent-items-item-namespace" + data-testid="frequent-items-item-metadata-container" + class="frequent-items-item-metadata-container" > - {{ truncatedNamespace }} + <div + v-safe-html="highlightedItemName" + data-testid="frequent-items-item-title" + :title="itemName" + class="frequent-items-item-title" + ></div> + <div + v-if="namespace" + data-testid="frequent-items-item-namespace" + :title="namespace" + class="frequent-items-item-namespace" + > + {{ truncatedNamespace }} + </div> </div> </div> </gl-button> + <gl-button + v-if="isItemsListEditable" + v-gl-tooltip.left + size="small" + category="tertiary" + :aria-label="__('Remove')" + :title="__('Remove')" + class="gl-align-self-center gl-p-1! gl-absolute! gl-w-auto! gl-top-4 gl-right-4" + data-testid="item-remove" + @click.stop.prevent="removeFrequentItem(itemId)" + > + <gl-icon name="close" /> + </gl-button> </li> </template> |