fix: prevent making a home view personal (#5334)
This commit is contained in:
committed by
GitHub
parent
dbb3c4a374
commit
e7ed024d52
@@ -108,6 +108,7 @@ import {
|
||||
useCollectNewSavedViewViewerData,
|
||||
useUpdateSavedView
|
||||
} from '~/lib/viewer/composables/savedViews/management'
|
||||
import { useSavedViewValidationHelpers } from '~/lib/viewer/composables/savedViews/validation'
|
||||
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
|
||||
|
||||
const MenuItems = StringEnum([
|
||||
@@ -143,6 +144,7 @@ graphql(`
|
||||
...UseDeleteSavedView_SavedView
|
||||
...UseUpdateSavedView_SavedView
|
||||
...ViewerSavedViewsPanelViewEditDialog_SavedView
|
||||
...UseSavedViewValidationHelpers_SavedView
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -161,15 +163,19 @@ const isLoading = useMutationLoading()
|
||||
const { copyLink, applyView } = useViewerSavedViewsUtils()
|
||||
const eventBus = useEventBus()
|
||||
const { formattedRelativeDate, formattedFullDate } = useDateFormatters()
|
||||
const {
|
||||
canUpdate,
|
||||
isOnlyVisibleToMe,
|
||||
canSetHomeView,
|
||||
isHomeView,
|
||||
canToggleVisibility
|
||||
} = useSavedViewValidationHelpers({
|
||||
view: computed(() => props.view)
|
||||
})
|
||||
|
||||
const showMenu = ref(false)
|
||||
const menuId = useId()
|
||||
|
||||
const canUpdate = computed(() => props.view.permissions.canUpdate)
|
||||
const isOnlyVisibleToMe = computed(
|
||||
() => props.view.visibility === SavedViewVisibility.AuthorOnly
|
||||
)
|
||||
const isHomeView = computed(() => props.view.isHomeView)
|
||||
const isActive = computed(() => props.view.id === savedView.value?.id)
|
||||
|
||||
const isOriginalVersionAlreadyLoaded = computed(() => {
|
||||
@@ -188,29 +194,6 @@ const canLoadOriginal = computed(
|
||||
}
|
||||
)
|
||||
|
||||
const canSetHomeView = computed(
|
||||
(): { authorized: boolean; message: Optional<string> } => {
|
||||
if (!canUpdate.value?.authorized || isLoading.value) {
|
||||
return { authorized: false, message: canUpdate.value.errorMessage || undefined }
|
||||
}
|
||||
|
||||
if (isFederatedView.value) {
|
||||
return {
|
||||
authorized: false,
|
||||
message: "Home view settings can't be updated while in a federated view"
|
||||
}
|
||||
}
|
||||
|
||||
if (isOnlyVisibleToMe.value) {
|
||||
return {
|
||||
authorized: false,
|
||||
message: 'A view must be shared to be set as home view'
|
||||
}
|
||||
}
|
||||
|
||||
return { authorized: true, message: undefined }
|
||||
}
|
||||
)
|
||||
const menuItems = computed((): LayoutMenuItem<MenuItems>[][] => [
|
||||
[
|
||||
{
|
||||
@@ -223,13 +206,13 @@ const menuItems = computed((): LayoutMenuItem<MenuItems>[][] => [
|
||||
id: MenuItems.ReplaceView,
|
||||
title: 'Replace view',
|
||||
disabled: !canUpdate.value?.authorized || isLoading.value,
|
||||
disabledTooltip: canUpdate.value.errorMessage
|
||||
disabledTooltip: canUpdate.value?.errorMessage
|
||||
},
|
||||
{
|
||||
id: MenuItems.MoveToGroup,
|
||||
title: 'Move to group',
|
||||
disabled: !canUpdate.value?.authorized || isLoading.value,
|
||||
disabledTooltip: canUpdate.value.errorMessage
|
||||
disabledTooltip: canUpdate.value?.errorMessage
|
||||
},
|
||||
{
|
||||
id: MenuItems.CopyLink,
|
||||
@@ -247,8 +230,8 @@ const menuItems = computed((): LayoutMenuItem<MenuItems>[][] => [
|
||||
{
|
||||
id: MenuItems.ChangeVisibility,
|
||||
title: isOnlyVisibleToMe.value ? 'Make view shared' : 'Make view private',
|
||||
disabled: !canUpdate.value?.authorized || isLoading.value,
|
||||
disabledTooltip: canUpdate.value.errorMessage
|
||||
disabled: !canToggleVisibility.value.authorized,
|
||||
disabledTooltip: canToggleVisibility.value.message
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -256,7 +239,7 @@ const menuItems = computed((): LayoutMenuItem<MenuItems>[][] => [
|
||||
id: MenuItems.Delete,
|
||||
title: 'Delete',
|
||||
disabled: !canUpdate.value?.authorized || isLoading.value,
|
||||
disabledTooltip: canUpdate.value.errorMessage
|
||||
disabledTooltip: canUpdate.value?.errorMessage
|
||||
}
|
||||
]
|
||||
])
|
||||
|
||||
@@ -31,28 +31,28 @@
|
||||
:rules="[isRequired]"
|
||||
/>
|
||||
<FormRadioGroup
|
||||
:options="radioOptions"
|
||||
:options="visibilityOptions"
|
||||
size="sm"
|
||||
name="visibility"
|
||||
:rules="[isRequired]"
|
||||
:rules="[isRequired, validateVisibility]"
|
||||
/>
|
||||
</div>
|
||||
</LayoutDialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { FormRadioGroupItem, LayoutDialogButton } from '@speckle/ui-components'
|
||||
import { Globe, Lock } from 'lucide-vue-next'
|
||||
import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import {
|
||||
SavedViewVisibility,
|
||||
type FormSelectSavedViewGroup_SavedViewGroupFragment,
|
||||
type ViewerSavedViewsPanelViewEditDialog_SavedViewFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { isRequired, isStringOfLength } from '~/lib/common/helpers/validation'
|
||||
import { useUpdateSavedView } from '~/lib/viewer/composables/savedViews/management'
|
||||
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
|
||||
import { isUndefined } from 'lodash-es'
|
||||
import { useSavedViewValidationHelpers } from '~/lib/viewer/composables/savedViews/validation'
|
||||
import type {
|
||||
FormSelectSavedViewGroup_SavedViewGroupFragment,
|
||||
SavedViewVisibility,
|
||||
ViewerSavedViewsPanelViewEditDialog_SavedViewFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
|
||||
graphql(`
|
||||
fragment ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {
|
||||
@@ -64,6 +64,7 @@ graphql(`
|
||||
...FormSelectSavedViewGroup_SavedViewGroup
|
||||
}
|
||||
...UseUpdateSavedView_SavedView
|
||||
...UseSavedViewValidationHelpers_SavedView
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -89,6 +90,9 @@ const {
|
||||
}
|
||||
} = useInjectedViewerState()
|
||||
const updateView = useUpdateSavedView()
|
||||
const { validateVisibility, visibilityOptions } = useSavedViewValidationHelpers({
|
||||
view: computed(() => props.view)
|
||||
})
|
||||
|
||||
const buttons = computed((): LayoutDialogButton[] => [
|
||||
{
|
||||
@@ -108,21 +112,6 @@ const buttons = computed((): LayoutDialogButton[] => [
|
||||
}
|
||||
])
|
||||
|
||||
const radioOptions = computed((): FormRadioGroupItem<SavedViewVisibility>[] => [
|
||||
{
|
||||
value: SavedViewVisibility.Public,
|
||||
title: 'Shared',
|
||||
introduction: 'Visible to anyone with access to the model.',
|
||||
icon: Globe
|
||||
},
|
||||
{
|
||||
value: SavedViewVisibility.AuthorOnly,
|
||||
title: 'Private',
|
||||
introduction: 'Visible only to the view author.',
|
||||
icon: Lock
|
||||
}
|
||||
])
|
||||
|
||||
const onSubmit = handleSubmit(async (values) => {
|
||||
if (!props.view) return
|
||||
|
||||
|
||||
@@ -169,9 +169,9 @@ type Documents = {
|
||||
"\n fragment ViewerSavedViewsPanel_Project on Project {\n id\n permissions {\n canCreateSavedView {\n ...FullPermissionCheckResult\n }\n }\n workspace {\n id\n seatType\n planSupportsSavedViews: hasAccessToFeature(featureName: savedViews)\n }\n }\n": typeof types.ViewerSavedViewsPanel_ProjectFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelGroups_Project on Project {\n id\n savedViewGroups(input: $savedViewGroupsInput) {\n totalCount\n cursor\n items {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n ...ViewerSavedViewsPanelViewsGroup_Project\n }\n": typeof types.ViewerSavedViewsPanelGroups_ProjectFragmentDoc,
|
||||
"\n query ViewerSavedViewsPanelGroups_SavedViewGroups(\n $projectId: String!\n $savedViewGroupsInput: SavedViewGroupsInput!\n ) {\n project(id: $projectId) {\n id\n ...ViewerSavedViewsPanelGroups_Project\n }\n }\n": typeof types.ViewerSavedViewsPanelGroups_SavedViewGroupsDocument,
|
||||
"\n fragment ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n visibility\n isHomeView\n resourceIds\n author {\n id\n name\n }\n updatedAt\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n ...UseDeleteSavedView_SavedView\n ...UseUpdateSavedView_SavedView\n ...ViewerSavedViewsPanelViewEditDialog_SavedView\n }\n": typeof types.ViewerSavedViewsPanelView_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n visibility\n isHomeView\n resourceIds\n author {\n id\n name\n }\n updatedAt\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n ...UseDeleteSavedView_SavedView\n ...UseUpdateSavedView_SavedView\n ...ViewerSavedViewsPanelViewEditDialog_SavedView\n ...UseSavedViewValidationHelpers_SavedView\n }\n": typeof types.ViewerSavedViewsPanelView_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewDeleteDialog_SavedView on SavedView {\n id\n name\n ...UseDeleteSavedView_SavedView\n }\n": typeof types.ViewerSavedViewsPanelViewDeleteDialog_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n": typeof types.ViewerSavedViewsPanelViewEditDialog_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n ...UseSavedViewValidationHelpers_SavedView\n }\n": typeof types.ViewerSavedViewsPanelViewEditDialog_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {\n id\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n": typeof types.ViewerSavedViewsPanelViewMoveDialog_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewsGroup_Project on Project {\n id\n permissions {\n canCreateSavedView {\n ...FullPermissionCheckResult\n }\n }\n }\n": typeof types.ViewerSavedViewsPanelViewsGroup_ProjectFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {\n id\n isUngroupedViewsGroup\n title\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n ...ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup\n ...ViewerSavedViewsPanelViewsGroupDeleteDialog_SavedViewGroup\n ...UseUpdateSavedViewGroup_SavedViewGroup\n }\n": typeof types.ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragmentDoc,
|
||||
@@ -421,6 +421,7 @@ type Documents = {
|
||||
"\n fragment UseDeleteSavedViewGroup_SavedViewGroup on SavedViewGroup {\n id\n groupId\n projectId\n isUngroupedViewsGroup\n }\n": typeof types.UseDeleteSavedViewGroup_SavedViewGroupFragmentDoc,
|
||||
"\n mutation UpdateSavedViewGroup($input: UpdateSavedViewGroupInput!) {\n projectMutations {\n savedViewMutations {\n updateGroup(input: $input) {\n id\n ...UseUpdateSavedViewGroup_SavedViewGroup\n }\n }\n }\n }\n": typeof types.UpdateSavedViewGroupDocument,
|
||||
"\n fragment UseUpdateSavedViewGroup_SavedViewGroup on SavedViewGroup {\n id\n projectId\n groupId\n title\n isUngroupedViewsGroup\n }\n": typeof types.UseUpdateSavedViewGroup_SavedViewGroupFragmentDoc,
|
||||
"\n fragment UseSavedViewValidationHelpers_SavedView on SavedView {\n id\n isHomeView\n visibility\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n }\n": typeof types.UseSavedViewValidationHelpers_SavedViewFragmentDoc,
|
||||
"\n fragment UseViewerSavedViewSetup_SavedView on SavedView {\n id\n viewerState\n }\n": typeof types.UseViewerSavedViewSetup_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerCommentThread on Comment {\n ...ViewerCommentsListItem\n ...ViewerCommentBubblesData\n ...ViewerCommentsReplyItem\n ...ViewerCommentThreadData\n }\n": typeof types.ViewerCommentThreadFragmentDoc,
|
||||
"\n fragment ViewerCommentsReplyItem on Comment {\n id\n archived\n rawText\n text {\n doc\n }\n author {\n ...LimitedUserAvatar\n }\n createdAt\n ...ThreadCommentAttachment\n }\n": typeof types.ViewerCommentsReplyItemFragmentDoc,
|
||||
@@ -673,9 +674,9 @@ const documents: Documents = {
|
||||
"\n fragment ViewerSavedViewsPanel_Project on Project {\n id\n permissions {\n canCreateSavedView {\n ...FullPermissionCheckResult\n }\n }\n workspace {\n id\n seatType\n planSupportsSavedViews: hasAccessToFeature(featureName: savedViews)\n }\n }\n": types.ViewerSavedViewsPanel_ProjectFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelGroups_Project on Project {\n id\n savedViewGroups(input: $savedViewGroupsInput) {\n totalCount\n cursor\n items {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n ...ViewerSavedViewsPanelViewsGroup_Project\n }\n": types.ViewerSavedViewsPanelGroups_ProjectFragmentDoc,
|
||||
"\n query ViewerSavedViewsPanelGroups_SavedViewGroups(\n $projectId: String!\n $savedViewGroupsInput: SavedViewGroupsInput!\n ) {\n project(id: $projectId) {\n id\n ...ViewerSavedViewsPanelGroups_Project\n }\n }\n": types.ViewerSavedViewsPanelGroups_SavedViewGroupsDocument,
|
||||
"\n fragment ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n visibility\n isHomeView\n resourceIds\n author {\n id\n name\n }\n updatedAt\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n ...UseDeleteSavedView_SavedView\n ...UseUpdateSavedView_SavedView\n ...ViewerSavedViewsPanelViewEditDialog_SavedView\n }\n": types.ViewerSavedViewsPanelView_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n visibility\n isHomeView\n resourceIds\n author {\n id\n name\n }\n updatedAt\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n ...UseDeleteSavedView_SavedView\n ...UseUpdateSavedView_SavedView\n ...ViewerSavedViewsPanelViewEditDialog_SavedView\n ...UseSavedViewValidationHelpers_SavedView\n }\n": types.ViewerSavedViewsPanelView_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewDeleteDialog_SavedView on SavedView {\n id\n name\n ...UseDeleteSavedView_SavedView\n }\n": types.ViewerSavedViewsPanelViewDeleteDialog_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n": types.ViewerSavedViewsPanelViewEditDialog_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n ...UseSavedViewValidationHelpers_SavedView\n }\n": types.ViewerSavedViewsPanelViewEditDialog_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {\n id\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n": types.ViewerSavedViewsPanelViewMoveDialog_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewsGroup_Project on Project {\n id\n permissions {\n canCreateSavedView {\n ...FullPermissionCheckResult\n }\n }\n }\n": types.ViewerSavedViewsPanelViewsGroup_ProjectFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {\n id\n isUngroupedViewsGroup\n title\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n ...ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup\n ...ViewerSavedViewsPanelViewsGroupDeleteDialog_SavedViewGroup\n ...UseUpdateSavedViewGroup_SavedViewGroup\n }\n": types.ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragmentDoc,
|
||||
@@ -925,6 +926,7 @@ const documents: Documents = {
|
||||
"\n fragment UseDeleteSavedViewGroup_SavedViewGroup on SavedViewGroup {\n id\n groupId\n projectId\n isUngroupedViewsGroup\n }\n": types.UseDeleteSavedViewGroup_SavedViewGroupFragmentDoc,
|
||||
"\n mutation UpdateSavedViewGroup($input: UpdateSavedViewGroupInput!) {\n projectMutations {\n savedViewMutations {\n updateGroup(input: $input) {\n id\n ...UseUpdateSavedViewGroup_SavedViewGroup\n }\n }\n }\n }\n": types.UpdateSavedViewGroupDocument,
|
||||
"\n fragment UseUpdateSavedViewGroup_SavedViewGroup on SavedViewGroup {\n id\n projectId\n groupId\n title\n isUngroupedViewsGroup\n }\n": types.UseUpdateSavedViewGroup_SavedViewGroupFragmentDoc,
|
||||
"\n fragment UseSavedViewValidationHelpers_SavedView on SavedView {\n id\n isHomeView\n visibility\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n }\n": types.UseSavedViewValidationHelpers_SavedViewFragmentDoc,
|
||||
"\n fragment UseViewerSavedViewSetup_SavedView on SavedView {\n id\n viewerState\n }\n": types.UseViewerSavedViewSetup_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerCommentThread on Comment {\n ...ViewerCommentsListItem\n ...ViewerCommentBubblesData\n ...ViewerCommentsReplyItem\n ...ViewerCommentThreadData\n }\n": types.ViewerCommentThreadFragmentDoc,
|
||||
"\n fragment ViewerCommentsReplyItem on Comment {\n id\n archived\n rawText\n text {\n doc\n }\n author {\n ...LimitedUserAvatar\n }\n createdAt\n ...ThreadCommentAttachment\n }\n": types.ViewerCommentsReplyItemFragmentDoc,
|
||||
@@ -1659,7 +1661,7 @@ export function graphql(source: "\n query ViewerSavedViewsPanelGroups_SavedView
|
||||
/**
|
||||
* 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 ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n visibility\n isHomeView\n resourceIds\n author {\n id\n name\n }\n updatedAt\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n ...UseDeleteSavedView_SavedView\n ...UseUpdateSavedView_SavedView\n ...ViewerSavedViewsPanelViewEditDialog_SavedView\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n visibility\n isHomeView\n resourceIds\n author {\n id\n name\n }\n updatedAt\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n ...UseDeleteSavedView_SavedView\n ...UseUpdateSavedView_SavedView\n ...ViewerSavedViewsPanelViewEditDialog_SavedView\n }\n"];
|
||||
export function graphql(source: "\n fragment ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n visibility\n isHomeView\n resourceIds\n author {\n id\n name\n }\n updatedAt\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n ...UseDeleteSavedView_SavedView\n ...UseUpdateSavedView_SavedView\n ...ViewerSavedViewsPanelViewEditDialog_SavedView\n ...UseSavedViewValidationHelpers_SavedView\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n visibility\n isHomeView\n resourceIds\n author {\n id\n name\n }\n updatedAt\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n ...UseDeleteSavedView_SavedView\n ...UseUpdateSavedView_SavedView\n ...ViewerSavedViewsPanelViewEditDialog_SavedView\n ...UseSavedViewValidationHelpers_SavedView\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1667,7 +1669,7 @@ export function graphql(source: "\n fragment ViewerSavedViewsPanelViewDeleteDia
|
||||
/**
|
||||
* 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 ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n"];
|
||||
export function graphql(source: "\n fragment ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n ...UseSavedViewValidationHelpers_SavedView\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n ...UseSavedViewValidationHelpers_SavedView\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -2664,6 +2666,10 @@ export function graphql(source: "\n mutation UpdateSavedViewGroup($input: Updat
|
||||
* 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 UseUpdateSavedViewGroup_SavedViewGroup on SavedViewGroup {\n id\n projectId\n groupId\n title\n isUngroupedViewsGroup\n }\n"): (typeof documents)["\n fragment UseUpdateSavedViewGroup_SavedViewGroup on SavedViewGroup {\n id\n projectId\n groupId\n title\n isUngroupedViewsGroup\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 fragment UseSavedViewValidationHelpers_SavedView on SavedView {\n id\n isHomeView\n visibility\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n }\n"): (typeof documents)["\n fragment UseSavedViewValidationHelpers_SavedView on SavedView {\n id\n isHomeView\n visibility\n permissions {\n canUpdate {\n ...FullPermissionCheckResult\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,134 @@
|
||||
import type { MaybeNullOrUndefined, Optional } from '@speckle/shared'
|
||||
import type { GenericValidateFunction } from 'vee-validate'
|
||||
import { graphql } from '~/lib/common/generated/gql/gql'
|
||||
import {
|
||||
SavedViewVisibility,
|
||||
type UseSavedViewValidationHelpers_SavedViewFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { Globe, Lock } from 'lucide-vue-next'
|
||||
import type { FormRadioGroupItem } from '@speckle/ui-components'
|
||||
import { useMutationLoading } from '@vue/apollo-composable'
|
||||
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
|
||||
|
||||
graphql(`
|
||||
fragment UseSavedViewValidationHelpers_SavedView on SavedView {
|
||||
id
|
||||
isHomeView
|
||||
visibility
|
||||
permissions {
|
||||
canUpdate {
|
||||
...FullPermissionCheckResult
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const useSavedViewValidationHelpers = (params: {
|
||||
view: ComputedRef<
|
||||
MaybeNullOrUndefined<UseSavedViewValidationHelpers_SavedViewFragment>
|
||||
>
|
||||
}) => {
|
||||
const homeViewPrivateError = 'A home view must be shared'
|
||||
|
||||
const isLoading = useMutationLoading()
|
||||
const {
|
||||
resources: {
|
||||
response: { isFederatedView }
|
||||
}
|
||||
} = useInjectedViewerState()
|
||||
|
||||
const canUpdate = computed(() => params.view.value?.permissions.canUpdate)
|
||||
const isOnlyVisibleToMe = computed(
|
||||
() => params.view.value?.visibility === SavedViewVisibility.AuthorOnly
|
||||
)
|
||||
const isHomeView = computed(() => params.view.value?.isHomeView)
|
||||
|
||||
/**
|
||||
* Visibility options for visibility radio group
|
||||
*/
|
||||
const visibilityOptions = computed((): FormRadioGroupItem<SavedViewVisibility>[] => [
|
||||
{
|
||||
value: SavedViewVisibility.Public,
|
||||
title: 'Shared',
|
||||
introduction: 'Visible to anyone with access to the model.',
|
||||
icon: Globe
|
||||
},
|
||||
{
|
||||
value: SavedViewVisibility.AuthorOnly,
|
||||
title: 'Private',
|
||||
introduction: 'Visible only to the view author.',
|
||||
icon: Lock,
|
||||
...(params.view.value?.isHomeView
|
||||
? {
|
||||
disabled: true,
|
||||
help: homeViewPrivateError
|
||||
}
|
||||
: {})
|
||||
}
|
||||
])
|
||||
|
||||
const canSetHomeView = computed(
|
||||
(): { authorized: boolean; message: Optional<string> } => {
|
||||
if (!canUpdate.value?.authorized || isLoading.value) {
|
||||
return {
|
||||
authorized: false,
|
||||
message: canUpdate.value?.errorMessage || undefined
|
||||
}
|
||||
}
|
||||
|
||||
if (isFederatedView.value) {
|
||||
return {
|
||||
authorized: false,
|
||||
message: "Home view settings can't be updated while in a federated view"
|
||||
}
|
||||
}
|
||||
|
||||
if (isOnlyVisibleToMe.value) {
|
||||
return {
|
||||
authorized: false,
|
||||
message: 'A view must be shared to be set as home view'
|
||||
}
|
||||
}
|
||||
|
||||
return { authorized: true, message: undefined }
|
||||
}
|
||||
)
|
||||
|
||||
const canToggleVisibility = computed(() => {
|
||||
if (!canUpdate.value?.authorized || isLoading.value) {
|
||||
return {
|
||||
authorized: false,
|
||||
message: canUpdate.value?.errorMessage || undefined
|
||||
}
|
||||
}
|
||||
|
||||
if (isHomeView.value && !isOnlyVisibleToMe.value) {
|
||||
return {
|
||||
authorized: false,
|
||||
message: homeViewPrivateError
|
||||
}
|
||||
}
|
||||
|
||||
return { authorized: true, message: undefined }
|
||||
})
|
||||
|
||||
/**
|
||||
* Vee-validate rule for visibility checks
|
||||
*/
|
||||
const validateVisibility: GenericValidateFunction<SavedViewVisibility> = (value) => {
|
||||
if (!params.view.value) return true
|
||||
if (!params.view.value.isHomeView) return true
|
||||
|
||||
return value === SavedViewVisibility.AuthorOnly ? homeViewPrivateError : true
|
||||
}
|
||||
|
||||
return {
|
||||
validateVisibility,
|
||||
visibilityOptions,
|
||||
canUpdate,
|
||||
isOnlyVisibleToMe,
|
||||
canSetHomeView,
|
||||
isHomeView,
|
||||
canToggleVisibility
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ import { formatResourceIdsForGroup } from '@/modules/viewer/helpers/savedViews'
|
||||
import { difference, isUndefined, omit } from 'lodash-es'
|
||||
import type { DependenciesOf } from '@/modules/shared/helpers/factory'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import { removeNullOrUndefinedKeys } from '@speckle/shared'
|
||||
import { removeNullOrUndefinedKeys, firstDefinedValue } from '@speckle/shared'
|
||||
import { isUngroupedGroup } from '@speckle/shared/saved-views'
|
||||
import { NotFoundError } from '@/modules/shared/errors'
|
||||
|
||||
@@ -580,8 +580,8 @@ export const updateSavedViewFactory =
|
||||
|
||||
// Validate home view settings
|
||||
const { homeViewModel } = validateHomeViewSettingsFactory()({
|
||||
isHomeView: changes.isHomeView,
|
||||
visibility: changes.visibility || view.visibility,
|
||||
isHomeView: firstDefinedValue(changes.isHomeView, view.isHomeView),
|
||||
visibility: firstDefinedValue(changes.visibility, view.visibility),
|
||||
errorMetadata: {
|
||||
input,
|
||||
userId
|
||||
|
||||
@@ -1223,6 +1223,32 @@ const fakeViewerState = (overrides?: PartialDeep<ViewerState.SerializedViewerSta
|
||||
expect(res.data?.projectMutations.savedViewMutations.updateView).to.not.be.ok
|
||||
})
|
||||
|
||||
it('fails if updating already home view to be private view', async () => {
|
||||
await updateView(
|
||||
{
|
||||
input: {
|
||||
id: testView.id,
|
||||
projectId: updatablesProject.id,
|
||||
isHomeView: true
|
||||
}
|
||||
},
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
const res2 = await updateView({
|
||||
input: {
|
||||
id: testView.id,
|
||||
projectId: updatablesProject.id,
|
||||
visibility: SavedViewVisibility.authorOnly
|
||||
}
|
||||
})
|
||||
|
||||
expect(res2).to.haveGraphQLErrors({
|
||||
code: SavedViewInvalidHomeViewSettingsError.code
|
||||
})
|
||||
expect(res2.data?.projectMutations.savedViewMutations.updateView).to.not.be.ok
|
||||
})
|
||||
|
||||
it('fails if updating view to be a federated home view', async () => {
|
||||
const resourceIdString = resourceBuilder()
|
||||
.addModel(models.at(-1)!.id)
|
||||
|
||||
@@ -186,3 +186,15 @@ export const StringEnum = <T extends string>(args: T[]) => {
|
||||
export type StringEnumValues<T extends Record<string, string>> = {
|
||||
[K in keyof T]: T[K] extends string ? T[K] : never
|
||||
}[keyof T]
|
||||
|
||||
/**
|
||||
* Get first non-undefined/null value, or undefined if none found
|
||||
*/
|
||||
export const firstDefinedValue = <T>(
|
||||
...args: (T | undefined | null)[]
|
||||
): T | undefined => {
|
||||
for (const arg of args) {
|
||||
if (!isNullOrUndefined(arg)) return arg
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"Prorotation"
|
||||
],
|
||||
"typescript.tsserver.maxTsServerMemory": 8192,
|
||||
"typescript.disableAutomaticTypeAcquisition": true,
|
||||
"tailwindCSS.experimental.configFile": {
|
||||
"packages/frontend-2/tailwind.config.cjs": "packages/frontend-2/**"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user