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.

282 lines
7.3 KiB
Vue
Raw Normal View History

2020-07-28 23:09:34 +05:30
<script>
2023-04-23 21:23:45 +05:30
import { GlBadge, GlIcon, GlCollapsibleListbox } 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';
2023-04-23 21:23:45 +05:30
import { sprintf } from '~/locale';
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,
} from '../constants';
2021-03-11 19:13:27 +05:30
import createStore from '../stores';
2023-04-23 21:23:45 +05:30
import { formatListBoxItems, formatErrors } from '../format_refs';
2020-07-28 23:09:34 +05:30
export default {
name: 'RefSelector',
components: {
2023-04-23 21:23:45 +05:30
GlBadge,
GlIcon,
GlCollapsibleListbox,
2020-07-28 23:09:34 +05:30
},
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: '',
},
2023-04-23 21:23:45 +05:30
toggleButtonClass: {
type: [String, Object, Array],
required: false,
default: null,
},
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,
};
},
2023-04-23 21:23:45 +05:30
listBoxItems() {
return formatListBoxItems(this.branches, this.tags, this.commits);
2020-07-28 23:09:34 +05:30
},
2023-04-23 21:23:45 +05:30
branches() {
return this.enabledRefTypes.includes(REF_TYPE_BRANCHES) ? this.matches.branches.list : [];
2020-07-28 23:09:34 +05:30
},
2023-04-23 21:23:45 +05:30
tags() {
return this.enabledRefTypes.includes(REF_TYPE_TAGS) ? this.matches.tags.list : [];
2020-07-28 23:09:34 +05:30
},
2023-04-23 21:23:45 +05:30
commits() {
return this.enabledRefTypes.includes(REF_TYPE_COMMITS) ? this.matches.commits.list : [];
2020-07-28 23:09:34 +05:30
},
2023-04-23 21:23:45 +05:30
extendedToggleButtonClass() {
const classes = [
{
'gl-inset-border-1-red-500!': !this.state,
'gl-font-monospace': Boolean(this.selectedRef),
},
'gl-mb-0',
];
if (Array.isArray(this.toggleButtonClass)) {
classes.push(...this.toggleButtonClass);
} else {
classes.push(this.toggleButtonClass);
}
return classes;
2021-04-17 20:07:23 +05:30
},
footerSlotProps() {
return {
isLoading: this.isLoading,
matches: this.matches,
query: this.lastQuery,
};
},
2023-04-23 21:23:45 +05:30
errors() {
return formatErrors(this.matches.branches, this.matches.tags, this.matches.commits);
},
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-04-23 21:23:45 +05:30
noResultsMessage() {
return this.lastQuery
? sprintf(this.i18n.noResultsWithQuery, {
query: this.lastQuery,
})
: this.i18n.noResults;
2023-03-17 16:20:25 +05:30
},
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.
2023-04-23 21:23:45 +05:30
this.debouncedSearch = debounce(this.search, SEARCH_DEBOUNCE_MS);
2020-11-24 15:15:51 +05:30
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' }),
2023-04-23 21:23:45 +05:30
onSearchBoxInput(searchQuery = '') {
this.query = searchQuery?.trim();
2020-11-24 15:15:51 +05:30
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);
},
2023-04-23 21:23:45 +05:30
totalCountText(count) {
return count > 999 ? this.i18n.totalCountLabel : `${count}`;
},
2020-07-28 23:09:34 +05:30
},
};
</script>
<template>
2022-11-25 23:54:43 +05:30
<div>
2023-04-23 21:23:45 +05:30
<gl-collapsible-listbox
2022-11-25 23:54:43 +05:30
class="ref-selector gl-w-full"
2023-04-23 21:23:45 +05:30
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"
2022-11-25 23:54:43 +05:30
v-bind="$attrs"
v-on="$listeners"
2023-04-23 21:23:45 +05:30
@hidden="$emit('hide')"
@search="onSearchBoxInput"
@select="selectRef"
2022-11-25 23:54:43 +05:30
>
2023-04-23 21:23:45 +05:30
<template #group-label="{ group }">
{{ group.text }} <gl-badge size="sm">{{ totalCountText(group.options.length) }}</gl-badge>
2022-11-25 23:54:43 +05:30
</template>
2023-04-23 21:23:45 +05:30
<template #list-item="{ item }">
{{ item.text }}
<gl-badge v-if="item.default" size="sm" variant="info">{{
i18n.defaultLabelText
}}</gl-badge>
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>
2023-04-23 21:23:45 +05:30
<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>
2022-11-25 23:54:43 +05:30
</template>
2023-04-23 21:23:45 +05:30
</gl-collapsible-listbox>
2022-11-25 23:54:43 +05:30
<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>