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:
Diffstat (limited to 'app/assets/javascripts/ref/components/ref_selector.vue')
-rw-r--r--app/assets/javascripts/ref/components/ref_selector.vue220
1 files changed, 80 insertions, 140 deletions
diff --git a/app/assets/javascripts/ref/components/ref_selector.vue b/app/assets/javascripts/ref/components/ref_selector.vue
index 10967fb84ed..359909b8f3b 100644
--- a/app/assets/javascripts/ref/components/ref_selector.vue
+++ b/app/assets/javascripts/ref/components/ref_selector.vue
@@ -1,13 +1,8 @@
<script>
-import {
- GlDropdown,
- GlDropdownDivider,
- GlSearchBoxByType,
- GlSprintf,
- GlLoadingIcon,
-} from '@gitlab/ui';
+import { GlBadge, GlIcon, GlCollapsibleListbox } from '@gitlab/ui';
import { debounce, isArray } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex';
+import { sprintf } from '~/locale';
import {
ALL_REF_TYPES,
SEARCH_DEBOUNCE_MS,
@@ -15,21 +10,16 @@ import {
REF_TYPE_BRANCHES,
REF_TYPE_TAGS,
REF_TYPE_COMMITS,
- BRANCH_REF_TYPE,
- TAG_REF_TYPE,
} from '../constants';
import createStore from '../stores';
-import RefResultsSection from './ref_results_section.vue';
+import { formatListBoxItems, formatErrors } from '../format_refs';
export default {
name: 'RefSelector',
components: {
- GlDropdown,
- GlDropdownDivider,
- GlSearchBoxByType,
- GlSprintf,
- GlLoadingIcon,
- RefResultsSection,
+ GlBadge,
+ GlIcon,
+ GlCollapsibleListbox,
},
inheritAttrs: false,
props: {
@@ -87,6 +77,11 @@ export default {
required: false,
default: '',
},
+ toggleButtonClass: {
+ type: [String, Object, Array],
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -106,35 +101,33 @@ export default {
...this.translations,
};
},
- showBranchesSection() {
- return (
- this.enabledRefTypes.includes(REF_TYPE_BRANCHES) &&
- Boolean(this.matches.branches.totalCount > 0 || this.matches.branches.error)
- );
+ listBoxItems() {
+ return formatListBoxItems(this.branches, this.tags, this.commits);
},
- showTagsSection() {
- return (
- this.enabledRefTypes.includes(REF_TYPE_TAGS) &&
- Boolean(this.matches.tags.totalCount > 0 || this.matches.tags.error)
- );
+ branches() {
+ return this.enabledRefTypes.includes(REF_TYPE_BRANCHES) ? this.matches.branches.list : [];
},
- showCommitsSection() {
- return (
- this.enabledRefTypes.includes(REF_TYPE_COMMITS) &&
- Boolean(this.matches.commits.totalCount > 0 || this.matches.commits.error)
- );
+ tags() {
+ return this.enabledRefTypes.includes(REF_TYPE_TAGS) ? this.matches.tags.list : [];
},
- showNoResults() {
- return !this.showBranchesSection && !this.showTagsSection && !this.showCommitsSection;
+ commits() {
+ return this.enabledRefTypes.includes(REF_TYPE_COMMITS) ? this.matches.commits.list : [];
},
- showSectionHeaders() {
- return this.enabledRefTypes.length > 1;
- },
- toggleButtonClass() {
- return {
- 'gl-inset-border-1-red-500!': !this.state,
- 'gl-font-monospace': Boolean(this.selectedRef),
- };
+ extendedToggleButtonClass() {
+ const classes = [
+ {
+ 'gl-inset-border-1-red-500!': !this.state,
+ 'gl-font-monospace': Boolean(this.selectedRef),
+ },
+ ];
+
+ if (Array.isArray(this.toggleButtonClass)) {
+ classes.push(...this.toggleButtonClass);
+ } else {
+ classes.push(this.toggleButtonClass);
+ }
+
+ return classes;
},
footerSlotProps() {
return {
@@ -143,6 +136,9 @@ export default {
query: this.lastQuery,
};
},
+ errors() {
+ return formatErrors(this.matches.branches, this.matches.tags, this.matches.commits);
+ },
selectedRefForDisplay() {
if (this.useSymbolicRefNames && this.selectedRef) {
return this.selectedRef.replace(/^refs\/(tags|heads)\//, '');
@@ -153,11 +149,12 @@ export default {
buttonText() {
return this.selectedRefForDisplay || this.i18n.noRefSelected;
},
- isTagRefType() {
- return this.refType === TAG_REF_TYPE;
- },
- isBranchRefType() {
- return this.refType === BRANCH_REF_TYPE;
+ noResultsMessage() {
+ return this.lastQuery
+ ? sprintf(this.i18n.noResultsWithQuery, {
+ query: this.lastQuery,
+ })
+ : this.i18n.noResults;
},
},
watch: {
@@ -185,9 +182,7 @@ export default {
// because we need to access the .cancel() method
// lodash attaches to the function, which is
// made inaccessible by Vue.
- this.debouncedSearch = debounce(function search() {
- this.search();
- }, SEARCH_DEBOUNCE_MS);
+ this.debouncedSearch = debounce(this.search, SEARCH_DEBOUNCE_MS);
this.setProjectId(this.projectId);
@@ -214,14 +209,8 @@ export default {
'setSelectedRef',
]),
...mapActions({ storeSearch: 'search' }),
- focusSearchBox() {
- this.$refs.searchBox.$el.querySelector('input').focus();
- },
- onSearchBoxEnter() {
- this.debouncedSearch.cancel();
- this.search();
- },
- onSearchBoxInput() {
+ onSearchBoxInput(searchQuery = '') {
+ this.query = searchQuery?.trim();
this.debouncedSearch();
},
selectRef(ref) {
@@ -231,104 +220,55 @@ export default {
search() {
this.storeSearch(this.query);
},
+ totalCountText(count) {
+ return count > 999 ? this.i18n.totalCountLabel : `${count}`;
+ },
},
};
</script>
<template>
<div>
- <gl-dropdown
- :header-text="i18n.dropdownHeader"
- :toggle-class="toggleButtonClass"
- :text="buttonText"
+ <gl-collapsible-listbox
class="ref-selector gl-w-full"
+ block
+ searchable
+ :selected="selectedRef"
+ :header-text="i18n.dropdownHeader"
+ :items="listBoxItems"
+ :no-results-text="noResultsMessage"
+ :searching="isLoading"
+ :search-placeholder="i18n.searchPlaceholder"
+ :toggle-class="extendedToggleButtonClass"
+ :toggle-text="buttonText"
v-bind="$attrs"
v-on="$listeners"
- @shown="focusSearchBox"
+ @hidden="$emit('hide')"
+ @search="onSearchBoxInput"
+ @select="selectRef"
>
- <template #header>
- <gl-search-box-by-type
- ref="searchBox"
- v-model.trim="query"
- :placeholder="i18n.searchPlaceholder"
- autocomplete="off"
- data-qa-selector="ref_selector_searchbox"
- @input="onSearchBoxInput"
- @keydown.enter.prevent="onSearchBoxEnter"
- />
+ <template #group-label="{ group }">
+ {{ group.text }} <gl-badge size="sm">{{ totalCountText(group.options.length) }}</gl-badge>
</template>
-
- <gl-loading-icon v-if="isLoading" size="lg" class="gl-my-3" />
-
- <div
- v-else-if="showNoResults"
- class="gl-text-center gl-mx-3 gl-py-3"
- data-testid="no-results"
- >
- <gl-sprintf v-if="lastQuery" :message="i18n.noResultsWithQuery">
- <template #query>
- <b class="gl-word-break-all">{{ lastQuery }}</b>
- </template>
- </gl-sprintf>
-
- <span v-else>{{ i18n.noResults }}</span>
- </div>
-
- <template v-else>
- <template v-if="showBranchesSection">
- <ref-results-section
- :section-title="i18n.branches"
- :total-count="matches.branches.totalCount"
- :items="matches.branches.list"
- :selected-ref="selectedRef"
- :error="matches.branches.error"
- :error-message="i18n.branchesErrorMessage"
- :show-header="showSectionHeaders"
- data-testid="branches-section"
- data-qa-selector="branches_section"
- :should-show-check="!useSymbolicRefNames || isBranchRefType"
- @selected="selectRef($event)"
- />
-
- <gl-dropdown-divider v-if="showTagsSection || showCommitsSection" />
- </template>
-
- <template v-if="showTagsSection">
- <ref-results-section
- :section-title="i18n.tags"
- :total-count="matches.tags.totalCount"
- :items="matches.tags.list"
- :selected-ref="selectedRef"
- :error="matches.tags.error"
- :error-message="i18n.tagsErrorMessage"
- :show-header="showSectionHeaders"
- data-testid="tags-section"
- :should-show-check="!useSymbolicRefNames || isTagRefType"
- @selected="selectRef($event)"
- />
-
- <gl-dropdown-divider v-if="showCommitsSection" />
- </template>
-
- <template v-if="showCommitsSection">
- <ref-results-section
- :section-title="i18n.commits"
- :total-count="matches.commits.totalCount"
- :items="matches.commits.list"
- :selected-ref="selectedRef"
- :error="matches.commits.error"
- :error-message="i18n.commitsErrorMessage"
- :show-header="showSectionHeaders"
- data-testid="commits-section"
- @selected="selectRef($event)"
- />
- </template>
+ <template #list-item="{ item }">
+ {{ item.text }}
+ <gl-badge v-if="item.default" size="sm" variant="info">{{
+ i18n.defaultLabelText
+ }}</gl-badge>
</template>
-
<template #footer>
<slot name="footer" v-bind="footerSlotProps"></slot>
+ <div
+ v-for="errorMessage in errors"
+ :key="errorMessage"
+ data-testid="red-selector-error-list"
+ class="gl-display-flex gl-align-items-flex-start gl-text-red-500 gl-mx-4 gl-my-3"
+ >
+ <gl-icon name="error" class="gl-mr-2 gl-mt-2 gl-flex-shrink-0" />
+ <span>{{ errorMessage }}</span>
+ </div>
</template>
- </gl-dropdown>
+ </gl-collapsible-listbox>
<input
v-if="name"
data-testid="selected-ref-form-field"