Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 15 additions & 39 deletions src/hooks/useRestoreWorkspacesTabOnNavigate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {getPreservedNavigatorState} from '@libs/Navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState';
import {isFullScreenName, isWorkspaceNavigatorRouteName} from '@libs/Navigation/helpers/isNavigatorName';
import {isWorkspaceNavigatorRouteName} from '@libs/Navigation/helpers/isNavigatorName';
import {getWorkspacesTabStateFromSessionStorage} from '@libs/Navigation/helpers/lastVisitedTabPathUtils';
import navigateToWorkspacesPage from '@libs/Navigation/helpers/navigateToWorkspacesPage';
import {getTabState} from '@libs/Navigation/helpers/tabNavigatorUtils';
Expand Down Expand Up @@ -33,48 +33,25 @@ function useRestoreWorkspacesTabOnNavigate() {
// Find the last route the user had open in the Workspaces tab (workspace, domain, or list).
// Priority: live nav state (root level) -> inside TabNavigator -> preserved state -> session storage.
const rootState = navigationRef.isReady() ? navigationRef.getRootState() : undefined;
const routeState = (() => {
const topmostFullScreenRoute = rootState?.routes?.findLast((route) => isFullScreenName(route.name));
if (!topmostFullScreenRoute) {
return {};
const lastTabNavigatorRoute = rootState?.routes?.findLast((route) => route.name === NAVIGATORS.TAB_NAVIGATOR);
const lastWorkspacesTabNavigatorRoute = (() => {
if (lastTabNavigatorRoute) {
const workspaceNavigatorRoute = getTabState(lastTabNavigatorRoute)?.routes?.find((route) => route.name === NAVIGATORS.WORKSPACE_NAVIGATOR);
const workspaceNavigatorState = workspaceNavigatorRoute?.state ?? (workspaceNavigatorRoute?.key ? getPreservedNavigatorState(workspaceNavigatorRoute.key) : undefined);
const lastWorkspaceRoute = workspaceNavigatorState?.routes?.findLast((route) => isWorkspaceNavigatorRouteName(route.name));
if (lastWorkspaceRoute) {
return lastWorkspaceRoute;
}
}

// Multiple TAB_NAVIGATOR instances can coexist in the root stack — when navigation from
// inside an RHP targets a tab, linkTo PUSHes a fresh TabNavigator above the modal, and that
// new instance's WORKSPACE_NAVIGATOR slot starts empty. Older instances kept alive by
// ensureTabNavigatorRoutes still hold the previous workspace state, so flatten every
// workspace route from every TabNavigator in stack order and take the most recent one.
const lastWorkspaceRoute = (rootState?.routes ?? [])
.filter((route) => route.name === NAVIGATORS.TAB_NAVIGATOR)
.flatMap((tabNavigatorRoute) => {
const workspaceNavigatorRoute = getTabState(tabNavigatorRoute)?.routes?.find((route) => route.name === NAVIGATORS.WORKSPACE_NAVIGATOR);
const workspaceNavigatorState = workspaceNavigatorRoute?.state ?? (workspaceNavigatorRoute?.key ? getPreservedNavigatorState(workspaceNavigatorRoute.key) : undefined);
return workspaceNavigatorState?.routes?.filter((route) => isWorkspaceNavigatorRouteName(route.name)) ?? [];
})
.at(-1);

if (lastWorkspaceRoute) {
const tabState = lastWorkspaceRoute.state ?? (lastWorkspaceRoute.key ? getPreservedNavigatorState(lastWorkspaceRoute.key) : undefined);
return {lastWorkspacesTabNavigatorRoute: lastWorkspaceRoute, workspacesTabState: tabState, topmostFullScreenRoute};
}

// Fall back to session storage when no workspace route exists anywhere in the navigation tree.
// getStateFromPath returns state rooted at TAB_NAVIGATOR, so we must drill into it first.
// Fall back to session storage. Shape mirrors the live nav state: TabNavigator -> WorkspaceNavigator -> WorkspaceSplitNavigator.
const sessionTabNavigatorRoute = getWorkspacesTabStateFromSessionStorage()?.routes?.findLast((route) => route.name === NAVIGATORS.TAB_NAVIGATOR);
const sessionRoute = getTabState(sessionTabNavigatorRoute)
?.routes?.findLast((route) => route.name === NAVIGATORS.WORKSPACE_NAVIGATOR)
?.state?.routes?.findLast((route) => isWorkspaceNavigatorRouteName(route.name));
if (sessionRoute) {
return {lastWorkspacesTabNavigatorRoute: sessionRoute, workspacesTabState: sessionRoute.state, topmostFullScreenRoute};
}

return {topmostFullScreenRoute};
const sessionWorkspaceNavigatorRoute = sessionTabNavigatorRoute?.state?.routes?.find((route) => route.name === NAVIGATORS.WORKSPACE_NAVIGATOR);
return sessionWorkspaceNavigatorRoute?.state?.routes?.findLast((route) => isWorkspaceNavigatorRouteName(route.name));
})();

const {lastWorkspacesTabNavigatorRoute, workspacesTabState, topmostFullScreenRoute} = routeState;

// If the last route was a specific workspace or domain, extract its ID from params
const params = workspacesTabState?.routes?.at(0)?.params as
const params = lastWorkspacesTabNavigatorRoute?.state?.routes?.at(0)?.params as
| WorkspaceSplitNavigatorParamList[typeof SCREENS.WORKSPACE.INITIAL]
| DomainSplitNavigatorParamList[typeof SCREENS.DOMAIN.INITIAL];
const paramsPolicyID = params && 'policyID' in params ? params.policyID : undefined;
Expand All @@ -94,8 +71,7 @@ function useRestoreWorkspacesTabOnNavigate() {
policy: lastViewedPolicy,
domain: lastViewedDomain,
lastWorkspacesTabNavigatorRoute,
topmostFullScreenRoute,
workspacesTabState,
lastTabNavigatorRoute,
});
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
import type {RouterConfigOptions} from '@react-navigation/native';
import type {ParamListBase, PartialState, RouterConfigOptions, StackNavigationState} from '@react-navigation/native';
import {StackRouter} from '@react-navigation/native';
import {getWorkspacesTabStateFromSessionStorage} from '@libs/Navigation/helpers/lastVisitedTabPathUtils';
import {getPreservedNavigatorState} from '@navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState';
import NAVIGATORS from '@src/NAVIGATORS';
import SCREENS from '@src/SCREENS';
import type WorkspaceNavigatorRouterOptions from './types';

/**
* Builds the WorkspaceNavigator initial state from the saved Workspaces-tab path in sessionStorage —
* the routes the user had open in the tab before navigating away. Prepends WORKSPACES_LIST when the
* saved path didn't include it so the back-stack works (swipe back from a workspace returns to the list).
* Returns `undefined` on native (no sessionStorage), when nothing is saved, or when the parsed
* state has no WORKSPACE_NAVIGATOR slot.
*/
function buildWorkspaceInitialStateFromSessionStorage(): PartialState<StackNavigationState<ParamListBase>> | undefined {
const sessionState = getWorkspacesTabStateFromSessionStorage();
const restoredState = sessionState?.routes?.find((r) => r.name === NAVIGATORS.TAB_NAVIGATOR)?.state?.routes?.find((r) => r.name === NAVIGATORS.WORKSPACE_NAVIGATOR)?.state as
| PartialState<StackNavigationState<ParamListBase>>
| undefined;
if (!restoredState?.routes?.length) {
return undefined;
}
if (restoredState.routes.some((r) => r.name === SCREENS.WORKSPACES_LIST)) {
return restoredState;
}
const routes = [{name: SCREENS.WORKSPACES_LIST}, ...restoredState.routes];
return {routes, index: routes.length - 1};
}

function WorkspaceRouter(options: WorkspaceNavigatorRouterOptions) {
const stackRouter = StackRouter(options);

return {
...stackRouter,
getInitialState({routeNames, routeParamList, routeGetIdList}: RouterConfigOptions) {
getInitialState(configOptions: RouterConfigOptions) {
const preservedState = getPreservedNavigatorState(options.parentRoute.key);
return preservedState ?? stackRouter.getInitialState({routeNames, routeParamList, routeGetIdList});
if (preservedState) {
return preservedState;
}
const sessionState = buildWorkspaceInitialStateFromSessionStorage();
if (sessionState) {
return stackRouter.getRehydratedState(sessionState, configOptions);
}
return stackRouter.getInitialState(configOptions);
},
};
}
Expand Down
15 changes: 11 additions & 4 deletions src/libs/Navigation/NavigationRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,17 @@ function parseAndLogRoute(state: NavigationState) {
const lastRoute = state.routes.at(-1);
const activeTabName = getActiveTabName(lastRoute);

if (activeTabName === NAVIGATORS.WORKSPACE_NAVIGATOR) {
saveWorkspacesTabPathToSessionStorage(currentPath);
} else if (activeTabName === NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR) {
saveSettingsTabPathToSessionStorage(currentPath);
// Skip saving when the focused route is the navigator itself (no nested screen yet), e.g. during
// the intermediate state right after `TabActions.jumpTo(WORKSPACE_NAVIGATOR)` and before the
// navigator mounts. The path collapses to `/` in that moment and would clobber the real path
// that WorkspaceRouter.getInitialState needs to read.
const isFocusedOnEmptyNavigator = focusedRoute?.name === activeTabName;
if (!isFocusedOnEmptyNavigator) {
if (activeTabName === NAVIGATORS.WORKSPACE_NAVIGATOR) {
saveWorkspacesTabPathToSessionStorage(currentPath);
} else if (activeTabName === NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR) {
saveSettingsTabPathToSessionStorage(currentPath);
}
}

// Fullstory Page navigation tracking
Expand Down
Loading
Loading