Files
speckle-server/packages/frontend-2/components/workspace/ProjectList.vue
T
2024-08-23 17:54:20 +02:00

158 lines
4.6 KiB
Vue

<template>
<div>
<Portal to="navigation">
<HeaderNavLink :to="workspacesRoute" name="Workspaces" :separator="false" />
<HeaderNavLink :to="workspaceRoute(workspaceId)" :name="workspace?.name" />
</Portal>
<WorkspaceHeader
v-if="workspace"
:icon="Squares2X2Icon"
:workspace-info="workspace"
/>
<div class="flex flex-col gap-4 mt-4">
<div class="flex flex-row gap-2 sm:items-center justify-between">
<FormTextInput
name="modelsearch"
:show-label="false"
placeholder="Search..."
:custom-icon="MagnifyingGlassIcon"
color="foundation"
wrapper-classes="grow md:grow-0 md:w-60"
show-clear
v-bind="bind"
v-on="on"
/>
<FormButton v-if="!isWorkspaceGuest" @click="openNewProject = true">
New project
</FormButton>
</div>
</div>
<CommonLoadingBar :loading="showLoadingBar" class="my-2" />
<ProjectsDashboardEmptyState
v-if="showEmptyState"
:is-guest="isWorkspaceGuest"
@create-project="openNewProject = true"
/>
<template v-else-if="projects?.items?.length">
<ProjectsDashboardFilled :projects="projects" />
<InfiniteLoading :settings="{ identifier }" @infinite="onInfiniteLoad" />
</template>
<CommonEmptySearchState v-else-if="!showLoadingBar" @clear-search="clearSearch" />
<ProjectsAddDialog v-model:open="openNewProject" :workspace-id="workspaceId" />
</div>
</template>
<script setup lang="ts">
import { MagnifyingGlassIcon, Squares2X2Icon } from '@heroicons/vue/24/outline'
import { useQuery, useQueryLoading } from '@vue/apollo-composable'
import type { Optional, StreamRoles } from '@speckle/shared'
import {
workspacePageQuery,
workspaceProjectsQuery
} from '~~/lib/workspaces/graphql/queries'
import { useDebouncedTextInput } from '@speckle/ui-components'
import { usePaginatedQuery } from '~/lib/common/composables/graphql'
import { graphql } from '~~/lib/common/generated/gql'
import type {
WorkspaceProjectList_ProjectCollectionFragment,
WorkspaceProjectsQueryQueryVariables
} from '~~/lib/common/generated/gql/graphql'
import { skipLoggingErrorsIfOneFieldError } from '~/lib/common/helpers/graphql'
import { workspaceRoute, workspacesRoute } from '~/lib/common/helpers/route'
import { Roles } from '@speckle/shared'
graphql(`
fragment WorkspaceProjectList_ProjectCollection on ProjectCollection {
totalCount
items {
...ProjectDashboardItem
}
cursor
}
`)
const selectedRoles = ref(undefined as Optional<StreamRoles[]>)
const openNewProject = ref(false)
const areQueriesLoading = useQueryLoading()
const props = defineProps<{
workspaceId: string
}>()
const {
on,
bind,
value: search
} = useDebouncedTextInput({
debouncedBy: 800
})
const { result: initialQueryResult } = useQuery(
workspacePageQuery,
{
workspaceId: props.workspaceId,
filter: {
search: (search.value || '').trim() || null
}
},
() => ({
// Custom error policy so that a failing invitedTeam resolver (due to access rights)
// doesn't kill the entire query
errorPolicy: 'all',
context: {
skipLoggingErrors: skipLoggingErrorsIfOneFieldError('invitedTeam')
}
})
)
const { query, identifier, onInfiniteLoad } = usePaginatedQuery<
{ workspace: { projects: WorkspaceProjectList_ProjectCollectionFragment } },
WorkspaceProjectsQueryQueryVariables
>({
query: workspaceProjectsQuery,
baseVariables: computed(() => ({
workspaceId: props.workspaceId,
filter: {
search: (search.value || '').trim() || null
}
})),
resolveKey: (vars: WorkspaceProjectsQueryQueryVariables) => ({
workspaceId: vars.workspaceId,
search: vars.filter?.search || ''
}),
resolveInitialResult: () => initialQueryResult.value?.workspace.projects,
resolveCurrentResult: (result) => result?.workspace?.projects,
resolveNextPageVariables: (baseVariables, newCursor) => ({
...baseVariables,
cursor: newCursor
}),
resolveCursorFromVariables: (vars) => vars.cursor
})
const workspace = computed(() => initialQueryResult.value?.workspace)
const projects = computed(() => query.result.value?.workspace?.projects)
const showEmptyState = computed(() => {
if (search.value) return false
return projects.value && !projects.value?.items?.length
})
const showLoadingBar = computed(() => {
return areQueriesLoading.value && (!!search.value || !projects.value?.items?.length)
})
const isWorkspaceGuest = computed(() => workspace.value?.role === Roles.Workspace.Guest)
const clearSearch = () => {
search.value = ''
selectedRoles.value = []
}
</script>