fix: Recipe Search Quirks and Session Storage (#3541)

Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
Michael Genson 2024-05-06 10:01:56 -05:00 committed by GitHub
parent 770630bf73
commit 418a8ec72b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 113 additions and 55 deletions

View File

@ -143,7 +143,7 @@ import { watchDebounced } from "@vueuse/shared";
import SearchFilter from "~/components/Domain/SearchFilter.vue";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import { useCategoryStore, useFoodStore, useTagStore, useToolStore } from "~/composables/store";
import { useUserSortPreferences } from "~/composables/use-users/preferences";
import { useUserSearchQuerySession } from "~/composables/use-users/preferences";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
import { IngredientFood, RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
import { NoUndefinedField } from "~/lib/api/types/non-generated";
@ -177,7 +177,7 @@ export default defineComponent({
const route = useRoute();
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
const preferences = useUserSortPreferences();
const searchQuerySession = useUserSearchQuerySession();
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
const categories = isOwnGroup.value ? useCategoryStore() : usePublicCategoryStore(groupSlug.value);
@ -194,7 +194,9 @@ export default defineComponent({
function calcPassedQuery(): RecipeSearchQuery {
return {
search: state.value.search,
// the search clear button sets search to null, which still renders the query param for a moment,
// whereas an empty string is not rendered
search: state.value.search ? state.value.search : "",
categories: toIDArray(selectedCategories.value),
foods: toIDArray(selectedFoods.value),
tags: toIDArray(selectedTags.value),
@ -217,14 +219,24 @@ export default defineComponent({
};
})
const queryDefaults = {
search: "",
orderBy: "created_at",
orderDirection: "desc" as "asc" | "desc",
requireAllCategories: false,
requireAllTags: false,
requireAllTools: false,
requireAllFoods: false,
}
function reset() {
state.value.search = "";
state.value.orderBy = "created_at";
state.value.orderDirection = "desc";
state.value.requireAllCategories = false;
state.value.requireAllTags = false;
state.value.requireAllTools = false;
state.value.requireAllFoods = false;
state.value.search = queryDefaults.search;
state.value.orderBy = queryDefaults.orderBy;
state.value.orderDirection = queryDefaults.orderDirection;
state.value.requireAllCategories = queryDefaults.requireAllCategories;
state.value.requireAllTags = queryDefaults.requireAllTags;
state.value.requireAllTools = queryDefaults.requireAllTools;
state.value.requireAllFoods = queryDefaults.requireAllFoods;
selectedCategories.value = [];
selectedFoods.value = [];
selectedTags.value = [];
@ -262,12 +274,12 @@ export default defineComponent({
foods: passedQuery.value.foods,
tags: passedQuery.value.tags,
tools: passedQuery.value.tools,
// Only add the query param if it's or not default
// Only add the query param if it's not the default value
...{
auto: state.value.auto ? undefined : "false",
search: passedQuery.value.search === "" ? undefined : passedQuery.value.search,
orderBy: passedQuery.value.orderBy === "created_at" ? undefined : passedQuery.value.orderBy,
orderDirection: passedQuery.value.orderDirection === "desc" ? undefined : passedQuery.value.orderDirection,
search: passedQuery.value.search === queryDefaults.search ? undefined : passedQuery.value.search,
orderBy: passedQuery.value.orderBy === queryDefaults.orderBy ? undefined : passedQuery.value.orderBy,
orderDirection: passedQuery.value.orderDirection === queryDefaults.orderDirection ? undefined : passedQuery.value.orderDirection,
requireAllCategories: passedQuery.value.requireAllCategories ? "true" : undefined,
requireAllTags: passedQuery.value.requireAllTags ? "true" : undefined,
requireAllTools: passedQuery.value.requireAllTools ? "true" : undefined,
@ -275,7 +287,7 @@ export default defineComponent({
},
}
await router.push({ query });
preferences.value.searchQuery = JSON.stringify(query);
searchQuerySession.value.recipe = JSON.stringify(query);
}
function waitUntilAndExecute(
@ -360,25 +372,55 @@ export default defineComponent({
async function hydrateSearch() {
const query = router.currentRoute.query;
if (query.auto) {
if (query.auto?.length) {
state.value.auto = query.auto === "true";
}
if (query.search) {
if (query.search?.length) {
state.value.search = query.search as string;
} else {
state.value.search = queryDefaults.search;
}
if (query.orderBy) {
if (query.orderBy?.length) {
state.value.orderBy = query.orderBy as string;
} else {
state.value.orderBy = queryDefaults.orderBy;
}
if (query.orderDirection) {
if (query.orderDirection?.length) {
state.value.orderDirection = query.orderDirection as "asc" | "desc";
} else {
state.value.orderDirection = queryDefaults.orderDirection;
}
if (query.requireAllCategories?.length) {
state.value.requireAllCategories = query.requireAllCategories === "true";
} else {
state.value.requireAllCategories = queryDefaults.requireAllCategories;
}
if (query.requireAllTags?.length) {
state.value.requireAllTags = query.requireAllTags === "true";
} else {
state.value.requireAllTags = queryDefaults.requireAllTags;
}
if (query.requireAllTools?.length) {
state.value.requireAllTools = query.requireAllTools === "true";
} else {
state.value.requireAllTools = queryDefaults.requireAllTools;
}
if (query.requireAllFoods?.length) {
state.value.requireAllFoods = query.requireAllFoods === "true";
} else {
state.value.requireAllFoods = queryDefaults.requireAllFoods;
}
const promises: Promise<void>[] = [];
if (query.categories) {
if (query.categories?.length) {
promises.push(
waitUntilAndExecute(
() => categories.items.value.length > 0,
@ -395,7 +437,35 @@ export default defineComponent({
selectedCategories.value = [];
}
if (query.foods) {
if (query.tags?.length) {
promises.push(
waitUntilAndExecute(
() => tags.items.value.length > 0,
() => {
const result = tags.items.value.filter((item) => (query.tags as string[]).includes(item.id as string));
selectedTags.value = result as NoUndefinedField<RecipeTag>[];
}
)
);
} else {
selectedTags.value = [];
}
if (query.tools?.length) {
promises.push(
waitUntilAndExecute(
() => tools.items.value.length > 0,
() => {
const result = tools.items.value.filter((item) => (query.tools as string[]).includes(item.id));
selectedTools.value = result as NoUndefinedField<RecipeTool>[];
}
)
);
} else {
selectedTools.value = [];
}
if (query.foods?.length) {
promises.push(
waitUntilAndExecute(
() => {
@ -414,45 +484,17 @@ export default defineComponent({
selectedFoods.value = [];
}
if (query.tags) {
promises.push(
waitUntilAndExecute(
() => tags.items.value.length > 0,
() => {
const result = tags.items.value.filter((item) => (query.tags as string[]).includes(item.id as string));
selectedTags.value = result as NoUndefinedField<RecipeTag>[];
}
)
);
} else {
selectedTags.value = [];
}
if (query.tools) {
promises.push(
waitUntilAndExecute(
() => tools.items.value.length > 0,
() => {
const result = tools.items.value.filter((item) => (query.tools as string[]).includes(item.id));
selectedTools.value = result as NoUndefinedField<RecipeTool>[];
}
)
);
} else {
selectedTools.value = [];
}
await Promise.allSettled(promises);
};
onMounted(async () => {
// restore the user's last search query
if (preferences.value.searchQuery && !(Object.keys(route.value.query).length > 0)) {
if (searchQuerySession.value.recipe && !(Object.keys(route.value.query).length > 0)) {
try {
const query = JSON.parse(preferences.value.searchQuery);
const query = JSON.parse(searchQuerySession.value.recipe);
await router.replace({ query });
} catch (error) {
preferences.value.searchQuery = "";
searchQuerySession.value.recipe = "";
router.replace({ query: {} });
}
}

View File

@ -1,5 +1,5 @@
import { Ref, useContext } from "@nuxtjs/composition-api";
import { useLocalStorage } from "@vueuse/core";
import { useLocalStorage, useSessionStorage } from "@vueuse/core";
import { TimelineEventType } from "~/lib/api/types/recipe";
export interface UserPrintPreferences {
@ -8,6 +8,10 @@ export interface UserPrintPreferences {
showNotes: boolean;
}
export interface UserSearchQuery {
recipe: string;
}
export enum ImagePosition {
hidden = "hidden",
left = "left",
@ -20,7 +24,6 @@ export interface UserRecipePreferences {
filterNull: boolean;
sortIcon: string;
useMobileCards: boolean;
searchQuery: string;
}
export interface UserShoppingListPreferences {
@ -60,7 +63,6 @@ export function useUserSortPreferences(): Ref<UserRecipePreferences> {
filterNull: false,
sortIcon: $globals.icons.sortAlphabeticalAscending,
useMobileCards: false,
searchQuery: "",
},
{ mergeDefaults: true }
// we cast to a Ref because by default it will return an optional type ref
@ -70,6 +72,20 @@ export function useUserSortPreferences(): Ref<UserRecipePreferences> {
return fromStorage;
}
export function useUserSearchQuerySession(): Ref<UserSearchQuery> {
const fromStorage = useSessionStorage(
"search-query",
{
recipe: "",
},
{ mergeDefaults: true }
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref<UserSearchQuery>;
return fromStorage;
}
export function useShoppingListPreferences(): Ref<UserShoppingListPreferences> {
const fromStorage = useLocalStorage(