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/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue')
-rw-r--r--app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue177
1 files changed, 177 insertions, 0 deletions
diff --git a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue
new file mode 100644
index 00000000000..c1939dc7785
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue
@@ -0,0 +1,177 @@
+<script>
+import { GlDropdownForm, GlDropdownItem, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui';
+import fuzzaldrinPlus from 'fuzzaldrin-plus';
+import { createAlert } from '~/flash';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { __ } from '~/locale';
+import { workspaceLabelsQueries } from '../../../constants';
+import LabelItem from './label_item.vue';
+
+export default {
+ components: {
+ GlDropdownForm,
+ GlDropdownItem,
+ GlLoadingIcon,
+ GlIntersectionObserver,
+ LabelItem,
+ },
+ model: {
+ prop: 'localSelectedLabels',
+ },
+ props: {
+ allowMultiselect: {
+ type: Boolean,
+ required: true,
+ },
+ localSelectedLabels: {
+ type: Array,
+ required: true,
+ },
+ fullPath: {
+ type: String,
+ required: true,
+ },
+ searchKey: {
+ type: String,
+ required: true,
+ },
+ workspaceType: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ labels: [],
+ isVisible: false,
+ };
+ },
+ apollo: {
+ labels: {
+ query() {
+ return workspaceLabelsQueries[this.workspaceType].query;
+ },
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ searchTerm: this.searchKey,
+ };
+ },
+ skip() {
+ return this.searchKey.length === 1 || !this.isVisible;
+ },
+ update: (data) => data.workspace?.labels?.nodes || [],
+ error() {
+ createAlert({ message: __('Error fetching labels.') });
+ },
+ },
+ },
+ computed: {
+ labelsFetchInProgress() {
+ return this.$apollo.queries.labels.loading;
+ },
+ localSelectedLabelsIds() {
+ return this.localSelectedLabels.map((label) => getIdFromGraphQLId(label.id));
+ },
+ visibleLabels() {
+ if (this.searchKey) {
+ return fuzzaldrinPlus.filter(this.labels, this.searchKey, {
+ key: ['title'],
+ });
+ }
+ return this.labels;
+ },
+ showNoMatchingResultsMessage() {
+ return Boolean(this.searchKey) && this.visibleLabels.length === 0;
+ },
+ shouldHighlightFirstItem() {
+ return this.searchKey !== '' && this.visibleLabels.length > 0;
+ },
+ },
+ methods: {
+ isLabelSelected(label) {
+ return this.localSelectedLabelsIds.includes(getIdFromGraphQLId(label.id));
+ },
+ /**
+ * This method scrolls item from dropdown into
+ * the view if it is off the viewable area of the
+ * container.
+ */
+ scrollIntoViewIfNeeded() {
+ const highlightedLabel = this.$refs.labelsListContainer.querySelector('.is-focused');
+
+ if (highlightedLabel) {
+ const container = this.$refs.labelsListContainer.getBoundingClientRect();
+ const label = highlightedLabel.getBoundingClientRect();
+
+ if (label.bottom > container.bottom) {
+ this.$refs.labelsListContainer.scrollTop += label.bottom - container.bottom;
+ } else if (label.top < container.top) {
+ this.$refs.labelsListContainer.scrollTop -= container.top - label.top;
+ }
+ }
+ },
+ updateSelectedLabels(label) {
+ let labels;
+ if (this.isLabelSelected(label)) {
+ labels = this.localSelectedLabels.filter(
+ ({ id }) => id !== getIdFromGraphQLId(label.id) && id !== label.id,
+ );
+ } else {
+ labels = [...this.localSelectedLabels, label];
+ }
+ this.$emit('input', labels);
+ },
+ handleLabelClick(label) {
+ this.updateSelectedLabels(label);
+ if (!this.allowMultiselect) {
+ this.$emit('closeDropdown', this.localSelectedLabels);
+ }
+ },
+ onDropdownAppear() {
+ this.isVisible = true;
+ },
+ selectFirstItem() {
+ if (this.shouldHighlightFirstItem) {
+ this.handleLabelClick(this.visibleLabels[0]);
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-intersection-observer @appear="onDropdownAppear">
+ <gl-dropdown-form class="labels-select-contents-list js-labels-list">
+ <div ref="labelsListContainer" data-testid="dropdown-content">
+ <gl-loading-icon
+ v-if="labelsFetchInProgress"
+ class="labels-fetch-loading gl-align-items-center gl-w-full gl-h-full gl-mb-3"
+ size="lg"
+ />
+ <template v-else>
+ <gl-dropdown-item
+ v-for="(label, index) in visibleLabels"
+ :key="label.id"
+ :is-checked="isLabelSelected(label)"
+ is-check-centered
+ is-check-item
+ :active="shouldHighlightFirstItem && index === 0"
+ active-class="is-focused"
+ data-testid="labels-list"
+ @click.native.capture.stop="handleLabelClick(label)"
+ >
+ <label-item :label="label" />
+ </gl-dropdown-item>
+ <gl-dropdown-item
+ v-show="showNoMatchingResultsMessage"
+ class="gl-p-3 gl-text-center"
+ data-testid="no-results"
+ >
+ {{ __('No matching results') }}
+ </gl-dropdown-item>
+ </template>
+ </div>
+ </gl-dropdown-form>
+ </gl-intersection-observer>
+</template>