Merge branch 'main' into andrew/web-2865-implement-upgrade-confirmation-modal

This commit is contained in:
andrewwallacespeckle
2025-03-26 12:31:28 +00:00
13 changed files with 348 additions and 11 deletions
@@ -92,8 +92,20 @@ type WorkspacePlan {
}
type WorkspaceSubscriptionSeats {
plan: Int!
guest: Int!
plan: Int! @deprecated
guest: Int! @deprecated
"""
Total number of seats purchased and available in the current subscription cycle
"""
totalCount: Int!
"""
Number assigned seats in the current billing cycle
"""
assigned: Int!
"""
Number of viewer seats currently assigned in the workspace
"""
viewersCount: Int!
}
type WorkspaceSubscription {
+1
View File
@@ -84,6 +84,7 @@ generates:
ServerRegionMutations: '@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn'
ServerRegionItem: '@/modules/multiregion/helpers/graphTypes#ServerRegionItemGraphQLReturn'
Price: '@/modules/gatekeeperCore/helpers/graphTypes#PriceGraphQLReturn'
WorkspaceSubscription: '@/modules/gatekeeper/helpers/graphTypes#WorkspaceSubscriptionGraphQLReturn'
modules/cross-server-sync/graph/generated/graphql.ts:
plugins:
- 'typescript'
@@ -6,7 +6,7 @@ import { PendingStreamCollaboratorGraphQLReturn } from '@/modules/serverinvites/
import { FileUploadGraphQLReturn } from '@/modules/fileuploads/helpers/types';
import { AutomateFunctionGraphQLReturn, AutomateFunctionReleaseGraphQLReturn, AutomationGraphQLReturn, AutomationRevisionGraphQLReturn, AutomationRevisionFunctionGraphQLReturn, AutomateRunGraphQLReturn, AutomationRunTriggerGraphQLReturn, AutomationRevisionTriggerDefinitionGraphQLReturn, AutomateFunctionRunGraphQLReturn, TriggeredAutomationsStatusGraphQLReturn, ProjectAutomationMutationsGraphQLReturn, ProjectTriggeredAutomationsStatusUpdatedMessageGraphQLReturn, ProjectAutomationsUpdatedMessageGraphQLReturn, UserAutomateInfoGraphQLReturn } from '@/modules/automate/helpers/graphTypes';
import { WorkspaceGraphQLReturn, WorkspaceSsoGraphQLReturn, WorkspaceMutationsGraphQLReturn, WorkspaceJoinRequestMutationsGraphQLReturn, WorkspaceInviteMutationsGraphQLReturn, WorkspaceProjectMutationsGraphQLReturn, PendingWorkspaceCollaboratorGraphQLReturn, WorkspaceCollaboratorGraphQLReturn, WorkspaceJoinRequestGraphQLReturn, LimitedWorkspaceJoinRequestGraphQLReturn, ProjectRoleGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes';
import { WorkspaceBillingMutationsGraphQLReturn } from '@/modules/gatekeeper/helpers/graphTypes';
import { WorkspaceBillingMutationsGraphQLReturn, WorkspaceSubscriptionGraphQLReturn } from '@/modules/gatekeeper/helpers/graphTypes';
import { WebhookGraphQLReturn } from '@/modules/webhooks/helpers/graphTypes';
import { SmartTextEditorValueGraphQLReturn } from '@/modules/core/services/richTextEditorService';
import { BlobStorageItem } from '@/modules/blobstorage/domain/types';
@@ -4878,8 +4878,16 @@ export type WorkspaceSubscription = {
export type WorkspaceSubscriptionSeats = {
__typename?: 'WorkspaceSubscriptionSeats';
/** Number assigned seats in the current billing cycle */
assigned: Scalars['Int']['output'];
/** @deprecated Field no longer supported */
guest: Scalars['Int']['output'];
/** @deprecated Field no longer supported */
plan: Scalars['Int']['output'];
/** Total number of seats purchased and available in the current subscription cycle */
totalCount: Scalars['Int']['output'];
/** Number of viewer seats currently assigned in the workspace */
viewersCount: Scalars['Int']['output'];
};
export type WorkspaceTeamFilter = {
@@ -5297,7 +5305,7 @@ export type ResolversTypes = {
WorkspaceSso: ResolverTypeWrapper<WorkspaceSsoGraphQLReturn>;
WorkspaceSsoProvider: ResolverTypeWrapper<WorkspaceSsoProvider>;
WorkspaceSsoSession: ResolverTypeWrapper<WorkspaceSsoSession>;
WorkspaceSubscription: ResolverTypeWrapper<WorkspaceSubscription>;
WorkspaceSubscription: ResolverTypeWrapper<WorkspaceSubscriptionGraphQLReturn>;
WorkspaceSubscriptionSeats: ResolverTypeWrapper<WorkspaceSubscriptionSeats>;
WorkspaceTeamFilter: WorkspaceTeamFilter;
WorkspaceUpdateInput: WorkspaceUpdateInput;
@@ -5583,7 +5591,7 @@ export type ResolversParentTypes = {
WorkspaceSso: WorkspaceSsoGraphQLReturn;
WorkspaceSsoProvider: WorkspaceSsoProvider;
WorkspaceSsoSession: WorkspaceSsoSession;
WorkspaceSubscription: WorkspaceSubscription;
WorkspaceSubscription: WorkspaceSubscriptionGraphQLReturn;
WorkspaceSubscriptionSeats: WorkspaceSubscriptionSeats;
WorkspaceTeamFilter: WorkspaceTeamFilter;
WorkspaceUpdateInput: WorkspaceUpdateInput;
@@ -7241,8 +7249,11 @@ export type WorkspaceSubscriptionResolvers<ContextType = GraphQLContext, ParentT
};
export type WorkspaceSubscriptionSeatsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceSubscriptionSeats'] = ResolversParentTypes['WorkspaceSubscriptionSeats']> = {
assigned?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
guest?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
plan?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
totalCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
viewersCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@@ -4858,8 +4858,16 @@ export type WorkspaceSubscription = {
export type WorkspaceSubscriptionSeats = {
__typename?: 'WorkspaceSubscriptionSeats';
/** Number assigned seats in the current billing cycle */
assigned: Scalars['Int']['output'];
/** @deprecated Field no longer supported */
guest: Scalars['Int']['output'];
/** @deprecated Field no longer supported */
plan: Scalars['Int']['output'];
/** Total number of seats purchased and available in the current subscription cycle */
totalCount: Scalars['Int']['output'];
/** Number of viewer seats currently assigned in the workspace */
viewersCount: Scalars['Int']['output'];
};
export type WorkspaceTeamFilter = {
@@ -67,6 +67,7 @@ import {
} from '@/modules/gatekeeper/repositories/workspaceSeat'
import { assignWorkspaceSeatFactory } from '@/modules/workspaces/services/workspaceSeat'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { getTotalSeatsCountByPlanFactory } from '@/modules/gatekeeper/services/subscriptions'
const { FF_GATEKEEPER_MODULE_ENABLED, FF_BILLING_INTEGRATION_ENABLED } =
getFeatureFlags()
@@ -158,6 +159,45 @@ export = FF_GATEKEEPER_MODULE_ENABLED
})
}
},
WorkspaceSubscription: {
seats: async (parent) => {
const workspacePlan = await getWorkspacePlanFactory({ db })({
workspaceId: parent.workspaceId
})
if (!workspacePlan || !isNewPlanType(workspacePlan.name)) {
return {
...calculateSubscriptionSeats({
subscriptionData: parent.subscriptionData,
guestSeatProductId: getWorkspacePlanProductId({
workspacePlan: 'guest'
})
}),
// These values have no reference in the old plans FF_WORKSPACES_NEW_PLANS_ENABLED
totalCount: 0,
assigned: 0
}
}
// Only editor seats are considered
const assignedSeatsCount = await countSeatsByTypeInWorkspaceFactory({ db })({
workspaceId: parent.workspaceId,
type: 'editor'
})
return {
assigned: assignedSeatsCount,
totalCount: getTotalSeatsCountByPlanFactory({ getWorkspacePlanProductId })({
workspacePlan,
subscriptionData: parent.subscriptionData
}),
viewersCount: await countSeatsByTypeInWorkspaceFactory({ db })({
workspaceId: parent.workspaceId,
type: 'viewer'
}),
// These values have no reference in the new plans
guest: 0,
plan: 0
}
}
},
WorkspaceCollaborator: {
seatType: async (parent, _args, context) => {
const seat = await context.loaders
@@ -1,3 +1,8 @@
import { MutationsObjectGraphQLReturn } from '@/modules/core/helpers/graphTypes'
import { WorkspaceSubscription } from '@/modules/gatekeeper/domain/billing'
import { Workspace } from '@/modules/workspacesCore/domain/types'
export type WorkspaceBillingMutationsGraphQLReturn = MutationsObjectGraphQLReturn
export type WorkspaceSubscriptionGraphQLReturn = WorkspaceSubscription & {
parent: Workspace
}
@@ -26,6 +26,7 @@ import {
} from '@speckle/shared'
import { cloneDeep, sum } from 'lodash'
import { CountSeatsByTypeInWorkspace } from '@/modules/gatekeeper/domain/operations'
import { WorkspacePlan } from '@/modules/gatekeeperCore/domain/billing'
export const handleSubscriptionUpdateFactory =
({
@@ -292,3 +293,28 @@ export const addWorkspaceSubscriptionSeatIfNeededFactoryOld =
prorationBehavior: 'create_prorations'
})
}
export const getTotalSeatsCountByPlanFactory =
({
getWorkspacePlanProductId
}: {
getWorkspacePlanProductId: GetWorkspacePlanProductId
}) =>
({
workspacePlan,
subscriptionData
}: {
workspacePlan: Pick<WorkspacePlan, 'name'>
subscriptionData: Pick<SubscriptionData, 'products'>
}) => {
if (workspacePlan.name === 'free') {
return 3 // Max editors seats in the free plan
}
const productId = getWorkspacePlanProductId({
workspacePlan: workspacePlan.name as 'pro' | 'team'
})
const product = subscriptionData.products.find(
(product) => product.productId === productId
)
return product?.quantity ?? 0
}
@@ -1,12 +1,27 @@
import { db } from '@/db/knex'
import { AllScopes } from '@/modules/core/helpers/mainConstants'
import {
createRandomEmail,
createRandomString
} from '@/modules/core/helpers/testHelpers'
import { WorkspaceSeatType } from '@/modules/gatekeeper/domain/billing'
import { upsertWorkspaceSubscriptionFactory } from '@/modules/gatekeeper/repositories/billing'
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
import { createTestWorkspace } from '@/modules/workspaces/tests/helpers/creation'
import {
assignToWorkspace,
createTestWorkspace
} from '@/modules/workspaces/tests/helpers/creation'
import {
BasicTestUser,
createAuthTokenForUser,
createTestUsers
createTestUser,
createTestUsers,
login
} from '@/test/authHelper'
import { GetWorkspaceDocument } from '@/test/graphql/generated/graphql'
import {
GetWorkspaceDocument,
GetWorkspaceWithSubscriptionDocument
} from '@/test/graphql/generated/graphql'
import {
createTestContext,
testApolloServer,
@@ -16,6 +31,7 @@ import { beforeEachContext } from '@/test/hooks'
import { Roles } from '@speckle/shared'
import { expect } from 'chai'
import cryptoRandomString from 'crypto-random-string'
import dayjs from 'dayjs'
const { FF_BILLING_INTEGRATION_ENABLED } = getFeatureFlags()
@@ -103,4 +119,136 @@ describe('Workspaces Billing', () => {
})
}
)
;(FF_BILLING_INTEGRATION_ENABLED ? describe : describe.skip)(
'workspace.subscription',
() => {
describe('subscription.seats', () => {
it('should return the number of assigned seats', async () => {
const user = await createTestUser({
name: createRandomString(),
email: createRandomEmail(),
role: Roles.Server.Admin,
verified: true
})
const workspace = {
id: createRandomString(),
name: createRandomString(),
slug: cryptoRandomString({ length: 10 }),
ownerId: user.id
}
await createTestWorkspace(workspace, user, {
addPlan: { name: 'pro', status: 'valid' }
})
await upsertWorkspaceSubscriptionFactory({ db })({
workspaceSubscription: {
workspaceId: workspace.id,
createdAt: new Date(),
updatedAt: new Date(),
currentBillingCycleEnd: dayjs().add(1, 'month').toDate(),
billingInterval: 'monthly',
subscriptionData: {
subscriptionId: cryptoRandomString({ length: 10 }),
customerId: cryptoRandomString({ length: 10 }),
cancelAt: null,
status: 'active',
currentPeriodEnd: new Date(),
products: [
{
priceId: createRandomString(),
quantity: 12,
productId: createRandomString(),
subscriptionItemId: createRandomString()
}
]
}
}
})
const session = await login(user)
const res = await session.execute(GetWorkspaceWithSubscriptionDocument, {
workspaceId: workspace.id
})
expect(res).to.not.haveGraphQLErrors()
const seats = res.data?.workspace.subscription?.seats
expect(seats?.assigned).to.eq(1)
})
it('should return the number of viewers', async () => {
const user = await createTestUser({
name: createRandomString(),
email: createRandomEmail(),
role: Roles.Server.Admin,
verified: true
})
const workspace = {
id: createRandomString(),
name: createRandomString(),
slug: cryptoRandomString({ length: 10 }),
ownerId: user.id
}
await createTestWorkspace(workspace, user, {
addPlan: { name: 'pro', status: 'valid' }
})
await upsertWorkspaceSubscriptionFactory({ db })({
workspaceSubscription: {
workspaceId: workspace.id,
createdAt: new Date(),
updatedAt: new Date(),
currentBillingCycleEnd: dayjs().add(1, 'month').toDate(),
billingInterval: 'monthly',
subscriptionData: {
subscriptionId: cryptoRandomString({ length: 10 }),
customerId: cryptoRandomString({ length: 10 }),
cancelAt: null,
status: 'active',
currentPeriodEnd: new Date(),
products: [
{
priceId: createRandomString(),
quantity: 12,
productId: createRandomString(),
subscriptionItemId: createRandomString()
}
]
}
}
})
const viewer1 = await createTestUser({
name: createRandomString(),
email: createRandomEmail(),
role: Roles.Server.User,
verified: true
})
await assignToWorkspace(
workspace,
viewer1,
Roles.Workspace.Member,
WorkspaceSeatType.Viewer
)
const viewer2 = await createTestUser({
name: createRandomString(),
email: createRandomEmail(),
role: Roles.Server.User,
verified: true
})
await assignToWorkspace(
workspace,
viewer2,
Roles.Workspace.Member,
WorkspaceSeatType.Viewer
)
const session = await login(user)
const res = await session.execute(GetWorkspaceWithSubscriptionDocument, {
workspaceId: workspace.id
})
expect(res).to.not.haveGraphQLErrors()
const seats = res.data?.workspace.subscription?.seats
expect(seats?.viewersCount).to.eq(2)
})
})
}
)
})
@@ -15,6 +15,7 @@ import {
import {
addWorkspaceSubscriptionSeatIfNeededFactoryNew,
addWorkspaceSubscriptionSeatIfNeededFactoryOld,
getTotalSeatsCountByPlanFactory,
handleSubscriptionUpdateFactory
} from '@/modules/gatekeeper/services/subscriptions'
import {
@@ -2350,4 +2351,40 @@ describe('subscriptions @gatekeeper', () => {
expect(newProduct!.priceId).to.equal('newPlanPrice')
})
})
describe('getTotalSeatsCountByPlanFactory returns a function that, ', () => {
it('should return the fixed value for the free plan', () => {
const getWorkspacePlanProductId = () => expect.fail()
expect(
getTotalSeatsCountByPlanFactory({ getWorkspacePlanProductId })({
workspacePlan: { name: 'free' },
subscriptionData: { products: [] }
})
).to.eq(3)
})
it('should return 0 if subscription data has no product', () => {
const getWorkspacePlanProductId = () => 'any'
expect(
getTotalSeatsCountByPlanFactory({ getWorkspacePlanProductId })({
workspacePlan: { name: 'pro' },
subscriptionData: { products: [] }
})
).to.eq(0)
})
it('should return the number of purchased seats in the current billing period for the subscription', () => {
const getWorkspacePlanProductId = () => 'productId'
expect(
getTotalSeatsCountByPlanFactory({ getWorkspacePlanProductId })({
workspacePlan: { name: 'pro' },
subscriptionData: {
products: [
{
productId: 'productId',
quantity: 4
} as SubscriptionData['products'][number]
]
}
})
).to.eq(4)
})
})
})
@@ -363,6 +363,28 @@ export const getWorkspaceWithJoinRequestsQuery = gql`
${basicWorkspaceFragment}
`
export const getWorkspaceWithSubscriptionQuery = gql`
query GetWorkspaceWithSubscription($workspaceId: String!) {
workspace(id: $workspaceId) {
...BasicWorkspace
subscription {
createdAt
updatedAt
currentBillingCycleEnd
billingInterval
seats {
guest
plan
assigned
totalCount
viewersCount
}
}
}
}
${basicWorkspaceFragment}
`
export const updateWorkspaceProjectRoleMutation = gql`
mutation UpdateWorkspaceProjectRole($input: ProjectUpdateRoleInput!) {
workspaceMutations {
@@ -4859,8 +4859,16 @@ export type WorkspaceSubscription = {
export type WorkspaceSubscriptionSeats = {
__typename?: 'WorkspaceSubscriptionSeats';
/** Number assigned seats in the current billing cycle */
assigned: Scalars['Int']['output'];
/** @deprecated Field no longer supported */
guest: Scalars['Int']['output'];
/** @deprecated Field no longer supported */
plan: Scalars['Int']['output'];
/** Total number of seats purchased and available in the current subscription cycle */
totalCount: Scalars['Int']['output'];
/** Number of viewer seats currently assigned in the workspace */
viewersCount: Scalars['Int']['output'];
};
export type WorkspaceTeamFilter = {
@@ -5180,6 +5188,13 @@ export type GetWorkspaceWithJoinRequestsQueryVariables = Exact<{
export type GetWorkspaceWithJoinRequestsQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', id: string, name: string, slug: string, updatedAt: string, createdAt: string, role?: string | null, readOnly: boolean, adminWorkspacesJoinRequests?: { __typename?: 'WorkspaceJoinRequestCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'WorkspaceJoinRequest', status: WorkspaceJoinRequestStatus, createdAt: string, user: { __typename?: 'LimitedUser', id: string, name: string }, workspace: { __typename?: 'Workspace', id: string, name: string } }> } | null } };
export type GetWorkspaceWithSubscriptionQueryVariables = Exact<{
workspaceId: Scalars['String']['input'];
}>;
export type GetWorkspaceWithSubscriptionQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', id: string, name: string, slug: string, updatedAt: string, createdAt: string, role?: string | null, readOnly: boolean, subscription?: { __typename?: 'WorkspaceSubscription', createdAt: string, updatedAt: string, currentBillingCycleEnd: string, billingInterval: BillingInterval, seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number, assigned: number, totalCount: number, viewersCount: number } } | null } };
export type UpdateWorkspaceProjectRoleMutationVariables = Exact<{
input: ProjectUpdateRoleInput;
}>;
@@ -6020,6 +6035,7 @@ export const OnWorkspaceUpdatedDocument = {"kind":"Document","definitions":[{"ki
export const DismissWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"dismissWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceDismissInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dismiss"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]} as unknown as DocumentNode<DismissWorkspaceMutation, DismissWorkspaceMutationVariables>;
export const RequestToJoinWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"requestToJoinWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceRequestToJoinInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"requestToJoin"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]} as unknown as DocumentNode<RequestToJoinWorkspaceMutation, RequestToJoinWorkspaceMutationVariables>;
export const GetWorkspaceWithJoinRequestsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceWithJoinRequests"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"AdminWorkspaceJoinRequestFilter"}}},{"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":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"adminWorkspacesJoinRequests"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode<GetWorkspaceWithJoinRequestsQuery, GetWorkspaceWithJoinRequestsQueryVariables>;
export const GetWorkspaceWithSubscriptionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceWithSubscription"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}},{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}},{"kind":"Field","name":{"kind":"Name","value":"assigned"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"viewersCount"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode<GetWorkspaceWithSubscriptionQuery, GetWorkspaceWithSubscriptionQueryVariables>;
export const UpdateWorkspaceProjectRoleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceProjectRole"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectUpdateRoleInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateRole"},"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":"BasicProjectFields"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectFields"},"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":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<UpdateWorkspaceProjectRoleMutation, UpdateWorkspaceProjectRoleMutationVariables>;
export const UpdateWorkspaceSeatTypeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceSeatType"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceUpdateSeatTypeInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateSeatType"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"seatType"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<UpdateWorkspaceSeatTypeMutation, UpdateWorkspaceSeatTypeMutationVariables>;
export const CreateStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateStreamAccessRequest"},"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":"streamAccessRequestCreate"},"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"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<CreateStreamAccessRequestMutation, CreateStreamAccessRequestMutationVariables>;
+3 -1
View File
@@ -492,7 +492,9 @@ const getStream = () => {
// REGIONS
// https://app.speckle.systems/projects/16ce7b208c/models/1c14e37363@0614bb2957
// SUPER slow tree build time
// 'https://app.speckle.systems/projects/7591c56179/models/82b94108a3'
// SUPER slow tree build time (LARGE N-GONS TRIANGULATION)
// 'https://app.speckle.systems/projects/0edb6ef628/models/ff3d8480bc@cd83d90a2c'
)
}
@@ -38,6 +38,7 @@ import { computeOrthographicSize } from '../CameraController.js'
import { ObjectLayers } from '../../../IViewer.js'
import SpeckleBasicMaterial from '../../materials/SpeckleBasicMaterial.js'
import SpeckleRenderer from '../../SpeckleRenderer.js'
import { ExtendedMeshIntersection } from '../../objects/SpeckleRaycaster.js'
/**
* @param {Number} value
@@ -1076,6 +1077,12 @@ export class SmoothOrbitControls extends SpeckleControls {
this.orbitSphere.visible = false
}
/** By default hidden objects are ignored when picking for orbit around cursor */
protected filterOrbitToCursorHits(hit: ExtendedMeshIntersection) {
const material = this.renderer.getMaterial(hit.batchObject.renderView)
return material?.visible ?? false
}
protected onPointerDown = (event: PointerEvent) => {
if (this._options.orbitAroundCursor) {
/** Hope this is not slow */
@@ -1083,7 +1090,7 @@ export class SmoothOrbitControls extends SpeckleControls {
const x = ((event.clientX - rect.left) / rect.width) * 2 - 1
const y = ((event.clientY - rect.top) / rect.height) * -2 + 1
const res = this.renderer.intersections.intersect(
let res = this.renderer.intersections.intersect(
this.renderer.scene,
this._targetCamera as PerspectiveCamera,
new Vector2(x, y),
@@ -1091,7 +1098,9 @@ export class SmoothOrbitControls extends SpeckleControls {
true,
this.renderer.clippingVolume
)
if (res && res.length) {
res = res?.filter(this.filterOrbitToCursorHits.bind(this)) ?? []
if (res.length) {
this.pivotPoint.copy(res[0].point)
this.usePivotal = true
this.orbitSphere.visible = this._options.showOrbitPoint