feat(fe1 & fe2): guest role (#1768)

* feat: user guest role switching in FE1

* removed stream create buttons

* fe1 done

* fe1 - specifying role in invite dialogs

* fe1 - bulk invites

* WIP FE2 changes

* fe1: allow role select condition fixes

* xtra limitations on createForOnboarding

* more invite creation validations

* no longer able to set guest as project owner in invite

* preparations for server role select in invite dialog

* team management dialog done

* server invite dialog updated

* hiding invite dialog

* fixed mocks
This commit is contained in:
Kristaps Fabians Geikins
2023-08-24 10:30:09 +03:00
committed by GitHub
parent 872cb3fe7c
commit 069f64afc9
76 changed files with 1336 additions and 324 deletions
@@ -0,0 +1,87 @@
<template>
<FormSelectBase
v-model="selectedValue"
:items="roles"
:multiple="multiple"
:disabled-item-predicate="disabledItemPredicate"
name="serverRoles"
label="Server roles"
class="min-w-[110px]"
>
<template #nothing-selected>
{{ multiple ? 'Select roles' : 'Select role' }}
</template>
<template #something-selected="{ value }">
<template v-if="isMultiItemArrayValue(value)">
<div ref="elementToWatchForChanges" class="flex items-center space-x-0.5">
<div
ref="itemContainer"
class="flex flex-wrap overflow-hidden space-x-0.5 h-6"
>
<div v-for="(item, i) in value" :key="item" class="text-foreground">
{{ RoleInfo.Server[item] + (i < value.length - 1 ? ', ' : '') }}
</div>
</div>
<div v-if="hiddenSelectedItemCount > 0" class="text-foreground-2 normal">
+{{ hiddenSelectedItemCount }}
</div>
</div>
</template>
<template v-else>
<div class="truncate text-foreground">
{{ RoleInfo.Server[firstItem(value)] }}
</div>
</template>
</template>
<template #option="{ item }">
<div class="flex items-center">
<span class="truncate">{{ RoleInfo.Server[firstItem(item)] }}</span>
</div>
</template>
</FormSelectBase>
</template>
<script setup lang="ts">
import { Nullable, Roles, ServerRoles, RoleInfo } from '@speckle/shared'
import { useFormSelectChildInternals } from '@speckle/ui-components'
import { PropType } from 'nuxt/dist/app/compat/capi'
type ValueType = ServerRoles | ServerRoles[] | undefined
const emit = defineEmits<{
(e: 'update:modelValue', v: ValueType): void
}>()
const props = defineProps({
multiple: Boolean,
modelValue: {
type: [String, Array] as PropType<ValueType>,
default: undefined
},
allowGuest: Boolean,
allowAdmin: Boolean,
allowArchived: Boolean
})
const elementToWatchForChanges = ref(null as Nullable<HTMLElement>)
const itemContainer = ref(null as Nullable<HTMLElement>)
const { selectedValue, isMultiItemArrayValue, hiddenSelectedItemCount, firstItem } =
useFormSelectChildInternals<ServerRoles>({
props: toRefs(props),
emit,
dynamicVisibility: { elementToWatchForChanges, itemContainer }
})
const roles = computed(() =>
Object.values(Roles.Server).filter((r) => {
if (r === Roles.Server.Admin) return props.allowAdmin
if (r === Roles.Server.ArchivedUser) return props.allowArchived
return true
})
)
const disabledItemPredicate = (item: ServerRoles) => {
if (item === Roles.Server.Guest) return !props.allowGuest
return false
}
</script>
@@ -56,7 +56,7 @@
<Icon class="w-5 h-5 mr-2" />
</NuxtLink>
</MenuItem>
<MenuItem v-if="activeUser" v-slot="{ active }">
<MenuItem v-if="activeUser && !isGuest" v-slot="{ active }">
<NuxtLink
:class="[
active ? 'bg-foundation-focus' : '',
@@ -124,7 +124,7 @@ import { useTheme, AppTheme } from '~~/lib/core/composables/theme'
import { serverVersionInfoQuery } from '~~/lib/core/graphql/queries'
const { logout } = useAuthManager()
const { activeUser } = useActiveUser()
const { activeUser, isGuest } = useActiveUser()
const { isDarkTheme, setTheme } = useTheme()
const { result } = useQuery(serverVersionInfoQuery)
const route = useRoute()
@@ -1,7 +1,10 @@
<template>
<LayoutDialog v-model:open="isOpen" max-width="md">
<div class="flex flex-col text-foreground space-y-4">
<ProjectPageTeamDialogInviteUser v-if="isOwner" :project="project" />
<ProjectPageTeamDialogInviteUser
v-if="isOwner && !isServerGuest"
:project="project"
/>
<ProjectPageTeamDialogManageUsers :project="project" />
<ProjectPageTeamDialogManagePermissions :project="project" />
</div>
@@ -23,6 +26,7 @@ graphql(`
role
user {
...LimitedUserAvatar
role
}
}
invitedTeam {
@@ -32,6 +36,7 @@ graphql(`
role
user {
...LimitedUserAvatar
role
}
}
}
@@ -46,7 +51,7 @@ const props = defineProps<{
project: ProjectPageTeamDialogFragment
}>()
const { isOwner } = useTeamDialogInternals({ props: toRefs(props) })
const { isOwner, isServerGuest } = useTeamDialogInternals({ props: toRefs(props) })
const isOpen = computed({
get: () => props.open,
@@ -34,7 +34,7 @@
</template>
<script setup lang="ts">
import { roleSelectItems } from '~~/lib/projects/helpers/components'
import { StreamRoles } from '@speckle/shared'
import { Roles, StreamRoles } from '@speckle/shared'
import { reduce } from 'lodash-es'
const emit = defineEmits<{
@@ -48,15 +48,25 @@ const props = defineProps<{
name?: string
disabled?: boolean
hideRemove?: boolean
hideOwner?: boolean
}>()
const items = ref(
reduce(
roleSelectItems,
(results, item) => {
if (!props.hideRemove || item.id !== 'delete') {
if (item.id === 'delete') {
if (!props.hideRemove) {
results[item.id] = item
}
} else if (item.id === Roles.Stream.Owner) {
if (!props.hideOwner) {
results[item.id] = item
}
} else {
results[item.id] = item
}
return results
},
{} as typeof roleSelectItems
@@ -18,38 +18,35 @@
</div>
</template>
</FormTextInput>
<div v-if="searchUsers.length || selectedEmails" class="flex flex-col space-y-4">
<div
v-if="searchUsers.length || selectedEmails?.length"
class="flex flex-col space-y-4"
>
<template v-if="searchUsers.length">
<template v-for="user in searchUsers" :key="user.id">
<div class="flex items-center space-x-2">
<UserAvatar :user="user" />
<span class="grow truncate">{{ user.name }}</span>
<FormButton :disabled="loading" @click="onInviteUser(user)">
Invite
</FormButton>
</div>
</template>
</template>
<template v-else-if="selectedEmails?.length">
<div class="flex items-center space-x-2">
<UserAvatar />
<span class="grow truncate">{{ selectedEmails.join(', ') }}</span>
<FormButton
:disabled="loading"
@click="() => onInviteUser(selectedEmails || [])"
>
Invite
</FormButton>
</div>
<ProjectPageTeamDialogInviteUserServerUserRow
v-for="user in searchUsers"
:key="user.id"
:user="user"
:stream-role="role"
:disabled="loading"
@invite-user="($event) => onInviteUser($event.user)"
/>
</template>
<ProjectPageTeamDialogInviteUserEmailsRow
v-else-if="selectedEmails?.length"
:selected-emails="selectedEmails"
:stream-role="role"
:disabled="loading"
:is-guest-mode="isGuestMode"
@invite-emails="($event) => onInviteUser($event.emails, $event.serverRole)"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Roles } from '@speckle/shared'
import { Get } from 'type-fest'
import { useUserSearch } from '~~/lib/common/composables/users'
import { Roles, ServerRoles, StreamRoles } from '@speckle/shared'
import { UserSearchItem, useUserSearch } from '~~/lib/common/composables/users'
import {
ProjectInviteCreateInput,
ProjectPageTeamDialogFragment
@@ -61,8 +58,9 @@ import { useInviteUserToProject } from '~~/lib/projects/composables/projectManag
import { useTeamDialogInternals } from '~~/lib/projects/composables/team'
import { UserPlusIcon } from '@heroicons/vue/24/solid'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useServerInfo } from '~~/lib/core/composables/server'
type InvitableUser = NonNullable<Get<typeof searchUsers.value, '[0]'>> | string
type InvitableUser = UserSearchItem | string
const props = defineProps<{
project: ProjectPageTeamDialogFragment
@@ -70,8 +68,9 @@ const props = defineProps<{
const loading = ref(false)
const search = ref('')
const role = ref(Roles.Stream.Contributor)
const role = ref<StreamRoles>(Roles.Stream.Contributor)
const { isGuestMode } = useServerInfo()
const createInvite = useInviteUserToProject()
const { userSearch, searchVariables } = useUserSearch({
variables: computed(() => ({
@@ -83,6 +82,17 @@ const { collaboratorListItems } = useTeamDialogInternals({
props: toRefs(props)
})
const selectedEmails = computed(() => {
const query = searchVariables.value?.query || ''
if (isValidEmail(query)) return [query]
const multipleEmails = query.split(',').map((i) => i.trim())
const validEmails = multipleEmails.filter((e) => isValidEmail(e))
return validEmails.length ? validEmails : null
})
const isOwnerSelected = computed(() => role.value === Roles.Stream.Owner)
const searchUsers = computed(() => {
const searchResults = userSearch.value?.userSearch.items || []
const collaboratorIds = new Set(
@@ -103,15 +113,22 @@ const isValidEmail = (val: string) =>
: false
const mp = useMixpanel()
const onInviteUser = async (user: InvitableUser | InvitableUser[]) => {
const users = isArray(user) ? user : [user]
const onInviteUser = async (
user: InvitableUser | InvitableUser[],
serverRole?: ServerRoles
) => {
const users = (isArray(user) ? user : [user]).filter(
(u) => !isOwnerSelected.value || isString(u) || u.role !== Roles.Server.Guest
)
const inputs: ProjectInviteCreateInput[] = users
.filter((u) => (isString(u) ? isValidEmail(u) : u))
.map((u) => ({
role: role.value,
...(isString(u)
? {
email: u
email: u,
serverRole
}
: {
userId: u.id
@@ -136,13 +153,4 @@ const onInviteUser = async (user: InvitableUser | InvitableUser[]) => {
loading.value = false
}
const selectedEmails = computed(() => {
const query = searchVariables.value?.query || ''
if (isValidEmail(query)) return [query]
const multipleEmails = query.split(',').map((i) => i.trim())
const validEmails = multipleEmails.filter((e) => isValidEmail(e))
return validEmails.length ? validEmails : null
})
</script>
@@ -20,6 +20,7 @@
class="shrink-0"
:model-value="collaborator.role"
:disabled="loading"
:hide-owner="collaborator.serverRole === Roles.Server.Guest"
@update:model-value="onCollaboratorRoleChange(collaborator, $event)"
@delete="onCollaboratorRoleChange(collaborator, null)"
/>
@@ -51,7 +52,7 @@
</div>
</template>
<script setup lang="ts">
import { Nullable, StreamRoles } from '@speckle/shared'
import { Nullable, StreamRoles, Roles } from '@speckle/shared'
import { useApolloClient } from '@vue/apollo-composable'
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
import {
@@ -0,0 +1,75 @@
<template>
<div class="flex items-center space-x-2">
<UserAvatar />
<span class="grow truncate">{{ selectedEmails.join(', ') }}</span>
<div class="flex items-center space-x-2">
<FormSelectServerRoles
v-if="showServerRoleSelect"
v-model="serverRole"
:allow-guest="isGuestMode"
:allow-admin="isAdmin"
fixed-height
/>
<span
v-tippy="
isTryingToSetGuestOwner ? `Server guests can't be project owners` : undefined
"
>
<FormButton
:disabled="isButtonDisabled"
@click="
() =>
$emit('invite-emails', {
emails: selectedEmails || [],
streamRole,
serverRole: showServerRoleSelect ? serverRole : undefined
})
"
>
Invite
</FormButton>
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { Roles, StreamRoles, ServerRoles } from '@speckle/shared'
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
defineEmits<{
(
e: 'invite-emails',
v: { emails: string[]; streamRole: StreamRoles; serverRole?: ServerRoles }
): void
}>()
const props = defineProps<{
selectedEmails: string[]
streamRole: StreamRoles
disabled?: boolean
isGuestMode?: boolean
}>()
const { isAdmin } = useActiveUser()
const serverRole = ref<ServerRoles>(Roles.Server.User)
const showServerRoleSelect = computed(() => props.isGuestMode || isAdmin.value)
const isTryingToSetGuestOwner = computed(() => {
if (!showServerRoleSelect.value) return false
if (
serverRole.value === Roles.Server.Guest &&
props.streamRole === Roles.Stream.Owner
)
return true
return false
})
const isButtonDisabled = computed(() => {
if (props.disabled) return true
if (isTryingToSetGuestOwner.value) return true
if (!props.selectedEmails.length) return true
return false
})
</script>
@@ -0,0 +1,42 @@
<template>
<div class="flex items-center space-x-2">
<UserAvatar :user="user" />
<span class="grow truncate">{{ user.name }}</span>
<span
v-tippy="
isTryingToSetGuestOwner ? `Server guests can't be project owners` : undefined
"
>
<FormButton
:disabled="isButtonDisabled"
@click="() => $emit('invite-user', { user, streamRole })"
>
Invite
</FormButton>
</span>
</div>
</template>
<script setup lang="ts">
import { StreamRoles, Roles } from '@speckle/shared'
import { UserSearchItem } from '~~/lib/common/composables/users'
defineEmits<{
(e: 'invite-user', v: { user: UserSearchItem; streamRole: StreamRoles }): void
}>()
const props = defineProps<{
streamRole: StreamRoles
user: UserSearchItem
disabled?: boolean
}>()
const isOwnerSelected = computed(() => props.streamRole === Roles.Stream.Owner)
const isTryingToSetGuestOwner = computed(
() => props.user.role === Roles.Server.Guest && isOwnerSelected.value
)
const isButtonDisabled = computed(() => {
if (props.disabled) return true
if (isTryingToSetGuestOwner.value) return true
return false
})
</script>
@@ -41,7 +41,11 @@
class="w-56 grow md:grow-0"
fixed-height
/>
<FormButton :icon-left="PlusIcon" @click="openNewProject = true">
<FormButton
v-if="!isGuest"
:icon-left="PlusIcon"
@click="openNewProject = true"
>
New
</FormButton>
</div>
@@ -113,7 +117,7 @@ const debouncedSearch = ref('')
const openNewProject = ref(false)
const showLoadingBar = ref(false)
const { activeUser } = useActiveUser()
const { activeUser, isGuest } = useActiveUser()
const { triggerNotification } = useGlobalToast()
const areQueriesLoading = useQueryLoading()
const apollo = useApolloClient().client
@@ -26,15 +26,26 @@
<div
class="grow flex flex-col space-y-4 sm:space-y-0 sm:flex-row sm:justify-between sm:items-center"
>
<div class="grow">
<div
class="grow flex flex-col space-y-2 sm:flex-row sm:space-x-2 sm:space-y-0"
>
<FormSelectProjects
v-model="selectedProject"
label="(Optional) Select project to invite to"
class="w-full sm:w-60"
owned-only
show-label
/>
<FormSelectServerRoles
v-if="allowServerRoleSelect"
v-model="serverRole"
label="Select server role"
show-label
:allow-guest="isGuestMode"
:allow-admin="isAdmin"
/>
</div>
<div class="flex justify-end">
<div class="flex justify-end self-end">
<FormButton text @click="isOpen = false">Cancel</FormButton>
<FormButton submit :disabled="anyMutationsLoading">Send</FormButton>
</div>
@@ -45,9 +56,10 @@
</template>
<script setup lang="ts">
import { EnvelopeIcon } from '@heroicons/vue/24/solid'
import { Optional } from '@speckle/shared'
import { Optional, Roles, ServerRoles } from '@speckle/shared'
import { useMutationLoading } from '@vue/apollo-composable'
import { useForm } from 'vee-validate'
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
import { FormSelectProjects_ProjectFragment } from '~~/lib/common/generated/gql/graphql'
import {
isRequired,
@@ -55,6 +67,7 @@ import {
isStringOfLength
} from '~~/lib/common/helpers/validation'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useServerInfo } from '~~/lib/core/composables/server'
import { useInviteUserToProject } from '~~/lib/projects/composables/projectManagement'
import { useInviteUserToServer } from '~~/lib/server/composables/invites'
@@ -67,17 +80,22 @@ const props = defineProps<{
}>()
const selectedProject = ref(undefined as Optional<FormSelectProjects_ProjectFragment>)
const serverRole = ref<ServerRoles>(Roles.Server.User)
const { handleSubmit } = useForm<{ message?: string; emailsString: string }>()
const { mutate: inviteUserToServer } = useInviteUserToServer()
const inviteUserToProject = useInviteUserToProject()
const anyMutationsLoading = useMutationLoading()
const { isAdmin } = useActiveUser()
const { isGuestMode } = useServerInfo()
const isOpen = computed({
get: () => props.open,
set: (newVal) => emit('update:open', newVal)
})
const allowServerRoleSelect = computed(() => isAdmin.value || isGuestMode.value)
const mp = useMixpanel()
const onSubmit = handleSubmit(async (values) => {
const emails = values.emailsString.split(',').map((i) => i.trim())
@@ -87,13 +105,15 @@ const onSubmit = handleSubmit(async (values) => {
? await inviteUserToProject(
project.id,
emails.map((email) => ({
email
email,
serverRole: allowServerRoleSelect.value ? serverRole.value : undefined
}))
)
: await inviteUserToServer(
emails.map((email) => ({
email,
message: values.message
message: values.message,
serverRole: allowServerRoleSelect.value ? serverRole.value : undefined
}))
)
if (success) {
@@ -1,3 +1,4 @@
import { Roles } from '@speckle/shared'
import { useApolloClient, useQuery } from '@vue/apollo-composable'
import { graphql } from '~~/lib/common/generated/gql'
import md5 from '~~/lib/common/helpers/md5'
@@ -37,7 +38,10 @@ export function useActiveUser() {
return '@' + md5(user.email.toLowerCase()).toUpperCase()
})
return { activeUser, isLoggedIn, distinctId, refetch, onResult }
const isGuest = computed(() => activeUser.value?.role === Roles.Server.Guest)
const isAdmin = computed(() => activeUser.value?.role === Roles.Server.Admin)
return { activeUser, isLoggedIn, distinctId, refetch, onResult, isGuest, isAdmin }
}
/**
@@ -1,7 +1,13 @@
import { useQuery } from '@vue/apollo-composable'
import { UserSearchQueryVariables } from '~~/lib/common/generated/gql/graphql'
import { Get } from 'type-fest'
import {
UserSearchQuery,
UserSearchQueryVariables
} from '~~/lib/common/generated/gql/graphql'
import { userSearchQuery } from '~~/lib/common/graphql/queries'
export type UserSearchItem = NonNullable<Get<UserSearchQuery, 'userSearch.items[0]'>>
export function useUserSearch(params: { variables: Ref<UserSearchQueryVariables> }) {
const { variables } = params
const { result, variables: usedVariables } = useQuery(
@@ -46,7 +46,7 @@ const documents = {
"\n fragment ProjectPageStatsBlockModels on Project {\n modelCount: models(limit: 0) {\n totalCount\n }\n }\n": types.ProjectPageStatsBlockModelsFragmentDoc,
"\n fragment ProjectPageStatsBlockTeam on Project {\n id\n role\n team {\n role\n user {\n ...LimitedUserAvatar\n }\n }\n ...ProjectPageTeamDialog\n }\n": types.ProjectPageStatsBlockTeamFragmentDoc,
"\n fragment ProjectPageStatsBlockVersions on Project {\n versionCount: versions(limit: 0) {\n totalCount\n }\n }\n": types.ProjectPageStatsBlockVersionsFragmentDoc,
"\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n role\n user {\n ...LimitedUserAvatar\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n }\n }\n }\n": types.ProjectPageTeamDialogFragmentDoc,
"\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n }\n": types.ProjectPageTeamDialogFragmentDoc,
"\n subscription OnUserProjectsUpdate {\n userProjectsUpdated {\n type\n id\n project {\n ...ProjectDashboardItem\n }\n }\n }\n": types.OnUserProjectsUpdateDocument,
"\n fragment ProjectsDashboardFilled on ProjectCollection {\n items {\n ...ProjectDashboardItem\n }\n }\n": types.ProjectsDashboardFilledFragmentDoc,
"\n fragment ProjectsInviteBanner on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n }\n": types.ProjectsInviteBannerFragmentDoc,
@@ -68,9 +68,10 @@ const documents = {
"\n query AuthServerInfo {\n serverInfo {\n ...AuthStategiesServerInfoFragment\n ...ServerTermsOfServicePrivacyPolicyFragment\n ...AuthRegisterPanelServerInfo\n }\n }\n": types.AuthServerInfoDocument,
"\n query AuthorizableAppMetadata($id: String!) {\n app(id: $id) {\n id\n name\n description\n trustByDefault\n redirectUrl\n scopes {\n name\n description\n }\n author {\n name\n id\n avatar\n }\n }\n }\n": types.AuthorizableAppMetadataDocument,
"\n query MentionsUserSearch($query: String!, $emailOnly: Boolean = false) {\n userSearch(\n query: $query\n limit: 5\n cursor: null\n archived: false\n emailOnly: $emailOnly\n ) {\n items {\n id\n name\n company\n }\n }\n }\n": types.MentionsUserSearchDocument,
"\n query UserSearch($query: String!, $limit: Int, $cursor: String, $archived: Boolean) {\n userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived) {\n cursor\n items {\n id\n name\n bio\n company\n avatar\n verified\n }\n }\n }\n": types.UserSearchDocument,
"\n query UserSearch($query: String!, $limit: Int, $cursor: String, $archived: Boolean) {\n userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived) {\n cursor\n items {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n }\n": types.UserSearchDocument,
"\n query ServerInfoBlobSizeLimit {\n serverInfo {\n blobSizeLimitBytes\n }\n }\n": types.ServerInfoBlobSizeLimitDocument,
"\n query ProjectModelsSelectorValues($projectId: String!, $cursor: String) {\n project(id: $projectId) {\n id\n models(limit: 100, cursor: $cursor) {\n cursor\n totalCount\n items {\n ...CommonModelSelectorModel\n }\n }\n }\n }\n": types.ProjectModelsSelectorValuesDocument,
"\n query MainServerInfoData {\n serverInfo {\n adminContact\n blobSizeLimitBytes\n canonicalUrl\n company\n description\n guestModeEnabled\n inviteOnly\n name\n termsOfService\n version\n }\n }\n": types.MainServerInfoDataDocument,
"\n query ServerVersionInfo {\n serverInfo {\n version\n }\n }\n": types.ServerVersionInfoDocument,
"\n query SearchProjects($search: String, $onlyWithRoles: [String!] = null) {\n activeUser {\n projects(limit: 10, filter: { search: $search, onlyWithRoles: $onlyWithRoles }) {\n totalCount\n items {\n ...FormSelectProjects_Project\n }\n }\n }\n }\n": types.SearchProjectsDocument,
"\n fragment ProjectDashboardItemNoModels on Project {\n id\n name\n createdAt\n updatedAt\n role\n team {\n user {\n id\n name\n avatar\n }\n }\n ...ProjectPageModelsCardProject\n }\n": types.ProjectDashboardItemNoModelsFragmentDoc,
@@ -288,7 +289,7 @@ export function graphql(source: "\n fragment ProjectPageStatsBlockVersions on P
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n role\n user {\n ...LimitedUserAvatar\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n role\n user {\n ...LimitedUserAvatar\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n }\n }\n }\n"];
export function graphql(source: "\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -376,7 +377,7 @@ export function graphql(source: "\n query MentionsUserSearch($query: String!, $
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query UserSearch($query: String!, $limit: Int, $cursor: String, $archived: Boolean) {\n userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived) {\n cursor\n items {\n id\n name\n bio\n company\n avatar\n verified\n }\n }\n }\n"): (typeof documents)["\n query UserSearch($query: String!, $limit: Int, $cursor: String, $archived: Boolean) {\n userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived) {\n cursor\n items {\n id\n name\n bio\n company\n avatar\n verified\n }\n }\n }\n"];
export function graphql(source: "\n query UserSearch($query: String!, $limit: Int, $cursor: String, $archived: Boolean) {\n userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived) {\n cursor\n items {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n }\n"): (typeof documents)["\n query UserSearch($query: String!, $limit: Int, $cursor: String, $archived: Boolean) {\n userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived) {\n cursor\n items {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -385,6 +386,10 @@ export function graphql(source: "\n query ServerInfoBlobSizeLimit {\n server
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ProjectModelsSelectorValues($projectId: String!, $cursor: String) {\n project(id: $projectId) {\n id\n models(limit: 100, cursor: $cursor) {\n cursor\n totalCount\n items {\n ...CommonModelSelectorModel\n }\n }\n }\n }\n"): (typeof documents)["\n query ProjectModelsSelectorValues($projectId: String!, $cursor: String) {\n project(id: $projectId) {\n id\n models(limit: 100, cursor: $cursor) {\n cursor\n totalCount\n items {\n ...CommonModelSelectorModel\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query MainServerInfoData {\n serverInfo {\n adminContact\n blobSizeLimitBytes\n canonicalUrl\n company\n description\n guestModeEnabled\n inviteOnly\n name\n termsOfService\n version\n }\n }\n"): (typeof documents)["\n query MainServerInfoData {\n serverInfo {\n adminContact\n blobSizeLimitBytes\n canonicalUrl\n company\n description\n guestModeEnabled\n inviteOnly\n name\n termsOfService\n version\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -1418,6 +1418,8 @@ export type ProjectInviteCreateInput = {
email?: InputMaybe<Scalars['String']>;
/** Defaults to the contributor role, if not specified */
role?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
/** Either this or email must be filled */
userId?: InputMaybe<Scalars['String']>;
};
@@ -1506,7 +1508,10 @@ export type ProjectMutations = {
__typename?: 'ProjectMutations';
/** Create new project */
create: Project;
/** Create onboarding/tutorial project */
/**
* Create onboarding/tutorial project. If one is already created for the active user, that
* one will be returned instead.
*/
createForOnboarding: Project;
/** Delete an existing project */
delete: Scalars['Boolean'];
@@ -1844,7 +1849,6 @@ export enum ResourceType {
Stream = 'stream'
}
/** Available roles. */
export type Role = {
__typename?: 'Role';
description: Scalars['String'];
@@ -1900,8 +1904,10 @@ export type ServerInfo = {
guestModeEnabled: Scalars['Boolean'];
inviteOnly?: Maybe<Scalars['Boolean']>;
name: Scalars['String'];
roles: Array<Maybe<Role>>;
scopes: Array<Maybe<Scope>>;
/** @deprecated Use role constants from the @speckle/shared npm package instead */
roles: Array<Role>;
scopes: Array<Scope>;
serverRoles: Array<ServerRoleItem>;
termsOfService?: Maybe<Scalars['String']>;
version?: Maybe<Scalars['String']>;
};
@@ -1926,6 +1932,8 @@ export type ServerInvite = {
export type ServerInviteCreateInput = {
email: Scalars['String'];
message?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
};
export enum ServerRole {
@@ -1935,6 +1943,12 @@ export enum ServerRole {
ServerUser = 'SERVER_USER'
}
export type ServerRoleItem = {
__typename?: 'ServerRoleItem';
id: Scalars['String'];
title: Scalars['String'];
};
export type ServerStatistics = {
__typename?: 'ServerStatistics';
totalPendingInvites: Scalars['Int'];
@@ -2109,6 +2123,7 @@ export type StreamCollaborator = {
id: Scalars['String'];
name: Scalars['String'];
role: Scalars['String'];
serverRole: Scalars['String'];
};
export type StreamCollection = {
@@ -2137,6 +2152,8 @@ export type StreamInviteCreateInput = {
message?: InputMaybe<Scalars['String']>;
/** Defaults to the contributor role, if not specified */
role?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
streamId: Scalars['String'];
userId?: InputMaybe<Scalars['String']>;
};
@@ -2765,11 +2782,11 @@ export type ProjectPageStatsBlockCommentsFragment = { __typename?: 'Project', co
export type ProjectPageStatsBlockModelsFragment = { __typename?: 'Project', modelCount: { __typename?: 'ModelCollection', totalCount: number } };
export type ProjectPageStatsBlockTeamFragment = { __typename?: 'Project', id: string, role?: string | null, name: string, allowPublicComments: boolean, visibility: ProjectVisibility, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> | null };
export type ProjectPageStatsBlockTeamFragment = { __typename?: 'Project', id: string, role?: string | null, name: string, allowPublicComments: boolean, visibility: ProjectVisibility, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null };
export type ProjectPageStatsBlockVersionsFragment = { __typename?: 'Project', versionCount: { __typename?: 'VersionCollection', totalCount: number } };
export type ProjectPageTeamDialogFragment = { __typename?: 'Project', id: string, name: string, role?: string | null, allowPublicComments: boolean, visibility: ProjectVisibility, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> | null };
export type ProjectPageTeamDialogFragment = { __typename?: 'Project', id: string, name: string, role?: string | null, allowPublicComments: boolean, visibility: ProjectVisibility, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null };
export type OnUserProjectsUpdateSubscriptionVariables = Exact<{ [key: string]: never; }>;
@@ -2812,7 +2829,7 @@ export type ActiveUserMainMetadataQuery = { __typename?: 'Query', activeUser?: {
export type CreateOnboardingProjectMutationVariables = Exact<{ [key: string]: never; }>;
export type CreateOnboardingProjectMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', createForOnboarding: { __typename?: 'Project', id: string, createdAt: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, updatedAt: string, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }> }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } } } };
export type CreateOnboardingProjectMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', createForOnboarding: { __typename?: 'Project', id: string, createdAt: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, updatedAt: string, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }> }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } } } };
export type FinishOnboardingMutationVariables = Exact<{ [key: string]: never; }>;
@@ -2847,7 +2864,7 @@ export type UserSearchQueryVariables = Exact<{
}>;
export type UserSearchQuery = { __typename?: 'Query', userSearch: { __typename?: 'UserSearchResultCollection', cursor?: string | null, items: Array<{ __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null }> } };
export type UserSearchQuery = { __typename?: 'Query', userSearch: { __typename?: 'UserSearchResultCollection', cursor?: string | null, items: Array<{ __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, role?: string | null }> } };
export type ServerInfoBlobSizeLimitQueryVariables = Exact<{ [key: string]: never; }>;
@@ -2862,6 +2879,11 @@ export type ProjectModelsSelectorValuesQueryVariables = Exact<{
export type ProjectModelsSelectorValuesQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, models: { __typename?: 'ModelCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string }> } } };
export type MainServerInfoDataQueryVariables = Exact<{ [key: string]: never; }>;
export type MainServerInfoDataQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', adminContact?: string | null, blobSizeLimitBytes: number, canonicalUrl?: string | null, company?: string | null, description?: string | null, guestModeEnabled: boolean, inviteOnly?: boolean | null, name: string, termsOfService?: string | null, version?: string | null } };
export type ServerVersionInfoQueryVariables = Exact<{ [key: string]: never; }>;
@@ -2897,7 +2919,7 @@ export type CreateProjectMutationVariables = Exact<{
}>;
export type CreateProjectMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', create: { __typename?: 'Project', id: string, createdAt: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, updatedAt: string, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }> }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } } } };
export type CreateProjectMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', create: { __typename?: 'Project', id: string, createdAt: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, updatedAt: string, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }> }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } } } };
export type UpdateModelMutationVariables = Exact<{
input: UpdateModelInput;
@@ -2926,7 +2948,7 @@ export type InviteProjectUserMutationVariables = Exact<{
}>;
export type InviteProjectUserMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', invites: { __typename?: 'ProjectInviteMutations', batchCreate: { __typename?: 'Project', id: string, name: string, role?: string | null, allowPublicComments: boolean, visibility: ProjectVisibility, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> | null } } } };
export type InviteProjectUserMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', invites: { __typename?: 'ProjectInviteMutations', batchCreate: { __typename?: 'Project', id: string, name: string, role?: string | null, allowPublicComments: boolean, visibility: ProjectVisibility, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null } } } };
export type CancelProjectInviteMutationVariables = Exact<{
projectId: Scalars['ID'];
@@ -2934,7 +2956,7 @@ export type CancelProjectInviteMutationVariables = Exact<{
}>;
export type CancelProjectInviteMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', invites: { __typename?: 'ProjectInviteMutations', cancel: { __typename?: 'Project', id: string, name: string, role?: string | null, allowPublicComments: boolean, visibility: ProjectVisibility, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> | null } } } };
export type CancelProjectInviteMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', invites: { __typename?: 'ProjectInviteMutations', cancel: { __typename?: 'Project', id: string, name: string, role?: string | null, allowPublicComments: boolean, visibility: ProjectVisibility, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null } } } };
export type UpdateProjectMetadataMutationVariables = Exact<{
update: ProjectUpdateInput;
@@ -3006,7 +3028,7 @@ export type ProjectPageQueryQueryVariables = Exact<{
}>;
export type ProjectPageQueryQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, createdAt: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } }, projectInvite?: { __typename?: 'PendingStreamCollaborator', id: string, projectId: string, projectName: string, token?: string | null, invitedBy: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } } | null };
export type ProjectPageQueryQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, createdAt: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } }, projectInvite?: { __typename?: 'PendingStreamCollaborator', id: string, projectId: string, projectName: string, token?: string | null, invitedBy: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } } | null };
export type ProjectLatestModelsQueryVariables = Exact<{
projectId: Scalars['String'];
@@ -3112,7 +3134,7 @@ export type OnProjectUpdatedSubscriptionVariables = Exact<{
}>;
export type OnProjectUpdatedSubscription = { __typename?: 'Subscription', projectUpdated: { __typename?: 'ProjectUpdatedMessage', id: string, type: ProjectUpdatedMessageType, project?: { __typename?: 'Project', id: string, createdAt: string, name: string, updatedAt: string, role?: string | null, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } } | null } };
export type OnProjectUpdatedSubscription = { __typename?: 'Subscription', projectUpdated: { __typename?: 'ProjectUpdatedMessage', id: string, type: ProjectUpdatedMessageType, project?: { __typename?: 'Project', id: string, createdAt: string, name: string, updatedAt: string, role?: string | null, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null, role?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } } | null } };
export type OnProjectModelsUpdateSubscriptionVariables = Exact<{
id: Scalars['String'];
@@ -3294,7 +3316,7 @@ export type GetActiveUserQueryVariables = Exact<{ [key: string]: never; }>;
export type GetActiveUserQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, name: string, role?: string | null } | null };
export type ProjectPageProjectFragment = { __typename?: 'Project', id: string, createdAt: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } };
export type ProjectPageProjectFragment = { __typename?: 'Project', id: string, createdAt: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } };
export type ModelPageProjectFragment = { __typename?: 'Project', id: string, createdAt: string, name: string };
@@ -3347,7 +3369,7 @@ export const ViewerCommentsListItemFragmentDoc = {"kind":"Document","definitions
export const ViewerCommentBubblesDataFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerCommentBubblesData"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"viewedAt"}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}}]}}]} as unknown as DocumentNode<ViewerCommentBubblesDataFragment, unknown>;
export const ViewerCommentThreadFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerCommentThread"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewerCommentsListItem"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewerCommentBubblesData"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewerCommentsReplyItem"}}]}}]} as unknown as DocumentNode<ViewerCommentThreadFragment, unknown>;
export const ProjectPageProjectHeaderFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageProjectHeader"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}}]}}]} as unknown as DocumentNode<ProjectPageProjectHeaderFragment, unknown>;
export const ProjectPageTeamDialogFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}}]} as unknown as DocumentNode<ProjectPageTeamDialogFragment, unknown>;
export const ProjectPageTeamDialogFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]}}]} as unknown as DocumentNode<ProjectPageTeamDialogFragment, unknown>;
export const ProjectPageStatsBlockTeamFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageStatsBlockTeam"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageTeamDialog"}}]}}]} as unknown as DocumentNode<ProjectPageStatsBlockTeamFragment, unknown>;
export const ProjectPageStatsBlockVersionsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageStatsBlockVersions"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode<ProjectPageStatsBlockVersionsFragment, unknown>;
export const ProjectPageStatsBlockModelsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageStatsBlockModels"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"modelCount"},"name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode<ProjectPageStatsBlockModelsFragment, unknown>;
@@ -3364,9 +3386,10 @@ export const FinishOnboardingDocument = {"kind":"Document","definitions":[{"kind
export const AuthServerInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AuthServerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AuthStategiesServerInfoFragment"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerTermsOfServicePrivacyPolicyFragment"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AuthRegisterPanelServerInfo"}}]}}]}},...AuthStategiesServerInfoFragmentFragmentDoc.definitions,...ServerTermsOfServicePrivacyPolicyFragmentFragmentDoc.definitions,...AuthRegisterPanelServerInfoFragmentDoc.definitions]} as unknown as DocumentNode<AuthServerInfoQuery, AuthServerInfoQueryVariables>;
export const AuthorizableAppMetadataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AuthorizableAppMetadata"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"app"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"trustByDefault"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUrl"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]}}]}}]} as unknown as DocumentNode<AuthorizableAppMetadataQuery, AuthorizableAppMetadataQueryVariables>;
export const MentionsUserSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MentionsUserSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"emailOnly"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}},"defaultValue":{"kind":"BooleanValue","value":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userSearch"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"5"}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"NullValue"}},{"kind":"Argument","name":{"kind":"Name","value":"archived"},"value":{"kind":"BooleanValue","value":false}},{"kind":"Argument","name":{"kind":"Name","value":"emailOnly"},"value":{"kind":"Variable","name":{"kind":"Name","value":"emailOnly"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}}]}}]}}]}}]} as unknown as DocumentNode<MentionsUserSearchQuery, MentionsUserSearchQueryVariables>;
export const UserSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"archived"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userSearch"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"archived"},"value":{"kind":"Variable","name":{"kind":"Name","value":"archived"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}}]}}]}}]}}]} as unknown as DocumentNode<UserSearchQuery, UserSearchQueryVariables>;
export const UserSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"archived"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userSearch"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"archived"},"value":{"kind":"Variable","name":{"kind":"Name","value":"archived"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]}}]} as unknown as DocumentNode<UserSearchQuery, UserSearchQueryVariables>;
export const ServerInfoBlobSizeLimitDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerInfoBlobSizeLimit"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"blobSizeLimitBytes"}}]}}]}}]} as unknown as DocumentNode<ServerInfoBlobSizeLimitQuery, ServerInfoBlobSizeLimitQueryVariables>;
export const ProjectModelsSelectorValuesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectModelsSelectorValues"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"100"}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonModelSelectorModel"}}]}}]}}]}}]}},...CommonModelSelectorModelFragmentDoc.definitions]} as unknown as DocumentNode<ProjectModelsSelectorValuesQuery, ProjectModelsSelectorValuesQueryVariables>;
export const MainServerInfoDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MainServerInfoData"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminContact"}},{"kind":"Field","name":{"kind":"Name","value":"blobSizeLimitBytes"}},{"kind":"Field","name":{"kind":"Name","value":"canonicalUrl"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"guestModeEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"inviteOnly"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"termsOfService"}},{"kind":"Field","name":{"kind":"Name","value":"version"}}]}}]}}]} as unknown as DocumentNode<MainServerInfoDataQuery, MainServerInfoDataQueryVariables>;
export const ServerVersionInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerVersionInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}}]}}]}}]} as unknown as DocumentNode<ServerVersionInfoQuery, ServerVersionInfoQueryVariables>;
export const SearchProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SearchProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"search"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"onlyWithRoles"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},"defaultValue":{"kind":"NullValue"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"search"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"onlyWithRoles"},"value":{"kind":"Variable","name":{"kind":"Name","value":"onlyWithRoles"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FormSelectProjects_Project"}}]}}]}}]}}]}},...FormSelectProjects_ProjectFragmentDoc.definitions]} as unknown as DocumentNode<SearchProjectsQuery, SearchProjectsQueryVariables>;
export const CreateModelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateModel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateModelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}}]}},...ProjectPageLatestItemsModelItemFragmentDoc.definitions,...PendingFileUploadFragmentDoc.definitions,...ProjectPageModelsCardRenameDialogFragmentDoc.definitions,...ProjectPageModelsCardDeleteDialogFragmentDoc.definitions,...ProjectPageModelsActionsFragmentDoc.definitions]} as unknown as DocumentNode<CreateModelMutation, CreateModelMutationVariables>;
@@ -29,6 +29,7 @@ export const userSearchQuery = graphql(`
company
avatar
verified
role
}
}
}
@@ -0,0 +1,29 @@
import { useQuery } from '@vue/apollo-composable'
import { graphql } from '~~/lib/common/generated/gql'
const serverInfoQuery = graphql(`
query MainServerInfoData {
serverInfo {
adminContact
blobSizeLimitBytes
canonicalUrl
company
description
guestModeEnabled
inviteOnly
name
termsOfService
version
}
}
`)
export function useServerInfo() {
const { result } = useQuery(serverInfoQuery)
const serverInfo = computed(() => result.value?.serverInfo)
const isGuestMode = computed(() => !!serverInfo.value?.guestModeEnabled)
return { serverInfo, isGuestMode }
}
@@ -1,4 +1,4 @@
import { Roles } from '@speckle/shared'
import { Nullable, Roles, ServerRoles } from '@speckle/shared'
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
import { ProjectPageTeamDialogFragment } from '~~/lib/common/generated/gql/graphql'
import { ProjectCollaboratorListItem } from '~~/lib/projects/helpers/components'
@@ -12,7 +12,7 @@ export function useTeamDialogInternals(params: {
props: { project }
} = params
const { activeUser } = useActiveUser()
const { activeUser, isGuest: isServerGuest } = useActiveUser()
const collaboratorListItems = computed((): ProjectCollaboratorListItem[] => {
const results: ProjectCollaboratorListItem[] = []
@@ -23,7 +23,8 @@ export function useTeamDialogInternals(params: {
title: invitedUser.title,
user: invitedUser.user || null,
role: invitedUser.role,
inviteId: invitedUser.inviteId
inviteId: invitedUser.inviteId,
serverRole: (invitedUser.user?.role || null) as Nullable<ServerRoles>
})
}
@@ -33,7 +34,8 @@ export function useTeamDialogInternals(params: {
title: collaborator.user.name,
user: collaborator.user,
role: collaborator.role,
inviteId: null
inviteId: null,
serverRole: collaborator.user.role as ServerRoles
})
}
@@ -54,6 +56,7 @@ export function useTeamDialogInternals(params: {
return {
collaboratorListItems,
isOwner,
canLeaveProject
canLeaveProject,
isServerGuest
}
}
@@ -1,4 +1,4 @@
import { Nullable, Roles, StreamRoles } from '@speckle/shared'
import { Nullable, Roles, ServerRoles, StreamRoles } from '@speckle/shared'
import { LimitedUserAvatarFragment } from '~~/lib/common/generated/gql/graphql'
export type ProjectCollaboratorListItem = {
@@ -7,6 +7,7 @@ export type ProjectCollaboratorListItem = {
user: Nullable<LimitedUserAvatarFragment>
role: string
inviteId: Nullable<string>
serverRole: Nullable<ServerRoles>
}
export type SelectableStreamRole = StreamRoles | 'delete'
@@ -58,7 +58,10 @@ export const mockProjectPageQuery = apolloMockRequestWithDefaults<
team: fakeUsers.map((u) => ({
__typename: 'ProjectCollaborator',
role: Roles.Stream.Contributor,
user: u
user: {
...u,
role: Roles.Server.User
}
})),
invitedTeam: null
},
+2 -1
View File
@@ -27,7 +27,8 @@ const config = {
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
'vue/component-name-in-template-casing': ['warn', 'kebab-case']
'vue/component-name-in-template-casing': ['warn', 'kebab-case'],
'vue/require-default-prop': 'off'
}
},
{
@@ -18,6 +18,7 @@ export const streamCollaboratorFieldsFragment = gql`
role
company
avatar
serverRole
}
`
@@ -54,6 +54,63 @@ export type ActivityCollection = {
totalCount: Scalars['Int'];
};
export type AdminInviteList = {
__typename?: 'AdminInviteList';
cursor?: Maybe<Scalars['String']>;
items: Array<ServerInvite>;
totalCount: Scalars['Int'];
};
export type AdminQueries = {
__typename?: 'AdminQueries';
inviteList: AdminInviteList;
projectList: ProjectCollection;
serverStatistics: ServerStatistics;
userList: AdminUserList;
};
export type AdminQueriesInviteListArgs = {
cursor?: InputMaybe<Scalars['String']>;
limit?: Scalars['Int'];
query?: InputMaybe<Scalars['String']>;
};
export type AdminQueriesProjectListArgs = {
cursor?: InputMaybe<Scalars['String']>;
limit?: Scalars['Int'];
orderBy?: InputMaybe<Scalars['String']>;
query?: InputMaybe<Scalars['String']>;
visibility?: InputMaybe<Scalars['String']>;
};
export type AdminQueriesUserListArgs = {
cursor?: InputMaybe<Scalars['String']>;
limit?: Scalars['Int'];
query?: InputMaybe<Scalars['String']>;
role?: InputMaybe<ServerRole>;
};
export type AdminUserList = {
__typename?: 'AdminUserList';
cursor?: Maybe<Scalars['String']>;
items: Array<AdminUserListItem>;
totalCount: Scalars['Int'];
};
export type AdminUserListItem = {
__typename?: 'AdminUserListItem';
avatar?: Maybe<Scalars['String']>;
company?: Maybe<Scalars['String']>;
email?: Maybe<Scalars['String']>;
id: Scalars['ID'];
name: Scalars['String'];
role?: Maybe<Scalars['String']>;
verified?: Maybe<Scalars['Boolean']>;
};
export type AdminUsersListCollection = {
__typename?: 'AdminUsersListCollection';
items: Array<AdminUsersListItem>;
@@ -369,6 +426,7 @@ export type Commit = {
authorAvatar?: Maybe<Scalars['String']>;
authorId?: Maybe<Scalars['String']>;
authorName?: Maybe<Scalars['String']>;
branch?: Maybe<Branch>;
branchName?: Maybe<Scalars['String']>;
/**
* The total number of comments for this commit. To actually get the comments, use the comments query and pass in a resource array consisting of of this commit's id.
@@ -709,6 +767,8 @@ export type ModelMutationsUpdateArgs = {
export type ModelVersionsFilter = {
/** Make sure these specified versions are always loaded first */
priorityIds?: InputMaybe<Array<Scalars['String']>>;
/** Only return versions specified in `priorityIds` */
priorityIdsOnly?: InputMaybe<Scalars['Boolean']>;
};
export type ModelsTreeItem = {
@@ -1593,11 +1653,16 @@ export type Query = {
_?: Maybe<Scalars['String']>;
/** Gets the profile of the authenticated user or null if not authenticated */
activeUser?: Maybe<User>;
/** All the streams of the server. Available to admins only. */
admin: AdminQueries;
/**
* All the streams of the server. Available to admins only.
* @deprecated use admin.projectList instead
*/
adminStreams?: Maybe<StreamCollection>;
/**
* Get all (or search for specific) users, registered or invited, from the server in a paginated view.
* The query looks for matches in name, company and email.
* @deprecated use admin.UserList instead
*/
adminUsers?: Maybe<AdminUsersListCollection>;
/** Gets a specific app from the server. */
@@ -1629,6 +1694,7 @@ export type Query = {
*/
projectInvite?: Maybe<PendingStreamCollaborator>;
serverInfo: ServerInfo;
/** @deprecated use admin.serverStatistics instead */
serverStats: ServerStats;
/**
* Returns a specific stream. Will throw an authorization error if active user isn't authorized
@@ -1795,7 +1861,6 @@ export enum ResourceType {
Stream = 'stream'
}
/** Available roles. */
export type Role = {
__typename?: 'Role';
description: Scalars['String'];
@@ -1854,10 +1919,12 @@ export type ServerInfo = {
canonicalUrl?: Maybe<Scalars['String']>;
company?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
guestModeEnabled: Scalars['Boolean'];
inviteOnly?: Maybe<Scalars['Boolean']>;
name: Scalars['String'];
roles: Array<Maybe<Role>>;
scopes: Array<Maybe<Scope>>;
roles: Array<Role>;
scopes: Array<Scope>;
serverRoles: Array<ServerRoleItem>;
termsOfService?: Maybe<Scalars['String']>;
version?: Maybe<Scalars['String']>;
};
@@ -1866,6 +1933,7 @@ export type ServerInfoUpdateInput = {
adminContact?: InputMaybe<Scalars['String']>;
company?: InputMaybe<Scalars['String']>;
description?: InputMaybe<Scalars['String']>;
guestModeEnabled?: InputMaybe<Scalars['Boolean']>;
inviteOnly?: InputMaybe<Scalars['Boolean']>;
name: Scalars['String'];
termsOfService?: InputMaybe<Scalars['String']>;
@@ -1886,9 +1954,23 @@ export type ServerInviteCreateInput = {
export enum ServerRole {
ServerAdmin = 'SERVER_ADMIN',
ServerArchivedUser = 'SERVER_ARCHIVED_USER',
ServerGuest = 'SERVER_GUEST',
ServerUser = 'SERVER_USER'
}
export type ServerRoleItem = {
__typename?: 'ServerRoleItem';
id: Scalars['String'];
title: Scalars['String'];
};
export type ServerStatistics = {
__typename?: 'ServerStatistics';
totalPendingInvites: Scalars['Int'];
totalProjectCount: Scalars['Int'];
totalUserCount: Scalars['Int'];
};
export type ServerStats = {
__typename?: 'ServerStats';
/** An array of objects currently structured as { created_month: Date, count: int }. */
@@ -2056,6 +2138,7 @@ export type StreamCollaborator = {
id: Scalars['String'];
name: Scalars['String'];
role: Scalars['String'];
serverRole: Scalars['String'];
};
export type StreamCollection = {
@@ -2289,6 +2372,7 @@ export type SubscriptionUserViewerActivityArgs = {
export type SubscriptionViewerUserActivityBroadcastedArgs = {
sessionId?: InputMaybe<Scalars['String']>;
target: ViewerUpdateTrackingTarget;
};
@@ -2333,10 +2417,6 @@ export type User = {
/** Returns the apps you have created. */
createdApps?: Maybe<Array<ServerApp>>;
createdAt?: Maybe<Scalars['DateTime']>;
/**
* E-mail can be null, if it's requested for a user other than the authenticated one
* and the user isn't an admin
*/
email?: Maybe<Scalars['String']>;
/**
* All the streams that a active user has favorited.
@@ -2737,7 +2817,7 @@ export type StreamPendingAccessRequestsFragment = { __typename?: 'Stream', pendi
export type LimitedUserFieldsFragment = { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null };
export type StreamCollaboratorFieldsFragment = { __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null };
export type StreamCollaboratorFieldsFragment = { __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null, serverRole: string };
export type UsersOwnInviteFieldsFragment = { __typename?: 'PendingStreamCollaborator', id: string, inviteId: string, streamId: string, streamName: string, token?: string | null, invitedBy: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null } };
@@ -2817,27 +2897,32 @@ export type StreamObjectNoDataQuery = { __typename?: 'Query', stream?: { __typen
export type ServerInfoBlobSizeFieldsFragment = { __typename?: 'ServerInfo', blobSizeLimitBytes: number };
export type MainServerInfoFieldsFragment = { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null };
export type MainServerInfoFieldsFragment = { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null, guestModeEnabled: boolean };
export type ServerInfoRolesFieldsFragment = { __typename?: 'ServerInfo', roles: Array<{ __typename?: 'Role', name: string, description: string, resourceTarget: string } | null> };
export type ServerInfoRolesFieldsFragment = { __typename?: 'ServerInfo', serverRoles: Array<{ __typename?: 'ServerRoleItem', id: string, title: string }> };
export type ServerInfoScopesFieldsFragment = { __typename?: 'ServerInfo', scopes: Array<{ __typename?: 'Scope', name: string, description: string } | null> };
export type ServerInfoScopesFieldsFragment = { __typename?: 'ServerInfo', scopes: Array<{ __typename?: 'Scope', name: string, description: string }> };
export type MainServerInfoQueryVariables = Exact<{ [key: string]: never; }>;
export type MainServerInfoQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null } };
export type MainServerInfoQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null, guestModeEnabled: boolean } };
export type FullServerInfoQueryVariables = Exact<{ [key: string]: never; }>;
export type FullServerInfoQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null, blobSizeLimitBytes: number, roles: Array<{ __typename?: 'Role', name: string, description: string, resourceTarget: string } | null>, scopes: Array<{ __typename?: 'Scope', name: string, description: string } | null> } };
export type FullServerInfoQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', name: string, company?: string | null, description?: string | null, adminContact?: string | null, canonicalUrl?: string | null, termsOfService?: string | null, inviteOnly?: boolean | null, version?: string | null, guestModeEnabled: boolean, blobSizeLimitBytes: number, serverRoles: Array<{ __typename?: 'ServerRoleItem', id: string, title: string }>, scopes: Array<{ __typename?: 'Scope', name: string, description: string }> } };
export type ServerInfoBlobSizeLimitQueryVariables = Exact<{ [key: string]: never; }>;
export type ServerInfoBlobSizeLimitQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', blobSizeLimitBytes: number } };
export type AvailableServerRolesQueryVariables = Exact<{ [key: string]: never; }>;
export type AvailableServerRolesQuery = { __typename?: 'Query', serverInfo: { __typename?: 'ServerInfo', guestModeEnabled: boolean, serverRoles: Array<{ __typename?: 'ServerRoleItem', id: string, title: string }> } };
export type StreamCommitsQueryVariables = Exact<{
id: Scalars['String'];
}>;
@@ -2852,21 +2937,21 @@ export type StreamsQueryVariables = Exact<{
export type StreamsQuery = { __typename?: 'Query', streams?: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, company?: string | null, avatar?: string | null, role: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null, message?: string | null, authorId?: string | null, branchName?: string | null, authorName?: string | null, authorAvatar?: string | null, referencedObject: string }> | null } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null } | null };
export type CommonStreamFieldsFragment = { __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null };
export type CommonStreamFieldsFragment = { __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null, serverRole: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null };
export type StreamQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type StreamQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null } | null };
export type StreamQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null, serverRole: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null } | null };
export type StreamWithCollaboratorsQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type StreamWithCollaboratorsQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, isPublic: boolean, role?: string | null, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null }>, pendingCollaborators?: Array<{ __typename?: 'PendingStreamCollaborator', title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null } | null }> | null, pendingAccessRequests?: Array<{ __typename?: 'StreamAccessRequest', id: string, streamId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null } }> | null } | null };
export type StreamWithCollaboratorsQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, name: string, isPublic: boolean, role?: string | null, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null, serverRole: string }>, pendingCollaborators?: Array<{ __typename?: 'PendingStreamCollaborator', title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null } | null }> | null, pendingAccessRequests?: Array<{ __typename?: 'StreamAccessRequest', id: string, streamId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null } }> | null } | null };
export type StreamWithActivityQueryVariables = Exact<{
id: Scalars['String'];
@@ -2938,7 +3023,7 @@ export type ShareableStreamQueryVariables = Exact<{
}>;
export type ShareableStreamQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, isPublic: boolean, role?: string | null, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null }> } | null };
export type ShareableStreamQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, isPublic: boolean, role?: string | null, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null, serverRole: string }> } | null };
export type CommonUserFieldsFragment = { __typename?: 'User', id: string, email?: string | null, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, streams: { __typename?: 'StreamCollection', totalCount: number }, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null }> | null } | null };
@@ -2947,7 +3032,7 @@ export type UserFavoriteStreamsQueryVariables = Exact<{
}>;
export type UserFavoriteStreamsQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, email?: string | null, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, favoriteStreams: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null }, streams: { __typename?: 'StreamCollection', totalCount: number }, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null }> | null } | null } | null };
export type UserFavoriteStreamsQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, email?: string | null, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, hasPendingVerification?: boolean | null, profiles?: Record<string, unknown> | null, role?: string | null, favoriteStreams: { __typename?: 'StreamCollection', totalCount: number, cursor?: string | null, items?: Array<{ __typename?: 'Stream', id: string, name: string, description?: string | null, role?: string | null, isPublic: boolean, createdAt: string, updatedAt: string, commentCount: number, favoritedDate?: string | null, favoritesCount: number, collaborators: Array<{ __typename?: 'StreamCollaborator', id: string, name: string, role: string, company?: string | null, avatar?: string | null, serverRole: string }>, commits?: { __typename?: 'CommitCollection', totalCount: number } | null, branches?: { __typename?: 'BranchCollection', totalCount: number } | null }> | null }, streams: { __typename?: 'StreamCollection', totalCount: number }, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, createdAt?: string | null }> | null } | null } | null };
export type MainUserDataQueryVariables = Exact<{ [key: string]: never; }>;
@@ -3153,14 +3238,14 @@ export const MainServerInfoFields = gql`
termsOfService
inviteOnly
version
guestModeEnabled
}
`;
export const ServerInfoRolesFields = gql`
fragment ServerInfoRolesFields on ServerInfo {
roles {
name
description
resourceTarget
serverRoles {
id
title
}
}
`;
@@ -3179,6 +3264,7 @@ export const StreamCollaboratorFields = gql`
role
company
avatar
serverRole
}
`;
export const CommonStreamFields = gql`
@@ -3446,6 +3532,17 @@ export const ServerInfoBlobSizeLimit = gql`
}
}
${ServerInfoBlobSizeFields}`;
export const AvailableServerRoles = gql`
query AvailableServerRoles {
serverInfo {
serverRoles {
id
title
}
guestModeEnabled
}
}
`;
export const StreamCommits = gql`
query StreamCommits($id: String!) {
stream(id: $id) {
@@ -3865,10 +3962,10 @@ export const FullStreamAccessRequestFieldsFragmentDoc = {"kind":"Document","defi
export const StreamPendingAccessRequestsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"StreamPendingAccessRequests"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Stream"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pendingAccessRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullStreamAccessRequestFields"}}]}}]}}]} as unknown as DocumentNode<StreamPendingAccessRequestsFragment, unknown>;
export const UsersOwnInviteFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UsersOwnInviteFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingStreamCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"streamName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserFields"}}]}}]}}]} as unknown as DocumentNode<UsersOwnInviteFieldsFragment, unknown>;
export const ServerInfoBlobSizeFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerInfoBlobSizeFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"blobSizeLimitBytes"}}]}}]} as unknown as DocumentNode<ServerInfoBlobSizeFieldsFragment, unknown>;
export const MainServerInfoFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MainServerInfoFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"adminContact"}},{"kind":"Field","name":{"kind":"Name","value":"canonicalUrl"}},{"kind":"Field","name":{"kind":"Name","value":"termsOfService"}},{"kind":"Field","name":{"kind":"Name","value":"inviteOnly"}},{"kind":"Field","name":{"kind":"Name","value":"version"}}]}}]} as unknown as DocumentNode<MainServerInfoFieldsFragment, unknown>;
export const ServerInfoRolesFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerInfoRolesFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"roles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"resourceTarget"}}]}}]}}]} as unknown as DocumentNode<ServerInfoRolesFieldsFragment, unknown>;
export const MainServerInfoFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MainServerInfoFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"adminContact"}},{"kind":"Field","name":{"kind":"Name","value":"canonicalUrl"}},{"kind":"Field","name":{"kind":"Name","value":"termsOfService"}},{"kind":"Field","name":{"kind":"Name","value":"inviteOnly"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"guestModeEnabled"}}]}}]} as unknown as DocumentNode<MainServerInfoFieldsFragment, unknown>;
export const ServerInfoRolesFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerInfoRolesFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]} as unknown as DocumentNode<ServerInfoRolesFieldsFragment, unknown>;
export const ServerInfoScopesFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerInfoScopesFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"scopes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}}]} as unknown as DocumentNode<ServerInfoScopesFieldsFragment, unknown>;
export const StreamCollaboratorFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"StreamCollaboratorFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]} as unknown as DocumentNode<StreamCollaboratorFieldsFragment, unknown>;
export const StreamCollaboratorFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"StreamCollaboratorFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"serverRole"}}]}}]} as unknown as DocumentNode<StreamCollaboratorFieldsFragment, unknown>;
export const CommonStreamFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CommonStreamFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Stream"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"isPublic"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"commentCount"}},{"kind":"Field","name":{"kind":"Name","value":"collaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"StreamCollaboratorFields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"branches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"favoritedDate"}},{"kind":"Field","name":{"kind":"Name","value":"favoritesCount"}}]}}]} as unknown as DocumentNode<CommonStreamFieldsFragment, unknown>;
export const CommonUserFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CommonUserFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"hasPendingVerification"}},{"kind":"Field","name":{"kind":"Name","value":"profiles"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"streams"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode<CommonUserFieldsFragment, unknown>;
export const GetStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetStreamAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"}}]}}]}},...BasicStreamAccessRequestFieldsFragmentDoc.definitions]} as unknown as DocumentNode<GetStreamAccessRequestQuery, GetStreamAccessRequestQueryVariables>;
@@ -3894,6 +3991,7 @@ export const StreamObjectNoDataDocument = {"kind":"Document","definitions":[{"ki
export const MainServerInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MainServerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MainServerInfoFields"}}]}}]}},...MainServerInfoFieldsFragmentDoc.definitions]} as unknown as DocumentNode<MainServerInfoQuery, MainServerInfoQueryVariables>;
export const FullServerInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FullServerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MainServerInfoFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoRolesFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoScopesFields"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoBlobSizeFields"}}]}}]}},...MainServerInfoFieldsFragmentDoc.definitions,...ServerInfoRolesFieldsFragmentDoc.definitions,...ServerInfoScopesFieldsFragmentDoc.definitions,...ServerInfoBlobSizeFieldsFragmentDoc.definitions]} as unknown as DocumentNode<FullServerInfoQuery, FullServerInfoQueryVariables>;
export const ServerInfoBlobSizeLimitDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerInfoBlobSizeLimit"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerInfoBlobSizeFields"}}]}}]}},...ServerInfoBlobSizeFieldsFragmentDoc.definitions]} as unknown as DocumentNode<ServerInfoBlobSizeLimitQuery, ServerInfoBlobSizeLimitQueryVariables>;
export const AvailableServerRolesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AvailableServerRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}},{"kind":"Field","name":{"kind":"Name","value":"guestModeEnabled"}}]}}]}}]} as unknown as DocumentNode<AvailableServerRolesQuery, AvailableServerRolesQueryVariables>;
export const StreamCommitsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamCommits"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"authorAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"branchName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}}]}}]}}]}}]}}]} as unknown as DocumentNode<StreamCommitsQuery, StreamCommitsQueryVariables>;
export const StreamsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Streams"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"isPublic"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"commentCount"}},{"kind":"Field","name":{"kind":"Name","value":"collaborators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}},{"kind":"Field","name":{"kind":"Name","value":"branchName"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"authorAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"branches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"favoritedDate"}},{"kind":"Field","name":{"kind":"Name","value":"favoritesCount"}}]}}]}}]}}]} as unknown as DocumentNode<StreamsQuery, StreamsQueryVariables>;
export const StreamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Stream"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonStreamFields"}}]}}]}},...CommonStreamFieldsFragmentDoc.definitions,...StreamCollaboratorFieldsFragmentDoc.definitions]} as unknown as DocumentNode<StreamQuery, StreamQueryVariables>;
+16 -4
View File
@@ -16,15 +16,15 @@ export const mainServerInfoFieldsFragment = gql`
termsOfService
inviteOnly
version
guestModeEnabled
}
`
export const serverInfoRolesFieldsFragment = gql`
fragment ServerInfoRolesFields on ServerInfo {
roles {
name
description
resourceTarget
serverRoles {
id
title
}
}
`
@@ -75,3 +75,15 @@ export const serverInfoBlobSizeLimitQuery = gql`
}
${serverInfoBlobSizeFragment}
`
export const availableServerRolesQuery = gql`
query AvailableServerRoles {
serverInfo {
serverRoles {
id
title
}
guestModeEnabled
}
}
`
@@ -2,7 +2,7 @@
<users-list-user-item
v-if="registeredUser"
:user="registeredUser"
:roles="roles"
:allow-guest="allowGuest"
@change-role="$emit('user-change-role', $event)"
@delete="$emit('user-delete', $event)"
/>
@@ -34,10 +34,7 @@ export default Vue.extend({
return !!(val.invitedUser || val.registeredUser)
}
},
roles: {
type: Array as PropType<{ text: string; value: string }[]>,
required: true
}
allowGuest: { type: Boolean }
},
computed: {
registeredUser(): MaybeFalsy<User> {
@@ -35,25 +35,18 @@
<v-col cols="3" class="d-flex align-center text-right">
<v-icon small class="mr-2">
{{
selfUser.role === 'server:admin'
selfUser.role === serverRoles.Admin
? 'mdi-key'
: selfUser.role === 'server:user'
? 'mdi-account'
: 'mdi-account-off'
: selfUser.role === serverRoles.ArchivedUser
? 'mdi-account-off'
: 'mdi-account'
}}
</v-icon>
<v-select
v-tooltip="'Change role'"
:value="selfUser.role"
:items="roles"
dense
filled
rounded
hide-details
@change="(e) => $emit('change-role', { user, role: e })"
></v-select>
<!-- </v-col>
<v-col cols="1" class="text-right"> -->
<user-role-select
:allow-guest="allowGuest"
:role="selfUser.role"
@update:role="(e) => $emit('change-role', { user, role: e })"
/>
<v-btn
v-tooltip="'Delete user'"
small
@@ -67,18 +60,23 @@
</v-row>
</template>
<script>
import UserRoleSelect from '@/main/components/common/UserRoleSelect.vue'
import { Roles } from '@speckle/shared'
export default {
name: 'UsersListUserItem',
components: {
UserAvatar: () => import('@/main/components/common/UserAvatar')
UserAvatar: () => import('@/main/components/common/UserAvatar'),
UserRoleSelect
},
props: {
user: { type: Object, default: () => null },
roles: { type: Array, default: () => [] }
allowGuest: { type: Boolean }
},
data() {
return {
selfUser: this.user
selfUser: this.user,
serverRoles: Roles.Server
}
}
}
@@ -0,0 +1,53 @@
<template>
<v-select
v-model="finalRole"
:items="roles"
dense
filled
rounded
hide-details
:label="label"
></v-select>
</template>
<script setup lang="ts">
import { ServerRoles, RoleInfo, Roles } from '@speckle/shared'
import { computed } from 'vue'
const emit = defineEmits<{
(e: 'update:role', val: ServerRoles): void
}>()
const props = withDefaults(
defineProps<{
role: ServerRoles
allowGuest?: boolean
allowAdmin?: boolean
label?: string
forInvite?: boolean
}>(),
{
allowAdmin: true
}
)
const roles = computed(() =>
Object.values(Roles.Server)
.filter((r) => {
if (r === Roles.Server.Admin) return props.allowAdmin
if (r === Roles.Server.ArchivedUser) return !props.forInvite
return true
})
.map((r) => ({
text: RoleInfo.Server[r],
value: r,
disabled: r === Roles.Server.Guest && !props.allowGuest
}))
)
const finalRole = computed({
get: () => props.role,
set: (newVal) => {
emit('update:role', newVal)
}
})
</script>
@@ -42,13 +42,14 @@
<no-data-placeholder v-if="quickUser">
<h2>Welcome {{ quickUser.name.split(' ')[0] }}!</h2>
<p class="caption">
Once you create a stream and start sending some data, your activity will
show up here.
Once you {{ isGuestUser ? 'join' : 'create' }} a stream and start sending
some data, your activity will show up here.
</p>
<template #actions>
<v-list rounded class="transparent">
<v-list-item
v-if="!isGuestUser"
link
class="primary mb-4"
dark
@@ -83,6 +84,7 @@ import { useQuery } from '@vue/apollo-composable'
import { computed } from 'vue'
import { AppLocalStorage } from '@/utils/localStorage'
import { SKIPPABLE_ACTION_TYPES } from '@/main/lib/feed/helpers/activityStream'
import { isGuest } from '@/main/lib/core/helpers/users'
export default {
name: 'FeedTimeline',
@@ -174,12 +176,15 @@ export default {
quickUser: activeUser {
id
name
role
}
}
`)
const quickUser = computed(() => quickUserResult.value?.quickUser || null)
const isGuestUser = computed(() => isGuest(quickUser.value))
return {
isGuestUser,
quickUser,
groupedTimeline,
timeline,
@@ -20,8 +20,8 @@
</template>
<v-list dense>
<v-list-item
v-for="(item, index) in roles.filter((r) => r.name !== user.role)"
:key="index"
v-for="item in availableRoles"
:key="item.name"
@click="$emit('update-user-role', { user, role: item })"
>
<v-list-item-action>
@@ -41,9 +41,15 @@
</div>
</template>
<script lang="ts">
import { StreamCollaboratorFieldsFragment, Role } from '@/graphql/generated/graphql'
import { StreamCollaboratorFieldsFragment } from '@/graphql/generated/graphql'
import { Roles } from '@speckle/shared'
import Vue, { PropType } from 'vue'
type RoleItem = {
name: string
description: string
}
export default Vue.extend({
name: 'StreamCollaboratorRow',
components: {
@@ -54,8 +60,16 @@ export default Vue.extend({
type: Object as PropType<StreamCollaboratorFieldsFragment>,
required: true
},
roles: { type: Array as PropType<Role[]>, required: true },
roles: { type: Array as PropType<RoleItem[]>, required: true },
disabled: { type: Boolean, default: false }
},
computed: {
availableRoles(): RoleItem[] {
const baseRoles = this.roles.filter((r) => r.name !== this.user.role)
if (this.user.serverRole !== Roles.Server.Guest) return baseRoles
return baseRoles.filter((r) => r.name !== Roles.Stream.Owner)
}
}
})
</script>
@@ -49,7 +49,6 @@
</template>
<script lang="ts">
import {
Role,
StreamWithCollaboratorsQuery,
StreamCollaborator
} from '@/graphql/generated/graphql'
@@ -57,8 +56,13 @@ import Vue, { PropType } from 'vue'
import type { Get } from 'type-fest'
import StreamCollaboratorRow from '@/main/components/stream/collaborators/StreamCollaboratorRow.vue'
import SectionCard from '@/main/components/common/SectionCard.vue'
import { Roles } from '@/helpers/mainConstants'
import StreamPendingCollaboratorRow from '@/main/components/stream/collaborators/StreamPendingCollaboratorRow.vue'
import { Roles } from '@speckle/shared'
type RoleItem = {
name: string
description: string
}
export default Vue.extend({
name: 'StreamRoleCollaborators',
@@ -73,7 +77,7 @@ export default Vue.extend({
required: true
},
roles: {
type: Array as PropType<Role[]>,
type: Array as PropType<RoleItem[]>,
required: true
},
stream: {
@@ -81,10 +85,11 @@ export default Vue.extend({
NonNullable<Get<StreamWithCollaboratorsQuery, 'stream'>>
>,
required: true
}
},
disabledUpdates: Boolean
},
computed: {
role(): Role {
role(): RoleItem {
const role = this.roles.find((r) => r.name === this.roleName)
if (!role) {
throw new Error('Invalid role name provided')
@@ -93,7 +98,7 @@ export default Vue.extend({
return role
},
disabled(): boolean {
return this.stream.role !== Roles.Stream.Owner
return this.stream.role !== Roles.Stream.Owner || this.disabledUpdates
},
collaborators(): StreamCollaborator[] {
return this.stream.collaborators.filter((c) => c.role === this.roleName)
@@ -51,6 +51,15 @@
:disabled="loading"
label="message"
/>
<user-role-select
v-if="allowServerRoleSelect && !user"
class="mb-4"
label="Select server role"
for-invite
:allow-admin="isAdmin"
:allow-guest="isGuestMode"
:role.sync="role"
/>
<v-card-actions>
<v-btn block color="primary" type="submit" :disabled="loading">
Send invite
@@ -64,7 +73,7 @@
</template>
<script lang="ts">
import { gql } from '@apollo/client/core'
import Vue, { PropType } from 'vue'
import { PropType, defineComponent } from 'vue'
import { email, maxLength, noXss, required } from '@/main/lib/common/vuetify/validators'
import { Nullable, Optional } from '@/helpers/typeHelpers'
import { VFormInstance } from '@/helpers/vuetifyHelpers'
@@ -73,13 +82,18 @@ import type { FetchResult } from '@apollo/client/core'
import { UserSearchQuery } from '@/graphql/generated/graphql'
import BasicUserInfoRow from '@/main/components/user/BasicUserInfoRow.vue'
import { StreamEvents } from '@/main/lib/core/helpers/eventHubHelper'
import { useActiveUser } from '@/main/lib/core/composables/activeUser'
import UserRoleSelect from '@/main/components/common/UserRoleSelect.vue'
import { useServerInfo } from '@/main/lib/core/composables/server'
import { Roles } from '@speckle/shared'
type UserType = Get<UserSearchQuery, 'userSearch.items.0'>
export default Vue.extend({
export default defineComponent({
name: 'InviteDialog',
components: {
BasicUserInfoRow
BasicUserInfoRow,
UserRoleSelect
},
props: {
streamId: {
@@ -99,6 +113,11 @@ export default Vue.extend({
default: false
}
},
setup() {
const { isAdmin } = useActiveUser()
const { isGuestMode } = useServerInfo()
return { isAdmin, isGuestMode }
},
data() {
return {
localEmail: null as Nullable<string>,
@@ -107,6 +126,7 @@ export default Vue.extend({
error: null as Nullable<string>,
showError: false,
loading: false,
role: Roles.Server.User,
validation: {
emailRules: [required(), email()],
messageRules: [
@@ -117,6 +137,9 @@ export default Vue.extend({
}
},
computed: {
allowServerRoleSelect() {
return this.isAdmin || this.isGuestMode
},
isServerInvite(): boolean {
return !this.streamId
},
@@ -165,7 +188,8 @@ export default Vue.extend({
email: this.user ? null : this.localEmail,
message: this.message,
streamId: this.streamId,
userId: this.user ? this.user.id : null
userId: this.user ? this.user.id : null,
serverRole: this.allowServerRoleSelect && this.role ? this.role : null
}
}
})
@@ -180,7 +204,8 @@ export default Vue.extend({
variables: {
input: {
email: this.localEmail,
message: this.message
message: this.message,
serverRole: this.isAdmin ? this.role : null
}
}
})
@@ -142,7 +142,7 @@
:class="`${!$vuetify.theme.dark ? 'grey lighten-4' : 'grey darken-4'}`"
>
<v-toolbar
v-if="stream.role === 'stream:owner'"
v-if="stream.role === streamRoles.Owner"
class="transparent"
rounded
flat
@@ -176,7 +176,7 @@
<v-toolbar
v-tooltip="
`${
stream.role !== 'stream:owner'
stream.role !== streamRoles.Owner
? 'You do not have the right access level (' +
stream.role +
') to add collaborators.'
@@ -207,7 +207,7 @@
color="primary"
text
rounded
:disabled="stream.role !== 'stream:owner'"
:disabled="stream.role !== streamRoles.Owner"
@click="goToStreamCollabs()"
>
Manage
@@ -222,7 +222,7 @@
v-if="!stream.isPublic"
v-tooltip="
`${
stream.role !== 'stream:owner'
stream.role !== streamRoles.Owner
? 'You do not have the right access level (' +
stream.role +
') to invite people to this stream.'
@@ -241,7 +241,7 @@
color="primary"
text
rounded
:disabled="stream.role !== 'stream:owner'"
:disabled="stream.role !== streamRoles.Owner"
@click="showStreamInviteDialog()"
>
Send Invite
@@ -266,7 +266,7 @@ import {
UpdateStreamSettingsDocument
} from '@/graphql/generated/graphql'
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
import { Optional } from '@speckle/shared'
import { Optional, Roles } from '@speckle/shared'
import { useQuery } from '@vue/apollo-composable'
/**
@@ -355,7 +355,8 @@ export default {
...options,
url,
iFrameUrl,
resetUrlOptions
resetUrlOptions,
streamRoles: Roles.Stream
}
},
data() {
@@ -0,0 +1,30 @@
import { MainUserDataDocument } from '@/graphql/generated/graphql'
import { md5 } from '@speckle/shared'
import { useQuery } from '@vue/apollo-composable'
import { computed } from 'vue'
import { isAdmin } from '@/main/lib/core/helpers/users'
/**
* Get active user.
* undefined - not yet resolved
* null - resolved that user is a guest
*/
export function useActiveUser() {
const { result, refetch, onResult } = useQuery(MainUserDataDocument)
const activeUser = computed(() =>
result.value ? result.value.activeUser : undefined
)
const isLoggedIn = computed(() => !!activeUser.value?.id)
const distinctId = computed(() => {
const user = activeUser.value
if (!user) return user // null or undefined
if (!user.email) return null
return '@' + md5(user.email.toLowerCase()).toUpperCase()
})
const isAdminUser = computed(() => isAdmin(activeUser.value))
return { activeUser, isLoggedIn, distinctId, refetch, onResult, isAdmin: isAdminUser }
}
@@ -0,0 +1,12 @@
import { MainServerInfoDocument } from '@/graphql/generated/graphql'
import { useQuery } from '@vue/apollo-composable'
import { computed } from 'vue'
export function useServerInfo() {
const { result } = useQuery(MainServerInfoDocument)
const serverInfo = computed(() => result.value?.serverInfo)
const isGuestMode = computed(() => !!serverInfo.value?.guestModeEnabled)
return { serverInfo, isGuestMode }
}
@@ -0,0 +1,9 @@
import { Nullable, Roles } from '@speckle/shared'
export function isGuest(user?: { role?: string }) {
return user?.role === Roles.Server.Guest
}
export function isAdmin(user?: Nullable<{ role?: Nullable<string> }>) {
return user?.role === Roles.Server.Admin
}
@@ -9,7 +9,12 @@
<portal-target name="nav">
<!-- Main Actions -->
<v-list v-if="true" dense nav class="mb-0 pb-0">
<v-list-item class="primary elevation-5" dark @click="newStreamDialog = true">
<v-list-item
v-if="!isUserGuest"
class="primary elevation-5"
dark
@click="showNewStreamDialog = true"
>
<v-list-item-icon>
<v-icon class="">mdi-folder-plus</v-icon>
</v-list-item-icon>
@@ -17,7 +22,7 @@
<v-list-item-title>New Stream</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item @click="inviteUsersDialog = true">
<v-list-item v-if="!isUserGuest" @click="showInviteUsersDialog = true">
<v-list-item-icon>
<v-icon class="">mdi-email</v-icon>
</v-list-item-icon>
@@ -83,7 +88,12 @@
</v-list-item>
<portal-target name="subnav-commits" />
<v-list-item v-if="user && user.role === 'server:admin'" exact link to="/admin">
<v-list-item
v-if="user && user.role === serverRoles.Admin"
exact
link
to="/admin"
>
<v-list-item-icon>
<v-icon class="mt-2">mdi-cog-outline</v-icon>
</v-list-item-icon>
@@ -119,20 +129,25 @@
<!-- Dialogs -->
<v-dialog
v-model="newStreamDialog"
v-model="showNewStreamDialog"
max-width="500"
:fullscreen="$vuetify.breakpoint.xsOnly"
>
<new-stream @created="newStreamDialog = false" @close="newStreamDialog = false" />
<new-stream
@created="showNewStreamDialog = false"
@close="showNewStreamDialog = false"
/>
</v-dialog>
<invite-dialog :visible.sync="inviteUsersDialog" />
<invite-dialog :visible.sync="showInviteUsersDialog" />
</div>
</template>
<script>
import { mainUserDataQuery } from '@/graphql/user'
import InviteDialog from '@/main/dialogs/InviteDialog.vue'
import { setDarkTheme } from '@/main/utils/themeStateManager'
import { Roles } from '@speckle/shared'
import { isGuest } from '@/main/lib/core/helpers/users'
export default {
components: {
@@ -158,7 +173,31 @@ export default {
return {
newStreamDialog: false,
inviteUsersDialog: false,
shadowSpeckle: false
shadowSpeckle: false,
serverRoles: Roles.Server
}
},
computed: {
isUserGuest() {
return isGuest(this.user)
},
showNewStreamDialog: {
get() {
return this.newStreamDialog
},
set(val) {
if (this.isUserGuest) return
this.newStreamDialog = val
}
},
showInviteUsersDialog: {
get() {
return this.inviteUsersDialog
},
set(val) {
if (this.isUserGuest) return
this.inviteUsersDialog = val
}
}
},
mounted() {
@@ -169,7 +208,10 @@ export default {
if (navContent.scrollTop > 50) this.shadowSpeckle = true
else this.shadowSpeckle = false
})
this.$eventHub.$on('show-new-stream-dialog', () => (this.newStreamDialog = true))
this.$eventHub.$on(
'show-new-stream-dialog',
() => (this.showNewStreamDialog = true)
)
},
methods: {
switchTheme() {
@@ -28,7 +28,7 @@
<span v-else class="font-italic">No description provided</span>
</div>
<router-link
v-if="stream.role === 'stream:owner'"
v-if="stream.role === streamRoles.Owner"
:to="`/streams/${$route.params.streamId}/settings`"
class="text-decoration-none"
>
@@ -55,7 +55,7 @@
</template>
<!-- <v-divider class="mb-1"></v-divider> -->
<v-list-item
v-if="stream.role !== 'stream:reviewer'"
v-if="stream.role !== streamRoles.Reviewer"
v-tooltip.bottom="'Create a new branch to help categorise your commits.'"
link
@click="newBranchDialog = true"
@@ -226,6 +226,7 @@ import {
import { StreamEvents } from '@/main/lib/core/helpers/eventHubHelper'
import { useRoute } from '@/main/lib/core/composables/router'
import { useAllStreamBranches } from '@/main/lib/stream/composables/branches'
import { Roles } from '@speckle/shared'
export default {
components: {
@@ -247,7 +248,8 @@ export default {
localBranches,
refetchBranches,
totalBranchCount,
loading: branchesLoading
loading: branchesLoading,
streamRoles: Roles.Stream
}
},
data() {
@@ -48,12 +48,13 @@
<no-data-placeholder v-if="user && user.commits.totalCount === 0">
<h2>Welcome {{ user.name.split(' ')[0] }}!</h2>
<p class="caption">
Once you create a stream and start sending some data, your activity will show up
here.
Once you {{ isGuestUser ? 'join' : 'create' }} a stream and start sending some
data, your activity will show up here.
</p>
<template #actions>
<v-list rounded class="transparent">
<v-list-item
v-if="!isGuestUser"
link
class="primary mb-4"
dark
@@ -96,6 +97,7 @@ import {
deleteCommitsFromCachedCommitsQuery,
disabledCheckboxMessage
} from '@/main/lib/stream/services/commitMultiActions'
import { isGuest } from '@/main/lib/core/helpers/users'
export default defineComponent({
name: 'TheCommits',
@@ -113,6 +115,7 @@ export default defineComponent({
activeUser {
id
name
role
commits(limit: 10, cursor: $cursor) {
totalCount
cursor
@@ -186,8 +189,10 @@ export default defineComponent({
})
const shareDialogCommitId = computed(() => shareDialogCommit.value?.id)
const shareDialogStreamId = computed(() => shareDialogCommit.value?.stream.id)
const isGuestUser = computed(() => isGuest(user.value))
return {
isGuestUser,
user,
commitItems,
totalCommitCount,
@@ -20,12 +20,13 @@
<no-data-placeholder v-if="user">
<h2>Welcome {{ user.name.split(' ')[0] }}!</h2>
<p class="caption">
Once you create a stream and start sending some data, your activity will show
up here.
Once you {{ isGuestUser ? 'join' : 'create' }} a stream and start sending some
data, your activity will show up here.
</p>
<template #actions>
<v-list rounded class="transparent">
<v-list-item
v-if="!isGuestUser"
link
class="primary mb-4"
dark
@@ -94,6 +95,8 @@ import StreamPreviewCard from '@/main/components/common/StreamPreviewCard.vue'
import NoDataPlaceholder from '@/main/components/common/NoDataPlaceholder.vue'
import { useQuery } from '@vue/apollo-composable'
import { computed } from 'vue'
import { Roles } from '@speckle/shared'
import { isGuest } from '@/main/lib/core/helpers/users'
export default {
name: 'TheStreams',
@@ -129,19 +132,23 @@ export default {
data() {
return {
streamFilter: 1,
infiniteId: 0
infiniteId: 0,
streamRoles: Roles.Stream
}
},
computed: {
isGuestUser() {
return isGuest(this.user)
},
filteredStreams() {
if (!this.streams) return []
if (this.streamFilter === 1) return this.streams.items
if (this.streamFilter === 2)
return this.streams.items.filter((s) => s.role === 'stream:owner')
return this.streams.items.filter((s) => s.role === this.streamRoles.Owner)
if (this.streamFilter === 3)
return this.streams.items.filter((s) => s.role === 'stream:contributor')
return this.streams.items.filter((s) => s.role === this.streamRoles.Contributor)
if (this.streamFilter === 4)
return this.streams.items.filter((s) => s.role === 'stream:reviewer')
return this.streams.items.filter((s) => s.role === this.streamRoles.Reviewer)
return this.streams.items
},
/**
@@ -169,9 +176,9 @@ export default {
},
checkFilter(role) {
if (this.streamFilter === 1) return true
if (this.streamFilter === 2 && role === 'stream:owner') return true
if (this.streamFilter === 3 && role === 'stream:contributor') return true
if (this.streamFilter === 4 && role === 'stream:reviewer') return true
if (this.streamFilter === 2 && role === this.streamRoles.Owner) return true
if (this.streamFilter === 3 && role === this.streamRoles.Contributor) return true
if (this.streamFilter === 4 && role === this.streamRoles.Reviewer) return true
return false
},
async infiniteHandler($state) {
@@ -18,13 +18,13 @@
</error-placeholder>
</v-container>
</template>
<script>
import { gql } from '@apollo/client/core'
import {
STANDARD_PORTAL_KEYS,
buildPortalStateMixin
} from '@/main/utils/portalStateManager'
import { Roles } from '@speckle/shared'
export default {
name: 'AdminPanel',
@@ -54,7 +54,7 @@ export default {
},
computed: {
isAdmin() {
return this.user?.role === 'server:admin'
return this.user?.role === Roles.Server.Admin
}
}
}
@@ -62,6 +62,13 @@
</v-chip>
</template>
</v-combobox>
<user-role-select
class="mb-4"
label="Select server role"
for-invite
:allow-guest="isGuestMode"
:role.sync="role"
/>
<p v-if="!selectedStream">Optionally invite users to stream.</p>
<stream-search-bar
v-if="!selectedStream"
@@ -87,7 +94,6 @@
</section-card>
</div>
</template>
<script>
import { gql } from '@apollo/client/core'
import { isEmailValid } from '@/plugins/authHelpers'
@@ -102,12 +108,15 @@ import {
BatchInviteToServerDocument
} from '@/graphql/generated/graphql'
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
import { Roles } from '@speckle/shared'
import UserRoleSelect from '@/main/components/common/UserRoleSelect.vue'
export default {
name: 'AdminInvites',
components: {
SectionCard: () => import('@/main/components/common/SectionCard'),
StreamSearchBar: () => import('@/main/components/common/SearchBar')
StreamSearchBar: () => import('@/main/components/common/SearchBar'),
UserRoleSelect
},
mixins: [buildPortalStateMixin([STANDARD_PORTAL_KEYS.Toolbar], 'admin-invites', 1)],
data() {
@@ -123,6 +132,7 @@ export default {
chips: [],
inputErrors: [],
selectedStream: null,
role: Roles.Server.User,
validation: {
messageRules: [
maxLength(1024, "Message can't be longer than 1024 characters"),
@@ -134,6 +144,9 @@ export default {
computed: {
submitable() {
return this.chips && this.chips.length !== 0
},
isGuestMode() {
return !!this.serverInfo?.guestModeEnabled
}
},
apollo: {
@@ -199,11 +212,13 @@ export default {
? targetEmails.map((e) => ({
email: e,
streamId,
message
message,
serverRole: this.role
}))
: targetEmails.map((e) => ({
email: e,
message
message,
serverRole: this.role
}))
try {
@@ -88,6 +88,11 @@ export default {
label: 'Invite-Only mode',
hint: 'Only users with an invitation will be able to join',
type: 'boolean'
},
guestModeEnabled: {
label: 'Guest mode',
hint: "Enable the 'Guest' server role, which allows users to only contribute to streams that they're invited to",
type: 'boolean'
}
}
}
@@ -106,6 +111,7 @@ export default {
methods: {
async saveEdit() {
this.loading = true
const changes = pick(this.serverModifications, Object.keys(this.serverDetails))
await this.$apollo.mutate({
mutation: gql`
mutation ($info: ServerInfoUpdateInput!) {
@@ -113,10 +119,21 @@ export default {
}
`,
variables: {
info: pick(this.serverModifications, Object.keys(this.serverDetails))
info: changes
},
update: (cache) => {
cache.writeQuery({
query: mainServerInfoQuery,
data: {
serverInfo: {
...this.serverInfo,
...changes
}
}
})
}
})
await this.$apollo.queries['serverInfo'].refetch()
// await this.$apollo.queries['serverInfo'].refetch()
this.loading = false
}
}
@@ -33,7 +33,7 @@
<div v-for="item in adminUsers.items" :key="item.id">
<users-list-item
:item="item"
:roles="availableRoles"
:allow-guest="serverInfo?.guestModeEnabled"
@user-change-role="changeUserRole"
@user-delete="initiateDeleteUser"
@invite-delete="deleteInvite"
@@ -111,11 +111,12 @@ import {
import {
DeleteInviteDocument,
ResendInviteDocument,
AdminUsersListDocument
AdminUsersListDocument,
AvailableServerRolesDocument
} from '@/graphql/generated/graphql'
import SectionCard from '@/main/components/common/SectionCard.vue'
import UsersListItem from '@/main/components/admin/UsersListItem.vue'
import { Roles } from '@/helpers/mainConstants'
import { RoleInfo } from '@speckle/shared'
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
// TODO: This needs a redesign, it's pretty unusable on small screens
@@ -140,12 +141,7 @@ export default {
},
data() {
return {
roleLookupTable: {
[Roles.Server.User]: 'User',
[Roles.Server.Admin]: 'Admin',
[Roles.Server.ArchivedUser]: 'Archived',
[Roles.Server.Guest]: 'Guest'
},
roleLookupTable: RoleInfo.Server,
adminUsers: {
items: [],
totalCount: 0
@@ -181,13 +177,6 @@ export default {
},
numberOfPages() {
return Math.ceil(this.adminUsers.totalCount / this.limit)
},
availableRoles() {
const roleItems = []
for (const role in this.roleLookupTable) {
roleItems.push({ text: this.roleLookupTable[role], value: role })
}
return roleItems
}
},
methods: {
@@ -328,6 +317,9 @@ export default {
query: this.q
}
}
},
serverInfo: {
query: AvailableServerRolesDocument
}
}
}
@@ -23,7 +23,7 @@
<v-col v-if="serverInfo && stream" cols="12">
<v-row>
<!-- Add contributors panel -->
<v-col v-if="isStreamOwner" cols="12">
<v-col v-if="canEditCollaborators" cols="12">
<section-card :elevation="4">
<v-progress-linear v-show="loading" indeterminate></v-progress-linear>
<template slot="header">
@@ -66,17 +66,18 @@
</template>
<!-- Users found -->
<basic-user-info-row
v-for="user in filteredSearchResults"
v-else
:key="user.id"
:user="user"
@click="showUserInviteDialog(user)"
>
<template #actions>
<v-btn color="primary">Invite</v-btn>
</template>
</basic-user-info-row>
<template v-else>
<basic-user-info-row
v-for="user in filteredSearchResults"
:key="user.id"
:user="user"
@click="showUserInviteDialog(user)"
>
<template #actions>
<v-btn color="primary">Invite</v-btn>
</template>
</basic-user-info-row>
</template>
</v-list>
</v-card-text>
<invite-dialog
@@ -90,7 +91,7 @@
</v-col>
<!-- No permissions warning -->
<v-col v-if="stream.role !== 'stream:owner'" cols="12">
<v-col v-if="!isStreamOwner" cols="12">
<v-alert type="warning" class="mb-0">
Your permission level ({{ stream.role ? stream.role : 'none' }}) is not
high enough to edit this stream's collaborators.
@@ -100,7 +101,9 @@
<!-- Stream access requests -->
<v-col
v-if="
isStreamOwner && pendingAccessRequests && pendingAccessRequests.length
canEditCollaborators &&
pendingAccessRequests &&
pendingAccessRequests.length
"
cols="12"
>
@@ -117,6 +120,7 @@
:role-name="role.name"
:roles="roles"
:stream="stream"
:disabled-updates="!canEditCollaborators"
@update-user-role="setUserPermissions"
@remove-user="removeUser"
@cancel-invite="cancelInvite"
@@ -154,13 +158,14 @@ import {
UpdateStreamPermissionDocument
} from '@/graphql/generated/graphql'
import { StreamEvents } from '@/main/lib/core/helpers/eventHubHelper'
import { Roles } from '@/helpers/mainConstants'
import { Roles, RoleInfo } from '@speckle/shared'
import LeaveStreamPanel from '@/main/components/stream/collaborators/LeaveStreamPanel.vue'
import { IsLoggedInMixin } from '@/main/lib/core/mixins/isLoggedInMixin'
import { vueWithMixins } from '@/helpers/typeHelpers'
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
import { AppLocalStorage } from '@/utils/localStorage'
import StreamAccessRequestBanner from '@/main/components/stream/StreamAccessRequestBanner.vue'
import { MainUserDataDocument } from '@/graphql/generated/graphql'
export default vueWithMixins(IsLoggedInMixin).extend({
// @vue/component
@@ -221,6 +226,9 @@ export default vueWithMixins(IsLoggedInMixin).extend({
serverInfo: {
prefetch: true,
query: fullServerInfoQuery
},
activeUser: {
query: MainUserDataDocument
}
},
computed: {
@@ -231,29 +239,23 @@ export default vueWithMixins(IsLoggedInMixin).extend({
return true
},
canEditCollaborators() {
return this.isStreamOwner && !this.isServerGuest
},
isStreamOwner() {
return this.stream?.role === Roles.Stream.Owner
},
isServerGuest() {
return this.activeUser?.role === Roles.Server.Guest
},
streamId() {
return this.$route.params.streamId
},
roles() {
if (this.serverInfo.roles.length === 0) return []
const temp = this.serverInfo.roles.filter((x) => x.resourceTarget === 'streams')
const ret = [null, null, null]
// World's most idiotic way of enforcing order
for (const role of temp) {
if (role.name === 'stream:owner') {
ret[0] = role
} else if (role.name === 'stream:contributor') {
ret[1] = role
} else if (role.name === 'stream:reviewer') {
ret[2] = role
} else {
ret.push(role)
}
}
return ret
return Object.values(Roles.Stream).map((r) => ({
name: r,
description: RoleInfo.Stream[r].description
}))
},
collaborators() {
if (!this.stream) return []
@@ -261,15 +263,17 @@ export default vueWithMixins(IsLoggedInMixin).extend({
},
reviewers() {
if (!this.stream) return []
return this.stream.collaborators.filter((u) => u.role === 'stream:reviewer')
return this.stream.collaborators.filter((u) => u.role === Roles.Stream.Reviewer)
},
contributors() {
if (!this.stream) return []
return this.stream.collaborators.filter((u) => u.role === 'stream:contributor')
return this.stream.collaborators.filter(
(u) => u.role === Roles.Stream.Contributor
)
},
owners() {
if (!this.stream) return []
return this.stream.collaborators.filter((u) => u.role === 'stream:owner')
return this.stream.collaborators.filter((u) => u.role === Roles.Stream.Owner)
},
pendingAccessRequests() {
return this.stream?.pendingAccessRequests
@@ -14,7 +14,7 @@
<template #header><b>Authorized Apps</b></template>
<user-authorised-apps />
</section-card>
<section-card expandable class="mt-6 mb-10">
<section-card v-if="!isGuestUser" expandable class="mt-6 mb-10">
<template #header><b>Developer Settings</b></template>
<v-alert type="info" color="primary" dense class="my-2 mx-4">
Heads up! The sections below are intended for developers.
@@ -50,6 +50,7 @@ import {
buildPortalStateMixin
} from '@/main/utils/portalStateManager'
import UserNotificationPreferences from '@/main/components/user/UserNotificationPreferences'
import { isGuest } from '@/main/lib/core/helpers/users'
export default {
name: 'TheProfileSelf',
@@ -71,6 +72,11 @@ export default {
update: (data) => data.activeUser
}
},
computed: {
isGuestUser() {
return isGuest(this.user)
}
},
methods: {
update() {
this.$apollo.queries.user.refetch()
@@ -54,6 +54,10 @@ input ProjectInviteCreateInput {
Defaults to the contributor role, if not specified
"""
role: String
"""
Can only be specified if guest mode is on or if the user is an admin
"""
serverRole: String
}
input ProjectInviteUseInput {
@@ -68,11 +72,13 @@ type ProjectInviteMutations {
"""
create(projectId: ID!, input: ProjectInviteCreateInput!): Project!
@hasScope(scope: "users:invite")
@hasServerRole(role: SERVER_USER)
"""
Batch invite to project
"""
batchCreate(projectId: ID!, input: [ProjectInviteCreateInput!]!): Project!
@hasScope(scope: "users:invite")
@hasServerRole(role: SERVER_USER)
"""
Accept or decline a project invite
@@ -82,29 +88,32 @@ type ProjectInviteMutations {
"""
Cancel a pending stream invite. Can only be invoked by a project owner.
"""
cancel(projectId: ID!, inviteId: String!): Project! @hasScope(scope: "users:invite")
cancel(projectId: ID!, inviteId: String!): Project!
@hasScope(scope: "users:invite")
@hasServerRole(role: SERVER_USER)
}
type ProjectMutations {
"""
Delete an existing project
"""
delete(id: String!): Boolean!
delete(id: String!): Boolean! @hasServerRole(role: SERVER_USER)
"""
Updates an existing project
"""
update(update: ProjectUpdateInput!): Project!
update(update: ProjectUpdateInput!): Project! @hasServerRole(role: SERVER_USER)
"""
Create onboarding/tutorial project
Create onboarding/tutorial project. If one is already created for the active user, that
one will be returned instead.
"""
createForOnboarding: Project!
"""
Create new project
"""
create(input: ProjectCreateInput): Project!
create(input: ProjectCreateInput): Project! @hasServerRole(role: SERVER_USER)
"""
Invite related mutations
@@ -114,7 +123,7 @@ type ProjectMutations {
"""
Update role for a collaborator
"""
updateRole(input: ProjectUpdateRoleInput!): Project!
updateRole(input: ProjectUpdateRoleInput!): Project! @hasServerRole(role: SERVER_USER)
"""
Leave a project. Only possible if you're not the last remaining owner.
@@ -124,7 +133,7 @@ type ProjectMutations {
extend type Mutation {
projectMutations: ProjectMutations!
@hasServerRole(role: SERVER_USER)
@hasServerRole(role: SERVER_GUEST)
@hasScope(scope: "streams:write")
}
@@ -241,7 +250,7 @@ extend type User {
Get all invitations to projects that the active user has
"""
projectInvites: [PendingStreamCollaborator!]!
@hasServerRole(role: SERVER_USER)
@hasServerRole(role: SERVER_GUEST)
@hasScope(scope: "streams:read")
@isOwner
}
@@ -12,22 +12,28 @@ type ServerInfo {
adminContact: String
canonicalUrl: String
termsOfService: String
roles: [Role]!
scopes: [Scope]!
roles: [Role!]!
@deprecated(
reason: "Use role constants from the @speckle/shared npm package instead"
)
scopes: [Scope!]!
inviteOnly: Boolean
guestModeEnabled: Boolean!
version: String
serverRoles: [ServerRoleItem!]!
}
"""
Available roles.
"""
type Role {
name: String!
description: String!
resourceTarget: String!
}
type ServerRoleItem {
id: String!
title: String!
}
"""
Available scopes.
"""
@@ -116,6 +116,7 @@ type StreamCollaborator {
role: String!
company: String
avatar: String
serverRole: String!
}
type PendingStreamCollaborator {
@@ -79,6 +79,10 @@ type ServerInvite {
input ServerInviteCreateInput {
email: String!
message: String
"""
Can only be specified if guest mode is on or if the user is an admin
"""
serverRole: String
}
input StreamInviteCreateInput {
@@ -90,4 +94,8 @@ input StreamInviteCreateInput {
Defaults to the contributor role, if not specified
"""
role: String
"""
Can only be specified if guest mode is on or if the user is an admin
"""
serverRole: String
}
@@ -126,26 +126,45 @@ export async function addStreamDeletedActivity(params: {
export async function addStreamClonedActivity(
params: {
sourceStreamId: string
newStreamId: string
newStream: StreamRecord
clonerId: string
},
options?: Partial<{ trx: Knex.Transaction }>
) {
const { trx } = options || {}
const { sourceStreamId, newStreamId, clonerId } = params
const { sourceStreamId, newStream, clonerId } = params
const newStreamId = newStream.id
await saveActivity(
{
streamId: newStreamId,
resourceType: ResourceTypes.Stream,
resourceId: newStreamId,
actionType: ActionTypes.Stream.Clone,
userId: clonerId,
info: { sourceStreamId, newStreamId, clonerId },
message: `User ${clonerId} cloned stream ${sourceStreamId} as ${newStreamId}`
},
{ trx }
)
const publishSubscriptions = async () =>
publish(UserSubscriptions.UserProjectsUpdated, {
userProjectsUpdated: {
id: newStreamId,
type: UserProjectsUpdatedMessageType.Added,
project: newStream
},
ownerId: clonerId
})
await Promise.all([
saveActivity(
{
streamId: newStreamId,
resourceType: ResourceTypes.Stream,
resourceId: newStreamId,
actionType: ActionTypes.Stream.Clone,
userId: clonerId,
info: { sourceStreamId, newStreamId, clonerId },
message: `User ${clonerId} cloned stream ${sourceStreamId} as ${newStreamId}`
},
{ trx }
),
!trx ? publishSubscriptions() : null
])
if (trx) {
// can't await this, cause it'll block everything
void trx.executionPromise.then(publishSubscriptions)
}
}
/**
@@ -107,7 +107,13 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => {
const validInvite = await validateServerInvite(user.email, req.session.token)
// create the user
const myUser = await findOrCreateUser({ user, rawProfile: req.user._json })
const myUser = await findOrCreateUser({
user: {
...user,
role: validInvite?.serverRole
},
rawProfile: req.user._json
})
// ID is used later for verifying access token
req.user.id = myUser.id
@@ -84,7 +84,13 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => {
const validInvite = await validateServerInvite(user.email, req.session.token)
// create the user
const myUser = await findOrCreateUser({ user, rawProfile: profile._raw })
const myUser = await findOrCreateUser({
user: {
...user,
role: validInvite?.serverRole
},
rawProfile: profile._raw
})
// use the invite
await finalizeInvitedServerRegistration(user.email, myUser.id)
@@ -78,7 +78,13 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => {
const validInvite = await validateServerInvite(user.email, req.session.token)
// create the user
const myUser = await findOrCreateUser({ user, rawProfile: profile._raw })
const myUser = await findOrCreateUser({
user: {
...user,
role: validInvite?.serverRole
},
rawProfile: profile._raw
})
// use the invite
await finalizeInvitedServerRegistration(user.email, myUser.id)
@@ -97,7 +97,10 @@ module.exports = async (app, session, sessionAppId, finalizeAuth) => {
// * the server public and the user has a valid invite
// * the server public and the user doesn't have an invite
// so we go ahead and register the user
const userId = await createUser(user)
const userId = await createUser({
...user,
role: invite?.serverRole
})
req.user = {
id: userId,
email: user.email,
@@ -97,7 +97,13 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => {
)
// create the user
const myUser = await findOrCreateUser({ user, rawProfile: userinfo })
const myUser = await findOrCreateUser({
user: {
...user,
role: validInvite?.serverRole
},
rawProfile: userinfo
})
await finalizeInvitedServerRegistration(user.email, myUser.id)
+3 -2
View File
@@ -254,7 +254,7 @@ export const StreamFavorites = buildTableHelper('stream_favorites', [
export const UsersMeta = buildMetaTableHelper(
'users_meta',
['userId', 'key', 'value', 'createdAt', 'updatedAt'],
['isOnboardingFinished', 'foo', 'bar'],
['isOnboardingFinished', 'foo', 'bar', 'onboardingStreamId'],
'userId'
)
@@ -314,7 +314,8 @@ export const ServerInvites = buildTableHelper('server_invites', [
'resourceTarget',
'resourceId',
'role',
'token'
'token',
'serverRole'
])
export const PasswordResetTokens = buildTableHelper('pwdreset_tokens', [
@@ -1430,6 +1430,8 @@ export type ProjectInviteCreateInput = {
email?: InputMaybe<Scalars['String']>;
/** Defaults to the contributor role, if not specified */
role?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
/** Either this or email must be filled */
userId?: InputMaybe<Scalars['String']>;
};
@@ -1518,7 +1520,10 @@ export type ProjectMutations = {
__typename?: 'ProjectMutations';
/** Create new project */
create: Project;
/** Create onboarding/tutorial project */
/**
* Create onboarding/tutorial project. If one is already created for the active user, that
* one will be returned instead.
*/
createForOnboarding: Project;
/** Delete an existing project */
delete: Scalars['Boolean'];
@@ -1856,7 +1861,6 @@ export enum ResourceType {
Stream = 'stream'
}
/** Available roles. */
export type Role = {
__typename?: 'Role';
description: Scalars['String'];
@@ -1912,8 +1916,10 @@ export type ServerInfo = {
guestModeEnabled: Scalars['Boolean'];
inviteOnly?: Maybe<Scalars['Boolean']>;
name: Scalars['String'];
roles: Array<Maybe<Role>>;
scopes: Array<Maybe<Scope>>;
/** @deprecated Use role constants from the @speckle/shared npm package instead */
roles: Array<Role>;
scopes: Array<Scope>;
serverRoles: Array<ServerRoleItem>;
termsOfService?: Maybe<Scalars['String']>;
version?: Maybe<Scalars['String']>;
};
@@ -1938,6 +1944,8 @@ export type ServerInvite = {
export type ServerInviteCreateInput = {
email: Scalars['String'];
message?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
};
export enum ServerRole {
@@ -1947,6 +1955,12 @@ export enum ServerRole {
ServerUser = 'SERVER_USER'
}
export type ServerRoleItem = {
__typename?: 'ServerRoleItem';
id: Scalars['String'];
title: Scalars['String'];
};
export type ServerStatistics = {
__typename?: 'ServerStatistics';
totalPendingInvites: Scalars['Int'];
@@ -2121,6 +2135,7 @@ export type StreamCollaborator = {
id: Scalars['String'];
name: Scalars['String'];
role: Scalars['String'];
serverRole: Scalars['String'];
};
export type StreamCollection = {
@@ -2149,6 +2164,8 @@ export type StreamInviteCreateInput = {
message?: InputMaybe<Scalars['String']>;
/** Defaults to the contributor role, if not specified */
role?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
streamId: Scalars['String'];
userId?: InputMaybe<Scalars['String']>;
};
@@ -2894,6 +2911,7 @@ export type ResolversTypes = {
ServerInvite: ResolverTypeWrapper<ServerInviteGraphQLReturnType>;
ServerInviteCreateInput: ServerInviteCreateInput;
ServerRole: ServerRole;
ServerRoleItem: ResolverTypeWrapper<ServerRoleItem>;
ServerStatistics: ResolverTypeWrapper<GraphQLEmptyReturn>;
ServerStats: ResolverTypeWrapper<ServerStats>;
SmartTextEditorValue: ResolverTypeWrapper<SmartTextEditorValue>;
@@ -3047,6 +3065,7 @@ export type ResolversParentTypes = {
ServerInfoUpdateInput: ServerInfoUpdateInput;
ServerInvite: ServerInviteGraphQLReturnType;
ServerInviteCreateInput: ServerInviteCreateInput;
ServerRoleItem: ServerRoleItem;
ServerStatistics: GraphQLEmptyReturn;
ServerStats: ServerStats;
SmartTextEditorValue: SmartTextEditorValue;
@@ -3767,8 +3786,9 @@ export type ServerInfoResolvers<ContextType = GraphQLContext, ParentType extends
guestModeEnabled?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
inviteOnly?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
roles?: Resolver<Array<Maybe<ResolversTypes['Role']>>, ParentType, ContextType>;
scopes?: Resolver<Array<Maybe<ResolversTypes['Scope']>>, ParentType, ContextType>;
roles?: Resolver<Array<ResolversTypes['Role']>, ParentType, ContextType>;
scopes?: Resolver<Array<ResolversTypes['Scope']>, ParentType, ContextType>;
serverRoles?: Resolver<Array<ResolversTypes['ServerRoleItem']>, ParentType, ContextType>;
termsOfService?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
version?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@@ -3781,6 +3801,12 @@ export type ServerInviteResolvers<ContextType = GraphQLContext, ParentType exten
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ServerRoleItemResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ServerRoleItem'] = ResolversParentTypes['ServerRoleItem']> = {
id?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ServerStatisticsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ServerStatistics'] = ResolversParentTypes['ServerStatistics']> = {
totalPendingInvites?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
totalProjectCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
@@ -3855,6 +3881,7 @@ export type StreamCollaboratorResolvers<ContextType = GraphQLContext, ParentType
id?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
role?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
serverRole?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@@ -4087,6 +4114,7 @@ export type Resolvers<ContextType = GraphQLContext> = {
ServerAppListItem?: ServerAppListItemResolvers<ContextType>;
ServerInfo?: ServerInfoResolvers<ContextType>;
ServerInvite?: ServerInviteResolvers<ContextType>;
ServerRoleItem?: ServerRoleItemResolvers<ContextType>;
ServerStatistics?: ServerStatisticsResolvers<ContextType>;
ServerStats?: ServerStatsResolvers<ContextType>;
SmartTextEditorValue?: SmartTextEditorValueResolvers<ContextType>;
@@ -19,7 +19,7 @@ import {
updateStreamAndNotify,
updateStreamRoleAndNotify
} from '@/modules/core/services/streams/management'
import { createOnboardingStream } from '@/modules/core/services/streams/onboarding'
import { ensureOnboardingStream } from '@/modules/core/services/streams/onboarding'
import { removeStreamCollaborator } from '@/modules/core/services/streams/streamAccessService'
import { cancelStreamInvite } from '@/modules/serverinvites/services/inviteProcessingService'
import {
@@ -69,7 +69,7 @@ export = {
return await deleteStreamAndNotify(id, userId!)
},
async createForOnboarding(_parent, _args, { userId }) {
return await createOnboardingStream(userId!)
return await ensureOnboardingStream(userId!)
},
async update(_parent, { update }, { userId }) {
await authorizeResolver(userId, update.id, Roles.Stream.Owner)
@@ -6,7 +6,7 @@ const {
getPublicScopes,
getPublicRoles
} = require('../../services/generic')
const { Roles, Scopes } = require('@speckle/shared')
const { Roles, Scopes, RoleInfo } = require('@speckle/shared')
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
module.exports = {
@@ -23,6 +23,16 @@ module.exports = {
async scopes() {
return await getPublicScopes()
},
async serverRoles(parent) {
const { guestModeEnabled } = parent
return Object.values(Roles.Server)
.filter((role) => guestModeEnabled || role !== Roles.Server.Guest)
.map((r) => ({
id: r,
title: RoleInfo.Server[r]
}))
}
},
@@ -330,6 +330,13 @@ module.exports = {
)
}
},
StreamCollaborator: {
async serverRole(parent, _args, ctx) {
const { id } = parent
const user = await ctx.loaders.users.getUser.load(id)
return user?.role
}
},
PendingStreamCollaborator: {
/**
* @param {import('@/modules/serverinvites/services/inviteRetrievalService').PendingStreamCollaboratorGraphQLType} parent
+10 -5
View File
@@ -10,7 +10,7 @@ import {
getCommitStreams,
StreamWithCommitId
} from '@/modules/core/repositories/streams'
import { getUsers } from '@/modules/core/repositories/users'
import { UserWithOptionalRole, getUsers } from '@/modules/core/repositories/users'
import { keyBy } from 'lodash'
import { getInvites } from '@/modules/serverinvites/repositories'
import { AuthContext } from '@/modules/shared/authz'
@@ -346,10 +346,15 @@ export function buildRequestLoaders(
/**
* Get user from DB
*/
getUser: createLoader<string, Nullable<LimitedUserRecord>>(async (userIds) => {
const results = keyBy(await getUsers(userIds.slice()), 'id')
return userIds.map((i) => results[i] || null)
}),
getUser: createLoader<string, Nullable<UserWithOptionalRole<LimitedUserRecord>>>(
async (userIds) => {
const results = keyBy(
await getUsers(userIds.slice(), { withRole: true }),
'id'
)
return userIds.map((i) => results[i] || null)
}
),
/**
* Get meta values associated with one or more users
@@ -989,3 +989,32 @@ export async function getOnboardingBaseStream(version: string) {
return await q
}
/**
* Get user's own onboarding stream, if any
*/
export async function getUserOnboardingStream(userId: string) {
const q = Users.meta
.knex()
.select<StreamRecord[]>(Streams.cols)
.innerJoin(
Streams.name,
Streams.col.id,
knex.raw(`?? #>> '{}'`, [Users.meta.col.value])
)
.where(Users.meta.col.userId, userId)
.andWhere(Users.meta.col.key, Users.meta.metaKey.onboardingStreamId)
.first()
return await q
}
export async function markUserOnboardingStream(userId: string, streamId: string) {
const stream = await getStream({ streamId })
if (!stream) {
throw new Error(`Stream ${streamId} not found`)
}
const meta = metaHelpers(Users)
await meta.set(userId, Users.meta.metaKey.onboardingStreamId, streamId)
}
@@ -131,9 +131,21 @@ export async function getUser(userId: string, params?: GetUserParams) {
*/
export async function getUserByEmail(
email: string,
options?: Partial<{ skipClean: boolean }>
options?: Partial<{ skipClean: boolean; withRole: boolean }>
) {
const q = Users.knex<UserRecord[]>().whereRaw('lower(email) = lower(?)', [email])
const q = Users.knex<UserWithOptionalRole[]>().whereRaw('lower(email) = lower(?)', [
email
])
if (options?.withRole) {
q.columns([
...Object.values(Users.col),
// Getting first role from grouped results
knex.raw(`(array_agg("server_acl"."role"))[1] as role`)
])
q.leftJoin(ServerAcl.name, ServerAcl.col.userId, Users.col.id)
q.groupBy(Users.col.id)
}
const user = await q.first()
return user ? (!options?.skipClean ? sanitizeUserRecord(user) : user) : null
}
@@ -381,7 +381,7 @@ export async function cloneStream(userId: string, sourceStreamId: string) {
try {
// Clone stream/commits/branches/objects
const coreCloneResult = await cloneStreamCore(state)
const newStreamId = coreCloneResult.newStreamId
const { newStream } = coreCloneResult
// Clone comments
await cloneStreamComments(state, coreCloneResult)
@@ -390,7 +390,7 @@ export async function cloneStream(userId: string, sourceStreamId: string) {
await addStreamClonedActivity(
{
sourceStreamId,
newStreamId,
newStream,
clonerId: userId
},
{ trx: state.trx }
@@ -5,12 +5,17 @@ import { StreamRecord } from '@/modules/core/helpers/types'
import { logger } from '@/logging/logging'
import { createStreamReturnRecord } from '@/modules/core/services/streams/management'
import { getOnboardingBaseProject } from '@/modules/cross-server-sync/services/onboardingProject'
import {
getUserOnboardingStream,
markUserOnboardingStream
} from '@/modules/core/repositories/streams'
export async function createOnboardingStream(targetUserId: string) {
const sourceStream = await getOnboardingBaseProject()
// clone from base
let newStream: Optional<StreamRecord> = undefined
if (sourceStream) {
let newStream: Optional<StreamRecord> = undefined
try {
newStream = await cloneStream(targetUserId, sourceStream.id)
} catch (e) {
@@ -20,10 +25,22 @@ export async function createOnboardingStream(targetUserId: string) {
logger.warn(e, 'Stream clone failed')
}
}
if (newStream) return newStream
}
// clone failed, just create empty stream
return await createStreamReturnRecord({ ownerId: targetUserId })
if (!newStream) {
newStream = await createStreamReturnRecord({ ownerId: targetUserId })
}
// mark as onboarding stream
await markUserOnboardingStream(targetUserId, newStream.id)
return newStream
}
export async function ensureOnboardingStream(targetUserId: string) {
return (
(await getUserOnboardingStream(targetUserId)) ||
(await createOnboardingStream(targetUserId))
)
}
+13 -1
View File
@@ -27,6 +27,7 @@ const {
PasswordTooShortError
} = require('@/modules/core/errors/userinput')
const { Roles } = require('@speckle/shared')
const { getServerInfo } = require('@/modules/core/services/generic')
const _changeUserRole = async ({ userId, role }) =>
await Acl().where({ userId }).update({ role })
@@ -73,6 +74,15 @@ module.exports = {
// ONLY ALLOW SKIPPING WHEN CREATING USERS FOR TESTS, IT'S UNSAFE OTHERWISE
const { skipPropertyValidation = false } = options || {}
let expectedRole = null
if (user.role) {
const isValidRole = Object.values(Roles.Server).includes(user.role)
const isValidIfGuestModeEnabled =
user.role === Roles.Server.Guest && (await getServerInfo()).guestModeEnabled
expectedRole = isValidRole && isValidIfGuestModeEnabled ? user.role : null
}
delete user.role
user = skipPropertyValidation
? user
: pick(user, ['id', 'bio', 'email', 'password', 'name', 'company'])
@@ -95,7 +105,9 @@ module.exports = {
if (!newUser) throw new Error("Couldn't create user")
const userRole =
(await countAdminUsers()) === 0 ? Roles.Server.Admin : Roles.Server.User
(await countAdminUsers()) === 0
? Roles.Server.Admin
: expectedRole || Roles.Server.User
await Acl().insert({ userId: newId, role: userRole })
@@ -1421,6 +1421,8 @@ export type ProjectInviteCreateInput = {
email?: InputMaybe<Scalars['String']>;
/** Defaults to the contributor role, if not specified */
role?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
/** Either this or email must be filled */
userId?: InputMaybe<Scalars['String']>;
};
@@ -1509,7 +1511,10 @@ export type ProjectMutations = {
__typename?: 'ProjectMutations';
/** Create new project */
create: Project;
/** Create onboarding/tutorial project */
/**
* Create onboarding/tutorial project. If one is already created for the active user, that
* one will be returned instead.
*/
createForOnboarding: Project;
/** Delete an existing project */
delete: Scalars['Boolean'];
@@ -1847,7 +1852,6 @@ export enum ResourceType {
Stream = 'stream'
}
/** Available roles. */
export type Role = {
__typename?: 'Role';
description: Scalars['String'];
@@ -1903,8 +1907,10 @@ export type ServerInfo = {
guestModeEnabled: Scalars['Boolean'];
inviteOnly?: Maybe<Scalars['Boolean']>;
name: Scalars['String'];
roles: Array<Maybe<Role>>;
scopes: Array<Maybe<Scope>>;
/** @deprecated Use role constants from the @speckle/shared npm package instead */
roles: Array<Role>;
scopes: Array<Scope>;
serverRoles: Array<ServerRoleItem>;
termsOfService?: Maybe<Scalars['String']>;
version?: Maybe<Scalars['String']>;
};
@@ -1929,6 +1935,8 @@ export type ServerInvite = {
export type ServerInviteCreateInput = {
email: Scalars['String'];
message?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
};
export enum ServerRole {
@@ -1938,6 +1946,12 @@ export enum ServerRole {
ServerUser = 'SERVER_USER'
}
export type ServerRoleItem = {
__typename?: 'ServerRoleItem';
id: Scalars['String'];
title: Scalars['String'];
};
export type ServerStatistics = {
__typename?: 'ServerStatistics';
totalPendingInvites: Scalars['Int'];
@@ -2112,6 +2126,7 @@ export type StreamCollaborator = {
id: Scalars['String'];
name: Scalars['String'];
role: Scalars['String'];
serverRole: Scalars['String'];
};
export type StreamCollection = {
@@ -2140,6 +2155,8 @@ export type StreamInviteCreateInput = {
message?: InputMaybe<Scalars['String']>;
/** Defaults to the contributor role, if not specified */
role?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
streamId: Scalars['String'];
userId?: InputMaybe<Scalars['String']>;
};
@@ -26,13 +26,15 @@ const {
const { authorizeResolver } = require('@/modules/shared')
const { chunk } = require('lodash')
/** @type {import('@/modules/core/graph/generated/graphql').Resolvers} */
module.exports = {
Mutation: {
async serverInviteCreate(_parent, args, context) {
await createAndSendInvite({
target: args.input.email,
inviterId: context.userId,
message: args.input.message
message: args.input.message,
serverRole: args.input.serverRole
})
return true
@@ -56,7 +58,8 @@ module.exports = {
createAndSendInvite({
target: params.email,
inviterId: context.userId,
message: params.message
message: params.message,
serverRole: params.serverRole
})
)
)
@@ -83,7 +86,7 @@ module.exports = {
for (const paramsBatchArray of batches) {
await Promise.all(
paramsBatchArray.map((params) => {
const { email, userId, message, streamId, role } = params
const { email, userId, message, streamId, role, serverRole } = params
const target = userId ? buildUserTarget(userId) : email
return createAndSendInvite({
target,
@@ -91,7 +94,8 @@ module.exports = {
message,
resourceTarget: ResourceTargets.Streams,
resourceId: streamId,
role: role || Roles.Stream.Contributor
role: role || Roles.Stream.Contributor,
serverRole
})
})
)
@@ -11,4 +11,5 @@ export type ServerInviteRecord = {
resourceId: Nullable<string>
role: Nullable<string>
token: string
serverRole: Nullable<string>
}
@@ -0,0 +1,17 @@
import { Knex } from 'knex'
const TABLE_NAME = 'server_invites'
const COL_NAME = 'serverRole'
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable(TABLE_NAME, (table) => {
table.string(COL_NAME).nullable()
})
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(TABLE_NAME, (table) => {
// Drop token field
table.dropColumn(COL_NAME)
})
}
@@ -50,11 +50,13 @@ async function getResource(invite) {
/**
* Try to find a user using the target value
* @param {string} target
* @returns {Promise<import('@/modules/core/helpers/userHelper').UserRecord>}
* @returns {Promise<import('@/modules/core/repositories/users').UserWithOptionalRole | undefined>}
*/
async function getUserFromTarget(target) {
const { userEmail, userId } = resolveTarget(target)
return userEmail ? await getUserByEmail(userEmail) : await getUser(userId)
return userEmail
? await getUserByEmail(userEmail, { withRole: true })
: await getUser(userId, { withRole: true })
}
/**
@@ -36,6 +36,7 @@ const { getFrontendOrigin } = require('@/modules/shared/helpers/envHelper')
* resourceTarget?: string;
* resourceId?: string;
* role?: string;
* serverRole?: string
* }} CreateInviteParams
*/
@@ -327,12 +328,15 @@ async function buildEmailContents(invite, inviter, targetUser, resource) {
* @returns {Promise<string>} The ID of the created invite
*/
async function createAndSendInvite(params) {
const { inviterId, resourceTarget, resourceId, role } = params
const { inviterId, resourceTarget, resourceId, role, serverRole } = params
let { message, target } = params
const inviter = await getUser(inviterId)
const targetUser = await getUserFromTarget(target)
const resource = await getResource(params)
const [inviter, targetUser, resource, serverInfo] = await Promise.all([
getUser(inviterId, { withRole: true }),
getUserFromTarget(target),
getResource(params),
getServerInfo()
])
// if target user found, always use the user ID
if (targetUser) target = buildUserTarget(targetUser.id)
@@ -347,6 +351,24 @@ async function createAndSendInvite(params) {
message = sanitizeMessage(message)
}
// validate server role
if (serverRole && !Object.values(Roles.Server).includes(serverRole)) {
throw new InviteCreateValidationError('Invalid server role')
}
if (inviter.role !== Roles.Server.Admin && serverRole === Roles.Server.Admin) {
throw new InviteCreateValidationError(
'Only server admins can assign the admin server role'
)
}
if (serverRole === Roles.Server.Guest && !serverInfo.guestModeEnabled) {
throw new InviteCreateValidationError('Guest mode is not enabled on this server')
}
if (targetUser && targetUser.role === Roles.Server.Guest) {
if (role === Roles.Stream.Owner) {
throw new InviteCreateValidationError('Guest users cannot be owners of streams')
}
}
// write to DB
const invite = {
id: crs({ length: 20 }),
@@ -356,7 +378,8 @@ async function createAndSendInvite(params) {
resourceTarget,
resourceId,
role,
token: crs({ length: 50 })
token: crs({ length: 50 }),
serverRole
}
await insertInviteAndDeleteOld(
invite,
@@ -37,7 +37,8 @@ export async function createStreamInviteAndNotify(
resourceTarget: ResourceTargets.Streams,
resourceId: isStreamInviteCreateInput(input) ? input.streamId : input.projectId,
role: role || Roles.Stream.Contributor,
message: isStreamInviteCreateInput(input) ? input.message || undefined : undefined
message: isStreamInviteCreateInput(input) ? input.message || undefined : undefined,
serverRole: input.serverRole || undefined
})
}
@@ -1421,6 +1421,8 @@ export type ProjectInviteCreateInput = {
email?: InputMaybe<Scalars['String']>;
/** Defaults to the contributor role, if not specified */
role?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
/** Either this or email must be filled */
userId?: InputMaybe<Scalars['String']>;
};
@@ -1509,7 +1511,10 @@ export type ProjectMutations = {
__typename?: 'ProjectMutations';
/** Create new project */
create: Project;
/** Create onboarding/tutorial project */
/**
* Create onboarding/tutorial project. If one is already created for the active user, that
* one will be returned instead.
*/
createForOnboarding: Project;
/** Delete an existing project */
delete: Scalars['Boolean'];
@@ -1847,7 +1852,6 @@ export enum ResourceType {
Stream = 'stream'
}
/** Available roles. */
export type Role = {
__typename?: 'Role';
description: Scalars['String'];
@@ -1903,8 +1907,10 @@ export type ServerInfo = {
guestModeEnabled: Scalars['Boolean'];
inviteOnly?: Maybe<Scalars['Boolean']>;
name: Scalars['String'];
roles: Array<Maybe<Role>>;
scopes: Array<Maybe<Scope>>;
/** @deprecated Use role constants from the @speckle/shared npm package instead */
roles: Array<Role>;
scopes: Array<Scope>;
serverRoles: Array<ServerRoleItem>;
termsOfService?: Maybe<Scalars['String']>;
version?: Maybe<Scalars['String']>;
};
@@ -1929,6 +1935,8 @@ export type ServerInvite = {
export type ServerInviteCreateInput = {
email: Scalars['String'];
message?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
};
export enum ServerRole {
@@ -1938,6 +1946,12 @@ export enum ServerRole {
ServerUser = 'SERVER_USER'
}
export type ServerRoleItem = {
__typename?: 'ServerRoleItem';
id: Scalars['String'];
title: Scalars['String'];
};
export type ServerStatistics = {
__typename?: 'ServerStatistics';
totalPendingInvites: Scalars['Int'];
@@ -2112,6 +2126,7 @@ export type StreamCollaborator = {
id: Scalars['String'];
name: Scalars['String'];
role: Scalars['String'];
serverRole: Scalars['String'];
};
export type StreamCollection = {
@@ -2140,6 +2155,8 @@ export type StreamInviteCreateInput = {
message?: InputMaybe<Scalars['String']>;
/** Defaults to the contributor role, if not specified */
role?: InputMaybe<Scalars['String']>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']>;
streamId: Scalars['String'];
userId?: InputMaybe<Scalars['String']>;
};
+25
View File
@@ -19,6 +19,31 @@ export const Roles = Object.freeze(<const>{
}
})
export const RoleInfo = Object.freeze(<const>{
Stream: {
[Roles.Stream.Owner]: {
title: 'Owner',
description:
'Owners have full access, including deletion rights & access control.'
},
[Roles.Stream.Contributor]: {
title: 'Contributor',
description:
'Contributors can create new branches and commits, but they cannot edit stream details or manage collaborators.'
},
[Roles.Stream.Reviewer]: {
title: 'Reviewer',
description: 'Reviewers can only view (read) the data from this stream.'
}
},
Server: {
[Roles.Server.Admin]: 'Admin',
[Roles.Server.User]: 'User',
[Roles.Server.Guest]: 'Guest',
[Roles.Server.ArchivedUser]: 'Archived'
}
})
export type ServerRoles = (typeof Roles)['Server'][keyof (typeof Roles)['Server']]
export type StreamRoles = (typeof Roles)['Stream'][keyof (typeof Roles)['Stream']]
@@ -290,3 +290,11 @@ export const Simple: StoryType = {
buttonStyle: 'simple'
}
}
export const WithDisabledItems: StoryType = {
...Default,
args: {
...Default.args,
disabledItemPredicate: (item: FakeItemType) => item.id === '3'
}
}
@@ -102,13 +102,15 @@
:key="itemKey(item)"
v-slot="{ active, selected }: { active: boolean, selected: boolean }"
:value="item"
:disabled="disabledItemPredicate?.(item) || false"
>
<li
:class="[
active ? 'text-primary' : 'text-foreground',
'relative transition cursor-pointer select-none py-1.5 pl-3',
!hideCheckmarks ? 'pr-9' : ''
]"
:class="
listboxOptionClasses({
active,
disabled: disabledItemPredicate?.(item) || false
})
"
>
<span :class="['block truncate']">
<slot
@@ -116,6 +118,7 @@
:item="item"
:active="active"
:selected="selected"
:disabled="disabledItemPredicate?.(item) || false"
>
{{ simpleDisplayText(item) }}
</slot>
@@ -216,6 +219,13 @@ const props = defineProps({
>,
default: undefined
},
/**
* Set this to disable certain items in the list
*/
disabledItemPredicate: {
type: Function as PropType<Optional<(item: SingleItem) => boolean>>,
default: undefined
},
/**
* If search=true and this is set, you can use this to load data asynchronously depending
* on the search query
@@ -508,6 +518,24 @@ const triggerSearch = async () => {
}
const debouncedSearch = debounce(triggerSearch, 1000)
const listboxOptionClasses = (params: { active: boolean; disabled: boolean }) => {
const { active, disabled } = params || {}
const { hideCheckmarks } = props
const classParts = [
'relative transition cursor-pointer select-none py-1.5 pl-3',
!hideCheckmarks ? 'pr-9' : ''
]
if (disabled) {
classParts.push('opacity-50 cursor-not-allowed')
} else {
classParts.push(active ? 'text-primary' : 'text-foreground')
}
return classParts.join(' ')
}
watch(
() => props.items,
(newItems) => {