Skip to content

Commit 1caebf1

Browse files
Hedger WangFacebook Github Bot 9
authored andcommitted
Add data structure to manage the stack for the legacy navigator.
Reviewed By: ericvicenti Differential Revision: D3002015 fb-gh-sync-id: 27519a1b67fe514bd6d9102031ea482f76fae16b shipit-source-id: 27519a1b67fe514bd6d9102031ea482f76fae16b
1 parent f5edabf commit 1caebf1

2 files changed

Lines changed: 682 additions & 0 deletions

File tree

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
/**
2+
* Copyright 2004-present Facebook. All Rights Reserved.
3+
*
4+
* @providesModule NavigationLegacyNavigatorRouteStack
5+
* @flow
6+
*/
7+
'use strict';
8+
9+
const invariant = require('fbjs/lib/invariant');
10+
11+
import type {
12+
NavigationState,
13+
NavigationParentState,
14+
} from 'NavigationStateUtils';
15+
16+
17+
type IterationCallback = (route: any, index: number, key: string) => void;
18+
19+
function isRouteEmpty(route: any): boolean {
20+
return (route === undefined || route === null || route === '') || false;
21+
}
22+
23+
function areRouteNodesEqual(
24+
one: Array<RouteNode>,
25+
two: Array<RouteNode>,
26+
): boolean {
27+
if (one === two) {
28+
return true;
29+
}
30+
31+
if (one.length !== two.length) {
32+
return false;
33+
}
34+
for (let ii = 0, jj = one.length; ii < jj; ii++) {
35+
if (one[ii] !== two[ii]) {
36+
return false;
37+
}
38+
}
39+
return true;
40+
}
41+
42+
let _nextRouteNodeID = 0;
43+
44+
/**
45+
* Private struct class that holds the key for a route.
46+
*/
47+
class RouteNode {
48+
key: string;
49+
route: any;
50+
constructor(route: any) {
51+
// Key value gets bigger incrementally. Developer can compare the
52+
// keys of two routes then know which route is added to the stack
53+
// earlier.
54+
const key = String(_nextRouteNodeID++);
55+
if (__DEV__ ) {
56+
// Ensure the immutability of the node.
57+
Object.defineProperty(this, 'key' , {
58+
enumerable: true,
59+
configurable: false,
60+
writable: false,
61+
value: key,
62+
});
63+
Object.defineProperty(this, 'route' , {
64+
enumerable: true,
65+
configurable: false,
66+
writable: false,
67+
value: route,
68+
});
69+
} else {
70+
this.key = key;
71+
this.route = route;
72+
}
73+
}
74+
75+
toNavigationState(): NavigationState {
76+
const state: NavigationState = this;
77+
return state;
78+
}
79+
}
80+
81+
let _nextRouteStackID = 0;
82+
83+
/**
84+
* The data structure that holds a list of routes and the focused index
85+
* of the routes. This data structure is implemented as immutable data
86+
* and mutation (e.g. push, pop...etc) will yields a new instance.
87+
*/
88+
class RouteStack {
89+
_index: number;
90+
_key: string;
91+
_routeNodes: Array<RouteNode>;
92+
93+
constructor(index: number, routes: Array<any>) {
94+
invariant(
95+
routes.length > 0,
96+
'routes must not be an empty array'
97+
);
98+
99+
invariant(
100+
index > -1 && index <= routes.length - 1,
101+
'index out of bound'
102+
);
103+
104+
105+
let routeNodes;
106+
if (routes[0] instanceof RouteNode) {
107+
// The array is already an array of <RouteNode>.
108+
routeNodes = routes;
109+
} else {
110+
// Wrap the route with <RouteNode>.
111+
routeNodes = routes.map((route) => {
112+
invariant(!isRouteEmpty(route), 'route must not be mepty');
113+
return new RouteNode(route);
114+
});
115+
}
116+
117+
this._routeNodes = routeNodes;
118+
this._index = index;
119+
this._key = String(_nextRouteStackID++);
120+
}
121+
122+
/* $FlowFixMe - get/set properties not yet supported */
123+
get size(): number {
124+
return this._routeNodes.length;
125+
}
126+
127+
/* $FlowFixMe - get/set properties not yet supported */
128+
get index(): number {
129+
return this._index;
130+
}
131+
132+
// Export as...
133+
toArray(): Array<any> {
134+
return this._routeNodes.map(node => node.route);
135+
}
136+
137+
toNavigationState(): NavigationParentState {
138+
return {
139+
index: this._index,
140+
key: this._key,
141+
children: this._routeNodes.map(node => node.toNavigationState()),
142+
};
143+
}
144+
145+
get(index: number): any {
146+
if (index < 0 || index > this._routeNodes.length - 1) {
147+
return null;
148+
}
149+
return this._routeNodes[index].route;
150+
}
151+
152+
/**
153+
* Returns the key associated with the route.
154+
* When a route is added to a stack, the stack creates a key for this route.
155+
* The key will persist until the initial stack and its derived stack
156+
* no longer contains this route.
157+
*/
158+
keyOf(route: any): ?string {
159+
if (isRouteEmpty(route)) {
160+
return null;
161+
}
162+
const index = this.indexOf(route);
163+
return index > -1 ?
164+
this._routeNodes[index].key :
165+
null;
166+
}
167+
168+
indexOf(route: any): number {
169+
if (isRouteEmpty(route)) {
170+
return -1;
171+
}
172+
173+
for (let ii = 0, jj = this._routeNodes.length; ii < jj; ii++) {
174+
let node = this._routeNodes[ii];
175+
if (node.route === route) {
176+
return ii;
177+
}
178+
}
179+
180+
return -1;
181+
}
182+
183+
slice(begin: ?number, end: ?number): RouteStack {
184+
// check `begin` and `end` first to keep @flow happy.
185+
const routeNodes = (end === undefined || end === null) ?
186+
this._routeNodes.slice(begin || 0) :
187+
this._routeNodes.slice(begin || 0, end || 0);
188+
189+
const index = Math.min(this._index, routeNodes.length - 1);
190+
return this._update(index, routeNodes);
191+
}
192+
193+
/**
194+
* Returns a new stack with the provided route appended,
195+
* starting at this stack size.
196+
*/
197+
push(route: any): RouteStack {
198+
199+
invariant(
200+
!isRouteEmpty(route),
201+
'Must supply route to push'
202+
);
203+
204+
invariant(this.indexOf(route) === -1, 'route must be unique');
205+
206+
// When pushing, removes the rest of the routes past the current index.
207+
const routeNodes = this._routeNodes.slice(0, this._index + 1);
208+
routeNodes.push(new RouteNode(route));
209+
return this._update(routeNodes.length - 1, routeNodes);
210+
}
211+
212+
/**
213+
* Returns a new stack a size ones less than this stack,
214+
* excluding the last index in this stack.
215+
*/
216+
pop(): RouteStack {
217+
invariant(
218+
this._routeNodes.length > 1,
219+
'should not pop routeNodes stack to empty'
220+
);
221+
222+
// When popping, removes the rest of the routes past the current index.
223+
const routeNodes = this._routeNodes.slice(0, this._index);
224+
return this._update(routeNodes.length - 1, routeNodes);
225+
}
226+
227+
jumpToIndex(index: number): RouteStack {
228+
invariant(
229+
index > -1 && index < this._routeNodes.length,
230+
'jumpToIndex: index out of bound'
231+
);
232+
233+
return this._update(index, this._routeNodes);
234+
}
235+
236+
/**
237+
* Replace a route in the navigation stack.
238+
*
239+
* `index` specifies the route in the stack that should be replaced.
240+
* If it's negative, it counts from the back.
241+
*/
242+
replaceAtIndex(index: number, route: any): RouteStack {
243+
invariant(
244+
!isRouteEmpty(route),
245+
'Must supply route to replace'
246+
);
247+
248+
if (this.get(index) === route) {
249+
return this;
250+
}
251+
252+
invariant(this.indexOf(route) === -1, 'route must be unique');
253+
254+
if (index < 0) {
255+
index += this._routeNodes.length;
256+
}
257+
258+
invariant(
259+
index > -1 && index < this._routeNodes.length,
260+
'replaceAtIndex: index out of bound'
261+
);
262+
263+
const routeNodes = this._routeNodes.slice(0);
264+
routeNodes[index] = new RouteNode(route);
265+
return this._update(index, routeNodes);
266+
}
267+
268+
// Iterations
269+
forEach(callback: IterationCallback, context: ?Object): void {
270+
this._routeNodes.forEach((node, index) => {
271+
callback.call(context, node.route, index, node.key);
272+
});
273+
}
274+
275+
mapToArray(callback: IterationCallback, context: ?Object): Array<any> {
276+
return this._routeNodes.map((node, index) => {
277+
return callback.call(context, node.route, index, node.key);
278+
});
279+
}
280+
281+
_update(index: number, routeNodes: Array<RouteNode>): RouteStack {
282+
if (
283+
this._index === index &&
284+
areRouteNodesEqual(this._routeNodes, routeNodes)
285+
) {
286+
return this;
287+
}
288+
289+
return new RouteStack(index, routeNodes);
290+
}
291+
}
292+
293+
module.exports = RouteStack;

0 commit comments

Comments
 (0)