import React from 'react'; import BackAndroid from '../modules/back-android'; import Hippy from '../hippy'; import { callUIFunction } from '../modules/ui-manager-module'; import { Device } from '../native'; interface Top { data: any; next: Top } interface Route { routeName: string; component?: string | React.FunctionComponent | React.ComponentClass; initProps?: any; animated?: boolean } interface NavigatorProps { /** * Initial page option, the option object should contains. * * * {string} routeName - Router name * * {React.Comopnent} component - Initial react component * * {Object} initProps - Initial props for inital react component * * {boolean} animated - Use animation effect to switch to new page */ initialRoute: Route; } class Stack { top: Top | null = null; size = 0; /** * Push into a new page/component. * * @param {Object} route - New router * @param {string} routeName - Router name * @param {React.Comopnent} component - Initial react component * @param {Object} initProps - Initial props for inital react component * @param {boolean} animated - Use animation effect to switch to new page */ public push(route: Route) { (this.top as Top) = { data: route, next: this.top as Top, }; this.size += 1; } /** * Returns latest push router. */ public peek() { return this.top === null ? null : this.top.data; } /** * Return back to previous page. */ public pop() { if (this.top === null) { return null; } const out = this.top; this.top = this.top.next; if (this.size > 0) { this.size -= 1; } return out.data; } /** * Clear history stack */ clear() { this.top = null; this.size = 0; } /** * Returns all of routes */ public displayAll() { const arr: any[] = []; if (this.top === null) { return arr; } let current = this.top; for (let i = 0, len = this.size; i < len; i += 1) { arr[i] = current.data; current = current.next; } return arr; } } /** * Simply router component for switch in multiple Hippy page. * @noInheritDoc */ class Navigator extends React.Component { private stack = new Stack(); private instance: HTMLDivElement | null = null; private routeList: { [key: string]: boolean; } = {}; private backListener?: ReturnType; /** * @ignore */ constructor(props: NavigatorProps) { super(props); const { initialRoute } = props; if (initialRoute && initialRoute.component) { const hippy = new Hippy({ appName: initialRoute.routeName, entryPage: initialRoute.component, }); hippy.regist(); this.routeList[initialRoute.routeName] = true; } this.handleAndroidBack = this.handleAndroidBack.bind(this); } /** * @ignore */ public componentWillMount() { if (Device.platform.OS === 'android') { this.backListener = BackAndroid.addListener(this.handleAndroidBack); } } /** * @ignore */ public componentDidMount() { const { initialRoute } = this.props; this.stack.push({ routeName: initialRoute.routeName || '', component: initialRoute.component || '', initProps: initialRoute.initProps || '', }); } /** * @ignore */ public componentWillUnmount() { if (this.backListener) { this.backListener.remove(); } } public getCurrentPage() { return this.stack.peek(); } public handleAndroidBack() { if (this.stack.size > 1) { this.pop({ animated: true, }); } } /** * Push into a new page/component. * * @param {Object} route - New router * @param {string} routeName - Router name * @param {React.Comopnent} component - Initial react component * @param {Object} initProps - Initial props for inital react component * @param {boolean} animated - Use animation effect to switch to new page */ public push(route: Route) { if (route && route.component) { if (!this.routeList[route.routeName]) { const hippy = new Hippy({ appName: route.routeName, entryPage: route.component, }); hippy.regist(); this.routeList[route.routeName] = true; } const newRoute = route; delete newRoute.component; } const routes = [route]; this.stack.push(route); callUIFunction(this.instance, 'push', routes); } /** * Return back to previous page. */ public pop(option: { animated: boolean }) { if (this.stack.size > 1) { const options = [option]; this.stack.pop(); callUIFunction(this.instance, 'pop', options); } } /** * Clear history stack */ public clear() { this.stack.clear(); } /** * @ignore */ public render() { const { initialRoute: { /* eslint-disable @typescript-eslint/no-unused-vars */ component, ...otherInitialRoute }, ...nativeProps } = this.props; (nativeProps as NavigatorProps).initialRoute = otherInitialRoute; return (
{ this.instance = ref; }} {...nativeProps} /> ); } } export default Navigator;