1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-07-24 07:39:41 +02:00

feat: Migrate to Nuxt 3 framework (#5184)

Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
Hoa (Kyle) Trinh 2025-06-20 00:09:12 +07:00 committed by GitHub
parent 89ab7fac25
commit c24d532608
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
403 changed files with 23959 additions and 19557 deletions

View file

@ -1,11 +1,18 @@
<template>
<div v-if="dialog">
<BaseDialog v-if="shoppingListDialog && ready" v-model="dialog" :title="$t('recipe.add-to-list')" :icon="$globals.icons.cartCheck">
<v-container v-if="!shoppingListChoices.length">
<BasePageTitle>
<template #title>{{ $t('shopping-list.no-shopping-lists-found') }}</template>
</BasePageTitle>
</v-container>
<BaseDialog
v-if="shoppingListDialog && ready"
v-model="dialog"
:title="$t('recipe.add-to-list')"
:icon="$globals.icons.cartCheck"
>
<v-container v-if="!shoppingListChoices.length">
<BasePageTitle>
<template #title>
{{ $t('shopping-list.no-shopping-lists-found') }}
</template>
</BasePageTitle>
</v-container>
<v-card-text>
<v-card
v-for="list in shoppingListChoices"
@ -21,14 +28,23 @@
</v-card-text>
<template #card-actions>
<v-btn
text
variant="text"
color="grey"
@click="dialog = false"
>
{{ $t("general.cancel") }}
</v-btn>
<div class="d-flex justify-end" style="width: 100%;">
<v-checkbox v-model="preferences.viewAllLists" hide-details :label="$tc('general.show-all')" class="my-auto mr-4" @click="setShowAllToggled()" />
<div
class="d-flex justify-end"
style="width: 100%;"
>
<v-checkbox
v-model="preferences.viewAllLists"
hide-details
:label="$t('general.show-all')"
class="my-auto mr-4"
@click="setShowAllToggled()"
/>
</div>
</template>
</BaseDialog>
@ -38,32 +54,52 @@
:title="selectedShoppingList ? selectedShoppingList.name : $t('recipe.add-to-list')"
:icon="$globals.icons.cartCheck"
width="70%"
:submit-text="$tc('recipe.add-to-list')"
:submit-text="$t('recipe.add-to-list')"
can-submit
@submit="addRecipesToList()"
>
<div style="max-height: 70vh; overflow-y: auto">
<v-card
v-for="(recipeSection, recipeSectionIndex) in recipeIngredientSections" :key="recipeSection.recipeId + recipeSectionIndex"
v-for="(recipeSection, recipeSectionIndex) in recipeIngredientSections"
:key="recipeSection.recipeId + recipeSectionIndex"
elevation="0"
height="fit-content"
width="100%"
>
<v-divider v-if="recipeSectionIndex > 0" class="mt-3" />
<v-divider
v-if="recipeSectionIndex > 0"
class="mt-3"
/>
<v-card-title
v-if="recipeIngredientSections.length > 1"
class="justify-center text-h5"
width="100%"
>
<v-container style="width: 100%;">
<v-row no-gutters class="ma-0 pa-0">
<v-col cols="12" align-self="center" class="text-center">
<v-row
no-gutters
class="ma-0 pa-0"
>
<v-col
cols="12"
align-self="center"
class="text-center"
>
{{ recipeSection.recipeName }}
</v-col>
</v-row>
<v-row v-if="recipeSection.recipeScale > 1" no-gutters class="ma-0 pa-0">
<v-row
v-if="recipeSection.recipeScale > 1"
no-gutters
class="ma-0 pa-0"
>
<!-- TODO: make this editable in the dialog and visible on single-recipe lists -->
<v-col cols="12" align-self="center" class="text-center">
({{ $tc("recipe.quantity") }}: {{ recipeSection.recipeScale }})
<v-col
cols="12"
align-self="center"
class="text-center"
>
({{ $t("recipe.quantity") }}: {{ recipeSection.recipeScale }})
</v-col>
</v-row>
</v-container>
@ -73,36 +109,41 @@
v-for="(ingredientSection, ingredientSectionIndex) in recipeSection.ingredientSections"
:key="recipeSection.recipeId + recipeSectionIndex + ingredientSectionIndex"
>
<v-card-title v-if="ingredientSection.sectionName" class="ingredient-title mt-2 pb-0 text-h6">
<v-card-title
v-if="ingredientSection.sectionName"
class="ingredient-title mt-2 pb-0 text-h6"
>
{{ ingredientSection.sectionName }}
</v-card-title>
<div
:class="$vuetify.breakpoint.smAndDown ? '' : 'ingredient-grid'"
:style="$vuetify.breakpoint.smAndDown ? '' : { gridTemplateRows: `repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)` }"
:class="$vuetify.display.smAndDown ? '' : 'ingredient-grid'"
:style="$vuetify.display.smAndDown ? '' : { gridTemplateRows: `repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)` }"
>
<v-list-item
v-for="(ingredientData, i) in ingredientSection.ingredients"
:key="recipeSection.recipeId + recipeSectionIndex + ingredientSectionIndex + i"
dense
density="compact"
@click="recipeIngredientSections[recipeSectionIndex]
.ingredientSections[ingredientSectionIndex]
.ingredients[i].checked = !recipeIngredientSections[recipeSectionIndex]
.ingredientSections[ingredientSectionIndex]
.ingredients[i]
.checked"
.ingredientSections[ingredientSectionIndex]
.ingredients[i]
.checked"
>
<v-checkbox
hide-details
:input-value="ingredientData.checked"
:model-value="ingredientData.checked"
class="pt-0 my-auto py-auto"
color="secondary"
density="compact"
/>
<v-list-item-content :key="ingredientData.ingredient.quantity">
<div :key="ingredientData.ingredient.quantity">
<RecipeIngredientListItem
:ingredient="ingredientData.ingredient"
:disable-amount="ingredientData.disableAmount"
:scale="recipeSection.recipeScale" />
</v-list-item-content>
:scale="recipeSection.recipeScale"
/>
</div>
</v-list-item>
</div>
</div>
@ -114,12 +155,12 @@
:buttons="[
{
icon: $globals.icons.checkboxBlankOutline,
text: $tc('shopping-list.uncheck-all-items'),
text: $t('shopping-list.uncheck-all-items'),
event: 'uncheck',
},
{
icon: $globals.icons.checkboxOutline,
text: $tc('shopping-list.check-all-items'),
text: $t('shopping-list.check-all-items'),
event: 'check',
},
]"
@ -132,14 +173,13 @@
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref, useContext, watchEffect } from "@nuxtjs/composition-api";
import { toRefs } from "@vueuse/core";
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { useShoppingListPreferences } from "~/composables/use-users/preferences";
import { RecipeIngredient, ShoppingListAddRecipeParamsBulk, ShoppingListSummary } from "~/lib/api/types/household";
import { Recipe } from "~/lib/api/types/recipe";
import type { RecipeIngredient, ShoppingListAddRecipeParamsBulk, ShoppingListSummary } from "~/lib/api/types/household";
import type { Recipe } from "~/lib/api/types/recipe";
export interface RecipeWithScale extends Recipe {
scale: number;
@ -163,12 +203,12 @@ export interface ShoppingListRecipeIngredientSection {
ingredientSections: ShoppingListIngredientSection[];
}
export default defineComponent({
export default defineNuxtComponent({
components: {
RecipeIngredientListItem,
},
props: {
value: {
modelValue: {
type: Boolean,
default: false,
},
@ -181,8 +221,10 @@ export default defineComponent({
default: () => [],
},
},
emits: ["update:modelValue"],
setup(props, context) {
const { $auth, i18n } = useContext();
const i18n = useI18n();
const $auth = useMealieAuth();
const api = useUserApi();
const preferences = useShoppingListPreferences();
const ready = ref(false);
@ -190,10 +232,10 @@ export default defineComponent({
// v-model support
const dialog = computed({
get: () => {
return props.value;
return props.modelValue;
},
set: (val) => {
context.emit("input", val);
context.emit("update:modelValue", val);
initState();
},
});
@ -205,11 +247,11 @@ export default defineComponent({
});
const userHousehold = computed(() => {
return $auth.user?.householdSlug || "";
return $auth.user.value?.householdSlug || "";
});
const shoppingListChoices = computed(() => {
return props.shoppingLists.filter((list) => preferences.value.viewAllLists || list.userId === $auth.user?.id);
return props.shoppingLists.filter(list => preferences.value.viewAllLists || list.userId === $auth.user.value?.id);
});
const recipeIngredientSections = ref<ShoppingListRecipeIngredientSection[]>([]);
@ -220,7 +262,8 @@ export default defineComponent({
if (shoppingListChoices.value.length === 1 && !state.shoppingListShowAllToggled) {
selectedShoppingList.value = shoppingListChoices.value[0];
openShoppingListIngredientDialog(selectedShoppingList.value);
} else {
}
else {
ready.value = true;
}
},
@ -234,7 +277,6 @@ export default defineComponent({
}
if (recipeSectionMap.has(recipe.slug)) {
// @ts-ignore not undefined, see above
recipeSectionMap.get(recipe.slug).recipeScale += recipe.scale;
continue;
}
@ -247,7 +289,8 @@ export default defineComponent({
recipe.id = data.id || "";
recipe.name = data.name || "";
recipe.recipeIngredient = data.recipeIngredient;
} else if (!recipe.recipeIngredient.length) {
}
else if (!recipe.recipeIngredient.length) {
continue;
}
@ -257,7 +300,7 @@ export default defineComponent({
checked: !householdsWithFood.includes(userHousehold.value),
ingredient: ing,
disableAmount: recipe.settings?.disableAmount || false,
}
};
});
let currentTitle = "";
@ -300,7 +343,7 @@ export default defineComponent({
recipeName: recipe.name,
recipeScale: recipe.scale,
ingredientSections: shoppingListIngredientSections,
})
});
}
recipeIngredientSections.value = Array.from(recipeSectionMap.values());
@ -366,13 +409,13 @@ export default defineComponent({
recipeId: section.recipeId,
recipeIncrementQuantity: section.recipeScale,
recipeIngredients: ingredients,
}
},
);
});
const { error } = await api.shopping.lists.addRecipes(selectedShoppingList.value.id, recipeData);
error ? alert.error(i18n.tc("recipe.failed-to-add-recipes-to-list"))
: alert.success(i18n.tc("recipe.successfully-added-to-list"));
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
error ? alert.error(i18n.t("recipe.failed-to-add-recipes-to-list")) : alert.success(i18n.t("recipe.successfully-added-to-list"));
state.shoppingListDialog = false;
state.shoppingListIngredientDialog = false;
@ -391,9 +434,9 @@ export default defineComponent({
setShowAllToggled,
recipeIngredientSections,
selectedShoppingList,
}
};
},
})
});
</script>
<style scoped lang="css">