Files
speckle-server/packages/frontend-2/components/settings/Dialog.vue
T

233 lines
7.9 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:p-4 md:pt-6 md:bg-foundation md:border-r md:border-outline-3"
>
<LayoutSidebarMenu>
<LayoutSidebarMenuGroup title="Account settings">
<template #title-icon>
<UserIcon class="h-5 w-5" />
</template>
<LayoutSidebarMenuGroupItem
v-for="(sidebarMenuItem, key) in menuItemConfig.user"
:key="key"
:label="sidebarMenuItem.title"
:class="{
'bg-highlight-2 hover:!bg-highlight-2': targetMenuItem === key
}"
@click="targetMenuItem = `${key}`"
/>
</LayoutSidebarMenuGroup>
<LayoutSidebarMenuGroup v-if="isAdmin" title="Server settings">
<template #title-icon>
<ServerStackIcon class="h-5 w-5" />
</template>
<LayoutSidebarMenuGroupItem
v-for="(sidebarMenuItem, key) in menuItemConfig.server"
:key="key"
:label="sidebarMenuItem.title"
:class="{
'bg-highlight-2 hover:!bg-highlight-2': targetMenuItem === key
}"
@click="targetMenuItem = `${key}`"
/>
</LayoutSidebarMenuGroup>
<LayoutSidebarMenuGroup
v-if="isWorkspacesEnabled && hasWorkspaceItems"
title="Workspace settings"
>
<template #title-icon>
<ServerStackIcon class="h-5 w-5" />
</template>
<LayoutSidebarMenuGroup
v-for="(workspaceItem, key) in workspaceItems"
:key="key"
:title="workspaceItem.name"
collapsible
>
<LayoutSidebarMenuGroupItem
v-for="(workspaceMenuItem, itemKey) in menuItemConfig.workspace"
:key="`${key}-${itemKey}`"
:label="workspaceMenuItem.title"
:class="{
'bg-highlight-2 hover:!bg-highlight-2':
targetMenuItem === itemKey && targetWorkspaceId === workspaceItem.id
}"
@click="onWorkspaceMenuItemClick(workspaceItem.id, `${itemKey}`)"
/>
<LayoutSidebarMenuGroupItem
label="Billing"
tag="Coming soon"
tooltip-text="Manage billing for your workspace"
disabled
/>
<LayoutSidebarMenuGroupItem
label="Security"
tag="Coming soon"
tooltip-text="SSO, manage permissions, restrict domain access"
disabled
/>
<LayoutSidebarMenuGroupItem
label="Regions"
tag="Coming soon"
tooltip-text="Set up regions for custom data residency"
disabled
/>
</LayoutSidebarMenuGroup>
</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"
/>
</div>
</LayoutDialog>
</template>
<script setup lang="ts">
import type { defineComponent } from 'vue'
import SettingsUserProfile from '~/components/settings/user/Profile.vue'
import SettingsUserNotifications from '~/components/settings/user/Notifications.vue'
import SettingsUserDeveloper from '~/components/settings/user/Developer.vue'
import SettingsServerGeneral from '~/components/settings/server/General.vue'
import SettingsServerProjects from '~/components/settings/server/Projects.vue'
import SettingsServerActiveUsers from '~/components/settings/server/ActiveUsers.vue'
import SettingsServerPendingInvitations from '~/components/settings/server/PendingInvitations.vue'
import SettingsWorkspacesMembers from '~/components/settings/workspaces/Members.vue'
import { useBreakpoints } from '@vueuse/core'
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
import { UserIcon, ServerStackIcon } from '@heroicons/vue/24/outline'
import { settingsQueries } from '~/lib/common/helpers/route'
import { useActiveUser } from '~/lib/auth/composables/activeUser'
import {
LayoutSidebar,
LayoutSidebarMenu,
LayoutSidebarMenuGroup
} from '@speckle/ui-components'
import { Roles } from '@speckle/shared'
import { useQuery } from '@vue/apollo-composable'
import { settingsSidebarWorkspacesQuery } from '~/lib/settings/graphql/queries'
import { useIsWorkspacesEnabled } from '~/composables/globals'
type MenuItem = {
title: string
component: ReturnType<typeof defineComponent>
}
const { activeUser: user } = useActiveUser()
const breakpoints = useBreakpoints(TailwindBreakpoints)
const isWorkspacesEnabled = useIsWorkspacesEnabled()
const { result: workspaceResult } = useQuery(settingsSidebarWorkspacesQuery, null, {
enabled: isWorkspacesEnabled.value
})
const isMobile = breakpoints.smaller('md')
const targetWorkspaceId = ref<string | null>(null)
const menuItemConfig = shallowRef<{ [key: string]: { [key: string]: MenuItem } }>({
user: {
[settingsQueries.user.profile]: {
title: 'Profile',
component: SettingsUserProfile
},
[settingsQueries.user.notifications]: {
title: 'Notifications',
component: SettingsUserNotifications
},
[settingsQueries.user.developerSettings]: {
title: 'Developer settings',
component: SettingsUserDeveloper
}
},
server: {
[settingsQueries.server.general]: {
title: 'General',
component: SettingsServerGeneral
},
[settingsQueries.server.projects]: {
title: 'Projects',
component: SettingsServerProjects
},
[settingsQueries.server.activeUsers]: {
title: 'Active users',
component: SettingsServerActiveUsers
},
[settingsQueries.server.pendingInvitations]: {
title: 'Pending invitations',
component: SettingsServerPendingInvitations
}
},
workspace: {
members: {
title: 'Members',
component: SettingsWorkspacesMembers
}
}
})
const isOpen = defineModel<boolean>('open', { required: true })
const targetMenuItem = defineModel<string | null>('targetMenuItem', { required: true })
const workspaceItems = computed(() =>
workspaceResult.value?.activeUser
? workspaceResult.value.activeUser.workspaces.items
: []
)
const hasWorkspaceItems = computed(() => workspaceItems.value.length > 0)
const isAdmin = computed(() => user.value?.role === Roles.Server.Admin)
const selectedMenuItem = computed((): MenuItem | null => {
const categories = [
menuItemConfig.value.user,
menuItemConfig.value.server,
menuItemConfig.value.workspace
]
for (const category of categories) {
if (targetMenuItem.value && targetMenuItem.value in category) {
return category[targetMenuItem.value]
}
}
if (!isMobile.value && targetMenuItem.value) {
// Fallback for invalid queries/typos
return targetMenuItem.value.includes('server') && isAdmin.value
? menuItemConfig.value.server.general
: menuItemConfig.value.user.profile
}
return null
})
// Keep track of the selected workspace ID, to open the page for the correct workspace
const onWorkspaceMenuItemClick = (id: string, target: string) => {
targetWorkspaceId.value = id
targetMenuItem.value = target
}
watch(
() => user.value,
(newVal) => {
if (!newVal) {
isOpen.value = false
}
},
{ immediate: true }
)
</script>