Fix after rebase
This commit is contained in:
parent
f245aa8b4f
commit
dcfe4de61f
@ -83,6 +83,11 @@ class Migration(migrations.Migration):
|
|||||||
name='ingredient',
|
name='ingredient',
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.ingredient'),
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.ingredient'),
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='shoppinglistrecipe',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=32),
|
||||||
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='shoppinglistentry',
|
model_name='shoppinglistentry',
|
||||||
name='unit',
|
name='unit',
|
||||||
|
@ -822,7 +822,7 @@ class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), mod
|
|||||||
|
|
||||||
|
|
||||||
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
|
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
|
||||||
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True) # TODO remove when shoppinglist is deprecated
|
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
|
||||||
food = models.ForeignKey(Food, on_delete=models.CASCADE)
|
food = models.ForeignKey(Food, on_delete=models.CASCADE)
|
||||||
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)
|
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, null=True, blank=True)
|
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
@ -3,10 +3,11 @@ from rest_framework.schemas.utils import is_list_view
|
|||||||
|
|
||||||
|
|
||||||
class QueryParam(object):
|
class QueryParam(object):
|
||||||
def __init__(self, name, description=None, qtype='string'):
|
def __init__(self, name, description=None, qtype='string', required=False):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
self.qtype = qtype
|
self.qtype = qtype
|
||||||
|
self.required = required
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.name}, {self.qtype}, {self.description}'
|
return f'{self.name}, {self.qtype}, {self.description}'
|
||||||
@ -19,7 +20,7 @@ class QueryParamAutoSchema(AutoSchema):
|
|||||||
parameters = super().get_path_parameters(path, method)
|
parameters = super().get_path_parameters(path, method)
|
||||||
for q in self.view.query_params:
|
for q in self.view.query_params:
|
||||||
parameters.append({
|
parameters.append({
|
||||||
"name": q.name, "in": "query", "required": False,
|
"name": q.name, "in": "query", "required": q.required,
|
||||||
"description": q.description,
|
"description": q.description,
|
||||||
'schema': {'type': q.qtype, },
|
'schema': {'type': q.qtype, },
|
||||||
})
|
})
|
||||||
|
@ -36,12 +36,17 @@ class ExtendedRecipeMixin(serializers.ModelSerializer):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
api_serializer = None
|
api_serializer = None
|
||||||
# extended values are computationally expensive and not needed in normal circumstances
|
# extended values are computationally expensive and not needed in normal circumstances
|
||||||
if self.context.get('request', False) and bool(int(self.context['request'].query_params.get('extended', False))) and self.__class__ == api_serializer:
|
try:
|
||||||
return fields
|
if bool(int(self.context['request'].query_params.get('extended', False))) and self.__class__ == api_serializer:
|
||||||
else:
|
return fields
|
||||||
|
except (AttributeError, KeyError) as e:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
del fields['image']
|
del fields['image']
|
||||||
del fields['numrecipe']
|
del fields['numrecipe']
|
||||||
return fields
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return fields
|
||||||
|
|
||||||
def get_image(self, obj):
|
def get_image(self, obj):
|
||||||
# TODO add caching
|
# TODO add caching
|
||||||
@ -286,8 +291,9 @@ class KeywordSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Keyword
|
model = Keyword
|
||||||
fields = (
|
fields = (
|
||||||
'id', 'name', 'icon', 'label', 'description', 'image', 'parent', 'numchild', 'numrecipe')
|
'id', 'name', 'icon', 'label', 'description', 'image', 'parent', 'numchild', 'numrecipe', 'created_at',
|
||||||
read_only_fields = ('id', 'label', 'image', 'parent', 'numchild', 'numrecipe')
|
'updated_at')
|
||||||
|
read_only_fields = ('id', 'label', 'numchild', 'parent', 'image')
|
||||||
|
|
||||||
|
|
||||||
class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
|
class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
|
||||||
@ -368,15 +374,6 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
|
|||||||
def get_shopping_status(self, obj):
|
def get_shopping_status(self, obj):
|
||||||
return ShoppingListEntry.objects.filter(space=obj.space, food=obj, checked=False).count() > 0
|
return ShoppingListEntry.objects.filter(space=obj.space, food=obj, checked=False).count() > 0
|
||||||
|
|
||||||
def get_fields(self, *args, **kwargs):
|
|
||||||
fields = super().get_fields(*args, **kwargs)
|
|
||||||
print('food', self.__class__, self.parent.__class__)
|
|
||||||
# extended values are computationally expensive and not needed in normal circumstances
|
|
||||||
if not bool(int(self.context['request'].query_params.get('extended', False))) or not self.parent:
|
|
||||||
del fields['image']
|
|
||||||
del fields['numrecipe']
|
|
||||||
return fields
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
validated_data['name'] = validated_data['name'].strip()
|
validated_data['name'] = validated_data['name'].strip()
|
||||||
validated_data['space'] = self.context['request'].space
|
validated_data['space'] = self.context['request'].space
|
||||||
@ -633,6 +630,7 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
|||||||
read_only_fields = ('created_by',)
|
read_only_fields = ('created_by',)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO deprecate
|
||||||
class ShoppingListRecipeSerializer(serializers.ModelSerializer):
|
class ShoppingListRecipeSerializer(serializers.ModelSerializer):
|
||||||
name = serializers.SerializerMethodField('get_name') # should this be done at the front end?
|
name = serializers.SerializerMethodField('get_name') # should this be done at the front end?
|
||||||
recipe_name = serializers.ReadOnlyField(source='recipe.name')
|
recipe_name = serializers.ReadOnlyField(source='recipe.name')
|
||||||
@ -698,8 +696,8 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
|
|||||||
def run_validation(self, data):
|
def run_validation(self, data):
|
||||||
if (
|
if (
|
||||||
data.get('checked', False)
|
data.get('checked', False)
|
||||||
and not (id := data.get('id', None))
|
and self.root.instance
|
||||||
and not ShoppingListEntry.objects.get(id=id).checked
|
and not self.root.instance.checked
|
||||||
):
|
):
|
||||||
# if checked flips from false to true set completed datetime
|
# if checked flips from false to true set completed datetime
|
||||||
data['completed_at'] = timezone.now()
|
data['completed_at'] = timezone.now()
|
||||||
@ -711,20 +709,6 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
|
|||||||
if 'completed_at' in data:
|
if 'completed_at' in data:
|
||||||
del data['completed_at']
|
del data['completed_at']
|
||||||
|
|
||||||
############################################################
|
|
||||||
# temporary while old and new shopping lists are both in use
|
|
||||||
try:
|
|
||||||
# this serializer is the parent serializer for the API
|
|
||||||
api_serializer = self.context['view'].serializer_class
|
|
||||||
except Exception:
|
|
||||||
# this serializer is probably nested or a foreign key
|
|
||||||
api_serializer = None
|
|
||||||
if self.context['request'].method == 'POST' and not self.__class__ == api_serializer:
|
|
||||||
data['space'] = self.context['request'].space.id
|
|
||||||
data['created_by'] = self.context['request'].user.id
|
|
||||||
############################################################
|
|
||||||
if self.context['request'].method == 'POST' and self.__class__ == api_serializer:
|
|
||||||
data['created_by'] = {'id': self.context['request'].user.id}
|
|
||||||
return super().run_validation(data)
|
return super().run_validation(data)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
@ -119,8 +119,13 @@ def test_delete(u1_s1, u1_s2, obj_1):
|
|||||||
assert r.status_code == 204
|
assert r.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
# test sharing
|
# TODO test sharing
|
||||||
# test completed entries still visible if today, but not yesterday
|
# TODO test completed entries still visible if today, but not yesterday
|
||||||
# test create shopping list from recipe
|
# TODO test create shopping list from recipe
|
||||||
# test create shopping list from mealplan
|
# TODO test delete shopping list from recipe - include created by, shared with and not shared with
|
||||||
# test create shopping list from recipe, excluding ingredients
|
# TODO test create shopping list from food
|
||||||
|
# TODO test delete shopping list from food - include created by, shared with and not shared with
|
||||||
|
# TODO test create shopping list from mealplan
|
||||||
|
# TODO test create shopping list from recipe, excluding ingredients
|
||||||
|
# TODO test auto creating shopping list from meal plan
|
||||||
|
# TODO test excluding on-hand when auto creating shopping list
|
||||||
|
27502
vue/package-lock.json
generated
Normal file
27502
vue/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -84,4 +84,5 @@
|
|||||||
"@vue/cli-plugin-pwa/workbox-webpack-plugin": "^5.1.3",
|
"@vue/cli-plugin-pwa/workbox-webpack-plugin": "^5.1.3",
|
||||||
"coa": "2.0.2"
|
"coa": "2.0.2"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,158 +1,164 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app" class="col-12 col-xl-8 col-lg-10 offset-xl-2 offset-lg-1 offset">
|
<div id="app" class="col-12 col-xl-8 col-lg-10 offset-xl-2 offset-lg-1 offset">
|
||||||
<div class="col-12 col-xl-8 col-lg-10 offset-xl-2 offset-lg-1">
|
<div class="col-12 col-xl-8 col-lg-10 offset-xl-2 offset-lg-1">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-12 col-lg-10 mt-3 mb-3">
|
<div class="col-12 col-lg-10 col-xl-8 mt-3 mb-3">
|
||||||
<b-input-group>
|
<b-input-group>
|
||||||
<b-input class="form-control form-control-lg form-control-borderless form-control-search"
|
<b-input
|
||||||
v-model="search"
|
class="form-control form-control-lg form-control-borderless form-control-search"
|
||||||
v-bind:placeholder="$t('Search')"></b-input>
|
v-model="search"
|
||||||
<b-input-group-append>
|
v-bind:placeholder="$t('Search')"
|
||||||
<b-button variant="primary"
|
></b-input>
|
||||||
v-b-tooltip.hover :title="$t('Create')"
|
<b-input-group-append>
|
||||||
@click="createNew">
|
<b-button variant="primary" v-b-tooltip.hover :title="$t('Create')" @click="createNew">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
</b-button>
|
</b-button>
|
||||||
</b-input-group-append>
|
</b-input-group-append>
|
||||||
</b-input-group>
|
</b-input-group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3" v-for="book in filteredBooks" :key="book.id">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<b-card class="d-flex flex-column" v-hover
|
|
||||||
v-on:click="openBook(book.id)">
|
|
||||||
<b-row no-gutters style="height:inherit;">
|
|
||||||
<b-col no-gutters md="2" style="height:inherit;">
|
|
||||||
<h3>{{ book.icon }}</h3>
|
|
||||||
</b-col>
|
|
||||||
<b-col no-gutters md="10" style="height:inherit;">
|
|
||||||
<b-card-body class="m-0 py-0" style="height:inherit;">
|
|
||||||
<b-card-text class="h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
|
|
||||||
<h5 class="m-0 mt-1 text-truncate">{{ book.name }} <span class="float-right"><i
|
|
||||||
class="fa fa-book"></i></span></h5>
|
|
||||||
<div class="m-0 text-truncate">{{ book.description }}</div>
|
|
||||||
<div class="mt-auto mb-1 d-flex flex-row justify-content-end">
|
|
||||||
</div>
|
</div>
|
||||||
</b-card-text>
|
</div>
|
||||||
</b-card-body>
|
</div>
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="mb-3" v-for="book in filteredBooks" :key="book.id">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<b-card class="d-flex flex-column" v-hover v-on:click="openBook(book.id)">
|
||||||
|
<b-row no-gutters style="height:inherit;">
|
||||||
|
<b-col no-gutters md="2" style="height:inherit;">
|
||||||
|
<h3>{{ book.icon }}</h3>
|
||||||
|
</b-col>
|
||||||
|
<b-col no-gutters md="10" style="height:inherit;">
|
||||||
|
<b-card-body class="m-0 py-0" style="height:inherit;">
|
||||||
|
<b-card-text class="h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
|
||||||
|
<h5 class="m-0 mt-1 text-truncate">
|
||||||
|
{{ book.name }} <span class="float-right"><i class="fa fa-book"></i></span>
|
||||||
|
</h5>
|
||||||
|
<div class="m-0 text-truncate">{{ book.description }}</div>
|
||||||
|
<div class="mt-auto mb-1 d-flex flex-row justify-content-end"></div>
|
||||||
|
</b-card-text>
|
||||||
|
</b-card-body>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<loading-spinner v-if="current_book === book.id && loading"></loading-spinner>
|
<loading-spinner v-if="current_book === book.id && loading"></loading-spinner>
|
||||||
<transition name="slide-fade">
|
<transition name="slide-fade">
|
||||||
<cookbook-slider :recipes="recipes" :book="book" :key="`slider_${book.id}`"
|
<cookbook-slider
|
||||||
v-if="current_book === book.id && !loading" v-on:refresh="refreshData"></cookbook-slider>
|
:recipes="recipes"
|
||||||
</transition>
|
:book="book"
|
||||||
|
:key="`slider_${book.id}`"
|
||||||
|
v-if="current_book === book.id && !loading"
|
||||||
|
v-on:refresh="refreshData"
|
||||||
|
></cookbook-slider>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue'
|
import Vue from "vue"
|
||||||
import {BootstrapVue} from 'bootstrap-vue'
|
import { BootstrapVue } from "bootstrap-vue"
|
||||||
|
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||||
import {ApiApiFactory} from "@/utils/openapi/api.ts";
|
import { ApiApiFactory } from "@/utils/openapi/api"
|
||||||
import CookbookSlider from "../../components/CookbookSlider";
|
import CookbookSlider from "@/components/CookbookSlider"
|
||||||
import LoadingSpinner from "../../components/LoadingSpinner";
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
import {StandardToasts} from "../../utils/utils";
|
import { StandardToasts } from "@/utils/utils"
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CookbookView',
|
name: "CookbookView",
|
||||||
mixins: [],
|
mixins: [],
|
||||||
components: {LoadingSpinner, CookbookSlider},
|
components: { LoadingSpinner, CookbookSlider },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
cookbooks: [],
|
cookbooks: [],
|
||||||
book_background: window.IMAGE_BOOK,
|
book_background: window.IMAGE_BOOK,
|
||||||
recipes: [],
|
recipes: [],
|
||||||
current_book: undefined,
|
current_book: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
search: ''
|
search: "",
|
||||||
}
|
}
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
filteredBooks: function () {
|
|
||||||
return this.cookbooks.filter(book => {
|
|
||||||
return book.name.toLowerCase().includes(this.search.toLowerCase())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.refreshData()
|
|
||||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
refreshData: function () {
|
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
|
|
||||||
apiClient.listRecipeBooks().then(result => {
|
|
||||||
this.cookbooks = result.data
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
openBook: function (book) {
|
computed: {
|
||||||
if (book === this.current_book) {
|
filteredBooks: function() {
|
||||||
this.current_book = undefined
|
return this.cookbooks.filter((book) => {
|
||||||
this.recipes = []
|
return book.name.toLowerCase().includes(this.search.toLowerCase())
|
||||||
return
|
})
|
||||||
}
|
},
|
||||||
this.loading = true
|
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
|
|
||||||
this.current_book = book
|
|
||||||
apiClient.listRecipeBookEntrys({query: {book: book}}).then(result => {
|
|
||||||
this.recipes = result.data
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
createNew: function () {
|
mounted() {
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
|
|
||||||
apiClient.createRecipeBook({name: this.$t('New_Cookbook'), description: '', icon: '', shared: []}).then(result => {
|
|
||||||
let new_book = result.data
|
|
||||||
this.refreshData()
|
this.refreshData()
|
||||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||||
}).catch(error => {
|
},
|
||||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
methods: {
|
||||||
})
|
refreshData: function() {
|
||||||
}
|
let apiClient = new ApiApiFactory()
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
hover: {
|
|
||||||
inserted: function (el) {
|
|
||||||
el.addEventListener('mouseenter', () => {
|
|
||||||
el.classList.add("shadow")
|
|
||||||
});
|
|
||||||
el.addEventListener('mouseleave', () => {
|
|
||||||
el.classList.remove("shadow")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
apiClient.listRecipeBooks().then((result) => {
|
||||||
|
this.cookbooks = result.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
openBook: function(book) {
|
||||||
|
if (book === this.current_book) {
|
||||||
|
this.current_book = undefined
|
||||||
|
this.recipes = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
let apiClient = new ApiApiFactory()
|
||||||
|
|
||||||
|
this.current_book = book
|
||||||
|
apiClient.listRecipeBookEntrys({ query: { book: book } }).then((result) => {
|
||||||
|
this.recipes = result.data
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
createNew: function() {
|
||||||
|
let apiClient = new ApiApiFactory()
|
||||||
|
|
||||||
|
apiClient
|
||||||
|
.createRecipeBook({ name: "New Book", description: "", icon: "", shared: [] })
|
||||||
|
.then((result) => {
|
||||||
|
let new_book = result.data
|
||||||
|
this.refreshData()
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
hover: {
|
||||||
|
inserted: function(el) {
|
||||||
|
el.addEventListener("mouseenter", () => {
|
||||||
|
el.classList.add("shadow")
|
||||||
|
})
|
||||||
|
el.addEventListener("mouseleave", () => {
|
||||||
|
el.classList.remove("shadow")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.slide-fade-enter-active {
|
.slide-fade-enter-active {
|
||||||
transition: all .6s ease;
|
transition: all 0.6s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-fade-enter, .slide-fade-leave-to
|
.slide-fade-enter, .slide-fade-leave-to
|
||||||
/* .slide-fade-leave-active below version 2.1.8 */
|
/* .slide-fade-leave-active below version 2.1.8 */
|
||||||
{
|
{
|
||||||
transform: translateX(10px);
|
transform: translateX(10px);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -166,6 +166,7 @@
|
|||||||
>
|
>
|
||||||
<a class="dropdown-item p-2" href="#"><i class="fas fa-shopping-cart"></i> {{ $t("Add_to_Shopping") }}</a>
|
<a class="dropdown-item p-2" href="#"><i class="fas fa-shopping-cart"></i> {{ $t("Add_to_Shopping") }}</a>
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
<!-- TODO: Add new shopping Modal -->
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
@click="
|
@click="
|
||||||
$refs.menu.close()
|
$refs.menu.close()
|
||||||
@ -250,22 +251,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Vue from "vue"
|
||||||
import { BootstrapVue } from "bootstrap-vue"
|
import { BootstrapVue } from "bootstrap-vue"
|
||||||
import "bootstrap-vue/dist/bootstrap-vue.css"
|
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||||
|
|
||||||
import ContextMenu from "@/components/ContextMenu/ContextMenu"
|
import ContextMenu from "@/components/ContextMenu/ContextMenu"
|
||||||
import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem"
|
import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem"
|
||||||
import { CalendarView, CalendarMathMixin } from "vue-simple-calendar/src/components/bundle"
|
import MealPlanCard from "@/components/MealPlanCard"
|
||||||
import Vue from "vue"
|
import MealPlanEditModal from "@/components/MealPlanEditModal"
|
||||||
import { ApiApiFactory } from "@/utils/openapi/api"
|
|
||||||
import MealPlanCard from "../../components/MealPlanCard"
|
|
||||||
import moment from "moment"
|
|
||||||
import { ApiMixin, StandardToasts } from "@/utils/utils"
|
|
||||||
import MealPlanEditModal from "@/components/Modals/MealPlanEditModal"
|
|
||||||
import VueCookies from "vue-cookies"
|
|
||||||
import MealPlanCalenderHeader from "@/components/MealPlanCalenderHeader"
|
import MealPlanCalenderHeader from "@/components/MealPlanCalenderHeader"
|
||||||
import EmojiInput from "../../components/Modals/EmojiInput"
|
import EmojiInput from "@/components/Modals/EmojiInput"
|
||||||
|
|
||||||
|
import moment from "moment"
|
||||||
import draggable from "vuedraggable"
|
import draggable from "vuedraggable"
|
||||||
|
import VueCookies from "vue-cookies"
|
||||||
|
|
||||||
|
import { ApiMixin, StandardToasts } from "@/utils/utils"
|
||||||
|
import { CalendarView, CalendarMathMixin } from "vue-simple-calendar/src/components/bundle"
|
||||||
|
import { ApiApiFactory } from "@/utils/openapi/api"
|
||||||
|
|
||||||
const { makeToast } = require("@/utils/utils")
|
const { makeToast } = require("@/utils/utils")
|
||||||
|
|
||||||
|
@ -25,8 +25,34 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align: center">
|
<div style="text-align: center">
|
||||||
<keywords :recipe="recipe"></keywords>
|
<keywords-component :recipe="recipe"></keywords-component>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-3">
|
||||||
|
<div class="row d-flex" style="padding-left: 16px">
|
||||||
|
<div class="my-auto" style="padding-right: 4px">
|
||||||
|
<i class="fas fa-user-clock fa-2x text-primary"></i>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="my-auto" style="padding-right: 4px">
|
||||||
|
<span class="text-primary"><b>{{ $t('Preparation') }}</b></span><br/>
|
||||||
|
{{ recipe.working_time }} {{ $t('min') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
<recipe-rating :recipe="recipe"></recipe-rating>
|
||||||
|
<last-cooked :recipe="recipe" class="mt-2"></last-cooked>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-auto">
|
||||||
|
<div class="col-12" style="text-align: center">
|
||||||
|
<i>{{ recipe.description }}</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -92,38 +118,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" style="margin-top: 2vh; margin-bottom: 2vh">
|
|
||||||
<div class="col-12">
|
|
||||||
<Nutrition :recipe="recipe" :ingredient_factor="ingredient_factor"></Nutrition>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-if="!recipe.internal">
|
|
||||||
<div v-if="recipe.file_path.includes('.pdf')">
|
|
||||||
<PdfViewer :recipe="recipe"></PdfViewer>
|
|
||||||
</div>
|
|
||||||
<div v-if="recipe.file_path.includes('.png') || recipe.file_path.includes('.jpg') || recipe.file_path.includes('.jpeg') || recipe.file_path.includes('.gif')">
|
|
||||||
<ImageViewer :recipe="recipe"></ImageViewer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
|
<div class="row" style="margin-top: 2vh; margin-bottom: 2vh">
|
||||||
<Step
|
<div class="col-12">
|
||||||
:recipe="recipe"
|
<Nutrition-component :recipe="recipe" :ingredient_factor="ingredient_factor"></Nutrition-component>
|
||||||
:step="s"
|
|
||||||
:ingredient_factor="ingredient_factor"
|
|
||||||
:index="index"
|
|
||||||
:start_time="start_time"
|
|
||||||
@update-start-time="updateStartTime"
|
|
||||||
@checked-state-changed="updateIngredientCheckedState"
|
|
||||||
></Step>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
|
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
|
||||||
|
|
||||||
|
<div class="row text-center d-print-none" style="margin-top: 3vh; margin-bottom: 3vh" v-if="share_uid !== 'None'">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
<a :href="resolveDjangoUrl('view_report_share_abuse', share_uid)">{{ $t("Report Abuse") }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<div v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
|
||||||
|
<step-component :recipe="recipe" :step="s" :ingredient_factor="ingredient_factor" :index="index" :start_time="start_time"
|
||||||
|
@update-start-time="updateStartTime" @checked-state-changed="updateIngredientCheckedState"></step-component>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
|
||||||
|
|
||||||
<div class="row text-center d-print-none" style="margin-top: 3vh; margin-bottom: 3vh" v-if="share_uid !== 'None'">
|
<div class="row text-center d-print-none" style="margin-top: 3vh; margin-bottom: 3vh" v-if="share_uid !== 'None'">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
<a :href="resolveDjangoUrl('view_report_share_abuse', share_uid)">{{ $t("Report Abuse") }}</a>
|
<a :href="resolveDjangoUrl('view_report_share_abuse', share_uid)">{{ $t("Report Abuse") }}</a>
|
||||||
@ -139,17 +159,17 @@ import "bootstrap-vue/dist/bootstrap-vue.css"
|
|||||||
|
|
||||||
import { apiLoadRecipe } from "@/utils/api"
|
import { apiLoadRecipe } from "@/utils/api"
|
||||||
|
|
||||||
import Step from "@/components/Step"
|
import StepComponent from "@/components/StepComponent";
|
||||||
import RecipeContextMenu from "@/components/ContextMenu/RecipeContextMenu"
|
import RecipeContextMenu from "@/components/ContextMenu/RecipeContextMenu"
|
||||||
import { ResolveUrlMixin, ToastMixin } from "@/utils/utils"
|
import { ResolveUrlMixin, ToastMixin } from "@/utils/utils"
|
||||||
|
|
||||||
import PdfViewer from "@/components/PdfViewer"
|
import PdfViewer from "@/components/PdfViewer"
|
||||||
import ImageViewer from "@/components/ImageViewer"
|
import ImageViewer from "@/components/ImageViewer"
|
||||||
import Nutrition from "@/components/Nutrition"
|
|
||||||
import IngredientsCard from "@/components/IngredientsCard"
|
import IngredientsCard from "@/components/IngredientsCard"
|
||||||
|
|
||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import Keywords from "@/components/Keywords"
|
import KeywordsComponent from "@/components/KeywordsComponent";
|
||||||
|
import NutritionComponent from "@/components/NutritionComponent";
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
import AddRecipeToBook from "@/components/Modals/AddRecipeToBook"
|
import AddRecipeToBook from "@/components/Modals/AddRecipeToBook"
|
||||||
import RecipeRating from "@/components/RecipeRating"
|
import RecipeRating from "@/components/RecipeRating"
|
||||||
@ -168,10 +188,10 @@ export default {
|
|||||||
PdfViewer,
|
PdfViewer,
|
||||||
ImageViewer,
|
ImageViewer,
|
||||||
IngredientsCard,
|
IngredientsCard,
|
||||||
Step,
|
StepComponent,
|
||||||
RecipeContextMenu,
|
RecipeContextMenu,
|
||||||
Nutrition,
|
NutritionComponent,
|
||||||
Keywords,
|
KeywordsComponent,
|
||||||
LoadingSpinner,
|
LoadingSpinner,
|
||||||
AddRecipeToBook,
|
AddRecipeToBook,
|
||||||
},
|
},
|
||||||
|
@ -1,217 +0,0 @@
|
|||||||
<template>
|
|
||||||
<tr>
|
|
||||||
<template v-if="ingredient.is_header">
|
|
||||||
<td colspan="5" @click="done">
|
|
||||||
<b>{{ ingredient.note }}</b>
|
|
||||||
</td>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<td class="d-print-non" v-if="detailed && !add_shopping_mode" @click="done">
|
|
||||||
<i class="far fa-check-circle text-success" v-if="ingredient.checked"></i>
|
|
||||||
<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" v-html="calculateAmount(ingredient.amount)"></span>
|
|
||||||
</td>
|
|
||||||
<td @click="done">
|
|
||||||
<span v-if="ingredient.unit !== null && !ingredient.no_amount">{{ ingredient.unit.name }}</span>
|
|
||||||
</td>
|
|
||||||
<td @click="done">
|
|
||||||
<template v-if="ingredient.food !== null">
|
|
||||||
<!-- <i
|
|
||||||
v-if="show_shopping && !add_shopping_mode"
|
|
||||||
class="far fa-edit fa-sm px-1"
|
|
||||||
@click="editFood()"
|
|
||||||
></i> -->
|
|
||||||
<a :href="resolveDjangoUrl('view_recipe', ingredient.food.recipe.id)" v-if="ingredient.food.recipe !== null" target="_blank" rel="noopener noreferrer">{{
|
|
||||||
ingredient.food.name
|
|
||||||
}}</a>
|
|
||||||
<span v-if="ingredient.food.recipe === null">{{ ingredient.food.name }}</span>
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
<td v-if="detailed && !show_shopping">
|
|
||||||
<div v-if="ingredient.note">
|
|
||||||
<span v-b-popover.hover="ingredient.note" class="d-print-none touchable">
|
|
||||||
<i class="far fa-comment"></i>
|
|
||||||
</span>
|
|
||||||
<!-- v-if="ingredient.note.length > 15" -->
|
|
||||||
<!-- <span v-else>-->
|
|
||||||
<!-- {{ ingredient.note }}-->
|
|
||||||
<!-- </span>-->
|
|
||||||
|
|
||||||
<div class="d-none d-print-block"><i class="far fa-comment-alt d-print-none"></i> {{ ingredient.note }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td v-else-if="show_shopping" class="text-right text-nowrap">
|
|
||||||
<!-- in shopping mode and ingredient is not ignored -->
|
|
||||||
<div v-if="!ingredient.food.ignore_shopping">
|
|
||||||
<b-button
|
|
||||||
class="btn text-decoration-none fas fa-shopping-cart px-2 user-select-none"
|
|
||||||
variant="link"
|
|
||||||
v-b-popover.hover.click.blur.html.top="{ title: ShoppingPopover, variant: 'outline-dark' }"
|
|
||||||
:class="{
|
|
||||||
'text-success': shopping_status === true,
|
|
||||||
'text-muted': shopping_status === false,
|
|
||||||
'text-warning': shopping_status === null,
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<span class="px-2">
|
|
||||||
<input type="checkbox" class="align-middle" v-model="shop" @change="changeShopping" />
|
|
||||||
</span>
|
|
||||||
<on-hand-badge :item="ingredient.food" />
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<!-- or in shopping mode and food is ignored: Shopping Badge bypasses linking ingredient to Recipe which would get ignored -->
|
|
||||||
<shopping-badge :item="ingredient.food" :override_ignore="true" class="px-1" />
|
|
||||||
<span class="px-2">
|
|
||||||
<input type="checkbox" class="align-middle" disabled v-b-popover.hover.click.blur :title="$t('IgnoredFood', { food: ingredient.food.name })" />
|
|
||||||
</span>
|
|
||||||
<on-hand-badge :item="ingredient.food" />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</template>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { calculateAmount, ResolveUrlMixin, ApiMixin } from "@/utils/utils"
|
|
||||||
import OnHandBadge from "@/components/Badges/OnHand"
|
|
||||||
import ShoppingBadge from "@/components/Badges/Shopping"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "Ingredient",
|
|
||||||
components: { OnHandBadge, ShoppingBadge },
|
|
||||||
props: {
|
|
||||||
ingredient: Object,
|
|
||||||
ingredient_factor: { type: Number, default: 1 },
|
|
||||||
detailed: { type: Boolean, default: true },
|
|
||||||
recipe_list: { type: Number }, // ShoppingListRecipe ID, to filter ShoppingStatus
|
|
||||||
show_shopping: { type: Boolean, default: false },
|
|
||||||
add_shopping_mode: { type: Boolean, default: false },
|
|
||||||
shopping_list: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
}, // list of unchecked ingredients in shopping list
|
|
||||||
},
|
|
||||||
mixins: [ResolveUrlMixin, ApiMixin],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
checked: false,
|
|
||||||
shopping_status: null,
|
|
||||||
shopping_items: [],
|
|
||||||
shop: false,
|
|
||||||
dirty: undefined,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
ShoppingListAndFilter: {
|
|
||||||
immediate: true,
|
|
||||||
handler(newVal, oldVal) {
|
|
||||||
let filtered_list = this.shopping_list
|
|
||||||
// if a recipe list is provided, filter the shopping list
|
|
||||||
if (this.recipe_list) {
|
|
||||||
filtered_list = filtered_list.filter((x) => x.list_recipe == this.recipe_list)
|
|
||||||
}
|
|
||||||
// how many ShoppingListRecipes are there for this recipe?
|
|
||||||
let count_shopping_recipes = [...new Set(filtered_list.map((x) => x.list_recipe))].length
|
|
||||||
let count_shopping_ingredient = filtered_list.filter((x) => x.ingredient == this.ingredient.id).length
|
|
||||||
|
|
||||||
if (count_shopping_recipes > 1) {
|
|
||||||
this.shop = false // don't check any boxes until user selects a shopping list to edit
|
|
||||||
if (count_shopping_ingredient >= 1) {
|
|
||||||
this.shopping_status = true
|
|
||||||
} else if (this.ingredient.food.shopping) {
|
|
||||||
this.shopping_status = null // food is in the shopping list, just not for this ingredient/recipe
|
|
||||||
} else {
|
|
||||||
this.shopping_status = false // food is not in any shopping list
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// mark checked if the food is in the shopping list for this ingredient/recipe
|
|
||||||
if (count_shopping_ingredient >= 1) {
|
|
||||||
// ingredient is in this shopping list
|
|
||||||
this.shop = true
|
|
||||||
this.shopping_status = true
|
|
||||||
} else if (count_shopping_ingredient == 0 && this.ingredient.food.shopping) {
|
|
||||||
// food is in the shopping list, just not for this ingredient/recipe
|
|
||||||
this.shop = false
|
|
||||||
this.shopping_status = null
|
|
||||||
} else {
|
|
||||||
// the food is not in any shopping list
|
|
||||||
this.shop = false
|
|
||||||
this.shopping_status = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if we are in add shopping mode start with all checks marked
|
|
||||||
if (this.add_shopping_mode) {
|
|
||||||
this.shop = !this.ingredient.food.on_hand && !this.ingredient.food.ignore_shopping && !this.ingredient.food.recipe
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {},
|
|
||||||
computed: {
|
|
||||||
ShoppingListAndFilter() {
|
|
||||||
// hack to watch the shopping list and the recipe list at the same time
|
|
||||||
return this.shopping_list.map((x) => x.id).join(this.recipe_list)
|
|
||||||
},
|
|
||||||
ShoppingPopover() {
|
|
||||||
if (this.shopping_status == false) {
|
|
||||||
return this.$t("NotInShopping", { food: this.ingredient.food.name })
|
|
||||||
} else {
|
|
||||||
let list = this.shopping_list.filter((x) => x.food.id == this.ingredient.food.id)
|
|
||||||
let category = this.$t("Category") + ": " + this.ingredient?.food?.supermarket_category?.name ?? this.$t("Undefined")
|
|
||||||
let popover = []
|
|
||||||
|
|
||||||
list.forEach((x) => {
|
|
||||||
popover.push(
|
|
||||||
[
|
|
||||||
"<tr style='border-bottom: 1px solid #ccc'>",
|
|
||||||
"<td style='padding: 3px;'><em>",
|
|
||||||
x?.recipe_mealplan?.name ?? "",
|
|
||||||
"</em></td>",
|
|
||||||
"<td style='padding: 3px;'>",
|
|
||||||
x?.amount ?? "",
|
|
||||||
"</td>",
|
|
||||||
"<td style='padding: 3px;'>",
|
|
||||||
x?.unit?.name ?? "" + "</td>",
|
|
||||||
"<td style='padding: 3px;'>",
|
|
||||||
x?.food?.name ?? "",
|
|
||||||
"</td></tr>",
|
|
||||||
].join("")
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return "<table class='table-small'><th colspan='4'>" + category + "</th>" + popover.join("") + "</table>"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
calculateAmount: function(x) {
|
|
||||||
return calculateAmount(x, this.ingredient_factor)
|
|
||||||
},
|
|
||||||
// sends parent recipe ingredient to notify complete has been toggled
|
|
||||||
done: function() {
|
|
||||||
this.$emit("checked-state-changed", this.ingredient)
|
|
||||||
},
|
|
||||||
// sends true/false to parent to save all ingredient shopping updates as a batch
|
|
||||||
changeShopping: function() {
|
|
||||||
this.$emit("add-to-shopping", { item: this.ingredient, add: this.shop })
|
|
||||||
},
|
|
||||||
editFood: function() {
|
|
||||||
console.log("edit the food")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* increase size of hover/touchable space without changing spacing */
|
|
||||||
.touchable {
|
|
||||||
padding-right: 2em;
|
|
||||||
padding-left: 2em;
|
|
||||||
margin-right: -2em;
|
|
||||||
margin-left: -2em;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -19,11 +19,6 @@
|
|||||||
</td>
|
</td>
|
||||||
<td @click="done">
|
<td @click="done">
|
||||||
<template v-if="ingredient.food !== null">
|
<template v-if="ingredient.food !== null">
|
||||||
<!-- <i
|
|
||||||
v-if="show_shopping && !add_shopping_mode"
|
|
||||||
class="far fa-edit fa-sm px-1"
|
|
||||||
@click="editFood()"
|
|
||||||
></i> -->
|
|
||||||
<a :href="resolveDjangoUrl('view_recipe', ingredient.food.recipe.id)" v-if="ingredient.food.recipe !== null" target="_blank" rel="noopener noreferrer">{{
|
<a :href="resolveDjangoUrl('view_recipe', ingredient.food.recipe.id)" v-if="ingredient.food.recipe !== null" target="_blank" rel="noopener noreferrer">{{
|
||||||
ingredient.food.name
|
ingredient.food.name
|
||||||
}}</a>
|
}}</a>
|
||||||
@ -35,10 +30,6 @@
|
|||||||
<span v-b-popover.hover="ingredient.note" class="d-print-none touchable">
|
<span v-b-popover.hover="ingredient.note" class="d-print-none touchable">
|
||||||
<i class="far fa-comment"></i>
|
<i class="far fa-comment"></i>
|
||||||
</span>
|
</span>
|
||||||
<!-- v-if="ingredient.note.length > 15" -->
|
|
||||||
<!-- <span v-else>-->
|
|
||||||
<!-- {{ ingredient.note }}-->
|
|
||||||
<!-- </span>-->
|
|
||||||
|
|
||||||
<div class="d-none d-print-block"><i class="far fa-comment-alt d-print-none"></i> {{ ingredient.note }}</div>
|
<div class="d-none d-print-block"><i class="far fa-comment-alt d-print-none"></i> {{ ingredient.note }}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -80,7 +71,7 @@ import OnHandBadge from "@/components/Badges/OnHand"
|
|||||||
import ShoppingBadge from "@/components/Badges/Shopping"
|
import ShoppingBadge from "@/components/Badges/Shopping"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Ingredient",
|
name: "IngredientComponent",
|
||||||
components: { OnHandBadge, ShoppingBadge },
|
components: { OnHandBadge, ShoppingBadge },
|
||||||
props: {
|
props: {
|
||||||
ingredient: Object,
|
ingredient: Object,
|
||||||
|
@ -12,6 +12,17 @@
|
|||||||
<b-badge pill variant="secondary" class="mt-1 font-weight-normal"><i class="fa fa-pause"></i> {{ recipe.waiting_time }} {{ $t("min") }} </b-badge>
|
<b-badge pill variant="secondary" class="mt-1 font-weight-normal"><i class="fa fa-pause"></i> {{ recipe.waiting_time }} {{ $t("min") }} </b-badge>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</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>
|
||||||
|
{{ recipe.working_time }} {{ $t('min') }}
|
||||||
|
</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>
|
||||||
|
{{ recipe.waiting_time }} {{ $t('min') }}
|
||||||
|
</b-badge>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
<b-card-body class="p-4">
|
<b-card-body class="p-4">
|
||||||
<h6>
|
<h6>
|
||||||
@ -34,7 +45,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<p class="mt-1">
|
<p class="mt-1">
|
||||||
<last-cooked :recipe="recipe"></last-cooked>
|
<last-cooked :recipe="recipe"></last-cooked>
|
||||||
<keywords :recipe="recipe" style="margin-top: 4px"></keywords>
|
<keywords-component :recipe="recipe" style="margin-top: 4px"></keywords-component>
|
||||||
</p>
|
</p>
|
||||||
<transition name="fade" mode="in-out">
|
<transition name="fade" mode="in-out">
|
||||||
<div class="row mt-3" v-if="detailed">
|
<div class="row mt-3" v-if="detailed">
|
||||||
@ -47,10 +58,6 @@
|
|||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<b-badge pill variant="info" v-if="!recipe.internal">{{ $t("External") }}</b-badge>
|
<b-badge pill variant="info" v-if="!recipe.internal">{{ $t("External") }}</b-badge>
|
||||||
<!-- <b-badge pill variant="success"
|
|
||||||
v-if="Date.parse(recipe.created_at) > new Date(Date.now() - (7 * (1000 * 60 * 60 * 24)))">
|
|
||||||
{{ $t('New') }}
|
|
||||||
</b-badge> -->
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>{{ meal_plan.note }}</template>
|
<template v-else>{{ meal_plan.note }}</template>
|
||||||
</b-card-text>
|
</b-card-text>
|
||||||
@ -62,7 +69,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RecipeContextMenu from "@/components/ContextMenu/RecipeContextMenu"
|
import RecipeContextMenu from "@/components/ContextMenu/RecipeContextMenu"
|
||||||
import Keywords from "@/components/Keywords"
|
import KeywordsComponent from "@/components/KeywordsComponent";
|
||||||
import { resolveDjangoUrl, ResolveUrlMixin } from "@/utils/utils"
|
import { resolveDjangoUrl, ResolveUrlMixin } from "@/utils/utils"
|
||||||
import RecipeRating from "@/components/RecipeRating"
|
import RecipeRating from "@/components/RecipeRating"
|
||||||
import moment from "moment/moment"
|
import moment from "moment/moment"
|
||||||
@ -75,7 +82,7 @@ Vue.prototype.moment = moment
|
|||||||
export default {
|
export default {
|
||||||
name: "RecipeCard",
|
name: "RecipeCard",
|
||||||
mixins: [ResolveUrlMixin],
|
mixins: [ResolveUrlMixin],
|
||||||
components: { LastCooked, RecipeRating, Keywords, RecipeContextMenu, IngredientsCard },
|
components: { LastCooked, RecipeRating, KeywordsComponent, RecipeContextMenu, IngredientsCard },
|
||||||
props: {
|
props: {
|
||||||
recipe: Object,
|
recipe: Object,
|
||||||
meal_plan: Object,
|
meal_plan: Object,
|
||||||
|
@ -1,217 +1,200 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
|
<hr />
|
||||||
|
|
||||||
<div>
|
<template v-if="step.type === 'TEXT' || step.type === 'RECIPE'">
|
||||||
<hr />
|
<div class="row" v-if="recipe.steps.length > 1">
|
||||||
|
<div class="col col-md-8">
|
||||||
<template v-if="step.type === 'TEXT' || step.type === 'RECIPE'">
|
<h5 class="text-primary">
|
||||||
<div class="row" v-if="recipe.steps.length > 1">
|
<template v-if="step.name">{{ step.name }}</template>
|
||||||
<div class="col col-md-8">
|
<template v-else>{{ $t("Step") }} {{ index + 1 }}</template>
|
||||||
<h5 class="text-primary">
|
<small style="margin-left: 4px" class="text-muted" v-if="step.time !== 0"><i class="fas fa-user-clock"></i> {{ step.time }} {{ $t("min") }} </small>
|
||||||
<template v-if="step.name">{{ step.name }}</template>
|
<small v-if="start_time !== ''" class="d-print-none">
|
||||||
<template v-else>{{ $t('Step') }} {{ index + 1 }}</template>
|
<b-link :id="`id_reactive_popover_${step.id}`" @click="openPopover" href="#">
|
||||||
<small style="margin-left: 4px" class="text-muted" v-if="step.time !== 0"><i class="fas fa-user-clock"></i>
|
{{
|
||||||
{{ step.time }} {{ $t('min') }}
|
moment(start_time)
|
||||||
|
.add(step.time_offset, "minutes")
|
||||||
</small>
|
.format("HH:mm")
|
||||||
<small v-if="start_time !== ''" class="d-print-none">
|
}}
|
||||||
<b-link :id="`id_reactive_popover_${step.id}`" @click="openPopover" href="#">
|
</b-link>
|
||||||
{{ moment(start_time).add(step.time_offset, 'minutes').format('HH:mm') }}
|
</small>
|
||||||
</b-link>
|
</h5>
|
||||||
</small>
|
</div>
|
||||||
</h5>
|
<div class="col col-md-4" style="text-align: right">
|
||||||
</div>
|
<b-button
|
||||||
<div class="col col-md-4" style="text-align: right">
|
@click="details_visible = !details_visible"
|
||||||
<b-button @click="details_visible = !details_visible" style="border: none; background: none"
|
style="border: none; background: none"
|
||||||
class="shadow-none d-print-none"
|
class="shadow-none d-print-none"
|
||||||
:class="{ 'text-primary': details_visible, 'text-success': !details_visible}">
|
:class="{ 'text-primary': details_visible, 'text-success': !details_visible }"
|
||||||
<i class="far fa-check-circle"></i>
|
>
|
||||||
</b-button>
|
<i class="far fa-check-circle"></i>
|
||||||
</div>
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="step.type === 'TEXT'">
|
|
||||||
|
|
||||||
<b-collapse id="collapse-1" v-model="details_visible">
|
|
||||||
<div class="row">
|
|
||||||
<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"
|
|
||||||
@checked-state-changed="$emit('checked-state-changed', $event)"
|
|
||||||
/>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="col" :class="{ 'col-md-8': recipe.steps.length > 1, 'col-md-12': recipe.steps.length <= 1,}">
|
|
||||||
<compile-component :code="step.ingredients_markdown"
|
|
||||||
:ingredient_factor="ingredient_factor"></compile-component>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</b-collapse>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="step.type === 'TIME' || step.type === 'FILE'">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-8 offset-md-2" style="text-align: center">
|
|
||||||
<h4 class="text-primary">
|
|
||||||
<template v-if="step.name">{{ step.name }}</template>
|
|
||||||
<template v-else>{{ $t('Step') }} {{ index + 1 }}</template>
|
|
||||||
</h4>
|
|
||||||
<span style="margin-left: 4px" class="text-muted" v-if="step.time !== 0"><i class="fa fa-stopwatch"></i>
|
|
||||||
{{ step.time }} {{ $t('min') }}</span>
|
|
||||||
<b-link class="d-print-none" :id="`id_reactive_popover_${step.id}`" @click="openPopover" href="#"
|
|
||||||
v-if="start_time !== ''">
|
|
||||||
{{ moment(start_time).add(step.time_offset, 'minutes').format('HH:mm') }}
|
|
||||||
</b-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-2" style="text-align: right">
|
|
||||||
<b-button @click="details_visible = !details_visible" style="border: none; background: none"
|
|
||||||
class="shadow-none d-print-none"
|
|
||||||
:class="{ 'text-primary': details_visible, 'text-success': !details_visible}">
|
|
||||||
<i class="far fa-check-circle"></i>
|
|
||||||
</b-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<b-collapse id="collapse-1" v-model="details_visible">
|
|
||||||
<div class="row" v-if="step.instruction !== ''">
|
|
||||||
<div class="col col-md-12" style="text-align: center">
|
|
||||||
<compile-component :code="step.ingredients_markdown"
|
|
||||||
:ingredient_factor="ingredient_factor"></compile-component>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</b-collapse>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="row" style="text-align: center">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
<template v-if="step.file !== null">
|
|
||||||
<div
|
|
||||||
v-if="step.file.file.includes('.png') || recipe.file_path.includes('.jpg') || recipe.file_path.includes('.jpeg') || recipe.file_path.includes('.gif')">
|
|
||||||
<img :src="step.file.file" style="max-width: 50vw; max-height: 50vh">
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<a :href="step.file.file" target="_blank" rel="noreferrer nofollow">{{ $t('Download') }} {{
|
|
||||||
$t('File')
|
|
||||||
}}</a>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<template v-if="step.type === 'TEXT'">
|
||||||
|
<b-collapse id="collapse-1" v-model="details_visible">
|
||||||
|
<div class="row">
|
||||||
|
<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" @checked-state-changed="$emit('checked-state-changed', $event)" />
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col" :class="{ 'col-md-8': recipe.steps.length > 1, 'col-md-12': recipe.steps.length <= 1 }">
|
||||||
|
<compile-component :code="step.ingredients_markdown" :ingredient_factor="ingredient_factor"></compile-component>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div class="card" v-if="step.type === 'RECIPE' && step.step_recipe_data !== null">
|
<template v-if="step.type === 'TIME' || step.type === 'FILE'">
|
||||||
<b-collapse id="collapse-1" v-model="details_visible">
|
<div class="row">
|
||||||
<div class="card-body">
|
<div class="col-md-8 offset-md-2" style="text-align: center">
|
||||||
<h2 class="card-title">
|
<h4 class="text-primary">
|
||||||
<a :href="resolveDjangoUrl('view_recipe',step.step_recipe_data.id)">{{ step.step_recipe_data.name }}</a>
|
<template v-if="step.name">{{ step.name }}</template>
|
||||||
</h2>
|
<template v-else>{{ $t("Step") }} {{ index + 1 }}</template>
|
||||||
<div v-for="(sub_step, index) in step.step_recipe_data.steps" v-bind:key="`substep_${sub_step.id}`">
|
</h4>
|
||||||
<Step :recipe="step.step_recipe_data" :step="sub_step" :ingredient_factor="ingredient_factor" :index="index"
|
<span style="margin-left: 4px" class="text-muted" v-if="step.time !== 0"><i class="fa fa-stopwatch"></i> {{ step.time }} {{ $t("min") }}</span>
|
||||||
:start_time="start_time" :force_ingredients="true"></Step>
|
<b-link class="d-print-none" :id="`id_reactive_popover_${step.id}`" @click="openPopover" href="#" v-if="start_time !== ''">
|
||||||
|
{{
|
||||||
|
moment(start_time)
|
||||||
|
.add(step.time_offset, "minutes")
|
||||||
|
.format("HH:mm")
|
||||||
|
}}
|
||||||
|
</b-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-2" style="text-align: right">
|
||||||
|
<b-button
|
||||||
|
@click="details_visible = !details_visible"
|
||||||
|
style="border: none; background: none"
|
||||||
|
class="shadow-none d-print-none"
|
||||||
|
:class="{ 'text-primary': details_visible, 'text-success': !details_visible }"
|
||||||
|
>
|
||||||
|
<i class="far fa-check-circle"></i>
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<b-collapse id="collapse-1" v-model="details_visible">
|
||||||
|
<div class="row" v-if="step.instruction !== ''">
|
||||||
|
<div class="col col-md-12" style="text-align: center">
|
||||||
|
<compile-component :code="step.ingredients_markdown" :ingredient_factor="ingredient_factor"></compile-component>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="row" style="text-align: center">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
<template v-if="step.file !== null">
|
||||||
|
<div v-if="step.file.file.includes('.png') || recipe.file_path.includes('.jpg') || recipe.file_path.includes('.jpeg') || recipe.file_path.includes('.gif')">
|
||||||
|
<img :src="step.file.file" style="max-width: 50vw; max-height: 50vh" />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<a :href="step.file.file" target="_blank" rel="noreferrer nofollow">{{ $t("Download") }} {{ $t("File") }}</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</b-collapse>
|
|
||||||
|
|
||||||
</div>
|
<div class="card" v-if="step.type === 'RECIPE' && step.step_recipe_data !== null">
|
||||||
|
<b-collapse id="collapse-1" v-model="details_visible">
|
||||||
|
<div class="card-body">
|
||||||
<div v-if="start_time !== ''">
|
<h2 class="card-title">
|
||||||
<b-popover
|
<a :href="resolveDjangoUrl('view_recipe', step.step_recipe_data.id)">{{ step.step_recipe_data.name }}</a>
|
||||||
:target="`id_reactive_popover_${step.id}`"
|
</h2>
|
||||||
triggers="click"
|
<div v-for="(sub_step, index) in step.step_recipe_data.steps" v-bind:key="`substep_${sub_step.id}`">
|
||||||
placement="bottom"
|
<Step
|
||||||
:ref="`id_reactive_popover_${step.id}`"
|
:recipe="step.step_recipe_data"
|
||||||
:title="$t('Step start time')">
|
:step="sub_step"
|
||||||
<div>
|
:ingredient_factor="ingredient_factor"
|
||||||
<b-form-group
|
:index="index"
|
||||||
label="Time"
|
:start_time="start_time"
|
||||||
label-for="popover-input-1"
|
:force_ingredients="true"
|
||||||
label-cols="3"
|
></Step>
|
||||||
class="mb-1">
|
</div>
|
||||||
<b-form-input
|
</div>
|
||||||
type="datetime-local"
|
</b-collapse>
|
||||||
id="popover-input-1"
|
|
||||||
v-model.datetime-local="set_time_input"
|
|
||||||
size="sm"
|
|
||||||
></b-form-input>
|
|
||||||
</b-form-group>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row" style="margin-top: 1vh">
|
|
||||||
<div class="col-12" style="text-align: right">
|
|
||||||
<b-button @click="closePopover" size="sm" variant="secondary" style="margin-right:8px">Cancel</b-button>
|
|
||||||
<b-button @click="updateTime" size="sm" variant="primary">Ok</b-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</b-popover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div v-if="start_time !== ''">
|
||||||
|
<b-popover :target="`id_reactive_popover_${step.id}`" triggers="click" placement="bottom" :ref="`id_reactive_popover_${step.id}`" :title="$t('Step start time')">
|
||||||
|
<div>
|
||||||
|
<b-form-group label="Time" label-for="popover-input-1" label-cols="3" class="mb-1">
|
||||||
|
<b-form-input type="datetime-local" id="popover-input-1" v-model.datetime-local="set_time_input" size="sm"></b-form-input>
|
||||||
|
</b-form-group>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="margin-top: 1vh">
|
||||||
|
<div class="col-12" style="text-align: right">
|
||||||
|
<b-button @click="closePopover" size="sm" variant="secondary" style="margin-right:8px">Cancel</b-button>
|
||||||
|
<b-button @click="updateTime" size="sm" variant="primary">Ok</b-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { calculateAmount } from "@/utils/utils"
|
||||||
|
|
||||||
import {calculateAmount} from "@/utils/utils";
|
import { GettextMixin } from "@/utils/utils"
|
||||||
|
|
||||||
import {GettextMixin} from "@/utils/utils";
|
import CompileComponent from "@/components/CompileComponent"
|
||||||
|
import IngredientsCard from "@/components/IngredientsCard"
|
||||||
import CompileComponent from "@/components/CompileComponent";
|
import Vue from "vue"
|
||||||
import IngredientsCard from "@/components/IngredientsCard";
|
import moment from "moment"
|
||||||
import Vue from "vue";
|
import { ResolveUrlMixin } from "@/utils/utils"
|
||||||
import moment from "moment";
|
|
||||||
import {ResolveUrlMixin} from "@/utils/utils";
|
|
||||||
import IngredientComponent from "@/components/IngredientComponent";
|
|
||||||
|
|
||||||
Vue.prototype.moment = moment
|
Vue.prototype.moment = moment
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'StepComponent',
|
name: "StepComponent",
|
||||||
mixins: [
|
mixins: [GettextMixin, ResolveUrlMixin],
|
||||||
GettextMixin,
|
components: { CompileComponent, IngredientsCard },
|
||||||
ResolveUrlMixin,
|
props: {
|
||||||
],
|
step: Object,
|
||||||
components: { CompileComponent, IngredientsCard},
|
ingredient_factor: Number,
|
||||||
props: {
|
index: Number,
|
||||||
step: Object,
|
recipe: Object,
|
||||||
ingredient_factor: Number,
|
start_time: String,
|
||||||
index: Number,
|
force_ingredients: {
|
||||||
recipe: Object,
|
type: Boolean,
|
||||||
start_time: String,
|
default: false,
|
||||||
force_ingredients: {
|
},
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
details_visible: true,
|
|
||||||
set_time_input: '',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.set_time_input = moment(this.start_time).add(this.step.time_offset, 'minutes').format('yyyy-MM-DDTHH:mm')
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
calculateAmount: function (x) {
|
|
||||||
// used by the jinja2 template
|
|
||||||
return calculateAmount(x, this.ingredient_factor)
|
|
||||||
},
|
},
|
||||||
updateTime: function () {
|
data() {
|
||||||
let new_start_time = moment(this.set_time_input).add(this.step.time_offset * -1, 'minutes').format('yyyy-MM-DDTHH:mm')
|
return {
|
||||||
|
details_visible: true,
|
||||||
|
set_time_input: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.set_time_input = moment(this.start_time)
|
||||||
|
.add(this.step.time_offset, "minutes")
|
||||||
|
.format("yyyy-MM-DDTHH:mm")
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
calculateAmount: function(x) {
|
||||||
|
// used by the jinja2 template
|
||||||
|
return calculateAmount(x, this.ingredient_factor)
|
||||||
|
},
|
||||||
|
updateTime: function() {
|
||||||
|
let new_start_time = moment(this.set_time_input)
|
||||||
|
.add(this.step.time_offset * -1, "minutes")
|
||||||
|
.format("yyyy-MM-DDTHH:mm")
|
||||||
|
|
||||||
this.$emit('update-start-time', new_start_time)
|
this.$emit("update-start-time", new_start_time)
|
||||||
this.closePopover()
|
this.closePopover()
|
||||||
|
},
|
||||||
|
closePopover: function() {
|
||||||
|
this.$refs[`id_reactive_popover_${this.step.id}`].$emit("close")
|
||||||
|
},
|
||||||
|
openPopover: function() {
|
||||||
|
this.$refs[`id_reactive_popover_${this.step.id}`].$emit("open")
|
||||||
|
},
|
||||||
},
|
},
|
||||||
closePopover: function () {
|
|
||||||
this.$refs[`id_reactive_popover_${this.step.id}`].$emit('close')
|
|
||||||
},
|
|
||||||
openPopover: function () {
|
|
||||||
this.$refs[`id_reactive_popover_${this.step.id}`].$emit('open')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -209,6 +209,18 @@
|
|||||||
"DeleteShoppingConfirm": "Are you sure that you want to remove all {food} from the shopping list?",
|
"DeleteShoppingConfirm": "Are you sure that you want to remove all {food} from the shopping list?",
|
||||||
"IgnoredFood": "{food} is set to ignore shopping.",
|
"IgnoredFood": "{food} is set to ignore shopping.",
|
||||||
"Add_Servings_to_Shopping": "Add {servings} Servings to Shopping",
|
"Add_Servings_to_Shopping": "Add {servings} Servings to Shopping",
|
||||||
|
"Week_Numbers": "Week numbers",
|
||||||
|
"Show_Week_Numbers": "Show week numbers ?",
|
||||||
|
"Export_As_ICal": "Export current period to iCal format",
|
||||||
|
"Export_To_ICal": "Export .ics",
|
||||||
|
"Cannot_Add_Notes_To_Shopping": "Notes cannot be added to the shopping list",
|
||||||
|
"Added_To_Shopping_List": "Added to shopping list",
|
||||||
|
"Shopping_List_Empty": "Your shopping list is currently empty, you can add items via the context menu of a meal plan entry (right click on the card or left click the menu icon)",
|
||||||
|
"Next_Period": "Next Period",
|
||||||
|
"Previous_Period": "Previous Period",
|
||||||
|
"Current_Period": "Current Period",
|
||||||
|
"Next_Day": "Next Day",
|
||||||
|
"Previous_Day": "Previous Day",
|
||||||
"Inherit": "Inherit",
|
"Inherit": "Inherit",
|
||||||
"IgnoreInherit": "Do Not Inherit Fields",
|
"IgnoreInherit": "Do Not Inherit Fields",
|
||||||
"FoodInherit": "Food Inheritable Fields",
|
"FoodInherit": "Food Inheritable Fields",
|
||||||
@ -238,18 +250,6 @@
|
|||||||
"mealplan_autoinclude_related_desc": "When adding a meal plan to the shopping list (manually or automatically), include all related recipes.",
|
"mealplan_autoinclude_related_desc": "When adding a meal plan to the shopping list (manually or automatically), include all related recipes.",
|
||||||
"default_delay_desc": "Default number of hours to delay a shopping list entry.",
|
"default_delay_desc": "Default number of hours to delay a shopping list entry.",
|
||||||
"filter_to_supermarket": "Filter to Supermarket",
|
"filter_to_supermarket": "Filter to Supermarket",
|
||||||
"Week_Numbers": "Week numbers",
|
|
||||||
"Show_Week_Numbers": "Show week numbers ?",
|
|
||||||
"Export_As_ICal": "Export current period to iCal format",
|
|
||||||
"Export_To_ICal": "Export .ics",
|
|
||||||
"Cannot_Add_Notes_To_Shopping": "Notes cannot be added to the shopping list",
|
|
||||||
"Added_To_Shopping_List": "Added to shopping list",
|
|
||||||
"Shopping_List_Empty": "Your shopping list is currently empty, you can add items via the context menu of a meal plan entry (right click on the card or left click the menu icon)",
|
|
||||||
"Next_Period": "Next Period",
|
|
||||||
"Previous_Period": "Previous Period",
|
|
||||||
"Current_Period": "Current Period",
|
|
||||||
"Next_Day": "Next Day",
|
|
||||||
"Previous_Day": "Previous Day",
|
|
||||||
"Coming_Soon": "Coming-Soon",
|
"Coming_Soon": "Coming-Soon",
|
||||||
"Auto_Planner": "Auto-Planner",
|
"Auto_Planner": "Auto-Planner",
|
||||||
"New_Cookbook": "New cookbook",
|
"New_Cookbook": "New cookbook",
|
||||||
|
@ -2283,24 +2283,12 @@ export interface ShoppingListRecipe {
|
|||||||
* @memberof ShoppingListRecipe
|
* @memberof ShoppingListRecipe
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string;
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {FoodSupermarketCategory}
|
|
||||||
* @memberof ShoppingListEntry
|
|
||||||
*/
|
|
||||||
unit?: FoodSupermarketCategory | null;
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @memberof ShoppingListRecipe
|
* @memberof ShoppingListRecipe
|
||||||
*/
|
*/
|
||||||
ingredient?: number | null;
|
recipe?: number | null;
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof ShoppingListEntry
|
|
||||||
*/
|
|
||||||
ingredient_note?: string;
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@ -2353,13 +2341,7 @@ export interface ShoppingListRecipeMealplan {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @memberof ShoppingListRecipe
|
* @memberof ShoppingListRecipeMealplan
|
||||||
*/
|
|
||||||
mealplan?: number | null;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof ShoppingListRecipe
|
|
||||||
*/
|
*/
|
||||||
mealplan?: number | null;
|
mealplan?: number | null;
|
||||||
/**
|
/**
|
||||||
@ -2408,13 +2390,7 @@ export interface ShoppingListRecipes {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @memberof ShoppingListRecipeMealplan
|
* @memberof ShoppingListRecipes
|
||||||
*/
|
|
||||||
mealplan?: number | null;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof ShoppingListRecipeMealplan
|
|
||||||
*/
|
*/
|
||||||
mealplan?: number | null;
|
mealplan?: number | null;
|
||||||
/**
|
/**
|
||||||
|
@ -47,7 +47,7 @@ module.exports = {
|
|||||||
pages: pages,
|
pages: pages,
|
||||||
filenameHashing: false,
|
filenameHashing: false,
|
||||||
productionSourceMap: false,
|
productionSourceMap: false,
|
||||||
publicPath: process.env.NODE_ENV === "production" ? "/static/vue" : "http://localhost:8080/",
|
publicPath: process.env.NODE_ENV === "production" ? "" : "http://localhost:8080/",
|
||||||
outputDir: "../cookbook/static/vue/",
|
outputDir: "../cookbook/static/vue/",
|
||||||
runtimeCompiler: true,
|
runtimeCompiler: true,
|
||||||
pwa: {
|
pwa: {
|
||||||
|
Loading…
Reference in New Issue
Block a user