mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-02 04:39:54 -05:00
Merge branch 'develop' of https://github.com/vabene1111/recipes into develop
# Conflicts: # vue/src/apps/ShoppingListView/ShoppingListView.vue
This commit is contained in:
@@ -38,10 +38,12 @@ def get_filetype(name):
|
|||||||
|
|
||||||
# TODO this whole file needs proper documentation, refactoring, and testing
|
# TODO this whole file needs proper documentation, refactoring, and testing
|
||||||
# TODO also add env variable to define which images sizes should be compressed
|
# TODO also add env variable to define which images sizes should be compressed
|
||||||
def handle_image(request, image_object, filetype='.jpeg'):
|
# filetype argument can not be optional, otherwise this function will treat all images as if they were a jpeg
|
||||||
|
# Because it's no longer optional, no reason to return it
|
||||||
|
def handle_image(request, image_object, filetype):
|
||||||
if (image_object.size / 1000) > 500: # if larger than 500 kb compress
|
if (image_object.size / 1000) > 500: # if larger than 500 kb compress
|
||||||
if filetype == '.jpeg' or filetype == '.jpg':
|
if filetype == '.jpeg' or filetype == '.jpg':
|
||||||
return rescale_image_jpeg(image_object), filetype
|
return rescale_image_jpeg(image_object)
|
||||||
if filetype == '.png':
|
if filetype == '.png':
|
||||||
return rescale_image_png(image_object), filetype
|
return rescale_image_png(image_object)
|
||||||
return image_object, filetype
|
return image_object
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class IngredientParser:
|
|||||||
|
|
||||||
# some people/languages put amount and unit at the end of the ingredient string
|
# some people/languages put amount and unit at the end of the ingredient string
|
||||||
# if something like this is detected move it to the beginning so the parser can handle it
|
# if something like this is detected move it to the beginning so the parser can handle it
|
||||||
if re.search(r'^([A-z])+(.)*[1-9](\d)*\s([A-z])+', ingredient):
|
if len(ingredient) < 1000 and re.search(r'^([A-z])+(.)*[1-9](\d)*\s([A-z])+', ingredient):
|
||||||
match = re.search(r'[1-9](\d)*\s([A-z])+', ingredient)
|
match = re.search(r'[1-9](\d)*\s([A-z])+', ingredient)
|
||||||
print(f'reording from {ingredient} to {ingredient[match.start():match.end()] + " " + ingredient.replace(ingredient[match.start():match.end()], "")}')
|
print(f'reording from {ingredient} to {ingredient[match.start():match.end()] + " " + ingredient.replace(ingredient[match.start():match.end()], "")}')
|
||||||
ingredient = ingredient[match.start():match.end()] + ' ' + ingredient.replace(ingredient[match.start():match.end()], '')
|
ingredient = ingredient[match.start():match.end()] + ' ' + ingredient.replace(ingredient[match.start():match.end()], '')
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ class Integration:
|
|||||||
:param image_file: ByteIO stream containing the image
|
:param image_file: ByteIO stream containing the image
|
||||||
:param filetype: type of file to write bytes to, default to .jpeg if unknown
|
:param filetype: type of file to write bytes to, default to .jpeg if unknown
|
||||||
"""
|
"""
|
||||||
recipe.image = File(handle_image(self.request, File(image_file, name='image'), filetype=filetype)[0], name=f'{uuid.uuid4()}_{recipe.pk}{filetype}')
|
recipe.image = File(handle_image(self.request, File(image_file, name='image'), filetype=filetype), name=f'{uuid.uuid4()}_{recipe.pk}{filetype}')
|
||||||
recipe.save()
|
recipe.save()
|
||||||
|
|
||||||
def get_recipe_from_file(self, file):
|
def get_recipe_from_file(self, file):
|
||||||
|
|||||||
@@ -2467,7 +2467,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Das direkte ausliefern von Mediendateien mit gunicorn/python ist <b>nicht "
|
"Das direkte ausliefern von Mediendateien mit gunicorn/python ist <b>nicht "
|
||||||
"empfehlenswert</b>! Bitte folge den beschriebenen Schritten <a href=\"https"
|
"empfehlenswert</b>! Bitte folge den beschriebenen Schritten <a href=\"https"
|
||||||
"\\://github.com/vabene1111/recipes/releases/tag/0.8.1\">hier</a>, um Ihre "
|
"://github.com/vabene1111/recipes/releases/tag/0.8.1\">hier</a>, um Ihre "
|
||||||
"Installation zu aktualisieren.\n"
|
"Installation zu aktualisieren.\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
|
|||||||
@@ -337,6 +337,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if HOSTED and request.space.max_recipes == 10 %}
|
||||||
|
<div class="bg-warning" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px">
|
||||||
|
{% trans 'You are using the free version of Tandor' %} <a class="btn-success btn-sm" href="https://tandoor.dev/manage">{% trans 'Upgrade Now' %}</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="container-fluid mt-2 mt-md-5 mt-xl-5 mt-lg-5{% if request.user.userpreference.left_handed %} left-handed {% endif %}"
|
<div class="container-fluid mt-2 mt-md-5 mt-xl-5 mt-lg-5{% if request.user.userpreference.left_handed %} left-handed {% endif %}"
|
||||||
id="id_base_container">
|
id="id_base_container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ def test_list_permission(arg, request):
|
|||||||
|
|
||||||
|
|
||||||
def test_list_space(recipe_1_s1, u1_s1, u1_s2, space_2):
|
def test_list_space(recipe_1_s1, u1_s1, u1_s2, space_2):
|
||||||
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 10
|
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 10
|
||||||
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
|
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 0
|
||||||
|
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
recipe_1_s1.space = space_2
|
recipe_1_s1.space = space_2
|
||||||
@@ -32,8 +32,8 @@ def test_list_space(recipe_1_s1, u1_s1, u1_s2, space_2):
|
|||||||
Step.objects.update(space=Subquery(Step.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
|
Step.objects.update(space=Subquery(Step.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
|
||||||
Ingredient.objects.update(space=Subquery(Ingredient.objects.filter(pk=OuterRef('pk')).values('step__recipe__space')[:1]))
|
Ingredient.objects.update(space=Subquery(Ingredient.objects.filter(pk=OuterRef('pk')).values('step__recipe__space')[:1]))
|
||||||
|
|
||||||
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 0
|
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 0
|
||||||
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 10
|
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 10
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("arg", [
|
@pytest.mark.parametrize("arg", [
|
||||||
|
|||||||
@@ -230,4 +230,4 @@ def test_shopping_with_header_ingredient(u1_s1, recipe):
|
|||||||
# recipe.step_set.first().ingredient_set.add(IngredientFactory(ingredients__header=1))
|
# recipe.step_set.first().ingredient_set.add(IngredientFactory(ingredients__header=1))
|
||||||
u1_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id}))
|
u1_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id}))
|
||||||
assert len(json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 10
|
assert len(json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 10
|
||||||
assert len(json.loads(u1_s1.get(reverse('api:ingredient-list')).content)) == 11
|
assert len(json.loads(u1_s1.get(reverse('api:ingredient-list')).content)['results']) == 11
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import mimetypes
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@@ -773,14 +774,16 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
|||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
image = None
|
image = None
|
||||||
|
filetype = ".jpeg" # fall-back to .jpeg, even if wrong, at least users will know it's an image and most image viewers can open it correctly anyways
|
||||||
|
|
||||||
if 'image' in serializer.validated_data:
|
if 'image' in serializer.validated_data:
|
||||||
image = obj.image
|
image = obj.image
|
||||||
|
filetype = mimetypes.guess_extension(serializer.validated_data['image'].content_type) or filetype
|
||||||
elif 'image_url' in serializer.validated_data:
|
elif 'image_url' in serializer.validated_data:
|
||||||
try:
|
try:
|
||||||
response = requests.get(serializer.validated_data['image_url'])
|
response = requests.get(serializer.validated_data['image_url'])
|
||||||
image = File(io.BytesIO(response.content))
|
image = File(io.BytesIO(response.content))
|
||||||
print('test')
|
filetype = mimetypes.guess_extension(response.headers['content-type']) or filetype
|
||||||
except UnidentifiedImageError as e:
|
except UnidentifiedImageError as e:
|
||||||
print(e)
|
print(e)
|
||||||
pass
|
pass
|
||||||
@@ -792,7 +795,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if image is not None:
|
if image is not None:
|
||||||
img, filetype = handle_image(request, image)
|
img = handle_image(request, image, filetype)
|
||||||
obj.image = File(img, name=f'{uuid.uuid4()}_{obj.pk}{filetype}')
|
obj.image = File(img, name=f'{uuid.uuid4()}_{obj.pk}{filetype}')
|
||||||
obj.save()
|
obj.save()
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|||||||
@@ -218,8 +218,7 @@
|
|||||||
:href="resolveDjangoUrl('view_recipe',r.id)">{{
|
:href="resolveDjangoUrl('view_recipe',r.id)">{{
|
||||||
r.name
|
r.name
|
||||||
}}</a>
|
}}</a>
|
||||||
<span class="float-right">Imported</span>
|
<span class="float-right">{{ $t('Imported') }}</span>
|
||||||
<!-- TODO localize -->
|
|
||||||
</div>
|
</div>
|
||||||
</b-list-group-item>
|
</b-list-group-item>
|
||||||
|
|
||||||
@@ -230,8 +229,7 @@
|
|||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
<a class="float-left" target="_blank"
|
<a class="float-left" target="_blank"
|
||||||
rel="noreferrer nofollow" :href="u">{{ u }}</a>
|
rel="noreferrer nofollow" :href="u">{{ u }}</a>
|
||||||
<span class="float-right">Failed</span>
|
<span class="float-right">{{ $t('Failure') }}</span>
|
||||||
<!-- TODO localize -->
|
|
||||||
</div>
|
</div>
|
||||||
</b-list-group-item>
|
</b-list-group-item>
|
||||||
</b-list-group>
|
</b-list-group>
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<beta-warning></beta-warning>
|
|
||||||
|
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
<generic-multiselect @change="food = $event.val; refreshList()" ref="food_multiselect"
|
<b-input-group>
|
||||||
:model="Models.FOOD"
|
<generic-multiselect @change="food = $event.val; refreshList()" ref="food_multiselect"
|
||||||
:initial_single_selection="food"
|
:model="Models.FOOD"
|
||||||
:multiple="false"></generic-multiselect>
|
:initial_single_selection="food"
|
||||||
<b-button @click="generic_model = Models.FOOD; generic_action=Actions.DELETE" :disabled="food === null">
|
:multiple="false"
|
||||||
<i class="fas fa-trash-alt"></i>
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"></generic-multiselect>
|
||||||
</b-button>
|
|
||||||
<b-button @click="generic_model = Models.FOOD; generic_action=Actions.MERGE" :disabled="food === null">
|
|
||||||
<i class="fas fa-compress-arrows-alt"></i>
|
|
||||||
</b-button>
|
|
||||||
|
|
||||||
<b-button @click="generic_model = Models.FOOD; generic_action=Actions.UPDATE" :disabled="food === null">
|
<b-input-group-append>
|
||||||
<i class="fas fa-edit"></i>
|
<b-dropdown no-caret right :disabled="food === null">
|
||||||
</b-button>
|
<template #button-content>
|
||||||
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<b-dropdown-item @click="generic_model = Models.FOOD; generic_action=Actions.UPDATE"
|
||||||
|
:disabled="food === null">
|
||||||
|
<i class="fas fa-edit fa-fw"></i> {{ $t('Edit') }}
|
||||||
|
</b-dropdown-item>
|
||||||
|
<b-dropdown-item @click="generic_model = Models.FOOD; generic_action=Actions.MERGE"
|
||||||
|
:disabled="food === null">
|
||||||
|
<i class="fas fa-compress-arrows-alt fa-fw"></i> {{ $t('Merge') }}
|
||||||
|
</b-dropdown-item>
|
||||||
|
<b-dropdown-item @click="generic_model = Models.FOOD; generic_action=Actions.DELETE"
|
||||||
|
:disabled="food === null">
|
||||||
|
<i class="fas fa-trash-alt fa-fw"></i> {{ $t('Delete') }}
|
||||||
|
</b-dropdown-item>
|
||||||
|
|
||||||
|
</b-dropdown>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
|
||||||
<generic-modal-form :model="Models.FOOD" :action="generic_action" :show="generic_model === Models.FOOD"
|
<generic-modal-form :model="Models.FOOD" :action="generic_action" :show="generic_model === Models.FOOD"
|
||||||
:item1="food"
|
:item1="food"
|
||||||
@@ -25,36 +39,58 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
|
|
||||||
<generic-multiselect
|
<b-input-group>
|
||||||
@change="unit = $event.val; refreshList()"
|
|
||||||
:model="Models.UNIT"
|
<generic-multiselect
|
||||||
ref="unit_multiselect"
|
@change="unit = $event.val; refreshList()"
|
||||||
:initial_single_selection="unit"
|
:model="Models.UNIT"
|
||||||
:multiple="false"></generic-multiselect>
|
ref="unit_multiselect"
|
||||||
|
:initial_single_selection="unit"
|
||||||
|
:multiple="false"
|
||||||
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"></generic-multiselect>
|
||||||
|
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-dropdown no-caret right :disabled="unit === null">
|
||||||
|
<template #button-content>
|
||||||
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<b-dropdown-item @click="generic_model = Models.UNIT; generic_action=Actions.UPDATE"
|
||||||
|
:disabled="unit === null">
|
||||||
|
<i class="fas fa-edit fa-fw"></i> {{ $t('Edit') }}
|
||||||
|
</b-dropdown-item>
|
||||||
|
<b-dropdown-item @click="generic_model = Models.UNIT; generic_action=Actions.MERGE"
|
||||||
|
:disabled="unit === null">
|
||||||
|
<i class="fas fa-compress-arrows-alt fa-fw"></i> {{ $t('Merge') }}
|
||||||
|
</b-dropdown-item>
|
||||||
|
<b-dropdown-item @click="generic_model = Models.UNIT; generic_action=Actions.DELETE"
|
||||||
|
:disabled="unit === null">
|
||||||
|
<i class="fas fa-trash-alt fa-fw"></i> {{ $t('Delete') }}
|
||||||
|
</b-dropdown-item>
|
||||||
|
|
||||||
|
</b-dropdown>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
|
||||||
<b-button @click="generic_model = Models.UNIT; generic_action=Actions.DELETE" :disabled="unit === null">
|
|
||||||
<i class="fas fa-trash-alt"></i>
|
|
||||||
</b-button>
|
|
||||||
<b-button @click="generic_model = Models.UNIT; generic_action=Actions.MERGE" :disabled="unit === null">
|
|
||||||
<i class="fas fa-compress-arrows-alt"></i>
|
|
||||||
</b-button>
|
|
||||||
<b-button @click="generic_model = Models.UNIT; generic_action=Actions.UPDATE" :disabled="unit === null">
|
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</b-button>
|
|
||||||
<generic-modal-form :model="Models.UNIT" :action="generic_action" :show="generic_model === Models.UNIT"
|
<generic-modal-form :model="Models.UNIT" :action="generic_action" :show="generic_model === Models.UNIT"
|
||||||
:item1="unit"
|
:item1="unit"
|
||||||
@finish-action="finishGenericAction()"/>
|
@finish-action="finishGenericAction()"/>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<b-pagination pills v-model="current_page" :total-rows="total_object_count" :per-page="page_size"
|
<b-row class="mt-2">
|
||||||
@change="pageChange"></b-pagination>
|
<b-col cols="12">
|
||||||
|
<b-pagination align="center" pills v-model="current_page" :total-rows="total_object_count"
|
||||||
|
:per-page="page_size"
|
||||||
|
@change="pageChange"></b-pagination>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
|
||||||
<div class="row mt-2">
|
|
||||||
<div class="col col-md-12">
|
<b-row class="mt-2">
|
||||||
<table class="table table-bordered">
|
<b-col>
|
||||||
|
<table class="table table-bordered table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('Amount') }}</th>
|
<th>{{ $t('Amount') }}</th>
|
||||||
@@ -62,7 +98,8 @@
|
|||||||
<th>{{ $t('Food') }}</th>
|
<th>{{ $t('Food') }}</th>
|
||||||
<th>{{ $t('Note') }}</th>
|
<th>{{ $t('Note') }}</th>
|
||||||
<th>
|
<th>
|
||||||
<b-button variant="success" @click="updateIngredient()"><i class="fas fa-save"></i>
|
<b-button variant="success" class="btn btn-sm" @click="updateIngredient()"><i
|
||||||
|
class="fas fa-save"></i>
|
||||||
</b-button>
|
</b-button>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -108,15 +145,17 @@
|
|||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td style="width: 5vw">
|
<td style="width: 5vw">
|
||||||
<b-button :disabled="i.changed !== true"
|
<b-button-group>
|
||||||
:variant="(i.changed !== true) ? 'primary' : 'success'"
|
<b-button :disabled="i.changed !== true"
|
||||||
@click="updateIngredient(i)">
|
:variant="(i.changed !== true) ? 'primary' : 'success'"
|
||||||
<i class="fas fa-save"></i>
|
@click="updateIngredient(i)">
|
||||||
</b-button>
|
<i class="fas fa-save"></i>
|
||||||
<b-button variant="danger"
|
</b-button>
|
||||||
@click="deleteIngredient(i)">
|
<b-button variant="danger"
|
||||||
<i class="fas fa-trash"></i>
|
@click="deleteIngredient(i)">
|
||||||
</b-button>
|
<i class="fas fa-trash"></i>
|
||||||
|
</b-button>
|
||||||
|
</b-button-group>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@@ -127,12 +166,17 @@
|
|||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<b-pagination pills v-model="current_page" :total-rows="total_object_count" :per-page="page_size"
|
|
||||||
@change="pageChange"></b-pagination>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
|
||||||
|
<b-row class="mt-2">
|
||||||
|
<b-col cols="12">
|
||||||
|
<b-pagination align="center" pills v-model="current_page" :total-rows="total_object_count"
|
||||||
|
:per-page="page_size"
|
||||||
|
@change="pageChange"></b-pagination>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -153,7 +197,7 @@ Vue.use(BootstrapVue)
|
|||||||
export default {
|
export default {
|
||||||
name: "IngredientEditorView",
|
name: "IngredientEditorView",
|
||||||
mixins: [ApiMixin, ResolveUrlMixin],
|
mixins: [ApiMixin, ResolveUrlMixin],
|
||||||
components: {BetaWarning, LoadingSpinner, GenericMultiselect, GenericModalForm},
|
components: {LoadingSpinner, GenericMultiselect, GenericModalForm},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ingredients: [],
|
ingredients: [],
|
||||||
@@ -202,7 +246,7 @@ export default {
|
|||||||
if (this.unit !== null) {
|
if (this.unit !== null) {
|
||||||
params.query.unit = this.unit.id
|
params.query.unit = this.unit.id
|
||||||
}
|
}
|
||||||
apiClient.listIngredients(this.current_page, this.page_size,params).then(result => {
|
apiClient.listIngredients(this.current_page, this.page_size, params).then(result => {
|
||||||
this.ingredients = result.data.results
|
this.ingredients = result.data.results
|
||||||
this.total_object_count = result.data.count
|
this.total_object_count = result.data.count
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
|||||||
@@ -112,21 +112,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- shopping list table -->
|
<!-- shopping list table -->
|
||||||
<div v-if="items && items.length > 0">
|
<div v-if="items && items.length > 0">
|
||||||
<div v-for="(done, x) in Sections" :key="x">
|
<div v-for="(categories, checked_key) in Sections" :key="checked_key">
|
||||||
<div v-if="x == 'true'"
|
<div v-if="checked_key == 'true'"
|
||||||
class="bg-header w-100 text-center d-flex justify-content-center align-items-center">
|
class="bg-header w-100 text-center d-flex justify-content-center align-items-center">
|
||||||
<span class="h4 d-flex mt-1 mb-1">{{ $t("Completed") }}</span>
|
<span class="h4 d-flex mt-1 mb-1">{{ $t("Completed") }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="(s, i) in done" :key="i">
|
<div v-for="(foods_group, category_key) in categories" :key="category_key">
|
||||||
<div class="dropdown b-dropdown position-static inline-block"
|
<div class="dropdown b-dropdown position-static inline-block"
|
||||||
data-html2canvas-ignore="true" v-if="Object.entries(s).length > 0">
|
data-html2canvas-ignore="true" v-if="Object.entries(foods_group).length > 0">
|
||||||
<button
|
<button
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn dropdown-toggle btn-link text-decoration-none text-dark pr-2 dropdown-toggle-no-caret"
|
class="btn dropdown-toggle btn-link text-decoration-none text-dark pr-2 dropdown-toggle-no-caret"
|
||||||
@click.stop="openContextMenu($event, s, true)"
|
@click.stop="openContextMenu($event, foods_group, true)"
|
||||||
>
|
>
|
||||||
<i class="fas fa-ellipsis-v"></i>
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -135,20 +135,20 @@
|
|||||||
class="btn btn-lg text-decoration-none text-dark px-1 py-0 border-0"
|
class="btn btn-lg text-decoration-none text-dark px-1 py-0 border-0"
|
||||||
variant="link"
|
variant="link"
|
||||||
data-toggle="collapse"
|
data-toggle="collapse"
|
||||||
:href="'#section-' + sectionID(x, i)"
|
:href="'#section-' + sectionID(checked_key, category_key)"
|
||||||
:aria-expanded="'true' ? x == 'false' : 'true'"
|
:aria-expanded="'true' ? checked_key == 'false' : 'true'"
|
||||||
>
|
>
|
||||||
<i class="fa fa-chevron-right rotate"/>
|
<i class="fa fa-chevron-right rotate"/>
|
||||||
<span class="h6 ml-2 text-secondary">{{ i }}</span>
|
<span class="h6 ml-2 text-secondary">{{ category_key }}</span>
|
||||||
</b-button>
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="collapse" :id="'section-' + sectionID(x, i)" visible
|
<div class="collapse" :id="'section-' + sectionID(checked_key, category_key)" visible
|
||||||
role="tabpanel" :class="{ show: x == 'false' }">
|
role="tabpanel" :class="{ show: checked_key == 'false' }">
|
||||||
<!-- passing an array of values to the table grouped by Food -->
|
<!-- passing an array of values to the table grouped by Food -->
|
||||||
<transition-group name="slide-fade">
|
<transition-group name="slide-fade">
|
||||||
<div class="pl-4 pr-0" v-for="(entries, x) in Object.entries(s)"
|
<div class="pl-4 pr-0" v-for="(entries, index) in Object.entries(foods_group)"
|
||||||
:key="x">
|
:key="index">
|
||||||
<transition name="slide-fade" mode="out-in">
|
<transition name="slide-fade" mode="out-in">
|
||||||
<shopping-line-item
|
<shopping-line-item
|
||||||
:entries="entries[1]"
|
:entries="entries[1]"
|
||||||
@@ -846,6 +846,8 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
Sections() {
|
Sections() {
|
||||||
|
// Sections to display in list (checked/unchecked -> category -> food group -> entries)
|
||||||
|
// ordering/sorting is definied by the order in which categories are added to the sections array (even trough the dev console does not show it like this)
|
||||||
function getKey(item, group_by, x) {
|
function getKey(item, group_by, x) {
|
||||||
switch (group_by) {
|
switch (group_by) {
|
||||||
case "category":
|
case "category":
|
||||||
@@ -879,10 +881,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var groups = {false: {}, true: {}} // force unchecked to always be first
|
var groups = {false: {}, true: {}} // force unchecked to always be first
|
||||||
|
// TODO: make nulls_first a user setting
|
||||||
|
// add undefined group to both the checked and non checked
|
||||||
|
groups.false[this.$t("Undefined")] = {}
|
||||||
|
groups.true[this.$t("Undefined")] = {}
|
||||||
|
// category order is defined by order of insertion into groups variable
|
||||||
if (this.ui.selected_supermarket) {
|
if (this.ui.selected_supermarket) {
|
||||||
// TODO: make nulls_first a user setting
|
|
||||||
groups.false[this.$t("Undefined")] = {}
|
|
||||||
groups.true[this.$t("Undefined")] = {}
|
|
||||||
let super_cats = this.supermarkets
|
let super_cats = this.supermarkets
|
||||||
.filter((x) => x.id === this.ui.selected_supermarket)
|
.filter((x) => x.id === this.ui.selected_supermarket)
|
||||||
.map((x) => x.category_to_supermarket)
|
.map((x) => x.category_to_supermarket)
|
||||||
@@ -978,7 +982,7 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
ui: {
|
ui: {
|
||||||
handler() {
|
handler() {
|
||||||
this.$cookies.set(SETTINGS_COOKIE_NAME, {ui: this.ui, settings: {entrymode: this.entrymode}})
|
this.$cookies.set(SETTINGS_COOKIE_NAME, {ui: this.ui, settings: {entrymode: this.entrymode}}, "100y")
|
||||||
if (this.entrymode) {
|
if (this.entrymode) {
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
this.setFocus()
|
this.setFocus()
|
||||||
@@ -989,7 +993,7 @@ export default {
|
|||||||
},
|
},
|
||||||
entrymode: {
|
entrymode: {
|
||||||
handler() {
|
handler() {
|
||||||
this.$cookies.set(SETTINGS_COOKIE_NAME, {ui: this.ui, settings: {entrymode: this.entrymode}})
|
this.$cookies.set(SETTINGS_COOKIE_NAME, {ui: this.ui, settings: {entrymode: this.entrymode}}, "100y")
|
||||||
if (this.entrymode) {
|
if (this.entrymode) {
|
||||||
document.getElementById('shoppinglist').scrollTop = 0
|
document.getElementById('shoppinglist').scrollTop = 0
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
@@ -1069,7 +1073,7 @@ export default {
|
|||||||
if (this.new_item.ingredient !== "" && this.new_item.ingredient !== undefined) {
|
if (this.new_item.ingredient !== "" && this.new_item.ingredient !== undefined) {
|
||||||
this.genericPostAPI("api_ingredient_from_string", {text: this.new_item.ingredient}).then((result) => {
|
this.genericPostAPI("api_ingredient_from_string", {text: this.new_item.ingredient}).then((result) => {
|
||||||
let unit = null
|
let unit = null
|
||||||
if (result.data.unit !== "") {
|
if (result.data.unit !== null) {
|
||||||
unit = {name: result.data.unit}
|
unit = {name: result.data.unit}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1129,9 +1133,10 @@ export default {
|
|||||||
promises.push(this.saveThis({id: entry, delay_until: delay_date}, false))
|
promises.push(this.saveThis({id: entry, delay_until: delay_date}, false))
|
||||||
})
|
})
|
||||||
Promise.all(promises).then(() => {
|
Promise.all(promises).then(() => {
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
|
||||||
this.items = this.items.filter((x) => !entries.includes(x.id))
|
this.items = this.items.filter((x) => !entries.includes(x.id))
|
||||||
this.delay = this.defaultDelay
|
this.delay = this.defaultDelay
|
||||||
|
}).catch(err => {
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
deleteRecipe: function (e, recipe) {
|
deleteRecipe: function (e, recipe) {
|
||||||
|
|||||||
@@ -368,6 +368,7 @@
|
|||||||
"no_pinned_recipes": "You have no pinned recipes!",
|
"no_pinned_recipes": "You have no pinned recipes!",
|
||||||
"Planned": "Planned",
|
"Planned": "Planned",
|
||||||
"Pinned": "Pinned",
|
"Pinned": "Pinned",
|
||||||
|
"Imported": "Imported",
|
||||||
"Quick actions": "Quick actions",
|
"Quick actions": "Quick actions",
|
||||||
"Ratings": "Ratings",
|
"Ratings": "Ratings",
|
||||||
"Internal": "Internal",
|
"Internal": "Internal",
|
||||||
|
|||||||
Reference in New Issue
Block a user