diff options
Diffstat (limited to 'packages/aml-backoffice-ui/src/route.ts')
-rw-r--r-- | packages/aml-backoffice-ui/src/route.ts | 197 |
1 files changed, 0 insertions, 197 deletions
diff --git a/packages/aml-backoffice-ui/src/route.ts b/packages/aml-backoffice-ui/src/route.ts deleted file mode 100644 index f515a590a..000000000 --- a/packages/aml-backoffice-ui/src/route.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { TranslatedString } from "@gnu-taler/taler-util"; -import { createHashHistory } from "history"; -import { ComponentChildren, h as create, createContext, VNode } from "preact"; -import { useContext, useEffect, useState } from "preact/hooks"; - -type ContextType = { - onChange: (listener: () => void) => VoidFunction -} -const nullChangeListener = { onChange: () => () => { } } -const Context = createContext<ContextType>(nullChangeListener); - -export const usePathChangeContext = (): ContextType => useContext(Context); - -export function HashPathProvider({ children }: { children: ComponentChildren }): VNode { - const history = createHashHistory(); - return create(Context.Provider, { value: { onChange: history.listen }, children }, children) -} - -type PageDefinition<DynamicPart extends Record<string, string>> = { - pattern: string; - (params: DynamicPart): string; -}; - -function replaceAll( - pattern: string, - vars: Record<string, string>, - values: Record<string, string>, -): string { - let result = pattern; - for (const v in vars) { - result = result.replace(vars[v], !values[v] ? "" : values[v]); - } - return result; -} - -export function pageDefinition<T extends Record<string, string>>( - pattern: string, -): PageDefinition<T> { - const patternParams = pattern.match(/(:[\w?]*)/g); - if (!patternParams) - throw Error( - `page definition pattern ${pattern} doesn't have any parameter`, - ); - - const vars = patternParams.reduce((prev, cur) => { - const pName = cur.match(/(\w+)/g); - - //skip things like :? in the path pattern - if (!pName || !pName[0]) return prev; - const name = pName[0]; - return { ...prev, [name]: cur }; - }, {} as Record<string, string>); - - const f = (values: T): string => replaceAll(pattern, vars, values); - f.pattern = pattern; - return f; -} - -export type PageEntry<T = unknown> = T extends Record<string, string> - ? { - url: PageDefinition<T>; - view: (props: T) => VNode; - name: TranslatedString, - Icon?: () => VNode, - } - : T extends unknown - ? { - url: string; - view: (props: {}) => VNode; - name: TranslatedString, - Icon?: () => VNode, - } - : never; - -export function Router({ - pageList, - onNotFound, -}: { - pageList: Array<PageEntry<any>>; - onNotFound: () => VNode; -}): VNode { - const current = useCurrentLocation(pageList); - if (current !== undefined) { - return create(current.page.view, current.values); - } - return onNotFound(); -} - -type Location = { - page: PageEntry<any>; - path: string; - values: Record<string, string>; -}; -export function useCurrentLocation(pageList: Array<PageEntry<any>>): Location | undefined { - const [currentLocation, setCurrentLocation] = useState<Location | null | undefined>(null); - const path = usePathChangeContext(); - useEffect(() => { - return path.onChange(() => { - const result = doSync(window.location.hash, new URLSearchParams(window.location.search), pageList); - setCurrentLocation(result); - }); - }, []); - if (currentLocation === null) { - return doSync(window.location.hash, new URLSearchParams(window.location.search), pageList); - } - return currentLocation; -} - -export function useChangeLocation() { - const [location, setLocation] = useState(window.location.hash) - const path = usePathChangeContext() - useEffect(() => { - return path.onChange(() => { - setLocation(window.location.hash) - }); - }, []); - return location; -} - -/** - * Search path in the pageList - * get the values from the path found - * add params from searchParams - * - * @param path - * @param params - */ -export function doSync(path: string, params: URLSearchParams, pageList: Array<PageEntry<any>>): Location | undefined { - for (let idx = 0; idx < pageList.length; idx++) { - const page = pageList[idx]; - if (typeof page.url === "string") { - if (page.url === path) { - const values: Record<string, string> = {}; - params.forEach((v, k) => { - values[k] = v; - }); - return { page, values, path }; - } - } else { - const values = doestUrlMatchToRoute(path, page.url.pattern); - if (values !== undefined) { - params.forEach((v, k) => { - values[k] = v; - }); - return { page, values, path }; - } - } - } - return undefined; -} - -function doestUrlMatchToRoute( - url: string, - route: string, -): undefined | Record<string, string> { - const paramsPattern = /(?:\?([^#]*))?$/; - // const paramsPattern = /(?:\?([^#]*))?(#.*)?$/; - const params = url.match(paramsPattern); - const urlWithoutParams = url.replace(paramsPattern, ""); - - const result: Record<string, string> = {}; - if (params && params[1]) { - const paramList = params[1].split("&"); - for (let i = 0; i < paramList.length; i++) { - const idx = paramList[i].indexOf("="); - const name = paramList[i].substring(0, idx); - const value = paramList[i].substring(idx + 1); - result[decodeURIComponent(name)] = decodeURIComponent(value); - } - } - const urlSeg = urlWithoutParams.split("/"); - const routeSeg = route.split("/"); - let max = Math.max(urlSeg.length, routeSeg.length); - for (let i = 0; i < max; i++) { - if (routeSeg[i] && routeSeg[i].charAt(0) === ":") { - const param = routeSeg[i].replace(/(^:|[+*?]+$)/g, ""); - - const flags = (routeSeg[i].match(/[+*?]+$/) || EMPTY)[0] || ""; - const plus = ~flags.indexOf("+"); - const star = ~flags.indexOf("*"); - const val = urlSeg[i] || ""; - - if (!val && !star && (flags.indexOf("?") < 0 || plus)) { - return undefined; - } - result[param] = decodeURIComponent(val); - if (plus || star) { - result[param] = urlSeg.slice(i).map(decodeURIComponent).join("/"); - break; - } - } else if (routeSeg[i] !== urlSeg[i]) { - return undefined; - } - } - return result; -} -const EMPTY: Record<string, string> = {}; |