summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib/Navigation.ts
diff options
context:
space:
mode:
authorWolfgang Müller2024-03-05 18:08:09 +0100
committerWolfgang Müller2024-03-05 19:25:59 +0100
commitd1d654ebac2d51e3841675faeb56480e440f622f (patch)
tree56ef123c1a15a10dfd90836e4038e27efde950c6 /frontend/src/lib/Navigation.ts
downloadhircine-d1d654ebac2d51e3841675faeb56480e440f622f.tar.gz
Initial commit0.1.0
Diffstat (limited to '')
-rw-r--r--frontend/src/lib/Navigation.ts114
1 files changed, 114 insertions, 0 deletions
diff --git a/frontend/src/lib/Navigation.ts b/frontend/src/lib/Navigation.ts
new file mode 100644
index 0000000..e6b17cd
--- /dev/null
+++ b/frontend/src/lib/Navigation.ts
@@ -0,0 +1,114 @@
+import { goto as svelteGoto } from '$app/navigation';
+import { SortDirection } from '$gql/graphql';
+import JsonURL from '@jsonurl/jsonurl';
+import { type PaginationData } from './Pagination';
+import { type SortData } from './Sort';
+import { toastError } from './Toasts';
+
+function paramToNum<T>(value: string | null, fallback: T) {
+ if (value) {
+ const number = +value;
+
+ if (Number.isNaN(number) || number < 0) {
+ return fallback;
+ }
+
+ return number;
+ }
+
+ return fallback;
+}
+
+export function parseSortData<T>(params: URLSearchParams, fallback: T): SortData<T> {
+ return {
+ on: (params.get('s') as T) || fallback,
+ direction: (params.get('d') as SortDirection) || SortDirection.Ascending,
+ seed: paramToNum(params.get('r'), undefined)
+ };
+}
+
+export function parsePaginationData(params: URLSearchParams, defaultItems = 120): PaginationData {
+ return {
+ page: paramToNum(params.get('p'), 1),
+ items: paramToNum(params.get('i'), defaultItems)
+ };
+}
+
+export function parseFilter<T>(params: URLSearchParams): T {
+ const param = params.get('f');
+
+ if (!param) return {} as T;
+
+ try {
+ return JsonURL.parse(param, { AQF: true, impliedObject: {} }) as T;
+ } catch (e) {
+ return {} as T;
+ }
+}
+
+interface NavigationOptions {
+ to?: string;
+ params: URLSearchParams;
+ options?: Parameters<typeof svelteGoto>[1];
+}
+
+export function goto({ to = '', params, options }: NavigationOptions) {
+ svelteGoto(`${to}?${params.toString()}`, options).catch(() => toastError('Navigation failed'));
+}
+
+interface NavigationParameters<T> {
+ filter?: T;
+ sort?: Partial<SortData<string>>;
+ pagination?: Partial<PaginationData>;
+}
+
+function paramsFrom<T>(
+ { pagination, filter, sort }: NavigationParameters<T>,
+ current?: URLSearchParams
+) {
+ const params = new URLSearchParams(current);
+
+ if (filter !== undefined) {
+ const json = JsonURL.stringify(filter, { AQF: true, impliedObject: true });
+ if (json) {
+ params.set('f', json);
+ } else {
+ params.delete('f');
+ }
+ }
+
+ if (sort !== undefined) {
+ if (sort.on !== undefined) {
+ params.set('s', sort.on);
+ }
+ if (sort.direction !== undefined) {
+ params.set('d', sort.direction);
+ }
+ if (sort.seed !== undefined) {
+ params.set('r', sort.seed.toString());
+ }
+ }
+
+ params.delete('p');
+
+ if (pagination?.items) {
+ params.set('i', pagination.items.toString());
+ }
+
+ if (pagination?.page) {
+ params.set('p', pagination.page.toString());
+ }
+
+ return params;
+}
+
+export function navigate(parameters: NavigationParameters<object>, current?: URLSearchParams) {
+ goto({
+ params: paramsFrom(parameters, current),
+ options: { noScroll: false, keepFocus: true, replaceState: true }
+ });
+}
+
+export function href<T>(base: string, params: NavigationParameters<T>) {
+ return `/${base}/?${paramsFrom(params).toString()}`;
+}