mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 02:03:35 -04:00 
			
		
		
		
	feature/editor-improvements (#289)
* pin editor buttons on scroll * scaler scratch * fix langauge assignment 1st pass * set lang on navigate * refactor/breakup router * unify style for language selectro * refactor/code-cleanup * refactor/page specific components to page folder * Fix time card layout issue * fix timecard display * update mobile cards / fix overflow errors Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
		
							
								
								
									
										42
									
								
								dev/ingredientScaler/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								dev/ingredientScaler/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | import { recipeIngredient } from "./recipeIngredient"; | ||||||
|  | import { recipeNumber } from "./recipeNumber"; | ||||||
|  |  | ||||||
|  | export const ingredientScaler = { | ||||||
|  |   process(ingredientArray, scale) { | ||||||
|  |     console.log(scale); | ||||||
|  |     let workingArray = ingredientArray.map(x => | ||||||
|  |       ingredientScaler.markIngredient(x) | ||||||
|  |     ); | ||||||
|  |     return workingArray.map(x => ingredientScaler.adjustIngredients(x, scale)); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   adjustIngredients(ingredient, scale) { | ||||||
|  |     var scaledQuantity = new recipeNumber(ingredient.quantity).multiply(scale); | ||||||
|  |     const newText = ingredient.text.replace( | ||||||
|  |       ingredient.quantity, | ||||||
|  |       scaledQuantity | ||||||
|  |     ); | ||||||
|  |     return { ...ingredient, quantity: scaledQuantity, text: newText }; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   markIngredient(ingredient) { | ||||||
|  |     console.log(ingredient); | ||||||
|  |     const returnVar = ingredient.replace( | ||||||
|  |       /^([\d/?[^\s&]*)(?: |\s)(\w*)/g, | ||||||
|  |       (match, quantity, unit) => { | ||||||
|  |         return `${unit}${quantity},${match}`; | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |     const split = returnVar.split(","); | ||||||
|  |     const [unit, quantity, match] = split; | ||||||
|  |     console.log("Split", unit, quantity, match); | ||||||
|  |     const n = new recipeNumber(quantity); | ||||||
|  |     const i = new recipeIngredient(n, unit); | ||||||
|  |     const serializedQuantity = n.isFraction() ? n.toImproperFraction() : n; | ||||||
|  |     return { | ||||||
|  |       unit: i, | ||||||
|  |       quantity: serializedQuantity.toString(), | ||||||
|  |       text: match, | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
							
								
								
									
										75
									
								
								dev/ingredientScaler/recipeIngredient.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								dev/ingredientScaler/recipeIngredient.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | export const recipeIngredient = function(quantity, unit) { | ||||||
|  |   this.quantity = quantity; | ||||||
|  |   this.unit = unit; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | recipeIngredient.prototype.isSingular = function() { | ||||||
|  |   return this.quantity > 0 && this.quantity <= 1; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | recipeIngredient.prototype.pluralize = function() { | ||||||
|  |   if (this.isSingular()) { | ||||||
|  |     return this.unit; | ||||||
|  |   } else { | ||||||
|  |     return `${this.unit}s`; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | recipeIngredient.prototype.getSingularUnit = function() { | ||||||
|  |   if (this.isSingular()) { | ||||||
|  |     return this.unit; | ||||||
|  |   } else { | ||||||
|  |     return this.unit.replace(/s$/, ""); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | recipeIngredient.prototype.toString = function() { | ||||||
|  |   return `${this.quantity.toString()} ${this.pluralize()}`; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | recipeIngredient.prototype.convertUnits = function() { | ||||||
|  |   const conversion = recipeIngredient.CONVERSIONS[this.unit] || {}; | ||||||
|  |   if (conversion.min && this.quantity < conversion.min.value) { | ||||||
|  |     this.unit = conversion.min.next; | ||||||
|  |     this.quantity.multiply(conversion.to[this.unit]); | ||||||
|  |   } else if (conversion.max && this.quantity >= conversion.max.value) { | ||||||
|  |     this.unit = conversion.max.next; | ||||||
|  |     this.quantity.multiply(conversion.to[this.unit]); | ||||||
|  |   } | ||||||
|  |   return this; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | recipeIngredient.CONVERSIONS = { | ||||||
|  |   cup: { | ||||||
|  |     to: { | ||||||
|  |       tablespoon: 16, | ||||||
|  |     }, | ||||||
|  |     min: { | ||||||
|  |       value: 1 / 4, | ||||||
|  |       next: "tablespoon", | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   tablespoon: { | ||||||
|  |     to: { | ||||||
|  |       teaspoon: 3, | ||||||
|  |       cup: 1 / 16, | ||||||
|  |     }, | ||||||
|  |     min: { | ||||||
|  |       value: 1, | ||||||
|  |       next: "teaspoon", | ||||||
|  |     }, | ||||||
|  |     max: { | ||||||
|  |       value: 4, | ||||||
|  |       next: "cup", | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   teaspoon: { | ||||||
|  |     to: { | ||||||
|  |       tablespoon: 1 / 3, | ||||||
|  |     }, | ||||||
|  |     max: { | ||||||
|  |       value: 3, | ||||||
|  |       next: "tablespoon", | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
							
								
								
									
										166
									
								
								dev/ingredientScaler/recipeNumber.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								dev/ingredientScaler/recipeNumber.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | |||||||
|  | export const recipeNumber = function(number) { | ||||||
|  |   const match = number.match( | ||||||
|  |     /^(?:(\d+)|(?:(\d+)(?: | ))?(?:(\d+)\/(\d+))?)$/ | ||||||
|  |   ); | ||||||
|  |   if (!match || !match[0] || match[4] == "0") { | ||||||
|  |     throw `Invalid number: "${number}".`; | ||||||
|  |   } | ||||||
|  |   this.wholeNumber = +(match[1] || match[2]); | ||||||
|  |   this.numerator = +match[3]; | ||||||
|  |   this.denominator = +match[4]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Determines if the number is a fraction. | ||||||
|  |  * @this {recipeNumber} | ||||||
|  |  * @return {boolean} If the number is a fraction. | ||||||
|  |  */ | ||||||
|  | recipeNumber.prototype.isFraction = function() { | ||||||
|  |   return !!(this.numerator && this.denominator); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Determines if the fraction is proper, which is defined as | ||||||
|  |  * the numerator being strictly less than the denominator. | ||||||
|  |  * @this {recipeNumber} | ||||||
|  |  * @return {boolean} If the fraction is proper. | ||||||
|  |  */ | ||||||
|  | recipeNumber.prototype.isProperFraction = function() { | ||||||
|  |   return this.numerator < this.denominator; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Determines if the fraction is improper, which is defined as | ||||||
|  |  * the numerator being greater than or equal to the denominator. | ||||||
|  |  * @this {recipeNumber} | ||||||
|  |  * @return {boolean} If the fraction is improper. | ||||||
|  |  */ | ||||||
|  | recipeNumber.prototype.isImproperFraction = function() { | ||||||
|  |   return this.numerator >= this.denominator; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Determines if the fraction is mixed, which is defined as | ||||||
|  |  * a whole number with a proper fraction. | ||||||
|  |  * @this {recipeNumber} | ||||||
|  |  * @return {boolean} If the fraction is mixed. | ||||||
|  |  */ | ||||||
|  | recipeNumber.prototype.isMixedFraction = function() { | ||||||
|  |   return this.isProperFraction() && !isNaN(this.wholeNumber); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Simplifies fractions. Examples: | ||||||
|  |  *   3/2 = 1 1/2 | ||||||
|  |  *   4/2 = 2 | ||||||
|  |  *   1 3/2 = 2 1/2 | ||||||
|  |  *   0/1 = 0 | ||||||
|  |  *   1 0/1 = 1 | ||||||
|  |  * @this {recipeNumber} | ||||||
|  |  * @return {recipeNumber} The instance. | ||||||
|  |  */ | ||||||
|  | recipeNumber.prototype.simplifyFraction = function() { | ||||||
|  |   if (this.isImproperFraction()) { | ||||||
|  |     this.wholeNumber |= 0; | ||||||
|  |     this.wholeNumber += Math.floor(this.numerator / this.denominator); | ||||||
|  |     const modulus = this.numerator % this.denominator; | ||||||
|  |     if (modulus) { | ||||||
|  |       this.numerator = modulus; | ||||||
|  |     } else { | ||||||
|  |       this.numerator = this.denominator = NaN; | ||||||
|  |     } | ||||||
|  |   } else if (this.numerator == 0) { | ||||||
|  |     this.wholeNumber |= 0; | ||||||
|  |     this.numerator = this.denominator = NaN; | ||||||
|  |   } | ||||||
|  |   return this; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Reduces a fraction. Examples: | ||||||
|  |  *   2/6 = 1/3 | ||||||
|  |  *   6/2 = 3/1 | ||||||
|  |  * @this {recipeNumber} | ||||||
|  |  * @return {recipeNumber} The instance. | ||||||
|  |  */ | ||||||
|  | recipeNumber.prototype.reduceFraction = function() { | ||||||
|  |   if (this.isFraction()) { | ||||||
|  |     const gcd = recipeNumber.gcd(this.numerator, this.denominator); | ||||||
|  |     this.numerator /= gcd; | ||||||
|  |     this.denominator /= gcd; | ||||||
|  |   } | ||||||
|  |   return this; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Converts proper fractions to improper fractions. Examples: | ||||||
|  |  *   1 1/2 = 3/2 | ||||||
|  |  *   3/2 = 3/2 | ||||||
|  |  *   1/2 = 1/2 | ||||||
|  |  *   2 = 2 | ||||||
|  |  * | ||||||
|  |  * @this {recipeNumber} | ||||||
|  |  * @return {recipeNumber} The instance. | ||||||
|  |  */ | ||||||
|  | recipeNumber.prototype.toImproperFraction = function() { | ||||||
|  |   if (!isNaN(this.wholeNumber)) { | ||||||
|  |     this.numerator |= 0; | ||||||
|  |     this.denominator = this.denominator || 1; | ||||||
|  |     this.numerator += this.wholeNumber * this.denominator; | ||||||
|  |     this.wholeNumber = NaN; | ||||||
|  |   } | ||||||
|  |   return this; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Multiplies the number by some decimal value. | ||||||
|  |  * @param {number} multiplier The multiplier. | ||||||
|  |  * @this {recipeNumber} | ||||||
|  |  * @return {recipeNumber} The instance. | ||||||
|  |  */ | ||||||
|  | recipeNumber.prototype.multiply = function(multiplier) { | ||||||
|  |   this.toImproperFraction(); | ||||||
|  |   this.numerator *= multiplier; | ||||||
|  |   return this.reduceFraction().simplifyFraction(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Gets a string representation of the number. | ||||||
|  |  * @this {recipeNumber} | ||||||
|  |  * @return {string} The string representation of the number. | ||||||
|  |  */ | ||||||
|  | recipeNumber.prototype.toString = function() { | ||||||
|  |   let number = ""; | ||||||
|  |   let fraction = ""; | ||||||
|  |   if (!isNaN(this.wholeNumber)) { | ||||||
|  |     number += this.wholeNumber; | ||||||
|  |   } | ||||||
|  |   if (this.isFraction()) { | ||||||
|  |     fraction = `${this.numerator}/${this.denominator}`; | ||||||
|  |   } | ||||||
|  |   if (number && fraction) { | ||||||
|  |     number += ` ${fraction}`; | ||||||
|  |   } | ||||||
|  |   return number || fraction; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Gets a numeric representation of the number. | ||||||
|  |  * @this {recipeNumber} | ||||||
|  |  * @return {number} The numeric representation of the number. | ||||||
|  |  */ | ||||||
|  | recipeNumber.prototype.valueOf = function() { | ||||||
|  |   let value = this.wholeNumber || 0; | ||||||
|  |   value += this.numerator / this.denominator || 0; | ||||||
|  |   return value; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Euclid's algorithm to find the greatest common divisor of two numbers. | ||||||
|  |  * @param {number} a One number. | ||||||
|  |  * @param {number} b Another number. | ||||||
|  |  * @return {number} The GCD of the numbers. | ||||||
|  |  */ | ||||||
|  | recipeNumber.gcd = function gcd(a, b) { | ||||||
|  |   return b ? recipeNumber.gcd(b, a % b) : a; | ||||||
|  | }; | ||||||
							
								
								
									
										11
									
								
								dev/scripts/publish-release-branch.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								dev/scripts/publish-release-branch.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | git checkout dev | ||||||
|  | git merge --strategy=ours master    # keep the content of this branch, but record a merge | ||||||
|  | git checkout master | ||||||
|  | git merge dev             # fast-forward master up to the merge | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## TODOs | ||||||
|  |  | ||||||
|  | # Create New Branch v0.x.x | ||||||
|  | # Push Branch Version to Github | ||||||
|  | # Create Pull Request | ||||||
| @@ -9,7 +9,7 @@ | |||||||
|       > |       > | ||||||
|  |  | ||||||
|       <v-slide-x-reverse-transition> |       <v-slide-x-reverse-transition> | ||||||
|         <AddRecipeFab v-if="loggedIn" /> |         <TheRecipeFab v-if="loggedIn" /> | ||||||
|       </v-slide-x-reverse-transition> |       </v-slide-x-reverse-transition> | ||||||
|       <router-view></router-view> |       <router-view></router-view> | ||||||
|     </v-main> |     </v-main> | ||||||
| @@ -19,7 +19,7 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import TheAppBar from "@/components/UI/TheAppBar"; | import TheAppBar from "@/components/UI/TheAppBar"; | ||||||
| import AddRecipeFab from "@/components/UI/AddRecipeFab"; | import TheRecipeFab from "@/components/UI/TheRecipeFab"; | ||||||
| import Vuetify from "./plugins/vuetify"; | import Vuetify from "./plugins/vuetify"; | ||||||
| import { user } from "@/mixins/user"; | import { user } from "@/mixins/user"; | ||||||
|  |  | ||||||
| @@ -28,7 +28,7 @@ export default { | |||||||
|  |  | ||||||
|   components: { |   components: { | ||||||
|     TheAppBar, |     TheAppBar, | ||||||
|     AddRecipeFab, |     TheRecipeFab, | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   mixins: [user], |   mixins: [user], | ||||||
| @@ -40,13 +40,12 @@ export default { | |||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   created() { |   async created() { | ||||||
|     window.addEventListener("keyup", e => { |     window.addEventListener("keyup", e => { | ||||||
|       if (e.key == "/" && !document.activeElement.id.startsWith("input")) { |       if (e.key == "/" && !document.activeElement.id.startsWith("input")) { | ||||||
|         this.search = !this.search; |         this.search = !this.search; | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|     this.$store.dispatch("initLang", { currentVueComponent: this }); |  | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async mounted() { |   async mounted() { | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { baseURL } from "./api-utils"; | import { baseURL } from "./api-utils"; | ||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
|  | import { store } from "@/store"; | ||||||
|  |  | ||||||
| const settingsBase = baseURL + "site-settings"; | const settingsBase = baseURL + "site-settings"; | ||||||
|  |  | ||||||
| @@ -19,6 +20,7 @@ export const siteSettingsAPI =  { | |||||||
|  |  | ||||||
|   async update(body) { |   async update(body) { | ||||||
|     let response = await apiReq.put(settingsURLs.updateSiteSettings, body); |     let response = await apiReq.put(settingsURLs.updateSiteSettings, body); | ||||||
|  |     store.dispatch("requestSiteSettings"); | ||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,38 +0,0 @@ | |||||||
| <template> |  | ||||||
|   <v-card> |  | ||||||
|     <v-card-title>Last Scrapped JSON Data</v-card-title> |  | ||||||
|     <v-card-text> |  | ||||||
|       <VJsoneditor |  | ||||||
|         @error="logError()" |  | ||||||
|         v-model="lastRecipeJson" |  | ||||||
|         height="1500px" |  | ||||||
|         :options="jsonEditorOptions" |  | ||||||
|       /> |  | ||||||
|     </v-card-text> |  | ||||||
|   </v-card> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script> |  | ||||||
| import VJsoneditor from "v-jsoneditor"; |  | ||||||
| import { api } from "@/api"; |  | ||||||
| export default { |  | ||||||
|   components: { VJsoneditor }, |  | ||||||
|   data() { |  | ||||||
|     return { |  | ||||||
|       lastRecipeJson: {}, |  | ||||||
|       jsonEditorOptions: { |  | ||||||
|         mode: "code", |  | ||||||
|         search: false, |  | ||||||
|         mainMenuBar: false, |  | ||||||
|       }, |  | ||||||
|     }; |  | ||||||
|   }, |  | ||||||
|  |  | ||||||
|   async mounted() { |  | ||||||
|     this.lastRecipeJson = await api.meta.getLastJson(); |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style> |  | ||||||
| </style> |  | ||||||
| @@ -1,37 +0,0 @@ | |||||||
| <template> |  | ||||||
|   <v-card> |  | ||||||
|     <v-card-title>Last Scrapped JSON Data</v-card-title> |  | ||||||
|     <v-card-text> |  | ||||||
|       <VJsoneditor |  | ||||||
|         @error="logError()" |  | ||||||
|         v-model="lastRecipeJson" |  | ||||||
|         height="1500px" |  | ||||||
|         :options="jsonEditorOptions" |  | ||||||
|       /> |  | ||||||
|     </v-card-text> |  | ||||||
|   </v-card> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script> |  | ||||||
| import VJsoneditor from "v-jsoneditor"; |  | ||||||
| export default { |  | ||||||
|   components: { VJsoneditor }, |  | ||||||
|   data() { |  | ||||||
|     return { |  | ||||||
|       lastRecipeJson: "", |  | ||||||
|       jsonEditorOptions: { |  | ||||||
|         mode: "code", |  | ||||||
|         search: false, |  | ||||||
|         mainMenuBar: false, |  | ||||||
|       }, |  | ||||||
|     }; |  | ||||||
|   }, |  | ||||||
|  |  | ||||||
|   async mounted() { |  | ||||||
|     this.lastRecipeJson = "Hello \n 123 \n 567" |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style> |  | ||||||
| </style> |  | ||||||
							
								
								
									
										48
									
								
								frontend/src/components/FormHelpers/LanguageSelector.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								frontend/src/components/FormHelpers/LanguageSelector.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | <template> | ||||||
|  |   <v-select | ||||||
|  |     dense | ||||||
|  |     :items="allLanguages" | ||||||
|  |     item-text="name" | ||||||
|  |     :label="$t('settings.language')" | ||||||
|  |     prepend-icon="mdi-translate" | ||||||
|  |     :value="selectedItem" | ||||||
|  |     @input="setLanguage" | ||||||
|  |   > | ||||||
|  |   </v-select> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | const SELECT_EVENT = "select-lang"; | ||||||
|  | export default { | ||||||
|  |   props: { | ||||||
|  |     siteSettings: { | ||||||
|  |       default: false, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   data: function() { | ||||||
|  |     return { | ||||||
|  |       selectedItem: 0, | ||||||
|  |       items: [ | ||||||
|  |         { | ||||||
|  |           name: "English", | ||||||
|  |           value: "en-US", | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   mounted() { | ||||||
|  |     this.selectedItem = this.$store.getters.getActiveLang; | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     allLanguages() { | ||||||
|  |       return this.$store.getters.getAllLangs; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   methods: { | ||||||
|  |     setLanguage(selectedLanguage) { | ||||||
|  |       this.$emit(SELECT_EVENT, selectedLanguage); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
| @@ -45,7 +45,7 @@ | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import DataTable from "@/components/Admin/Backup/ImportSummaryDialog/DataTable"; | import DataTable from "@/components/ImportSummaryDialog"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     DataTable, |     DataTable, | ||||||
| @@ -31,11 +31,7 @@ | |||||||
|                 v-on="on" |                 v-on="on" | ||||||
|               ></v-text-field> |               ></v-text-field> | ||||||
|             </template> |             </template> | ||||||
|             <DatePicker  |             <DatePicker v-model="startDate" no-title @input="menu2 = false" /> | ||||||
|               v-model="startDate" |  | ||||||
|               no-title |  | ||||||
|               @input="menu2 = false" |  | ||||||
|               /> |  | ||||||
|           </v-menu> |           </v-menu> | ||||||
|         </v-col> |         </v-col> | ||||||
|         <v-col cols="12" lg="6" md="6" sm="12"> |         <v-col cols="12" lg="6" md="6" sm="12"> | ||||||
| @@ -59,11 +55,7 @@ | |||||||
|                 v-on="on" |                 v-on="on" | ||||||
|               ></v-text-field> |               ></v-text-field> | ||||||
|             </template> |             </template> | ||||||
|             <DatePicker  |             <DatePicker v-model="endDate" no-title @input="menu2 = false" /> | ||||||
|               v-model="endDate" |  | ||||||
|               no-title |  | ||||||
|               @input="menu2 = false" |  | ||||||
|               /> |  | ||||||
|           </v-menu> |           </v-menu> | ||||||
|         </v-col> |         </v-col> | ||||||
|       </v-row> |       </v-row> | ||||||
| @@ -87,7 +79,7 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| const CREATE_EVENT = "created"; | const CREATE_EVENT = "created"; | ||||||
| import DatePicker from "../UI/DatePicker"; | import DatePicker from "@/components/FormHelpers/DatePicker"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import utils from "@/utils"; | import utils from "@/utils"; | ||||||
| import MealPlanCard from "./MealPlanCard"; | import MealPlanCard from "./MealPlanCard"; | ||||||
|   | |||||||
| @@ -1,5 +1,20 @@ | |||||||
| <template> | <template> | ||||||
|   <v-toolbar class="card-btn" flat height="0" extension-height="0"> |   <v-expand-transition> | ||||||
|  |     <v-toolbar | ||||||
|  |       class="card-btn pt-1" | ||||||
|  |       flat | ||||||
|  |       :height="isSticky ? null : '0'" | ||||||
|  |       :extension-height="isSticky ? '20' : '0'" | ||||||
|  |       color="rgb(255, 0, 0, 0.0)" | ||||||
|  |     > | ||||||
|  |       <ConfirmationDialog | ||||||
|  |         :title="$t('recipe.delete-recipe')" | ||||||
|  |         :message="$t('recipe.delete-ConfirmationDialog')" | ||||||
|  |         color="error" | ||||||
|  |         icon="mdi-alert-circle" | ||||||
|  |         ref="deleteRecipieConfirm" | ||||||
|  |         v-on:confirm="deleteRecipe()" | ||||||
|  |       /> | ||||||
|       <template v-slot:extension> |       <template v-slot:extension> | ||||||
|         <v-col></v-col> |         <v-col></v-col> | ||||||
|         <div v-if="open"> |         <div v-if="open"> | ||||||
| @@ -13,14 +28,7 @@ | |||||||
|           > |           > | ||||||
|             <v-icon>mdi-delete</v-icon> |             <v-icon>mdi-delete</v-icon> | ||||||
|           </v-btn> |           </v-btn> | ||||||
|         <Confirmation |  | ||||||
|           :title="$t('recipe.delete-recipe')" |  | ||||||
|           :message="$t('recipe.delete-confirmation')" |  | ||||||
|           color="error" |  | ||||||
|           icon="mdi-alert-circle" |  | ||||||
|           ref="deleteRecipieConfirm" |  | ||||||
|           v-on:confirm="deleteRecipe()" |  | ||||||
|         /> |  | ||||||
|           <v-btn class="mr-2" fab dark small color="success" @click="save"> |           <v-btn class="mr-2" fab dark small color="success" @click="save"> | ||||||
|             <v-icon>mdi-content-save</v-icon> |             <v-icon>mdi-content-save</v-icon> | ||||||
|           </v-btn> |           </v-btn> | ||||||
| @@ -33,10 +41,11 @@ | |||||||
|         </v-btn> |         </v-btn> | ||||||
|       </template> |       </template> | ||||||
|     </v-toolbar> |     </v-toolbar> | ||||||
|  |   </v-expand-transition> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import Confirmation from "../../components/UI/Confirmation.vue"; | import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog.vue"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   props: { |   props: { | ||||||
| @@ -47,7 +56,25 @@ export default { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   components: { |   components: { | ||||||
|     Confirmation, |     ConfirmationDialog, | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       stickyTop: 50, | ||||||
|  |       scrollPosition: null, | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   mounted() { | ||||||
|  |     window.addEventListener("scroll", this.updateScroll); | ||||||
|  |   }, | ||||||
|  |   destroy() { | ||||||
|  |     window.removeEventListener("scroll", this.updateScroll); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   computed: { | ||||||
|  |     isSticky() { | ||||||
|  |       return this.scrollPosition >= 500; | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   methods: { |   methods: { | ||||||
| @@ -57,6 +84,9 @@ export default { | |||||||
|     save() { |     save() { | ||||||
|       this.$emit("save"); |       this.$emit("save"); | ||||||
|     }, |     }, | ||||||
|  |     updateScroll() { | ||||||
|  |       this.scrollPosition = window.scrollY; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|     deleteRecipeConfrim() { |     deleteRecipeConfrim() { | ||||||
|       this.$refs.deleteRecipieConfirm.open(); |       this.$refs.deleteRecipieConfirm.open(); | ||||||
|   | |||||||
| @@ -1,23 +1,39 @@ | |||||||
| <template> | <template> | ||||||
|   <v-card |   <v-card | ||||||
|  |     class="mx-auto" | ||||||
|     hover |     hover | ||||||
|     :to="`/recipe/${slug}`" |     :to="`/recipe/${slug}`" | ||||||
|     max-height="125" |  | ||||||
|     @click="$emit('selected')" |     @click="$emit('selected')" | ||||||
|   > |   > | ||||||
|     <v-list-item> |     <v-list-item three-line> | ||||||
|       <v-list-item-avatar rounded size="125" class="mt-0 ml-n4"> |       <v-list-item-avatar | ||||||
|         <v-img :src="getImage(slug)"> </v-img> |         tile | ||||||
|       </v-list-item-avatar> |         size="125" | ||||||
|       <v-list-item-content class="align-self-start"> |         color="grey" | ||||||
|         <v-list-item-title> |         class="v-mobile-img rounded-sm my-0 ml-n4" | ||||||
|           {{ name }} |       > | ||||||
|         </v-list-item-title> |         <v-img :src="getImage(slug)" lazy-src=""></v-img | ||||||
|         <v-rating length="5" size="16" dense :value="rating"></v-rating> |       ></v-list-item-avatar> | ||||||
|         <div class="text"> |       <v-list-item-content> | ||||||
|           <v-list-item-action-text> |         <v-list-item-title class=" mb-1">{{ name }}</v-list-item-title> | ||||||
|             {{ description | truncate(115) }} |         <v-list-item-subtitle> {{ description }} </v-list-item-subtitle> | ||||||
|           </v-list-item-action-text> |         <div class="d-flex justify-center align-center"> | ||||||
|  |           <RecipeChips | ||||||
|  |             :items="tags" | ||||||
|  |             :title="false" | ||||||
|  |             :limit="1" | ||||||
|  |             :small="true" | ||||||
|  |             :isCategory="false" | ||||||
|  |           /> | ||||||
|  |           <v-rating | ||||||
|  |             color="secondary" | ||||||
|  |             class="ml-auto" | ||||||
|  |             background-color="secondary lighten-3" | ||||||
|  |             dense | ||||||
|  |             length="5" | ||||||
|  |             size="15" | ||||||
|  |             :value="rating" | ||||||
|  |           ></v-rating> | ||||||
|         </div> |         </div> | ||||||
|       </v-list-item-content> |       </v-list-item-content> | ||||||
|     </v-list-item> |     </v-list-item> | ||||||
| @@ -25,8 +41,12 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|  | import RecipeChips from "@/components/Recipe/RecipeViewer/RecipeChips"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| export default { | export default { | ||||||
|  |   components: { | ||||||
|  |     RecipeChips, | ||||||
|  |   }, | ||||||
|   props: { |   props: { | ||||||
|     name: String, |     name: String, | ||||||
|     slug: String, |     slug: String, | ||||||
| @@ -36,6 +56,9 @@ export default { | |||||||
|     route: { |     route: { | ||||||
|       default: true, |       default: true, | ||||||
|     }, |     }, | ||||||
|  |     tags: { | ||||||
|  |       default: true, | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   methods: { |   methods: { | ||||||
| @@ -47,6 +70,11 @@ export default { | |||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style> | <style> | ||||||
|  | .v-mobile-img { | ||||||
|  |   padding-top: 0; | ||||||
|  |   padding-bottom: 0; | ||||||
|  |   padding-left: 0; | ||||||
|  | } | ||||||
| .v-card--reveal { | .v-card--reveal { | ||||||
|   align-items: center; |   align-items: center; | ||||||
|   bottom: 0; |   bottom: 0; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
|           <div> |           <div> | ||||||
|             Recipe Image |             Recipe Image | ||||||
|           </div> |           </div> | ||||||
|           <UploadBtn |           <TheUploadBtn | ||||||
|             class="ml-auto" |             class="ml-auto" | ||||||
|             url="none" |             url="none" | ||||||
|             file-name="image" |             file-name="image" | ||||||
| @@ -44,12 +44,12 @@ | |||||||
| <script> | <script> | ||||||
| const REFRESH_EVENT = "refresh"; | const REFRESH_EVENT = "refresh"; | ||||||
| const UPLOAD_EVENT = "upload"; | const UPLOAD_EVENT = "upload"; | ||||||
| import UploadBtn from "@/components/UI/UploadBtn"; | import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| // import axios from "axios"; | // import axios from "axios"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     UploadBtn, |     TheUploadBtn, | ||||||
|   }, |   }, | ||||||
|   props: { |   props: { | ||||||
|     slug: String, |     slug: String, | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ | |||||||
|         class="my-3" |         class="my-3" | ||||||
|         :label="$t('recipe.recipe-name')" |         :label="$t('recipe.recipe-name')" | ||||||
|         v-model="value.name" |         v-model="value.name" | ||||||
|         :rules="[rules.required]" |         :rules="[existsRule]" | ||||||
|       > |       > | ||||||
|       </v-text-field> |       </v-text-field> | ||||||
|       <v-textarea |       <v-textarea | ||||||
| @@ -94,7 +94,7 @@ | |||||||
|                       class="mr-n1" |                       class="mr-n1" | ||||||
|                       slot="prepend" |                       slot="prepend" | ||||||
|                       color="error" |                       color="error" | ||||||
|                       @click="removeIngredient(index)" |                       @click="removeByIndex(value.recipeIngredient, index)" | ||||||
|                     > |                     > | ||||||
|                       mdi-delete |                       mdi-delete | ||||||
|                     </v-icon> |                     </v-icon> | ||||||
| @@ -107,7 +107,7 @@ | |||||||
|           <v-btn color="secondary" fab dark small @click="addIngredient"> |           <v-btn color="secondary" fab dark small @click="addIngredient"> | ||||||
|             <v-icon>mdi-plus</v-icon> |             <v-icon>mdi-plus</v-icon> | ||||||
|           </v-btn> |           </v-btn> | ||||||
|           <BulkAdd @bulk-data="appendIngredients" /> |           <BulkAdd @bulk-data="addIngredient" /> | ||||||
|  |  | ||||||
|           <h2 class="mt-6">{{ $t("recipe.categories") }}</h2> |           <h2 class="mt-6">{{ $t("recipe.categories") }}</h2> | ||||||
|           <CategoryTagSelector |           <CategoryTagSelector | ||||||
| @@ -140,7 +140,7 @@ | |||||||
|                   color="white" |                   color="white" | ||||||
|                   class="mr-2" |                   class="mr-2" | ||||||
|                   elevation="0" |                   elevation="0" | ||||||
|                   @click="removeNote(index)" |                   @click="removeByIndex(value.notes, index)" | ||||||
|                 > |                 > | ||||||
|                   <v-icon color="error">mdi-delete</v-icon> |                   <v-icon color="error">mdi-delete</v-icon> | ||||||
|                 </v-btn> |                 </v-btn> | ||||||
| @@ -183,7 +183,7 @@ | |||||||
|                     color="white" |                     color="white" | ||||||
|                     class="mr-2" |                     class="mr-2" | ||||||
|                     elevation="0" |                     elevation="0" | ||||||
|                     @click="removeStep(index)" |                     @click="removeByIndex(value.recipeInstructions, index)" | ||||||
|                   > |                   > | ||||||
|                     <v-icon size="24" color="error">mdi-delete</v-icon> |                     <v-icon size="24" color="error">mdi-delete</v-icon> | ||||||
|                   </v-btn> |                   </v-btn> | ||||||
| @@ -218,6 +218,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|  | const UPLOAD_EVENT = "upload"; | ||||||
| import draggable from "vuedraggable"; | import draggable from "vuedraggable"; | ||||||
| import utils from "@/utils"; | import utils from "@/utils"; | ||||||
| import BulkAdd from "./BulkAdd"; | import BulkAdd from "./BulkAdd"; | ||||||
| @@ -225,6 +226,7 @@ import ExtrasEditor from "./ExtrasEditor"; | |||||||
| import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector"; | import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector"; | ||||||
| import NutritionEditor from "./NutritionEditor"; | import NutritionEditor from "./NutritionEditor"; | ||||||
| import ImageUploadBtn from "./ImageUploadBtn.vue"; | import ImageUploadBtn from "./ImageUploadBtn.vue"; | ||||||
|  | import { validators } from "@/mixins/validators"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     BulkAdd, |     BulkAdd, | ||||||
| @@ -237,26 +239,20 @@ export default { | |||||||
|   props: { |   props: { | ||||||
|     value: Object, |     value: Object, | ||||||
|   }, |   }, | ||||||
|  |   mixins: [validators], | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       drag: false, |       drag: false, | ||||||
|       fileObject: null, |       fileObject: null, | ||||||
|       rules: { |  | ||||||
|         required: v => !!v || this.$i18n.t("recipe.key-name-required"), |  | ||||||
|         whiteSpace: v => |  | ||||||
|           !v || |  | ||||||
|           v.split(" ").length <= 1 || |  | ||||||
|           this.$i18n.t("recipe.no-white-space-allowed"), |  | ||||||
|       }, |  | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     uploadImage(fileObject) { |     uploadImage(fileObject) { | ||||||
|       this.$emit("upload", fileObject); |       this.$emit(UPLOAD_EVENT, fileObject); | ||||||
|     }, |     }, | ||||||
|     toggleDisabled(stepIndex) { |     toggleDisabled(stepIndex) { | ||||||
|       if (this.disabledSteps.includes(stepIndex)) { |       if (this.disabledSteps.includes(stepIndex)) { | ||||||
|         let index = this.disabledSteps.indexOf(stepIndex); |         const index = this.disabledSteps.indexOf(stepIndex); | ||||||
|         if (index !== -1) { |         if (index !== -1) { | ||||||
|           this.disabledSteps.splice(index, 1); |           this.disabledSteps.splice(index, 1); | ||||||
|         } |         } | ||||||
| @@ -265,66 +261,40 @@ export default { | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     isDisabled(stepIndex) { |     isDisabled(stepIndex) { | ||||||
|       if (this.disabledSteps.includes(stepIndex)) { |       return this.disabledSteps.includes(stepIndex) ? "disabled-card" : null; | ||||||
|         return "disabled-card"; |  | ||||||
|       } else { |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|     generateKey(item, index) { |     generateKey(item, index) { | ||||||
|       return utils.generateUniqueKey(item, index); |       return utils.generateUniqueKey(item, index); | ||||||
|     }, |     }, | ||||||
|  |     addIngredient(ingredients = null) { | ||||||
|     appendIngredients(ingredients) { |       if (ingredients) { | ||||||
|         this.value.recipeIngredient.push(...ingredients); |         this.value.recipeIngredient.push(...ingredients); | ||||||
|     }, |       } else { | ||||||
|     addIngredient() { |         this.value.recipeIngredient.push(""); | ||||||
|       let list = this.value.recipeIngredient; |       } | ||||||
|       list.push(""); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     removeIngredient(index) { |  | ||||||
|       this.value.recipeIngredient.splice(index, 1); |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     appendSteps(steps) { |     appendSteps(steps) { | ||||||
|       let processSteps = []; |       this.value.recipeInstructions.push( | ||||||
|       steps.forEach(element => { |         ...steps.map(x => ({ | ||||||
|         processSteps.push({ text: element }); |           text: x, | ||||||
|       }); |         })) | ||||||
|  |       ); | ||||||
|       this.value.recipeInstructions.push(...processSteps); |  | ||||||
|     }, |     }, | ||||||
|     addStep() { |     addStep() { | ||||||
|       let list = this.value.recipeInstructions; |       this.value.recipeInstructions.push({ text: "" }); | ||||||
|       list.push({ text: "" }); |  | ||||||
|     }, |     }, | ||||||
|     removeStep(index) { |  | ||||||
|       this.value.recipeInstructions.splice(index, 1); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     addNote() { |     addNote() { | ||||||
|       let list = this.value.notes; |       this.value.notes.push({ text: "" }); | ||||||
|       list.push({ text: "" }); |  | ||||||
|     }, |  | ||||||
|     removeNote(index) { |  | ||||||
|       this.value.notes.splice(index, 1); |  | ||||||
|     }, |  | ||||||
|     removeCategory(index) { |  | ||||||
|       this.value.recipeCategory.splice(index, 1); |  | ||||||
|     }, |  | ||||||
|     removeTags(index) { |  | ||||||
|       this.value.tags.splice(index, 1); |  | ||||||
|     }, |     }, | ||||||
|     saveExtras(extras) { |     saveExtras(extras) { | ||||||
|       this.value.extras = extras; |       this.value.extras = extras; | ||||||
|     }, |     }, | ||||||
|  |     removeByIndex(list, index) { | ||||||
|  |       list.splice(index, 1); | ||||||
|  |     }, | ||||||
|     validateRecipe() { |     validateRecipe() { | ||||||
|       if (this.$refs.form.validate()) { |       return this.$refs.form.validate(); | ||||||
|         return true; |  | ||||||
|       } else { |  | ||||||
|         return false; |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,57 +1,26 @@ | |||||||
| <template> | <template> | ||||||
|   <v-card |   <v-card | ||||||
|     color="accent" |     color="accent" | ||||||
|     class="custom-transparent d-flex justify-start align-center text-center " |     class="custom-transparent d-flex justify-start align-center text-center time-card-flex" | ||||||
|     tile |     tile | ||||||
|     :width="`${timeCardWidth}`" |     v-if="showCards" | ||||||
|     height="55" |  | ||||||
|     v-if="totalTime || prepTime || performTime" |  | ||||||
|   > |   > | ||||||
|     <v-card flat color="rgb(255, 0, 0, 0.0)"> |     <v-card flat color="rgb(255, 0, 0, 0.0)"> | ||||||
|       <v-icon large color="white" class="mx-2"> mdi-clock-outline </v-icon> |       <v-icon large color="white" class="mx-2"> mdi-clock-outline </v-icon> | ||||||
|     </v-card> |     </v-card> | ||||||
|  |  | ||||||
|     <v-divider vertical color="white" class="py-1" v-if="totalTime"> |  | ||||||
|     </v-divider> |  | ||||||
|     <v-card flat color="rgb(255, 0, 0, 0.0)" class=" my-2 " v-if="totalTime"> |  | ||||||
|       <v-card-text class="white--text"> |  | ||||||
|         <div> |  | ||||||
|           <strong> {{ $t("recipe.total-time") }} </strong> |  | ||||||
|         </div> |  | ||||||
|         <div>{{ totalTime }}</div> |  | ||||||
|       </v-card-text> |  | ||||||
|     </v-card> |  | ||||||
|  |  | ||||||
|     <v-divider vertical color="white" class="py-1" v-if="prepTime"> </v-divider> |  | ||||||
|  |  | ||||||
|     <v-card |     <v-card | ||||||
|  |       v-for="(time, index) in allTimes" | ||||||
|  |       :key="index" | ||||||
|  |       class="d-flex justify-start align-center text-center time-card-flex" | ||||||
|       flat |       flat | ||||||
|       color="rgb(255, 0, 0, 0.0)" |       color="rgb(255, 0, 0, 0.0)" | ||||||
|       class="white--text my-2 " |  | ||||||
|       v-if="prepTime" |  | ||||||
|     > |     > | ||||||
|       <v-card-text class="white--text"> |       <v-card-text class="caption white--text py-2"> | ||||||
|         <div> |         <div> | ||||||
|           <strong> {{ $t("recipe.prep-time") }} </strong> |           <strong> {{ time.name }} </strong> | ||||||
|         </div> |         </div> | ||||||
|         <div>{{ prepTime }}</div> |         <div>{{ time.value }}</div> | ||||||
|       </v-card-text> |  | ||||||
|     </v-card> |  | ||||||
|  |  | ||||||
|     <v-divider vertical color="white" class="my-1" v-if="performTime"> |  | ||||||
|     </v-divider> |  | ||||||
|  |  | ||||||
|     <v-card |  | ||||||
|       flat |  | ||||||
|       color="rgb(255, 0, 0, 0.0)" |  | ||||||
|       class="white--text py-2 " |  | ||||||
|       v-if="performTime" |  | ||||||
|     > |  | ||||||
|       <v-card-text class="white--text"> |  | ||||||
|         <div> |  | ||||||
|           <strong> {{ $t("recipe.perform-time") }} </strong> |  | ||||||
|         </div> |  | ||||||
|         <div>{{ performTime }}</div> |  | ||||||
|       </v-card-text> |       </v-card-text> | ||||||
|     </v-card> |     </v-card> | ||||||
|   </v-card> |   </v-card> | ||||||
| @@ -64,52 +33,52 @@ export default { | |||||||
|     totalTime: String, |     totalTime: String, | ||||||
|     performTime: String, |     performTime: String, | ||||||
|   }, |   }, | ||||||
|  |   watch: { | ||||||
|  |     showCards(val) { | ||||||
|  |       console.log(val); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     timeLength() { |     showCards() { | ||||||
|       let times = []; |       return [this.prepTime, this.totalTime, this.performTime].some( | ||||||
|       let timeArray = [this.totalTime, this.prepTime, this.performTime]; |         x => !this.isEmpty(x) | ||||||
|       timeArray.forEach(element => { |       ); | ||||||
|         if (element) { |  | ||||||
|           times.push(element); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|  |  | ||||||
|       return times.length; |  | ||||||
|     }, |     }, | ||||||
|     iconColumn() { |     allTimes() { | ||||||
|       switch (this.timeLength) { |       return [ | ||||||
|         case 0: |         this.validateTotalTime, | ||||||
|           return null; |         this.validatePrepTime, | ||||||
|         case 1: |         this.validatePerformTime, | ||||||
|           return 4; |       ].filter(x => x !== null); | ||||||
|         case 2: |  | ||||||
|           return 3; |  | ||||||
|         case 3: |  | ||||||
|           return 2; |  | ||||||
|         default: |  | ||||||
|           return 1; |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|     timeCardWidth() { |     validateTotalTime() { | ||||||
|       let timeArray = [this.totalTime, this.prepTime, this.performTime]; |       return !this.isEmpty(this.totalTime) | ||||||
|       let width = 80; |         ? { name: this.$t("recipe.total-time"), value: this.totalTime } | ||||||
|       timeArray.forEach(element => { |         : null; | ||||||
|         if (element) { |     }, | ||||||
|           width += 95; |     validatePrepTime() { | ||||||
|         } |       return !this.isEmpty(this.prepTime) | ||||||
|       }); |         ? { name: this.$t("recipe.prep-time"), value: this.prepTime } | ||||||
|  |         : null; | ||||||
|       if (this.$vuetify.breakpoint.name === "xs") { |     }, | ||||||
|         return "100%"; |     validatePerformTime() { | ||||||
|       } |       return !this.isEmpty(this.performTime) | ||||||
|  |         ? { name: this.$t("recipe.perform-time"), value: this.performTime } | ||||||
|       return `${width}px`; |         : null; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     isEmpty(str) { | ||||||
|  |       return !str || str.length === 0; | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style scoped> | <style scoped> | ||||||
|  | .time-card-flex { | ||||||
|  |   width: fit-content; | ||||||
|  | } | ||||||
| .custom-transparent { | .custom-transparent { | ||||||
|   opacity: 0.7; |   opacity: 0.7; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -73,6 +73,7 @@ | |||||||
|             :slug="recipe.slug" |             :slug="recipe.slug" | ||||||
|             :rating="recipe.rating" |             :rating="recipe.rating" | ||||||
|             :image="recipe.image" |             :image="recipe.image" | ||||||
|  |             :tags="recipe.tags" | ||||||
|           /> |           /> | ||||||
|         </v-col> |         </v-col> | ||||||
|       </v-row> |       </v-row> | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|     @keydown.esc="cancel" |     @keydown.esc="cancel" | ||||||
|   > |   > | ||||||
|     <v-card> |     <v-card> | ||||||
|       <v-app-bar v-if="Boolean(title)" :color="color" dense flat dark> |       <v-app-bar v-if="Boolean(title)" :color="color" dense  dark> | ||||||
|         <v-icon v-if="Boolean(icon)" left> {{ icon }}</v-icon> |         <v-icon v-if="Boolean(icon)" left> {{ icon }}</v-icon> | ||||||
|         <v-toolbar-title v-text="title" /> |         <v-toolbar-title v-text="title" /> | ||||||
|       </v-app-bar> |       </v-app-bar> | ||||||
| @@ -36,13 +36,13 @@ | |||||||
| const CLOSE_EVENT = "close"; | const CLOSE_EVENT = "close"; | ||||||
| const OPEN_EVENT = "open"; | const OPEN_EVENT = "open"; | ||||||
| /** | /** | ||||||
|  * Confirmation Component used to add a second validaion step to an action. |  * ConfirmationDialog Component used to add a second validaion step to an action. | ||||||
|  * @version 1.0.1 |  * @version 1.0.1 | ||||||
|  * @author [zackbcom](https://github.com/zackbcom) |  * @author [zackbcom](https://github.com/zackbcom) | ||||||
|  * @since Version 1.0.0 |  * @since Version 1.0.0 | ||||||
|  */ |  */ | ||||||
| export default { | export default { | ||||||
|   name: "Confirmation", |   name: "ConfirmationDialog", | ||||||
|   props: { |   props: { | ||||||
|     /** |     /** | ||||||
|      * Message to be in body. |      * Message to be in body. | ||||||
| @@ -1,87 +0,0 @@ | |||||||
| <template> |  | ||||||
|   <div class="text-center"> |  | ||||||
|     <v-menu |  | ||||||
|       transition="slide-x-transition" |  | ||||||
|       bottom |  | ||||||
|       right |  | ||||||
|       offset-y |  | ||||||
|       close-delay="200" |  | ||||||
|     > |  | ||||||
|       <template v-slot:activator="{ on, attrs }"> |  | ||||||
|         <v-btn v-bind="attrs" v-on="on" icon> |  | ||||||
|           <v-icon>mdi-translate</v-icon> |  | ||||||
|         </v-btn> |  | ||||||
|       </template> |  | ||||||
|  |  | ||||||
|       <v-list> |  | ||||||
|         <v-list-item-group v-model="selectedItem" color="primary"> |  | ||||||
|           <v-list-item |  | ||||||
|             v-for="(item, i) in allLanguages" |  | ||||||
|             :key="i" |  | ||||||
|             link |  | ||||||
|             @click="setLanguage(item.value)" |  | ||||||
|           > |  | ||||||
|             <v-list-item-content> |  | ||||||
|               <v-list-item-title> |  | ||||||
|                 {{ item.name }} |  | ||||||
|               </v-list-item-title> |  | ||||||
|             </v-list-item-content> |  | ||||||
|           </v-list-item> |  | ||||||
|         </v-list-item-group> |  | ||||||
|       </v-list> |  | ||||||
|     </v-menu> |  | ||||||
|   </div> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script> |  | ||||||
| const SELECT_EVENT = "select-lang"; |  | ||||||
| export default { |  | ||||||
|   props: { |  | ||||||
|     siteSettings: { |  | ||||||
|       default: false, |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   data: function() { |  | ||||||
|     return { |  | ||||||
|       selectedItem: 0, |  | ||||||
|       items: [ |  | ||||||
|         { |  | ||||||
|           name: "English", |  | ||||||
|           value: "en-US", |  | ||||||
|         }, |  | ||||||
|       ], |  | ||||||
|     }; |  | ||||||
|   }, |  | ||||||
|   mounted() { |  | ||||||
|     let active = this.$store.getters.getActiveLang; |  | ||||||
|     this.allLanguages.forEach((element, index) => { |  | ||||||
|       if (element.value === active) { |  | ||||||
|         this.selectedItem = index; |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|   }, |  | ||||||
|   computed: { |  | ||||||
|     allLanguages() { |  | ||||||
|       return this.$store.getters.getAllLangs; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|  |  | ||||||
|   methods: { |  | ||||||
|     setLanguage(selectedLanguage) { |  | ||||||
|       if (this.siteSettings) { |  | ||||||
|         this.$emit(SELECT_EVENT, selectedLanguage); |  | ||||||
|       } else { |  | ||||||
|         this.$store.dispatch("setLang", {  |  | ||||||
|           currentVueComponent: this,  |  | ||||||
|           language: selectedLanguage }); |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
| </script> |  | ||||||
| <style> |  | ||||||
| .menu-text { |  | ||||||
|   text-align: left !important; |  | ||||||
| } |  | ||||||
| </style> |  | ||||||
| @@ -1,66 +0,0 @@ | |||||||
| <template> |  | ||||||
|   <v-dialog |  | ||||||
|     v-model="dialog" |  | ||||||
|     max-width="900px" |  | ||||||
|     :fullscreen="$vuetify.breakpoint.xsOnly" |  | ||||||
|   > |  | ||||||
|     <v-card> |  | ||||||
|       <v-toolbar dark color="primary" v-show="$vuetify.breakpoint.xsOnly"> |  | ||||||
|         <v-btn icon dark @click="dialog = false"> |  | ||||||
|           <v-icon>mdi-close</v-icon> |  | ||||||
|         </v-btn> |  | ||||||
|         <v-toolbar-title>{{ title }}</v-toolbar-title> |  | ||||||
|         <v-spacer></v-spacer> |  | ||||||
|         <v-toolbar-items></v-toolbar-items> |  | ||||||
|       </v-toolbar> |  | ||||||
|       <v-card-title v-show="$vuetify.breakpoint.smAndUp"> |  | ||||||
|         {{ title }} |  | ||||||
|       </v-card-title> |  | ||||||
|       <v-card-text class="mt-3"> |  | ||||||
|         <v-row> |  | ||||||
|           <v-col> |  | ||||||
|             <v-alert outlined dense type="success"> |  | ||||||
|               <h4>{{ successHeader }}</h4> |  | ||||||
|               <p v-for="success in this.success" :key="success" class="my-1"> |  | ||||||
|                 - {{ success }} |  | ||||||
|               </p> |  | ||||||
|             </v-alert> |  | ||||||
|           </v-col> |  | ||||||
|           <v-col> |  | ||||||
|             <v-alert v-if="failed[0]" outlined dense type="error"> |  | ||||||
|               <h4>{{ failedHeader }}</h4> |  | ||||||
|               <p v-for="fail in this.failed" :key="fail" class="my-1"> |  | ||||||
|                 - {{ fail }} |  | ||||||
|               </p> |  | ||||||
|             </v-alert> |  | ||||||
|           </v-col> |  | ||||||
|         </v-row> |  | ||||||
|       </v-card-text> |  | ||||||
|     </v-card> |  | ||||||
|   </v-dialog> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script> |  | ||||||
| export default { |  | ||||||
|   props: { |  | ||||||
|     title: String, |  | ||||||
|     successHeader: String, |  | ||||||
|     success: Array, |  | ||||||
|     failedHeader: String, |  | ||||||
|     failed: Array, |  | ||||||
|   }, |  | ||||||
|   data() { |  | ||||||
|     return { |  | ||||||
|       dialog: false, |  | ||||||
|     }; |  | ||||||
|   }, |  | ||||||
|   methods: { |  | ||||||
|     open() { |  | ||||||
|       this.dialog = true; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style> |  | ||||||
| </style> |  | ||||||
| @@ -35,7 +35,7 @@ | |||||||
|         <v-icon>mdi-magnify</v-icon> |         <v-icon>mdi-magnify</v-icon> | ||||||
|       </v-btn> |       </v-btn> | ||||||
|  |  | ||||||
|       <SiteMenu /> |       <TheSiteMenu /> | ||||||
|     </v-app-bar> |     </v-app-bar> | ||||||
|     <v-app-bar |     <v-app-bar | ||||||
|       v-else |       v-else | ||||||
| @@ -67,13 +67,13 @@ | |||||||
|         <v-icon>mdi-magnify</v-icon> |         <v-icon>mdi-magnify</v-icon> | ||||||
|       </v-btn> |       </v-btn> | ||||||
|  |  | ||||||
|       <SiteMenu /> |       <TheSiteMenu /> | ||||||
|     </v-app-bar> |     </v-app-bar> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import SiteMenu from "@/components/UI/SiteMenu"; | import TheSiteMenu from "@/components/UI/TheSiteMenu"; | ||||||
| import SearchBar from "@/components/UI/Search/SearchBar"; | import SearchBar from "@/components/UI/Search/SearchBar"; | ||||||
| import SearchDialog from "@/components/UI/Search/SearchDialog"; | import SearchDialog from "@/components/UI/Search/SearchDialog"; | ||||||
| import { user } from "@/mixins/user"; | import { user } from "@/mixins/user"; | ||||||
| @@ -82,7 +82,7 @@ export default { | |||||||
|  |  | ||||||
|   mixins: [user], |   mixins: [user], | ||||||
|   components: { |   components: { | ||||||
|     SiteMenu, |     TheSiteMenu, | ||||||
|     SearchBar, |     SearchBar, | ||||||
|     SearchDialog, |     SearchDialog, | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import App from "./App.vue"; | |||||||
| import vuetify from "./plugins/vuetify"; | import vuetify from "./plugins/vuetify"; | ||||||
| import store from "./store"; | import store from "./store"; | ||||||
| import VueRouter from "vue-router"; | import VueRouter from "vue-router"; | ||||||
| import { routes } from "./routes"; | import { router } from "./routes"; | ||||||
| import i18n from "./i18n"; | import i18n from "./i18n"; | ||||||
| import FlashMessage from "@smartweb/vue-flash-message"; | import FlashMessage from "@smartweb/vue-flash-message"; | ||||||
| import "@mdi/font/css/materialdesignicons.css"; | import "@mdi/font/css/materialdesignicons.css"; | ||||||
| @@ -13,25 +13,6 @@ Vue.use(FlashMessage); | |||||||
| Vue.config.productionTip = false; | Vue.config.productionTip = false; | ||||||
| Vue.use(VueRouter); | Vue.use(VueRouter); | ||||||
|  |  | ||||||
| const router = new VueRouter({ |  | ||||||
|   routes, |  | ||||||
|   mode: process.env.NODE_ENV === "production" ? "history" : "hash", |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| const DEFAULT_TITLE = 'Mealie'; |  | ||||||
| const TITLE_SEPARATOR = '🍴'; |  | ||||||
| const TITLE_SUFFIX = " " + TITLE_SEPARATOR + " " + DEFAULT_TITLE; |  | ||||||
| router.afterEach( (to) => { |  | ||||||
|   Vue.nextTick( async () => { |  | ||||||
|     if(typeof to.meta.title === 'function' ) { |  | ||||||
|       const title  = await to.meta.title(to); |  | ||||||
|       document.title = title + TITLE_SUFFIX; |  | ||||||
|     } else { |  | ||||||
|       document.title = to.meta.title ? to.meta.title + TITLE_SUFFIX : DEFAULT_TITLE; |  | ||||||
|     } |  | ||||||
|   }); |  | ||||||
| });   |  | ||||||
|  |  | ||||||
| const vueApp = new Vue({ | const vueApp = new Vue({ | ||||||
|   vuetify, |   vuetify, | ||||||
|   store, |   store, | ||||||
| @@ -56,5 +37,4 @@ let titleCase = function(value) { | |||||||
| Vue.filter("truncate", truncate); | Vue.filter("truncate", truncate); | ||||||
| Vue.filter("titleCase", titleCase); | Vue.filter("titleCase", titleCase); | ||||||
|  |  | ||||||
| export { vueApp }; | export { router, vueApp }; | ||||||
| export { router }; |  | ||||||
|   | |||||||
| @@ -4,12 +4,18 @@ export const validators = { | |||||||
|       emailRule: v => |       emailRule: v => | ||||||
|         !v || |         !v || | ||||||
|         /^[^@\s]+@[^@\s.]+.[^@.\s]+$/.test(v) || |         /^[^@\s]+@[^@\s.]+.[^@.\s]+$/.test(v) || | ||||||
|        this.$t('user.e-mail-must-be-valid'), |         this.$t("user.e-mail-must-be-valid"), | ||||||
|  |  | ||||||
|       existsRule: value => !!value || this.$t('general.field-required'), |       existsRule: value => !!value || this.$t("general.field-required"), | ||||||
|  |  | ||||||
|       minRule: v => |       minRule: v => | ||||||
|         v.length >= 8 || this.$t('user.use-8-characters-or-more-for-your-password'), |         v.length >= 8 || | ||||||
|  |         this.$t("user.use-8-characters-or-more-for-your-password"), | ||||||
|  |  | ||||||
|  |       whiteSpace: v => | ||||||
|  |         !v || | ||||||
|  |         v.split(" ").length <= 1 || | ||||||
|  |         this.$t("recipe.no-white-space-allowed"), | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import TheDownloadBtn from "@/components/UI/TheDownloadBtn"; | import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn"; | ||||||
| export default { | export default { | ||||||
|   components: { TheDownloadBtn }, |   components: { TheDownloadBtn }, | ||||||
|   data() { |   data() { | ||||||
|   | |||||||
| @@ -58,8 +58,8 @@ | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import ImportOptions from "@/components/Admin/Backup/ImportOptions"; | import ImportOptions from "./ImportOptions"; | ||||||
| import TheDownloadBtn from "@/components/UI/TheDownloadBtn.vue"; | import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn.vue"; | ||||||
| import { backupURLs } from "@/api/backup"; | import { backupURLs } from "@/api/backup"; | ||||||
| export default { | export default { | ||||||
|   components: { ImportOptions, TheDownloadBtn }, |   components: { ImportOptions, TheDownloadBtn }, | ||||||
| @@ -42,7 +42,7 @@ | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import ImportOptions from "@/components/Admin/Backup/ImportOptions"; | import ImportOptions from "./ImportOptions"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| export default { | export default { | ||||||
|   components: { ImportOptions }, |   components: { ImportOptions }, | ||||||
| @@ -20,7 +20,7 @@ | |||||||
|       <v-card-title class="mt-n6"> |       <v-card-title class="mt-n6"> | ||||||
|         {{ $t("settings.available-backups") }} |         {{ $t("settings.available-backups") }} | ||||||
|         <span> |         <span> | ||||||
|           <UploadBtn |           <TheUploadBtn | ||||||
|             class="mt-1" |             class="mt-1" | ||||||
|             url="/api/backups/upload" |             url="/api/backups/upload" | ||||||
|             @uploaded="getAvailableBackups" |             @uploaded="getAvailableBackups" | ||||||
| @@ -33,14 +33,7 @@ | |||||||
|         @finished="processFinished" |         @finished="processFinished" | ||||||
|         :backups="availableBackups" |         :backups="availableBackups" | ||||||
|       /> |       /> | ||||||
|       <SuccessFailureAlert |  | ||||||
|         ref="report" |  | ||||||
|         :title="$t('settings.backup.backup-restore-report')" |  | ||||||
|         :success-header="$t('settings.backup.successfully-imported')" |  | ||||||
|         :success="successfulImports" |  | ||||||
|         :failed-header="$t('settings.backup.failed-imports')" |  | ||||||
|         :failed="failedImports" |  | ||||||
|       /> |  | ||||||
|       <ImportSummaryDialog ref="report" :import-data="importData" /> |       <ImportSummaryDialog ref="report" :import-data="importData" /> | ||||||
|     </v-card-text> |     </v-card-text> | ||||||
|   </v-card> |   </v-card> | ||||||
| @@ -48,16 +41,14 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import SuccessFailureAlert from "@/components/UI/SuccessFailureAlert"; | import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn"; | ||||||
| import ImportSummaryDialog from "@/components/Admin/Backup/ImportSummaryDialog"; | import ImportSummaryDialog from "@/components/ImportSummaryDialog"; | ||||||
| import UploadBtn from "@/components/UI/UploadBtn"; | import AvailableBackupCard from "@/pages/Admin/Backup/AvailableBackupCard"; | ||||||
| import AvailableBackupCard from "@/components/Admin/Backup/AvailableBackupCard"; | import NewBackupCard from "@/pages/Admin/Backup/NewBackupCard"; | ||||||
| import NewBackupCard from "@/components/Admin/Backup/NewBackupCard"; |  | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     SuccessFailureAlert, |     TheUploadBtn, | ||||||
|     UploadBtn, |  | ||||||
|     AvailableBackupCard, |     AvailableBackupCard, | ||||||
|     NewBackupCard, |     NewBackupCard, | ||||||
|     ImportSummaryDialog, |     ImportSummaryDialog, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <Confirmation |     <ConfirmationDialog | ||||||
|       ref="deleteGroupConfirm" |       ref="deleteGroupConfirm" | ||||||
|       :title="$t('user.confirm-group-deletion')" |       :title="$t('user.confirm-group-deletion')" | ||||||
|       :message=" |       :message=" | ||||||
| @@ -55,10 +55,10 @@ | |||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| const RENDER_EVENT = "update"; | const RENDER_EVENT = "update"; | ||||||
| import Confirmation from "@/components/UI/Confirmation"; | import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| export default { | export default { | ||||||
|   components: { Confirmation }, |   components: { ConfirmationDialog }, | ||||||
|   props: { |   props: { | ||||||
|     group: { |     group: { | ||||||
|       default: { |       default: { | ||||||
| @@ -85,7 +85,7 @@ | |||||||
| <script> | <script> | ||||||
| import { validators } from "@/mixins/validators"; | import { validators } from "@/mixins/validators"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import GroupCard from "@/components/Admin/ManageUsers/GroupCard"; | import GroupCard from "./GroupCard"; | ||||||
| export default { | export default { | ||||||
|   components: { GroupCard }, |   components: { GroupCard }, | ||||||
|   mixins: [validators], |   mixins: [validators], | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
|   <v-card outlined class="mt-n1"> |   <v-card outlined class="mt-n1"> | ||||||
|     <Confirmation |     <ConfirmationDialog | ||||||
|       ref="deleteUserDialog" |       ref="deleteUserDialog" | ||||||
|       :title="$t('user.confirm-link-deletion')" |       :title="$t('user.confirm-link-deletion')" | ||||||
|       :message=" |       :message=" | ||||||
| @@ -107,11 +107,11 @@ | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import Confirmation from "@/components/UI/Confirmation"; | import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import { validators } from "@/mixins/validators"; | import { validators } from "@/mixins/validators"; | ||||||
| export default { | export default { | ||||||
|   components: { Confirmation }, |   components: { ConfirmationDialog }, | ||||||
|   mixins: [validators], |   mixins: [validators], | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
|   <v-card outlined class="mt-n1"> |   <v-card outlined class="mt-n1"> | ||||||
|     <Confirmation |     <ConfirmationDialog | ||||||
|       ref="deleteUserDialog" |       ref="deleteUserDialog" | ||||||
|       :title="$t('user.confirm-user-deletion')" |       :title="$t('user.confirm-user-deletion')" | ||||||
|       :message=" |       :message=" | ||||||
| @@ -144,11 +144,11 @@ | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import Confirmation from "@/components/UI/Confirmation"; | import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import { validators } from "@/mixins/validators"; | import { validators } from "@/mixins/validators"; | ||||||
| export default { | export default { | ||||||
|   components: { Confirmation }, |   components: { ConfirmationDialog }, | ||||||
|   mixins: [validators], |   mixins: [validators], | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
| @@ -11,17 +11,17 @@ | |||||||
|         <v-tabs-slider></v-tabs-slider> |         <v-tabs-slider></v-tabs-slider> | ||||||
|  |  | ||||||
|         <v-tab> |         <v-tab> | ||||||
|           {{$t('user.users')}} |           {{ $t("user.users") }} | ||||||
|           <v-icon>mdi-account</v-icon> |           <v-icon>mdi-account</v-icon> | ||||||
|         </v-tab> |         </v-tab> | ||||||
|  |  | ||||||
|         <v-tab> |         <v-tab> | ||||||
|           {{$t('user.sign-up-links')}} |           {{ $t("user.sign-up-links") }} | ||||||
|           <v-icon>mdi-account-plus-outline</v-icon> |           <v-icon>mdi-account-plus-outline</v-icon> | ||||||
|         </v-tab> |         </v-tab> | ||||||
|  |  | ||||||
|         <v-tab> |         <v-tab> | ||||||
|           {{$t('user.groups')}} |           {{ $t("user.groups") }} | ||||||
|           <v-icon>mdi-account-group</v-icon> |           <v-icon>mdi-account-group</v-icon> | ||||||
|         </v-tab> |         </v-tab> | ||||||
|       </v-tabs> |       </v-tabs> | ||||||
| @@ -42,9 +42,9 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import TheUserTable from "@/components/Admin/ManageUsers/TheUserTable"; | import TheUserTable from "./TheUserTable"; | ||||||
| import GroupDashboard from "@/components/Admin/ManageUsers/GroupDashboard"; | import GroupDashboard from "./GroupDashboard"; | ||||||
| import TheSignUpTable from "@/components/Admin/ManageUsers/TheSignUpTable"; | import TheSignUpTable from "./TheSignUpTable"; | ||||||
| export default { | export default { | ||||||
|   components: { TheUserTable, GroupDashboard, TheSignUpTable }, |   components: { TheUserTable, GroupDashboard, TheSignUpTable }, | ||||||
|   data() { |   data() { | ||||||
|   | |||||||
| @@ -82,7 +82,7 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import TimePickerDialog from "@/components/Admin/MealPlanner/TimePickerDialog"; | import TimePickerDialog from "@/components/FormHelpers/TimePickerDialog"; | ||||||
| import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector"; | import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|       {{ title }} |       {{ title }} | ||||||
|       <v-spacer></v-spacer> |       <v-spacer></v-spacer> | ||||||
|       <span> |       <span> | ||||||
|         <UploadBtn |         <TheUploadBtn | ||||||
|           class="mt-1" |           class="mt-1" | ||||||
|           :url="`/api/migrations/${folder}/upload`" |           :url="`/api/migrations/${folder}/upload`" | ||||||
|           fileName="archive" |           fileName="archive" | ||||||
| @@ -66,10 +66,10 @@ | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import UploadBtn from "../../UI/UploadBtn"; | import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn"; | ||||||
| import utils from "@/utils"; | import utils from "@/utils"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import MigrationDialog from "@/components/Admin/Migration/MigrationDialog.vue"; | import MigrationDialog from "./MigrationDialog"; | ||||||
| export default { | export default { | ||||||
|   props: { |   props: { | ||||||
|     folder: String, |     folder: String, | ||||||
| @@ -78,7 +78,7 @@ export default { | |||||||
|     available: Array, |     available: Array, | ||||||
|   }, |   }, | ||||||
|   components: { |   components: { | ||||||
|     UploadBtn, |     TheUploadBtn, | ||||||
|     MigrationDialog, |     MigrationDialog, | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
| @@ -42,7 +42,7 @@ | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import DataTable from "@/components/Admin/Backup/ImportSummaryDialog/DataTable"; | import DataTable from "@/components/ImportSummaryDialog"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     DataTable, |     DataTable, | ||||||
| @@ -1,13 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <SuccessFailureAlert |  | ||||||
|       :title="$t('migration.migration-report')" |  | ||||||
|       ref="report" |  | ||||||
|       :failedHeader="$t('migration.failed-imports')" |  | ||||||
|       :failed="failed" |  | ||||||
|       :successHeader="$t('migration.successful-imports')" |  | ||||||
|       :success="success" |  | ||||||
|     /> |  | ||||||
|     <v-card :loading="loading"> |     <v-card :loading="loading"> | ||||||
|       <v-card-title class="headline"> |       <v-card-title class="headline"> | ||||||
|         {{ $t("migration.recipe-migration") }} |         {{ $t("migration.recipe-migration") }} | ||||||
| @@ -42,13 +34,11 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import MigrationCard from "@/components/Admin/Migration/MigrationCard"; | import MigrationCard from "./MigrationCard"; | ||||||
| import SuccessFailureAlert from "@/components/UI/SuccessFailureAlert"; |  | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     MigrationCard, |     MigrationCard, | ||||||
|     SuccessFailureAlert, |  | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ | |||||||
|         </v-card-text> |         </v-card-text> | ||||||
|  |  | ||||||
|         <v-card-actions> |         <v-card-actions> | ||||||
|           <UploadBtn |           <TheUploadBtn | ||||||
|             icon="mdi-image-area" |             icon="mdi-image-area" | ||||||
|             :text="$t('user.upload-photo')" |             :text="$t('user.upload-photo')" | ||||||
|             :url="userProfileImage" |             :url="userProfileImage" | ||||||
| @@ -145,13 +145,13 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| // import AvatarPicker from '@/components/AvatarPicker' | // import AvatarPicker from '@/components/AvatarPicker' | ||||||
| import UploadBtn from "@/components/UI/UploadBtn"; | import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import { validators } from "@/mixins/validators"; | import { validators } from "@/mixins/validators"; | ||||||
| import { initials } from "@/mixins/initials"; | import { initials } from "@/mixins/initials"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     UploadBtn, |     TheUploadBtn, | ||||||
|   }, |   }, | ||||||
|   mixins: [validators, initials], |   mixins: [validators, initials], | ||||||
|   data() { |   data() { | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ | |||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import draggable from "vuedraggable"; | import draggable from "vuedraggable"; | ||||||
| import CreatePageDialog from "@/components/Admin/General/CreatePageDialog"; | import CreatePageDialog from "./CreatePageDialog"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
| @@ -117,12 +117,12 @@ | |||||||
|       </v-row> |       </v-row> | ||||||
|     </v-card-text> |     </v-card-text> | ||||||
|     <v-card-text> |     <v-card-text> | ||||||
|       <h2 class="mt-1 mb-4">{{$t('settings.locale-settings')}}</h2> |       <h2 class="mt-1 mb-4">{{ $t("settings.locale-settings") }}</h2> | ||||||
|       <v-row> |       <v-row> | ||||||
|         <v-col cols="1"> |         <v-col cols="12" md="3" sm="12"> | ||||||
|           <LanguageMenu @select-lang="writeLang" :site-settings="true" /> |           <LanguageSelector @select-lang="writeLang" :site-settings="true" /> | ||||||
|         </v-col> |         </v-col> | ||||||
|         <v-col sm="3"> |         <v-col cols="12" md="3" sm="12"> | ||||||
|           <v-select |           <v-select | ||||||
|             dense |             dense | ||||||
|             prepend-icon="mdi-calendar-week-begin" |             prepend-icon="mdi-calendar-week-begin" | ||||||
| @@ -147,14 +147,14 @@ | |||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import LanguageMenu from "@/components/UI/LanguageMenu"; | import LanguageSelector from "@/components/FormHelpers/LanguageSelector"; | ||||||
| import draggable from "vuedraggable"; | import draggable from "vuedraggable"; | ||||||
| import NewCategoryTagDialog from "@/components/UI/Dialogs/NewCategoryTagDialog.vue"; | import NewCategoryTagDialog from "@/components/UI/Dialogs/NewCategoryTagDialog.vue"; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     draggable, |     draggable, | ||||||
|     LanguageMenu, |     LanguageSelector, | ||||||
|     NewCategoryTagDialog, |     NewCategoryTagDialog, | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
| @@ -178,33 +178,33 @@ export default { | |||||||
|     allDays() { |     allDays() { | ||||||
|       return [ |       return [ | ||||||
|         { |         { | ||||||
|           name: this.$t('general.sunday'), |           name: this.$t("general.sunday"), | ||||||
|           value: 0, |           value: 0, | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           name: this.$t('general.monday'), |           name: this.$t("general.monday"), | ||||||
|           value: 1, |           value: 1, | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           name: this.$t('general.tuesday'), |           name: this.$t("general.tuesday"), | ||||||
|           value: 2, |           value: 2, | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           name: this.$t('general.wednesday'), |           name: this.$t("general.wednesday"), | ||||||
|           value: 3, |           value: 3, | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           name: this.$t('general.thursday'), |           name: this.$t("general.thursday"), | ||||||
|           value: 4, |           value: 4, | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           name: this.$t('general.friday'), |           name: this.$t("general.friday"), | ||||||
|           value: 5, |           value: 5, | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           name: this.$t('general.saturday'), |           name: this.$t("general.saturday"), | ||||||
|           value: 6, |           value: 6, | ||||||
|         } |         }, | ||||||
|       ]; |       ]; | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| @@ -223,10 +223,8 @@ export default { | |||||||
|       this.settings.categories.splice(index, 1); |       this.settings.categories.splice(index, 1); | ||||||
|     }, |     }, | ||||||
|     async saveSettings() { |     async saveSettings() { | ||||||
|       await api.siteSettings.update(this.settings); |       const newSettings = await api.siteSettings.update(this.settings); | ||||||
|       this.$store.dispatch("setLang", {  |       console.log("New Settings", newSettings); | ||||||
|         currentVueComponent: this,  |  | ||||||
|         language: this.settings.language }); |  | ||||||
|       this.getOptions(); |       this.getOptions(); | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| @@ -20,8 +20,8 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import HomePageSettings from "@/components/Admin/General/HomePageSettings"; | import HomePageSettings from "./HomePageSettings"; | ||||||
| import CustomPageCreator from "@/components/Admin/General/CustomPageCreator"; | import CustomPageCreator from "./CustomPageCreator"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <Confirmation |     <ConfirmationDialog | ||||||
|       :title="$t('settings.theme.delete-theme')" |       :title="$t('settings.theme.delete-theme')" | ||||||
|       :message="$t('settings.theme.are-you-sure-you-want-to-delete-this-theme')" |       :message="$t('settings.theme.are-you-sure-you-want-to-delete-this-theme')" | ||||||
|       color="error" |       color="error" | ||||||
| @@ -44,7 +44,7 @@ | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import Confirmation from "@/components/UI/Confirmation"; | import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog"; | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| 
 | 
 | ||||||
| const DELETE_EVENT = "delete"; | const DELETE_EVENT = "delete"; | ||||||
| @@ -52,7 +52,7 @@ const APPLY_EVENT = "apply"; | |||||||
| const EDIT_EVENT = "edit"; | const EDIT_EVENT = "edit"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     Confirmation, |     ConfirmationDialog, | ||||||
|   }, |   }, | ||||||
|   props: { |   props: { | ||||||
|     theme: Object, |     theme: Object, | ||||||
| @@ -135,9 +135,9 @@ | |||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import ColorPickerDialog from "@/components/Admin/Theme/ColorPickerDialog"; | import ColorPickerDialog from "@/components/FormHelpers/ColorPickerDialog"; | ||||||
| import NewThemeDialog from "@/components/Admin/Theme/NewThemeDialog"; | import NewThemeDialog from "./NewThemeDialog"; | ||||||
| import ThemeCard from "@/components/Admin/Theme/ThemeCard"; | import ThemeCard from "./ThemeCard"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import About from "@/pages/Admin/About"; | |||||||
| import { store } from "../store"; | import { store } from "../store"; | ||||||
| import i18n from '@/i18n.js'; | import i18n from '@/i18n.js'; | ||||||
|  |  | ||||||
| export default { | export const adminRoutes =  { | ||||||
|   path: "/admin", |   path: "/admin", | ||||||
|   component: Admin, |   component: Admin, | ||||||
|   beforeEnter: (to, _from, next) => { |   beforeEnter: (to, _from, next) => { | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								frontend/src/routes/auth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								frontend/src/routes/auth.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | import LoginPage from "@/pages/LoginPage"; | ||||||
|  | import SignUpPage from "@/pages/SignUpPage"; | ||||||
|  | import { store } from "../store"; | ||||||
|  |  | ||||||
|  | export const authRoutes = [ | ||||||
|  |   { | ||||||
|  |     path: "/logout", | ||||||
|  |     beforeEnter: (_to, _from, next) => { | ||||||
|  |       store.commit("setToken", ""); | ||||||
|  |       store.commit("setIsLoggedIn", false); | ||||||
|  |       next("/"); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   { path: "/login", component: LoginPage }, | ||||||
|  |  | ||||||
|  |   { path: "/sign-up", redirect: "/" }, | ||||||
|  |   { path: "/sign-up/:token", component: SignUpPage }, | ||||||
|  | ]; | ||||||
							
								
								
									
										15
									
								
								frontend/src/routes/general.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								frontend/src/routes/general.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | import i18n from "@/i18n.js"; | ||||||
|  | import SearchPage from "@/pages/SearchPage"; | ||||||
|  | import HomePage from "@/pages/HomePage"; | ||||||
|  |  | ||||||
|  | export const generalRoutes = [ | ||||||
|  |   { path: "/", name: "home", component: HomePage }, | ||||||
|  |   { path: "/mealie", component: HomePage }, | ||||||
|  |   { | ||||||
|  |     path: "/search", | ||||||
|  |     component: SearchPage, | ||||||
|  |     meta: { | ||||||
|  |       title: i18n.t("search.search"), | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
| @@ -1,87 +1,54 @@ | |||||||
| import HomePage from "@/pages/HomePage"; |  | ||||||
| import Page404 from "@/pages/404Page"; | import Page404 from "@/pages/404Page"; | ||||||
| import SearchPage from "@/pages/SearchPage"; | import { adminRoutes } from "./admin"; | ||||||
| import ViewRecipe from "@/pages/Recipe/ViewRecipe"; | import { authRoutes } from "./auth"; | ||||||
| import NewRecipe from "@/pages/Recipe/NewRecipe"; | import { recipeRoutes } from "./recipes"; | ||||||
| import CustomPage from "@/pages/Recipes/CustomPage"; | import { mealRoutes } from "./meal"; | ||||||
| import AllRecipes from "@/pages/Recipes/AllRecipes"; | import { generalRoutes } from "./general"; | ||||||
| import CategoryPage from "@/pages/Recipes/CategoryPage"; |  | ||||||
| import TagPage from "@/pages/Recipes/TagPage"; |  | ||||||
| import Planner from "@/pages/MealPlan/Planner"; |  | ||||||
| import Debug from "@/pages/Debug"; |  | ||||||
| import LoginPage from "@/pages/LoginPage"; |  | ||||||
| import SignUpPage from "@/pages/SignUpPage"; |  | ||||||
| import ThisWeek from "@/pages/MealPlan/ThisWeek"; |  | ||||||
| import { api } from "@/api"; |  | ||||||
| import Admin from "./admin"; |  | ||||||
| import { store } from "../store"; | import { store } from "../store"; | ||||||
| import i18n from '@/i18n.js'; | import VueRouter from "vue-router"; | ||||||
|  | import VueI18n from "@/i18n"; | ||||||
|  | import Vuetify from "@/plugins/vuetify"; | ||||||
|  | import Vue from "vue"; | ||||||
|  |  | ||||||
| export const routes = [ | export const routes = [ | ||||||
|   { path: "/", name: "home", component: HomePage }, |   ...generalRoutes, | ||||||
|   { |   adminRoutes, | ||||||
|     path: "/logout", |   ...authRoutes, | ||||||
|     beforeEnter: (_to, _from, next) => { |   ...mealRoutes, | ||||||
|       store.commit("setToken", ""); |   ...recipeRoutes, | ||||||
|       store.commit("setIsLoggedIn", false); |  | ||||||
|       next("/"); |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   { path: "/mealie", component: HomePage }, |  | ||||||
|   { path: "/login", component: LoginPage }, |  | ||||||
|   { path: "/sign-up", redirect: "/" }, |  | ||||||
|   { path: "/sign-up/:token", component: SignUpPage }, |  | ||||||
|   { path: "/debug", component: Debug }, |  | ||||||
|   {  |  | ||||||
|     path: "/search",  |  | ||||||
|     component: SearchPage, |  | ||||||
|     meta: { |  | ||||||
|       title: i18n.t('search.search'), |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   { path: "/recipes/all", component: AllRecipes }, |  | ||||||
|   { path: "/pages/:customPage", component: CustomPage }, |  | ||||||
|   { path: "/recipes/tag/:tag", component: TagPage }, |  | ||||||
|   { path: "/recipes/category/:category", component: CategoryPage }, |  | ||||||
|   {  |  | ||||||
|     path: "/recipe/:recipe",  |  | ||||||
|     component: ViewRecipe, |  | ||||||
|     meta: { |  | ||||||
|       title: async route => { |  | ||||||
|         const recipe = await api.recipes.requestDetails(route.params.recipe); |  | ||||||
|         return recipe.name; |  | ||||||
|       }, |  | ||||||
|     }  |  | ||||||
|   }, |  | ||||||
|   { path: "/new/", component: NewRecipe }, |  | ||||||
|   {  |  | ||||||
|     path: "/meal-plan/planner", |  | ||||||
|     component: Planner, |  | ||||||
|     meta: { |  | ||||||
|       title:  i18n.t('meal-plan.meal-planner'), |  | ||||||
|     }  |  | ||||||
|   }, |  | ||||||
|   {  |  | ||||||
|     path: "/meal-plan/this-week",  |  | ||||||
|     component: ThisWeek, |  | ||||||
|     meta: { |  | ||||||
|       title: i18n.t('meal-plan.dinner-this-week'), |  | ||||||
|     }  |  | ||||||
|  |  | ||||||
|   }, |  | ||||||
|   Admin, |  | ||||||
|   { |  | ||||||
|     path: "/meal-plan/today", |  | ||||||
|     beforeEnter: async (_to, _from, next) => { |  | ||||||
|       await todaysMealRoute().then(redirect => { |  | ||||||
|         next(redirect); |  | ||||||
|       }); |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   { path: "*", component: Page404 }, |   { path: "*", component: Page404 }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| async function todaysMealRoute() { | const router = new VueRouter({ | ||||||
|   const response = await api.mealPlans.today(); |   routes, | ||||||
|   return "/recipe/" + response.data; |   mode: process.env.NODE_ENV === "production" ? "history" : "hash", | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | const DEFAULT_TITLE = "Mealie"; | ||||||
|  | const TITLE_SEPARATOR = "🍴"; | ||||||
|  | const TITLE_SUFFIX = " " + TITLE_SEPARATOR + " " + DEFAULT_TITLE; | ||||||
|  | router.afterEach(to => { | ||||||
|  |   Vue.nextTick(async () => { | ||||||
|  |     if (typeof to.meta.title === "function") { | ||||||
|  |       const title = await to.meta.title(to); | ||||||
|  |       document.title = title + TITLE_SUFFIX; | ||||||
|  |     } else { | ||||||
|  |       document.title = to.meta.title | ||||||
|  |         ? to.meta.title + TITLE_SUFFIX | ||||||
|  |         : DEFAULT_TITLE; | ||||||
|     } |     } | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | function loadLocale() { | ||||||
|  |   VueI18n.locale = store.getters.getActiveLang; | ||||||
|  |   Vuetify.framework.lang.current = store.getters.getActiveLang; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | router.beforeEach((__, _, next) => { | ||||||
|  |   loadLocale(); | ||||||
|  |   next(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export { router }; | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								frontend/src/routes/meal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								frontend/src/routes/meal.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import Planner from "@/pages/MealPlan/Planner"; | ||||||
|  | import ThisWeek from "@/pages/MealPlan/ThisWeek"; | ||||||
|  | import i18n from "@/i18n.js"; | ||||||
|  | import { api } from "@/api"; | ||||||
|  |  | ||||||
|  | export const mealRoutes = [ | ||||||
|  |   { | ||||||
|  |     path: "/meal-plan/planner", | ||||||
|  |     component: Planner, | ||||||
|  |     meta: { | ||||||
|  |       title: i18n.t("meal-plan.meal-planner"), | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: "/meal-plan/this-week", | ||||||
|  |     component: ThisWeek, | ||||||
|  |     meta: { | ||||||
|  |       title: i18n.t("meal-plan.dinner-this-week"), | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: "/meal-plan/today", | ||||||
|  |     beforeEnter: async (_to, _from, next) => { | ||||||
|  |       await todaysMealRoute().then(redirect => { | ||||||
|  |         next(redirect); | ||||||
|  |       }); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | async function todaysMealRoute() { | ||||||
|  |   const response = await api.mealPlans.today(); | ||||||
|  |   return "/recipe/" + response.data; | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								frontend/src/routes/recipes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								frontend/src/routes/recipes.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | import ViewRecipe from "@/pages/Recipe/ViewRecipe"; | ||||||
|  | import NewRecipe from "@/pages/Recipe/NewRecipe"; | ||||||
|  | import CustomPage from "@/pages/Recipes/CustomPage"; | ||||||
|  | import AllRecipes from "@/pages/Recipes/AllRecipes"; | ||||||
|  | import CategoryPage from "@/pages/Recipes/CategoryPage"; | ||||||
|  | import TagPage from "@/pages/Recipes/TagPage"; | ||||||
|  | import { api } from "@/api"; | ||||||
|  |  | ||||||
|  | export const recipeRoutes = [ | ||||||
|  |   // Recipes | ||||||
|  |   { path: "/recipes/all", component: AllRecipes }, | ||||||
|  |   { path: "/recipes/tag/:tag", component: TagPage }, | ||||||
|  |   { path: "/recipes/category/:category", component: CategoryPage }, | ||||||
|  |   // Misc | ||||||
|  |   { path: "/new/", component: NewRecipe }, | ||||||
|  |   { path: "/pages/:customPage", component: CustomPage }, | ||||||
|  |  | ||||||
|  |   // Recipe Page | ||||||
|  |   { | ||||||
|  |     path: "/recipe/:recipe", | ||||||
|  |     component: ViewRecipe, | ||||||
|  |     meta: { | ||||||
|  |       title: async route => { | ||||||
|  |         const recipe = await api.recipes.requestDetails(route.params.recipe); | ||||||
|  |         return recipe.name; | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
| @@ -12,7 +12,7 @@ Vue.use(Vuex); | |||||||
| const store = new Vuex.Store({ | const store = new Vuex.Store({ | ||||||
|   plugins: [ |   plugins: [ | ||||||
|     createPersistedState({ |     createPersistedState({ | ||||||
|       paths: ["userSettings", "language.lang", "siteSettings"], |       paths: ["userSettings", "siteSettings"], | ||||||
|     }), |     }), | ||||||
|   ], |   ], | ||||||
|   modules: { |   modules: { | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| import VueI18n from "../../i18n"; | // This is the data store for the options for language selection. Property is reference only, you cannot set this property. | ||||||
|  |  | ||||||
| const state = { | const state = { | ||||||
|   lang: "en-US", |  | ||||||
|   allLangs: [ |   allLangs: [ | ||||||
|     { |     { | ||||||
|       name: "English", |       name: "English", | ||||||
| @@ -42,33 +40,11 @@ const state = { | |||||||
|   ], |   ], | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const mutations = { |  | ||||||
|   setLang(state, payload) { |  | ||||||
|     VueI18n.locale = payload; |  | ||||||
|     state.lang = payload; |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const actions = { |  | ||||||
|   initLang({ getters }, { currentVueComponent }) { |  | ||||||
|     VueI18n.locale = getters.getActiveLang; |  | ||||||
|     currentVueComponent.$vuetify.lang.current = getters.getActiveLang; |  | ||||||
|   }, |  | ||||||
|   setLang({ commit }, { language, currentVueComponent }) { |  | ||||||
|     VueI18n.locale = language; |  | ||||||
|     currentVueComponent.$vuetify.lang.current = language; |  | ||||||
|     commit('setLang', language); |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const getters = { | const getters = { | ||||||
|   getActiveLang: state => state.lang, |  | ||||||
|   getAllLangs: state => state.allLangs, |   getAllLangs: state => state.allLangs, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   state, |   state, | ||||||
|   mutations, |  | ||||||
|   actions, |  | ||||||
|   getters, |   getters, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,8 +1,10 @@ | |||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
|  | import VueI18n from "@/i18n"; | ||||||
|  | import Vuetify from "@/plugins/vuetify"; | ||||||
|  |  | ||||||
| const state = { | const state = { | ||||||
|   siteSettings: { |   siteSettings: { | ||||||
|     language: "en", |     language: "en-US", | ||||||
|     firstDayOfWeek: 0, |     firstDayOfWeek: 0, | ||||||
|     showRecent: true, |     showRecent: true, | ||||||
|     cardsPerSection: 9, |     cardsPerSection: 9, | ||||||
| @@ -13,17 +15,20 @@ const state = { | |||||||
| const mutations = { | const mutations = { | ||||||
|   setSettings(state, payload) { |   setSettings(state, payload) { | ||||||
|     state.siteSettings = payload; |     state.siteSettings = payload; | ||||||
|  |     VueI18n.locale = payload.language; | ||||||
|  |     Vuetify.framework.lang.current = payload.language; | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const actions = { | const actions = { | ||||||
|   async requestSiteSettings() { |   async requestSiteSettings({ commit }) { | ||||||
|     let settings = await api.siteSettings.get(); |     let settings = await api.siteSettings.get(); | ||||||
|     this.commit("setSettings", settings); |     commit("setSettings", settings); | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const getters = { | const getters = { | ||||||
|  |   getActiveLang: state => state.siteSettings.language, | ||||||
|   getSiteSettings: state => state.siteSettings, |   getSiteSettings: state => state.siteSettings, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { api } from "@/api"; | import { api } from "@/api"; | ||||||
| import Vuetify from "../../plugins/vuetify"; | import Vuetify from "@/plugins/vuetify"; | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
|  |  | ||||||
| function inDarkMode(payload) { | function inDarkMode(payload) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user