2016-03-21 18:13:59 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*
|
|
|
|
* @providesModule NavigationScenesReducer
|
|
|
|
* @flow
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const invariant = require('fbjs/lib/invariant');
|
Fix UIExplorer Search
Summary:
In UI explorer, the route is made of an object which look like this.
```
{key: 'AppList', filter: 'query string from the search box'}
```
When a new search query is enter, a new `filter` value is applied, and the key `AppList`
remains the same.
In NavigationScenesReducer, we should compare the routes with both their keys and references.
The current implementation only compares the keys, which unfortunately depends on the a weak
assumption that all routes immutable and keys are unique.
In UI Explore, the route key is always 'AppList', which makes sense since we use the key
to match the scene, and whenever a new search query is provides, a new route will be created.
Reviewed By: nicklockwood
Differential Revision: D3357023
fbshipit-source-id: a3c9e98092f5ce555e5dbb4cc806bab2e67d8014
2016-05-27 19:02:55 +00:00
|
|
|
const shallowEqual = require('fbjs/lib/shallowEqual');
|
2016-03-21 18:13:59 +00:00
|
|
|
|
|
|
|
import type {
|
2016-05-21 01:09:57 +00:00
|
|
|
NavigationRoute,
|
2016-03-21 18:13:59 +00:00
|
|
|
NavigationScene,
|
2016-05-21 01:09:57 +00:00
|
|
|
NavigationState,
|
2016-03-21 18:13:59 +00:00
|
|
|
} from 'NavigationTypeDefinition';
|
|
|
|
|
|
|
|
const SCENE_KEY_PREFIX = 'scene_';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function to compare route keys (e.g. "9", "11").
|
|
|
|
*/
|
|
|
|
function compareKey(one: string, two: string): number {
|
2016-05-18 20:32:52 +00:00
|
|
|
const delta = one.length - two.length;
|
2016-03-21 18:13:59 +00:00
|
|
|
if (delta > 0) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (delta < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return one > two ? 1 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function to sort scenes based on their index and view key.
|
|
|
|
*/
|
|
|
|
function compareScenes(
|
|
|
|
one: NavigationScene,
|
|
|
|
two: NavigationScene,
|
|
|
|
): number {
|
|
|
|
if (one.index > two.index) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (one.index < two.index) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return compareKey(
|
|
|
|
one.key,
|
|
|
|
two.key,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
Fix UIExplorer Search
Summary:
In UI explorer, the route is made of an object which look like this.
```
{key: 'AppList', filter: 'query string from the search box'}
```
When a new search query is enter, a new `filter` value is applied, and the key `AppList`
remains the same.
In NavigationScenesReducer, we should compare the routes with both their keys and references.
The current implementation only compares the keys, which unfortunately depends on the a weak
assumption that all routes immutable and keys are unique.
In UI Explore, the route key is always 'AppList', which makes sense since we use the key
to match the scene, and whenever a new search query is provides, a new route will be created.
Reviewed By: nicklockwood
Differential Revision: D3357023
fbshipit-source-id: a3c9e98092f5ce555e5dbb4cc806bab2e67d8014
2016-05-27 19:02:55 +00:00
|
|
|
/**
|
|
|
|
* Whether two routes are the same.
|
|
|
|
*/
|
2016-03-21 18:13:59 +00:00
|
|
|
function areScenesShallowEqual(
|
|
|
|
one: NavigationScene,
|
|
|
|
two: NavigationScene,
|
|
|
|
): boolean {
|
|
|
|
return (
|
|
|
|
one.key === two.key &&
|
|
|
|
one.index === two.index &&
|
|
|
|
one.isStale === two.isStale &&
|
2016-06-27 19:03:51 +00:00
|
|
|
one.isActive === two.isActive &&
|
Fix UIExplorer Search
Summary:
In UI explorer, the route is made of an object which look like this.
```
{key: 'AppList', filter: 'query string from the search box'}
```
When a new search query is enter, a new `filter` value is applied, and the key `AppList`
remains the same.
In NavigationScenesReducer, we should compare the routes with both their keys and references.
The current implementation only compares the keys, which unfortunately depends on the a weak
assumption that all routes immutable and keys are unique.
In UI Explore, the route key is always 'AppList', which makes sense since we use the key
to match the scene, and whenever a new search query is provides, a new route will be created.
Reviewed By: nicklockwood
Differential Revision: D3357023
fbshipit-source-id: a3c9e98092f5ce555e5dbb4cc806bab2e67d8014
2016-05-27 19:02:55 +00:00
|
|
|
areRoutesShallowEqual(one.route, two.route)
|
2016-03-21 18:13:59 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
Fix UIExplorer Search
Summary:
In UI explorer, the route is made of an object which look like this.
```
{key: 'AppList', filter: 'query string from the search box'}
```
When a new search query is enter, a new `filter` value is applied, and the key `AppList`
remains the same.
In NavigationScenesReducer, we should compare the routes with both their keys and references.
The current implementation only compares the keys, which unfortunately depends on the a weak
assumption that all routes immutable and keys are unique.
In UI Explore, the route key is always 'AppList', which makes sense since we use the key
to match the scene, and whenever a new search query is provides, a new route will be created.
Reviewed By: nicklockwood
Differential Revision: D3357023
fbshipit-source-id: a3c9e98092f5ce555e5dbb4cc806bab2e67d8014
2016-05-27 19:02:55 +00:00
|
|
|
/**
|
|
|
|
* Whether two routes are the same.
|
|
|
|
*/
|
|
|
|
function areRoutesShallowEqual(
|
|
|
|
one: ?NavigationRoute,
|
|
|
|
two: ?NavigationRoute,
|
|
|
|
): boolean {
|
|
|
|
if (!one || !two) {
|
|
|
|
return one === two;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (one.key !== two.key) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return shallowEqual(one, two);
|
|
|
|
}
|
|
|
|
|
2016-03-21 18:13:59 +00:00
|
|
|
function NavigationScenesReducer(
|
|
|
|
scenes: Array<NavigationScene>,
|
2016-05-20 21:24:24 +00:00
|
|
|
nextState: NavigationState,
|
|
|
|
prevState: ?NavigationState,
|
2016-03-21 18:13:59 +00:00
|
|
|
): Array<NavigationScene> {
|
2016-05-18 20:32:52 +00:00
|
|
|
if (prevState === nextState) {
|
|
|
|
return scenes;
|
|
|
|
}
|
2016-03-21 18:13:59 +00:00
|
|
|
|
2016-05-21 01:09:57 +00:00
|
|
|
const prevScenes: Map<string, NavigationScene> = new Map();
|
|
|
|
const freshScenes: Map<string, NavigationScene> = new Map();
|
|
|
|
const staleScenes: Map<string, NavigationScene> = new Map();
|
2016-03-21 18:13:59 +00:00
|
|
|
|
|
|
|
// Populate stale scenes from previous scenes marked as stale.
|
|
|
|
scenes.forEach(scene => {
|
|
|
|
const {key} = scene;
|
|
|
|
if (scene.isStale) {
|
|
|
|
staleScenes.set(key, scene);
|
|
|
|
}
|
|
|
|
prevScenes.set(key, scene);
|
|
|
|
});
|
|
|
|
|
|
|
|
const nextKeys = new Set();
|
2016-05-22 23:27:53 +00:00
|
|
|
nextState.routes.forEach((route, index) => {
|
2016-05-21 01:09:57 +00:00
|
|
|
const key = SCENE_KEY_PREFIX + route.key;
|
2016-03-21 18:13:59 +00:00
|
|
|
const scene = {
|
|
|
|
index,
|
2016-06-27 19:03:51 +00:00
|
|
|
isActive: false,
|
2016-03-21 18:13:59 +00:00
|
|
|
isStale: false,
|
|
|
|
key,
|
2016-05-21 01:09:57 +00:00
|
|
|
route,
|
2016-03-21 18:13:59 +00:00
|
|
|
};
|
|
|
|
invariant(
|
|
|
|
!nextKeys.has(key),
|
2016-06-07 22:25:40 +00:00
|
|
|
`navigationState.routes[${index}].key "${key}" conflicts with ` +
|
2016-05-22 23:27:53 +00:00
|
|
|
'another route!'
|
2016-03-21 18:13:59 +00:00
|
|
|
);
|
|
|
|
nextKeys.add(key);
|
|
|
|
|
|
|
|
if (staleScenes.has(key)) {
|
|
|
|
// A previously `stale` scene is now part of the nextState, so we
|
|
|
|
// revive it by removing it from the stale scene map.
|
|
|
|
staleScenes.delete(key);
|
|
|
|
}
|
|
|
|
freshScenes.set(key, scene);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (prevState) {
|
2016-05-22 23:27:53 +00:00
|
|
|
// Look at the previous routes and classify any removed scenes as `stale`.
|
|
|
|
prevState.routes.forEach((route: NavigationRoute, index) => {
|
2016-05-21 01:09:57 +00:00
|
|
|
const key = SCENE_KEY_PREFIX + route.key;
|
2016-03-21 18:13:59 +00:00
|
|
|
if (freshScenes.has(key)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
staleScenes.set(key, {
|
|
|
|
index,
|
2016-06-27 19:03:51 +00:00
|
|
|
isActive: false,
|
2016-03-21 18:13:59 +00:00
|
|
|
isStale: true,
|
|
|
|
key,
|
2016-05-21 01:09:57 +00:00
|
|
|
route,
|
2016-03-21 18:13:59 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const nextScenes = [];
|
|
|
|
|
|
|
|
const mergeScene = (nextScene => {
|
|
|
|
const {key} = nextScene;
|
|
|
|
const prevScene = prevScenes.has(key) ? prevScenes.get(key) : null;
|
|
|
|
if (prevScene && areScenesShallowEqual(prevScene, nextScene)) {
|
|
|
|
// Reuse `prevScene` as `scene` so view can avoid unnecessary re-render.
|
|
|
|
// This assumes that the scene's navigation state is immutable.
|
|
|
|
nextScenes.push(prevScene);
|
|
|
|
} else {
|
|
|
|
nextScenes.push(nextScene);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
staleScenes.forEach(mergeScene);
|
|
|
|
freshScenes.forEach(mergeScene);
|
|
|
|
|
2016-05-25 18:26:22 +00:00
|
|
|
nextScenes.sort(compareScenes);
|
|
|
|
|
2016-06-27 19:03:51 +00:00
|
|
|
let activeScenesCount = 0;
|
|
|
|
nextScenes.forEach((scene, ii) => {
|
|
|
|
const isActive = !scene.isStale && scene.index === nextState.index;
|
|
|
|
if (isActive !== scene.isActive) {
|
|
|
|
nextScenes[ii] = {
|
|
|
|
...scene,
|
|
|
|
isActive,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (isActive) {
|
|
|
|
activeScenesCount++;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
invariant(
|
|
|
|
activeScenesCount === 1,
|
|
|
|
'there should always be only one scene active, not %s.',
|
|
|
|
activeScenesCount,
|
|
|
|
);
|
|
|
|
|
2016-05-25 18:26:22 +00:00
|
|
|
if (nextScenes.length !== scenes.length) {
|
|
|
|
return nextScenes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nextScenes.some(
|
|
|
|
(scene, index) => !areScenesShallowEqual(scenes[index], scene)
|
|
|
|
)) {
|
|
|
|
return nextScenes;
|
|
|
|
}
|
|
|
|
|
|
|
|
// scenes haven't changed.
|
|
|
|
return scenes;
|
2016-03-21 18:13:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = NavigationScenesReducer;
|