feat: enable saved views for all workspace plans (#5343)

* feat: enable saved views for all workspace plans

* more test fixes
This commit is contained in:
Kristaps Fabians Geikins
2025-09-01 10:25:10 +03:00
committed by GitHub
parent fdf3b93e95
commit a074aedd65
11 changed files with 154 additions and 269 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

@@ -97,7 +97,6 @@
/> />
</div> </div>
</template> </template>
<ViewerSavedViewsPlanUpsell v-else />
<ViewerSavedViewsPanelGroupsCreateDialog <ViewerSavedViewsPanelGroupsCreateDialog
v-model:open="showCreateGroupDialog" v-model:open="showCreateGroupDialog"
@success="onAddGroup" @success="onAddGroup"
@@ -1,20 +0,0 @@
<template>
<div class="flex flex-col gap-4 p-4">
<img src="~/assets/images/viewer/saved-views/plan_upsell.webp" alt="Saved Views" />
<div>
<div class="text-foreground text-body font-semibold">Save custom views</div>
<div class="text-body-2xs font-medium text-foreground-2">
<p class="pb-3">Upgrade to a business plan to save, organise and present</p>
<ul class="flex flex-col gap-2 list-disc list-inside">
<li>It's cool</li>
<li>It's nice</li>
<li>It's got enough spice</li>
</ul>
</div>
</div>
<div class="flex gap-2">
<FormButton size="sm">Upgrade</FormButton>
<FormButton size="sm" color="outline">Learn more</FormButton>
</div>
</div>
</template>
@@ -74,7 +74,7 @@ import { Roles, WorkspacePlans } from '@speckle/shared'
import { import {
ProjectNotEnoughPermissionsError, ProjectNotEnoughPermissionsError,
SavedViewNoAccessError, SavedViewNoAccessError,
WorkspacePlanNoFeatureAccessError WorkspaceNoAccessError
} from '@speckle/shared/authz' } from '@speckle/shared/authz'
import * as ViewerRoute from '@speckle/shared/viewer/route' import * as ViewerRoute from '@speckle/shared/viewer/route'
import { resourceBuilder } from '@speckle/shared/viewer/route' import { resourceBuilder } from '@speckle/shared/viewer/route'
@@ -121,7 +121,6 @@ const fakeViewerState = (overrides?: PartialDeep<ViewerState.SerializedViewerSta
let otherGuy: BasicTestUser let otherGuy: BasicTestUser
let myProject: BasicTestStream let myProject: BasicTestStream
let myProjectWorkspace: BasicTestWorkspace let myProjectWorkspace: BasicTestWorkspace
let myLackingProjectWorkspace: BasicTestWorkspace
let myLackingProject: BasicTestStream let myLackingProject: BasicTestStream
let myModel1: BasicTestBranch let myModel1: BasicTestBranch
let myModel2: BasicTestBranch let myModel2: BasicTestBranch
@@ -269,13 +268,13 @@ const fakeViewerState = (overrides?: PartialDeep<ViewerState.SerializedViewerSta
addPlan: WorkspacePlans.Pro addPlan: WorkspacePlans.Pro
}) })
]) ])
myLackingProjectWorkspace = workspaceCreate[0]
myProjectWorkspace = workspaceCreate[1] myProjectWorkspace = workspaceCreate[1]
const projectCreate = await Promise.all([ const projectCreate = await Promise.all([
createTestStream( createTestStream(
buildBasicTestProject({ buildBasicTestProject({
workspaceId: myLackingProjectWorkspace.id // non-workspaced project
workspaceId: undefined
}), }),
me me
), ),
@@ -355,61 +354,6 @@ const fakeViewerState = (overrides?: PartialDeep<ViewerState.SerializedViewerSta
expect(res.data?.projectMutations.savedViewMutations.createView).to.not.be.ok expect(res.data?.projectMutations.savedViewMutations.createView).to.not.be.ok
}) })
it('should fail with ForbiddenError if workspace plan does not include SavedViews', async () => {
const res = await createSavedView(
buildCreateInput({
projectId: myLackingProject.id,
resourceIdString: 'abc'
})
)
expect(res).to.haveGraphQLErrors({ code: ForbiddenError.code })
expect(res.data?.projectMutations.savedViewMutations.createView).to.not.be.ok
})
it('should fail with ForbiddenError to create a saved view group if user lacks access (free plan)', async () => {
const resourceIds = ViewerRoute.resourceBuilder().addModel(
myLackingProject.id
)
const resourceIdString = resourceIds.toString()
const res = await createSavedViewGroup({
input: {
projectId: myLackingProject.id,
resourceIdString,
groupName: 'Should Not Work'
}
})
expect(res).to.haveGraphQLErrors({ code: ForbiddenError.code })
expect(res.data?.projectMutations.savedViewMutations.createGroup).to.not.be.ok
})
it('should fail with ForbiddenError to create a saved view if user lacks access (free plan)', async () => {
const resourceIds = ViewerRoute.resourceBuilder().addModel(
myLackingProject.id
)
const resourceIdString = resourceIds.toString()
const viewerState = fakeViewerState({
projectId: myLackingProject.id,
resources: {
request: {
resourceIdString
}
}
})
const res = await createSavedView(
buildCreateInput({
projectId: myLackingProject.id,
resourceIdString,
viewerState
})
)
expect(res).to.haveGraphQLErrors({ code: ForbiddenError.code })
expect(res.data?.projectMutations.savedViewMutations.createView).to.not.be.ok
})
it('should support dedicated auth policy check', async () => { it('should support dedicated auth policy check', async () => {
const res = await canCreateSavedView({ const res = await canCreateSavedView({
projectId: myLackingProject.id projectId: myLackingProject.id
@@ -419,7 +363,7 @@ const fakeViewerState = (overrides?: PartialDeep<ViewerState.SerializedViewerSta
const data = res.data?.project.permissions.canCreateSavedView const data = res.data?.project.permissions.canCreateSavedView
expect(data?.authorized).to.be.false expect(data?.authorized).to.be.false
expect(data?.code).to.equal(WorkspacePlanNoFeatureAccessError.code) expect(data?.code).to.equal(WorkspaceNoAccessError.code)
}) })
}) })
@@ -977,7 +977,7 @@ describe('ensureCanUseProjectWorkspacePlanFeatureFragment', () => {
const result = await sut({ const result = await sut({
projectId: 'project-id', projectId: 'project-id',
feature: WorkspacePlanFeatures.SavedViews feature: WorkspacePlanFeatures.HideSpeckleBranding
}) })
expect(result).toBeAuthOKResult() expect(result).toBeAuthOKResult()
@@ -990,7 +990,7 @@ describe('ensureCanUseProjectWorkspacePlanFeatureFragment', () => {
const result = await sut({ const result = await sut({
projectId: 'project-id', projectId: 'project-id',
feature: WorkspacePlanFeatures.SavedViews feature: WorkspacePlanFeatures.HideSpeckleBranding
}) })
expect(result).toBeAuthErrorResult({ expect(result).toBeAuthErrorResult({
@@ -1008,7 +1008,7 @@ describe('ensureCanUseProjectWorkspacePlanFeatureFragment', () => {
const result = await sut({ const result = await sut({
projectId: 'project-id', projectId: 'project-id',
feature: WorkspacePlanFeatures.SavedViews feature: WorkspacePlanFeatures.HideSpeckleBranding
}) })
expect(result).toBeAuthErrorResult({ expect(result).toBeAuthErrorResult({
@@ -1026,7 +1026,7 @@ describe('ensureCanUseProjectWorkspacePlanFeatureFragment', () => {
const result = await sut({ const result = await sut({
projectId: 'project-id', projectId: 'project-id',
feature: WorkspacePlanFeatures.SavedViews, feature: WorkspacePlanFeatures.HideSpeckleBranding,
allowUnworkspaced: true allowUnworkspaced: true
}) })
@@ -1044,7 +1044,7 @@ describe('ensureCanUseProjectWorkspacePlanFeatureFragment', () => {
const result = await sut({ const result = await sut({
projectId: 'project-id', projectId: 'project-id',
feature: WorkspacePlanFeatures.SavedViews feature: WorkspacePlanFeatures.HideSpeckleBranding
}) })
expect(result).toBeAuthErrorResult({ expect(result).toBeAuthErrorResult({
@@ -20,8 +20,7 @@ import {
SavedViewNoAccessError, SavedViewNoAccessError,
SavedViewNotFoundError, SavedViewNotFoundError,
UngroupedSavedViewGroupLockError, UngroupedSavedViewGroupLockError,
WorkspaceNoAccessError, WorkspaceNoAccessError
WorkspacePlanNoFeatureAccessError
} from '../domain/authErrors.js' } from '../domain/authErrors.js'
import { nanoid } from 'nanoid' import { nanoid } from 'nanoid'
@@ -191,11 +190,17 @@ describe('ensureCanAccessSavedViewFragment', () => {
) )
it.each(<const>['read', 'write'])( it.each(<const>['read', 'write'])(
'fails when workspace plan is too cheap (%s)', 'succeeds to %s even on free plan',
async (access) => { async (access) => {
const sut = buildWorkspaceSUT({ const sut = buildWorkspaceSUT({
getWorkspacePlan: getWorkspacePlanFake({ getWorkspacePlan: getWorkspacePlanFake({
name: 'team' name: 'free'
}),
getSavedView: getSavedViewFake({
id: savedViewId,
projectId,
visibility: SavedViewVisibility.public,
authorId: userId
}) })
}) })
@@ -205,9 +210,7 @@ describe('ensureCanAccessSavedViewFragment', () => {
savedViewId, savedViewId,
access access
}) })
expect(result).toBeAuthErrorResult({ expect(result).toBeAuthOKResult()
code: WorkspacePlanNoFeatureAccessError.code
})
} }
) )
@@ -413,27 +416,6 @@ describe('ensureCanAccessSavedViewGroupFragment', () => {
}) })
}) })
it.each(<const>['read', 'write'])(
'fails when workspace plan is too cheap (%s)',
async (access) => {
const sut = buildWorkspaceSUT({
getWorkspacePlan: getWorkspacePlanFake({
name: 'team'
})
})
const result = await sut({
userId,
projectId,
savedViewGroupId,
access
})
expect(result).toBeAuthErrorResult({
code: WorkspacePlanNoFeatureAccessError.code
})
}
)
it.each(<const>['read', 'write'])( it.each(<const>['read', 'write'])(
'fails if view doesnt exist (%s)', 'fails if view doesnt exist (%s)',
async (access) => { async (access) => {
@@ -347,7 +347,7 @@ describe('ensureCanUseWorkspacePlanFeatureFragment', () => {
const result = await sut({ const result = await sut({
workspaceId: cryptoRandomString({ length: 10 }), workspaceId: cryptoRandomString({ length: 10 }),
feature: WorkspacePlanFeatures.SavedViews feature: WorkspacePlanFeatures.HideSpeckleBranding
}) })
expect(result).toBeOKResult() expect(result).toBeOKResult()
@@ -362,7 +362,7 @@ describe('ensureCanUseWorkspacePlanFeatureFragment', () => {
const result = await sut({ const result = await sut({
workspaceId: cryptoRandomString({ length: 10 }), workspaceId: cryptoRandomString({ length: 10 }),
feature: WorkspacePlanFeatures.SavedViews feature: WorkspacePlanFeatures.HideSpeckleBranding
}) })
expect(result).toBeAuthErrorResult({ expect(result).toBeAuthErrorResult({
@@ -380,7 +380,7 @@ describe('ensureCanUseWorkspacePlanFeatureFragment', () => {
const result = await sut({ const result = await sut({
workspaceId: cryptoRandomString({ length: 10 }), workspaceId: cryptoRandomString({ length: 10 }),
feature: WorkspacePlanFeatures.SavedViews feature: WorkspacePlanFeatures.HideSpeckleBranding
}) })
expect(result).toBeAuthErrorResult({ expect(result).toBeAuthErrorResult({
@@ -395,7 +395,7 @@ describe('ensureCanUseWorkspacePlanFeatureFragment', () => {
const result = await sut({ const result = await sut({
workspaceId: cryptoRandomString({ length: 10 }), workspaceId: cryptoRandomString({ length: 10 }),
feature: WorkspacePlanFeatures.SavedViews feature: WorkspacePlanFeatures.HideSpeckleBranding
}) })
expect(result).toBeAuthErrorResult({ expect(result).toBeAuthErrorResult({
@@ -413,7 +413,7 @@ describe('ensureCanUseWorkspacePlanFeatureFragment', () => {
const result = await sut({ const result = await sut({
workspaceId: cryptoRandomString({ length: 10 }), workspaceId: cryptoRandomString({ length: 10 }),
feature: WorkspacePlanFeatures.SavedViews feature: WorkspacePlanFeatures.HideSpeckleBranding
}) })
expect(result).toBeAuthErrorResult({ expect(result).toBeAuthErrorResult({
@@ -14,10 +14,9 @@ import {
ProjectNotEnoughPermissionsError, ProjectNotEnoughPermissionsError,
ServerNoAccessError, ServerNoAccessError,
WorkspaceNoAccessError, WorkspaceNoAccessError,
WorkspacePlanNoFeatureAccessError,
WorkspaceReadOnlyError WorkspaceReadOnlyError
} from '../../../domain/authErrors.js' } from '../../../domain/authErrors.js'
import { PaidWorkspacePlans } from '../../../../workspaces/index.js' import { WorkspacePlans } from '../../../../workspaces/index.js'
const buildSUT = (overrides?: OverridesOf<typeof canCreateSavedViewPolicy>) => const buildSUT = (overrides?: OverridesOf<typeof canCreateSavedViewPolicy>) =>
canCreateSavedViewPolicy({ canCreateSavedViewPolicy({
@@ -71,7 +70,7 @@ describe('canCreateSavedViewPolicy', () => {
id: 'workspace-id' id: 'workspace-id'
}), }),
getWorkspacePlan: getWorkspacePlanFake({ getWorkspacePlan: getWorkspacePlanFake({
name: PaidWorkspacePlans.Pro name: WorkspacePlans.Pro
}), }),
getWorkspaceSsoProvider: async () => ({ getWorkspaceSsoProvider: async () => ({
providerId: 'provider-id' providerId: 'provider-id'
@@ -153,10 +152,10 @@ describe('canCreateSavedViewPolicy', () => {
}) })
}) })
it('fails if not on pro/business plan', async () => { it('succeeds even on free plan', async () => {
const canCreate = buildWorkspaceSUT({ const canCreate = buildWorkspaceSUT({
getWorkspacePlan: getWorkspacePlanFake({ getWorkspacePlan: getWorkspacePlanFake({
name: PaidWorkspacePlans.Team name: WorkspacePlans.Free
}) })
}) })
@@ -164,15 +163,13 @@ describe('canCreateSavedViewPolicy', () => {
userId: 'user-id', userId: 'user-id',
projectId: 'project-id' projectId: 'project-id'
}) })
expect(result).toBeAuthErrorResult({ expect(result).toBeAuthOKResult()
code: WorkspacePlanNoFeatureAccessError.code
})
}) })
it('fails if workspace readonly', async () => { it('fails if workspace readonly', async () => {
const canCreate = buildWorkspaceSUT({ const canCreate = buildWorkspaceSUT({
getWorkspacePlan: getWorkspacePlanFake({ getWorkspacePlan: getWorkspacePlanFake({
name: PaidWorkspacePlans.Pro, name: WorkspacePlans.Pro,
status: 'canceled' status: 'canceled'
}) })
}) })
+122 -120
View File
@@ -134,135 +134,137 @@ export const WorkspacePaidPlanConfigs: (params: {
featureFlags: Partial<FeatureFlags> | undefined featureFlags: Partial<FeatureFlags> | undefined
}) => { }) => {
[plan in PaidWorkspacePlans]: WorkspacePlanConfig<plan> [plan in PaidWorkspacePlans]: WorkspacePlanConfig<plan>
} = (params) => ({ } = (params) => {
[PaidWorkspacePlans.Team]: { const finalBaseFeatures = [
plan: PaidWorkspacePlans.Team, ...baseFeatures,
features: [...baseFeatures], ...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED
limits: { ? [WorkspacePlanFeatures.SavedViews]
projectCount: 5, : [])
modelCount: 25, ]
versionsHistory: { value: 30, unit: 'day' },
commentHistory: { value: 30, unit: 'day' } return {
} [PaidWorkspacePlans.Team]: {
}, plan: PaidWorkspacePlans.Team,
[PaidWorkspacePlans.TeamUnlimited]: { features: [...finalBaseFeatures],
plan: PaidWorkspacePlans.TeamUnlimited, limits: {
features: [...baseFeatures], projectCount: 5,
limits: { modelCount: 25,
projectCount: null, versionsHistory: { value: 30, unit: 'day' },
modelCount: null, commentHistory: { value: 30, unit: 'day' }
versionsHistory: { value: 30, unit: 'day' }, }
commentHistory: { value: 30, unit: 'day' } },
} [PaidWorkspacePlans.TeamUnlimited]: {
}, plan: PaidWorkspacePlans.TeamUnlimited,
[PaidWorkspacePlans.Pro]: { features: [...finalBaseFeatures],
plan: PaidWorkspacePlans.Pro, limits: {
features: [ projectCount: null,
...baseFeatures, modelCount: null,
WorkspacePlanFeatures.DomainSecurity, versionsHistory: { value: 30, unit: 'day' },
WorkspacePlanFeatures.SSO, commentHistory: { value: 30, unit: 'day' }
WorkspacePlanFeatures.CustomDataRegion, }
WorkspacePlanFeatures.HideSpeckleBranding, },
...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED [PaidWorkspacePlans.Pro]: {
? [WorkspacePlanFeatures.SavedViews] plan: PaidWorkspacePlans.Pro,
: []) features: [
], ...finalBaseFeatures,
limits: { WorkspacePlanFeatures.DomainSecurity,
projectCount: 10, WorkspacePlanFeatures.SSO,
modelCount: 50, WorkspacePlanFeatures.CustomDataRegion,
versionsHistory: null, WorkspacePlanFeatures.HideSpeckleBranding
commentHistory: null ],
} limits: {
}, projectCount: 10,
[PaidWorkspacePlans.ProUnlimited]: { modelCount: 50,
plan: PaidWorkspacePlans.ProUnlimited, versionsHistory: null,
features: [ commentHistory: null
...baseFeatures, }
WorkspacePlanFeatures.DomainSecurity, },
WorkspacePlanFeatures.SSO, [PaidWorkspacePlans.ProUnlimited]: {
WorkspacePlanFeatures.CustomDataRegion, plan: PaidWorkspacePlans.ProUnlimited,
WorkspacePlanFeatures.HideSpeckleBranding, features: [
...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED ...finalBaseFeatures,
? [WorkspacePlanFeatures.SavedViews] WorkspacePlanFeatures.DomainSecurity,
: []) WorkspacePlanFeatures.SSO,
], WorkspacePlanFeatures.CustomDataRegion,
limits: { WorkspacePlanFeatures.HideSpeckleBranding
projectCount: null, ],
modelCount: null, limits: {
versionsHistory: null, projectCount: null,
commentHistory: null modelCount: null,
versionsHistory: null,
commentHistory: null
}
} }
} }
}) }
export const WorkspaceUnpaidPlanConfigs: (params: { export const WorkspaceUnpaidPlanConfigs: (params: {
featureFlags: Partial<FeatureFlags> | undefined featureFlags: Partial<FeatureFlags> | undefined
}) => { }) => {
[plan in UnpaidWorkspacePlans]: WorkspacePlanConfig<plan> [plan in UnpaidWorkspacePlans]: WorkspacePlanConfig<plan>
} = (params) => ({ } = (params) => {
[UnpaidWorkspacePlans.Enterprise]: { const finalBaseFeatures = [
plan: UnpaidWorkspacePlans.Enterprise, ...baseFeatures,
features: [ ...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED
...baseFeatures, ? [WorkspacePlanFeatures.SavedViews]
WorkspacePlanFeatures.DomainSecurity, : [])
WorkspacePlanFeatures.SSO, ]
WorkspacePlanFeatures.CustomDataRegion, return {
WorkspacePlanFeatures.HideSpeckleBranding, [UnpaidWorkspacePlans.Enterprise]: {
WorkspacePlanFeatures.ExclusiveMembership, plan: UnpaidWorkspacePlans.Enterprise,
...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED features: [
? [WorkspacePlanFeatures.SavedViews] ...finalBaseFeatures,
: []) WorkspacePlanFeatures.DomainSecurity,
], WorkspacePlanFeatures.SSO,
limits: unlimited WorkspacePlanFeatures.CustomDataRegion,
}, WorkspacePlanFeatures.HideSpeckleBranding,
[UnpaidWorkspacePlans.Unlimited]: { WorkspacePlanFeatures.ExclusiveMembership
plan: UnpaidWorkspacePlans.Unlimited, ],
features: [ limits: unlimited
...baseFeatures, },
WorkspacePlanFeatures.DomainSecurity, [UnpaidWorkspacePlans.Unlimited]: {
WorkspacePlanFeatures.SSO, plan: UnpaidWorkspacePlans.Unlimited,
WorkspacePlanFeatures.CustomDataRegion, features: [
WorkspacePlanFeatures.HideSpeckleBranding, ...finalBaseFeatures,
WorkspacePlanFeatures.ExclusiveMembership, WorkspacePlanFeatures.DomainSecurity,
...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED WorkspacePlanFeatures.SSO,
? [WorkspacePlanFeatures.SavedViews] WorkspacePlanFeatures.CustomDataRegion,
: []) WorkspacePlanFeatures.HideSpeckleBranding,
], WorkspacePlanFeatures.ExclusiveMembership
limits: unlimited ],
}, limits: unlimited
[UnpaidWorkspacePlans.Academia]: { },
plan: UnpaidWorkspacePlans.Academia, [UnpaidWorkspacePlans.Academia]: {
features: [ plan: UnpaidWorkspacePlans.Academia,
...baseFeatures, features: [
WorkspacePlanFeatures.DomainSecurity, ...finalBaseFeatures,
WorkspacePlanFeatures.SSO, WorkspacePlanFeatures.DomainSecurity,
WorkspacePlanFeatures.CustomDataRegion, WorkspacePlanFeatures.SSO,
WorkspacePlanFeatures.HideSpeckleBranding, WorkspacePlanFeatures.CustomDataRegion,
...(params.featureFlags?.FF_SAVED_VIEWS_ENABLED WorkspacePlanFeatures.HideSpeckleBranding
? [WorkspacePlanFeatures.SavedViews] ],
: []) limits: unlimited
], },
limits: unlimited [UnpaidWorkspacePlans.TeamUnlimitedInvoiced]: {
}, ...WorkspacePaidPlanConfigs(params).teamUnlimited,
[UnpaidWorkspacePlans.TeamUnlimitedInvoiced]: { plan: UnpaidWorkspacePlans.TeamUnlimitedInvoiced
...WorkspacePaidPlanConfigs(params).teamUnlimited, },
plan: UnpaidWorkspacePlans.TeamUnlimitedInvoiced [UnpaidWorkspacePlans.ProUnlimitedInvoiced]: {
}, ...WorkspacePaidPlanConfigs(params).proUnlimited,
[UnpaidWorkspacePlans.ProUnlimitedInvoiced]: { plan: UnpaidWorkspacePlans.ProUnlimitedInvoiced
...WorkspacePaidPlanConfigs(params).proUnlimited, },
plan: UnpaidWorkspacePlans.ProUnlimitedInvoiced [UnpaidWorkspacePlans.Free]: {
}, plan: UnpaidWorkspacePlans.Free,
[UnpaidWorkspacePlans.Free]: { features: finalBaseFeatures,
plan: UnpaidWorkspacePlans.Free, limits: {
features: baseFeatures, projectCount: 1,
limits: { modelCount: 5,
projectCount: 1, versionsHistory: { value: 7, unit: 'day' },
modelCount: 5, commentHistory: { value: 7, unit: 'day' }
versionsHistory: { value: 7, unit: 'day' }, }
commentHistory: { value: 7, unit: 'day' }
} }
} }
}) }
export const WorkspacePlanConfigs = (params: { export const WorkspacePlanConfigs = (params: {
featureFlags: Partial<FeatureFlags> | undefined featureFlags: Partial<FeatureFlags> | undefined
+1 -21
View File
@@ -1,23 +1,3 @@
{ {
/* load each package separately, rather than as one giant progream */ "files": []
"files": [],
"references": [
{ "path": "packages/fileimport-service" },
{ "path": "packages/frontend-2" },
{ "path": "packages/monitor-deployment" },
{ "path": "packages/objectloader" },
{ "path": "packages/objectloader2" },
{ "path": "packages/objectsender" },
{ "path": "packages/preview-frontend" },
{ "path": "packages/preview-service" },
{ "path": "packages/server" },
{ "path": "packages/shared" },
{ "path": "packages/tailwind-theme" },
{ "path": "packages/ui-components" },
{ "path": "packages/ui-components-nuxt" },
{ "path": "packages/viewer" },
{ "path": "packages/viewer-sandbox" },
{ "path": "packages/webhook-service" }
/* add all other packages listed in workspace.code-workspace */
]
} }
+1
View File
@@ -111,6 +111,7 @@
"Prorotation" "Prorotation"
], ],
"typescript.tsserver.maxTsServerMemory": 8192, "typescript.tsserver.maxTsServerMemory": 8192,
"typescript.disableAutomaticTypeAcquisition": true,
"tailwindCSS.experimental.configFile": { "tailwindCSS.experimental.configFile": {
"packages/frontend-2/tailwind.config.cjs": "packages/frontend-2/**" "packages/frontend-2/tailwind.config.cjs": "packages/frontend-2/**"
}, },