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:
parent
3c2bf6304c
commit
fa5783e3ee
|
@ -27,11 +27,258 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'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
|
* NavigationLegacyNavigator is meant to replace Navigator seemlessly with
|
||||||
* API changes. While the APIs remain compatible with Navigator, it should
|
* minimum API changes.
|
||||||
* be built with the new Navigation API such as `NavigationAnimatedView`...etc.
|
*
|
||||||
|
* 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;
|
module.exports = NavigationLegacyNavigator;
|
||||||
|
|
|
@ -46,6 +46,19 @@ let _nextRouteNodeID = 0;
|
||||||
class RouteNode {
|
class RouteNode {
|
||||||
key: string;
|
key: string;
|
||||||
route: any;
|
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) {
|
constructor(route: any) {
|
||||||
// Key value gets bigger incrementally. Developer can compare the
|
// Key value gets bigger incrementally. Developer can compare the
|
||||||
// keys of two routes then know which route is added to the stack
|
// 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
|
* of the routes. This data structure is implemented as immutable data
|
||||||
* and mutation (e.g. push, pop...etc) will yields a new instance.
|
* and mutation (e.g. push, pop...etc) will yields a new instance.
|
||||||
*/
|
*/
|
||||||
class RouteStack {
|
class RouteStack {
|
||||||
_index: number;
|
_index: number;
|
||||||
_key: string;
|
_key: string;
|
||||||
_routeNodes: Array<RouteNode>;
|
_routeNodes: Array<RouteNode>;
|
||||||
|
|
||||||
|
static getRouteByNavigationState(navigationState: NavigationState): any {
|
||||||
|
return RouteNode.fromNavigationState(navigationState).route;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(index: number, routes: Array<any>) {
|
constructor(index: number, routes: Array<any>) {
|
||||||
invariant(
|
invariant(
|
||||||
routes.length > 0,
|
routes.length > 0,
|
||||||
|
|
|
@ -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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue