diff options
Diffstat (limited to 'app/assets/javascripts/boards/components/project_select.vue')
-rw-r--r-- | app/assets/javascripts/boards/components/project_select.vue | 193 |
1 files changed, 107 insertions, 86 deletions
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index 9c90938fc52..04699d0d3a4 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -1,120 +1,141 @@ <script> -import $ from 'jquery'; -import { escape } from 'lodash'; -import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; -import { __ } from '~/locale'; -import eventHub from '../eventhub'; -import Api from '../../api'; +import { mapActions, mapState } from 'vuex'; +import { + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlSearchBoxByType, + GlIntersectionObserver, + GlLoadingIcon, +} from '@gitlab/ui'; +import { s__ } from '~/locale'; import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; -import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; import { ListType } from '../constants'; export default { - name: 'BoardProjectSelect', + name: 'ProjectSelect', + i18n: { + headerTitle: s__(`BoardNewIssue|Projects`), + dropdownText: s__(`BoardNewIssue|Select a project`), + searchPlaceholder: s__(`BoardNewIssue|Search projects`), + emptySearchResult: s__(`BoardNewIssue|No matching results`), + }, + defaultFetchOptions: { + with_issues_enabled: true, + with_shared: false, + include_subgroups: true, + order_by: 'similarity', + }, components: { - GlIcon, + GlIntersectionObserver, GlLoadingIcon, + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlSearchBoxByType, }, + inject: ['groupId'], props: { list: { type: Object, required: true, }, }, - inject: ['groupId'], data() { return { - loading: true, + initialLoading: true, selectedProject: {}, + searchTerm: '', }; }, computed: { + ...mapState(['groupProjects', 'groupProjectsFlags']), selectedProjectName() { - return this.selectedProject.name || __('Select a project'); + return this.selectedProject.name || this.$options.i18n.dropdownText; + }, + fetchOptions() { + const additionalAttrs = {}; + if (this.list.type && this.list.type !== ListType.backlog) { + additionalAttrs.min_access_level = featureAccessLevel.EVERYONE; + } + + return { + ...this.$options.defaultFetchOptions, + ...additionalAttrs, + }; + }, + isFetchResultEmpty() { + return this.groupProjects.length === 0; + }, + hasNextPage() { + return this.groupProjectsFlags.pageInfo?.hasNextPage; + }, + }, + watch: { + searchTerm() { + this.fetchGroupProjects({ search: this.searchTerm }); }, }, mounted() { - initDeprecatedJQueryDropdown($(this.$refs.projectsDropdown), { - filterable: true, - filterRemote: true, - search: { - fields: ['name_with_namespace'], - }, - clicked: ({ $el, e }) => { - e.preventDefault(); - this.selectedProject = { - id: $el.data('project-id'), - name: $el.data('project-name'), - path: $el.data('project-path'), - }; - eventHub.$emit('setSelectedProject', this.selectedProject); - }, - selectable: true, - data: (term, callback) => { - this.loading = true; - const additionalAttrs = {}; + this.fetchGroupProjects({}); - if ((this.list.type || this.list.listType) !== ListType.backlog) { - additionalAttrs.min_access_level = featureAccessLevel.EVERYONE; - } - - return Api.groupProjects( - this.groupId, - term, - { - with_issues_enabled: true, - with_shared: false, - include_subgroups: true, - order_by: 'similarity', - ...additionalAttrs, - }, - projects => { - this.loading = false; - callback(projects); - }, - ); - }, - renderRow(project) { - return ` - <li> - <a href='#' class='dropdown-menu-link' - data-project-id="${project.id}" - data-project-name="${project.name}" - data-project-name-with-namespace="${project.name_with_namespace}" - data-project-path="${project.path_with_namespace}" - > - ${escape(project.name_with_namespace)} - </a> - </li> - `; - }, - text: project => project.name_with_namespace, - }); + this.initialLoading = false; + }, + methods: { + ...mapActions(['fetchGroupProjects', 'setSelectedProject']), + selectProject(projectId) { + this.selectedProject = this.groupProjects.find((project) => project.id === projectId); + this.setSelectedProject(this.selectedProject); + }, + loadMoreProjects() { + this.fetchGroupProjects({ search: this.searchTerm, fetchNext: true }); + }, }, }; </script> <template> <div> - <label class="label-bold gl-mt-3">{{ __('Project') }}</label> - <div ref="projectsDropdown" class="dropdown dropdown-projects"> - <button - class="dropdown-menu-toggle wide" - type="button" - data-toggle="dropdown" - aria-expanded="false" + <label class="gl-font-weight-bold gl-mt-3" data-testid="header-label">{{ + $options.i18n.headerTitle + }}</label> + <gl-dropdown + data-testid="project-select-dropdown" + :text="selectedProjectName" + :header-text="$options.i18n.headerTitle" + block + menu-class="gl-w-full!" + :loading="initialLoading" + > + <gl-search-box-by-type + v-model.trim="searchTerm" + debounce="250" + :placeholder="$options.i18n.searchPlaceholder" + /> + <gl-dropdown-item + v-for="project in groupProjects" + v-show="!groupProjectsFlags.isLoading" + :key="project.id" + :name="project.name" + @click="selectProject(project.id)" + > + {{ project.nameWithNamespace }} + </gl-dropdown-item> + <gl-dropdown-text + v-show="groupProjectsFlags.isLoading" + data-testid="dropdown-text-loading-icon" + > + <gl-loading-icon class="gl-mx-auto" /> + </gl-dropdown-text> + <gl-dropdown-text + v-if="isFetchResultEmpty && !groupProjectsFlags.isLoading" + data-testid="empty-result-message" > - {{ selectedProjectName }} <gl-icon name="chevron-down" class="dropdown-menu-toggle-icon" /> - </button> - <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width"> - <div class="dropdown-title">{{ __('Projects') }}</div> - <div class="dropdown-input"> - <input class="dropdown-input-field" type="search" :placeholder="__('Search projects')" /> - <gl-icon name="search" class="dropdown-input-search" data-hidden="true" /> - </div> - <div class="dropdown-content"></div> - <div class="dropdown-loading"><gl-loading-icon /></div> - </div> - </div> + <span class="gl-text-gray-500">{{ $options.i18n.emptySearchResult }}</span> + </gl-dropdown-text> + <gl-intersection-observer v-if="hasNextPage" @appear="loadMoreProjects"> + <gl-loading-icon v-if="groupProjectsFlags.isLoadingMore" size="md" /> + </gl-intersection-observer> + </gl-dropdown> </div> </template> |