From 4173aaaeaf3166ac65559587ca8e6654efe11a92 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Wed, 8 Jul 2026 12:09:13 +0530 Subject: [PATCH 1/2] refac: move table toolbar code to new rune stores --- .../features/branches/BranchesSection.svelte | 77 ++------ .../dashboards/listing/DashboardsTable.svelte | 18 +- .../resource-table/ProjectResources.svelte | 142 ++++++--------- .../status/tables/ProjectTables.svelte | 170 ++++++++---------- .../PublicURLsResourceTable.svelte | 34 ++-- .../environment-variables/+page.svelte | 163 +++++++---------- .../table-toolbar/TableToolbar.svelte | 39 ++-- .../TableToolbarAppliedFilters.svelte | 12 +- .../TableToolbarFilterDropdown.svelte | 15 +- .../table-toolbar/TableToolbarSearch.svelte | 25 +-- .../table-toolbar/TableToolbarSort.svelte | 11 +- .../TableToolbarViewToggle.svelte | 10 +- .../src/components/table-toolbar/types.ts | 31 ++-- .../src/lib/store-utils/types.svelte.ts | 21 +++ 14 files changed, 327 insertions(+), 441 deletions(-) diff --git a/web-admin/src/features/branches/BranchesSection.svelte b/web-admin/src/features/branches/BranchesSection.svelte index ebdaf55268fa..0f6d0d3d1674 100644 --- a/web-admin/src/features/branches/BranchesSection.svelte +++ b/web-admin/src/features/branches/BranchesSection.svelte @@ -38,11 +38,6 @@ import { TableToolbar } from "@rilldata/web-common/components/table-toolbar"; import type { FilterGroup } from "@rilldata/web-common/components/table-toolbar/types"; import DelayedSpinner from "@rilldata/web-common/features/entity-management/DelayedSpinner.svelte"; - import { - createUrlFilterSync, - parseArrayParam, - parseStringParam, - } from "@rilldata/web-common/lib/url-filter-sync"; import { GitBranchIcon, PlayIcon, @@ -50,8 +45,9 @@ Trash2Icon, } from "lucide-svelte"; import { eventBus } from "@rilldata/web-common/lib/event-bus/event-bus"; - import { onMount } from "svelte"; import { featureFlags } from "@rilldata/web-common/features/feature-flags"; + import { UrlParamsState } from "@rilldata/web-common/lib/store-utils/url-params-state.svelte.ts"; + import { DebouncedRuneStore } from "@rilldata/web-common/lib/store-utils/types.svelte.ts"; let { organization, project }: { organization: string; project: string } = $props(); @@ -120,41 +116,13 @@ : null, ); - // Toolbar state — synced to URL params `q` and `status` (multi-select array) - const filterSync = createUrlFilterSync([ - { key: "q", type: "string" }, - { key: "status", type: "array" }, - ]); - - let searchText = $state(parseStringParam(page.url.searchParams.get("q"))); - let statusFilter = $state( - parseArrayParam(page.url.searchParams.get("status")), + const searchTextStore = new DebouncedRuneStore( + UrlParamsState.createStringParam("q"), + 500, ); - let mounted = $state(false); - - onMount(() => { - filterSync.init(page.url); - mounted = true; - }); - - // URL → local state on external navigation (back/forward) - $effect(() => { - if (!mounted) return; - const url = page.url; - if (filterSync.hasExternalNavigation(url)) { - filterSync.markSynced(url); - searchText = parseStringParam(url.searchParams.get("q")); - statusFilter = parseArrayParam(url.searchParams.get("status")); - } - }); + const statusFilterStore = UrlParamsState.createStringArrayParam("status"); - // Local state → URL - $effect(() => { - if (!mounted) return; - filterSync.syncToUrl({ q: searchText, status: statusFilter }); - }); - - let filterGroups = $derived([ + let filterGroups = $derived([ { label: "Status", key: "status", @@ -164,16 +132,22 @@ { label: "Error", value: "errored" }, { label: "Stopped", value: "stopped" }, ], - selected: statusFilter, + selectedStore: statusFilterStore, + selected: statusFilterStore.value, defaultValue: [], multiSelect: true, }, - ] satisfies FilterGroup[]); + ]); + + function onClearAllFilters() { + searchTextStore.setter(""); + statusFilterStore.setter([]); + } function statusMatches(d: V1Deployment): boolean { - if (statusFilter.length === 0) return true; + if (statusFilterStore.value.length === 0) return true; const s = d.status; - return statusFilter.some((sel) => { + return statusFilterStore.value.some((sel) => { switch (sel) { case "running": return s === V1DeploymentStatus.DEPLOYMENT_STATUS_RUNNING; @@ -196,7 +170,7 @@ } let visibleDeployments = $derived.by(() => { - const q = searchText.trim().toLowerCase(); + const q = searchTextStore.value.trim().toLowerCase(); const active = ($allDeployments.data?.deployments ?? []).filter( (d: V1Deployment) => d.status !== V1DeploymentStatus.DEPLOYMENT_STATUS_DELETED && @@ -245,10 +219,6 @@ editable: boolean; } | null>(null); - function onFilterChange(key: string, selected: string[]) { - if (key === "status") statusFilter = selected; - } - async function mutateDeployment( deploymentId: string, branch: string | undefined, @@ -315,16 +285,7 @@

