diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js b/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js index a03bd9f4a..67e8aa01d 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js @@ -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): void { + const index = routes.length - 1; + const stack = new RouteStack(index, routes); + this.setState({ + key: guid(), + stack, + }); + } + + getCurrentRoutes(): Array { + 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 ( + + ); + } + + _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 ( + + ); + } + + _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; diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigatorRouteStack.js b/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigatorRouteStack.js index f71aa64e9..302678f3e 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigatorRouteStack.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigatorRouteStack.js @@ -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; + static getRouteByNavigationState(navigationState: NavigationState): any { + return RouteNode.fromNavigationState(navigationState).route; + } + constructor(index: number, routes: Array) { invariant( routes.length > 0, diff --git a/Libraries/CustomComponents/NavigationExperimental/__tests__/NavigationLegacyNavigatorRouteStack-test.js b/Libraries/CustomComponents/NavigationExperimental/__tests__/NavigationLegacyNavigatorRouteStack-test.js index eb84f408c..151c33b11 100644 --- a/Libraries/CustomComponents/NavigationExperimental/__tests__/NavigationLegacyNavigatorRouteStack-test.js +++ b/Libraries/CustomComponents/NavigationExperimental/__tests__/NavigationLegacyNavigatorRouteStack-test.js @@ -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'); + }); });