mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	feat(frontend): ✨ Add Recipe Editor to Assign Units and Foods for Recipe Scaling
This commit is contained in:
		| @@ -2,9 +2,9 @@ | |||||||
|   <div class="text-center"> |   <div class="text-center"> | ||||||
|     <v-dialog v-model="dialog" width="600"> |     <v-dialog v-model="dialog" width="600"> | ||||||
|       <template #activator="{ on, attrs }"> |       <template #activator="{ on, attrs }"> | ||||||
|         <v-btn color="secondary lighten-2" dark v-bind="attrs" v-on="on" @click="inputText = ''"> |         <BaseButton v-bind="attrs" v-on="on" @click="inputText = ''"> | ||||||
|           {{ $t("new-recipe.bulk-add") }} |           {{ $t("new-recipe.bulk-add") }} | ||||||
|         </v-btn> |         </BaseButton> | ||||||
|       </template> |       </template> | ||||||
|  |  | ||||||
|       <v-card> |       <v-card> | ||||||
|   | |||||||
							
								
								
									
										122
									
								
								frontend/components/Domain/Recipe/RecipeIngredientEditor.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								frontend/components/Domain/Recipe/RecipeIngredientEditor.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <v-text-field | ||||||
|  |       v-if="value.title || showTitle" | ||||||
|  |       v-model="value.title" | ||||||
|  |       dense | ||||||
|  |       hide-details | ||||||
|  |       class="mx-1 mb-4" | ||||||
|  |       placeholder="Section Title" | ||||||
|  |       style="max-width: 500px" | ||||||
|  |     > | ||||||
|  |     </v-text-field> | ||||||
|  |     <v-row :no-gutters="$vuetify.breakpoint.mdAndUp" dense class="d-flex flex-wrap my-1"> | ||||||
|  |       <v-col v-if="!disableAmount" sm="12" md="2" cols="12" class="flex-grow-0 flex-shrink-0"> | ||||||
|  |         <v-text-field | ||||||
|  |           v-model="value.quantity" | ||||||
|  |           solo | ||||||
|  |           hide-details | ||||||
|  |           dense | ||||||
|  |           class="mx-1" | ||||||
|  |           type="number" | ||||||
|  |           placeholder="Quantity" | ||||||
|  |         > | ||||||
|  |           <v-icon slot="prepend" class="mr-n1" color="error" @click="$emit('delete')"> | ||||||
|  |             {{ $globals.icons.delete }} | ||||||
|  |           </v-icon> | ||||||
|  |         </v-text-field> | ||||||
|  |       </v-col> | ||||||
|  |       <v-col v-if="!disableAmount && units" sm="12" md="3" cols="12"> | ||||||
|  |         <v-select | ||||||
|  |           v-model="value.unit" | ||||||
|  |           hide-details | ||||||
|  |           dense | ||||||
|  |           solo | ||||||
|  |           return-object | ||||||
|  |           :items="units" | ||||||
|  |           item-text="name" | ||||||
|  |           class="mx-1" | ||||||
|  |           placeholder="Choose Unit" | ||||||
|  |         > | ||||||
|  |         </v-select> | ||||||
|  |       </v-col> | ||||||
|  |       <v-col v-if="!disableAmount && foods" m="12" md="3" cols="12" class=""> | ||||||
|  |         <v-select | ||||||
|  |           v-model="value.food" | ||||||
|  |           hide-details | ||||||
|  |           dense | ||||||
|  |           solo | ||||||
|  |           return-object | ||||||
|  |           :items="foods" | ||||||
|  |           item-text="name" | ||||||
|  |           class="mx-1 py-0" | ||||||
|  |           placeholder="Choose Food" | ||||||
|  |         > | ||||||
|  |         </v-select> | ||||||
|  |       </v-col> | ||||||
|  |       <v-col sm="12" md="" cols="12"> | ||||||
|  |         <v-text-field v-model="value.note" hide-details dense solo class="mx-1" placeholder="Notes"> | ||||||
|  |           <v-icon v-if="disableAmount" slot="prepend" class="mr-n1" color="error" @click="$emit('delete')"> | ||||||
|  |             {{ $globals.icons.delete }} | ||||||
|  |           </v-icon> | ||||||
|  |           <template slot="append"> | ||||||
|  |             <v-tooltip top nudge-right="10"> | ||||||
|  |               <template #activator="{ on, attrs }"> | ||||||
|  |                 <v-btn icon small class="mt-n1" v-bind="attrs" v-on="on" @click="toggleTitle()"> | ||||||
|  |                   <v-icon>{{ showTitle || value.title ? $globals.icons.minus : $globals.icons.createAlt }}</v-icon> | ||||||
|  |                 </v-btn> | ||||||
|  |               </template> | ||||||
|  |               <span>{{ showTitle ? $t("recipe.remove-section") : $t("recipe.insert-section") }}</span> | ||||||
|  |             </v-tooltip> | ||||||
|  |           </template> | ||||||
|  |           <template slot="append-outer"> | ||||||
|  |             <v-icon class="handle">{{ $globals.icons.arrowUpDown }}</v-icon> | ||||||
|  |           </template> | ||||||
|  |         </v-text-field> | ||||||
|  |       </v-col> | ||||||
|  |     </v-row> | ||||||
|  |     <v-divider v-if="!$vuetify.breakpoint.mdAndUp" class="my-4"></v-divider> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import { defineComponent, reactive, toRefs } from "@nuxtjs/composition-api"; | ||||||
|  | import { useFoods } from "~/composables/use-recipe-foods"; | ||||||
|  | import { useUnits } from "~/composables/use-recipe-units"; | ||||||
|  |  | ||||||
|  | export default defineComponent({ | ||||||
|  |   props: { | ||||||
|  |     value: { | ||||||
|  |       type: Object, | ||||||
|  |       required: true, | ||||||
|  |     }, | ||||||
|  |     disableAmount: { | ||||||
|  |       type: Boolean, | ||||||
|  |       default: false, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   setup(props) { | ||||||
|  |     const { value } = props; | ||||||
|  |  | ||||||
|  |     const { foods } = useFoods(); | ||||||
|  |     const { units } = useUnits(); | ||||||
|  |  | ||||||
|  |     const state = reactive({ | ||||||
|  |       showTitle: false, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     function toggleTitle() { | ||||||
|  |       if (value.title) { | ||||||
|  |         state.showTitle = false; | ||||||
|  |         value.title = ""; | ||||||
|  |       } else { | ||||||
|  |         state.showTitle = true; | ||||||
|  |         value.title = "Section Title"; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return { foods, units, ...toRefs(state), toggleTitle }; | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
| @@ -89,7 +89,7 @@ export default { | |||||||
|  |  | ||||||
|     edit: { |     edit: { | ||||||
|       type: Boolean, |       type: Boolean, | ||||||
|       default: true, |       default: false, | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ | |||||||
|               class="ma-1" |               class="ma-1" | ||||||
|               :class="[{ 'on-hover': hover }, isChecked(index)]" |               :class="[{ 'on-hover': hover }, isChecked(index)]" | ||||||
|               :elevation="hover ? 12 : 2" |               :elevation="hover ? 12 : 2" | ||||||
|               :ripple="!edit" |               :ripple="false" | ||||||
|               @click="toggleDisabled(index)" |               @click="toggleDisabled(index)" | ||||||
|             > |             > | ||||||
|               <v-card-title :class="{ 'pb-0': !isChecked(index) }"> |               <v-card-title :class="{ 'pb-0': !isChecked(index) }"> | ||||||
|   | |||||||
| @@ -96,30 +96,6 @@ export default defineComponent({ | |||||||
|           restricted: false, |           restricted: false, | ||||||
|           nav: "/user/login", |           nav: "/user/login", | ||||||
|         }, |         }, | ||||||
|         { |  | ||||||
|           icon: this.$globals.icons.calendarWeek, |  | ||||||
|           title: this.$t("meal-plan.dinner-this-week"), |  | ||||||
|           nav: "/meal-plan/this-week", |  | ||||||
|           restricted: true, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           icon: this.$globals.icons.calendarToday, |  | ||||||
|           title: this.$t("meal-plan.dinner-today"), |  | ||||||
|           nav: "/meal-plan/today", |  | ||||||
|           restricted: true, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           icon: this.$globals.icons.calendarMultiselect, |  | ||||||
|           title: this.$t("meal-plan.planner"), |  | ||||||
|           nav: "/meal-plan/planner", |  | ||||||
|           restricted: true, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           icon: this.$globals.icons.formatListCheck, |  | ||||||
|           title: this.$t("shopping-list.shopping-lists"), |  | ||||||
|           nav: "/shopping-list", |  | ||||||
|           restricted: true, |  | ||||||
|         }, |  | ||||||
|         { |         { | ||||||
|           icon: this.$globals.icons.logout, |           icon: this.$globals.icons.logout, | ||||||
|           title: this.$t("user.logout"), |           title: this.$t("user.logout"), | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <v-navigation-drawer :value="value" clipped app> |   <v-navigation-drawer :value="value" clipped app width="240px"> | ||||||
|     <!-- User Profile --> |     <!-- User Profile --> | ||||||
|     <template v-if="$auth.user"> |     <template v-if="$auth.user"> | ||||||
|       <v-list-item two-line to="/user/profile"> |       <v-list-item two-line to="/user/profile"> | ||||||
| @@ -16,16 +16,47 @@ | |||||||
|     </template> |     </template> | ||||||
|  |  | ||||||
|     <!-- Primary Links --> |     <!-- Primary Links --> | ||||||
|  |     <template v-if="topLink"> | ||||||
|       <v-list nav dense> |       <v-list nav dense> | ||||||
|       <v-list-item-group v-model="topSelected" color="primary"> |         <template v-for="nav in topLink"> | ||||||
|         <v-list-item v-for="nav in topLink" :key="nav.title" exact link :to="nav.to"> |           <!-- Multi Items --> | ||||||
|  |           <v-list-group | ||||||
|  |             v-if="nav.children && ($auth.loggedIn || !nav.restricted)" | ||||||
|  |             :key="nav.title + 'multi-item'" | ||||||
|  |             v-model="dropDowns[nav.title]" | ||||||
|  |             color="primary" | ||||||
|  |             :prepend-icon="nav.icon" | ||||||
|  |           > | ||||||
|  |             <template #activator> | ||||||
|  |               <v-list-item-title>{{ nav.title }}</v-list-item-title> | ||||||
|  |             </template> | ||||||
|  |  | ||||||
|  |             <v-list-item v-for="child in nav.children" :key="child.title" :to="child.to"> | ||||||
|  |               <v-list-item-icon> | ||||||
|  |                 <v-icon>{{ child.icon }}</v-icon> | ||||||
|  |               </v-list-item-icon> | ||||||
|  |               <v-list-item-title>{{ child.title }}</v-list-item-title> | ||||||
|  |             </v-list-item> | ||||||
|  |             <v-divider class="mb-4"></v-divider> | ||||||
|  |           </v-list-group> | ||||||
|  |  | ||||||
|  |           <!-- Single Item --> | ||||||
|  |           <v-list-item-group | ||||||
|  |             v-else-if="$auth.loggedIn || !nav.restricted" | ||||||
|  |             :key="nav.title + 'single-item'" | ||||||
|  |             v-model="secondarySelected" | ||||||
|  |             color="primary" | ||||||
|  |           > | ||||||
|  |             <v-list-item link :to="nav.to"> | ||||||
|               <v-list-item-icon> |               <v-list-item-icon> | ||||||
|                 <v-icon>{{ nav.icon }}</v-icon> |                 <v-icon>{{ nav.icon }}</v-icon> | ||||||
|               </v-list-item-icon> |               </v-list-item-icon> | ||||||
|               <v-list-item-title>{{ nav.title }}</v-list-item-title> |               <v-list-item-title>{{ nav.title }}</v-list-item-title> | ||||||
|             </v-list-item> |             </v-list-item> | ||||||
|           </v-list-item-group> |           </v-list-item-group> | ||||||
|  |         </template> | ||||||
|       </v-list> |       </v-list> | ||||||
|  |     </template> | ||||||
|  |  | ||||||
|     <!-- Secondary Links --> |     <!-- Secondary Links --> | ||||||
|     <template v-if="secondaryLinks"> |     <template v-if="secondaryLinks"> | ||||||
| @@ -51,6 +82,7 @@ | |||||||
|               </v-list-item-icon> |               </v-list-item-icon> | ||||||
|               <v-list-item-title>{{ child.title }}</v-list-item-title> |               <v-list-item-title>{{ child.title }}</v-list-item-title> | ||||||
|             </v-list-item> |             </v-list-item> | ||||||
|  |             <v-divider class="mb-4"></v-divider> | ||||||
|           </v-list-group> |           </v-list-group> | ||||||
|  |  | ||||||
|           <!-- Single Item --> |           <!-- Single Item --> | ||||||
|   | |||||||
| @@ -37,20 +37,46 @@ export default defineComponent({ | |||||||
|       sidebar: null, |       sidebar: null, | ||||||
|       topLinks: [ |       topLinks: [ | ||||||
|         { |         { | ||||||
|           icon: this.$globals.icons.home, |           icon: this.$globals.icons.calendar, | ||||||
|           to: "/", |           restricted: true, | ||||||
|           title: this.$t("sidebar.home-page"), |           title: this.$t("meal-plan.meal-planner"), | ||||||
|  |           children: [ | ||||||
|  |             { | ||||||
|  |               icon: this.$globals.icons.calendarMultiselect, | ||||||
|  |               title: this.$t("meal-plan.planner"), | ||||||
|  |               to: "/meal-plan/planner", | ||||||
|  |               restricted: true, | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|           icon: this.$globals.icons.search, |               icon: this.$globals.icons.calendarWeek, | ||||||
|           to: "/search", |               title: this.$t("meal-plan.dinner-this-week"), | ||||||
|           title: this.$t("sidebar.search"), |               to: "/meal-plan/this-week", | ||||||
|  |               restricted: true, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               icon: this.$globals.icons.calendarToday, | ||||||
|  |               title: this.$t("meal-plan.dinner-today"), | ||||||
|  |               to: "/meal-plan/today", | ||||||
|  |               restricted: true, | ||||||
|  |             }, | ||||||
|  |           ], | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           icon: this.$globals.icons.formatListCheck, | ||||||
|  |           title: this.$t("shopping-list.shopping-lists"), | ||||||
|  |           to: "/shopping-list", | ||||||
|  |           restricted: true, | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           icon: this.$globals.icons.viewModule, |           icon: this.$globals.icons.viewModule, | ||||||
|           to: "/recipes/all", |           to: "/recipes/all", | ||||||
|           title: this.$t("sidebar.all-recipes"), |           title: this.$t("sidebar.all-recipes"), | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           icon: this.$globals.icons.search, | ||||||
|  |           to: "/search", | ||||||
|  |           title: this.$t("sidebar.search"), | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|           icon: this.$globals.icons.tags, |           icon: this.$globals.icons.tags, | ||||||
|           to: "/recipes/categories", |           to: "/recipes/categories", | ||||||
|   | |||||||
| @@ -1,13 +1,30 @@ | |||||||
| <template> | <template> | ||||||
|   <v-app dark> |   <div> | ||||||
|     <h1 v-if="error.statusCode === 404"> |     <v-card-title> | ||||||
|       {{ pageNotFound }} |       <slot> | ||||||
|     </h1> |         <h1 class="mx-auto">{{ $t("page.404-page-not-found") }}</h1> | ||||||
|     <h1 v-else> |       </slot> | ||||||
|       {{ otherError }} |     </v-card-title> | ||||||
|     </h1> |     <div class="d-flex justify-space-around"> | ||||||
|     <NuxtLink to="/"> Home page </NuxtLink> |       <div class="d-flex align-center"> | ||||||
|   </v-app> |         <p class="primary--text">4</p> | ||||||
|  |         <v-icon color="primary" class="mx-auto mb-0" size="200"> | ||||||
|  |           {{ $globals.icons.primary }} | ||||||
|  |         </v-icon> | ||||||
|  |         <p class="primary--text">4</p> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <v-card-actions> | ||||||
|  |       <v-spacer></v-spacer> | ||||||
|  |       <slot name="actions"> | ||||||
|  |         <v-btn v-for="(button, index) in buttons" :key="index" nuxt :to="button.to" color="primary"> | ||||||
|  |           <v-icon left> {{ button.icon }} </v-icon> | ||||||
|  |           {{ button.text }} | ||||||
|  |         </v-btn> | ||||||
|  |       </slot> | ||||||
|  |       <v-spacer></v-spacer> | ||||||
|  |     </v-card-actions> | ||||||
|  |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| @@ -31,6 +48,15 @@ export default { | |||||||
|       title, |       title, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|  |   computed: { | ||||||
|  |     buttons() { | ||||||
|  |       return [ | ||||||
|  |         { icon: this.$globals.icons.home, to: "/", text: this.$t("general.home") }, | ||||||
|  |         { icon: this.$globals.icons.primary, to: "/recipes/all", text: this.$t("page.all-recipes") }, | ||||||
|  |         { icon: this.$globals.icons.search, to: "/search", text: this.$t("search.search") }, | ||||||
|  |       ]; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| @@ -38,4 +64,10 @@ export default { | |||||||
| h1 { | h1 { | ||||||
|   font-size: 20px; |   font-size: 20px; | ||||||
| } | } | ||||||
|  | p { | ||||||
|  |   padding-bottom: 0 !important; | ||||||
|  |   margin-bottom: 0 !important; | ||||||
|  |   font-size: 200px; | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -97,6 +97,24 @@ | |||||||
|             </v-textarea> |             </v-textarea> | ||||||
|           </template> |           </template> | ||||||
|  |  | ||||||
|  |           <!-- Advanced Editor --> | ||||||
|  |           <div v-if="form"> | ||||||
|  |             <h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2> | ||||||
|  |             <draggable v-model="recipe.recipeIngredient" handle=".handle"> | ||||||
|  |               <RecipeIngredientEditor | ||||||
|  |                 v-for="(ingredient, index) in recipe.recipeIngredient" | ||||||
|  |                 :key="index + 'ing-editor'" | ||||||
|  |                 v-model="recipe.recipeIngredient[index]" | ||||||
|  |                 :disable-amount="recipe.settings.disableAmount" | ||||||
|  |                 @delete="removeByIndex(recipe.recipeIngredient, index)" | ||||||
|  |               /> | ||||||
|  |             </draggable> | ||||||
|  |             <div class="d-flex justify-end mt-2"> | ||||||
|  |               <RecipeDialogBulkAdd class="mr-2" @bulk-data="addIngredient" /> | ||||||
|  |               <BaseButton @click="addIngredient"> {{ $t("general.new") }} </BaseButton> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|           <div class="d-flex justify-space-between align-center pb-3"> |           <div class="d-flex justify-space-between align-center pb-3"> | ||||||
|             <v-btn |             <v-btn | ||||||
|               v-if="recipe.recipeYield" |               v-if="recipe.recipeYield" | ||||||
| @@ -121,24 +139,43 @@ | |||||||
|           </div> |           </div> | ||||||
|           <v-row> |           <v-row> | ||||||
|             <v-col cols="12" sm="12" md="4" lg="4"> |             <v-col cols="12" sm="12" md="4" lg="4"> | ||||||
|               <RecipeIngredients :value="recipe.recipeIngredient" :edit="form" /> |               <RecipeIngredients v-if="!form" :value="recipe.recipeIngredient" /> | ||||||
|               <div v-if="$vuetify.breakpoint.mdAndUp"> |  | ||||||
|                 <v-card v-if="recipe.recipeCategory.length > 0" class="mt-2"> |               <!-- Recipe Categories --> | ||||||
|  |               <div v-if="$vuetify.breakpoint.mdAndUp" class="mt-5"> | ||||||
|  |                 <v-card v-if="recipe.recipeCategory.length > 0 || form" class="mt-2"> | ||||||
|                   <v-card-title class="py-2"> |                   <v-card-title class="py-2"> | ||||||
|                     {{ $t("recipe.categories") }} |                     {{ $t("recipe.categories") }} | ||||||
|                   </v-card-title> |                   </v-card-title> | ||||||
|                   <v-divider class="mx-2"></v-divider> |                   <v-divider class="mx-2"></v-divider> | ||||||
|                   <v-card-text> |                   <v-card-text> | ||||||
|                     <RecipeChips :items="recipe.recipeCategory" /> |                     <RecipeCategoryTagSelector | ||||||
|  |                       v-if="form" | ||||||
|  |                       v-model="recipe.recipeCategory" | ||||||
|  |                       :return-object="false" | ||||||
|  |                       :show-add="true" | ||||||
|  |                       :show-label="false" | ||||||
|  |                     /> | ||||||
|  |                     <RecipeChips v-else :items="recipe.recipeCategory" /> | ||||||
|                   </v-card-text> |                   </v-card-text> | ||||||
|                 </v-card> |                 </v-card> | ||||||
|                 <v-card v-if="recipe.tags.length > 0" class="mt-2"> |  | ||||||
|  |                 <!-- Recipe Tags --> | ||||||
|  |                 <v-card v-if="recipe.tags.length > 0 || form" class="mt-2"> | ||||||
|                   <v-card-title class="py-2"> |                   <v-card-title class="py-2"> | ||||||
|                     {{ $t("tag.tags") }} |                     {{ $t("tag.tags") }} | ||||||
|                   </v-card-title> |                   </v-card-title> | ||||||
|                   <v-divider class="mx-2"></v-divider> |                   <v-divider class="mx-2"></v-divider> | ||||||
|                   <v-card-text> |                   <v-card-text> | ||||||
|                     <RecipeChips :items="recipe.tags" :is-category="false" /> |                     <RecipeCategoryTagSelector | ||||||
|  |                       v-if="form" | ||||||
|  |                       v-model="recipe.tags" | ||||||
|  |                       :return-object="false" | ||||||
|  |                       :show-add="true" | ||||||
|  |                       :tag-selector="true" | ||||||
|  |                       :show-label="false" | ||||||
|  |                     /> | ||||||
|  |                     <RecipeChips v-else :items="recipe.tags" :is-category="false" /> | ||||||
|                   </v-card-text> |                   </v-card-text> | ||||||
|                 </v-card> |                 </v-card> | ||||||
|  |  | ||||||
| @@ -168,6 +205,9 @@ | |||||||
| import { defineComponent, ref, useMeta, useRoute, useRouter } from "@nuxtjs/composition-api"; | import { defineComponent, ref, useMeta, useRoute, useRouter } from "@nuxtjs/composition-api"; | ||||||
| // @ts-ignore | // @ts-ignore | ||||||
| import VueMarkdown from "@adapttive/vue-markdown"; | import VueMarkdown from "@adapttive/vue-markdown"; | ||||||
|  | import draggable from "vuedraggable"; | ||||||
|  | import RecipeCategoryTagSelector from "@/components/Domain/Recipe/RecipeCategoryTagSelector.vue"; | ||||||
|  | import RecipeDialogBulkAdd from "@/components/Domain/Recipe//RecipeDialogBulkAdd.vue"; | ||||||
| import { useApiSingleton } from "~/composables/use-api"; | import { useApiSingleton } from "~/composables/use-api"; | ||||||
| import { validators } from "~/composables/use-validators"; | import { validators } from "~/composables/use-validators"; | ||||||
| import { useRecipeContext } from "~/composables/use-recipe-context"; | import { useRecipeContext } from "~/composables/use-recipe-context"; | ||||||
| @@ -182,23 +222,28 @@ import RecipeInstructions from "~/components/Domain/Recipe/RecipeInstructions.vu | |||||||
| import RecipeNotes from "~/components/Domain/Recipe/RecipeNotes.vue"; | import RecipeNotes from "~/components/Domain/Recipe/RecipeNotes.vue"; | ||||||
| import RecipeImageUploadBtn from "~/components/Domain/Recipe/RecipeImageUploadBtn.vue"; | import RecipeImageUploadBtn from "~/components/Domain/Recipe/RecipeImageUploadBtn.vue"; | ||||||
| import RecipeSettingsMenu from "~/components/Domain/Recipe/RecipeSettingsMenu.vue"; | import RecipeSettingsMenu from "~/components/Domain/Recipe/RecipeSettingsMenu.vue"; | ||||||
|  | import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue"; | ||||||
| import { Recipe } from "~/types/api-types/admin"; | import { Recipe } from "~/types/api-types/admin"; | ||||||
| import { useStaticRoutes } from "~/composables/api"; | import { useStaticRoutes } from "~/composables/api"; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   components: { |   components: { | ||||||
|     RecipeActionMenu, |     RecipeActionMenu, | ||||||
|  |     RecipeDialogBulkAdd, | ||||||
|     RecipeAssets, |     RecipeAssets, | ||||||
|  |     RecipeCategoryTagSelector, | ||||||
|     RecipeChips, |     RecipeChips, | ||||||
|  |     RecipeImageUploadBtn, | ||||||
|     RecipeIngredients, |     RecipeIngredients, | ||||||
|     RecipeInstructions, |     RecipeInstructions, | ||||||
|     RecipeNotes, |     RecipeNotes, | ||||||
|     RecipeNutrition, |     RecipeNutrition, | ||||||
|     RecipeRating, |     RecipeRating, | ||||||
|     RecipeTimeCard, |  | ||||||
|     RecipeImageUploadBtn, |  | ||||||
|     RecipeSettingsMenu, |     RecipeSettingsMenu, | ||||||
|  |     RecipeIngredientEditor, | ||||||
|  |     RecipeTimeCard, | ||||||
|     VueMarkdown, |     VueMarkdown, | ||||||
|  |     draggable, | ||||||
|   }, |   }, | ||||||
|   setup() { |   setup() { | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
| @@ -243,6 +288,38 @@ export default defineComponent({ | |||||||
|       imageKey.value++; |       imageKey.value++; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     function removeByIndex(list: Array<any>, index: number) { | ||||||
|  |       list.splice(index, 1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function addIngredient(ingredients: Array<string> | null = null) { | ||||||
|  |       if (ingredients?.length) { | ||||||
|  |         const newIngredients = ingredients.map((x) => { | ||||||
|  |           return { | ||||||
|  |             title: "", | ||||||
|  |             note: x, | ||||||
|  |             unit: {}, | ||||||
|  |             food: {}, | ||||||
|  |             disableAmount: true, | ||||||
|  |             quantity: 1, | ||||||
|  |           }; | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         if (newIngredients) { | ||||||
|  |           recipe?.value?.recipeIngredient?.push(...newIngredients); | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         recipe?.value?.recipeIngredient?.push({ | ||||||
|  |           title: "", | ||||||
|  |           note: "", | ||||||
|  |           unit: {}, | ||||||
|  |           food: {}, | ||||||
|  |           disableAmount: true, | ||||||
|  |           quantity: 1, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|       imageKey, |       imageKey, | ||||||
|       recipe, |       recipe, | ||||||
| @@ -254,6 +331,8 @@ export default defineComponent({ | |||||||
|       uploadImage, |       uploadImage, | ||||||
|       validators, |       validators, | ||||||
|       recipeImage, |       recipeImage, | ||||||
|  |       addIngredient, | ||||||
|  |       removeByIndex, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|   | |||||||
| @@ -56,15 +56,15 @@ export interface Recipe { | |||||||
|   name: string; |   name: string; | ||||||
|   slug: string; |   slug: string; | ||||||
|   image?: unknown; |   image?: unknown; | ||||||
|   description?: string; |   description: string; | ||||||
|   recipeCategory?: string[]; |   recipeCategory: string[]; | ||||||
|   tags?: string[]; |   tags: string[]; | ||||||
|   rating?: number; |   rating: number; | ||||||
|   dateAdded?: string; |   dateAdded?: string; | ||||||
|   dateUpdated?: string; |   dateUpdated?: string; | ||||||
|   recipeYield?: string; |   recipeYield?: string; | ||||||
|   recipeIngredient?: RecipeIngredient[]; |   recipeIngredient: RecipeIngredient[]; | ||||||
|   recipeInstructions?: RecipeStep[]; |   recipeInstructions: RecipeStep[]; | ||||||
|   nutrition?: Nutrition; |   nutrition?: Nutrition; | ||||||
|   tools?: string[]; |   tools?: string[]; | ||||||
|   totalTime?: string; |   totalTime?: string; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user