From b6b3178e3de4ac8c0ef59bf02aaaf2cbc81dc452 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 6 Aug 2025 22:09:43 -0500 Subject: [PATCH] Fixes (#19406) * Fix api filter hook cameras, labels, sub labels, plates, and zones could be parsed as numeric values rather than strings, which would break the explore filter. This change adds an optional param to the useApiFilterArgs hook to always parse keys as string[] * fix notifications register button from being incorrectly disabled --- web/src/hooks/use-api-filter.ts | 43 +++++++++++-------- web/src/pages/Explore.tsx | 8 +++- .../settings/NotificationsSettingsView.tsx | 4 +- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/web/src/hooks/use-api-filter.ts b/web/src/hooks/use-api-filter.ts index bffbff7e8..f9baead6e 100644 --- a/web/src/hooks/use-api-filter.ts +++ b/web/src/hooks/use-api-filter.ts @@ -47,9 +47,9 @@ export default function useApiFilter< return [filter, setFilter, searchParams]; } -export function useApiFilterArgs< - F extends FilterType, ->(): useApiFilterReturn { +export function useApiFilterArgs( + arrayKeys: string[], +): useApiFilterReturn { const [rawParams, setRawParams] = useSearchParams(); const setFilter = useCallback( @@ -64,30 +64,37 @@ export function useApiFilterArgs< const filter: { [key: string]: unknown } = {}; - rawParams.forEach((value, key) => { - const isValidNumber = /^-?\d+(\.\d+)?(?!.)/.test(value); - const isValidEventID = /^\d+\.\d+-[a-zA-Z0-9]+$/.test(value); + // always treat these keys as string[], not as a number or event id + const arrayKeySet = new Set(arrayKeys); - if ( - value != "true" && - value != "false" && - !isValidNumber && - !isValidEventID - ) { + rawParams.forEach((value, key) => { + if (arrayKeySet.has(key)) { filter[key] = value.includes(",") ? value.split(",") : [value]; } else { - if (value != undefined) { - try { - filter[key] = JSON.parse(value); - } catch { - filter[key] = `${value}`; + const isValidNumber = /^-?\d+(\.\d+)?(?!.)/.test(value); + const isValidEventID = /^\d+\.\d+-[a-zA-Z0-9]+$/.test(value); + + if ( + value != "true" && + value != "false" && + !isValidNumber && + !isValidEventID + ) { + filter[key] = value.includes(",") ? value.split(",") : [value]; + } else { + if (value != undefined) { + try { + filter[key] = JSON.parse(value); + } catch { + filter[key] = `${value}`; + } } } } }); return filter as F; - }, [rawParams]); + }, [rawParams, arrayKeys]); const searchParams = useMemo(() => { if (filter == undefined || Object.keys(filter).length == 0) { diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 62d55fee9..9cb97ae2a 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -58,7 +58,13 @@ export default function Explore() { const [search, setSearch] = useState(""); const [searchFilter, setSearchFilter, searchSearchParams] = - useApiFilterArgs(); + useApiFilterArgs([ + "cameras", + "labels", + "sub_labels", + "recognized_license_plate", + "zones", + ]); const searchTerm = useMemo( () => searchSearchParams?.["query"] || "", diff --git a/web/src/views/settings/NotificationsSettingsView.tsx b/web/src/views/settings/NotificationsSettingsView.tsx index 410ba9742..36213fc0e 100644 --- a/web/src/views/settings/NotificationsSettingsView.tsx +++ b/web/src/views/settings/NotificationsSettingsView.tsx @@ -523,7 +523,9 @@ export default function NotificationView({ aria-label={t("notification.registerDevice")} disabled={ (!config?.notifications.enabled && - notificationCameras.length === 0) || + notificationCameras.length === 0 && + !form.watch("allEnabled") && + form.watch("cameras").length === 0) || publicKey == undefined } onClick={() => {