From c6867de13d1ca877956f7b1b01a09177e83a93f4 Mon Sep 17 00:00:00 2001 From: Bastiaan <17990055+bbassie@users.noreply.github.com> Date: Wed, 4 Nov 2026 16:22:02 +0100 Subject: [PATCH] Keep users instance filter and icon search in the URL (#1185) The instance picker on the users page and the search box on the icon reference page held their value in component state, so leaving the page and coming back with the browser history reset them. Both now go through useTableQueryState like the table filters do, as user.instance and icon.search. Co-authored-by: Claude Opus 5.5 --- .../web_ui/src/components/IconReferencePage.tsx | 15 +++++++++++++-- packages/web_ui/src/components/UsersPage.tsx | 10 ++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/web_ui/src/components/IconReferencePage.tsx b/packages/web_ui/src/components/IconReferencePage.tsx index 2a8f58dc..16ee6dbc 100644 --- a/packages/web_ui/src/components/IconReferencePage.tsx +++ b/packages/web_ui/src/components/IconReferencePage.tsx @@ -9,6 +9,7 @@ import { useParams } from "react-router-dom"; import { useModPack } from "../model/mod_pack"; import { PrototypeMetadataEntry } from "../store/export_prototype_metadata_store"; import useResourceState from "../util/useResourceState"; +import useTableQueryState from "../util/useTableQueryState"; import ResourceState from "./ResourceState"; const { Text } = Typography; @@ -39,7 +40,9 @@ export default function IconReferencePage() { let params = useParams(); let modPackId = Number(params.id); - const [filter, setFilter] = useState(""); + // Filters live as you type, written to the URL on blur, enter or clear + const queryState = useTableQueryState({ namespace: "icon" }); + const filter = queryState.filteredValue("search")?.[0] ?? ""; const [expandedGroups, setExpandedGroups] = useState>(new Set()); const [modPack, synced, error] = useModPack(modPackId); const state = useResourceState(modPack, synced, error, modPackId); @@ -137,7 +140,15 @@ export default function IconReferencePage() { setFilter(e.target.value)} + onChange={e => { + const values = e.target.value ? [e.target.value] : null; + queryState.setFilter("search", values); + if (!values) { + queryState.commitFilter("search", null); + } + }} + onBlur={() => queryState.commitFilter("search")} + onPressEnter={() => queryState.commitFilter("search")} allowClear style={{ width: 320 }} /> diff --git a/packages/web_ui/src/components/UsersPage.tsx b/packages/web_ui/src/components/UsersPage.tsx index e8910729..800097ee 100644 --- a/packages/web_ui/src/components/UsersPage.tsx +++ b/packages/web_ui/src/components/UsersPage.tsx @@ -20,6 +20,7 @@ import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import UsersTable from "./UsersTable"; import { InputInstanceWithAll } from "./InputInstance"; +import useTableQueryState from "../util/useTableQueryState"; function CreateUserButton() { let control = useContext(ControlContext); @@ -368,7 +369,10 @@ function BulkUserActionButton() { export default function UsersPage() { const account = useAccount(); - const [instanceId, setInstanceId] = useState(null); + // Kept in the URL with the users table state so back/forward restores it + const tableState = useTableQueryState({ namespace: "user" }); + const instanceParam = Number(tableState.filteredValue("instance")?.[0]); + const instanceId = Number.isInteger(instanceParam) ? instanceParam : null; return setInstanceId(value as number)} + onChange={value => tableState.commitFilter( + "instance", value === null ? null : [String(value)] + )} fieldDefinition={{ optional: true } as any} disabled={false} />}