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()}`;
}
|