Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Structured Yields #4489

Merged
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
9e2bd29
added column for recipe yield qty
michael-genson Oct 23, 2024
089b3fb
Merge remote-tracking branch 'upstream/mealie-next' into feat/structu…
michael-genson Oct 31, 2024
6a43774
updated yield cleaning on recipe import
michael-genson Oct 31, 2024
bee731c
parse all recipe yields on migration
michael-genson Oct 31, 2024
14d61b2
added yield qty to schema
michael-genson Oct 31, 2024
9df445b
dev:generate
michael-genson Oct 31, 2024
b9c6ab6
updated data tables
michael-genson Oct 31, 2024
f5e3ffc
updated spa meta
michael-genson Oct 31, 2024
08d9891
updated tandoor migration
michael-genson Oct 31, 2024
26b4d29
Added new yield composable
michael-genson Oct 31, 2024
da160f5
replaced print view yield
michael-genson Oct 31, 2024
9942b7b
replace print view yield
michael-genson Oct 31, 2024
4e3eca6
round output
michael-genson Oct 31, 2024
f976571
simplify prop
michael-genson Oct 31, 2024
09a1b86
replace scale editor logic
michael-genson Oct 31, 2024
1fee77c
add yield qty to recipe editor
michael-genson Oct 31, 2024
8eaddba
fix edge cases when yield is 0 or invalid
michael-genson Oct 31, 2024
4025134
fix sanitization
michael-genson Nov 1, 2024
f390ed8
add frontend tests
michael-genson Nov 1, 2024
1ca4b2c
remove unused yield extract
michael-genson Nov 1, 2024
ae614ca
catch zero div errors
michael-genson Nov 1, 2024
7d011a2
fix mixed vulgar fractions
michael-genson Nov 1, 2024
8f45ca8
add/update yield cleaner tests
michael-genson Nov 1, 2024
a7c43f6
fix brute parser
michael-genson Nov 1, 2024
791a2c8
lint
michael-genson Nov 1, 2024
9c90736
removed old yield set
michael-genson Nov 1, 2024
5db9922
restore brute force parser code because I broke it
michael-genson Nov 1, 2024
38d0e8d
Merge branch 'mealie-next' into feat/structured-yields
michael-genson Nov 1, 2024
5279e3a
added recipe servings to recipe model
michael-genson Nov 8, 2024
b265eac
updated cleaner
michael-genson Nov 8, 2024
4f58443
updated tests
michael-genson Nov 8, 2024
a3e863c
added servings to models
michael-genson Nov 8, 2024
7b025cb
updated data tables
michael-genson Nov 8, 2024
6e475b8
added servings to editor
michael-genson Nov 8, 2024
69b0d9a
added servings to recipe page
michael-genson Nov 8, 2024
252352e
changed scale to use servings rather than yield
michael-genson Nov 8, 2024
4bd4d85
refactor yield to generic amount
michael-genson Nov 8, 2024
a4d99ad
fix tests
michael-genson Nov 8, 2024
9c86bb2
disable scale if empty
michael-genson Nov 8, 2024
9ccb44c
sanitize yield html
michael-genson Nov 8, 2024
fc174e4
sanitize yield html
michael-genson Nov 8, 2024
7ed026b
oops, that wasn't supposed to be there
michael-genson Nov 8, 2024
ad1e87b
Merge branch 'feat/structured-yields' of https://github.com/michael-g…
michael-genson Nov 8, 2024
7594e92
Merge remote-tracking branch 'upstream/mealie-next' into feat/structu…
michael-genson Nov 13, 2024
1c56e0f
refactor recipe page parts/landscape logic
michael-genson Nov 16, 2024
8699a74
shuffle around info and move yield to info
michael-genson Nov 16, 2024
9defb0e
fallback to yield qty or 1 when scaling
michael-genson Nov 16, 2024
e144239
allow 0 yield qty
michael-genson Nov 16, 2024
1cb240b
fix landscape mode
michael-genson Nov 16, 2024
35e3dfe
remove rating from scale
michael-genson Nov 16, 2024
298c738
lint
michael-genson Nov 16, 2024
179294a
more lint
michael-genson Nov 16, 2024
b30dffb
Merge branch 'mealie-next' into feat/structured-yields
michael-genson Nov 16, 2024
33c8efe
restored image and other info to edit page
michael-genson Nov 18, 2024
5827485
adjust recipe timer layout
michael-genson Nov 18, 2024
a126001
fixed missing recipe prop
michael-genson Nov 18, 2024
4987932
restore scale for yield
michael-genson Nov 18, 2024
a927d51
added edit icon to servings
michael-genson Nov 18, 2024
633a79f
match print view to info card
michael-genson Nov 18, 2024
9e93e10
lint
michael-genson Nov 18, 2024
b91b1ce
Merge branch 'mealie-next' into feat/structured-yields
michael-genson Nov 18, 2024
1069fae
Merge branch 'mealie-next' into feat/structured-yields
Kuchenpirat Nov 20, 2024
1670e71
Update frontend/components/Domain/Recipe/RecipeScaleEditButton.vue
michael-genson Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import sqlalchemy as sa

