From 4e0cf985bc55ca11b28d2dbfc04613b08a109f0d Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Tue, 3 Dec 2024 07:27:41 -0600 Subject: [PATCH] feat: Recipe Finder (aka Cocktail Builder) (#4542) --- frontend/assets/css/main.css | 8 + .../components/Domain/QueryFilterBuilder.vue | 29 +- .../Domain/Recipe/RecipeSuggestion.vue | 118 ++++ frontend/components/Layout/DefaultLayout.vue | 8 +- frontend/components/global/BaseDialog.vue | 10 +- frontend/composables/use-users/preferences.ts | 33 + frontend/lang/messages/en-US.json | 17 + frontend/lib/api/public/explore/recipes.ts | 8 +- frontend/lib/api/types/recipe.ts | 29 + frontend/lib/api/types/response.ts | 11 +- frontend/lib/api/user/recipes/recipe.ts | 9 + .../g/_groupSlug/recipes/finder/index.vue | 596 ++++++++++++++++++ mealie/repos/repository_generic.py | 24 +- mealie/repos/repository_recipes.py | 188 +++++- .../explore/controller_public_recipes.py | 21 + mealie/routes/recipe/__init__.py | 6 +- mealie/routes/recipe/_base.py | 57 ++ mealie/routes/recipe/exports.py | 76 +++ mealie/routes/recipe/recipe_crud_routes.py | 133 +--- mealie/routes/recipe/timeline_events.py | 18 +- mealie/schema/recipe/__init__.py | 4 + mealie/schema/recipe/recipe_suggestion.py | 24 + mealie/schema/response/__init__.py | 10 +- mealie/schema/response/pagination.py | 9 +- .../test_public_recipes.py | 70 ++ .../test_recipe_suggestions.py | 581 +++++++++++++++++ .../repository_tests/test_pagination.py | 6 +- tests/utils/api_routes/__init__.py | 7 + 28 files changed, 1959 insertions(+), 151 deletions(-) create mode 100644 frontend/components/Domain/Recipe/RecipeSuggestion.vue create mode 100644 frontend/pages/g/_groupSlug/recipes/finder/index.vue create mode 100644 mealie/routes/recipe/_base.py create mode 100644 mealie/routes/recipe/exports.py create mode 100644 mealie/schema/recipe/recipe_suggestion.py create mode 100644 tests/integration_tests/user_recipe_tests/test_recipe_suggestions.py diff --git a/frontend/assets/css/main.css b/frontend/assets/css/main.css index c89c6620d..74257bd4a 100644 --- a/frontend/assets/css/main.css +++ b/frontend/assets/css/main.css @@ -48,3 +48,11 @@ .v-card__title { word-break: normal !important; } + +.text-hide-overflow { + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +} diff --git a/frontend/components/Domain/QueryFilterBuilder.vue b/frontend/components/Domain/QueryFilterBuilder.vue index a0edf50fa..b87fb2461 100644 --- a/frontend/components/Domain/QueryFilterBuilder.vue +++ b/frontend/components/Domain/QueryFilterBuilder.vue @@ -253,7 +253,7 @@ - + { + const part: QueryFilterJSONPart = { + attributeName: field.name, + leftParenthesis: field.leftParenthesis, + rightParenthesis: field.rightParenthesis, + logicalOperator: field.logicalOperator?.value, + relationalOperator: field.relationalOperatorValue?.value, + }; + + if (field.fieldOptions?.length || isOrganizerType(field.type)) { + part.value = field.values.map((value) => value.toString()); + } else if (field.type === "boolean") { + part.value = field.value ? "true" : "false"; + } else { + part.value = (field.value || "").toString(); + } + + return part; + }); + + const qfJSON = { parts } as QueryFilterJSON; + console.debug(`Built query filter JSON: ${JSON.stringify(qfJSON)}`); + return qfJSON; + } + const attrs = computed(() => { const baseColMaxWidth = 55; diff --git a/frontend/components/Domain/Recipe/RecipeSuggestion.vue b/frontend/components/Domain/Recipe/RecipeSuggestion.vue new file mode 100644 index 000000000..b6b100877 --- /dev/null +++ b/frontend/components/Domain/Recipe/RecipeSuggestion.vue @@ -0,0 +1,118 @@ + + + diff --git a/frontend/components/Layout/DefaultLayout.vue b/frontend/components/Layout/DefaultLayout.vue index 5e1ea6eca..ffba96ca4 100644 --- a/frontend/components/Layout/DefaultLayout.vue +++ b/frontend/components/Layout/DefaultLayout.vue @@ -221,7 +221,13 @@ export default defineComponent({ icon: $globals.icons.silverwareForkKnife, to: `/g/${groupSlug.value}`, title: i18n.tc("general.recipes"), - restricted: true, + restricted: false, + }, + { + icon: $globals.icons.search, + to: `/g/${groupSlug.value}/recipes/finder`, + title: i18n.tc("recipe-finder.recipe-finder"), + restricted: false, }, { icon: $globals.icons.calendarMultiselect, diff --git a/frontend/components/global/BaseDialog.vue b/frontend/components/global/BaseDialog.vue index 9fe321009..1a8e77ff2 100644 --- a/frontend/components/global/BaseDialog.vue +++ b/frontend/components/global/BaseDialog.vue @@ -45,11 +45,13 @@ + - - + {{ submitText }}