Merge branch 'main' into andrew/web-2567-update-members-and-guests-settings-pages
This commit is contained in:
+29
-26
@@ -1,32 +1,35 @@
|
||||
<template>
|
||||
<ProjectPageSettingsBlock title="Collaborators">
|
||||
<template #introduction>
|
||||
<p class="text-body-xs text-foreground">
|
||||
Invite new collaborators and set permissions.
|
||||
</p>
|
||||
</template>
|
||||
<template #top-buttons>
|
||||
<FormButton :disabled="!canInvite" @click="toggleInviteDialog">Invite</FormButton>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-col mt-6">
|
||||
<ProjectPageSettingsCollaboratorsRow
|
||||
v-for="collaborator in collaboratorListItems"
|
||||
:key="collaborator.id"
|
||||
:can-edit="canEdit"
|
||||
:collaborator="collaborator"
|
||||
:loading="loading"
|
||||
@cancel-invite="onCancelInvite"
|
||||
@change-role="onCollaboratorRoleChange"
|
||||
<div>
|
||||
<div v-if="project">
|
||||
<div class="flex justify-between space-x-2">
|
||||
<div>
|
||||
<h1 class="block text-heading-xl mb-2">Collaborators</h1>
|
||||
<p class="text-body-xs text-foreground">
|
||||
Invite new collaborators and set permissions.
|
||||
</p>
|
||||
</div>
|
||||
<FormButton class="mt-1" :disabled="!canInvite" @click="toggleInviteDialog">
|
||||
Invite
|
||||
</FormButton>
|
||||
</div>
|
||||
<div class="flex flex-col mt-6">
|
||||
<ProjectPageSettingsCollaboratorsRow
|
||||
v-for="collaborator in collaboratorListItems"
|
||||
:key="collaborator.id"
|
||||
:can-edit="canEdit"
|
||||
:collaborator="collaborator"
|
||||
:loading="loading"
|
||||
@cancel-invite="onCancelInvite"
|
||||
@change-role="onCollaboratorRoleChange"
|
||||
/>
|
||||
</div>
|
||||
<InviteDialogProject
|
||||
v-if="project"
|
||||
v-model:open="showInviteDialog"
|
||||
:project="project"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<InviteDialogProject
|
||||
v-if="project"
|
||||
v-model:open="showInviteDialog"
|
||||
:project="project"
|
||||
/>
|
||||
</ProjectPageSettingsBlock>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { Project } from '~~/lib/common/generated/gql/graphql'
|
||||
|
||||
@@ -116,7 +116,7 @@ import { isProject } from '~~/lib/server-management/helpers/utils'
|
||||
import { useDebouncedTextInput, type LayoutMenuItem } from '@speckle/ui-components'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { projectCollaboratorsRoute, projectRoute } from '~/lib/common/helpers/route'
|
||||
import { projectRoute } from '~/lib/common/helpers/route'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsSharedProjects_Project on Project {
|
||||
@@ -187,7 +187,7 @@ const onActionChosen = (
|
||||
project: ProjectsDeleteDialog_ProjectFragment
|
||||
) => {
|
||||
if (actionItem.id === ActionTypes.EditMembers) {
|
||||
router.push(projectCollaboratorsRoute(project.id))
|
||||
router.push(projectRoute(project.id, 'collaborators'))
|
||||
} else if (actionItem.id === ActionTypes.ViewProject) {
|
||||
handleProjectClick(project.id)
|
||||
} else if (actionItem.id === ActionTypes.DeleteProject) {
|
||||
|
||||
@@ -132,7 +132,7 @@ graphql(`
|
||||
...SettingsWorkspacesMembersTableHeader_Workspace
|
||||
...SettingsSharedDeleteUserDialog_Workspace
|
||||
...SettingsWorkspacesMembersChangeRoleDialog_Workspace
|
||||
team {
|
||||
team(limit: 250) {
|
||||
items {
|
||||
id
|
||||
...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator
|
||||
|
||||
@@ -134,7 +134,7 @@ graphql(`
|
||||
...SettingsSharedDeleteUserDialog_Workspace
|
||||
...SettingsWorkspacesMembersTableHeader_Workspace
|
||||
...SettingsWorkspacesMembersChangeRoleDialog_Workspace
|
||||
team {
|
||||
team(limit: 250) {
|
||||
items {
|
||||
id
|
||||
...SettingsWorkspacesMembersTable_WorkspaceCollaborator
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
v-if="!isWorkspaceGuest"
|
||||
:workspace-info="workspaceInfo"
|
||||
:is-workspace-admin="isWorkspaceAdmin"
|
||||
:is-workspace-guest="isWorkspaceGuest"
|
||||
@show-invite-dialog="$emit('show-invite-dialog')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
:icon="iconName"
|
||||
:icon-click="iconClick"
|
||||
:icon-text="iconText"
|
||||
:tag="workspaceInfo.team.totalCount.toString() || undefined"
|
||||
no-hover
|
||||
>
|
||||
<div class="flex lg:flex-col items-center lg:items-start gap-y-3 pb-0 lg:pb-4 mt-1">
|
||||
@@ -15,6 +14,11 @@
|
||||
:users="team.map((teamMember) => teamMember.user)"
|
||||
:max-avatars="isDesktop ? 5 : 3"
|
||||
class="shrink-0"
|
||||
:on-hidden-count-click="
|
||||
() => {
|
||||
navigateTo(settingsWorkspaceRoutes.members.route(workspaceInfo.slug))
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div class="w-full flex items-center gap-x-2">
|
||||
<button
|
||||
@@ -66,6 +70,7 @@ const props = defineProps<{
|
||||
workspaceInfo: WorkspaceTeam_WorkspaceFragment
|
||||
collapsible?: boolean
|
||||
isWorkspaceAdmin?: boolean
|
||||
isWorkspaceGuest?: boolean
|
||||
}>()
|
||||
|
||||
const breakpoints = useBreakpoints(TailwindBreakpoints)
|
||||
@@ -74,19 +79,19 @@ const isDesktop = breakpoints.greaterOrEqual('lg')
|
||||
const team = computed(() => props.workspaceInfo.team.items || [])
|
||||
|
||||
const iconName = computed(() => {
|
||||
if (!props.isWorkspaceAdmin) return undefined
|
||||
return 'edit'
|
||||
if (props.isWorkspaceAdmin) return 'edit'
|
||||
return 'view'
|
||||
})
|
||||
|
||||
const iconClick = computed(() => {
|
||||
if (!props.isWorkspaceAdmin) return undefined
|
||||
if (props.isWorkspaceGuest) return undefined
|
||||
return () =>
|
||||
navigateTo(settingsWorkspaceRoutes.members.route(props.workspaceInfo.slug))
|
||||
})
|
||||
|
||||
const iconText = computed(() => {
|
||||
if (!props.isWorkspaceAdmin) return undefined
|
||||
return 'Manage members'
|
||||
if (props.isWorkspaceAdmin) return 'Manage members'
|
||||
return 'View members'
|
||||
})
|
||||
|
||||
const invitedTeamCount = computed(() => props.workspaceInfo?.invitedTeam?.length ?? 0)
|
||||
|
||||
@@ -80,7 +80,7 @@ export const settingsWorkspaceRoutes = {
|
||||
|
||||
export const projectRoute = (
|
||||
id: string,
|
||||
tab?: 'models' | 'discussions' | 'automations' | 'settings'
|
||||
tab?: 'models' | 'discussions' | 'automations' | 'collaborators' | 'settings'
|
||||
) => {
|
||||
let res = `/projects/${id}`
|
||||
if (tab && tab !== 'models') {
|
||||
@@ -113,9 +113,6 @@ export const projectDiscussionsRoute = (projectId: string) => `/projects/${proje
|
||||
export const projectSettingsRoute = (projectId: string) =>
|
||||
`/projects/${projectId}/settings`
|
||||
|
||||
export const projectCollaboratorsRoute = (projectId: string) =>
|
||||
`/projects/${projectId}/settings/collaborators`
|
||||
|
||||
export const projectWebhooksRoute = (projectId: string) =>
|
||||
`/projects/${projectId}/settings/webhooks`
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ export const settingsWorkspacesMembersSearchQuery = graphql(`
|
||||
query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {
|
||||
workspaceBySlug(slug: $slug) {
|
||||
id
|
||||
team(filter: $filter) {
|
||||
team(filter: $filter, limit: 250) {
|
||||
items {
|
||||
id
|
||||
...SettingsWorkspacesMembersTable_WorkspaceCollaborator
|
||||
|
||||
@@ -39,7 +39,7 @@ export const workspaceTeamFragment = graphql(`
|
||||
fragment WorkspaceTeam_Workspace on Workspace {
|
||||
id
|
||||
slug
|
||||
team {
|
||||
team(limit: 250) {
|
||||
totalCount
|
||||
items {
|
||||
id
|
||||
|
||||
@@ -170,31 +170,31 @@ export default defineNuxtConfig({
|
||||
// Redirect old settings pages
|
||||
'/server-management/projects': {
|
||||
redirect: {
|
||||
to: '/?settings=server/projects',
|
||||
to: '/settings/server/projects',
|
||||
statusCode: 301
|
||||
}
|
||||
},
|
||||
'/server-management/active-users': {
|
||||
redirect: {
|
||||
to: '/?settings=server/active-users',
|
||||
to: '/settings/server/active-users',
|
||||
statusCode: 301
|
||||
}
|
||||
},
|
||||
'/server-management/pending-invitations': {
|
||||
redirect: {
|
||||
to: '/?settings=server/pending-invitations',
|
||||
to: '/settings/server/pending-invitations',
|
||||
statusCode: 301
|
||||
}
|
||||
},
|
||||
'/server-management': {
|
||||
redirect: {
|
||||
to: '/?settings=server/general',
|
||||
to: '/settings/server/general',
|
||||
statusCode: 301
|
||||
}
|
||||
},
|
||||
'/profile': {
|
||||
redirect: {
|
||||
to: '/?settings/user/profile',
|
||||
to: '/settings/user/profile',
|
||||
statusCode: 301
|
||||
}
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<div class="flex flex-row gap-x-3">
|
||||
<div v-tippy="collaboratorsTooltip">
|
||||
<NuxtLink :to="hasRole ? projectCollaboratorsRoute(project.id) : ''">
|
||||
<NuxtLink :to="hasRole ? projectRoute(project.id, 'collaborators') : ''">
|
||||
<UserAvatarGroup
|
||||
:users="teamUsers"
|
||||
:max-count="2"
|
||||
@@ -74,7 +74,6 @@ import { useGeneralProjectPageUpdateTracking } from '~~/lib/projects/composables
|
||||
import { LayoutTabsHorizontal, type LayoutPageTabItem } from '@speckle/ui-components'
|
||||
import { projectRoute, projectWebhooksRoute } from '~/lib/common/helpers/route'
|
||||
import { canEditProject } from '~~/lib/projects/helpers/permissions'
|
||||
import { projectCollaboratorsRoute } from '~~/lib/common/helpers/route'
|
||||
import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
|
||||
import { EllipsisHorizontalIcon } from '@heroicons/vue/24/solid'
|
||||
import { HorizontalDirection } from '~~/lib/common/composables/window'
|
||||
@@ -227,6 +226,11 @@ const pageTabItems = computed((): LayoutPageTabItem[] => {
|
||||
}
|
||||
|
||||
if (hasRole.value) {
|
||||
items.push({
|
||||
title: 'Collaborators',
|
||||
id: 'collaborators'
|
||||
})
|
||||
|
||||
items.push({
|
||||
title: 'Settings',
|
||||
id: 'settings'
|
||||
@@ -248,6 +252,8 @@ const activePageTab = computed({
|
||||
const path = router.currentRoute.value.path
|
||||
if (/\/discussions\/?$/i.test(path)) return findTabById('discussions')
|
||||
if (/\/automations\/?.*$/i.test(path)) return findTabById('automations')
|
||||
if (/\/collaborators\/?/i.test(path) && hasRole.value)
|
||||
return findTabById('collaborators')
|
||||
if (/\/settings\/?/i.test(path) && hasRole.value) return findTabById('settings')
|
||||
return findTabById('models')
|
||||
},
|
||||
@@ -263,6 +269,11 @@ const activePageTab = computed({
|
||||
case 'automations':
|
||||
router.push({ path: projectRoute(projectId.value, 'automations') })
|
||||
break
|
||||
case 'collaborators':
|
||||
if (hasRole.value) {
|
||||
router.push({ path: projectRoute(projectId.value, 'collaborators') })
|
||||
}
|
||||
break
|
||||
case 'settings':
|
||||
if (hasRole.value) {
|
||||
router.push({ path: projectRoute(projectId.value, 'settings') })
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<ProjectPageSettingsCollaborators />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ProjectPageProjectFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
|
||||
const attrs = useAttrs() as {
|
||||
project: ProjectPageProjectFragment
|
||||
}
|
||||
|
||||
const projectName = computed(() =>
|
||||
attrs.project.name.length ? attrs.project.name : ''
|
||||
)
|
||||
|
||||
useHead({
|
||||
title: `Collaborators | ${projectName.value}`
|
||||
})
|
||||
</script>
|
||||
@@ -11,11 +11,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { LayoutTabsVertical, type LayoutPageTabItem } from '@speckle/ui-components'
|
||||
import {
|
||||
projectCollaboratorsRoute,
|
||||
projectSettingsRoute,
|
||||
projectWebhooksRoute
|
||||
} from '~~/lib/common/helpers/route'
|
||||
import { projectSettingsRoute, projectWebhooksRoute } from '~~/lib/common/helpers/route'
|
||||
import { graphql } from '~~/lib/common/generated/gql'
|
||||
import type { ProjectPageProjectFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
import { Roles } from '@speckle/shared'
|
||||
@@ -51,10 +47,6 @@ const settingsTabItems = computed((): LayoutPageTabItem[] => [
|
||||
title: 'General',
|
||||
id: 'general'
|
||||
},
|
||||
{
|
||||
title: 'Collaborators',
|
||||
id: 'collaborators'
|
||||
},
|
||||
{
|
||||
title: 'Webhooks',
|
||||
id: 'webhooks',
|
||||
@@ -68,15 +60,11 @@ const projectId = computed(() => route.params.id as string)
|
||||
const activeSettingsPageTab = computed({
|
||||
get: () => {
|
||||
const path = route.path
|
||||
if (path.includes('/settings/collaborators')) return settingsTabItems.value[1]
|
||||
if (path.includes('/settings/webhooks')) return settingsTabItems.value[2]
|
||||
return settingsTabItems.value[0]
|
||||
},
|
||||
set: (val: LayoutPageTabItem) => {
|
||||
switch (val.id) {
|
||||
case 'collaborators':
|
||||
router.push(projectCollaboratorsRoute(projectId.value))
|
||||
break
|
||||
case 'webhooks':
|
||||
router.push(projectWebhooksRoute(projectId.value))
|
||||
break
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<ProjectPageSettingsCollaborators />
|
||||
</template>
|
||||
@@ -46,6 +46,7 @@
|
||||
@click="iconClick"
|
||||
>
|
||||
<Edit v-if="icon === 'edit'" class="h-4 w-4" />
|
||||
<ChevronRightIcon v-else-if="icon === 'view'" class="h-4 w-4" />
|
||||
<Plus v-else class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -61,13 +62,14 @@ import Plus from '~~/src/components/global/icon/Plus.vue'
|
||||
import Edit from '~~/src/components/global/icon/Edit.vue'
|
||||
import ArrowFilled from '~~/src/components/global/icon/ArrowFilled.vue'
|
||||
import CommonBadge from '~~/src/components/common/Badge.vue'
|
||||
import { ChevronRightIcon } from '@heroicons/vue/24/outline'
|
||||
|
||||
defineProps<{
|
||||
tag?: string
|
||||
title?: string
|
||||
collapsible?: boolean
|
||||
collapsed?: boolean
|
||||
icon?: 'add' | 'edit'
|
||||
icon?: 'add' | 'edit' | 'view'
|
||||
iconText?: string
|
||||
iconClick?: () => void
|
||||
noHover?: boolean
|
||||
|
||||
@@ -14,7 +14,13 @@
|
||||
:hide-tooltip="hideTooltips"
|
||||
/>
|
||||
</div>
|
||||
<UserAvatar v-if="totalHiddenCount" :size="size" class="select-none">
|
||||
<UserAvatar
|
||||
v-if="totalHiddenCount"
|
||||
:size="size"
|
||||
class="select-none"
|
||||
:class="{ 'cursor-pointer': !!onHiddenCountClick }"
|
||||
@click="onHiddenCountClick && onHiddenCountClick()"
|
||||
>
|
||||
+{{ totalHiddenCount }}
|
||||
</UserAvatar>
|
||||
</div>
|
||||
@@ -35,6 +41,7 @@ const props = withDefaults(
|
||||
maxCount?: number
|
||||
hideTooltips?: boolean
|
||||
maxAvatars?: number
|
||||
onHiddenCountClick?: () => void
|
||||
}>(),
|
||||
{
|
||||
users: () => [],
|
||||
@@ -42,7 +49,8 @@ const props = withDefaults(
|
||||
size: 'base',
|
||||
maxCount: undefined,
|
||||
hideTooltips: false,
|
||||
maxAvatars: undefined
|
||||
maxAvatars: undefined,
|
||||
onHiddenCountClick: undefined
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1078,15 +1078,11 @@ export class SmoothOrbitControls extends SpeckleControls {
|
||||
|
||||
protected onPointerDown = (event: PointerEvent) => {
|
||||
if (this._options.orbitAroundCursor) {
|
||||
const x =
|
||||
((event.clientX - this._container.offsetLeft) / this._container.offsetWidth) *
|
||||
2 -
|
||||
1
|
||||
/** Hope this is not slow */
|
||||
const rect = this._container.getBoundingClientRect()
|
||||
const x = ((event.clientX - rect.left) / rect.width) * 2 - 1
|
||||
const y = ((event.clientY - rect.top) / rect.height) * -2 + 1
|
||||
|
||||
const y =
|
||||
((event.clientY - this._container.offsetTop) / this._container.offsetHeight) *
|
||||
-2 +
|
||||
1
|
||||
const res = this.renderer.intersections.intersect(
|
||||
this.renderer.scene,
|
||||
this._targetCamera as PerspectiveCamera,
|
||||
@@ -1242,14 +1238,10 @@ export class SmoothOrbitControls extends SpeckleControls {
|
||||
}
|
||||
|
||||
protected onWheel = (event: WheelEvent) => {
|
||||
const x =
|
||||
((event.clientX - this._container.offsetLeft) / this._container.offsetWidth) * 2 -
|
||||
1
|
||||
|
||||
const y =
|
||||
((event.clientY - this._container.offsetTop) / this._container.offsetHeight) *
|
||||
-2 +
|
||||
1
|
||||
/** Hope this is not slow */
|
||||
const rect = this._container.getBoundingClientRect()
|
||||
const x = ((event.clientX - rect.left) / rect.width) * 2 - 1
|
||||
const y = ((event.clientY - rect.top) / rect.height) * -2 + 1
|
||||
|
||||
this.zoomControlCoord.set(x, y)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user