mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-31 04:05:33 -04:00
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:
parent
770630bf73
commit
418a8ec72b
@ -143,7 +143,7 @@ import { watchDebounced } from "@vueuse/shared";
|
|||||||
import SearchFilter from "~/components/Domain/SearchFilter.vue";
|
import SearchFilter from "~/components/Domain/SearchFilter.vue";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useCategoryStore, useFoodStore, useTagStore, useToolStore } from "~/composables/store";
|
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 RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
import { IngredientFood, RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
import { IngredientFood, RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
||||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
@ -177,7 +177,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
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 { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
|
||||||
const categories = isOwnGroup.value ? useCategoryStore() : usePublicCategoryStore(groupSlug.value);
|
const categories = isOwnGroup.value ? useCategoryStore() : usePublicCategoryStore(groupSlug.value);
|
||||||
@ -194,7 +194,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
function calcPassedQuery(): RecipeSearchQuery {
|
function calcPassedQuery(): RecipeSearchQuery {
|
||||||
return {
|
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),
|
categories: toIDArray(selectedCategories.value),
|
||||||
foods: toIDArray(selectedFoods.value),
|
foods: toIDArray(selectedFoods.value),
|
||||||
tags: toIDArray(selectedTags.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() {
|
function reset() {
|
||||||
state.value.search = "";
|
state.value.search = queryDefaults.search;
|
||||||
state.value.orderBy = "created_at";
|
state.value.orderBy = queryDefaults.orderBy;
|
||||||
state.value.orderDirection = "desc";
|
state.value.orderDirection = queryDefaults.orderDirection;
|
||||||
state.value.requireAllCategories = false;
|
state.value.requireAllCategories = queryDefaults.requireAllCategories;
|
||||||
state.value.requireAllTags = false;
|
state.value.requireAllTags = queryDefaults.requireAllTags;
|
||||||
state.value.requireAllTools = false;
|
state.value.requireAllTools = queryDefaults.requireAllTools;
|
||||||
state.value.requireAllFoods = false;
|
state.value.requireAllFoods = queryDefaults.requireAllFoods;
|
||||||
selectedCategories.value = [];
|
selectedCategories.value = [];
|
||||||
selectedFoods.value = [];
|
selectedFoods.value = [];
|
||||||
selectedTags.value = [];
|
selectedTags.value = [];
|
||||||
@ -262,12 +274,12 @@ export default defineComponent({
|
|||||||
foods: passedQuery.value.foods,
|
foods: passedQuery.value.foods,
|
||||||
tags: passedQuery.value.tags,
|
tags: passedQuery.value.tags,
|
||||||
tools: passedQuery.value.tools,
|
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",
|
auto: state.value.auto ? undefined : "false",
|
||||||
search: passedQuery.value.search === "" ? undefined : passedQuery.value.search,
|
search: passedQuery.value.search === queryDefaults.search ? undefined : passedQuery.value.search,
|
||||||
orderBy: passedQuery.value.orderBy === "created_at" ? undefined : passedQuery.value.orderBy,
|
orderBy: passedQuery.value.orderBy === queryDefaults.orderBy ? undefined : passedQuery.value.orderBy,
|
||||||
orderDirection: passedQuery.value.orderDirection === "desc" ? undefined : passedQuery.value.orderDirection,
|
orderDirection: passedQuery.value.orderDirection === queryDefaults.orderDirection ? undefined : passedQuery.value.orderDirection,
|
||||||
requireAllCategories: passedQuery.value.requireAllCategories ? "true" : undefined,
|
requireAllCategories: passedQuery.value.requireAllCategories ? "true" : undefined,
|
||||||
requireAllTags: passedQuery.value.requireAllTags ? "true" : undefined,
|
requireAllTags: passedQuery.value.requireAllTags ? "true" : undefined,
|
||||||
requireAllTools: passedQuery.value.requireAllTools ? "true" : undefined,
|
requireAllTools: passedQuery.value.requireAllTools ? "true" : undefined,
|
||||||
@ -275,7 +287,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
await router.push({ query });
|
await router.push({ query });
|
||||||
preferences.value.searchQuery = JSON.stringify(query);
|
searchQuerySession.value.recipe = JSON.stringify(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
function waitUntilAndExecute(
|
function waitUntilAndExecute(
|
||||||
@ -360,25 +372,55 @@ export default defineComponent({
|
|||||||
|
|
||||||
async function hydrateSearch() {
|
async function hydrateSearch() {
|
||||||
const query = router.currentRoute.query;
|
const query = router.currentRoute.query;
|
||||||
if (query.auto) {
|
if (query.auto?.length) {
|
||||||
state.value.auto = query.auto === "true";
|
state.value.auto = query.auto === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.search) {
|
if (query.search?.length) {
|
||||||
state.value.search = query.search as string;
|
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;
|
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";
|
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>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
if (query.categories) {
|
if (query.categories?.length) {
|
||||||
promises.push(
|
promises.push(
|
||||||
waitUntilAndExecute(
|
waitUntilAndExecute(
|
||||||
() => categories.items.value.length > 0,
|
() => categories.items.value.length > 0,
|
||||||
@ -395,7 +437,35 @@ export default defineComponent({
|
|||||||
selectedCategories.value = [];
|
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(
|
promises.push(
|
||||||
waitUntilAndExecute(
|
waitUntilAndExecute(
|
||||||
() => {
|
() => {
|
||||||
@ -414,45 +484,17 @@ export default defineComponent({
|
|||||||
selectedFoods.value = [];
|
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);
|
await Promise.allSettled(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// restore the user's last search query
|
// 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 {
|
try {
|
||||||
const query = JSON.parse(preferences.value.searchQuery);
|
const query = JSON.parse(searchQuerySession.value.recipe);
|
||||||
await router.replace({ query });
|
await router.replace({ query });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
preferences.value.searchQuery = "";
|
searchQuerySession.value.recipe = "";
|
||||||
router.replace({ query: {} });
|
router.replace({ query: {} });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Ref, useContext } from "@nuxtjs/composition-api";
|
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";
|
import { TimelineEventType } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export interface UserPrintPreferences {
|
export interface UserPrintPreferences {
|
||||||
@ -8,6 +8,10 @@ export interface UserPrintPreferences {
|
|||||||
showNotes: boolean;
|
showNotes: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserSearchQuery {
|
||||||
|
recipe: string;
|
||||||
|
}
|
||||||
|
|
||||||
export enum ImagePosition {
|
export enum ImagePosition {
|
||||||
hidden = "hidden",
|
hidden = "hidden",
|
||||||
left = "left",
|
left = "left",
|
||||||
@ -20,7 +24,6 @@ export interface UserRecipePreferences {
|
|||||||
filterNull: boolean;
|
filterNull: boolean;
|
||||||
sortIcon: string;
|
sortIcon: string;
|
||||||
useMobileCards: boolean;
|
useMobileCards: boolean;
|
||||||
searchQuery: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserShoppingListPreferences {
|
export interface UserShoppingListPreferences {
|
||||||
@ -60,7 +63,6 @@ export function useUserSortPreferences(): Ref<UserRecipePreferences> {
|
|||||||
filterNull: false,
|
filterNull: false,
|
||||||
sortIcon: $globals.icons.sortAlphabeticalAscending,
|
sortIcon: $globals.icons.sortAlphabeticalAscending,
|
||||||
useMobileCards: false,
|
useMobileCards: false,
|
||||||
searchQuery: "",
|
|
||||||
},
|
},
|
||||||
{ mergeDefaults: true }
|
{ mergeDefaults: true }
|
||||||
// we cast to a Ref because by default it will return an optional type ref
|
// 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;
|
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> {
|
export function useShoppingListPreferences(): Ref<UserShoppingListPreferences> {
|
||||||
const fromStorage = useLocalStorage(
|
const fromStorage = useLocalStorage(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user