diff --git a/packages/frontend-2/components/viewer/saved-views/panel/View.vue b/packages/frontend-2/components/viewer/saved-views/panel/View.vue index 015704242..3a3042af6 100644 --- a/packages/frontend-2/components/viewer/saved-views/panel/View.vue +++ b/packages/frontend-2/components/viewer/saved-views/panel/View.vue @@ -49,7 +49,7 @@ name="editView" class="shrink-0 opacity-0 group-hover:opacity-100" :disabled="!canUpdate?.authorized || isLoading" - @click="showEditDialog = !showEditDialog" + @click="onEdit" /> @@ -63,7 +63,6 @@ - diff --git a/packages/frontend-2/components/viewer/saved-views/panel/Views.vue b/packages/frontend-2/components/viewer/saved-views/panel/Views.vue index ac60e14ea..f1cfa80e5 100644 --- a/packages/frontend-2/components/viewer/saved-views/panel/Views.vue +++ b/packages/frontend-2/components/viewer/saved-views/panel/Views.vue @@ -18,6 +18,15 @@ hide-when-complete @infinite="onInfiniteLoad" /> + + @@ -25,6 +34,10 @@ import { omit } from 'lodash-es' import { usePaginatedQuery } from '~/lib/common/composables/graphql' import { graphql } from '~/lib/common/generated/gql' +import type { + ViewerSavedViewsPanelViewEditDialog_SavedViewFragment, + ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment +} from '~/lib/common/generated/gql/graphql' import { useInjectedViewerState } from '~/lib/viewer/composables/setup' import { ViewsType } from '~/lib/viewer/helpers/savedViews' @@ -68,8 +81,11 @@ const { request: { resourceIdString } } } = useInjectedViewerState() +const eventBus = useEventBus() const search = ref('') +const viewBeingEdited = ref() +const viewBeingMoved = ref() const { identifier, @@ -111,6 +127,24 @@ const groups = computed(() => { return result.value?.project.savedViewGroups.items || [] }) +const showEditDialog = computed({ + get: () => !!viewBeingEdited.value, + set: (value) => { + if (!value) { + viewBeingEdited.value = undefined + } + } +}) + +const showMoveDialog = computed({ + get: () => !!viewBeingMoved.value, + set: (value) => { + if (!value) { + viewBeingMoved.value = undefined + } + } +}) + watch( groups, (newGroups) => { @@ -120,4 +154,16 @@ watch( }, { immediate: true } ) + +eventBus.on(ViewerEventBusKeys.MarkSavedViewForEdit, ({ type, view }) => { + if (type === 'edit') { + viewBeingEdited.value = view + } else if (type === 'move') { + viewBeingMoved.value = view + } +}) + +const onMoveSuccess = (groupId: string) => { + selectedGroupId.value = groupId +} diff --git a/packages/frontend-2/components/viewer/saved-views/panel/view/EditDialog.vue b/packages/frontend-2/components/viewer/saved-views/panel/view/EditDialog.vue index 3e6757c34..1220d5b82 100644 --- a/packages/frontend-2/components/viewer/saved-views/panel/view/EditDialog.vue +++ b/packages/frontend-2/components/viewer/saved-views/panel/view/EditDialog.vue @@ -77,7 +77,7 @@ type FormType = { } const props = defineProps<{ - view: ViewerSavedViewsPanelViewEditDialog_SavedViewFragment + view: ViewerSavedViewsPanelViewEditDialog_SavedViewFragment | undefined }>() const open = defineModel('open', { @@ -126,6 +126,8 @@ const radioOptions = computed((): FormRadioGroupItem[] => [ ]) const onSubmit = handleSubmit(async (values) => { + if (!props.view) return + const name = values.name.trim() && values.name.trim() !== props.view.name ? values.name.trim() @@ -156,6 +158,8 @@ const onSubmit = handleSubmit(async (values) => { }) watch(open, (newVal, oldVal) => { + if (!props.view) return + if (newVal && !oldVal) { // Reset form state when dialog opens setValues({ diff --git a/packages/frontend-2/components/viewer/saved-views/panel/view/MoveDialog.vue b/packages/frontend-2/components/viewer/saved-views/panel/view/MoveDialog.vue new file mode 100644 index 000000000..e1de79d03 --- /dev/null +++ b/packages/frontend-2/components/viewer/saved-views/panel/view/MoveDialog.vue @@ -0,0 +1,115 @@ + + diff --git a/packages/frontend-2/composables/globals.ts b/packages/frontend-2/composables/globals.ts index 3450b289b..0016401cc 100644 --- a/packages/frontend-2/composables/globals.ts +++ b/packages/frontend-2/composables/globals.ts @@ -1,6 +1,7 @@ import { useActiveUser } from '~/lib/auth/composables/activeUser' import { usePageQueryStandardFetchPolicy } from '~/lib/common/composables/graphql' import { useGlobalToast } from '~/lib/common/composables/toast' +import { useEventBus } from '~/lib/core/composables/eventBus' export const useIsAccModuleEnabled = () => { const { @@ -83,4 +84,4 @@ export const useIsRhinoFileImporterEnabled = () => { return ref(FF_RHINO_FILE_IMPORTER_ENABLED) } -export { useGlobalToast, useActiveUser, usePageQueryStandardFetchPolicy } +export { useGlobalToast, useActiveUser, usePageQueryStandardFetchPolicy, useEventBus } diff --git a/packages/frontend-2/lib/common/generated/gql/gql.ts b/packages/frontend-2/lib/common/generated/gql/gql.ts index 6b007067f..553c7f27f 100644 --- a/packages/frontend-2/lib/common/generated/gql/gql.ts +++ b/packages/frontend-2/lib/common/generated/gql/gql.ts @@ -171,6 +171,7 @@ type Documents = { "\n fragment ViewerSavedViewsPanelViews_Project on Project {\n id\n savedViewGroups(input: $savedViewGroupsInput) {\n totalCount\n cursor\n items {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n }\n": typeof types.ViewerSavedViewsPanelViews_ProjectFragmentDoc, "\n query ViewerSavedViewsPanelViews_Groups(\n $projectId: String!\n $savedViewGroupsInput: SavedViewGroupsInput!\n ) {\n project(id: $projectId) {\n id\n ...ViewerSavedViewsPanelViews_Project\n }\n }\n": typeof types.ViewerSavedViewsPanelViews_GroupsDocument, "\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 ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {\n id\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n": typeof types.ViewerSavedViewsPanelViewMoveDialog_SavedViewFragmentDoc, "\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {\n id\n isUngroupedViewsGroup\n ...ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup\n }\n": typeof types.ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragmentDoc, "\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated on SavedViewGroup {\n id\n views(input: $savedViewsInput) {\n cursor\n totalCount\n items {\n id\n ...ViewerSavedViewsPanelView_SavedView\n }\n }\n }\n": typeof types.ViewerSavedViewsPanelViewsGroup_SavedViewGroup_PaginatedFragmentDoc, "\n fragment ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup on SavedViewGroup {\n id\n title\n }\n": typeof types.ViewerSavedViewsPanelViewsGroupInner_SavedViewGroupFragmentDoc, @@ -665,6 +666,7 @@ const documents: Documents = { "\n fragment ViewerSavedViewsPanelViews_Project on Project {\n id\n savedViewGroups(input: $savedViewGroupsInput) {\n totalCount\n cursor\n items {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n }\n": types.ViewerSavedViewsPanelViews_ProjectFragmentDoc, "\n query ViewerSavedViewsPanelViews_Groups(\n $projectId: String!\n $savedViewGroupsInput: SavedViewGroupsInput!\n ) {\n project(id: $projectId) {\n id\n ...ViewerSavedViewsPanelViews_Project\n }\n }\n": types.ViewerSavedViewsPanelViews_GroupsDocument, "\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 ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {\n id\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n": types.ViewerSavedViewsPanelViewMoveDialog_SavedViewFragmentDoc, "\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {\n id\n isUngroupedViewsGroup\n ...ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup\n }\n": types.ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragmentDoc, "\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated on SavedViewGroup {\n id\n views(input: $savedViewsInput) {\n cursor\n totalCount\n items {\n id\n ...ViewerSavedViewsPanelView_SavedView\n }\n }\n }\n": types.ViewerSavedViewsPanelViewsGroup_SavedViewGroup_PaginatedFragmentDoc, "\n fragment ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup on SavedViewGroup {\n id\n title\n }\n": types.ViewerSavedViewsPanelViewsGroupInner_SavedViewGroupFragmentDoc, @@ -1644,6 +1646,10 @@ export function graphql(source: "\n query ViewerSavedViewsPanelViews_Groups(\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 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"]; +/** + * 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 ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {\n id\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {\n id\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/frontend-2/lib/common/generated/gql/graphql.ts b/packages/frontend-2/lib/common/generated/gql/graphql.ts index b7703c74c..202b98f0f 100644 --- a/packages/frontend-2/lib/common/generated/gql/graphql.ts +++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts @@ -6096,6 +6096,8 @@ export type ViewerSavedViewsPanelViews_GroupsQuery = { __typename?: 'Query', pro export type ViewerSavedViewsPanelViewEditDialog_SavedViewFragment = { __typename?: 'SavedView', id: string, name: string, description?: string | null, visibility: SavedViewVisibility, projectId: string, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } }; +export type ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment = { __typename?: 'SavedView', id: string, projectId: string, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } }; + export type ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragment = { __typename?: 'SavedViewGroup', id: string, isUngroupedViewsGroup: boolean, title: string }; export type ViewerSavedViewsPanelViewsGroup_SavedViewGroup_PaginatedFragment = { __typename?: 'SavedViewGroup', id: string, views: { __typename?: 'SavedViewCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'SavedView', id: string, name: string, description?: string | null, screenshot: string, visibility: SavedViewVisibility, updatedAt: string, projectId: string, author?: { __typename?: 'LimitedUser', id: string, name: string } | null, permissions: { __typename?: 'SavedViewPermissionChecks', canUpdate: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: {} | null, errorMessage?: string | null } }, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } }> } }; @@ -8156,9 +8158,10 @@ export const ViewerSavedViewsPanel_ProjectFragmentDoc = {"kind":"Document","defi export const ViewerSavedViewsPanelViewsGroupInner_SavedViewGroupFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]} as unknown as DocumentNode; export const ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewsGroup_SavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]} as unknown as DocumentNode; export const ViewerSavedViewsPanelViews_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViews_Project"},"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":"savedViewGroups"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"savedViewGroupsInput"}}}],"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":"FragmentSpread","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewsGroup_SavedViewGroup"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewsGroup_SavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup"}}]}}]} as unknown as DocumentNode; -export const UseDeleteSavedView_SavedViewFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseDeleteSavedView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; -export const UseUpdateSavedView_SavedViewFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const FormSelectSavedViewGroup_SavedViewGroupFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FormSelectSavedViewGroup_SavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}}]} as unknown as DocumentNode; +export const UseUpdateSavedView_SavedViewFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const ViewerSavedViewsPanelViewMoveDialog_SavedViewFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewMoveDialog_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FormSelectSavedViewGroup_SavedViewGroup"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FormSelectSavedViewGroup_SavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const UseDeleteSavedView_SavedViewFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseDeleteSavedView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const ViewerSavedViewsPanelViewEditDialog_SavedViewFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewEditDialog_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"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":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FormSelectSavedViewGroup_SavedViewGroup"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FormSelectSavedViewGroup_SavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const ViewerSavedViewsPanelView_SavedViewFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"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":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canUpdate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseDeleteSavedView_SavedView"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewEditDialog_SavedView"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FormSelectSavedViewGroup_SavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullPermissionCheckResult"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionCheckResult"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}},{"kind":"Field","name":{"kind":"Name","value":"errorMessage"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseDeleteSavedView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewEditDialog_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"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":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FormSelectSavedViewGroup_SavedViewGroup"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"}}]}}]} as unknown as DocumentNode; export const ViewerSavedViewsPanelViewsGroup_SavedViewGroup_PaginatedFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"views"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"savedViewsInput"}}}],"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":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewerSavedViewsPanelView_SavedView"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullPermissionCheckResult"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionCheckResult"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}},{"kind":"Field","name":{"kind":"Name","value":"errorMessage"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseDeleteSavedView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FormSelectSavedViewGroup_SavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewEditDialog_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"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":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FormSelectSavedViewGroup_SavedViewGroup"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerSavedViewsPanelView_SavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"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":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canUpdate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseDeleteSavedView_SavedView"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseUpdateSavedView_SavedView"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewerSavedViewsPanelViewEditDialog_SavedView"}}]}}]} as unknown as DocumentNode; diff --git a/packages/frontend-2/lib/viewer/composables/savedViews/general.ts b/packages/frontend-2/lib/viewer/composables/savedViews/general.ts index 0bbd6b0e0..b17fe1af9 100644 --- a/packages/frontend-2/lib/viewer/composables/savedViews/general.ts +++ b/packages/frontend-2/lib/viewer/composables/savedViews/general.ts @@ -41,7 +41,7 @@ export const useViewerSavedViewsUtils = () => { const applyView = (settings: SavedViewUrlSettings) => { // Force update, even if the view id is already set // (in case this is a frustration click w/ the state not applying) - eventBus.emit(ViewerEventBusKeys.UpdateSavedView, settings) + eventBus.emit(ViewerEventBusKeys.ApplySavedView, settings) } return { diff --git a/packages/frontend-2/lib/viewer/composables/savedViews/management.ts b/packages/frontend-2/lib/viewer/composables/savedViews/management.ts index 6350ab208..f4363d89a 100644 --- a/packages/frontend-2/lib/viewer/composables/savedViews/management.ts +++ b/packages/frontend-2/lib/viewer/composables/savedViews/management.ts @@ -13,6 +13,7 @@ import { onGroupViewRemovalCacheUpdates, onNewGroupViewCacheUpdates } from '~/lib/viewer/helpers/savedViews/cache' +import { isUngroupedGroup } from '@speckle/shared/saved-views' const createSavedViewMutation = graphql(` mutation CreateSavedView($input: CreateSavedViewInput!) { @@ -320,7 +321,7 @@ export const useCreateSavedViewGroup = () => { // default comes first, then new group const defaultIdx = newItems.findIndex((i) => - fromRef(i).id.startsWith('default-') + isUngroupedGroup(fromRef(i).id) ) newItems.splice(defaultIdx + 1, 0, ref('SavedViewGroup', group.id)) diff --git a/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts b/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts index fb08b8e69..4e877955a 100644 --- a/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts +++ b/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts @@ -1003,7 +1003,7 @@ const useViewerSavedViewSetup = () => { } // Allow force update - on(ViewerEventBusKeys.UpdateSavedView, async (settings) => { + on(ViewerEventBusKeys.ApplySavedView, async (settings) => { await update({ settings }) }) diff --git a/packages/frontend-2/lib/viewer/helpers/eventBus.ts b/packages/frontend-2/lib/viewer/helpers/eventBus.ts index 7a27dd18c..0deb71b11 100644 --- a/packages/frontend-2/lib/viewer/helpers/eventBus.ts +++ b/packages/frontend-2/lib/viewer/helpers/eventBus.ts @@ -1,10 +1,26 @@ -import type { ViewerSavedViewEventBusPayloads } from '~/lib/viewer/helpers/savedViews' +import type { + ViewerSavedViewsPanelViewEditDialog_SavedViewFragment, + ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment +} from '~/lib/common/generated/gql/graphql' +import type { SavedViewUrlSettings } from '~/lib/viewer/helpers/savedViews' export enum ViewerEventBusKeys { - UpdateSavedView = 'aaa' + ApplySavedView = 'UpdateSavedView', + MarkSavedViewForEdit = 'MarkSavedViewForEdit' +} + +export type ViewerSavedViewEventBusPayloads = { + [ViewerEventBusKeys.ApplySavedView]: SavedViewUrlSettings + [ViewerEventBusKeys.MarkSavedViewForEdit]: + | { + type: 'edit' + view: ViewerSavedViewsPanelViewEditDialog_SavedViewFragment + } + | { type: 'move'; view: ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment } } // Add mappings between event keys and expected payloads here export type ViewerEventBusKeyPayloadMap = { - [ViewerEventBusKeys.UpdateSavedView]: ViewerSavedViewEventBusPayloads[ViewerEventBusKeys.UpdateSavedView] + [ViewerEventBusKeys.ApplySavedView]: ViewerSavedViewEventBusPayloads[ViewerEventBusKeys.ApplySavedView] + [ViewerEventBusKeys.MarkSavedViewForEdit]: ViewerSavedViewEventBusPayloads[ViewerEventBusKeys.MarkSavedViewForEdit] } & { [k in ViewerEventBusKeys]: unknown } & Record diff --git a/packages/frontend-2/lib/viewer/helpers/savedViews.ts b/packages/frontend-2/lib/viewer/helpers/savedViews.ts index 9dc9fe50f..813cc475c 100644 --- a/packages/frontend-2/lib/viewer/helpers/savedViews.ts +++ b/packages/frontend-2/lib/viewer/helpers/savedViews.ts @@ -1,6 +1,5 @@ import type { StringEnumValues } from '@speckle/shared' import { isObjectLike, isString } from 'lodash-es' -import type { ViewerEventBusKeys } from '~/lib/viewer/helpers/eventBus' export const ViewsType = { All: 'all-views', @@ -23,10 +22,6 @@ export type SavedViewUrlSettings = { loadOriginal?: boolean } -export type ViewerSavedViewEventBusPayloads = { - [ViewerEventBusKeys.UpdateSavedView]: SavedViewUrlSettings -} - export const parseSavedViewUrlSettings = ( settingsString: string | null ): SavedViewUrlSettings | null => { diff --git a/packages/frontend-2/lib/viewer/helpers/savedViews/cache.ts b/packages/frontend-2/lib/viewer/helpers/savedViews/cache.ts index e6ef5dc00..ae0dc4404 100644 --- a/packages/frontend-2/lib/viewer/helpers/savedViews/cache.ts +++ b/packages/frontend-2/lib/viewer/helpers/savedViews/cache.ts @@ -1,4 +1,5 @@ import type { ApolloCache } from '@apollo/client/cache' +import { isUngroupedGroup } from '@speckle/shared/saved-views' /** * Cache mutations for when a group gets a new view: @@ -75,7 +76,7 @@ export const onGroupViewRemovalCacheUpdates = ( const { viewId: id, groupId, projectId } = params // Check if default/ungrouped group - const isDefaultGroup = groupId.startsWith('default-') + const isDefaultGroup = isUngroupedGroup(groupId) // If default group and its now empty - remove it as it doesn't exist otherwise let shouldEvict diff --git a/packages/frontend-2/utils/globals.ts b/packages/frontend-2/utils/globals.ts index 69c864cda..6b71b1c15 100644 --- a/packages/frontend-2/utils/globals.ts +++ b/packages/frontend-2/utils/globals.ts @@ -13,6 +13,7 @@ import { ROOT_SUBSCRIPTION } from '~/lib/common/helpers/graphql' import { checkIfIsInPlaceNavigation } from '~/lib/common/helpers/navigation' +import { ViewerEventBusKeys } from '~/lib/viewer/helpers/eventBus' /** * Debugging helper to ensure variables are available in debugging scope @@ -38,5 +39,6 @@ export { checkIfIsInPlaceNavigation, ROOT_QUERY, ROOT_MUTATION, - ROOT_SUBSCRIPTION + ROOT_SUBSCRIPTION, + ViewerEventBusKeys } diff --git a/packages/server/modules/viewer/helpers/savedViews.ts b/packages/server/modules/viewer/helpers/savedViews.ts index ae895dabc..7099a8db3 100644 --- a/packages/server/modules/viewer/helpers/savedViews.ts +++ b/packages/server/modules/viewer/helpers/savedViews.ts @@ -1,5 +1,6 @@ import { base64Decode, base64Encode } from '@/modules/shared/helpers/cryptoHelper' import type { Nullable } from '@speckle/shared' +import { isUngroupedGroup } from '@speckle/shared/saved-views' import type { ViewerResourcesTarget } from '@speckle/shared/viewer/route' import { isModelResource, @@ -29,7 +30,7 @@ export const buildDefaultGroupId = (params: { export const decodeDefaultGroupId = (id: string): Nullable => { try { - if (!id.startsWith('default-')) return null + if (!isUngroupedGroup(id)) return null const json = base64Decode(id.replace('default-', '')) const obj = JSON.parse(json) if ( diff --git a/packages/server/modules/viewer/repositories/savedViews.ts b/packages/server/modules/viewer/repositories/savedViews.ts index ac804c3d0..4249c0895 100644 --- a/packages/server/modules/viewer/repositories/savedViews.ts +++ b/packages/server/modules/viewer/repositories/savedViews.ts @@ -31,6 +31,7 @@ import { decodeDefaultGroupId, formatResourceIdsForGroup } from '@/modules/viewer/helpers/savedViews' +import { isUngroupedGroup } from '@speckle/shared/saved-views' import { resourceBuilder } from '@speckle/shared/viewer/route' import cryptoRandomString from 'crypto-random-string' import dayjs from 'dayjs' @@ -249,7 +250,7 @@ export const getProjectSavedViewGroupsPageItemsFactory = // Adjust cursor, in case it points to non-existant default group let cursor = decode(params.cursor) - if (cursor?.id.startsWith('default-')) { + if (cursor?.id && isUngroupedGroup(cursor.id)) { // Default appears first, so just unset the cursor to get the real first item cursor = null } diff --git a/packages/server/modules/viewer/services/savedViewsManagement.ts b/packages/server/modules/viewer/services/savedViewsManagement.ts index dee84a034..0098dac81 100644 --- a/packages/server/modules/viewer/services/savedViewsManagement.ts +++ b/packages/server/modules/viewer/services/savedViewsManagement.ts @@ -33,9 +33,10 @@ import { inputToVersionedState } from '@speckle/shared/viewer/state' import { isValidBase64Image } from '@speckle/shared/images/base64' import type { GetViewerResourceGroups } from '@/modules/viewer/domain/operations/resources' import { formatResourceIdsForGroup } from '@/modules/viewer/helpers/savedViews' -import { omit } from 'lodash-es' +import { isUndefined, omit } from 'lodash-es' import type { DependenciesOf } from '@/modules/shared/helpers/factory' import { removeNullOrUndefinedKeys } from '@speckle/shared' +import { isUngroupedGroup } from '@speckle/shared/saved-views' /** * Validates an incoming resourceIdString against the resources in the project and returns the validated list (as a builder) @@ -388,7 +389,14 @@ export const updateSavedViewFactory = } // Check if there's any actual changes - const changes = removeNullOrUndefinedKeys(omit(input, ['id', 'projectId'])) + const changes = { + ...removeNullOrUndefinedKeys(omit(input, ['id', 'projectId'])), + ...(!isUndefined(input.groupId) + ? { + groupId: input.groupId // we want to allow null, which means - no group + } + : {}) + } if (Object.keys(changes).length === 0) { throw new SavedViewUpdateValidationError('No changes submitted with the input.', { info: { @@ -429,20 +437,26 @@ export const updateSavedViewFactory = // Validate groupId - group is a valid and accessible group in the project if (changes.groupId) { - const group = await deps.getSavedViewGroup({ - id: changes.groupId, - projectId - }) - if (!group) { - throw new SavedViewUpdateValidationError( - 'Provided groupId does not exist in the project.', - { - info: { - input, - userId + // Check if default group (actually means - null group) + const isDefaultGroup = changes.groupId && isUngroupedGroup(changes.groupId) + if (isDefaultGroup) { + changes.groupId = null + } else { + const group = await deps.getSavedViewGroup({ + id: changes.groupId, + projectId + }) + if (!group) { + throw new SavedViewUpdateValidationError( + 'Provided groupId does not exist in the project.', + { + info: { + input, + userId + } } - } - ) + ) + } } } diff --git a/packages/server/modules/viewer/tests/integration/savedViewsCrud.graph.spec.ts b/packages/server/modules/viewer/tests/integration/savedViewsCrud.graph.spec.ts index f9dbea51e..f157cdd0c 100644 --- a/packages/server/modules/viewer/tests/integration/savedViewsCrud.graph.spec.ts +++ b/packages/server/modules/viewer/tests/integration/savedViewsCrud.graph.spec.ts @@ -186,6 +186,11 @@ const fakeViewerState = (overrides?: PartialDeep apollo.execute(UpdateSavedViewDocument, input, options) + const getProjectViewGroups = ( + input: GetProjectSavedViewGroupsQueryVariables, + options?: ExecuteOperationOptions + ) => apollo.execute(GetProjectSavedViewGroupsDocument, input, options) + const model1ResourceIds = () => ViewerRoute.resourceBuilder().addModel(myModel1.id) const model2ResourceIds = () => ViewerRoute.resourceBuilder().addModel(myModel2.id) @@ -866,6 +871,73 @@ const fakeViewerState = (overrides?: PartialDeep { + const newGroupId = optionalGroup.id + + const res = await updateView({ + input: { + id: testView.id, + projectId: updatablesProject.id, + groupId: newGroupId + } + }) + + expect(res).to.not.haveGraphQLErrors() + const updatedView = res.data?.projectMutations.savedViewMutations.updateView + expect(updatedView).to.be.ok + expect(updatedView!.id).to.equal(testView.id) + expect(updatedView!.groupId).to.equal(newGroupId) + + // Unset group + const res2 = await updateView({ + input: { + id: testView.id, + projectId: updatablesProject.id, + groupId: null + } + }) + + expect(res2).to.not.haveGraphQLErrors() + const updatedView2 = res2.data?.projectMutations.savedViewMutations.updateView + expect(updatedView2).to.be.ok + expect(updatedView2!.id).to.equal(testView.id) + expect(updatedView2!.groupId).to.be.null + }) + + it('allow setting default group as group, which actually sets it to null', async () => { + // Get default group id + const groupsRes = await getProjectViewGroups( + { + projectId: updatablesProject.id, + input: { + limit: 1, + resourceIdString: models[0].id + } + }, + { assertNoErrors: true } + ) + + const defaultGroup = groupsRes.data?.project.savedViewGroups.items[0] + expect(defaultGroup).to.be.ok + + // Update view to have that be the group + const update = await updateView( + { + input: { + id: testView.id, + projectId: updatablesProject.id, + groupId: defaultGroup!.id + } + }, + { assertNoErrors: true } + ) + + const updatedView = update.data?.projectMutations.savedViewMutations.updateView + expect(updatedView?.id).to.be.ok + expect(updatedView?.groupId).to.be.null + expect(updatedView?.group.id).to.equal(defaultGroup!.id) + }) + it('fails if user has no access to update the view', async () => { const newName = 'Updated View Name' @@ -1130,11 +1202,6 @@ const fakeViewerState = (overrides?: PartialDeep new ViewerRoute.ViewerModelResource(id)) ) - const getProjectViewGroups = ( - input: GetProjectSavedViewGroupsQueryVariables, - options?: ExecuteOperationOptions - ) => apollo.execute(GetProjectSavedViewGroupsDocument, input, options) - before(async () => { otherReader = await createTestUser(buildBasicTestUser({ name: 'other-reader' })) await assignToWorkspace( @@ -1317,22 +1384,42 @@ const fakeViewerState = (overrides?: PartialDeep { - // const res = await getProjectViewGroups({ - // projectId: readTestProject.id, - // input: { - // limit: GROUP_COUNT, // all in 1 page - // resourceIdString: 'ayy' - // } - // }) + it('should support default groups cursor', async () => { + const res1 = await getProjectViewGroups( + { + projectId: readTestProject.id, + input: { + limit: 1, // only get default group + resourceIdString: getAllReadModelResourceIds().toString() + } + }, + { assertNoErrors: true } + ) - // expect(res).to.not.haveGraphQLErrors() - // const data = res.data?.project.savedViewGroups - // expect(data).to.be.ok - // expect(data!.totalCount).to.equal(1) // only default group returned - // expect(data!.items.length).to.equal(1) - // expect(data!.cursor).to.not.be.null - // }) + const group = res1.data?.project.savedViewGroups.items[0] + const cursor = res1.data?.project.savedViewGroups.cursor + + expect(group).to.be.ok + expect(group!.isUngroupedViewsGroup).to.be.true + expect(cursor).to.be.ok + + const res2 = await getProjectViewGroups( + { + projectId: readTestProject.id, + input: { + limit: 1, // get first real item + resourceIdString: getAllReadModelResourceIds().toString(), + cursor + } + }, + { assertNoErrors: true } + ) + + const group2 = res2.data?.project.savedViewGroups.items[0] + + expect(group2).to.be.ok + expect(group2!.isUngroupedViewsGroup).to.be.false + }) it('should respect search filter and filter out groups w/ views that dont have the search string in their name', async () => { const res = await getProjectViewGroups({ diff --git a/packages/server/package.json b/packages/server/package.json index 6319a3874..75a6d75bf 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -145,7 +145,7 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { - "@apollo/rover": "^0.23.0", + "@apollo/rover": "^0.35.0", "@faker-js/faker": "^8.4.1", "@graphql-codegen/cli": "^5.0.7", "@graphql-codegen/typed-document-node": "^5.1.2", diff --git a/packages/shared/src/saved-views/helpers/values.spec.ts b/packages/shared/src/saved-views/helpers/values.spec.ts new file mode 100644 index 000000000..2b1bfed54 --- /dev/null +++ b/packages/shared/src/saved-views/helpers/values.spec.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'vitest' +import { isUngroupedGroup } from './values.js' + +describe('isUngroupedGroup', () => { + it('should return true for ungrouped groups', () => { + expect(isUngroupedGroup('default-group')).toBe(true) + }) + + it('should return false for grouped groups', () => { + expect(isUngroupedGroup('custom-group')).toBe(false) + }) +}) diff --git a/packages/shared/src/saved-views/helpers/values.ts b/packages/shared/src/saved-views/helpers/values.ts index 75f5efb97..0a503bb3a 100644 --- a/packages/shared/src/saved-views/helpers/values.ts +++ b/packages/shared/src/saved-views/helpers/values.ts @@ -2,3 +2,5 @@ * Title used for the default 'Ungrouped Scenes' group in the saved views panel. */ export const ungroupedScenesGroupTitle = 'Ungrouped' + +export const isUngroupedGroup = (groupId: string) => groupId.startsWith('default-') diff --git a/yarn.lock b/yarn.lock index 901992514..d71ed28de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -135,18 +135,18 @@ __metadata: languageName: node linkType: hard -"@apollo/rover@npm:^0.23.0": - version: 0.23.0 - resolution: "@apollo/rover@npm:0.23.0" +"@apollo/rover@npm:^0.35.0": + version: 0.35.0 + resolution: "@apollo/rover@npm:0.35.0" dependencies: axios: "npm:^1.6.5" axios-proxy-builder: "npm:^0.1.1" console.table: "npm:^0.10.0" detect-libc: "npm:^2.0.0" - tar: "npm:^6.2.0" + tar: "npm:^7.0.0" bin: rover: run.js - checksum: 10/945f723c0a076503daf6622225653db761c3a0e2e80ee11f58b5f8cdc1cfe92eeaa732a5488881967b9123c49a92589800d3586ef61355767de16012be0af34a + checksum: 10/d3f87f65e2fe9f57360315a5f8cb57c00fd9eb7a3c6154d9aea2644cc023c882c60caa92a189db9008695223927e76d6941d760ca740619dae3bf811ee446616 languageName: node linkType: hard @@ -16278,7 +16278,7 @@ __metadata: resolution: "@speckle/server@workspace:packages/server" dependencies: "@apollo/client": "npm:^3.7.0" - "@apollo/rover": "npm:^0.23.0" + "@apollo/rover": "npm:^0.35.0" "@apollo/server": "npm:^4.11.0" "@aws-sdk/client-s3": "npm:^3.276.0" "@aws-sdk/lib-storage": "npm:^3.100.0" @@ -47036,7 +47036,7 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.4.0": +"tar@npm:^7.0.0, tar@npm:^7.4.0": version: 7.4.3 resolution: "tar@npm:7.4.3" dependencies: