Files
ionic-framework/packages/vue-router/src/locationHistory.ts
2023-01-19 08:51:08 -05:00

270 lines
7.7 KiB
TypeScript

import type { RouteInfo } from "./types";
export const createLocationHistory = () => {
const locationHistory: RouteInfo[] = [];
const tabsHistory: { [k: string]: RouteInfo[] } = {};
const add = (routeInfo: RouteInfo) => {
switch (routeInfo.routerAction) {
case "pop":
pop(routeInfo);
break;
default:
addRoute(routeInfo);
break;
}
if (routeInfo.routerDirection === "root") {
clearHistory();
addRoute(routeInfo);
}
};
const update = (routeInfo: RouteInfo) => {
const locationIndex = locationHistory.findIndex(
(x) => x.id === routeInfo.id
);
if (locationIndex > -1) {
locationHistory.splice(locationIndex, 1, routeInfo);
}
const tabArray = tabsHistory[routeInfo.tab || ""];
if (tabArray) {
const tabIndex = tabArray.findIndex((x) => x.id === routeInfo.id);
if (tabIndex > -1) {
tabArray.splice(tabIndex, 1, routeInfo);
} else {
tabArray.push(routeInfo);
}
} else if (routeInfo.tab) {
tabsHistory[routeInfo.tab] = [routeInfo];
}
};
const pop = (routeInfo: RouteInfo) => {
const tabHistory = getTabsHistory(routeInfo.tab);
let ri;
if (tabHistory) {
// Pop all routes until we are back
ri = tabHistory[tabHistory.length - 1];
while (ri && ri.id !== routeInfo.id) {
tabHistory.pop();
ri = tabHistory[tabHistory.length - 1];
}
// Replace with updated route
tabHistory.pop();
tabHistory.push(routeInfo);
}
ri = locationHistory[locationHistory.length - 1];
while (ri && ri.id !== routeInfo.id) {
locationHistory.pop();
ri = locationHistory[locationHistory.length - 1];
}
// Replace with updated route
locationHistory.pop();
locationHistory.push(routeInfo);
};
const addRoute = (routeInfo: RouteInfo) => {
const tabHistory = getTabsHistory(routeInfo.tab);
if (tabHistory) {
// If the latest routeInfo is the same (going back and forth between tabs), replace it
if (
tabHistory[tabHistory.length - 1] &&
tabHistory[tabHistory.length - 1].id === routeInfo.id
) {
tabHistory.pop();
}
tabHistory.push(routeInfo);
}
locationHistory.push(routeInfo);
};
/**
* Wipes the location history arrays.
* You can optionally provide a routeInfo
* object which will wipe that entry
* and every entry that appears after it.
*/
const clearHistory = (routeInfo?: RouteInfo) => {
if (routeInfo) {
const { position, tab } = routeInfo;
/**
* If there is no route index in locationHistory
* then there will not be any route index in
* tabs either.
*/
const existingRouteIndex = locationHistory.findIndex(
(r) => r.position === position
);
if (existingRouteIndex === -1) return;
locationHistory.splice(existingRouteIndex);
const clearTabHistory = (tab: string) => {
const existingTabRouteIndex = tabsHistory[tab].findIndex(
(r) => r.position === position
);
if (existingTabRouteIndex === -1) return;
tabsHistory[tab].splice(existingTabRouteIndex);
};
/**
* We also need to search the current tab
* to correctly reset the individual tab
* stack. We should not clear the entire
* tab stack as that means we will lose
* a reference to the root tab route.
*/
const tabHistory = tabsHistory[tab];
if (tab && tabHistory) {
clearTabHistory(tab);
/**
* If we are not clearing items after
* a tabs page, it is still possible
* that there are future tabs pages to clear.
* As a result, we need to search through
* all the tab stacks and remove views that appear
* after the given routeInfo.
*
* Example: /non-tabs-page --> /tabs/tab1 --> /non-tabs-page
* (via router.go(-1)) --> /tabs/tab2. The /tabs/tab1 history
* has been overwritten with /tabs/tab2. As a result,
* the /tabs/tab1 route info in the Tab 1 stack should be removed.
*/
} else {
for (const tab in tabsHistory) {
clearTabHistory(tab);
}
}
} else {
for (const tab in tabsHistory) {
tabsHistory[tab] = [];
}
locationHistory.length = 0;
}
};
const getTabsHistory = (tab: string): RouteInfo[] => {
let history;
if (tab) {
history = tabsHistory[tab];
if (!history) {
history = tabsHistory[tab] = [];
}
}
return history;
};
const size = () => locationHistory.length;
/**
* Finds and returns the location history item
* given the state of browser's history API.
* This is useful when jumping around in browser
* history using router.go.
*/
const current = (initialHistory: number, currentHistory: number) => {
/**
* initialHistory does not always start at 0 if users navigated
* to app from another website, so doing this math lets us
* find the correct index in our locationHistory array.
*/
const index = currentHistory - initialHistory;
return locationHistory[index] || last();
};
const last = () => locationHistory[locationHistory.length - 1];
/**
* With the introduction of router.go support, we no longer remove
* items from locationHistory as they may be needed again in the future.
* As a result, we need to look at the current position in location history
* to see if users can navigate back n pages. Previously we were checking
* the length of locationHistory, but that only worked since we were pruning
* the array.
*/
const canGoBack = (
deep = 1,
initialHistory: number,
currentHistory: number
) => {
return currentHistory - deep >= initialHistory;
};
const getFirstRouteInfoForTab = (tab: string): RouteInfo | undefined => {
const tabHistory = getTabsHistory(tab);
if (tabHistory) {
return tabHistory[0];
}
return undefined;
};
const getCurrentRouteInfoForTab = (tab: string): RouteInfo | undefined => {
const tabHistory = getTabsHistory(tab);
if (tabHistory) {
return tabHistory[tabHistory.length - 1];
}
return undefined;
};
/**
* Finds and returns the previous view based upon
* what originally pushed it (pushedByRoute).
* When `delta` < -1 then we should just index into
* to array because the previous view that we want is not
* necessarily the view that pushed our current view.
* Additionally, when jumping around in history, we
* do not modify the locationHistory stack so we would
* not update pushedByRoute anyways.
*/
const findLastLocation = (
routeInfo: RouteInfo,
delta = -1
): RouteInfo | undefined => {
const routeInfos = getTabsHistory(routeInfo.tab);
if (routeInfos) {
if (delta < -1) {
return routeInfos[routeInfos.length - 1 + delta];
} else {
for (let i = routeInfos.length - 2; i >= 0; i--) {
const ri = routeInfos[i];
if (ri) {
if (ri.pathname === routeInfo.pushedByRoute) {
return ri;
}
}
}
}
}
if (delta < -1) {
return locationHistory[locationHistory.length - 1 + delta];
} else {
for (let i = locationHistory.length - 2; i >= 0; i--) {
const ri = locationHistory[i];
if (ri) {
if (ri.pathname === routeInfo.pushedByRoute) {
return ri;
}
}
}
}
return undefined;
};
return {
current,
size,
last,
add,
canGoBack,
update,
getFirstRouteInfoForTab,
getCurrentRouteInfoForTab,
findLastLocation,
clearHistory,
};
};