import type { ReactElementFiber } from '../element/types'; import type { ReactNavigationPageInfo } from './getPageStack'; import getPageStack from './getPageStack'; import trackPageView from './trackPageView'; import trackPageLeave from './trackPageLeave'; import findRootTag from '../element/findRootTag'; import screenManager from './screenManager'; import reactNavigationConfig from './reactNavigationConfig'; function parsePageInfo( rootTag: number, route: any, options: any ): ReactNavigationPageInfo { const pageinfo: ReactNavigationPageInfo = { id: route.key, screenName: route.name, custom: route.params ?? {}, path: options?.path ?? '', title: options?.title ?? route.name, isback: false, rootTag, }; return pageinfo; } function handlePageView( node: ReactElementFiber, navigation: any, route: any, options: any ) { const rootTag = findRootTag(node); const screenSet = screenManager.getScreenSet(rootTag); if (!screenSet.has(route.key)) { screenSet.add(route.key); navigation.addListener('beforeRemove', (event: any) => { const routeCopy = { ...route }; const eventData = event.data ?? {}; const config = reactNavigationConfig.getScreenConfig(route.name); const callback = reactNavigationConfig.getBeforeRemoveCallback(); callback && callback(routeCopy, eventData, config); }); } const pageStack = getPageStack(rootTag); const current = pageStack.getCurrent(); if (current) { trackPageLeave(current); } if (reactNavigationConfig.getIgnoreScreenSet().has(route.screenName)) { return; } const pageInfo = parsePageInfo(rootTag, route, options); if (pageStack.has(pageInfo.id)) { pageStack.backTo(pageInfo.id); pageInfo.isback = true; } else { pageStack.push(pageInfo); } trackPageView(pageInfo, current); } export function getRoute(state: any) { if (!state || !state.routes || !state.routes.length) { return undefined; } if (state.index >= state.routes.length) { return undefined; } return state.routes[state.index] || undefined; } interface NavigationData { node: ReactElementFiber; navigation: any; route: any; options: any; } class NavigationStack { rootSet = new Set(); rootMap = new Map(); list: NavigationData[] = []; _start = false; _lastScreen = undefined; constructor() {} _checkRoot( node: ReactElementFiber, navigation: any, route: any, options: any ) { const parentNavigation = navigation.getParent(); if (!parentNavigation) { return; } const rootRoute = getRoute(parentNavigation.getState()); if (rootRoute) { this.rootSet.add(rootRoute.key); this.rootMap.set(rootRoute.key, { node, navigation, route, options, }); } } _getLeaf(key: string): NavigationData | undefined { let data; while (this.rootMap.has(key)) { data = this.rootMap.get(key)!; key = data.route.key; } return data; } isRoot(key: string) { return this.rootSet.has(key); } add(node: ReactElementFiber, navigation: any, route: any, options: any) { this._checkRoot(node, navigation, route, options); this.list.push({ node, navigation, route, options, }); if (!this._start && this.list.length) { this._start = true; setTimeout(() => { this.execute(); this.list = []; this._start = false; }, 0); } } execute() { this.list.forEach(({ node, navigation, route, options }) => { if (this.rootSet.has(route.key)) { const data = this._getLeaf(route.key)!; // console.info('root:', route.key, ' -> ', data.route.key); node = data.node; navigation = data.navigation; route = data.route; options = data.options; } else { // console.info('leaf:', route.key); } if (this._lastScreen === route.key) { return; } this._lastScreen = route.key; handlePageView(node, navigation, route, options); }); // console.info('----------------------') } } export default new NavigationStack();