-
Notifications
You must be signed in to change notification settings - Fork 2.9k
/
linkTo.ts
121 lines (104 loc) · 5.42 KB
/
linkTo.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import {getActionFromState} from '@react-navigation/core';
import {NavigationAction, NavigationContainerRef, NavigationState, PartialState} from '@react-navigation/native';
import {Writable} from 'type-fest';
import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
import {Route} from '@src/ROUTES';
import getStateFromPath from './getStateFromPath';
import getTopmostReportId from './getTopmostReportId';
import linkingConfig from './linkingConfig';
import {NavigationRoot, RootStackParamList, StackNavigationAction} from './types';
type ActionPayloadParams = {
screen?: string;
params?: unknown;
path?: string;
};
type ActionPayload = {
params?: ActionPayloadParams;
};
/**
* Motivation for this function is described in NAVIGATION.md
*
* @param action action generated by getActionFromState
* @param state The root state
* @returns minimalAction minimal action is the action that we should dispatch
*/
function getMinimalAction(action: NavigationAction, state: NavigationState): Writable<NavigationAction> {
let currentAction: NavigationAction = action;
let currentState: NavigationState | PartialState<NavigationState> | undefined = state;
let currentTargetKey: string | undefined;
while (currentAction.payload && 'name' in currentAction.payload && currentState?.routes[currentState.index ?? -1].name === currentAction.payload.name) {
if (!currentState?.routes[currentState.index ?? -1].state) {
break;
}
currentState = currentState?.routes[currentState.index ?? -1].state;
currentTargetKey = currentState?.key;
const payload = currentAction.payload as ActionPayload;
// Creating new smaller action
currentAction = {
type: currentAction.type,
payload: {
name: payload?.params?.screen,
params: payload?.params?.params,
path: payload?.params?.path,
},
target: currentTargetKey,
};
}
return currentAction;
}
export default function linkTo(navigation: NavigationContainerRef<RootStackParamList> | null, path: Route, type?: string, isActiveRoute?: boolean) {
if (!navigation) {
throw new Error("Couldn't find a navigation object. Is your component inside a screen in a navigator?");
}
let root: NavigationRoot = navigation;
let current: NavigationRoot | undefined;
// Traverse up to get the root navigation
// eslint-disable-next-line no-cond-assign
while ((current = root.getParent())) {
root = current;
}
const rootState = root.getState();
const state = getStateFromPath(path);
const action: StackNavigationAction = getActionFromState(state, linkingConfig.config);
// If action type is different than NAVIGATE we can't change it to the PUSH safely
if (action?.type === CONST.NAVIGATION.ACTION_TYPE.NAVIGATE) {
// In case if type is 'FORCED_UP' we replace current screen with the provided. This means the current screen no longer exists in the stack
if (type === CONST.NAVIGATION.TYPE.FORCED_UP) {
action.type = CONST.NAVIGATION.ACTION_TYPE.REPLACE;
// If this action is navigating to the report screen and the top most navigator is different from the one we want to navigate - PUSH the new screen to the top of the stack
} else if (action.payload.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR && getTopmostReportId(rootState) !== getTopmostReportId(state)) {
action.type = CONST.NAVIGATION.ACTION_TYPE.PUSH;
// If the type is UP, we deeplinked into one of the RHP flows and we want to replace the current screen with the previous one in the flow
// and at the same time we want the back button to go to the page we were before the deeplink
} else if (type === CONST.NAVIGATION.TYPE.UP) {
action.type = CONST.NAVIGATION.ACTION_TYPE.REPLACE;
// If this action is navigating to the RightModalNavigator and the last route on the root navigator is not RightModalNavigator then push
} else if (action.payload.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR && rootState?.routes?.at(-1)?.name !== NAVIGATORS.RIGHT_MODAL_NAVIGATOR) {
action.type = CONST.NAVIGATION.ACTION_TYPE.PUSH;
}
}
if (action && 'payload' in action && action.payload && 'name' in action.payload && action.payload.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR) {
const minimalAction = getMinimalAction(action, navigation.getRootState());
if (minimalAction) {
// There are situations where a route already exists on the current navigation stack
// But we want to push the same route instead of going back in the stack
// Which would break the user navigation history
if (!isActiveRoute && type === CONST.NAVIGATION.ACTION_TYPE.PUSH) {
minimalAction.type = CONST.NAVIGATION.ACTION_TYPE.PUSH;
}
// There are situations when the user is trying to access a route which he has no access to
// So we want to redirect him to the right one and replace the one he tried to access
if (type === CONST.NAVIGATION.ACTION_TYPE.REPLACE) {
minimalAction.type = CONST.NAVIGATION.ACTION_TYPE.REPLACE;
}
root.dispatch(minimalAction);
return;
}
}
if (action !== undefined) {
root.dispatch(action);
} else {
root.reset(state);
}
}