From 74ebb21594ffb2c87a9ee6724bc57da4e3569758 Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Thu, 28 Aug 2025 13:06:55 +0300 Subject: [PATCH] feat(fe2): updated saved view tabs (#5332) --- .../components/error/page/Renderer.vue | 6 +- .../components/viewer/saved-views/Panel.vue | 14 +---- .../viewer/saved-views/panel/views/Group.vue | 17 +++--- packages/frontend-2/composables/routing.ts | 55 ++++++++++++------- .../composables/savedViews/management.ts | 25 +++++---- .../lib/viewer/helpers/savedViews.ts | 17 +++--- 6 files changed, 69 insertions(+), 65 deletions(-) diff --git a/packages/frontend-2/components/error/page/Renderer.vue b/packages/frontend-2/components/error/page/Renderer.vue index a674d9749..f1acd6f68 100644 --- a/packages/frontend-2/components/error/page/Renderer.vue +++ b/packages/frontend-2/components/error/page/Renderer.vue @@ -25,10 +25,10 @@ const props = defineProps<{ isGenericErrorPage?: boolean }>() -const route = useRoute() +const route = useCurrentRouteTillNavigated() -const isProjectRoute = computed(() => route.path.match(/\/projects\/[^/]+/)) -const isWorkspaceRoute = computed(() => route.path.match(/\/workspaces\/[^/]+/)) +const isProjectRoute = computed(() => route.value.path.match(/\/projects\/[^/]+/)) +const isWorkspaceRoute = computed(() => route.value.path.match(/\/workspaces\/[^/]+/)) const finalError = computed(() => formatAppError(props.error)) const isNoProjectAccessError = computed( diff --git a/packages/frontend-2/components/viewer/saved-views/Panel.vue b/packages/frontend-2/components/viewer/saved-views/Panel.vue index 47de90b11..cd417cffd 100644 --- a/packages/frontend-2/components/viewer/saved-views/Panel.vue +++ b/packages/frontend-2/components/viewer/saved-views/Panel.vue @@ -106,10 +106,7 @@ import { useMutationLoading } from '@vue/apollo-composable' import { Search, FolderPlus, Plus, X } from 'lucide-vue-next' import { useSynchronizedCookie } from '~/lib/common/composables/reactiveCookie' import { graphql } from '~/lib/common/generated/gql' -import { - SavedViewVisibility, - WorkspaceSeatType -} from '~/lib/common/generated/gql/graphql' +import { WorkspaceSeatType } from '~/lib/common/generated/gql/graphql' import { useCreateSavedView } from '~/lib/viewer/composables/savedViews/management' import { useInjectedViewerState } from '~/lib/viewer/composables/setup' import { ViewsType, viewsTypeLabels } from '~/lib/viewer/helpers/savedViews' @@ -147,7 +144,7 @@ const createSavedView = useCreateSavedView() const isLoading = useMutationLoading() const { on, bind, value: search } = useDebouncedTextInput() -const selectedViewsType = ref(ViewsType.Personal) +const selectedViewsType = ref(ViewsType.All) const hideViewerSeatDisclaimer = useSynchronizedCookie( 'hideViewerSeatSavedViewsDisclaimer', { @@ -167,12 +164,7 @@ const isLowerPlan = computed(() => !project.value?.workspace?.planSupportsSavedV const onAddView = async () => { if (isLoading.value) return - const view = await createSavedView({ - visibility: - selectedViewsType.value === ViewsType.Shared - ? SavedViewVisibility.Public - : undefined - }) + const view = await createSavedView({}) if (view) { // Auto-open the group that the view created to openedGroupState.value.set(view.group.id, true) diff --git a/packages/frontend-2/components/viewer/saved-views/panel/views/Group.vue b/packages/frontend-2/components/viewer/saved-views/panel/views/Group.vue index e4b8f37ba..ad4361807 100644 --- a/packages/frontend-2/components/viewer/saved-views/panel/views/Group.vue +++ b/packages/frontend-2/components/viewer/saved-views/panel/views/Group.vue @@ -63,18 +63,17 @@ import type { LayoutMenuItem } from '@speckle/ui-components' import { useMutationLoading } from '@vue/apollo-composable' import { Ellipsis, Plus } from 'lucide-vue-next' import { graphql } from '~/lib/common/generated/gql' -import { - SavedViewVisibility, - type UseUpdateSavedViewGroup_SavedViewGroupFragment, - type ViewerSavedViewsPanelViewsGroup_ProjectFragment, - type ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragment, - type ViewerSavedViewsPanelViewsGroupDeleteDialog_SavedViewGroupFragment +import type { + UseUpdateSavedViewGroup_SavedViewGroupFragment, + ViewerSavedViewsPanelViewsGroup_ProjectFragment, + ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragment, + ViewerSavedViewsPanelViewsGroupDeleteDialog_SavedViewGroupFragment } from '~/lib/common/generated/gql/graphql' import { useCreateSavedView, useUpdateSavedViewGroup } from '~/lib/viewer/composables/savedViews/management' -import { ViewsType } from '~/lib/viewer/helpers/savedViews' +import type { ViewsType } from '~/lib/viewer/helpers/savedViews' const MenuItems = StringEnum(['Delete', 'Rename']) type MenuItems = StringEnumValues @@ -182,9 +181,7 @@ const onActionChosen = async (item: LayoutMenuItem) => { const onAddGroupView = async () => { await createView({ - groupId: props.group.id, - visibility: - props.viewsType === ViewsType.Shared ? SavedViewVisibility.Public : undefined + groupId: props.group.id }) open.value = true } diff --git a/packages/frontend-2/composables/routing.ts b/packages/frontend-2/composables/routing.ts index 642acdf00..00a2bcd38 100644 --- a/packages/frontend-2/composables/routing.ts +++ b/packages/frontend-2/composables/routing.ts @@ -4,9 +4,10 @@ import type { RouteLocationAsPathGeneric } from '#vue-router' import { buildManualPromise } from '@speckle/shared' +import { useScopedState } from '~/lib/common/composables/scopedState' const useRouterNavigatingState = () => - useState('use_router_navigating_state', () => ({ + useScopedState('use_router_navigating_state', () => ({ allActiveWaits: >>[], /** * Used for debugging to assign an incrementing id to each invocation @@ -20,8 +21,8 @@ const useRouterNavigatingDevUtils = () => { const ret = { getLogId: () => { - const newVal = state.value.logId + 1 - state.value.logId = newVal + const newVal = state.logId + 1 + state.logId = newVal return newVal + '' }, @@ -40,6 +41,16 @@ const useRouterNavigatingDevUtils = () => { devTrace(...args) }) }, + waitForNavigationsClear: async () => + await until($isNavigating) + .toBe(false, { + throwOnTimeout: true, + timeout: 500 + }) + .catch((err) => { + // Swallow throw, just log and continue + $logger.error({ err }, 'Waiting for nuxt navigations to clear timed out') + }), isNuxtNavigating: $isNavigating, logger: $logger } @@ -69,7 +80,7 @@ type SafeRouterNavigationOptions< * Supports debugRoutes=1 query param for debug logs */ export const useSafeRouter = () => { - const { getLogId, debugLog, debugTrace, isNuxtNavigating, logger } = + const { getLogId, debugLog, debugTrace, waitForNavigationsClear } = useRouterNavigatingDevUtils() const router = useRouter() const state = useRouterNavigatingState() @@ -87,27 +98,16 @@ export const useSafeRouter = () => { const waitPromise = buildManualPromise() const logId = getLogId() - const waitForNavigationsClear = async () => - await until(isNuxtNavigating) - .toBe(false, { - throwOnTimeout: true, - timeout: 500 - }) - .catch((err) => { - // Swallow throw, just log and continue - logger.error({ err }, 'Waiting for nuxt navigations to clear timed out') - }) - debugTrace(`[{logId}] Safe router ${action} registered`, { initialTo: to(), logId }) try { - const activeWaits = state.value.allActiveWaits.slice() + const activeWaits = state.allActiveWaits.slice() // Queue up another wait - state.value.allActiveWaits = [...state.value.allActiveWaits, waitPromise.promise] + state.allActiveWaits = [...state.allActiveWaits, waitPromise.promise] // Wait for all previously queued up waits await Promise.allSettled(activeWaits) @@ -150,7 +150,7 @@ export const useSafeRouter = () => { logId, navResult }) - state.value.allActiveWaits = state.value.allActiveWaits.filter( + state.allActiveWaits = state.allActiveWaits.filter( (p) => p !== waitPromise.promise ) waitPromise.resolve() @@ -160,7 +160,7 @@ export const useSafeRouter = () => { waitPromise.reject(e) throw e } finally { - state.value.allActiveWaits = state.value.allActiveWaits.filter( + state.allActiveWaits = state.allActiveWaits.filter( (p) => p !== waitPromise.promise ) } @@ -186,3 +186,20 @@ export const useSafeRouter = () => { return { ...router, push, replace } } + +/** + * Similar to useRoute, but will not change the value until the new/incoming route has fully finished navigating + */ +export const useCurrentRouteTillNavigated = () => { + const baseRoute = useRoute() + const { $isNavigating } = useNuxtApp() + const route = shallowRef({ ...toRaw(baseRoute) }) + + watch($isNavigating, (newVal, oldVal) => { + if (!newVal && oldVal) { + route.value = { ...toRaw(baseRoute) } + } + }) + + return route +} diff --git a/packages/frontend-2/lib/viewer/composables/savedViews/management.ts b/packages/frontend-2/lib/viewer/composables/savedViews/management.ts index 0d6586061..ecbba4aad 100644 --- a/packages/frontend-2/lib/viewer/composables/savedViews/management.ts +++ b/packages/frontend-2/lib/viewer/composables/savedViews/management.ts @@ -240,7 +240,7 @@ export const useUpdateSavedView = () => { const { input } = params const oldGroupId = params.view.group.id - const oldVisibility = params.view.visibility + // const oldVisibility = params.view.visibility const result = await mutate( { input }, @@ -267,17 +267,18 @@ export const useUpdateSavedView = () => { }) } - const newVisibility = update.visibility - const visibilityChanged = oldVisibility !== newVisibility - if (visibilityChanged) { - // Update all SavedViewGroup.views to see if it now should appear in there or not - modifyObjectField( - cache, - getCacheId('SavedViewGroup', newGroupId), - 'views', - ({ helpers: { evict } }) => evict() - ) - } + // W/ current filter setup, if u can change visibility, you're gonna see it in all filtered groups + // const newVisibility = update.visibility + // const visibilityChanged = oldVisibility !== newVisibility + // if (visibilityChanged) { + // // Update all SavedViewGroup.views to see if it now should appear in there or not + // modifyObjectField( + // cache, + // getCacheId('SavedViewGroup', newGroupId), + // 'views', + // ({ helpers: { evict } }) => evict() + // ) + // } } } ).catch(convertThrowIntoFetchResult) diff --git a/packages/frontend-2/lib/viewer/helpers/savedViews.ts b/packages/frontend-2/lib/viewer/helpers/savedViews.ts index a3c1285aa..f53e5eb00 100644 --- a/packages/frontend-2/lib/viewer/helpers/savedViews.ts +++ b/packages/frontend-2/lib/viewer/helpers/savedViews.ts @@ -1,16 +1,15 @@ import { throwUncoveredError, type StringEnumValues } from '@speckle/shared' import { isObjectLike, isString } from 'lodash-es' -import { SavedViewVisibility } from '~/lib/common/generated/gql/graphql' export const ViewsType = { - Personal: 'personal', - Shared: 'shared' + All: 'all', + Mine: 'mine' } as const export type ViewsType = StringEnumValues export const viewsTypeLabels: Record = { - [ViewsType.Personal]: 'Personal', - [ViewsType.Shared]: 'Shared' + [ViewsType.All]: 'All views', + [ViewsType.Mine]: 'My views' } /** @@ -45,14 +44,12 @@ export const serializeSavedViewUrlSettings = ( } export const viewsTypeToFilters = (type: ViewsType) => { - if (type === ViewsType.Personal) { + if (type === ViewsType.Mine) { return { onlyAuthored: true } - } else if (type === ViewsType.Shared) { - return { - onlyVisibility: SavedViewVisibility.Public - } + } else if (type === ViewsType.All) { + return {} } else { throwUncoveredError(type) }