debian-mirror-gitlab/app/assets/javascripts/ref/components/ref_selector.vue

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

341 lines
8.9 KiB
Vue
Raw Normal View History

2020-07-28 23:09:34 +05:30
<script>
import {
2020-11-24 15:15:51 +05:30
GlDropdown,
GlDropdownDivider,
2020-07-28 23:09:34 +05:30
GlSearchBoxByType,
GlSprintf,
GlLoadingIcon,
} from '@gitlab/ui';
2021-04-17 20:07:23 +05:30
import { debounce, isArray } from 'lodash';
2021-03-11 19:13:27 +05:30
import { mapActions, mapGetters, mapState } from 'vuex';
2021-04-17 20:07:23 +05:30
import {
ALL_REF_TYPES,
SEARCH_DEBOUNCE_MS,
DEFAULT_I18N,
REF_TYPE_BRANCHES,
REF_TYPE_TAGS,
REF_TYPE_COMMITS,
2023-03-17 16:20:25 +05:30
BRANCH_REF_TYPE,
TAG_REF_TYPE,
2021-04-17 20:07:23 +05:30
} 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',
components: {
2020-11-24 15:15:51 +05:30
GlDropdown,
GlDropdownDivider,
2020-07-28 23:09:34 +05:30
GlSearchBoxByType,
GlSprintf,
GlLoadingIcon,
RefResultsSection,
},
2022-11-25 23:54:43 +05:30
inheritAttrs: false,
2020-07-28 23:09:34 +05:30
props: {
2021-04-17 20:07:23 +05:30
enabledRefTypes: {
type: Array,
required: false,
default: () => ALL_REF_TYPES,
validator: (val) =>
// It has to be an arrray
isArray(val) &&
// with at least one item
val.length > 0 &&
// and only "REF_TYPE_BRANCHES", "REF_TYPE_TAGS", and "REF_TYPE_COMMITS" are allowed
val.every((item) => ALL_REF_TYPES.includes(item)) &&
// and no duplicates are allowed
val.length === new Set(val).size,
},
2020-07-28 23:09:34 +05:30
value: {
type: String,
required: false,
default: '',
},
2023-03-17 16:20:25 +05:30
refType: {
type: String,
required: false,
default: null,
},
2020-07-28 23:09:34 +05:30
projectId: {
type: String,
required: true,
},
translations: {
type: Object,
required: false,
default: () => ({}),
},
2022-05-07 20:08:51 +05:30
useSymbolicRefNames: {
type: Boolean,
required: false,
default: false,
},
2021-04-17 20:07:23 +05:30
/** The validation state of this component. */
state: {
type: Boolean,
required: false,
default: true,
},
2022-11-25 23:54:43 +05:30
/* Underlying form field name for scenarios where ref_selector
* is used as part of submitting an HTML form
*/
name: {
type: String,
required: false,
default: '',
},
2020-07-28 23:09:34 +05:30
},
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() {
2021-04-17 20:07:23 +05:30
return (
this.enabledRefTypes.includes(REF_TYPE_BRANCHES) &&
Boolean(this.matches.branches.totalCount > 0 || this.matches.branches.error)
);
2020-07-28 23:09:34 +05:30
},
showTagsSection() {
2021-04-17 20:07:23 +05:30
return (
this.enabledRefTypes.includes(REF_TYPE_TAGS) &&
Boolean(this.matches.tags.totalCount > 0 || this.matches.tags.error)
);
2020-07-28 23:09:34 +05:30
},
showCommitsSection() {
2021-04-17 20:07:23 +05:30
return (
this.enabledRefTypes.includes(REF_TYPE_COMMITS) &&
Boolean(this.matches.commits.totalCount > 0 || this.matches.commits.error)
);
2020-07-28 23:09:34 +05:30
},
showNoResults() {
return !this.showBranchesSection && !this.showTagsSection && !this.showCommitsSection;
},
2021-04-17 20:07:23 +05:30
showSectionHeaders() {
return this.enabledRefTypes.length > 1;
},
toggleButtonClass() {
return {
'gl-inset-border-1-red-500!': !this.state,
'gl-font-monospace': Boolean(this.selectedRef),
};
},
footerSlotProps() {
return {
isLoading: this.isLoading,
matches: this.matches,
query: this.lastQuery,
};
},
2022-05-07 20:08:51 +05:30
selectedRefForDisplay() {
if (this.useSymbolicRefNames && this.selectedRef) {
return this.selectedRef.replace(/^refs\/(tags|heads)\//, '');
}
return this.selectedRef;
},
2021-04-17 20:07:23 +05:30
buttonText() {
2022-05-07 20:08:51 +05:30
return this.selectedRefForDisplay || this.i18n.noRefSelected;
2021-04-17 20:07:23 +05:30
},
2023-03-17 16:20:25 +05:30
isTagRefType() {
return this.refType === TAG_REF_TYPE;
},
isBranchRefType() {
return this.refType === BRANCH_REF_TYPE;
},
2020-07-28 23:09:34 +05:30
},
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);
}
},
},
},
2021-04-17 20:07:23 +05:30
beforeCreate() {
// Setting the store here instead of using
// the built in `store` component option because
// we need each new `RefSelector` instance to
// create a new Vuex store instance.
// See https://github.com/vuejs/vuex/issues/414#issue-184491718.
this.$store = createStore();
},
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
2021-11-11 11:23:49 +05:30
// made inaccessible by Vue.
2020-11-24 15:15:51 +05:30
this.debouncedSearch = debounce(function search() {
2021-04-17 20:07:23 +05:30
this.search();
2020-11-24 15:15:51 +05:30
}, SEARCH_DEBOUNCE_MS);
2020-07-28 23:09:34 +05:30
this.setProjectId(this.projectId);
2021-04-17 20:07:23 +05:30
this.$watch(
'enabledRefTypes',
() => {
this.setEnabledRefTypes(this.enabledRefTypes);
this.search();
},
{ immediate: true },
);
2022-05-07 20:08:51 +05:30
this.$watch(
'useSymbolicRefNames',
() => this.setUseSymbolicRefNames(this.useSymbolicRefNames),
{ immediate: true },
);
2020-07-28 23:09:34 +05:30
},
methods: {
2022-05-07 20:08:51 +05:30
...mapActions([
'setEnabledRefTypes',
'setUseSymbolicRefNames',
'setProjectId',
'setSelectedRef',
]),
2021-04-17 20:07:23 +05:30
...mapActions({ storeSearch: 'search' }),
2020-07-28 23:09:34 +05:30
focusSearchBox() {
this.$refs.searchBox.$el.querySelector('input').focus();
},
2020-11-24 15:15:51 +05:30
onSearchBoxEnter() {
this.debouncedSearch.cancel();
2021-04-17 20:07:23 +05:30
this.search();
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);
},
2021-04-17 20:07:23 +05:30
search() {
this.storeSearch(this.query);
},
2020-07-28 23:09:34 +05:30
},
};
</script>
<template>
2022-11-25 23:54:43 +05:30
<div>
<gl-dropdown
:header-text="i18n.dropdownHeader"
:toggle-class="toggleButtonClass"
:text="buttonText"
class="ref-selector gl-w-full"
v-bind="$attrs"
v-on="$listeners"
@shown="focusSearchBox"
>
<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>
2020-07-28 23:09:34 +05:30
2022-11-25 23:54:43 +05:30
<gl-loading-icon v-if="isLoading" size="lg" class="gl-my-3" />
2020-07-28 23:09:34 +05:30
2022-11-25 23:54:43 +05:30
<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>
2020-07-28 23:09:34 +05:30
2022-11-25 23:54:43 +05:30
<span v-else>{{ i18n.noResults }}</span>
</div>
2020-07-28 23:09:34 +05:30
2022-11-25 23:54:43 +05:30
<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"
2023-03-17 16:20:25 +05:30
:should-show-check="!useSymbolicRefNames || isBranchRefType"
2022-11-25 23:54:43 +05:30
@selected="selectRef($event)"
/>
2020-07-28 23:09:34 +05:30
2022-11-25 23:54:43 +05:30
<gl-dropdown-divider v-if="showTagsSection || showCommitsSection" />
</template>
2020-07-28 23:09:34 +05:30
2022-11-25 23:54:43 +05:30
<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"
2023-03-17 16:20:25 +05:30
:should-show-check="!useSymbolicRefNames || isTagRefType"
2022-11-25 23:54:43 +05:30
@selected="selectRef($event)"
/>
2020-07-28 23:09:34 +05:30
2022-11-25 23:54:43 +05:30
<gl-dropdown-divider v-if="showCommitsSection" />
</template>
2020-07-28 23:09:34 +05:30
2022-11-25 23:54:43 +05:30
<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>
2021-04-17 20:07:23 +05:30
</template>
2022-11-25 23:54:43 +05:30
<template #footer>
<slot name="footer" v-bind="footerSlotProps"></slot>
</template>
</gl-dropdown>
<input
v-if="name"
data-testid="selected-ref-form-field"
type="hidden"
:value="selectedRef"
:name="name"
/>
</div>
2020-07-28 23:09:34 +05:30
</template>