Files
speckle-server/packages/frontend-2/components/settings/Dialog.vue
T
andrewwallacespeckle d956fbf79d refactor(fe2): Change workspace creation triggers (#3211)
* Tasks from ticket

* Update copy

* Add confirm dialog to cancel button

* Show Workspace create dialog when on explainer page

* Reorder

* Add new mixpanel event

* Use 1 confirm modal
2024-10-10 16:10:21 +01:00

239 lines
7.6 KiB
Vue

<template>
<LayoutDialog
v-model:open="isOpen"
v-bind="
isMobile ? { title: selectedMenuItem ? selectedMenuItem.title : 'Settings' } : {}
"
fullscreen="all"
:show-back-button="isMobile && !!selectedMenuItem"
@back="targetMenuItem = null"
>
<div class="w-full h-full flex">
<LayoutSidebar
v-if="!isMobile || !selectedMenuItem"
class="w-full md:w-56 lg:w-60 md:pb-4 md:px-2 md:pt-6 md:bg-foundation md:border-r md:border-outline-3"
>
<LayoutSidebarMenu>
<LayoutSidebarMenuGroup title="User settings">
<template #title-icon>
<IconAccount class="size-4" />
</template>
<LayoutSidebarMenuGroupItem
v-for="(sidebarMenuItem, key) in userMenuItems"
:key="key"
:label="sidebarMenuItem.title"
:active="targetMenuItem === key"
@click="targetMenuItem = `${key}`"
/>
</LayoutSidebarMenuGroup>
<LayoutSidebarMenuGroup v-if="isAdmin" title="Server settings">
<template #title-icon>
<IconServer class="size-4" />
</template>
<LayoutSidebarMenuGroupItem
v-for="(sidebarMenuItem, key) in serverMenuItems"
:key="key"
:label="sidebarMenuItem.title"
:active="targetMenuItem === key"
@click="targetMenuItem = `${key}`"
/>
</LayoutSidebarMenuGroup>
<LayoutSidebarMenuGroup v-if="isWorkspacesEnabled" title="Workspace settings">
<template #title-icon>
<IconWorkspaces class="size-4 text-foreground-2" />
</template>
<LayoutSidebarMenuGroup
v-for="(workspaceItem, key) in workspaceItems"
:key="key"
:title="workspaceItem.name"
collapsible
class="workspace-item"
:collapsed="targetWorkspaceId !== workspaceItem.id"
>
<template #title-icon>
<WorkspaceAvatar
:logo="workspaceItem.logo"
:default-logo-index="workspaceItem.defaultLogoIndex"
size="sm"
/>
</template>
<template
v-for="(workspaceMenuItem, itemKey) in workspaceMenuItems"
:key="`${key}-${itemKey}`"
>
<LayoutSidebarMenuGroupItem
v-if="workspaceMenuItem.permission?.includes(workspaceItem.role as WorkspaceRoles)"
:label="workspaceMenuItem.title"
:active="
workspaceMenuItemClasses(
itemKey,
workspaceItem.id,
workspaceMenuItem.disabled
)
"
:tooltip-text="workspaceMenuItem.tooltipText"
:disabled="workspaceMenuItem.disabled"
:tag="workspaceMenuItem.disabled ? 'Coming soon' : undefined"
extra-padding
@click="
onWorkspaceMenuItemClick(
workspaceItem.id,
`${itemKey}`,
workspaceMenuItem.disabled
)
"
/>
</template>
</LayoutSidebarMenuGroup>
<NuxtLink
v-if="canCreateWorkspace"
:to="workspacesRoute"
@click="isOpen = false"
>
<LayoutSidebarMenuGroupItem label="Create workspace">
<template #icon>
<PlusIcon class="h-4 w-4 text-foreground-2" />
</template>
</LayoutSidebarMenuGroupItem>
</NuxtLink>
</LayoutSidebarMenuGroup>
</LayoutSidebarMenu>
</LayoutSidebar>
<component
:is="selectedMenuItem.component"
v-if="selectedMenuItem"
:class="[
'md:bg-foundation md:px-10 md:py-12 md:bg-foundation-page w-full',
!isMobile && 'simple-scrollbar overflow-y-auto flex-1'
]"
:user="user"
:workspace-id="targetWorkspaceId"
@close="isOpen = false"
/>
</div>
<WorkspaceCreateDialog
v-model:open="showWorkspaceCreateDialog"
event-source="settings"
/>
</LayoutDialog>
</template>
<script setup lang="ts">
import { Roles } from '@speckle/shared'
import type { SettingsMenuItem } from '~/lib/settings/helpers/types'
import { useIsWorkspacesEnabled } from '~/composables/globals'
import { useQuery } from '@vue/apollo-composable'
import { settingsSidebarQuery } from '~/lib/settings/graphql/queries'
import { useBreakpoints } from '@vueuse/core'
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
import { PlusIcon } from '@heroicons/vue/24/outline'
import { useActiveUser } from '~/lib/auth/composables/activeUser'
import { useSettingsMenu } from '~/lib/settings/composables/menu'
import {
LayoutSidebar,
LayoutSidebarMenu,
LayoutSidebarMenuGroup
} from '@speckle/ui-components'
import { graphql } from '~~/lib/common/generated/gql'
import type { WorkspaceRoles } from '@speckle/shared'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { workspacesRoute } from '~/lib/common/helpers/route'
graphql(`
fragment SettingsDialog_Workspace on Workspace {
...WorkspaceAvatar_Workspace
id
slug
role
name
}
`)
graphql(`
fragment SettingsDialog_User on User {
id
workspaces {
items {
...SettingsDialog_Workspace
}
}
}
`)
const isOpen = defineModel<boolean>('open', { required: true })
const targetMenuItem = defineModel<string | null>('targetMenuItem', { required: true })
const targetWorkspaceId = defineModel<string | null>('targetWorkspaceId')
const { activeUser: user } = useActiveUser()
const { userMenuItems, serverMenuItems, workspaceMenuItems } = useSettingsMenu()
const breakpoints = useBreakpoints(TailwindBreakpoints)
const mixpanel = useMixpanel()
const isWorkspacesEnabled = useIsWorkspacesEnabled()
const { result: workspaceResult } = useQuery(settingsSidebarQuery, null, {
enabled: isWorkspacesEnabled.value
})
const isMobile = breakpoints.smaller('md')
const showWorkspaceCreateDialog = ref(false)
const workspaceItems = computed(
() => workspaceResult.value?.activeUser?.workspaces.items ?? []
)
const isAdmin = computed(() => user.value?.role === Roles.Server.Admin)
const canCreateWorkspace = computed(
() =>
user.value?.role === Roles.Server.Admin || user.value?.role === Roles.Server.User
)
const selectedMenuItem = computed((): SettingsMenuItem | null => {
const categories = [
userMenuItems.value,
serverMenuItems.value,
workspaceMenuItems.value
]
for (const category of categories) {
if (targetMenuItem.value && targetMenuItem.value in category) {
return category[targetMenuItem.value]
}
}
return null
})
const onWorkspaceMenuItemClick = (id: string, target: string, disabled?: boolean) => {
if (disabled) return
targetWorkspaceId.value = id
targetMenuItem.value = target
mixpanel.track('Workspace Settings Menuitem Clicked', {
// eslint-disable-next-line camelcase
workspace_id: id,
item: target
})
}
const workspaceMenuItemClasses = (
itemKey: string | number,
workspaceId: string,
disabled?: boolean
) =>
targetMenuItem.value === itemKey &&
targetWorkspaceId.value === workspaceId &&
!disabled
watch(
() => user.value,
(newVal) => {
if (!newVal) {
isOpen.value = false
}
},
{ immediate: true }
)
</script>
<style>
.workspace-item h6 {
@apply !font-normal text-body-xs !text-foreground;
}
</style>