From e3633633e919e9fad10f53d45765c4d2d3282893 Mon Sep 17 00:00:00 2001 From: Benji Date: Sat, 11 Oct 2025 13:51:04 +0200 Subject: [PATCH] consider typescript --- frontend/layouts/default.vue | 4 +-- frontend/pages/items.vue | 57 +++++++++++++++++++++++++++++------- frontend/stores/labels.ts | 7 ++--- frontend/stores/locations.ts | 7 ++--- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index f7c6c984..7877da5b 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -368,12 +368,12 @@ ]); const labelStore = useLabelStore(); + labelStore.ensureAllLabelsFetched(); const locationStore = useLocationStore(); + locationStore.ensureLocationsFetched(); onMounted(() => { - labelStore.refresh(); - locationStore.refreshChildren(); locationStore.refreshParents(); locationStore.refreshTree(); }); diff --git a/frontend/pages/items.vue b/frontend/pages/items.vue index 9c8c3a4d..31d7c450 100644 --- a/frontend/pages/items.vue +++ b/frontend/pages/items.vue @@ -17,6 +17,7 @@ import BaseContainer from "@/components/Base/Container.vue"; import SearchFilter from "~/components/Search/Filter.vue"; import ItemViewSelectable from "~/components/Item/View/Selectable.vue"; + import type { LocationQueryRaw } from "vue-router"; const { t } = useI18n(); @@ -37,10 +38,33 @@ const items = ref([]); const total = ref(0); - const queryParamDefaultValues = {}; - function useOptionalRouteQuery(key, defaultValue) { + type queryParamValue = string | string[] | number | boolean; + type queryRef = + | WritableComputedRef + | WritableComputedRef + | WritableComputedRef + | WritableComputedRef; + const queryParamDefaultValues: Record = {}; + function useOptionalRouteQuery(key: string, defaultValue: string): WritableComputedRef; + function useOptionalRouteQuery(key: string, defaultValue: string[]): WritableComputedRef; + function useOptionalRouteQuery(key: string, defaultValue: number): WritableComputedRef; + function useOptionalRouteQuery(key: string, defaultValue: boolean): WritableComputedRef; + function useOptionalRouteQuery(key: string, defaultValue: queryParamValue): queryRef { queryParamDefaultValues[key] = defaultValue; - return useRouteQuery(key, defaultValue); + if (typeof defaultValue === "string") { + return useRouteQuery(key, defaultValue); + } + if (Array.isArray(defaultValue)) { + return useRouteQuery(key, defaultValue as string[]); + } + if (typeof defaultValue === "number") { + return useRouteQuery(key, defaultValue); + } + if (typeof defaultValue === "boolean") { + return useRouteQuery(key, defaultValue); + } + + throw Error(`Invalid query value type ${typeof defaultValue}`); } const page1 = useOptionalRouteQuery("page", 1); @@ -59,6 +83,8 @@ const onlyWithoutPhoto = useOptionalRouteQuery("onlyWithoutPhoto", false); const onlyWithPhoto = useOptionalRouteQuery("onlyWithPhoto", false); const orderBy = useOptionalRouteQuery("orderBy", "name"); + const qLoc = useOptionalRouteQuery("loc", []); + const qLab = useOptionalRouteQuery("lab", []); const preferences = useViewPreferences(); const pageSize = computed(() => preferences.value.itemsPerTablePage); @@ -70,14 +96,12 @@ loading.value = true; searchLocked.value = true; await Promise.all([locationsStore.ensureLocationsFetched(), labelStore.ensureAllLabelsFetched()]); - const qLoc = route.query.loc as string[]; if (qLoc) { - selectedLocations.value = locations.value.filter(l => qLoc.includes(l.id)); + selectedLocations.value = locations.value.filter(l => qLoc.value.includes(l.id)); } - const qLab = route.query.lab as string[]; if (qLab) { - selectedLabels.value = labels.value.filter(l => qLab.includes(l.id)); + selectedLabels.value = labels.value.filter(l => qLab.value.includes(l.id)); } queryParamsInitialized.value = true; @@ -233,7 +257,7 @@ } } - const push_query = { + const push_query: Record = { archived: includeArchived.value, fieldSelector: fieldSelector.value, negateLabels: negateLabels.value, @@ -249,12 +273,25 @@ for (const key in push_query) { const val = push_query[key]; - if ((Array.isArray(val) && val.length == 0) || val === queryParamDefaultValues[key]) { + const defaultVal = queryParamDefaultValues[key]; + if ( + (Array.isArray(val) && + Array.isArray(defaultVal) && + val.length == defaultVal.length && + val.every(v => (defaultVal as string[]).includes(v))) || + val === queryParamDefaultValues[key] + ) { push_query[key] = undefined; } + + // Empirically seen to be unnecessary but according to router.push types, + // booleans are not supported. This might be more stable. + if (typeof push_query[key] === "boolean") { + push_query[key] = String(val); + } } - await router.push({ query: push_query }); + await router.push({ query: push_query as LocationQueryRaw }); const { data, error } = await api.items.getAll({ q: query.value || "", diff --git a/frontend/stores/labels.ts b/frontend/stores/labels.ts index 96fb4621..b4290e66 100644 --- a/frontend/stores/labels.ts +++ b/frontend/stores/labels.ts @@ -5,6 +5,7 @@ export const useLabelStore = defineStore("labels", { state: () => ({ allLabels: null as LabelOut[] | null, client: useUserApi(), + refreshAllLabelsPromise: null as Promise | null, }), getters: { /** @@ -13,8 +14,6 @@ export const useLabelStore = defineStore("labels", { * response. */ labels(state): LabelOut[] { - // ensures that labels are eventually available but not synchronously - state.ensureAllLabelsFetched(); return state.allLabels ?? []; }, }, @@ -24,8 +23,8 @@ export const useLabelStore = defineStore("labels", { return; } - if (this.refreshAllLabelsPromise === undefined) { - this.refreshAllLabelsPromise = this.refresh(); + if (this.refreshAllLabelsPromise === null) { + this.refreshAllLabelsPromise = this.refresh().then(() => {}); } await this.refreshAllLabelsPromise; }, diff --git a/frontend/stores/locations.ts b/frontend/stores/locations.ts index 6d4972a1..39743cd2 100644 --- a/frontend/stores/locations.ts +++ b/frontend/stores/locations.ts @@ -8,6 +8,7 @@ export const useLocationStore = defineStore("locations", { Locations: null as LocationOutCount[] | null, client: useUserApi(), tree: null as TreeItem[] | null, + refreshLocationsPromise: null as Promise | null, }), getters: { /** @@ -29,8 +30,6 @@ export const useLocationStore = defineStore("locations", { return state.parents ?? []; }, allLocations(state): LocationOutCount[] { - // ensures that locations are eventually available but not synchronously - state.ensureLocationsFetched(); return state.Locations ?? []; }, }, @@ -40,8 +39,8 @@ export const useLocationStore = defineStore("locations", { return; } - if (this.refreshLocationsPromise === undefined) { - this.refreshLocationsPromise = this.refreshChildren(); + if (this.refreshLocationsPromise === null) { + this.refreshLocationsPromise = this.refreshChildren().then(() => {}); } await this.refreshLocationsPromise; },