Skip to content

Commit 6ad23b1

Browse files
committed
Use sveltekit-search-params to simplify query string manipulation
1 parent 1e87f5c commit 6ad23b1

File tree

7 files changed

+76
-49
lines changed

7 files changed

+76
-49
lines changed

frontend/package-lock.json

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"svelte-check": "^4.1.1",
4949
"svelte-intersection-observer": "^1.0.0",
5050
"svelte-radix": "^1.1.1",
51+
"sveltekit-search-params": "^4.0.0-next.0",
5152
"tailwindcss": "^3.4.17",
5253
"typescript": "^5.7.2",
5354
"typescript-eslint": "^8.18.1",

frontend/src/lib/components/ActionButtons/ActionButtons.svelte

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script lang="ts">
2+
import { queryParameters } from 'sveltekit-search-params';
3+
24
import { Button } from '$lib/components/ui/button';
35
import { cn } from '$lib/utils';
46
@@ -7,14 +9,7 @@
79
import IconSquare3Stack3d from '~icons/heroicons/square-3-stack-3d-16-solid';
810
import IconXMark from '~icons/heroicons/x-mark-16-solid';
911
10-
import { goto } from '$app/navigation';
11-
import { page } from '$app/stores';
12-
import {
13-
getSortDirection,
14-
updateContextSort,
15-
type SortDirection,
16-
type SortContext
17-
} from '$lib/util/sort';
12+
import { getSortParamKey, type SortContext, getSortParamConfig } from '$lib/util/sort';
1813
1914
let {
2015
showCluster = false,
@@ -30,14 +25,16 @@
3025
3126
let activeCluster = showCluster ? 'GroupBys' : null;
3227
33-
let currentSort: SortDirection = $derived.by(() =>
34-
getSortDirection($page.url.searchParams, context)
28+
const sortKey = $derived(getSortParamKey(context));
29+
const params = $derived(
30+
queryParameters(
31+
{ [sortKey]: getSortParamConfig() },
32+
{ pushHistory: false, showDefaults: false }
33+
)
3534
);
3635
37-
function handleSort() {
38-
const newSort: SortDirection = currentSort === 'asc' ? 'desc' : 'asc';
39-
const url = updateContextSort($page.url, context, newSort);
40-
goto(url, { replaceState: true });
36+
function toggleSort() {
37+
params[sortKey] = params[sortKey] === 'asc' ? 'desc' : 'asc';
4138
}
4239
</script>
4340

@@ -61,9 +58,9 @@
6158
<!-- Action Buttons Section -->
6259
<div class="flex gap-3">
6360
{#if showSort}
64-
<Button variant="secondary" size="sm" icon="leading" on:click={handleSort}>
61+
<Button variant="secondary" size="sm" icon="leading" on:click={toggleSort}>
6562
<IconArrowsUpDown />
66-
Sort {currentSort === 'asc' ? 'A-Z' : 'Z-A'}
63+
Sort {params[sortKey] === 'asc' ? 'A-Z' : 'Z-A'}
6764
</Button>
6865
{/if}
6966
<Button variant="secondary" size="sm" icon="leading" disabled>

frontend/src/lib/components/MetricTypeToggle/MetricTypeToggle.svelte

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
11
<script lang="ts">
2+
import { queryParameters } from 'sveltekit-search-params';
3+
24
import { Button } from '$lib/components/ui/button';
35
import {
46
METRIC_LABELS,
57
METRIC_TYPES,
6-
type MetricType,
7-
getMetricTypeFromParams
8+
getMetricTypeParamConfig
89
} from '$lib/types/MetricType/MetricType';
9-
import { page } from '$app/stores';
10-
import { goto } from '$app/navigation';
11-
12-
let selected = $derived(getMetricTypeFromParams(new URL($page.url).searchParams));
1310
14-
function toggle(value: MetricType) {
15-
const url = new URL($page.url);
16-
url.searchParams.set('metric', value);
17-
goto(url, { replaceState: true });
18-
}
11+
const params = queryParameters(
12+
{ metric: getMetricTypeParamConfig() },
13+
{ pushHistory: false, showDefaults: false }
14+
);
1915
</script>
2016

2117
<div class="flex space-x-[1px]">
2218
{#each METRIC_TYPES as metricType}
2319
<Button
24-
variant={selected === metricType ? 'default' : 'secondary'}
20+
variant={params.metric === metricType ? 'default' : 'secondary'}
2521
size="sm"
26-
on:click={() => toggle(metricType)}
22+
on:click={() => (params.metric = metricType)}
2723
class="first:rounded-r-none last:rounded-l-none [&:not(:first-child):not(:last-child)]:rounded-none"
2824
>
2925
{METRIC_LABELS[metricType]}

frontend/src/lib/types/MetricType/MetricType.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { EncodeAndDecodeOptions } from 'sveltekit-search-params/sveltekit-search-params';
2+
13
export const METRIC_TYPES = ['jsd', 'hellinger', 'psi'] as const;
24
export type MetricType = (typeof METRIC_TYPES)[number];
35

@@ -15,6 +17,14 @@ export const METRIC_SCALES: Record<MetricType, { min: number; max: number }> = {
1517
psi: { min: 0, max: 25 }
1618
};
1719

20+
export function getMetricTypeParamConfig() {
21+
return {
22+
encode: (value) => value,
23+
decode: (value) => (METRIC_TYPES.includes(value as MetricType) ? (value as MetricType) : null),
24+
defaultValue: DEFAULT_METRIC_TYPE
25+
} satisfies EncodeAndDecodeOptions<MetricType>;
26+
}
27+
1828
export function getMetricTypeFromParams(searchParams: URLSearchParams): MetricType {
1929
const metric = searchParams.get('metric');
2030
return METRIC_TYPES.includes(metric as MetricType) ? (metric as MetricType) : DEFAULT_METRIC_TYPE;

frontend/src/lib/util/sort.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import type { FeatureResponse, JoinTimeSeriesResponse } from '$lib/types/Model/Model';
2+
import type { EncodeAndDecodeOptions } from 'sveltekit-search-params/sveltekit-search-params';
23

3-
export type SortDirection = 'asc' | 'desc';
4+
export const SORT_DIRECTIONS = ['asc', 'desc'] as const;
5+
export type SortDirection = (typeof SORT_DIRECTIONS)[number];
46
export type SortContext = 'drift' | 'distributions';
57

68
export function getSortParamKey(context: SortContext): string {
79
return `${context}Sort`;
810
}
911

12+
export function getSortParamConfig() {
13+
return {
14+
encode: (value) => value,
15+
decode: (value) =>
16+
SORT_DIRECTIONS.includes(value as SortDirection) ? (value as SortDirection) : null,
17+
defaultValue: 'asc'
18+
} satisfies EncodeAndDecodeOptions<SortDirection>;
19+
}
20+
1021
export function getSortDirection(
1122
searchParams: URLSearchParams,
1223
context: SortContext
@@ -15,12 +26,6 @@ export function getSortDirection(
1526
return param === 'desc' ? 'desc' : 'asc';
1627
}
1728

18-
export function updateContextSort(url: URL, context: SortContext, direction: SortDirection): URL {
19-
const newUrl = new URL(url);
20-
newUrl.searchParams.set(getSortParamKey(context), direction);
21-
return newUrl;
22-
}
23-
2429
export function sortDrift(
2530
joinTimeseries: JoinTimeSeriesResponse,
2631
direction: SortDirection

frontend/src/routes/joins/[slug]/+page.svelte

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
<script lang="ts">
2+
import { untrack, onMount } from 'svelte';
23
import EChart from '$lib/components/EChart/EChart.svelte';
4+
import { connect } from 'echarts';
35
import type { EChartOption, EChartsType, ECElementEvent } from 'echarts';
6+
import { queryParameters } from 'sveltekit-search-params';
7+
import IntersectionObserver from 'svelte-intersection-observer';
8+
import { fade } from 'svelte/transition';
49
5-
import { Tabs, TabsList, TabsTrigger, TabsContent } from '$lib/components/ui/tabs';
610
import IconTableCells from '~icons/heroicons/table-cells-16-solid';
711
import IconChartLine from '~icons/zipline-ai/chart-line';
12+
13+
import { Tabs, TabsList, TabsTrigger, TabsContent } from '$lib/components/ui/tabs';
814
import CollapsibleSection from '$lib/components/CollapsibleSection/CollapsibleSection.svelte';
9-
import { connect } from 'echarts';
1015
import type { FeatureResponse, TimeSeriesItem } from '$lib/types/Model/Model';
1116
import { ScrollArea } from '$lib/components/ui/scroll-area';
12-
import { untrack } from 'svelte';
1317
import PageHeader from '$lib/components/PageHeader/PageHeader.svelte';
1418
import Separator from '$lib/components/ui/separator/separator.svelte';
1519
import ResetZoomButton from '$lib/components/ResetZoomButton/ResetZoomButton.svelte';
16-
import IntersectionObserver from 'svelte-intersection-observer';
17-
import { fade } from 'svelte/transition';
1820
import { Button } from '$lib/components/ui/button';
1921
import { Api } from '$lib/api/api';
2022
import InfoTooltip from '$lib/components/InfoTooltip/InfoTooltip.svelte';
@@ -28,9 +30,12 @@
2830
import { getSeriesColor } from '$lib/util/chart';
2931
import { handleChartHighlight } from '$lib/util/chart';
3032
import ChartControls from '$lib/components/ChartControls/ChartControls.svelte';
31-
import { onMount } from 'svelte';
32-
import { page } from '$app/stores';
33-
import { getSortDirection, sortDistributions, type SortContext } from '$lib/util/sort';
33+
import {
34+
getSortParamConfig,
35+
getSortParamKey,
36+
sortDistributions,
37+
type SortContext
38+
} from '$lib/util/sort';
3439
3540
const api = new Api();
3641
@@ -447,10 +452,12 @@
447452
loadDistributions();
448453
});
449454
450-
const sortedDistributions = $derived.by(() => {
451-
const distributionsSort = getSortDirection($page.url.searchParams, 'distributions');
452-
return sortDistributions(distributions, distributionsSort);
453-
});
455+
const sortKey = getSortParamKey('distributions');
456+
const params = queryParameters(
457+
{ [sortKey]: getSortParamConfig() },
458+
{ pushHistory: false, showDefaults: false }
459+
);
460+
const sortedDistributions = $derived(sortDistributions(distributions, params[sortKey]));
454461
455462
let selectedTab = $state<SortContext>('drift');
456463

0 commit comments

Comments
 (0)