diff options
Diffstat (limited to 'packages/web-util/src/context/navigation.ts')
-rw-r--r-- | packages/web-util/src/context/navigation.ts | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/packages/web-util/src/context/navigation.ts b/packages/web-util/src/context/navigation.ts new file mode 100644 index 000000000..c2f2bbbc1 --- /dev/null +++ b/packages/web-util/src/context/navigation.ts @@ -0,0 +1,114 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { ComponentChildren, createContext, h, VNode } from "preact"; +import { useContext, useEffect, useState } from "preact/hooks"; +import { + AppLocation, + ObjectOf, + Location, + findMatch, + RouteDefinition, +} from "../utils/route.js"; + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +export type Type = { + path: string; + params: Record<string, string[]>; + navigateTo: (path: AppLocation) => void; + // addNavigationListener: (listener: (path: string, params: Record<string, string>) => void) => (() => void); +}; + +// @ts-expect-error should not be used without provider +const Context = createContext<Type>(undefined); + +export const useNavigationContext = (): Type => useContext(Context); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function useCurrentLocation<T extends ObjectOf<RouteDefinition<any>>>( + pagesMap: T, +): Location<T> | undefined { + const pageList = Object.keys(pagesMap as object) as Array<keyof T>; + const { path, params } = useNavigationContext(); + + return findMatch(pagesMap, pageList, path, params); +} + +function getPathAndParamsFromWindow(): { + path: string; + params: Record<string, string[]>; +} { + const path = + typeof window !== "undefined" ? window.location.hash.substring(1) : "/"; + const params: Record<string, string[]> = {}; + if (typeof window !== "undefined") { + for (const [key, value] of new URLSearchParams(window.location.search)) { + if (!params[key]) { + params[key] = []; + } + params[key].push(value); + } + } + return { path, params }; +} + +const { path: initialPath, params: initialParams } = + getPathAndParamsFromWindow(); + +// there is a possibility that if the browser does a redirection +// (which doesn't go through navigatTo function) and that executed +// too early (before addEventListener runs) it won't be taking +// into account +const PopStateEventType = "popstate"; + +export const BrowserHashNavigationProvider = ({ + children, +}: { + children: ComponentChildren; +}): VNode => { + const [{ path, params }, setState] = useState({ + path: initialPath, + params: initialParams, + }); + if (typeof window === "undefined") { + throw Error( + "Can't use BrowserHashNavigationProvider if there is no window object", + ); + } + function navigateTo(path: string): void { + const { params } = getPathAndParamsFromWindow(); + setState({ path, params }); + window.location.href = path; + } + + useEffect(() => { + function eventListener(): void { + setState(getPathAndParamsFromWindow()); + } + window.addEventListener(PopStateEventType, eventListener); + return () => { + window.removeEventListener(PopStateEventType, eventListener); + }; + }, []); + return h(Context.Provider, { + value: { path, params, navigateTo }, + children, + }); +}; |