mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-02 04:39:54 -05:00
Merge remote-tracking branch 'origin/develop' into Auto-Planner
# Conflicts: # vue/src/apps/MealPlanView/MealPlanView.vue
This commit is contained in:
100
vue/src/components/BottomNavigationBar.vue
Normal file
100
vue/src/components/BottomNavigationBar.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<!-- bottom button nav -->
|
||||
<div class="fixed-bottom p-1 pt-2 pl-2 pr-2 border-top text-center d-lg-none" style="background: white">
|
||||
<div class="d-flex flex-row justify-content-around">
|
||||
<div class="flex-column" v-if="show_button_1">
|
||||
<slot name="button_1">
|
||||
<a class="nav-link bottom-nav-link p-0" v-bind:href="resolveDjangoUrl('view_search')">
|
||||
<i class="fas fa-fw fa-book " style="font-size: 1.5em"></i><br/><small>{{ $t('Recipes') }}</small></a> <!-- TODO localize -->
|
||||
</slot>
|
||||
|
||||
</div>
|
||||
<div class="flex-column" v-if="show_button_2">
|
||||
<slot name="button_2">
|
||||
<a class="nav-link bottom-nav-link p-0" v-bind:href="resolveDjangoUrl('view_plan')">
|
||||
<i class="fas fa-calendar-alt" style="font-size: 1.5em"></i><br/><small>{{ $t('Meal_Plan') }}</small></a>
|
||||
</slot>
|
||||
|
||||
</div>
|
||||
<div class="flex-column" v-if="show_button_create">
|
||||
<slot name="button_create">
|
||||
<div class="dropup">
|
||||
<a class="nav-link bottom-nav-link p-0" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i class="fas fa-plus-circle fa-2x bottom-nav-link"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu center-dropup" aria-labelledby="navbarDropdownMenuLink">
|
||||
|
||||
<a class="dropdown-item" v-bind:href="resolveDjangoUrl('new_recipe')"><i
|
||||
class="fas fa-fw fa-plus"></i> {{ $t('Create Recipe') }}</a>
|
||||
<a class="dropdown-item" v-bind:href="resolveDjangoUrl('data_import_url')"><i
|
||||
class="fas fa-fw fa-file-import"></i> {{ $t('Import Recipe') }}</a>
|
||||
<div class="dropdown-divider" v-if="create_links.length > 0"></div>
|
||||
|
||||
<slot name="custom_create_functions">
|
||||
|
||||
</slot>
|
||||
|
||||
<a class="dropdown-item" v-bind:href="cl.url" v-for="cl in create_links" v-bind:key="cl.label">
|
||||
<i :class="cl.icon + ' fa-fw'"></i> {{ cl.label }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
</div>
|
||||
<div class="flex-column" v-if="show_button_3">
|
||||
<slot name="button_3">
|
||||
<a class="nav-link bottom-nav-link p-0" v-bind:href="resolveDjangoUrl('view_shopping')">
|
||||
<i class="fas fa-shopping-cart" style="font-size: 1.5em"></i><br/><small>{{ $t('Shopping_list') }}</small></a>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="flex-column">
|
||||
|
||||
<slot name="button_4" v-if="show_button_4">
|
||||
<a class="nav-link bottom-nav-link p-0" v-bind:href="resolveDjangoUrl('view_books')">
|
||||
<i class="fas fa-book-open" style="font-size: 1.5em"></i><br/><small>{{ $t('Books') }}</small></a> <!-- TODO localize -->
|
||||
</slot>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ResolveUrlMixin} from "@/utils/utils";
|
||||
|
||||
export default {
|
||||
name: "BottomNavigationBar",
|
||||
mixins: [ResolveUrlMixin],
|
||||
props: {
|
||||
create_links: {
|
||||
type: Array, default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
show_button_1: {type: Boolean, default: true},
|
||||
show_button_2: {type: Boolean, default: true},
|
||||
show_button_3: {type: Boolean, default: true},
|
||||
show_button_4: {type: Boolean, default: true},
|
||||
show_button_create: {type: Boolean, default: true},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.bottom-nav-link {
|
||||
color: #666666
|
||||
}
|
||||
|
||||
.center-dropup {
|
||||
right: auto;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-50%, 0);
|
||||
-o-transform: translate(-50%, 0);
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
</style>
|
||||
@@ -95,7 +95,7 @@ export default {
|
||||
<style scoped>
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
z-index: 5000;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<cookbook-edit-card :book="book" v-if="current_page === 1" v-on:editing="cookbook_editing = $event" v-on:refresh="$emit('refresh')" @reload="$emit('reload')"></cookbook-edit-card>
|
||||
</transition>
|
||||
<transition name="flip" mode="out-in">
|
||||
<recipe-card :recipe="display_recipes[0].recipe_content" v-if="current_page > 1" :key="display_recipes[0].recipe" :use_plural="use_plural"></recipe-card>
|
||||
<recipe-card :recipe="display_recipes[0].recipe_content" v-if="current_page > 1" :key="display_recipes[0].recipe" ></recipe-card>
|
||||
</transition>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
<b-card-body class="m-0 py-0">
|
||||
<b-card-text class="h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
|
||||
<h5 class="m-0 mt-1 text-truncate">{{ item[title] }}</h5>
|
||||
<template v-if="use_plural">
|
||||
<div v-if="item[plural] !== '' && item[plural] !== null" class="m-0 text-truncate">({{ $t("plural_short") }}: {{ item[plural] }})</div>
|
||||
</template>
|
||||
|
||||
<div v-if="item[plural]!== '' && item[plural] !== null && item[plural] !== undefined" class="m-0 text-truncate">({{ $t("plural_short") }}: {{ item[plural] }})</div>
|
||||
|
||||
<div class="m-0 text-truncate">{{ item[subtitle] }}</div>
|
||||
<div class="m-0 text-truncate small text-muted" v-if="getFullname">{{ getFullname }}</div>
|
||||
|
||||
|
||||
@@ -155,8 +155,9 @@ export default {
|
||||
pageSize: this.limit,
|
||||
query: query,
|
||||
limit: this.limit,
|
||||
options: {query: {simple: 1}}, // for API endpoints that support a simple view
|
||||
}
|
||||
console.log(query, options)
|
||||
|
||||
this.genericAPI(this.model, this.Actions.LIST, options).then((result) => {
|
||||
this.objects = this.sticky_options.concat(result.data?.results ?? result.data)
|
||||
if (this.nothingSelected && this.objects.length > 0) {
|
||||
|
||||
@@ -12,21 +12,17 @@
|
||||
<i class="far fa-check-circle text-primary" v-if="!ingredient.checked"></i>
|
||||
</td>
|
||||
<td class="text-nowrap" @click="done">
|
||||
<span v-if="ingredient.amount !== 0 && !ingredient.no_amount"
|
||||
v-html="calculateAmount(ingredient.amount)"></span>
|
||||
<span v-if="ingredient.amount !== 0 && !ingredient.no_amount" v-html="calculateAmount(ingredient.amount)"></span>
|
||||
</td>
|
||||
<td @click="done">
|
||||
<template v-if="ingredient.unit !== null && !ingredient.no_amount">
|
||||
<template v-if="!use_plural">
|
||||
<span>{{ ingredient.unit.name }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template>
|
||||
<template v-if="ingredient.unit.plural_name === '' || ingredient.unit.plural_name === null">
|
||||
<span>{{ ingredient.unit.name }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-if="ingredient.always_use_plural_unit">{{ ingredient.unit.plural_name}}</span>
|
||||
<span v-else-if="(ingredient.amount * this.ingredient_factor) > 1">{{ ingredient.unit.plural_name }}</span>
|
||||
<span v-if="ingredient.always_use_plural_unit">{{ ingredient.unit.plural_name }}</span>
|
||||
<span v-else-if="ingredient.amount * this.ingredient_factor > 1">{{ ingredient.unit.plural_name }}</span>
|
||||
<span v-else>{{ ingredient.unit.name }}</span>
|
||||
</template>
|
||||
</template>
|
||||
@@ -34,21 +30,18 @@
|
||||
</td>
|
||||
<td @click="done">
|
||||
<template v-if="ingredient.food !== null">
|
||||
<a :href="resolveDjangoUrl('view_recipe', ingredient.food.recipe.id)"
|
||||
v-if="ingredient.food.recipe !== null" target="_blank"
|
||||
rel="noopener noreferrer">{{ ingredient.food.name }}</a>
|
||||
<a :href="resolveDjangoUrl('view_recipe', ingredient.food.recipe.id)" v-if="ingredient.food.recipe !== null" target="_blank" rel="noopener noreferrer">{{
|
||||
ingredient.food.name
|
||||
}}</a>
|
||||
<template v-if="ingredient.food.recipe === null">
|
||||
<template v-if="!use_plural">
|
||||
<span>{{ ingredient.food.name }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template>
|
||||
<template v-if="ingredient.food.plural_name === '' || ingredient.food.plural_name === null">
|
||||
<span>{{ ingredient.food.name }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-if="ingredient.always_use_plural_food">{{ ingredient.food.plural_name }}</span>
|
||||
<span v-else-if="ingredient.no_amount">{{ ingredient.food.name }}</span>
|
||||
<span v-else-if="(ingredient.amount * this.ingredient_factor) > 1">{{ ingredient.food.plural_name }}</span>
|
||||
<span v-else-if="ingredient.amount * this.ingredient_factor > 1">{{ ingredient.food.plural_name }}</span>
|
||||
<span v-else>{{ ingredient.food.name }}</span>
|
||||
</template>
|
||||
</template>
|
||||
@@ -56,36 +49,32 @@
|
||||
</template>
|
||||
</td>
|
||||
<td v-if="detailed">
|
||||
<div v-if="ingredient.note">
|
||||
<span v-b-popover.hover="ingredient.note" class="d-print-none touchable p-0 pl-md-2 pr-md-2">
|
||||
<template v-if="ingredient.note">
|
||||
<span v-b-popover.hover="ingredient.note" class="d-print-none touchable py-0 px-2">
|
||||
<i class="far fa-comment"></i>
|
||||
</span>
|
||||
|
||||
<div class="d-none d-print-block"><i class="far fa-comment-alt d-print-none"></i> {{
|
||||
ingredient.note
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-none d-print-block"><i class="far fa-comment-alt d-print-none"></i> {{ ingredient.note }}</div>
|
||||
</template>
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {calculateAmount, ResolveUrlMixin} from "@/utils/utils"
|
||||
import { calculateAmount, ResolveUrlMixin } from "@/utils/utils"
|
||||
|
||||
import Vue from "vue"
|
||||
import VueSanitize from "vue-sanitize";
|
||||
import VueSanitize from "vue-sanitize"
|
||||
|
||||
Vue.use(VueSanitize);
|
||||
Vue.use(VueSanitize)
|
||||
|
||||
export default {
|
||||
name: "IngredientComponent",
|
||||
props: {
|
||||
ingredient: Object,
|
||||
ingredient_factor: {type: Number, default: 1},
|
||||
use_plural:{type: Boolean, default: false},
|
||||
detailed: {type: Boolean, default: true},
|
||||
ingredient_factor: { type: Number, default: 1 },
|
||||
detailed: { type: Boolean, default: true },
|
||||
},
|
||||
mixins: [ResolveUrlMixin],
|
||||
data() {
|
||||
@@ -94,9 +83,7 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
calculateAmount: function (x) {
|
||||
return this.$sanitize(calculateAmount(x, this.ingredient_factor))
|
||||
@@ -112,9 +99,9 @@ export default {
|
||||
<style scoped>
|
||||
/* increase size of hover/touchable space without changing spacing */
|
||||
.touchable {
|
||||
padding-right: 2em;
|
||||
padding-left: 2em;
|
||||
margin-right: -2em;
|
||||
margin-left: -2em;
|
||||
/* padding-right: 2em;
|
||||
padding-left: 2em; */
|
||||
margin-right: -1em;
|
||||
margin-left: -1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-if="recipe.keywords.length > 0">
|
||||
<span :key="k.id" v-for="k in recipe.keywords.filter((kk) => { return kk.show || kk.show === undefined })" class="pl-1">
|
||||
<span :key="k.id" v-for="k in recipe.keywords.slice(0,keyword_splice).filter((kk) => { return kk.show || kk.show === undefined })" class="pl-1">
|
||||
<a :href="`${resolveDjangoUrl('view_search')}?keyword=${k.id}`"><b-badge pill variant="light"
|
||||
class="font-weight-normal">{{ k.label }}</b-badge></a>
|
||||
|
||||
@@ -17,6 +17,15 @@ export default {
|
||||
mixins: [ResolveUrlMixin],
|
||||
props: {
|
||||
recipe: Object,
|
||||
limit: Number,
|
||||
},
|
||||
computed: {
|
||||
keyword_splice: function (){
|
||||
if(this.limit){
|
||||
return this.limit
|
||||
}
|
||||
return this.recipe.keywords.lenght
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,114 +1,148 @@
|
||||
<template>
|
||||
<b-modal :id="modal_id" size="lg" :title="modal_title" hide-footer aria-label="" @show="showModal">
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-6 col-lg-9">
|
||||
<b-input-group>
|
||||
<b-form-input id="TitleInput" v-model="entryEditing.title" :placeholder="entryEditing.title_placeholder" @change="missing_recipe = false"></b-form-input>
|
||||
<b-input-group-append class="d-none d-lg-block">
|
||||
<b-button variant="primary" @click="entryEditing.title = ''"><i class="fa fa-eraser"></i></b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<span class="text-danger" v-if="missing_recipe">{{ $t("Title_or_Recipe_Required") }}</span>
|
||||
<small tabindex="-1" class="form-text text-muted" v-if="!missing_recipe">{{ $t("Title") }}</small>
|
||||
<div>
|
||||
<b-modal :id="modal_id" size="lg" :title="modal_title" hide-footer aria-label="" @show="showModal">
|
||||
<div class="row" v-if="entryEditing !== null">
|
||||
<div class="col col-md-12">
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-6 col-lg-9">
|
||||
<b-input-group>
|
||||
<b-form-input id="TitleInput" v-model="entryEditing.title"
|
||||
:placeholder="entryEditing.title_placeholder"
|
||||
@change="missing_recipe = false"></b-form-input>
|
||||
<b-input-group-append class="d-none d-lg-block">
|
||||
<b-button variant="primary" @click="entryEditing.title = ''"><i
|
||||
class="fa fa-eraser"></i></b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<span class="text-danger" v-if="missing_recipe">{{
|
||||
$t("Title_or_Recipe_Required")
|
||||
}}</span>
|
||||
<small tabindex="-1" class="form-text text-muted" v-if="!missing_recipe">{{
|
||||
$t("Title")
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<input type="date" id="DateInput" class="form-control" v-model="entryEditing.date"/>
|
||||
<small tabindex="-1" class="form-text text-muted">{{ $t("Date") }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<input type="date" id="DateInput" class="form-control" v-model="entryEditing.date" />
|
||||
<small tabindex="-1" class="form-text text-muted">{{ $t("Date") }}</small>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 col-lg-6 col-xl-6">
|
||||
<b-form-group>
|
||||
<generic-multiselect
|
||||
@change="selectRecipe"
|
||||
:initial_single_selection="entryEditing.recipe"
|
||||
:label="'name'"
|
||||
:model="Models.RECIPE"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
v-bind:placeholder="$t('Recipe')"
|
||||
:limit="10"
|
||||
:multiple="false"
|
||||
></generic-multiselect>
|
||||
<small tabindex="-1" class="form-text text-muted">{{ $t("Recipe") }}</small>
|
||||
</b-form-group>
|
||||
<b-form-group class="mt-3">
|
||||
<generic-multiselect
|
||||
required
|
||||
@change="selectMealType"
|
||||
:label="'name'"
|
||||
:model="Models.MEAL_TYPE"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
v-bind:placeholder="$t('Meal_Type')"
|
||||
:limit="10"
|
||||
:multiple="false"
|
||||
:initial_single_selection="entryEditing.meal_type"
|
||||
:allow_create="true"
|
||||
:create_placeholder="$t('Create_New_Meal_Type')"
|
||||
></generic-multiselect>
|
||||
<span class="text-danger" v-if="missing_meal_type">{{
|
||||
$t("Meal_Type_Required")
|
||||
}}</span>
|
||||
<small tabindex="-1" class="form-text text-muted"
|
||||
v-if="!missing_meal_type">{{ $t("Meal_Type") }}</small>
|
||||
</b-form-group>
|
||||
<b-form-group label-for="NoteInput" :description="$t('Note')" class="mt-3">
|
||||
<textarea class="form-control" id="NoteInput" v-model="entryEditing.note"
|
||||
:placeholder="$t('Note')"></textarea>
|
||||
</b-form-group>
|
||||
<b-input-group>
|
||||
<b-form-input id="ServingsInput" v-model="entryEditing.servings"
|
||||
:placeholder="$t('Servings')"></b-form-input>
|
||||
</b-input-group>
|
||||
<small tabindex="-1" class="form-text text-muted">{{ $t("Servings") }}</small>
|
||||
<b-form-group class="mt-3">
|
||||
<generic-multiselect
|
||||
required
|
||||
@change="entryEditing.shared = $event.val"
|
||||
parent_variable="entryEditing.shared"
|
||||
:label="'display_name'"
|
||||
:model="Models.USER_NAME"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
v-bind:placeholder="$t('Share')"
|
||||
:limit="10"
|
||||
:multiple="true"
|
||||
:initial_selection="entryEditing.shared"
|
||||
></generic-multiselect>
|
||||
<small tabindex="-1" class="form-text text-muted">{{ $t("Share") }}</small>
|
||||
</b-form-group>
|
||||
<b-input-group v-if="!autoMealPlan">
|
||||
<b-form-checkbox id="AddToShopping" v-model="mealplan_settings.addshopping"/>
|
||||
<small tabindex="-1" class="form-text text-muted">{{
|
||||
$t("AddToShopping")
|
||||
}}</small>
|
||||
</b-input-group>
|
||||
<b-input-group v-if="mealplan_settings.addshopping && !autoMealPlan">
|
||||
<b-form-checkbox id="reviewShopping"
|
||||
v-model="mealplan_settings.reviewshopping"/>
|
||||
<small tabindex="-1" class="form-text text-muted">{{
|
||||
$t("review_shopping")
|
||||
}}</small>
|
||||
</b-input-group>
|
||||
</div>
|
||||
<div class="col-lg-6 d-none d-lg-block d-xl-block">
|
||||
<recipe-card v-if="entryEditing.recipe" :recipe="entryEditing.recipe"
|
||||
:detailed="false"></recipe-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 col-lg-6 col-xl-6">
|
||||
<b-form-group>
|
||||
<generic-multiselect
|
||||
@change="selectRecipe"
|
||||
:initial_single_selection="entryEditing.recipe"
|
||||
:label="'name'"
|
||||
:model="Models.RECIPE"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
v-bind:placeholder="$t('Recipe')"
|
||||
:limit="10"
|
||||
:multiple="false"
|
||||
></generic-multiselect>
|
||||
<small tabindex="-1" class="form-text text-muted">{{ $t("Recipe") }}</small>
|
||||
</b-form-group>
|
||||
<b-form-group class="mt-3">
|
||||
<generic-multiselect
|
||||
required
|
||||
@change="selectMealType"
|
||||
:label="'name'"
|
||||
:model="Models.MEAL_TYPE"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
v-bind:placeholder="$t('Meal_Type')"
|
||||
:limit="10"
|
||||
:multiple="false"
|
||||
:initial_single_selection="entryEditing.meal_type"
|
||||
:allow_create="true"
|
||||
:create_placeholder="$t('Create_New_Meal_Type')"
|
||||
></generic-multiselect>
|
||||
<span class="text-danger" v-if="missing_meal_type">{{ $t("Meal_Type_Required") }}</span>
|
||||
<small tabindex="-1" class="form-text text-muted" v-if="!missing_meal_type">{{ $t("Meal_Type") }}</small>
|
||||
</b-form-group>
|
||||
<b-form-group label-for="NoteInput" :description="$t('Note')" class="mt-3">
|
||||
<textarea class="form-control" id="NoteInput" v-model="entryEditing.note" :placeholder="$t('Note')"></textarea>
|
||||
</b-form-group>
|
||||
<b-input-group>
|
||||
<b-form-input id="ServingsInput" v-model="entryEditing.servings" :placeholder="$t('Servings')"></b-form-input>
|
||||
</b-input-group>
|
||||
<small tabindex="-1" class="form-text text-muted">{{ $t("Servings") }}</small>
|
||||
<b-form-group class="mt-3">
|
||||
<generic-multiselect
|
||||
required
|
||||
@change="entryEditing.shared = $event.val"
|
||||
parent_variable="entryEditing.shared"
|
||||
:label="'display_name'"
|
||||
:model="Models.USER_NAME"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
v-bind:placeholder="$t('Share')"
|
||||
:limit="10"
|
||||
:multiple="true"
|
||||
:initial_selection="entryEditing.shared"
|
||||
></generic-multiselect>
|
||||
<small tabindex="-1" class="form-text text-muted">{{ $t("Share") }}</small>
|
||||
</b-form-group>
|
||||
<b-input-group v-if="!autoMealPlan">
|
||||
<b-form-checkbox id="AddToShopping" v-model="mealplan_settings.addshopping" />
|
||||
<small tabindex="-1" class="form-text text-muted">{{ $t("AddToShopping") }}</small>
|
||||
</b-input-group>
|
||||
<b-input-group v-if="mealplan_settings.addshopping">
|
||||
<b-form-checkbox id="reviewShopping" v-model="mealplan_settings.reviewshopping" />
|
||||
<small tabindex="-1" class="form-text text-muted">{{ $t("review_shopping") }}</small>
|
||||
</b-input-group>
|
||||
</div>
|
||||
<div class="col-lg-6 d-none d-lg-block d-xl-block">
|
||||
<recipe-card v-if="entryEditing.recipe" :recipe="entryEditing.recipe" :detailed="false" :use_plural="use_plural"></recipe-card>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3 mb-3">
|
||||
<div class="col-12">
|
||||
<b-button variant="danger" @click="deleteEntry" v-if="allow_delete">{{ $t("Delete") }} </b-button>
|
||||
<b-button class="float-right" variant="primary" @click="editEntry">{{ $t("Save") }}</b-button>
|
||||
<div class="row mt-3 mb-3">
|
||||
<div class="col-12">
|
||||
<b-button variant="danger" @click="deleteEntry" v-if="allow_delete">{{
|
||||
$t("Delete")
|
||||
}}
|
||||
</b-button>
|
||||
<b-button class="float-right" variant="primary" @click="editEntry">{{
|
||||
$t("Save")
|
||||
}}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</b-modal>
|
||||
|
||||
<shopping-modal :recipe="last_created_plan.recipe" :servings="last_created_plan.servings" :modal_id="999999"
|
||||
:mealplan="last_created_plan" v-if="last_created_plan !== null && last_created_plan.recipe !== null"/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from "vue"
|
||||
import VueCookies from "vue-cookies"
|
||||
import { BootstrapVue } from "bootstrap-vue"
|
||||
import {BootstrapVue} from "bootstrap-vue"
|
||||
import GenericMultiselect from "@/components/GenericMultiselect"
|
||||
import { ApiMixin, getUserPreference } from "@/utils/utils"
|
||||
import {ApiMixin, getUserPreference} from "@/utils/utils"
|
||||
|
||||
const { ApiApiFactory } = require("@/utils/openapi/api")
|
||||
const { StandardToasts } = require("@/utils/utils")
|
||||
const {ApiApiFactory} = require("@/utils/openapi/api")
|
||||
const {StandardToasts} = require("@/utils/utils")
|
||||
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||
import ShoppingModal from "@/components/Modals/ShoppingModal.vue";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
Vue.use(VueCookies)
|
||||
@@ -118,11 +152,11 @@ export default {
|
||||
name: "MealPlanEditModal",
|
||||
props: {
|
||||
entry: Object,
|
||||
entryEditing_inital_servings: Number,
|
||||
create_date: String,
|
||||
modal_title: String,
|
||||
modal_id: {
|
||||
type: String,
|
||||
default: "edit-modal",
|
||||
default: "id_meal_plan_edit_modal",
|
||||
},
|
||||
allow_delete: {
|
||||
type: Boolean,
|
||||
@@ -133,10 +167,11 @@ export default {
|
||||
components: {
|
||||
GenericMultiselect,
|
||||
RecipeCard: () => import("@/components/RecipeCard.vue"),
|
||||
ShoppingModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
entryEditing: {},
|
||||
entryEditing: null,
|
||||
missing_recipe: false,
|
||||
missing_meal_type: false,
|
||||
default_plan_share: [],
|
||||
@@ -144,22 +179,19 @@ export default {
|
||||
addshopping: false,
|
||||
reviewshopping: false,
|
||||
},
|
||||
use_plural: false,
|
||||
last_created_plan: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
entry: {
|
||||
handler() {
|
||||
this.entryEditing = Object.assign({}, this.entry)
|
||||
|
||||
if (this.entryEditing_inital_servings) {
|
||||
this.entryEditing.servings = this.entryEditing_inital_servings
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
entryEditing: {
|
||||
handler(newVal) {},
|
||||
handler(newVal) {
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
mealplan_settings: {
|
||||
@@ -168,19 +200,13 @@ export default {
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
entryEditing_inital_servings: function (newVal) {
|
||||
this.entryEditing.servings = newVal
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
let apiClient = new ApiApiFactory()
|
||||
apiClient.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
|
||||
this.use_plural = r.data.use_plural
|
||||
})
|
||||
useUserPreferenceStore().updateIfStaleOrEmpty()
|
||||
},
|
||||
computed: {
|
||||
autoMealPlan: function () {
|
||||
return getUserPreference("mealplan_autoadd_shopping")
|
||||
return useUserPreferenceStore().getStaleData()?.mealplan_autoadd_shopping
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -188,35 +214,53 @@ export default {
|
||||
if (this.$cookies.isKey(MEALPLAN_COOKIE_NAME)) {
|
||||
this.mealplan_settings = Object.assign({}, this.mealplan_settings, this.$cookies.get(MEALPLAN_COOKIE_NAME))
|
||||
}
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
apiClient.listUserPreferences().then((result) => {
|
||||
if (this.entry.id === -1) {
|
||||
this.entryEditing.shared = result.data[0].plan_share
|
||||
if (this.entry === null) {
|
||||
this.entryEditing = Object.assign({}, useMealPlanStore().empty_meal_plan, null)
|
||||
} else {
|
||||
this.entryEditing = Object.assign({}, this.entry, null)
|
||||
}
|
||||
|
||||
if (this.create_date) {
|
||||
this.entryEditing.date = this.create_date
|
||||
}
|
||||
|
||||
useUserPreferenceStore().getData().then(userPreference => {
|
||||
if (this.entryEditing.id === -1) {
|
||||
this.entryEditing.shared = userPreference.plan_share
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
editEntry() {
|
||||
this.missing_meal_type = false
|
||||
this.missing_recipe = false
|
||||
let cancel = false
|
||||
|
||||
if (this.entryEditing.meal_type == null) {
|
||||
this.missing_meal_type = true
|
||||
cancel = true
|
||||
return;
|
||||
}
|
||||
if (this.entryEditing.recipe == null && this.entryEditing.title === "") {
|
||||
this.missing_recipe = true
|
||||
cancel = true
|
||||
return
|
||||
}
|
||||
if (!cancel) {
|
||||
this.$bvModal.hide(`edit-modal`)
|
||||
this.$emit("save-entry", { ...this.mealplan_settings, ...this.entryEditing, ...{ addshopping: this.mealplan_settings.addshopping && !this.autoMealPlan } })
|
||||
//TODO properly validate
|
||||
this.$bvModal.hide(this.modal_id)
|
||||
|
||||
// only set addshopping if review is not enabled
|
||||
this.$set(this.entryEditing, 'addshopping', (this.mealplan_settings.addshopping && !this.mealplan_settings.reviewshopping))
|
||||
|
||||
if (!('id' in this.entryEditing) || this.entryEditing.id === -1) {
|
||||
useMealPlanStore().createObject(this.entryEditing).then((r) => {
|
||||
this.last_created_plan = r.data
|
||||
if (r.data.recipe && this.mealplan_settings.addshopping && !this.autoMealPlan && this.mealplan_settings.reviewshopping) {
|
||||
this.$nextTick(function () {
|
||||
this.$bvModal.show(`shopping_999999`)
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
useMealPlanStore().updateObject(this.entryEditing)
|
||||
}
|
||||
},
|
||||
deleteEntry() {
|
||||
this.$bvModal.hide(`edit-modal`)
|
||||
this.$emit("delete-entry", this.entryEditing)
|
||||
this.$bvModal.hide(this.modal_id)
|
||||
useMealPlanStore().deleteObject(this.entryEditing)
|
||||
},
|
||||
selectMealType(event) {
|
||||
this.missing_meal_type = false
|
||||
|
||||
@@ -3,69 +3,52 @@
|
||||
<b-form-group
|
||||
v-bind:label="label"
|
||||
class="mb-3">
|
||||
<twemoji-textarea
|
||||
:ref="'_edit_' + id"
|
||||
:initialContent="value"
|
||||
:emojiData="emojiDataAll"
|
||||
:emojiGroups="emojiGroups"
|
||||
triggerType="click"
|
||||
:recentEmojisFeat="true"
|
||||
recentEmojisStorage="local"
|
||||
@contentChanged="setIcon"
|
||||
/>
|
||||
|
||||
<input class="form-control" v-model="new_value">
|
||||
|
||||
<Picker :data="emojiIndex" :ref="'_edit_' + id" :native="true"
|
||||
@select="setIcon"/>
|
||||
|
||||
</b-form-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {TwemojiTextarea} from '@kevinfaguiar/vue-twemoji-picker';
|
||||
// TODO add localization
|
||||
import EmojiAllData from '@kevinfaguiar/vue-twemoji-picker/emoji-data/en/emoji-all-groups.json';
|
||||
import EmojiGroups from '@kevinfaguiar/vue-twemoji-picker/emoji-data/emoji-groups.json';
|
||||
|
||||
import data from "emoji-mart-vue-fast/data/all.json";
|
||||
import "emoji-mart-vue-fast/css/emoji-mart.css";
|
||||
import {Picker, EmojiIndex} from "emoji-mart-vue-fast";
|
||||
let emojiIndex = new EmojiIndex(data);
|
||||
|
||||
export default {
|
||||
name: 'EmojiInput',
|
||||
components: {TwemojiTextarea},
|
||||
props: {
|
||||
field: {type: String, default: 'You Forgot To Set Field Name'},
|
||||
label: {type: String, default: ''},
|
||||
value: {type: String, default: ''},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
new_value: undefined,
|
||||
id: null
|
||||
name: 'EmojiInput',
|
||||
components: {Picker},
|
||||
props: {
|
||||
field: {type: String, default: 'You Forgot To Set Field Name'},
|
||||
label: {type: String, default: ''},
|
||||
value: {type: String, default: ''},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
new_value: undefined,
|
||||
id: null,
|
||||
emojiIndex: emojiIndex,
|
||||
emojisOutput: ""
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'new_value': function () {
|
||||
this.$root.$emit('change', this.field, this.new_value ?? null)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.id = this._uid
|
||||
},
|
||||
methods: {
|
||||
setIcon: function (icon) {
|
||||
console.log(icon)
|
||||
this.new_value = icon.native
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// modelName() {
|
||||
// return this?.model?.name ?? this.$t('Search')
|
||||
// },
|
||||
emojiDataAll() {
|
||||
return EmojiAllData;
|
||||
},
|
||||
emojiGroups() {
|
||||
return EmojiGroups;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'new_value': function () {
|
||||
this.$root.$emit('change', this.field, this.new_value ?? null)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.id = this._uid
|
||||
},
|
||||
methods: {
|
||||
prepareEmoji: function() {
|
||||
this.$refs['_edit_' + this.id].addText(this.this_item.icon || '');
|
||||
this.$refs['_edit_' + this.id].blur()
|
||||
document.getElementById('btn-emoji-default').disabled = true;
|
||||
},
|
||||
setIcon: function(icon) {
|
||||
this.new_value = icon
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -15,6 +15,7 @@
|
||||
<file-input v-if="visibleCondition(f, 'file')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue" />
|
||||
<small-text v-if="visibleCondition(f, 'smalltext')" :value="f.value" />
|
||||
<date-input v-if="visibleCondition(f, 'date')" :label="f.label" :value="f.value" :field="f.field" :help="showHelp && f.help" :subtitle="f.subtitle" />
|
||||
<number-input v-if="visibleCondition(f, 'number')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" :help="showHelp && f.help" :subtitle="f.subtitle" />
|
||||
</div>
|
||||
<template v-slot:modal-footer>
|
||||
<div class="row w-100">
|
||||
@@ -49,10 +50,11 @@ import ChoiceInput from "@/components/Modals/ChoiceInput"
|
||||
import FileInput from "@/components/Modals/FileInput"
|
||||
import SmallText from "@/components/Modals/SmallText"
|
||||
import HelpBadge from "@/components/Badges/Help"
|
||||
import NumberInput from "@/components/Modals/NumberInput.vue";
|
||||
|
||||
export default {
|
||||
name: "GenericModalForm",
|
||||
components: { FileInput, CheckboxInput, LookupInput, TextInput, EmojiInput, ChoiceInput, SmallText, HelpBadge,DateInput },
|
||||
components: { FileInput, CheckboxInput, LookupInput, TextInput, EmojiInput, ChoiceInput, SmallText, HelpBadge,DateInput, NumberInput },
|
||||
mixins: [ApiMixin, ToastMixin],
|
||||
props: {
|
||||
model: { required: true, type: Object },
|
||||
|
||||
101
vue/src/components/Modals/ImportTandoor.vue
Normal file
101
vue/src/components/Modals/ImportTandoor.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<b-button v-b-modal.id_import_tandoor_modal>{{ $t("Import into Tandoor") }}</b-button>
|
||||
|
||||
<b-modal class="modal" id="id_import_tandoor_modal" :title="$t('Import')" hide-footer>
|
||||
<p>Tandoor ist eine OpenSource Rezeptverwaltungs Plattform</p>
|
||||
|
||||
<p>Bitte wähle aus ob du deinen eigenen Tandoor Server hast oder tandoor.dev nutzt.
|
||||
</p>
|
||||
<div class="justify-content-center text-center">
|
||||
<b-form-group v-slot="{ ariaDescribedby }">
|
||||
<b-form-radio-group
|
||||
id="btn-radios-1"
|
||||
v-model="import_mode"
|
||||
:options="options"
|
||||
:aria-describedby="ariaDescribedby"
|
||||
name="radios-btn-default"
|
||||
buttons
|
||||
></b-form-radio-group>
|
||||
</b-form-group>
|
||||
</div>
|
||||
<div v-if="import_mode === 'tandoor'">
|
||||
<ol>
|
||||
<li><a href="https://app.tandoor.dev/accounts/signup/" target="_blank" ref="nofollow">Hier</a> einen
|
||||
Account anlegen<br/></li>
|
||||
<li>
|
||||
<b-button @click="importTandoor()">Import</b-button>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
|
||||
</div>
|
||||
<div v-if="import_mode === 'selfhosted'">
|
||||
Deine Server URL (z.B. <code>https://tandoor.mydomain.com/</code>)
|
||||
<b-input v-model="selfhosted_url"></b-input>
|
||||
<b-button class="mt-2" :disabled="selfhosted_url === ''" @click="importSelfHosted()">Import</b-button>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3 text-left mb-3">
|
||||
<p>Alternativ kannst du den Link zum Rezept in den Importer in deiner Tandoor Instanz kopieren.</p>
|
||||
|
||||
<a href="https://tandoor.dev" target="_blank" rel="nofollow">Jetzt mehr über Tandoor erfahren</a>
|
||||
</div>
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import {BootstrapVue} from "bootstrap-vue";
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: 'ImportTandoor',
|
||||
components: {},
|
||||
props: {
|
||||
recipe: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
import_mode: 'tandoor',
|
||||
options: [
|
||||
{text: 'Tandoor.dev', value: 'tandoor'},
|
||||
{text: 'Self-Hosted', value: 'selfhosted'},
|
||||
],
|
||||
selfhosted_url: '',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selfhosted_url: function (newVal) {
|
||||
window.localStorage.setItem('MY_TANDOOR_URL', newVal)
|
||||
},
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||
let selfhosted_url = window.localStorage.getItem('MY_TANDOOR_URL')
|
||||
if (selfhosted_url !== undefined) {
|
||||
this.selfhosted_url = selfhosted_url
|
||||
this.import_mode = 'selfhosted'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
importTandoor: function () {
|
||||
location.href = 'https://app.tandoor.dev/data/import/url?url=' + location.href
|
||||
},
|
||||
importSelfHosted: function () {
|
||||
this.selfhosted_url = this.selfhosted_url.replace('/search/', '')
|
||||
let import_path = 'data/import/url?url='
|
||||
if (!this.selfhosted_url.endsWith('/')) {
|
||||
import_path = '/' + import_path
|
||||
}
|
||||
location.href = this.selfhosted_url + import_path + location.href
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
37
vue/src/components/Modals/NumberInput.vue
Normal file
37
vue/src/components/Modals/NumberInput.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-form-group v-bind:label="label" class="mb-3">
|
||||
<b-form-input v-model="new_value" type="number" :placeholder="placeholder"></b-form-input>
|
||||
<em v-if="help" class="small text-muted">{{ help }}</em>
|
||||
<small v-if="subtitle" class="text-muted">{{ subtitle }}</small>
|
||||
</b-form-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TextInput",
|
||||
props: {
|
||||
field: { type: String, default: "You Forgot To Set Field Name" },
|
||||
label: { type: String, default: "Text Field" },
|
||||
value: { type: String, default: "" },
|
||||
placeholder: { type: Number, default: 0 },
|
||||
help: { type: String, default: undefined },
|
||||
subtitle: { type: String, default: undefined },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
new_value: undefined,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.new_value = this.value
|
||||
},
|
||||
watch: {
|
||||
new_value: function () {
|
||||
this.$root.$emit("change", this.field, this.new_value)
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
@@ -10,12 +10,10 @@
|
||||
</b-card-header>
|
||||
<b-collapse id="accordion-0" class="p-2" visible accordion="my-accordion" role="tabpanel">
|
||||
|
||||
<div v-for="i in steps.flatMap(s => s.ingredients)" v-bind:key="i.id">
|
||||
<div>
|
||||
<table class="table table-sm mb-0">
|
||||
|
||||
<ingredient-component
|
||||
:use_plural="true"
|
||||
:key="i.id"
|
||||
<ingredient-component v-for="i in steps.flatMap(s => s.ingredients)" v-bind:key="i.id"
|
||||
:detailed="true"
|
||||
:ingredient="i"
|
||||
:ingredient_factor="ingredient_factor"
|
||||
@@ -79,6 +77,7 @@ const {ApiApiFactory} = require("@/utils/openapi/api")
|
||||
import {StandardToasts} from "@/utils/utils"
|
||||
import IngredientComponent from "@/components/IngredientComponent"
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||
// import CustomInputSpinButton from "@/components/CustomInputSpinButton"
|
||||
|
||||
export default {
|
||||
@@ -89,7 +88,7 @@ export default {
|
||||
recipe: {required: true, type: Object},
|
||||
servings: {type: Number, default: undefined},
|
||||
modal_id: {required: true, type: Number},
|
||||
mealplan: {type: Number, default: undefined},
|
||||
mealplan: {type: Object, default: undefined},
|
||||
list_recipe: {type: Number, default: undefined},
|
||||
},
|
||||
data() {
|
||||
@@ -128,7 +127,7 @@ export default {
|
||||
if (!this.recipe_servings) {
|
||||
this.recipe_servings = result.data?.servings
|
||||
}
|
||||
this.steps.forEach(s => s.ingredients.filter(i => i.food.food_onhand === false).forEach(i => this.$set(i, 'checked', true)))
|
||||
this.steps.forEach(s => s.ingredients.filter(i => i.food?.food_onhand === false).forEach(i => this.$set(i, 'checked', true)))
|
||||
this.loading = false
|
||||
})
|
||||
.then(() => {
|
||||
@@ -170,6 +169,9 @@ export default {
|
||||
.then((result) => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE)
|
||||
this.$emit("finish")
|
||||
if (this.mealplan !== undefined && this.mealplan !== null){
|
||||
useMealPlanStore().plans[this.mealplan.id].shopping = true
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE, err)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="recipe && recipe.loading">
|
||||
<b-card no-body v-hover>
|
||||
<b-card no-body v-hover style="height: 100%">
|
||||
<b-card-img-lazy style="height: 15vh; object-fit: cover" class="" :src="placeholder_image"
|
||||
v-bind:alt="$t('Recipe_Image')" top></b-card-img-lazy>
|
||||
|
||||
@@ -19,54 +19,59 @@
|
||||
</b-card>
|
||||
</template>
|
||||
<template v-else>
|
||||
<b-card no-body v-hover v-if="recipe">
|
||||
<b-card no-body v-hover v-if="recipe" style="height: 100%">
|
||||
|
||||
<a :href="this.recipe.id !== undefined ? resolveDjangoUrl('view_recipe', this.recipe.id) : null">
|
||||
<b-card-img-lazy style="height: 15vh; object-fit: cover" class="" :src="recipe_image"
|
||||
v-bind:alt="$t('Recipe_Image')" top></b-card-img-lazy>
|
||||
<div
|
||||
class="card-img-overlay h-100 d-flex flex-column justify-content-right float-right text-right pt-2 pr-1"
|
||||
v-if="show_context_menu">
|
||||
<a>
|
||||
<recipe-context-menu :recipe="recipe" class="float-right" :disabled_options="context_disabled_options"
|
||||
v-if="recipe !== null"></recipe-context-menu>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-img-overlay w-50 d-flex flex-column justify-content-left float-left text-left pt-2"
|
||||
v-if="recipe.working_time !== 0 || recipe.waiting_time !== 0">
|
||||
<b-badge pill variant="light" class="mt-1 font-weight-normal" v-if="recipe.working_time !== 0">
|
||||
<i
|
||||
class="fa fa-clock"></i> {{ working_time }}
|
||||
</b-badge>
|
||||
<b-badge pill variant="secondary" class="mt-1 font-weight-normal"
|
||||
v-if="recipe.waiting_time !== 0">
|
||||
<i class="fa fa-pause"></i> {{ waiting_time }}
|
||||
</b-badge>
|
||||
<div class="content">
|
||||
<div class="content-overlay" v-if="recipe.description !== null && recipe.description !== ''"></div>
|
||||
<b-card-img-lazy style="height: 15vh; object-fit: cover" class="" :src="recipe_image"
|
||||
v-bind:alt="$t('Recipe_Image')" top></b-card-img-lazy>
|
||||
|
||||
<div class="content-details" >
|
||||
<p class="content-text">
|
||||
{{ recipe.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-img-overlay d-flex flex-column justify-content-left float-left text-left pt-2" style="width:40%"
|
||||
v-if="recipe.working_time !== 0 || recipe.waiting_time !== 0">
|
||||
<b-badge pill variant="light" class="mt-1 font-weight-normal" v-if="recipe.working_time !== 0">
|
||||
<i
|
||||
class="fa fa-clock"></i> {{ working_time }}
|
||||
</b-badge>
|
||||
<b-badge pill variant="secondary" class="mt-1 font-weight-normal"
|
||||
v-if="recipe.waiting_time !== 0">
|
||||
<i class="fa fa-pause"></i> {{ waiting_time }}
|
||||
</b-badge>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<b-card-body class="p-4">
|
||||
<h6>
|
||||
<a :href="this.recipe.id !== undefined ? resolveDjangoUrl('view_recipe', this.recipe.id) : null">
|
||||
<b-card-body class="p-2 pl-3 pr-3">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="flex-grow-1">
|
||||
<a :href="this.recipe.id !== undefined ? resolveDjangoUrl('view_recipe', this.recipe.id) : null" class="text-body font-weight-bold two-row-text">
|
||||
<template v-if="recipe !== null">{{ recipe.name }}</template>
|
||||
<template v-else>{{ meal_plan.title }}</template>
|
||||
</a>
|
||||
</h6>
|
||||
</div>
|
||||
<div class="justify-content-end">
|
||||
<recipe-context-menu :recipe="recipe" class="justify-content-end float-right align-items-end pr-0"
|
||||
:disabled_options="context_disabled_options"
|
||||
v-if="recipe !== null"></recipe-context-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<b-card-text style="text-overflow: ellipsis">
|
||||
<template v-if="recipe !== null">
|
||||
<recipe-rating :recipe="recipe"></recipe-rating>
|
||||
<template v-if="recipe.description !== null && recipe.description !== undefined">
|
||||
<span v-if="recipe.description.length > text_length">
|
||||
{{ recipe.description.substr(0, text_length) + "\u2026" }}
|
||||
</span>
|
||||
<span v-if="recipe.description.length <= text_length">
|
||||
{{ recipe.description }}
|
||||
</span>
|
||||
</template>
|
||||
<p class="mt-1">
|
||||
<div v-if="show_detail">
|
||||
{{ recipe.description }}
|
||||
</div>
|
||||
|
||||
<p class="mt-1 mb-1">
|
||||
<last-cooked :recipe="recipe"></last-cooked>
|
||||
<keywords-component :recipe="recipe"
|
||||
<keywords-component :recipe="recipe" :limit="3"
|
||||
style="margin-top: 4px; position: relative; z-index: 3;"></keywords-component>
|
||||
</p>
|
||||
<transition name="fade" mode="in-out">
|
||||
@@ -75,29 +80,46 @@
|
||||
<h6 class="card-title"><i class="fas fa-pepper-hot"></i> {{ $t("Ingredients") }}
|
||||
</h6>
|
||||
|
||||
<ingredients-card
|
||||
:steps="recipe.steps"
|
||||
:header="false"
|
||||
:detailed="false"
|
||||
:servings="recipe.servings"
|
||||
:use_plural="use_plural" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<ingredients-card
|
||||
:steps="recipe.steps"
|
||||
:header="false"
|
||||
:detailed="false"
|
||||
:servings="recipe.servings"/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<b-badge pill variant="info" v-if="!recipe.internal">{{ $t("External") }}</b-badge>
|
||||
</template>
|
||||
<template v-else>{{ meal_plan.note }}</template>
|
||||
|
||||
</b-card-text>
|
||||
</b-card-body>
|
||||
|
||||
<b-card-footer v-if="footer_text !== undefined"><i v-bind:class="footer_icon"></i> {{ footer_text }}
|
||||
</b-card-footer>
|
||||
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
|
||||
<recipe-rating :recipe="recipe"></recipe-rating>
|
||||
|
||||
<template v-if="recipe.description !== null && recipe.description !== undefined">
|
||||
<span v-if="recipe.description.length > text_length">
|
||||
{{ recipe.description.substr(0, text_length) + "\u2026" }}
|
||||
</span>
|
||||
<span v-if="recipe.description.length <= text_length">
|
||||
{{ recipe.description }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<b-card-footer v-if="footer_text !== undefined"><i v-bind:class="footer_icon"></i> {{ footer_text }}
|
||||
</b-card-footer>
|
||||
|
||||
<template v-else>{{ meal_plan.note }}</template>
|
||||
|
||||
-->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -117,7 +139,6 @@ export default {
|
||||
mixins: [ResolveUrlMixin],
|
||||
components: {
|
||||
LastCooked,
|
||||
RecipeRating,
|
||||
KeywordsComponent,
|
||||
"recipe-context-menu": RecipeContextMenu,
|
||||
IngredientsCard
|
||||
@@ -125,7 +146,7 @@ export default {
|
||||
props: {
|
||||
recipe: Object,
|
||||
meal_plan: Object,
|
||||
use_plural: { type: Boolean, default: false},
|
||||
use_plural: {type: Boolean, default: false},
|
||||
footer_text: String,
|
||||
footer_icon: String,
|
||||
detailed: {type: Boolean, default: true},
|
||||
@@ -144,13 +165,6 @@ export default {
|
||||
show_detail: function () {
|
||||
return this.recipe?.steps !== undefined && this.detailed
|
||||
},
|
||||
text_length: function () {
|
||||
if (this.show_detail) {
|
||||
return 200
|
||||
} else {
|
||||
return 120
|
||||
}
|
||||
},
|
||||
recipe_image: function () {
|
||||
if (this.recipe == null || this.recipe.image === null) {
|
||||
return window.IMAGE_PLACEHOLDER
|
||||
@@ -191,4 +205,79 @@ export default {
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.two-row-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2; /* number of lines to show */
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
|
||||
margin: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.content .content-overlay {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
position: absolute;
|
||||
height: 99%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
-webkit-transition: all 0.4s ease-in-out 0s;
|
||||
-moz-transition: all 0.4s ease-in-out 0s;
|
||||
transition: all 0.4s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.content:hover .content-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.content-details {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
width: 100%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
opacity: 0;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
-moz-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-transition: all 0.3s ease-in-out 0s;
|
||||
-moz-transition: all 0.3s ease-in-out 0s;
|
||||
transition: all 0.3s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.content:hover .content-details {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.content-details h3 {
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.15em;
|
||||
margin-bottom: 0.5em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.content-details p {
|
||||
color: #fff;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.fadeIn-bottom {
|
||||
top: 80%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="dropdown d-print-none">
|
||||
<a class="btn shadow-none" href="javascript:void(0);" role="button" id="dropdownMenuLink"
|
||||
<a class="btn shadow-none pr-0 pl-0" href="javascript:void(0);" role="button" id="dropdownMenuLink"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-ellipsis-v fa-lg"></i>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink">
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink" >
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)" v-if="!disabled_options.edit"><i
|
||||
class="fas fa-pencil-alt fa-fw"></i> {{ $t("Edit") }}</a>
|
||||
|
||||
@@ -106,6 +106,7 @@ import ShoppingModal from "@/components/Modals/ShoppingModal"
|
||||
import moment from "moment"
|
||||
import Vue from "vue"
|
||||
import {ApiApiFactory} from "@/utils/openapi/api"
|
||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
@@ -191,6 +192,7 @@ export default {
|
||||
apiClient
|
||||
.createMealPlan(entry)
|
||||
.then((result) => {
|
||||
useMealPlanStore().plans.push(result.data)
|
||||
this.$bvModal.hide(`modal-meal-plan_${this.modal_id}`)
|
||||
if (reviewshopping) {
|
||||
this.mealplan = result.data.id
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<span v-if="user_preferences.shopping_auto_sync < 1">{{ $t('Disable') }}</span>
|
||||
</div>
|
||||
<br/>
|
||||
<b-button class="btn btn-sm" @click="user_preferences.shopping_auto_sync = 0">{{ $t('Disabled') }}</b-button>
|
||||
<b-button class="btn btn-sm" @click="user_preferences.shopping_auto_sync = 0; updateSettings(false)">{{ $t('Disabled') }}</b-button>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :description="$t('mealplan_autoadd_shopping_desc')">
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<div class="col col-md-4"
|
||||
v-if="step.ingredients.length > 0 && (recipe.steps.length > 1 || force_ingredients)">
|
||||
<table class="table table-sm">
|
||||
<ingredients-card :steps="[step]" :ingredient_factor="ingredient_factor" :use_plural="use_plural"
|
||||
<ingredients-card :steps="[step]" :ingredient_factor="ingredient_factor"
|
||||
@checked-state-changed="$emit('checked-state-changed', $event)"/>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user