summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/access_tokens/components/projects_token_selector.vue
diff options
context:
space:
mode:
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.vue156
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>