mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -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:
parent
a5306c31c6
commit
284df44209
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";
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ const settingsURLs = {
|
|||||||
customPage: id => `${settingsBase}/custom-pages/${id}`,
|
customPage: id => `${settingsBase}/custom-pages/${id}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const siteSettingsAPI = {
|
export const siteSettingsAPI = {
|
||||||
async get() {
|
async get() {
|
||||||
let response = await apiReq.get(settingsURLs.siteSettings);
|
let response = await apiReq.get(settingsURLs.siteSettings);
|
||||||
return response.data;
|
return response.data;
|
||||||
@ -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,42 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-toolbar class="card-btn" flat height="0" extension-height="0">
|
<v-expand-transition>
|
||||||
<template v-slot:extension>
|
<v-toolbar
|
||||||
<v-col></v-col>
|
class="card-btn pt-1"
|
||||||
<div v-if="open">
|
flat
|
||||||
<v-btn
|
:height="isSticky ? null : '0'"
|
||||||
class="mr-2"
|
:extension-height="isSticky ? '20' : '0'"
|
||||||
fab
|
color="rgb(255, 0, 0, 0.0)"
|
||||||
dark
|
>
|
||||||
small
|
<ConfirmationDialog
|
||||||
color="error"
|
:title="$t('recipe.delete-recipe')"
|
||||||
@click="deleteRecipeConfrim"
|
:message="$t('recipe.delete-ConfirmationDialog')"
|
||||||
>
|
color="error"
|
||||||
<v-icon>mdi-delete</v-icon>
|
icon="mdi-alert-circle"
|
||||||
|
ref="deleteRecipieConfirm"
|
||||||
|
v-on:confirm="deleteRecipe()"
|
||||||
|
/>
|
||||||
|
<template v-slot:extension>
|
||||||
|
<v-col></v-col>
|
||||||
|
<div v-if="open">
|
||||||
|
<v-btn
|
||||||
|
class="mr-2"
|
||||||
|
fab
|
||||||
|
dark
|
||||||
|
small
|
||||||
|
color="error"
|
||||||
|
@click="deleteRecipeConfrim"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn class="mr-2" fab dark small color="success" @click="save">
|
||||||
|
<v-icon>mdi-content-save</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn class="mr-5" fab dark small color="secondary" @click="json">
|
||||||
|
<v-icon>mdi-code-braces</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<v-btn color="accent" fab dark small @click="editor">
|
||||||
|
<v-icon>mdi-square-edit-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<Confirmation
|
</template>
|
||||||
:title="$t('recipe.delete-recipe')"
|
</v-toolbar>
|
||||||
:message="$t('recipe.delete-confirmation')"
|
</v-expand-transition>
|
||||||
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-icon>mdi-content-save</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn class="mr-5" fab dark small color="secondary" @click="json">
|
|
||||||
<v-icon>mdi-code-braces</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
<v-btn color="accent" fab dark small @click="editor">
|
|
||||||
<v-icon>mdi-square-edit-outline</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
</v-toolbar>
|
|
||||||
</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,21 +117,21 @@
|
|||||||
</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"
|
||||||
v-model="settings.firstDayOfWeek"
|
v-model="settings.firstDayOfWeek"
|
||||||
:items="allDays"
|
:items="allDays"
|
||||||
item-text="name"
|
item-text="name"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
:label="$t('settings.first-day-of-week')"
|
:label="$t('settings.first-day-of-week')"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user