2020-07-28 23:09:34 +05:30
|
|
|
<script>
|
|
|
|
import {
|
2020-11-24 15:15:51 +05:30
|
|
|
GlDropdown,
|
|
|
|
GlDropdownDivider,
|
|
|
|
GlDropdownSectionHeader,
|
2020-07-28 23:09:34 +05:30
|
|
|
GlSearchBoxByType,
|
|
|
|
GlSprintf,
|
|
|
|
GlIcon,
|
|
|
|
GlLoadingIcon,
|
|
|
|
} from '@gitlab/ui';
|
|
|
|
import { debounce } from 'lodash';
|
2021-03-11 19:13:27 +05:30
|
|
|
import { mapActions, mapGetters, mapState } from 'vuex';
|
2020-07-28 23:09:34 +05:30
|
|
|
import { SEARCH_DEBOUNCE_MS, DEFAULT_I18N } from '../constants';
|
2021-03-11 19:13:27 +05:30
|
|
|
import createStore from '../stores';
|
2020-07-28 23:09:34 +05:30
|
|
|
import RefResultsSection from './ref_results_section.vue';
|
|
|
|
|
|
|
|
export default {
|
|
|
|
name: 'RefSelector',
|
|
|
|
store: createStore(),
|
|
|
|
components: {
|
2020-11-24 15:15:51 +05:30
|
|
|
GlDropdown,
|
|
|
|
GlDropdownDivider,
|
|
|
|
GlDropdownSectionHeader,
|
2020-07-28 23:09:34 +05:30
|
|
|
GlSearchBoxByType,
|
|
|
|
GlSprintf,
|
|
|
|
GlIcon,
|
|
|
|
GlLoadingIcon,
|
|
|
|
RefResultsSection,
|
|
|
|
},
|
|
|
|
props: {
|
|
|
|
value: {
|
|
|
|
type: String,
|
|
|
|
required: false,
|
|
|
|
default: '',
|
|
|
|
},
|
|
|
|
projectId: {
|
|
|
|
type: String,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
translations: {
|
|
|
|
type: Object,
|
|
|
|
required: false,
|
|
|
|
default: () => ({}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
query: '',
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
...mapState({
|
2021-03-08 18:12:59 +05:30
|
|
|
matches: (state) => state.matches,
|
|
|
|
lastQuery: (state) => state.query,
|
|
|
|
selectedRef: (state) => state.selectedRef,
|
2020-07-28 23:09:34 +05:30
|
|
|
}),
|
|
|
|
...mapGetters(['isLoading', 'isQueryPossiblyASha']),
|
|
|
|
i18n() {
|
|
|
|
return {
|
|
|
|
...DEFAULT_I18N,
|
|
|
|
...this.translations,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
showBranchesSection() {
|
|
|
|
return Boolean(this.matches.branches.totalCount > 0 || this.matches.branches.error);
|
|
|
|
},
|
|
|
|
showTagsSection() {
|
|
|
|
return Boolean(this.matches.tags.totalCount > 0 || this.matches.tags.error);
|
|
|
|
},
|
|
|
|
showCommitsSection() {
|
|
|
|
return Boolean(this.matches.commits.totalCount > 0 || this.matches.commits.error);
|
|
|
|
},
|
|
|
|
showNoResults() {
|
|
|
|
return !this.showBranchesSection && !this.showTagsSection && !this.showCommitsSection;
|
|
|
|
},
|
|
|
|
},
|
2020-10-24 23:57:45 +05:30
|
|
|
watch: {
|
|
|
|
// Keep the Vuex store synchronized if the parent
|
|
|
|
// component updates the selected ref through v-model
|
|
|
|
value: {
|
|
|
|
immediate: true,
|
|
|
|
handler() {
|
|
|
|
if (this.value !== this.selectedRef) {
|
|
|
|
this.setSelectedRef(this.value);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-07-28 23:09:34 +05:30
|
|
|
created() {
|
2020-11-24 15:15:51 +05:30
|
|
|
// This method is defined here instead of in `methods`
|
|
|
|
// because we need to access the .cancel() method
|
|
|
|
// lodash attaches to the function, which is
|
|
|
|
// made inaccessible by Vue. More info:
|
|
|
|
// https://stackoverflow.com/a/52988020/1063392
|
|
|
|
this.debouncedSearch = debounce(function search() {
|
|
|
|
this.search(this.query);
|
|
|
|
}, SEARCH_DEBOUNCE_MS);
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
this.setProjectId(this.projectId);
|
|
|
|
this.search(this.query);
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
...mapActions(['setProjectId', 'setSelectedRef', 'search']),
|
|
|
|
focusSearchBox() {
|
|
|
|
this.$refs.searchBox.$el.querySelector('input').focus();
|
|
|
|
},
|
2020-11-24 15:15:51 +05:30
|
|
|
onSearchBoxEnter() {
|
|
|
|
this.debouncedSearch.cancel();
|
2020-07-28 23:09:34 +05:30
|
|
|
this.search(this.query);
|
2020-11-24 15:15:51 +05:30
|
|
|
},
|
|
|
|
onSearchBoxInput() {
|
|
|
|
this.debouncedSearch();
|
|
|
|
},
|
2020-07-28 23:09:34 +05:30
|
|
|
selectRef(ref) {
|
|
|
|
this.setSelectedRef(ref);
|
|
|
|
this.$emit('input', this.selectedRef);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
2020-11-24 15:15:51 +05:30
|
|
|
<gl-dropdown v-bind="$attrs" class="ref-selector" @shown="focusSearchBox">
|
2020-07-28 23:09:34 +05:30
|
|
|
<template slot="button-content">
|
2020-10-24 23:57:45 +05:30
|
|
|
<span class="gl-flex-grow-1 gl-ml-2 gl-text-gray-400" data-testid="button-content">
|
2020-07-28 23:09:34 +05:30
|
|
|
<span v-if="selectedRef" class="gl-font-monospace">{{ selectedRef }}</span>
|
|
|
|
<span v-else>{{ i18n.noRefSelected }}</span>
|
|
|
|
</span>
|
|
|
|
<gl-icon name="chevron-down" />
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<div class="gl-display-flex gl-flex-direction-column ref-selector-dropdown-content">
|
2020-11-24 15:15:51 +05:30
|
|
|
<gl-dropdown-section-header>
|
2020-07-28 23:09:34 +05:30
|
|
|
<span class="gl-text-center gl-display-block">{{ i18n.dropdownHeader }}</span>
|
2020-11-24 15:15:51 +05:30
|
|
|
</gl-dropdown-section-header>
|
2020-07-28 23:09:34 +05:30
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
<gl-dropdown-divider />
|
2020-07-28 23:09:34 +05:30
|
|
|
|
|
|
|
<gl-search-box-by-type
|
|
|
|
ref="searchBox"
|
|
|
|
v-model.trim="query"
|
|
|
|
:placeholder="i18n.searchPlaceholder"
|
|
|
|
@input="onSearchBoxInput"
|
2020-11-24 15:15:51 +05:30
|
|
|
@keydown.enter.prevent="onSearchBoxEnter"
|
2020-07-28 23:09:34 +05:30
|
|
|
/>
|
|
|
|
|
|
|
|
<div class="gl-flex-grow-1 gl-overflow-y-auto">
|
|
|
|
<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"
|
|
|
|
data-testid="branches-section"
|
|
|
|
@selected="selectRef($event)"
|
|
|
|
/>
|
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
<gl-dropdown-divider v-if="showTagsSection || showCommitsSection" />
|
2020-07-28 23:09:34 +05:30
|
|
|
</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"
|
|
|
|
data-testid="tags-section"
|
|
|
|
@selected="selectRef($event)"
|
|
|
|
/>
|
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
<gl-dropdown-divider v-if="showCommitsSection" />
|
2020-07-28 23:09:34 +05:30
|
|
|
</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"
|
|
|
|
data-testid="commits-section"
|
|
|
|
@selected="selectRef($event)"
|
|
|
|
/>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-11-24 15:15:51 +05:30
|
|
|
</gl-dropdown>
|
2020-07-28 23:09:34 +05:30
|
|
|
</template>
|