Initial implementation of the Navigator with NavigationExperimental.

Summary:This is the initial implementation of the Navigator done with the NavigationExperimental library.
There will be following diffs to support more features that are currently available from the Navigator.

Reviewed By: ericvicenti

Differential Revision: D3016084

fb-gh-sync-id: ed509fc86e9dc67b5334be9e60b582494fd52844
shipit-source-id: ed509fc86e9dc67b5334be9e60b582494fd52844
This commit is contained in:
Hedger Wang 2016-03-08 16:25:29 -08:00 committed by Facebook Github Bot 0
parent 3c2bf6304c
commit fa5783e3ee
3 changed files with 276 additions and 5 deletions

View File

@ -27,11 +27,258 @@
*/
'use strict';
const NavigationAnimatedView = require('NavigationAnimatedView');
const NavigationCard = require('NavigationCard');
const NavigationContext = require('NavigationContext');
const NavigationLegacyNavigatorRouteStack = require('NavigationLegacyNavigatorRouteStack');
const NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
const NavigatorNavigationBar = require('NavigatorNavigationBar');
const NavigatorSceneConfigs = require('NavigatorSceneConfigs');
const React = require('React');
const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
const invariant = require('invariant');
const guid = require('guid');
import type {
NavigationSceneRenderer,
NavigationSceneRendererProps,
} from 'NavigationTypeDefinition';
type Props = {
configureScene: any,
initialRoute: any,
initialRouteStack: any,
};
function getConfigPopDirection(config: any): ?string {
if (config && config.gestures && config.gestures.pop) {
const direction = config.gestures.pop.direction;
return direction ? String(direction) : null;
}
return null;
}
const RouteStack = NavigationLegacyNavigatorRouteStack;
/**
* NavigationLegacyNavigator is meant to replace Navigator seemlessly without
* API changes. While the APIs remain compatible with Navigator, it should
* be built with the new Navigation API such as `NavigationAnimatedView`...etc.
* NavigationLegacyNavigator is meant to replace Navigator seemlessly with
* minimum API changes.
*
* While the APIs remain compatible with Navigator, it is built with good
* intention by using the new Navigation API such as
* `NavigationAnimatedView`...etc.
*/
const NavigationLegacyNavigator = require('Navigator');
class NavigationLegacyNavigator extends React.Component {
_renderCard: NavigationSceneRenderer;
_renderHeader: NavigationSceneRenderer;
_renderScene: NavigationSceneRenderer;
navigationContext: NavigationContext;
constructor(props: Props, context: any) {
super(props, context);
this.navigationContext = new NavigationContext();
const stack = this._getInitialRouteStack();
this.state = {
key: guid(),
stack,
};
}
jumpTo(route: any): void {
const index = this.state.stack.indexOf(route);
invariant(
index > -1,
'Cannot jump to route that is not in the route stack'
);
this._jumpToIndex(index);
}
jumpForward(): void {
this._jumpToIndex(this.state.stack.index + 1);
}
jumpBack(): void {
this._jumpToIndex(this.state.stack.index - 1);
}
push(route: any): void {
this.setState({stack: this.state.stack.push(route)});
}
pop(): void {
const {stack} = this.state;
if (stack.size > 1) {
this.setState({stack: stack.pop()});
}
}
replaceAtIndex(route: any, index: number): void {
const {stack} = this.state;
if (index < 0) {
index += stack.size;
}
if (index >= stack.size) {
// Nothing to replace.
return;
}
this.setState({stack: stack.replaceAtIndex(index, route)});
}
replace(route: any): void {
this.replaceAtIndex(route, this.state.stack.index);
}
replacePrevious(route: any): void {
this.replaceAtIndex(route, this.state.stack.index - 1);
}
popToTop(): void {
this.setState({stack: this.state.stack.slice(0, 1)});
}
popToRoute(route: any): void {
const {stack} = this.state;
const nextIndex = stack.indexOf(route);
invariant(
nextIndex > -1,
'Calling popToRoute for a route that doesn\'t exist!'
);
this.setState({stack: stack.slice(0, nextIndex + 1)});
}
replacePreviousAndPop(route: any): void {
const {stack} = this.state;
const nextIndex = stack.index - 1;
if (nextIndex < 0) {
return;
}
this.setState({stack: stack.replaceAtIndex(nextIndex, route).pop()});
}
resetTo(route: any): void {
invariant(!!route, 'Must supply route');
this.setState({stack: this.state.stack.slice(0).replaceAtIndex(0, route)});
}
immediatelyResetRouteStack(routes: Array<any>): void {
const index = routes.length - 1;
const stack = new RouteStack(index, routes);
this.setState({
key: guid(),
stack,
});
}
getCurrentRoutes(): Array<any> {
return this.state.stack.toArray();
}
_jumpToIndex(index: number): void {
const {stack} = this.state;
if (index < 0 || index >= stack.size) {
return;
}
const nextStack = stack.jumpToIndex(index);
this.setState({stack: nextStack});
}
// Lyfe cycle and private methods below.
shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
this,
nextProps,
nextState
);
}
componentWillMount(): void {
this._renderCard = this._renderCard.bind(this);
this._renderHeader = this._renderHeader.bind(this);
this._renderScene = this._renderScene.bind(this);
}
render(): ReactElement {
return (
<NavigationAnimatedView
key={'main_' + this.state.key}
navigationState={this.state.stack.toNavigationState()}
renderOverlay={this._renderHeader}
renderScene={this._renderCard}
style={this.props.style}
/>
);
}
_getInitialRouteStack(): RouteStack {
const {initialRouteStack, initialRoute} = this.props;
const routes = initialRouteStack || [initialRoute];
const index = initialRoute ?
routes.indexOf(initialRoute) :
routes.length - 1;
return new RouteStack(index, routes);
}
_renderHeader(props: NavigationSceneRendererProps): ?ReactElement {
// TODO(hedger): Render the legacy header.
return null;
}
_renderCard(props: NavigationSceneRendererProps): ReactElement {
let direction = 'horizontal';
const {navigationState} = props.scene;
const {configureScene} = this.props;
if (configureScene) {
const route = RouteStack.getRouteByNavigationState(navigationState);
const config = configureScene(route, this.state.stack.toArray());
switch (getConfigPopDirection(config)) {
case 'left-to-right':
direction = 'horizontal';
break;
case 'top-to-bottom':
direction = 'vertical';
break;
default:
// unsupported config.
if (__DEV__) {
console.warn('unsupported scene configuration');
}
}
}
return (
<NavigationCard
{...props}
direction={direction}
key={'card_' + navigationState.key}
renderScene={this._renderScene}
/>
);
}
_renderScene(props: NavigationSceneRendererProps): ReactElement {
const {navigationState} = props.scene;
const route = RouteStack.getRouteByNavigationState(navigationState);
return this.props.renderScene(route, this);
}
}
// Legacy static members.
NavigationLegacyNavigator.BreadcrumbNavigationBar = NavigatorBreadcrumbNavigationBar;
NavigationLegacyNavigator.NavigationBar = NavigatorNavigationBar;
NavigationLegacyNavigator.SceneConfigs = NavigatorSceneConfigs;
module.exports = NavigationLegacyNavigator;

