summaryrefslogtreecommitdiff
path: root/packages/web-util/src/context/navigation.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/web-util/src/context/navigation.ts')
-rw-r--r--packages/web-util/src/context/navigation.ts114
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,
+ });
+};