diff --git a/dev/ingredientScaler/index.js b/dev/ingredientScaler/index.js
new file mode 100644
index 000000000000..f7ba9fd9cd6f
--- /dev/null
+++ b/dev/ingredientScaler/index.js
@@ -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,
+ };
+ },
+};
diff --git a/dev/ingredientScaler/recipeIngredient.js b/dev/ingredientScaler/recipeIngredient.js
new file mode 100644
index 000000000000..8bc3e2f1dcf7
--- /dev/null
+++ b/dev/ingredientScaler/recipeIngredient.js
@@ -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",
+ },
+ },
+};
diff --git a/dev/ingredientScaler/recipeNumber.js b/dev/ingredientScaler/recipeNumber.js
new file mode 100644
index 000000000000..74579aeb6378
--- /dev/null
+++ b/dev/ingredientScaler/recipeNumber.js
@@ -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;
+};
diff --git a/dev/scripts/publish-release-branch.sh b/dev/scripts/publish-release-branch.sh
new file mode 100644
index 000000000000..22bd2aa8eb3d
--- /dev/null
+++ b/dev/scripts/publish-release-branch.sh
@@ -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
\ No newline at end of file
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index bb545577b9a9..d9df8bf27cc5 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -9,7 +9,7 @@
>
-
+
@@ -19,7 +19,7 @@
-
-
\ No newline at end of file
diff --git a/frontend/src/components/Debug/LogFile.vue b/frontend/src/components/Debug/LogFile.vue
deleted file mode 100644
index 47f309496af9..000000000000
--- a/frontend/src/components/Debug/LogFile.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
- Last Scrapped JSON Data
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/frontend/src/components/Admin/Theme/ColorPickerDialog.vue b/frontend/src/components/FormHelpers/ColorPickerDialog.vue
similarity index 100%
rename from frontend/src/components/Admin/Theme/ColorPickerDialog.vue
rename to frontend/src/components/FormHelpers/ColorPickerDialog.vue
diff --git a/frontend/src/components/UI/DatePicker.vue b/frontend/src/components/FormHelpers/DatePicker.vue
similarity index 100%
rename from frontend/src/components/UI/DatePicker.vue
rename to frontend/src/components/FormHelpers/DatePicker.vue
diff --git a/frontend/src/components/FormHelpers/LanguageSelector.vue b/frontend/src/components/FormHelpers/LanguageSelector.vue
new file mode 100644
index 000000000000..797157173dec
--- /dev/null
+++ b/frontend/src/components/FormHelpers/LanguageSelector.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/Admin/MealPlanner/TimePickerDialog.vue b/frontend/src/components/FormHelpers/TimePickerDialog.vue
similarity index 100%
rename from frontend/src/components/Admin/MealPlanner/TimePickerDialog.vue
rename to frontend/src/components/FormHelpers/TimePickerDialog.vue
diff --git a/frontend/src/components/Admin/Backup/ImportSummaryDialog/DataTable.vue b/frontend/src/components/ImportSummaryDialog/DataTable.vue
similarity index 100%
rename from frontend/src/components/Admin/Backup/ImportSummaryDialog/DataTable.vue
rename to frontend/src/components/ImportSummaryDialog/DataTable.vue
diff --git a/frontend/src/components/Admin/Backup/ImportSummaryDialog/index.vue b/frontend/src/components/ImportSummaryDialog/index.vue
similarity index 97%
rename from frontend/src/components/Admin/Backup/ImportSummaryDialog/index.vue
rename to frontend/src/components/ImportSummaryDialog/index.vue
index 4c2f8597d49d..c41aa912e4d9 100644
--- a/frontend/src/components/Admin/Backup/ImportSummaryDialog/index.vue
+++ b/frontend/src/components/ImportSummaryDialog/index.vue
@@ -45,7 +45,7 @@
\ No newline at end of file
diff --git a/frontend/src/components/UI/SuccessFailureAlert.vue b/frontend/src/components/UI/SuccessFailureAlert.vue
deleted file mode 100644
index f9ea6836028b..000000000000
--- a/frontend/src/components/UI/SuccessFailureAlert.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
- mdi-close
-
- {{ title }}
-
-
-
-
- {{ title }}
-
-
-
-
-
- {{ successHeader }}
-
- - {{ success }}
-
-
-
-
-
- {{ failedHeader }}
-
- - {{ fail }}
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/frontend/src/components/UI/TheAppBar.vue b/frontend/src/components/UI/TheAppBar.vue
index 1c556feb6a00..72947116ae93 100644
--- a/frontend/src/components/UI/TheAppBar.vue
+++ b/frontend/src/components/UI/TheAppBar.vue
@@ -35,7 +35,7 @@
mdi-magnify
-
+
mdi-magnify
-
+