Skip to content

Commit

Permalink
Initial implementation of the Navigator with NavigationExperimental.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Hedger Wang authored and Facebook Github Bot 0 committed Mar 9, 2016
1 parent 3c2bf63 commit fa5783e
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});

4 comments on commit fa5783e

@xing-zheng
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hedgerwang Nice Job!

@hufeng
Copy link

@hufeng hufeng commented on fa5783e Mar 26, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome

@lucasfeliciano
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!!

@mozillo
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Please sign in to comment.