summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib/Navigation.ts
blob: e6b17cd432540f592befdce40e32d99d3bba93b1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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()}`;
}