1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
<script>
import { GlAvatarLabeled, GlCollapsibleListbox } from '@gitlab/ui';
import { debounce } from 'lodash';
import { __ } from '~/locale';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
import { PROJECTS_PER_PAGE } from '../constants';
import getProjectsQuery from '../graphql/queries/get_projects.query.graphql';
export default {
PROJECTS_PER_PAGE,
projectQueryPageInfo: {
endCursor: '',
},
components: {
GlAvatarLabeled,
GlCollapsibleListbox,
},
props: {
selectedProject: {
type: Object,
required: false,
default: null,
},
},
data() {
return {
initialProjectsLoading: true,
projectSearchQuery: '',
selectedProjectId: this.selectedProject?.id,
};
},
apollo: {
projects: {
query: getProjectsQuery,
variables() {
return {
search: this.projectSearchQuery,
first: this.$options.PROJECTS_PER_PAGE,
after: this.$options.projectQueryPageInfo.endCursor,
searchNamespaces: true,
sort: 'similarity',
};
},
update(data) {
return data?.projects?.nodes.filter((project) => !project.repository?.empty) ?? [];
},
result() {
this.initialProjectsLoading = false;
},
error() {
this.onError({ message: __('Failed to load projects') });
},
},
},
computed: {
projectsLoading() {
return Boolean(this.$apollo.queries.projects.loading);
},
projectDropdownText() {
return this.selectedProject?.nameWithNamespace || this.$options.i18n.selectProjectText;
},
projectList() {
return (this.projects || []).map((project) => ({
...project,
text: project.nameWithNamespace,
value: String(project.id),
}));
},
},
methods: {
findProjectById(id) {
return this.projects.find((project) => id === project.id);
},
onProjectSelect(projectId) {
this.$emit('change', this.findProjectById(projectId));
},
onError({ message } = {}) {
this.$emit('error', { message });
},
onSearch: debounce(function debouncedSearch(query) {
this.projectSearchQuery = query;
}, 250),
},
i18n: {
selectProjectText: __('Select a project'),
},
AVATAR_SHAPE_OPTION_RECT,
};
</script>
<template>
<gl-collapsible-listbox
v-model="selectedProjectId"
data-testid="project-select"
:items="projectList"
:toggle-text="projectDropdownText"
:header-text="$options.i18n.selectProjectText"
:loading="initialProjectsLoading"
:searchable="true"
:searching="projectsLoading"
@search="onSearch"
@select="onProjectSelect"
>
<template #list-item="{ item: project }">
<gl-avatar-labeled
v-if="project"
class="gl-text-truncate"
:shape="$options.AVATAR_SHAPE_OPTION_RECT"
:size="32"
:src="project.avatarUrl"
:label="project.name"
:entity-name="project.name"
:sub-label="project.nameWithNamespace"
/>
</template>
</gl-collapsible-listbox>
</template>
|