Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
[#1573](https://github.com/nextcloud/cookbook/pull/1573) @j0hannesr0th
- Add copy to clipboard action for ingredients
[#1602](https://github.com/nextcloud/cookbook/pull/1602) @j0hannesr0th
- Enhance recipe recalculation algorithm
[#1723](https://github.com/nextcloud/cookbook/pull/1723) @j0hannesr0th

### Fixed
- Fix translation string to not contain quotes
Expand Down
94 changes: 46 additions & 48 deletions src/components/RecipeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,27 @@
<span>
<button
:disabled="recipeYield === 1"
@click="recalculateIngredients(false)"
@click="changeRecipeYield(false)"
>
<span class="icon-view-previous" />
</button>
{{ recipeYield }}
<button @click="recalculateIngredients">
<input
v-model="recipeYield"
type="number"
min="0"
/>
<button @click="changeRecipeYield">
<span class="icon-view-next" />
</button>
<button
v-if="
recipeYield !==
$store.state.recipe.recipeYield
"
@click="restoreOriginalRecipeYield"
>
<span class="icon-history" />
</button>
</span>
</p>
</div>
Expand Down Expand Up @@ -125,16 +138,17 @@
</template>
{{ t("cookbook", "Copy ingredients") }}
</NcButton>
<h3 v-if="parsedIngredients.length">
<h3 v-if="scaledIngredients.length">
{{ t("cookbook", "Ingredients") }}
</h3>
<ul v-if="parsedIngredients.length">
<ul v-if="scaledIngredients.length">
<RecipeIngredient
v-for="(ingredient, idx) in parsedIngredients"
v-for="(ingredient, idx) in scaledIngredients"
:key="'ingr' + idx"
:ingredient="ingredient"
:ingredient-has-correct-syntax="
validateIngredientSyntax(ingredient)
/* yieldCalculator.isValidIngredientSyntax(ingredient) */
ingredientsWithValidSyntax[idx]
"
:recipe-ingredients-have-subgroups="
recipeIngredientsHaveSubgroups
Expand Down Expand Up @@ -305,6 +319,7 @@ import api from "cookbook/js/api-interface"
import helpers from "cookbook/js/helper"
import normalizeMarkdown from "cookbook/js/title-rename"
import { showSimpleAlertModal } from "cookbook/js/modals"
import yieldCalculator from "cookbook/js/yieldCalculator"

import ContentCopyIcon from "icons/ContentCopy.vue"

Expand Down Expand Up @@ -505,8 +520,20 @@ export default {
visibleInfoBlocks() {
return this.$store.state.config?.visibleInfoBlocks ?? {}
},
scaledIngredients() {
return yieldCalculator.recalculateIngredients(
this.parsedIngredients,
this.recipeYield,
this.$store.state.recipe.recipeYield
)
},
ingredientsWithValidSyntax() {
return this.parsedIngredients.map(
yieldCalculator.isValidIngredientSyntax
)
},
ingredientsSyntaxCorrect() {
return this.parsedIngredients.every(this.validateIngredientSyntax)
return this.ingredientsWithValidSyntax.every((x) => x)
},
},
watch: {
Expand Down Expand Up @@ -576,6 +603,11 @@ export default {
}
}
},
recipeYield() {
if (this.recipeYield < 0) {
this.restoreOriginalRecipeYield()
}
},
},
mounted() {
this.$log.info("RecipeView mounted")
Expand Down Expand Up @@ -662,48 +694,11 @@ export default {

this.recipeYield = this.$store.state.recipe.recipeYield
},
recalculateIngredients(increaseYield = true) {
this.recipeYield = increaseYield
? this.recipeYield + 1
: this.recipeYield - 1

this.parsedIngredients = this.parsedIngredients.map(
(ingredient) => {
if (this.validateIngredientSyntax(ingredient)) {
const amount = parseFloat(ingredient.split(" ")[0])
const unitAndIngredient = ingredient
.split(" ")
.slice(1)
.join(" ")
const newAmount =
amount *
(increaseYield
? this.recipeYield / (this.recipeYield - 1)
: this.recipeYield / (this.recipeYield + 1))

// Remove decimal places if they are .00
return `${newAmount
.toFixed(2)
.replace(/[.]00$/, "")} ${unitAndIngredient}`
}

return ingredient
}
)
},
validateIngredientSyntax(ingredient) {
/*
Explanation:
^: Start of string
(?:\d+(?:\.\d+)?|\.\d+): Non-capturing group that matches either a positive float value or a positive integer value. The first alternative matches one or more digits, followed by an optional decimal part consisting of a dot and one or more digits. The second alternative matches a decimal point followed by one or more digits.
(?:\s.+$|\s\S+$): Non-capturing group that matches a whitespace character followed by any character with unlimited length or any special character with unlimited length. The first alternative matches a whitespace character followed by any character(s) until the end of the string. The second alternative matches a whitespace character followed by any non-whitespace character(s) until the end of the string.
$: End of string
*/
const ingredientRegExp = /^(?:\d+(?:\.\d+)?|\.\d+)(?:\s.+$|\s\S+$)/
return ingredientRegExp.test(ingredient)
changeRecipeYield(increase = true) {
this.recipeYield = +this.recipeYield + (increase ? 1 : -1)
},
copyIngredientsToClipboard() {
const ingredientsToCopy = this.parsedIngredients.join("\n")
const ingredientsToCopy = this.scaledIngredients.join("\n")

if (navigator.clipboard) {
navigator.clipboard
Expand Down Expand Up @@ -736,6 +731,9 @@ export default {
document.body.removeChild(input)
}
},
restoreOriginalRecipeYield() {
this.recipeYield = this.$store.state.recipe.recipeYield
},
},
}
</script>
Expand Down
65 changes: 65 additions & 0 deletions src/js/yieldCalculator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
function isValidIngredientSyntax(ingredient) {
/*
*** Outdated!!! ***
Explanation of ingredientSyntaxRegExp:
^: Start of string
(?:\d+(?:\.\d+)?|\.\d+): Non-capturing group that matches either a positive float value or a positive integer value. The first alternative matches one or more digits, followed by an optional decimal part consisting of a dot and one or more digits. The second alternative matches a decimal point followed by one or more digits.
(?:\s.+$|\s\S+$): Non-capturing group that matches a whitespace character followed by any character with unlimited length or any special character with unlimited length. The first alternative matches a whitespace character followed by any character(s) until the end of the string. The second alternative matches a whitespace character followed by any non-whitespace character(s) until the end of the string.
$: End of string
*/
const ingredientSyntaxRegExp = /^(?:\d+(?:\.\d+)?(?:\/\d+)?)\s?.*$/
// Regular expression to match all possible fractions within a string
const ingredientFractionRegExp = /\b\d+\/\d+\b/g
/*
Explanation of ingredientMultipleSeperatorsRegExp:
/^ - Start of the string
-? - Matches an optional minus sign
\d+ - Matches one or more digits
(?:[.,]\d+){2,} - Non-capturing group that matches a separator (.,) followed by one or more digits.
The {2,} quantifier ensures that there are at least two occurrences of this pattern.
.* - Matches any characters (except newline) zero or more times.
*/
const ingredientMultipleSeperatorsRegExp = /^-?\d+(?:[.,]\d+){2,}.*/

return (
ingredientSyntaxRegExp.test(ingredient) &&
!ingredientFractionRegExp.test(ingredient) &&
!ingredientMultipleSeperatorsRegExp.test(ingredient)
)
}

function isIngredientsArrayValid(ingredients) {
return ingredients.every(isValidIngredientSyntax)
}

function recalculateIngredients(ingredients, currentYield, originalYield) {
return ingredients.map((ingredient, index) => {
if (isValidIngredientSyntax(ingredient)) {
// For some cases, where the unit is not separated from the amount: 100g cheese
const possibleUnit = ingredient
.split(" ")[0]
.replace(/[^a-zA-Z]/g, "")
const amount = parseFloat(ingredients[index].split(" ")[0])
const unitAndIngredient = ingredient.split(" ").slice(1).join(" ")
let newAmount = (amount / originalYield) * currentYield
newAmount = newAmount.toFixed(2).replace(/[.]00$/, "")

return `${newAmount}${possibleUnit} ${unitAndIngredient}`
}

const factor = currentYield / originalYield
const prefix = ((f) => {
if (f === 1) {
return ""
}
return `${f.toFixed(2)}x `
})(factor)
return `${prefix}${ingredient}`
})
}

export default {
isValidIngredientSyntax,
isIngredientsArrayValid,
recalculateIngredients,
}