View File

@ -46,6 +46,19 @@ let _nextRouteNodeID = 0;
class RouteNode {
key: string;
route: any;
/**
* Cast `navigationState` as `RouteNode`.
* Also see `RouteNode#toNavigationState`.
*/
static fromNavigationState(navigationState: NavigationState): RouteNode {
invariant(
navigationState instanceof RouteNode,
'navigationState should be an instacne of RouteNode'
);
return navigationState;
}
constructor(route: any) {
// Key value gets bigger incrementally. Developer can compare the
// keys of two routes then know which route is added to the stack
@ -84,11 +97,15 @@ let _nextRouteStackID = 0;
* of the routes. This data structure is implemented as immutable data
* and mutation (e.g. push, pop...etc) will yields a new instance.
*/
class RouteStack {
class RouteStack {
_index: number;
_key: string;
_routeNodes: Array<RouteNode>;
static getRouteByNavigationState(navigationState: NavigationState): any {
return RouteNode.fromNavigationState(navigationState).route;
}
constructor(index: number, routes: Array<any>) {
invariant(
routes.length > 0,

View File

@ -386,4 +386,11 @@ describe('NavigationLegacyNavigatorRouteStack:', () => {
],
});
});
it('coverts from navigation state', () => {
const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
const state = stack.toNavigationState().children[0];
const route = NavigationLegacyNavigatorRouteStack.getRouteByNavigationState(state);
expect(route).toBe('a');
});
});