diff options
Diffstat (limited to 'app/assets/javascripts/access_tokens/components/projects_token_selector.vue')
-rw-r--r-- | app/assets/javascripts/access_tokens/components/projects_token_selector.vue | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/app/assets/javascripts/access_tokens/components/projects_token_selector.vue b/app/assets/javascripts/access_tokens/components/projects_token_selector.vue new file mode 100644 index 00000000000..a746f62b3a1 --- /dev/null +++ b/app/assets/javascripts/access_tokens/components/projects_token_selector.vue @@ -0,0 +1,156 @@ +<script> +import { + GlTokenSelector, + GlAvatar, + GlAvatarLabeled, + GlIntersectionObserver, + GlLoadingIcon, +} from '@gitlab/ui'; +import produce from 'immer'; + +import { convertToGraphQLIds, convertNodeIdsFromGraphQLIds } from '~/graphql_shared/utils'; + +import getProjectsQuery from '../graphql/queries/get_projects.query.graphql'; + +const DEBOUNCE_DELAY = 250; +const PROJECTS_PER_PAGE = 20; +const GRAPHQL_ENTITY_TYPE = 'Project'; + +export default { + name: 'ProjectsTokenSelector', + components: { + GlTokenSelector, + GlAvatar, + GlAvatarLabeled, + GlIntersectionObserver, + GlLoadingIcon, + }, + model: { + prop: 'selectedProjects', + }, + props: { + selectedProjects: { + type: Array, + required: true, + }, + initialProjectIds: { + type: Array, + required: true, + }, + }, + apollo: { + projects: { + query: getProjectsQuery, + debounce: DEBOUNCE_DELAY, + variables() { + return { + search: this.searchQuery, + after: null, + first: PROJECTS_PER_PAGE, + }; + }, + update({ projects }) { + return { + list: convertNodeIdsFromGraphQLIds(projects.nodes), + pageInfo: projects.pageInfo, + }; + }, + result() { + this.isLoadingMoreProjects = false; + this.isSearching = false; + }, + }, + initialProjects: { + query: getProjectsQuery, + variables() { + return { + ids: convertToGraphQLIds(GRAPHQL_ENTITY_TYPE, this.initialProjectIds), + }; + }, + manual: true, + skip() { + return !this.initialProjectIds.length; + }, + result({ data: { projects } }) { + this.$emit('input', convertNodeIdsFromGraphQLIds(projects.nodes)); + }, + }, + }, + data() { + return { + projects: { + list: [], + pageInfo: {}, + }, + searchQuery: '', + isLoadingMoreProjects: false, + isSearching: false, + }; + }, + methods: { + handleSearch(query) { + this.isSearching = true; + this.searchQuery = query; + }, + loadMoreProjects() { + this.isLoadingMoreProjects = true; + + this.$apollo.queries.projects.fetchMore({ + variables: { + after: this.projects.pageInfo.endCursor, + first: PROJECTS_PER_PAGE, + }, + updateQuery(previousResult, { fetchMoreResult: { projects: newProjects } }) { + const { projects: previousProjects } = previousResult; + + return produce(previousResult, (draftData) => { + draftData.projects.nodes = [...previousProjects.nodes, ...newProjects.nodes]; + draftData.projects.pageInfo = newProjects.pageInfo; + }); + }, + }); + }, + }, +}; +</script> + +<template> + <div class="gl-relative"> + <gl-token-selector + :selected-tokens="selectedProjects" + :dropdown-items="projects.list" + :loading="isSearching" + :placeholder="__('Select projects')" + menu-class="gl-w-full! gl-max-w-full!" + @input="$emit('input', $event)" + @focus="$emit('focus', $event)" + @text-input="handleSearch" + @keydown.enter.prevent + > + <template #token-content="{ token: project }"> + <gl-avatar + :entity-id="project.id" + :entity-name="project.name" + :src="project.avatarUrl" + :size="16" + /> + {{ project.nameWithNamespace }} + </template> + <template #dropdown-item-content="{ dropdownItem: project }"> + <gl-avatar-labeled + :entity-id="project.id" + :entity-name="project.name" + :size="32" + :src="project.avatarUrl" + :label="project.name" + :sub-label="project.nameWithNamespace" + /> + </template> + <template #dropdown-footer> + <gl-intersection-observer v-if="projects.pageInfo.hasNextPage" @appear="loadMoreProjects"> + <gl-loading-icon v-if="isLoadingMoreProjects" size="md" /> + </gl-intersection-observer> + </template> + </gl-token-selector> + </div> +</template> |