Files
speckle-server/packages/frontend-2/components/header/WorkspaceSwitcher/WorkspaceSwitcher.vue
T
Mike Tasset 60a6f6bc39 Merged main
2025-07-24 15:06:39 +02:00

192 lines
6.6 KiB
Vue

<template>
<div class="inline-block">
<Menu as="div" class="flex items-center">
<MenuButton :id="menuButtonId" v-slot="{ open: userOpen }" class="w-full">
<span class="sr-only">Open workspace menu</span>
<div class="flex items-center gap-2 p-0.5 pr-1.5 hover:bg-highlight-2 rounded">
<div class="relative">
<WorkspaceAvatar
:name="activeWorkspace?.name || 'Personal projects'"
:logo="activeWorkspace?.logo"
/>
<div
v-if="hasDiscoverableWorkspaces"
class="absolute -top-[4px] -right-[4px] size-3 border-[2px] border-foundation-page bg-danger rounded-full"
/>
</div>
<p class="text-body-xs text-foreground truncate max-w-40">
{{ activeWorkspace?.name || 'Personal projects' }}
</p>
<ChevronDownIcon
:class="userOpen ? 'rotate-180' : ''"
class="h-3 w-3 flex-shrink-0"
/>
</div>
</MenuButton>
<Transition
enter-active-class="transition ease-out duration-200"
enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95"
>
<MenuItems
class="absolute left-2 lg:left-3 top-[3.2rem] lg:top-14 w-[17rem] origin-top-right bg-foundation outline outline-1 outline-primary-muted rounded-md shadow-lg overflow-hidden"
>
<HeaderWorkspaceSwitcherHeaderProjects v-if="!activeWorkspace" />
<HeaderWorkspaceSwitcherHeaderSsoExpired
v-else-if="ssoExpiredWorkspace"
:workspace="ssoExpiredWorkspace"
/>
<HeaderWorkspaceSwitcherHeaderWorkspace
v-else-if="activeWorkspace.role"
:workspace="fullActiveWorkspace"
@open-invite-dialog="isInviteDialogOpen = true"
/>
<HeaderWorkspaceSwitcherHeader
v-else
:name="activeWorkspace?.name"
:logo="activeWorkspace?.logo"
:to="workspaceRoute(activeWorkspace?.slug)"
>
<p class="text-body-xs text-foreground-2">
You are not part of this workspace.
</p>
</HeaderWorkspaceSwitcherHeader>
<HeaderWorkspaceSwitcherList class="border-t border-outline-2" />
<MenuItem v-if="hasDiscoverableWorkspacesOrJoinRequests">
<div class="p-2 border-t border-outline-2">
<NuxtLink
class="flex justify-between items-center cursor-pointer hover:bg-highlight-1 py-1 px-2 rounded"
@click="showDiscoverableWorkspacesModal = true"
>
<p class="text-body-xs text-foreground">Join existing workspaces</p>
<div class="relative">
<CommonBadge v-if="hasDiscoverableWorkspacesOrJoinRequests" rounded>
{{ discoverableWorkspacesAndJoinRequestsCount }}
</CommonBadge>
<div
v-if="hasDiscoverableWorkspaces"
class="absolute -top-[4px] -right-[4px] size-3 border-[2px] border-foundation-page bg-danger rounded-full"
/>
</div>
</NuxtLink>
</div>
</MenuItem>
</MenuItems>
</Transition>
</Menu>
<InviteDialogWorkspace
v-model:open="isInviteDialogOpen"
:workspace="fullActiveWorkspace"
/>
<WorkspaceDiscoverableWorkspacesModal
v-model:open="showDiscoverableWorkspacesModal"
/>
</div>
</template>
<script setup lang="ts">
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
import { useQuery } from '@vue/apollo-composable'
import {
navigationWorkspaceSwitcherQuery,
workspaceSwitcherHeaderWorkspaceQuery
} from '~/lib/navigation/graphql/queries'
import { ChevronDownIcon } from '@heroicons/vue/24/outline'
import { WorkspaceJoinRequestStatus } from '~/lib/common/generated/gql/graphql'
import { graphql } from '~/lib/common/generated/gql'
import { workspaceRoute } from '~/lib/common/helpers/route'
graphql(`
fragment WorkspaceSwitcherActiveWorkspace_LimitedWorkspace on LimitedWorkspace {
id
name
logo
slug
role
}
`)
graphql(`
fragment WorkspaceSwitcherActiveWorkspace_User on User {
id
activeWorkspace {
...WorkspaceSwitcherActiveWorkspace_LimitedWorkspace
}
expiredSsoSessions {
id
...HeaderWorkspaceSwitcherHeaderExpiredSso_LimitedWorkspace
}
discoverableWorkspaces {
id
}
workspaceJoinRequests(filter: $joinRequestFilter) {
totalCount
}
}
`)
const { $intercom } = useNuxtApp()
const isWorkspacesEnabled = useIsWorkspacesEnabled()
const menuButtonId = useId()
const { result, onResult: onActiveWorkspaceResult } = useQuery(
navigationWorkspaceSwitcherQuery,
() => ({
joinRequestFilter: {
status: WorkspaceJoinRequestStatus.Pending
}
}),
() => ({
enabled: isWorkspacesEnabled.value
})
)
// Seperate query to get the full workspace, because it's not always needed
const { result: fullWorkspaceResult } = useQuery(
workspaceSwitcherHeaderWorkspaceQuery,
() => ({
slug: result.value?.activeUser?.activeWorkspace?.slug || ''
}),
{
enabled:
!!result.value?.activeUser?.activeWorkspace?.slug &&
isWorkspacesEnabled.value &&
!!result.value?.activeUser?.activeWorkspace?.role
}
)
const showDiscoverableWorkspacesModal = ref(false)
const isInviteDialogOpen = ref(false)
const activeWorkspace = computed(() => result.value?.activeUser?.activeWorkspace)
const fullActiveWorkspace = computed(() => fullWorkspaceResult.value?.workspaceBySlug)
const ssoExpiredWorkspace = computed(() =>
result.value?.activeUser?.expiredSsoSessions?.find(
(session) => session.slug === activeWorkspace.value?.slug
)
)
const hasDiscoverableWorkspaces = computed(
() => (result.value?.activeUser?.discoverableWorkspaces?.length || 0) > 0
)
const discoverableWorkspacesAndJoinRequestsCount = computed(
() =>
(result.value?.activeUser?.discoverableWorkspaces?.length || 0) +
(result.value?.activeUser?.workspaceJoinRequests?.totalCount || 0)
)
const hasDiscoverableWorkspacesOrJoinRequests = computed(
() => discoverableWorkspacesAndJoinRequestsCount.value > 0
)
onActiveWorkspaceResult(({ data }) => {
if (data?.activeUser?.activeWorkspace) {
$intercom.updateCompany({
id: data.activeUser.activeWorkspace.id,
name: data.activeUser.activeWorkspace.name
})
}
})
</script>