import mealie.db.migration_types
from alembic import op

# revision identifiers, used by Alembic.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""add recipe yield quantity

Revision ID: b1020f328e98
Revises: 3897397b4631
Create Date: 2024-10-23 15:50:59.888793

"""

import sqlalchemy as sa
from sqlalchemy import orm

from alembic import op
from mealie.db.models._model_utils.guid import GUID
from mealie.services.scraper.cleaner import clean_yield

# revision identifiers, used by Alembic.
revision = "b1020f328e98"
down_revision: str | None = "3897397b4631"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None


# Intermediate table definitions
class SqlAlchemyBase(orm.DeclarativeBase):
pass


class RecipeModel(SqlAlchemyBase):
__tablename__ = "recipes"

id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
recipe_yield: orm.Mapped[str | None] = orm.mapped_column(sa.String)
recipe_yield_quantity: orm.Mapped[float] = orm.mapped_column(sa.Float, index=True, default=0)
recipe_servings: orm.Mapped[float] = orm.mapped_column(sa.Float, index=True, default=0)


def parse_recipe_yields():
bind = op.get_bind()
session = orm.Session(bind=bind)

for recipe in session.query(RecipeModel).all():
try:
recipe.recipe_servings, recipe.recipe_yield_quantity, recipe.recipe_yield = clean_yield(recipe.recipe_yield)
except Exception:
recipe.recipe_servings = 0
recipe.recipe_yield_quantity = 0

session.commit()


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("recipes", schema=None) as batch_op:
batch_op.add_column(sa.Column("recipe_yield_quantity", sa.Float(), nullable=False, server_default="0"))
batch_op.create_index(batch_op.f("ix_recipes_recipe_yield_quantity"), ["recipe_yield_quantity"], unique=False)
batch_op.add_column(sa.Column("recipe_servings", sa.Float(), nullable=False, server_default="0"))
batch_op.create_index(batch_op.f("ix_recipes_recipe_servings"), ["recipe_servings"], unique=False)

# ### end Alembic commands ###

parse_recipe_yields()


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("recipes", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_recipes_recipe_servings"))
batch_op.drop_column("recipe_servings")
batch_op.drop_index(batch_op.f("ix_recipes_recipe_yield_quantity"))
batch_op.drop_column("recipe_yield_quantity")

# ### end Alembic commands ###
12 changes: 11 additions & 1 deletion frontend/components/Domain/Recipe/RecipeDataTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ interface ShowHeaders {
tags: boolean;
categories: boolean;
tools: boolean;
recipeServings: boolean;
recipeYieldQuantity: boolean;
recipeYield: boolean;
dateAdded: boolean;
}
Expand Down Expand Up @@ -93,6 +95,8 @@ export default defineComponent({
owner: false,
tags: true,
categories: true,
recipeServings: true,
recipeYieldQuantity: true,
recipeYield: true,
dateAdded: true,
};
Expand Down Expand Up @@ -127,8 +131,14 @@ export default defineComponent({
if (props.showHeaders.tools) {
hdrs.push({ text: i18n.t("tool.tools"), value: "tools" });
}
if (props.showHeaders.recipeServings) {
hdrs.push({ text: i18n.t("recipe.servings"), value: "recipeServings" });
}
if (props.showHeaders.recipeYieldQuantity) {
hdrs.push({ text: i18n.t("recipe.yield"), value: "recipeYieldQuantity" });
}
if (props.showHeaders.recipeYield) {
hdrs.push({ text: i18n.t("recipe.yield"), value: "recipeYield" });
hdrs.push({ text: i18n.t("recipe.yield-text"), value: "recipeYield" });
}
if (props.showHeaders.dateAdded) {
hdrs.push({ text: i18n.t("general.date-added"), value: "dateAdded" });
Expand Down
12 changes: 6 additions & 6 deletions frontend/components/Domain/Recipe/RecipeLastMade.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,6 @@
</BaseDialog>
</div>
<div>
<div class="d-flex justify-center flex-wrap">
<BaseButton :small="$vuetify.breakpoint.smAndDown" @click="madeThisDialog = true">
<template #icon> {{ $globals.icons.chefHat }} </template>
{{ $t('recipe.made-this') }}
</BaseButton>
</div>
<div class="d-flex justify-center flex-wrap">
<v-chip
label
Expand All @@ -105,6 +99,12 @@
{{ $t('recipe.last-made-date', { date: value ? new Date(value).toLocaleDateString($i18n.locale) : $t("general.never") } ) }}
</v-chip>
</div>
<div class="d-flex justify-center flex-wrap mt-1">
<BaseButton :small="$vuetify.breakpoint.smAndDown" @click="madeThisDialog = true">
<template #icon> {{ $globals.icons.chefHat }} </template>
{{ $t('recipe.made-this') }}
</BaseButton>
</div>
</div>
</div>
</template>
Expand Down
16 changes: 8 additions & 8 deletions frontend/components/Domain/Recipe/RecipePage/RecipePage.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<v-container v-show="!isCookMode" key="recipe-page" :class="{ 'pa-0': $vuetify.breakpoint.smAndDown }">
<v-card :flat="$vuetify.breakpoint.smAndDown" class="d-print-none">
<v-card :flat="$vuetify.breakpoint.smAndDown" class="d-print-none">
<RecipePageHeader
:recipe="recipe"
:recipe-scale="scale"
Expand All @@ -22,9 +22,9 @@
data management and mutation system we're using.
-->
<RecipePageEditorToolbar v-if="isEditForm" :recipe="recipe" />
<RecipePageTitleContent :recipe="recipe" :landscape="landscape" />
<RecipePageInfoEditor :recipe="recipe" :landscape="landscape" />
<RecipePageIngredientEditor v-if="isEditForm" :recipe="recipe" />
<RecipePageScale :recipe="recipe" :scale.sync="scale" :landscape="landscape" />
<RecipePageScale :recipe="recipe" :scale.sync="scale" />

<!--
This section contains the 2 column layout for the recipe steps and other content.
Expand Down Expand Up @@ -76,7 +76,7 @@
<v-row style="height: 100%;" no-gutters class="overflow-hidden">
<v-col cols="12" sm="5" class="overflow-y-auto pl-4 pr-3 py-2" style="height: 100%;">
<div class="d-flex align-center">
<RecipePageScale :recipe="recipe" :scale.sync="scale" :landscape="landscape" />
<RecipePageScale :recipe="recipe" :scale.sync="scale" />
</div>
<RecipePageIngredientToolsView v-if="!isEditForm" :recipe="recipe" :scale="scale" :is-cook-mode="isCookMode" />
<v-divider></v-divider>
Expand All @@ -95,7 +95,7 @@
</v-sheet>
<v-sheet v-show="isCookMode && hasLinkedIngredients">
<div class="mt-2 px-2 px-md-4">
<RecipePageScale :recipe="recipe" :scale.sync="scale" :landscape="landscape"/>
<RecipePageScale :recipe="recipe" :scale.sync="scale"/>
</div>
<RecipePageInstructions
v-model="recipe.recipeInstructions"
Expand Down Expand Up @@ -154,7 +154,7 @@ import RecipePageIngredientToolsView from "./RecipePageParts/RecipePageIngredien
import RecipePageInstructions from "./RecipePageParts/RecipePageInstructions.vue";
import RecipePageOrganizers from "./RecipePageParts/RecipePageOrganizers.vue";
import RecipePageScale from "./RecipePageParts/RecipePageScale.vue";
import RecipePageTitleContent from "./RecipePageParts/RecipePageTitleContent.vue";
import RecipePageInfoEditor from "./RecipePageParts/RecipePageInfoEditor.vue";
import RecipePageComments from "./RecipePageParts/RecipePageComments.vue";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import RecipePrintContainer from "~/components/Domain/Recipe/RecipePrintContainer.vue";
Expand Down Expand Up @@ -185,7 +185,7 @@ export default defineComponent({
RecipePageHeader,
RecipePrintContainer,
RecipePageComments,
RecipePageTitleContent,
RecipePageInfoEditor,
RecipePageEditorToolbar,
RecipePageIngredientEditor,
RecipePageOrganizers,
Expand All @@ -195,7 +195,7 @@ export default defineComponent({
RecipeNotes,
RecipePageInstructions,
RecipePageFooter,
RecipeIngredients
RecipeIngredients,
},
props: {
recipe: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,12 @@
<template>
<div>
<div class="d-flex justify-end flex-wrap align-stretch">
<v-card v-if="!landscape" width="50%" flat class="d-flex flex-column justify-center align-center">
<v-card-text>
<v-card-title class="headline pa-0 flex-column align-center">
{{ recipe.name }}
<RecipeRating :key="recipe.slug" :value="recipe.rating" :recipe-id="recipe.id" :slug="recipe.slug" />
</v-card-title>
<v-divider class="my-2"></v-divider>
<SafeMarkdown :source="recipe.description" />
<v-divider></v-divider>
<div v-if="isOwnGroup" class="d-flex justify-center mt-5">
<RecipeLastMade
v-model="recipe.lastMade"
:recipe="recipe"
class="d-flex justify-center flex-wrap"
:class="true ? undefined : 'force-bottom'"
/>
</div>
<div class="d-flex justify-center mt-5">
<RecipeTimeCard
class="d-flex justify-center flex-wrap"
:class="true ? undefined : 'force-bottom'"
:prep-time="recipe.prepTime"
:total-time="recipe.totalTime"
:perform-time="recipe.performTime"
/>
</div>
</v-card-text>
</v-card>
<v-img
:key="imageKey"
:max-width="landscape ? null : '50%'"
min-height="50"
:height="hideImage ? undefined : imageHeight"
:src="recipeImageUrl"
class="d-print-none"
@error="hideImage = true"
>
</v-img>
</div>
<v-divider></v-divider>
<RecipePageInfoCard v-if="!isEditMode" :recipe="recipe" :landscape="landscape" />
<v-container v-else width="100%">
<v-card-title class="headline d-flex justify-center align-center pt-0 pb-12 mb-4">
{{ $t("recipe.editing-recipe", [recipe.name]) }}
</v-card-title>
</v-container>
michael-genson marked this conversation as resolved.
Show resolved Hide resolved
<v-divider />
<RecipeActionMenu
:recipe="recipe"
:slug="recipe.slug"
Expand All @@ -65,21 +31,17 @@
import { defineComponent, useContext, computed, ref, watch } from "@nuxtjs/composition-api";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import { useRecipePermissions } from "~/composables/recipes";
import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
import RecipeLastMade from "~/components/Domain/Recipe/RecipeLastMade.vue";
import RecipePageInfoCard from "~/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageInfoCard.vue";
import RecipeActionMenu from "~/components/Domain/Recipe/RecipeActionMenu.vue";
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
import { useStaticRoutes, useUserApi } from "~/composables/api";
import { HouseholdSummary } from "~/lib/api/types/household";
import { Recipe } from "~/lib/api/types/recipe";
import { NoUndefinedField } from "~/lib/api/types/non-generated";
import { usePageState, usePageUser, PageMode, EditorMode } from "~/composables/recipe-page/shared-state";
export default defineComponent({
components: {
RecipeTimeCard,
RecipePageInfoCard,
RecipeActionMenu,
RecipeRating,
RecipeLastMade,
},
props: {
recipe: {
Expand Down
michael-genson marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<template>
<div>
<div class="d-flex justify-end flex-wrap align-stretch">
<RecipePageInfoCardImage v-if="landscape" :recipe="recipe" />
<v-card
:width="landscape ? '100%' : '50%'"
flat
class="d-flex flex-column justify-center align-center"
>
<v-card-text>
<v-card-title class="headline pa-0 flex-column align-center">
{{ recipe.name }}
<RecipeRating :key="recipe.slug" :value="recipe.rating" :recipe-id="recipe.id" :slug="recipe.slug" />
</v-card-title>
<v-divider class="my-2" />
<SafeMarkdown :source="recipe.description" />
<v-divider />
<v-container class="d-flex flex-row flex-wrap justify-center align-center">
<div>
<v-row no-gutters class="mb-1">
<v-col v-if="recipe.recipeYieldQuantity || recipe.recipeYield" cols="12" class="d-flex flex-wrap justify-center">
<RecipeYield
:yield-quantity="recipe.recipeYieldQuantity"
:yield="recipe.recipeYield"
/>
michael-genson marked this conversation as resolved.
Show resolved Hide resolved
</v-col>
</v-row>
<v-row no-gutters>
<v-col cols="12" class="d-flex flex-wrap justify-center">
<RecipeLastMade
v-if="isOwnGroup"
:class="true ? undefined : 'force-bottom'"
:value="recipe.lastMade"
/>
</v-col>
michael-genson marked this conversation as resolved.
Show resolved Hide resolved
</v-row>
</div>
<div>
<RecipeTimeCard
stacked
container-class="d-flex flex-wrap justify-end"
:prep-time="recipe.prepTime"
:total-time="recipe.totalTime"
:perform-time="recipe.performTime"
/>
</div>
</v-container>
</v-card-text>
</v-card>
<RecipePageInfoCardImage v-if="!landscape" :recipe="recipe" max-width="50%" class="my-auto" />
</div>
</div>
</template>

<script lang="ts">
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
import RecipeLastMade from "~/components/Domain/Recipe/RecipeLastMade.vue";
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
import RecipeYield from "~/components/Domain/Recipe/RecipeYield.vue";
import RecipePageInfoCardImage from "~/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageInfoCardImage.vue";
import { Recipe } from "~/lib/api/types/recipe";
import { NoUndefinedField } from "~/lib/api/types/non-generated";
export default defineComponent({
components: {
RecipeRating,
RecipeLastMade,
RecipeTimeCard,
RecipeYield,
RecipePageInfoCardImage,
},
props: {
recipe: {
type: Object as () => NoUndefinedField<Recipe>,
required: true,
},
landscape: {
type: Boolean,
required: true,
},
},
setup() {
const { $vuetify } = useContext();
const useMobile = computed(() => $vuetify.breakpoint.smAndDown);

const { isOwnGroup } = useLoggedInState();

return {
isOwnGroup,
useMobile,
};
}
});
</script>
Loading
Loading