Branches

- { - statusFilter = []; - searchText = ""; - }} - showSort={false} - /> + {#if $allDeployments.isLoading}
diff --git a/web-admin/src/features/dashboards/listing/DashboardsTable.svelte b/web-admin/src/features/dashboards/listing/DashboardsTable.svelte index d46ada8a25a9..ffef7a5300a4 100644 --- a/web-admin/src/features/dashboards/listing/DashboardsTable.svelte +++ b/web-admin/src/features/dashboards/listing/DashboardsTable.svelte @@ -16,7 +16,7 @@ import ResizableSidebar from "@rilldata/web-common/layout/ResizableSidebar.svelte"; import DashboardsTagSidebar from "@rilldata/web-admin/features/dashboards/listing/DashboardsTagSidebar.svelte"; import { filterResources } from "@rilldata/web-common/features/resources/resource-filter-utils.ts"; - import { Throttler } from "@rilldata/web-common/lib/throttler.ts"; + import { DebouncedRuneStore } from "@rilldata/web-common/lib/store-utils/types.svelte.ts"; let { isEmbedded = false, @@ -30,11 +30,10 @@ const selectedTagsState = UrlParamsState.createStringArrayParam("tags"); - const searchTextState = UrlParamsState.createStringParam("search"); - const throttler = new Throttler(500, 500); - const throttledSearchSetter = (newValue: string) => { - throttler.throttle(() => searchTextState.setter(newValue)); - }; + const searchTextStore = new DebouncedRuneStore( + UrlParamsState.createStringParam("q"), + 500, + ); const runtimeClient = useRuntimeClient(); let { organization, project } = $derived(page.params); @@ -59,7 +58,7 @@ filterResources( allDashboards, [], - searchTextState.value, + searchTextStore.value, [], selectedTagsState.value, ), @@ -159,9 +158,10 @@
{/if} @@ -178,7 +178,7 @@ > {/if} diff --git a/web-admin/src/features/projects/status/resource-table/ProjectResources.svelte b/web-admin/src/features/projects/status/resource-table/ProjectResources.svelte index 7a4c3e3b6fd7..da6d2b8a8261 100644 --- a/web-admin/src/features/projects/status/resource-table/ProjectResources.svelte +++ b/web-admin/src/features/projects/status/resource-table/ProjectResources.svelte @@ -1,5 +1,4 @@
- + - import { page } from "$app/stores"; + import { page } from "$app/state"; import { createAdminServiceGetProjectVariables } from "@rilldata/web-admin/client"; import AddDialog from "@rilldata/web-admin/features/projects/environment-variables/AddDialog.svelte"; import EnvironmentVariablesTable from "@rilldata/web-admin/features/projects/environment-variables/EnvironmentVariablesTable.svelte"; - import { - EnvironmentType, - type EnvironmentTypes, - } from "@rilldata/web-admin/features/projects/environment-variables/types"; + import { EnvironmentType } from "@rilldata/web-admin/features/projects/environment-variables/types"; import { getEnvironmentType } from "@rilldata/web-admin/features/projects/environment-variables/utils"; import Button from "@rilldata/web-common/components/button/Button.svelte"; - import { TableToolbar } from "@rilldata/web-common/components/table-toolbar"; + import { + type FilterGroup, + TableToolbar, + } from "@rilldata/web-common/components/table-toolbar"; import RadixLarge from "@rilldata/web-common/components/typography/RadixLarge.svelte"; import DelayedSpinner from "@rilldata/web-common/features/entity-management/DelayedSpinner.svelte"; - import { - createUrlFilterSync, - parseArrayParam, - parseStringParam, - } from "@rilldata/web-common/lib/url-filter-sync"; import { Plus } from "lucide-svelte"; - import { onMount } from "svelte"; - - let open = false; - - // Filters — synced to URL params `q` and `env` (multi-select array) - const filterSync = createUrlFilterSync([ - { key: "q", type: "string" }, - { key: "env", type: "array" }, - ]); - filterSync.init($page.url); - - let searchText = parseStringParam($page.url.searchParams.get("q")); - let envFilter: EnvironmentTypes[] = parseArrayParam( - $page.url.searchParams.get("env"), - ) as EnvironmentTypes[]; - let mounted = false; + import { UrlParamsState } from "@rilldata/web-common/lib/store-utils/url-params-state.svelte.ts"; + import { DebouncedRuneStore } from "@rilldata/web-common/lib/store-utils/types.svelte.ts"; - // URL → local state on external navigation (back/forward) - $: if (mounted && filterSync.hasExternalNavigation($page.url)) { - filterSync.markSynced($page.url); - searchText = parseStringParam($page.url.searchParams.get("q")); - envFilter = parseArrayParam( - $page.url.searchParams.get("env"), - ) as EnvironmentTypes[]; - } - - // Local state → URL - $: if (mounted) { - filterSync.syncToUrl({ q: searchText, env: envFilter }); - } + let open = $state(false); - onMount(() => { - mounted = true; - }); + const searchTextStore = new DebouncedRuneStore( + UrlParamsState.createStringParam("q"), + 500, + ); + const envFilterStore = UrlParamsState.createStringArrayParam("env"); - $: organization = $page.params.organization; - $: project = $page.params.project; + let { organization, project } = $derived(page.params); - $: getProjectVariables = createAdminServiceGetProjectVariables( - organization, - project, - { + let getProjectVariables = $derived( + createAdminServiceGetProjectVariables(organization, project, { forAllEnvironments: true, - }, + }), ); - $: projectVariables = $getProjectVariables.data?.variables || []; + let projectVariables = $derived($getProjectVariables.data?.variables || []); - $: variableNames = projectVariables.map((variable) => { - return { - environment: getEnvironmentType(variable.environment), - name: variable.name, - }; - }); - - $: searchedVariables = projectVariables.filter((variable) => - variable.name.toLowerCase().includes(searchText.toLowerCase()), + let variableNames = $derived( + projectVariables.map((variable) => { + return { + environment: getEnvironmentType(variable.environment), + name: variable.name, + }; + }), ); - $: filteredVariables = searchedVariables.filter((variable) => { - if (envFilter.length === 0) return true; - return envFilter.some((sel) => { - if (sel === EnvironmentType.DEVELOPMENT) { - return ( - variable.environment === EnvironmentType.DEVELOPMENT || - variable.environment === EnvironmentType.UNDEFINED - ); - } - if (sel === EnvironmentType.PRODUCTION) { - return ( - variable.environment === EnvironmentType.PRODUCTION || - variable.environment === EnvironmentType.UNDEFINED - ); - } - return false; - }); - }); + let searchedVariables = $derived( + projectVariables.filter((variable) => + variable.name.toLowerCase().includes(searchTextStore.value.toLowerCase()), + ), + ); - $: sortedVariables = [...filteredVariables].sort((a, b) => { - return new Date(b.updatedOn).getTime() - new Date(a.updatedOn).getTime(); - }); + let filteredVariables = $derived( + searchedVariables.filter((variable) => { + if (envFilterStore.value.length === 0) return true; + return envFilterStore.value.some((sel) => { + if (sel === EnvironmentType.DEVELOPMENT) { + return ( + variable.environment === EnvironmentType.DEVELOPMENT || + variable.environment === EnvironmentType.UNDEFINED + ); + } + if (sel === EnvironmentType.PRODUCTION) { + return ( + variable.environment === EnvironmentType.PRODUCTION || + variable.environment === EnvironmentType.UNDEFINED + ); + } + return false; + }); + }), + ); - function handleFilterChange(_key: string, selected: string | string[]) { - envFilter = selected as EnvironmentTypes[]; - } + let sortedVariables = $derived( + [...filteredVariables].sort((a, b) => { + return new Date(b.updatedOn).getTime() - new Date(a.updatedOn).getTime(); + }), + ); function handleClearAllFilters() { - envFilter = []; - searchText = ""; + searchTextStore.setter(""); + envFilterStore.setter([]); } - $: emptyTextWhenNoVariables = - envFilter.length === 0 + let emptyTextWhenNoVariables = $derived( + envFilterStore.value.length === 0 ? "No environment variables" - : `No environment variables match the selected filters`; + : `No environment variables match the selected filters`, + ); - $: filterGroups = [ + let filterGroups = $derived([ { label: "Environment", key: "environment", @@ -122,11 +95,12 @@ { value: EnvironmentType.PRODUCTION, label: "Production" }, { value: EnvironmentType.DEVELOPMENT, label: "Development" }, ], - selected: envFilter, + selectedStore: envFilterStore, + selected: envFilterStore.value, defaultValue: [], multiSelect: true, }, - ]; + ]);
@@ -152,12 +126,9 @@

diff --git a/web-common/src/components/table-toolbar/TableToolbarSort.svelte b/web-common/src/components/table-toolbar/TableToolbarSort.svelte index d02d1401663b..e60fdaffce2f 100644 --- a/web-common/src/components/table-toolbar/TableToolbarSort.svelte +++ b/web-common/src/components/table-toolbar/TableToolbarSort.svelte @@ -1,17 +1,20 @@ diff --git a/web-common/src/components/table-toolbar/TableToolbarViewToggle.svelte b/web-common/src/components/table-toolbar/TableToolbarViewToggle.svelte index fab12f3696f0..a0877b594562 100644 --- a/web-common/src/components/table-toolbar/TableToolbarViewToggle.svelte +++ b/web-common/src/components/table-toolbar/TableToolbarViewToggle.svelte @@ -3,12 +3,14 @@ import List from "@rilldata/web-common/components/icons/List.svelte"; import { LayoutGrid } from "lucide-svelte"; import type { ViewMode } from "./types"; + import type { RuneStore } from "@rilldata/web-common/lib/store-utils/types.svelte.ts"; let { - viewMode = $bindable("list"), + viewModeStore, }: { - viewMode: ViewMode; + viewModeStore: RuneStore; } = $props(); + let viewMode = $derived(viewModeStore.value);
@@ -17,7 +19,7 @@ class="flex items-center justify-center w-8 h-8 {viewMode === 'grid' ? 'bg-surface-hover' : 'bg-input hover:bg-surface-hover'}" - onclick={() => (viewMode = "grid")} + onclick={() => viewModeStore.setter("grid")} aria-label="Grid view" aria-pressed={viewMode === "grid"} > @@ -29,7 +31,7 @@ 'list' ? 'bg-surface-hover' : 'bg-input hover:bg-surface-hover'}" - onclick={() => (viewMode = "list")} + onclick={() => viewModeStore.setter("list")} aria-label="List view" aria-pressed={viewMode === "list"} > diff --git a/web-common/src/components/table-toolbar/types.ts b/web-common/src/components/table-toolbar/types.ts index 5178712ff915..8e214449af7d 100644 --- a/web-common/src/components/table-toolbar/types.ts +++ b/web-common/src/components/table-toolbar/types.ts @@ -1,3 +1,8 @@ +import type { + ArrayRuneStore, + RuneStore, +} from "@rilldata/web-common/lib/store-utils/types.svelte.ts"; + export type SortDirection = "newest" | "oldest"; export type ViewMode = "list" | "grid"; @@ -7,17 +12,21 @@ export interface FilterOption { label: string; } -export interface FilterGroup { - /** Dropdown section header */ +export type FilterGroup = { label: string; - /** Unique key for this filter group */ key: string; - /** Available options */ options: FilterOption[]; - /** Currently selected value(s). String for single-select, string[] for multi-select. */ - selected: string | string[]; - /** Default value; when selected matches defaultValue, no chip is shown */ - defaultValue: string | string[]; - /** Allow multiple selections. Default: false (single-select radio behavior). */ - multiSelect?: boolean; -} +} & ( + | { + selectedStore: RuneStore; + selected: string; + defaultValue: string; + multiSelect?: false; + } + | { + selectedStore: ArrayRuneStore; + selected: string[]; + defaultValue: string[]; + multiSelect: true; + } +); diff --git a/web-common/src/lib/store-utils/types.svelte.ts b/web-common/src/lib/store-utils/types.svelte.ts index ec9f12382bdf..145672f382ca 100644 --- a/web-common/src/lib/store-utils/types.svelte.ts +++ b/web-common/src/lib/store-utils/types.svelte.ts @@ -1,3 +1,5 @@ +import { debounce } from "@rilldata/web-common/lib/create-debouncer.ts"; + export interface RuneStore { value: Val | DefaultVal; getter: () => Val | DefaultVal; @@ -44,4 +46,23 @@ export class ArrayRuneStore implements RuneStore { : [...this.value, value]; this.setter(newTags); }; + + public delete = (value: Val) => { + const newTags = this.value.filter((v) => v !== value); + this.setter(newTags); + }; +} + +export class DebouncedRuneStore + implements RuneStore +{ + public value: Val; + public getter: () => Val; + public setter: (newValue: Val) => void; + + public constructor(store: RuneStore, timeout: number) { + this.value = $derived(store.value); + this.getter = store.getter; + this.setter = debounce(store.setter, timeout); + } } From 74c0ac014bbc4c89f6070828e8148bed86fead70 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Fri, 10 Jul 2026 17:23:28 +0530 Subject: [PATCH 2/2] Match mocks --- .../features/branches/BranchesSection.svelte | 1 - .../resource-table/ProjectResources.svelte | 11 ++-- .../status/tables/ProjectTables.svelte | 2 - .../environment-variables/+page.svelte | 1 - .../table-toolbar/TableToolbar.svelte | 26 +++++----- .../TableToolbarAppliedFilters.svelte | 25 ++++----- .../TableToolbarFilterDropdown.svelte | 29 ++++++++--- .../table-toolbar/TableToolbarSearch.svelte | 52 ++----------------- .../table-toolbar/TableToolbarSort.svelte | 46 +++++++++------- .../src/components/table-toolbar/types.ts | 11 ++-- .../store-utils/url-params-state.svelte.ts | 28 +++++++--- 11 files changed, 111 insertions(+), 121 deletions(-) diff --git a/web-admin/src/features/branches/BranchesSection.svelte b/web-admin/src/features/branches/BranchesSection.svelte index 94d2190ac387..fe52c5cf942e 100644 --- a/web-admin/src/features/branches/BranchesSection.svelte +++ b/web-admin/src/features/branches/BranchesSection.svelte @@ -134,7 +134,6 @@ { label: m.branch_status_stopped(), value: "stopped" }, ], selectedStore: statusFilterStore, - selected: statusFilterStore.value, defaultValue: [], multiSelect: true, }, diff --git a/web-admin/src/features/projects/status/resource-table/ProjectResources.svelte b/web-admin/src/features/projects/status/resource-table/ProjectResources.svelte index 3e2f2e8d88dc..9ed67e25d12c 100644 --- a/web-admin/src/features/projects/status/resource-table/ProjectResources.svelte +++ b/web-admin/src/features/projects/status/resource-table/ProjectResources.svelte @@ -98,7 +98,6 @@ label: prettyResourceKind(t), })), selectedStore: selectedTypesStore, - selected: selectedTypesStore.value, defaultValue: [], multiSelect: true, }, @@ -110,21 +109,19 @@ label: s.label, })), selectedStore: selectedStatusesStore, - selected: selectedStatusesStore.value, defaultValue: [], multiSelect: true, }, ...(availableTags.length > 0 ? [ { - label: "Tags", + label: m.dashboard_tags(), key: "tags", options: availableTags.map((t) => ({ value: t.name, label: t.name, })), selectedStore: selectedTagsStore, - selected: selectedTagsStore.value, defaultValue: [], multiSelect: true, }, @@ -175,9 +172,9 @@ }} disabled={isRefreshButtonDisabled} > - + {m.status_refresh_all()} diff --git a/web-admin/src/features/projects/status/tables/ProjectTables.svelte b/web-admin/src/features/projects/status/tables/ProjectTables.svelte index 8c2f0f1809ed..f3ebf5c2df99 100644 --- a/web-admin/src/features/projects/status/tables/ProjectTables.svelte +++ b/web-admin/src/features/projects/status/tables/ProjectTables.svelte @@ -95,7 +95,6 @@ { label: m.status_view_singular(), value: "view" }, ], selectedStore: selectedTypesStore, - selected: selectedTagsStore.value, defaultValue: [], multiSelect: true, }, @@ -109,7 +108,6 @@ label: t.name, })), selectedStore: selectedTagsStore, - selected: selectedTagsStore.value, defaultValue: [], multiSelect: true, }, diff --git a/web-admin/src/routes/[organization]/[project]/-/settings/environment-variables/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/settings/environment-variables/+page.svelte index 1b1adac005ec..fb2b860552db 100644 --- a/web-admin/src/routes/[organization]/[project]/-/settings/environment-variables/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/settings/environment-variables/+page.svelte @@ -105,7 +105,6 @@ }, ], selectedStore: envFilterStore, - selected: envFilterStore.value, defaultValue: [], multiSelect: true, }, diff --git a/web-common/src/components/table-toolbar/TableToolbar.svelte b/web-common/src/components/table-toolbar/TableToolbar.svelte index ca4d311034b8..09105761f48b 100644 --- a/web-common/src/components/table-toolbar/TableToolbar.svelte +++ b/web-common/src/components/table-toolbar/TableToolbar.svelte @@ -4,40 +4,42 @@ import TableToolbarSearch from "./TableToolbarSearch.svelte"; import TableToolbarSort from "./TableToolbarSort.svelte"; import TableToolbarViewToggle from "./TableToolbarViewToggle.svelte"; - import type { FilterGroup, SortDirection, ViewMode } from "./types"; + import type { FilterGroup, SortOption, ViewMode } from "./types"; import type { Snippet } from "svelte"; import type { RuneStore } from "@rilldata/web-common/lib/store-utils/types.svelte.ts"; let { searchTextStore, filterGroups = [], - sortDirectionStore, + sortStore, + sortOptions, viewModeStore, onClearAllFilters, children, }: { searchTextStore?: RuneStore; filterGroups?: FilterGroup[]; - sortDirectionStore?: RuneStore; + sortStore?: RuneStore; + sortOptions?: SortOption[]; viewModeStore?: RuneStore; onClearAllFilters?: () => void; children?: Snippet; } = $props(); -
-
-
+
+
+
-
- -
- - {#if sortDirectionStore} - + {#if sortStore && sortOptions} + {/if} +
+ + +
{#if viewModeStore} {/if} diff --git a/web-common/src/components/table-toolbar/TableToolbarAppliedFilters.svelte b/web-common/src/components/table-toolbar/TableToolbarAppliedFilters.svelte index dfbc64a32060..e39aa862655a 100644 --- a/web-common/src/components/table-toolbar/TableToolbarAppliedFilters.svelte +++ b/web-common/src/components/table-toolbar/TableToolbarAppliedFilters.svelte @@ -20,24 +20,24 @@ let appliedFilters = $derived( filterGroups.flatMap((g): AppliedChip[] => { if (g.multiSelect) { - return g.selected.map((val) => ({ + return g.selectedStore.value.map((val) => ({ key: g.key, value: val, label: g.options.find((o) => o.value === val)?.label ?? val, })); } + const selected = g.selectedStore.value; if ( - typeof g.selected === "string" && - g.selected && - g.selected !== g.defaultValue + typeof selected === "string" && + selected && + selected !== g.defaultValue ) { return [ { key: g.key, - value: g.selected, + value: selected, label: - g.options.find((o) => o.value === g.selected)?.label ?? - g.selected, + g.options.find((o) => o.value === selected)?.label ?? selected, }, ]; } @@ -60,12 +60,13 @@ {#if hasFilters}
-
-
-
+
+
{#each appliedFilters as filter (`${filter.key}:${filter.value}`)} {filter.label}
-{:else} - {/if} diff --git a/web-common/src/components/table-toolbar/TableToolbarSort.svelte b/web-common/src/components/table-toolbar/TableToolbarSort.svelte index e60fdaffce2f..69867598f84d 100644 --- a/web-common/src/components/table-toolbar/TableToolbarSort.svelte +++ b/web-common/src/components/table-toolbar/TableToolbarSort.svelte @@ -1,29 +1,39 @@ - + + + + Sort by {sortLabel} + + + {#each sortOptions as option (option.value)} + sortStore.setter(option.value)} + > + {option.label} + + {/each} + + diff --git a/web-common/src/components/table-toolbar/types.ts b/web-common/src/components/table-toolbar/types.ts index 8e214449af7d..69f74c150070 100644 --- a/web-common/src/components/table-toolbar/types.ts +++ b/web-common/src/components/table-toolbar/types.ts @@ -2,8 +2,7 @@ import type { ArrayRuneStore, RuneStore, } from "@rilldata/web-common/lib/store-utils/types.svelte.ts"; - -export type SortDirection = "newest" | "oldest"; +import type { ColumnSort } from "tanstack-table-8-svelte-5"; export type ViewMode = "list" | "grid"; @@ -19,14 +18,18 @@ export type FilterGroup = { } & ( | { selectedStore: RuneStore; - selected: string; defaultValue: string; multiSelect?: false; } | { selectedStore: ArrayRuneStore; - selected: string[]; defaultValue: string[]; multiSelect: true; } ); + +export type SortOption = { + value: string; + label: string; + sort: ColumnSort; +}; diff --git a/web-common/src/lib/store-utils/url-params-state.svelte.ts b/web-common/src/lib/store-utils/url-params-state.svelte.ts index 7106245d9664..efba886a0ca8 100644 --- a/web-common/src/lib/store-utils/url-params-state.svelte.ts +++ b/web-common/src/lib/store-utils/url-params-state.svelte.ts @@ -5,6 +5,9 @@ import { ArrayRuneStore, type RuneStore, } from "@rilldata/web-common/lib/store-utils/types.svelte.ts"; +import { tick } from "svelte"; + +let newParams: [key: string, value: string | null][] = []; export class UrlParamsState implements RuneStore @@ -56,15 +59,24 @@ export class UrlParamsState public setter = (newValue: Val) => { const newParamValue = this.serializer(newValue); - const newUrl = new SvelteURL(window.location.href); - if (newParamValue === null) { - newUrl.searchParams.delete(this.param); + const hasParam = newParams.length > 0; + newParams.push([this.param, newParamValue]); + if (!hasParam) void tick().then(flushParams); + }; +} + +function flushParams() { + if (newParams.length === 0) return; + + const newUrl = new SvelteURL(window.location.href); + newParams.forEach(([key, value]) => { + if (value === null) { + newUrl.searchParams.delete(key); } else { - newUrl.searchParams.set(this.param, newParamValue); + newUrl.searchParams.set(key, value); } + }); - this.paramValue = newParamValue; - this.value = newValue; - void goto(newUrl, { noScroll: true, keepFocus: true }); - }; + newParams = []; + void goto(newUrl, { noScroll: true, keepFocus: true }); }