mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 04:10:06 -05:00
Merge remote-tracking branch 'upstream/master' into recipe-serving-count
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load theming_tags %}
|
||||
{% load custom_tags %}
|
||||
|
||||
<html>
|
||||
<head>
|
||||
@@ -72,7 +73,7 @@
|
||||
<a class="dropdown-item" href="{% url 'view_plan' %}"><i
|
||||
class="fas fa-calendar fa-fw"></i> {% trans 'Meal-Plan' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'view_shopping' %}"><i
|
||||
<a class="dropdown-item" href="{% url 'list_shopping_list' %}"><i
|
||||
class="fas fa-shopping-cart fa-fw"></i> {% trans 'Shopping' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'list_food' %}"><i
|
||||
@@ -158,6 +159,14 @@
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{% message_of_the_day as message_of_the_day %}
|
||||
{% if message_of_the_day %}
|
||||
<div class="bg-warning" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px">
|
||||
{{ message_of_the_day }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label :for="'id_instruction_' + step.id">{% trans 'Instructions' %}</label>
|
||||
<b-form-textarea class="form-control" rows="2" max-rows="8" v-model="step.instruction"
|
||||
<b-form-textarea class="form-control" rows="2" max-rows="20" v-model="step.instruction"
|
||||
:id="'id_instruction_' + step.id"></b-form-textarea>
|
||||
<small class="text-muted">{% trans 'You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>' %}</small>
|
||||
</div>
|
||||
@@ -330,7 +330,7 @@
|
||||
</div>
|
||||
</draggable>
|
||||
|
||||
<div class="row" style="margin-top: 1vh; margin-bottom: 2vh">
|
||||
<div class="row" style="margin-top: 1vh; margin-bottom: 8vh">
|
||||
<div class="col-12">
|
||||
<button type="button" @click="updateRecipe(true)"
|
||||
class="btn btn-success shadow-none">{% trans 'Save & View' %}</button>
|
||||
@@ -597,7 +597,7 @@
|
||||
|
||||
let new_unit = this.recipe.steps[step].ingredients[id]
|
||||
new_unit.unit = {'name': tag}
|
||||
this.foods.push(new_unit.unit)
|
||||
this.units.push(new_unit.unit)
|
||||
this.recipe.steps[step].ingredients[id] = new_unit
|
||||
},
|
||||
searchKeywords: function (query) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% block content %}
|
||||
|
||||
<div class="table-container">
|
||||
<h3>{{ title }} {% trans 'List' %}
|
||||
<h3 style="margin-bottom: 2vh">{{ title }} {% trans 'List' %}
|
||||
{% if create_url %}
|
||||
<a href="{% url create_url %}"> <i class="fas fa-plus-circle"></i>
|
||||
</a>
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
{% render_table recipes %}
|
||||
{% else %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{% trans "Log in to view Recipies" %}
|
||||
{% trans "Log in to view Recipes" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -103,8 +103,15 @@
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="text" class="form-control" v-model="recipe_query" @keyup="getRecipes"
|
||||
placeholder="{% trans 'Search Recipe' %}" style="margin-bottom: 8px">
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" v-model="recipe_query" @keyup="getRecipes"
|
||||
placeholder="{% trans 'Search Recipe' %}">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" @click="getRandomRecipes">
|
||||
<i class="fas fa-dice"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<draggable class="list-group" :list="recipes"
|
||||
@@ -126,6 +133,9 @@
|
||||
class="text-muted">{% trans 'You can use markdown to format this field. See the <a href="/docs/markdown/" target="_blank" rel="noopener noreferrer">docs here</a>' %}</span></small>
|
||||
<br/>
|
||||
<br/>
|
||||
<input type="number" class="form-control" v-model="new_note_multiplier"
|
||||
placeholder="{% trans 'Recipe Multiplier' %}" style="margin-bottom: 8px">
|
||||
<br/>
|
||||
<draggable :list="pseudo_note_list"
|
||||
:group="{ name: 'plan', pull: 'clone', put: false }" :clone="cloneNote">
|
||||
<div class="list-group-item" v-for="(element, index) in pseudo_note_list"
|
||||
@@ -349,6 +359,7 @@
|
||||
],
|
||||
new_note_title: '',
|
||||
new_note_text: '',
|
||||
new_note_multiplier: '',
|
||||
default_shared_users: [],
|
||||
user_id_update: [],
|
||||
user_names: {},
|
||||
@@ -367,7 +378,7 @@
|
||||
this.getRecipes();
|
||||
},
|
||||
methods: {
|
||||
makeToast: function(title, message, variant=null) {
|
||||
makeToast: function (title, message, variant = null) {
|
||||
//TODO remove duplicate function in favor of central one
|
||||
this.$bvToast.toast(message, {
|
||||
title: title,
|
||||
@@ -389,7 +400,7 @@
|
||||
this.plan_entries = response.data;
|
||||
}).catch((err) => {
|
||||
console.log("getPlanEntries error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
getPlanTypes: function () {
|
||||
@@ -401,7 +412,7 @@
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log("getPlanTypes error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
buildGrid: function () {
|
||||
@@ -441,17 +452,23 @@
|
||||
|
||||
this.updateUserNames()
|
||||
},
|
||||
getRandomRecipes: function () {
|
||||
this.$set(this, 'recipe_query', '');
|
||||
this.getRecipes();
|
||||
},
|
||||
getRecipes: function () {
|
||||
let url = "{% url 'api:recipe-list' %}?limit=5"
|
||||
if (this.recipe_query !== '') {
|
||||
url += '&query=' + this.recipe_query;
|
||||
} else {
|
||||
url += '&random=True'
|
||||
}
|
||||
|
||||
this.$http.get(url).then((response) => {
|
||||
this.recipes = response.data;
|
||||
}).catch((err) => {
|
||||
console.log("getRecipes error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
getMdNote: function () {
|
||||
@@ -464,18 +481,18 @@
|
||||
this.recipes = response.data;
|
||||
}).catch((err) => {
|
||||
console.log("getRecipes error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
updateUserNames: function () {
|
||||
return this.$http.get("{% url 'api:username-list' %}?filter_list=[" + this.user_id_update + ']').then((response) => {
|
||||
for (let u of response.data) {
|
||||
this.$set(this.user_names, u.id, u.username);
|
||||
this.$set(this.user_names, u.id, u.username);
|
||||
}
|
||||
|
||||
}).catch((err) => {
|
||||
console.log("updateUserNames error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
dragChanged: function (date, meal_type, evt) {
|
||||
@@ -501,7 +518,7 @@
|
||||
this.$http.put(`{% url 'api:mealplan-list' %}${plan_entry.id}/`, plan_entry).then((response) => {
|
||||
}).catch((err) => {
|
||||
console.log("dragChanged update error", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -512,7 +529,7 @@
|
||||
this.meal_plan[entry.meal_type_name].days[entry.date].items = this.meal_plan[entry.meal_type_name].days[entry.date].items.filter(item => item !== entry)
|
||||
}).catch((err) => {
|
||||
console.log("deleteEntry error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
updatePlanTypes: function () {
|
||||
@@ -526,14 +543,14 @@
|
||||
promise_list.push(this.$http.post("{% url 'api:mealtype-list' %}", x).then((response) => {
|
||||
}).catch((err) => {
|
||||
console.log("updatePlanTypes create error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
}))
|
||||
} else if (x.delete) {
|
||||
if (x.id !== undefined) {
|
||||
promise_list.push(this.$http.delete(`{% url 'api:mealtype-list' %}${x.id}/`, x).then((response) => {
|
||||
}).catch((err) => {
|
||||
console.log("updatePlanTypes delete error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
@@ -541,7 +558,7 @@
|
||||
|
||||
}).catch((err) => {
|
||||
console.log("updatePlanTypes update error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}','{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -556,20 +573,28 @@
|
||||
}
|
||||
},
|
||||
cloneRecipe: function (recipe) {
|
||||
return {
|
||||
let r = {
|
||||
id: Math.round(Math.random() * 1000) + 10000,
|
||||
recipe: recipe.id,
|
||||
recipe_name: recipe.name,
|
||||
recipe_multiplier: (this.new_note_multiplier > 1) ? this.new_note_multiplier : 1,
|
||||
title: this.new_note_title,
|
||||
note: this.new_note_text,
|
||||
is_new: true
|
||||
}
|
||||
|
||||
this.new_note_title = ''
|
||||
this.new_note_text = ''
|
||||
this.new_note_multiplier = ''
|
||||
|
||||
return r
|
||||
},
|
||||
cloneNote: function () {
|
||||
let new_entry = {
|
||||
id: Math.round(Math.random() * 1000) + 10000,
|
||||
title: this.new_note_title,
|
||||
note: this.new_note_text,
|
||||
recipe_multiplier: 1,
|
||||
is_new: true,
|
||||
}
|
||||
|
||||
@@ -579,6 +604,7 @@
|
||||
|
||||
this.new_note_title = ''
|
||||
this.new_note_text = ''
|
||||
this.new_note_multiplier = ''
|
||||
return new_entry
|
||||
},
|
||||
planElementName: function (element) {
|
||||
@@ -618,10 +644,10 @@
|
||||
let first = true
|
||||
for (let se of this.shopping_list) {
|
||||
if (first) {
|
||||
url += `?r=${se.recipe}`
|
||||
url += `?r=[${se.recipe},${se.recipe_multiplier}]`
|
||||
first = false
|
||||
} else {
|
||||
url += `&r=${se.recipe}`
|
||||
url += `&r=[${se.recipe},${se.recipe_multiplier}]`
|
||||
}
|
||||
}
|
||||
return url
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
class="fas fa-pencil-alt fa-fw"></i> {% trans 'Edit' %}</a>
|
||||
<button class="dropdown-item" onclick="$('#bookmarkModal').modal({'show':true})">
|
||||
<i class="fas fa-bookmark fa-fw"></i> {% trans 'Add to Book' %}</button>
|
||||
{% if ingredients %}
|
||||
<a class="dropdown-item" href="{% url 'view_shopping' %}?r={{ recipe.pk }}">
|
||||
<i class="fas fa-shopping-cart fa-fw"></i> {% trans 'Add to Shopping' %}</a>
|
||||
{% endif %}
|
||||
|
||||
<a class="dropdown-item" v-bind:href="getShoppingUrl()" v-if="has_ingredients">
|
||||
<i class="fas fa-shopping-cart fa-fw"></i> {% trans 'Add to Shopping' %}</a>
|
||||
|
||||
<a class="dropdown-item" href="{% url 'new_meal_plan' %}?recipe={{ recipe.pk }}"><i
|
||||
class="fas fa-calendar fa-fw"></i> {% trans 'Add to Plan' %}</a>
|
||||
<button class="dropdown-item" onclick="openCookLogModal({{ recipe.pk }})"><i
|
||||
@@ -95,8 +95,9 @@
|
||||
<br/>
|
||||
{% endif %}
|
||||
|
||||
<div class="row" v-if="recipe && has_ingredients"> <!-- TODO duplicate code remove -->
|
||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2">
|
||||
<div class="row">
|
||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2" v-if="recipe && has_ingredients">
|
||||
<!-- TODO duplicate code remove -->
|
||||
<div class="card border-primary">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
@@ -148,9 +149,12 @@
|
||||
<template v-if="i.no_amount">
|
||||
<span>⁣</span>
|
||||
</template>
|
||||
<template v-if="!i.no_amount && i.unit">
|
||||
<template v-if="!i.no_amount">
|
||||
<span>[[roundDecimals(i.amount * ingredient_factor)]]</span>
|
||||
[[i.unit.name]]
|
||||
{# Allow for amounts without units, such as "2 eggs" #}
|
||||
<template v-if="i.unit">
|
||||
[[i.unit.name]]
|
||||
</template>
|
||||
</template>
|
||||
</label>
|
||||
</div>
|
||||
@@ -168,7 +172,9 @@
|
||||
</td>
|
||||
<td style="vertical-align: middle!important;">
|
||||
<template v-if="i.note">
|
||||
<b-button v-b-popover.hover="i.note" class="btn btn-sm d-print-none"><i class="fas fa-info"></i></b-button>
|
||||
<b-button v-b-popover.hover="i.note"
|
||||
class="btn btn-sm d-print-none"><i
|
||||
class="fas fa-info"></i></b-button>
|
||||
|
||||
<div class="d-none d-print-block">
|
||||
<i class="far fa-comment-alt"></i> [[i.note]]
|
||||
@@ -240,7 +246,8 @@
|
||||
style="margin-top: 1vh">
|
||||
<div class="col-md-6">
|
||||
<table class="table table-sm">
|
||||
<template v-for="i in recipe.steps[{{ forloop.counter0 }}].ingredients"> <!-- TODO duplicate code remove -->
|
||||
<template v-for="i in recipe.steps[{{ forloop.counter0 }}].ingredients">
|
||||
<!-- TODO duplicate code remove -->
|
||||
|
||||
<template v-if="i.is_header">
|
||||
<tr>
|
||||
@@ -263,9 +270,12 @@
|
||||
<template v-if="i.no_amount">
|
||||
<span>⁣</span>
|
||||
</template>
|
||||
<template v-if="!i.no_amount && i.unit">
|
||||
<template v-if="!i.no_amount">
|
||||
<span>[[roundDecimals(i.amount * ingredient_factor)]]</span>
|
||||
[[i.unit.name]]
|
||||
{# Allow for amounts without units, such as "2 eggs" #}
|
||||
<template v-if="i.unit">
|
||||
[[i.unit.name]]
|
||||
</template>
|
||||
</template>
|
||||
</label>
|
||||
</div>
|
||||
@@ -283,7 +293,9 @@
|
||||
</td>
|
||||
<td style="vertical-align: middle!important;">
|
||||
<template v-if="i.note">
|
||||
<b-button v-b-popover.hover="i.note" class="btn btn-sm d-print-none"><i class="fas fa-info"></i></b-button>
|
||||
<b-button v-b-popover.hover="i.note"
|
||||
class="btn btn-sm d-print-none"><i
|
||||
class="fas fa-info"></i></b-button>
|
||||
<div class="d-none d-print-block">
|
||||
<i class="far fa-comment-alt"></i> [[i.note]]
|
||||
</div>
|
||||
@@ -514,6 +526,9 @@
|
||||
time_diff += s.time
|
||||
}
|
||||
|
||||
},
|
||||
getShoppingUrl: function () {
|
||||
return `{% url 'view_shopping' %}?r=[${this.recipe.id},${this.ingredient_factor}]`
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Login' %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if form.errors %}
|
||||
|
||||
18
cookbook/templates/registration/signup.html
Normal file
18
cookbook/templates/registration/signup.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Register' %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{% trans 'Create your Account' %}</h3>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Create User' %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -4,61 +4,700 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Cookbook" %}{% endblock %}
|
||||
{% block title %}{% trans "Shopping List" %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ form.media }}
|
||||
{% include 'include/vue_base.html' %}
|
||||
|
||||
<link rel="stylesheet" href="{% static 'css/vue-multiselect-bs4.min.css' %}">
|
||||
<script src="{% static 'js/vue-multiselect.min.js' %}"></script>
|
||||
|
||||
<script src="{% static 'js/Sortable.min.js' %}"></script>
|
||||
<script src="{% static 'js/vuedraggable.umd.min.js' %}"></script>
|
||||
|
||||
<link rel="stylesheet" href="{% static 'css/pretty-checkbox.min.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2><i class="fas fa-shopping-cart"></i> {% trans 'Shopping List' %}</h2>
|
||||
|
||||
<form action="{% url 'view_shopping' %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-sync-alt"></i> {% trans 'Load' %}</button>
|
||||
</form>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<!--// @formatter:off-->
|
||||
<textarea id="id_list" class="form-control" rows="{{ ingredients|length|add:1 }}">{% for i in ingredients %}{% if markdown_format %}- [ ] {% endif %}{{ i.amount.normalize }} {{ i.unit }} {{ i.food.name }} {% endfor %}</textarea>
|
||||
<!--// @formatter:on-->
|
||||
<div class="col col-md-9">
|
||||
<h2>{% trans 'Shopping List' %}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col col-md-12 text-center">
|
||||
<button class="btn btn-success" onclick="copy()" style="width: 15vw" data-toggle="tooltip"
|
||||
data-placement="right" title="{% trans 'Copy list to clipboard' %}" id="id_btn_copy" onmouseout="resetTooltip()"><i
|
||||
class="far fa-copy"></i></button>
|
||||
<div class="col col-mdd-3 text-right">
|
||||
<b-form-checkbox switch size="lg" v-model="edit_mode"
|
||||
@change="$forceUpdate()">{% trans 'Edit' %}</b-form-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function copy() {
|
||||
let list = $('#id_list');
|
||||
<template v-if="shopping_list !== undefined">
|
||||
|
||||
list.select();
|
||||
<div class="text-center" v-if="loading">
|
||||
<i class="fas fa-spinner fa-spin fa-8x"></i>
|
||||
</div>
|
||||
<div v-else-if="edit_mode">
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
|
||||
$('#id_btn_copy').attr('data-original-title','{% trans 'Copied!' %}').tooltip('show');
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="fa fa-search"></i> {% trans 'Search' %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input type="text" class="form-control" v-model="recipe_query" @keyup="getRecipes"
|
||||
placeholder="{% trans 'Search Recipe' %}">
|
||||
<ul class="list-group" style="margin-top: 8px">
|
||||
<li class="list-group-item" v-for="x in recipes">
|
||||
<div class="row flex-row" style="padding-left: 0.5vw; padding-right: 0.5vw">
|
||||
<div class="flex-column flex-fill my-auto"><a v-bind:href="getRecipeUrl(x.id)"
|
||||
target="_blank"
|
||||
rel="nofollow norefferer">[[x.name]]</a>
|
||||
</div>
|
||||
<div class="flex-column align-self-end">
|
||||
<button class="btn btn-outline-primary shadow-none"
|
||||
@click="addRecipeToList(x)"><i
|
||||
class="fa fa-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
document.execCommand("copy");
|
||||
}
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="fa fa-shopping-cart"></i> {% trans 'Shopping Recipes' %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<template v-if="shopping_list.recipes.length < 1">
|
||||
{% trans 'No recipes selected' %}
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="row flex-row my-auto" v-for="x in shopping_list.recipes"
|
||||
style="margin-top: 1vh!important;">
|
||||
<div class="flex-column align-self-start " style="margin-right: 0.4vw">
|
||||
<button class="btn btn-outline-danger" @click="removeRecipeFromList(x)"><i
|
||||
class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
<div class="flex-grow-1 flex-column my-auto"><a v-bind:href="getRecipeUrl(x.recipe)"
|
||||
target="_blank"
|
||||
rel="nofollow norefferer">[[x.recipe_name]]</a>
|
||||
</div>
|
||||
<div class="flex-column align-self-end ">
|
||||
<div class="input-group input-group-sm my-auto">
|
||||
<div class="input-group-prepend">
|
||||
<button class="text-muted btn btn-outline-primary shadow-none"
|
||||
@click="((x.multiplier - 1) > 0) ? x.multiplier -= 1 : 1">-
|
||||
</button>
|
||||
</div>
|
||||
<input class="form-control" type="number" v-model="x.multiplier">
|
||||
<div class="input-group-append">
|
||||
<button class="text-muted btn btn-outline-primary shadow-none"
|
||||
@click="x.multiplier += 1">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
function resetTooltip() {
|
||||
setTimeout(function () {
|
||||
$('#id_btn_copy').attr('data-original-title','{% trans 'Copy list to clipboard' %}');
|
||||
}, 300);
|
||||
}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
})
|
||||
<table class="table table-sm table-striped" style="margin-top: 1vh">
|
||||
|
||||
<tbody is="draggable" group="people" :list="display_entries" tag="tbody" :empty-insert-threshold="10"
|
||||
handle=".handle" @sort="sortEntries()">
|
||||
|
||||
<tr v-for="(element, index) in display_entries" :key="element.id">
|
||||
<!--<td class="handle"><i class="fas fa-sort"></i></td>-->
|
||||
<td>[[element.amount]]</td>
|
||||
<td>[[element.unit.name]]</td>
|
||||
<td>[[element.food.name]]</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-danger" v-if="element.list_recipe === null"
|
||||
@click="shopping_list.entries = shopping_list.entries.filter(item => item.id !== element.id)">
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-3">
|
||||
<input class="form-control" type="number" placeholder="{% trans 'Amount' %}"
|
||||
v-model="new_entry.amount" ref="new_entry_amount">
|
||||
</div>
|
||||
<div class="col col-md-4">
|
||||
<multiselect
|
||||
v-tabindex
|
||||
ref="unit"
|
||||
v-model="new_entry.unit"
|
||||
:options="units"
|
||||
:close-on-select="true"
|
||||
:clear-on-select="true"
|
||||
:allow-empty="true"
|
||||
:preserve-search="true"
|
||||
placeholder="{% trans 'Select Unit' %}"
|
||||
tag-placeholder="{% trans 'Create' %}"
|
||||
select-label="{% trans 'Select' %}"
|
||||
:taggable="true"
|
||||
@tag="addUnitType"
|
||||
label="name"
|
||||
track-by="name"
|
||||
:multiple="false"
|
||||
:loading="units_loading"
|
||||
@search-change="searchUnits">
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="col col-md-4">
|
||||
<multiselect
|
||||
v-tabindex
|
||||
ref="food"
|
||||
v-model="new_entry.food"
|
||||
:options="foods"
|
||||
:close-on-select="true"
|
||||
:clear-on-select="true"
|
||||
:allow-empty="true"
|
||||
:preserve-search="true"
|
||||
placeholder="{% trans 'Select Food' %}"
|
||||
tag-placeholder="{% trans 'Create' %}"
|
||||
select-label="{% trans 'Select' %}"
|
||||
:taggable="true"
|
||||
@tag="addFoodType"
|
||||
label="name"
|
||||
track-by="name"
|
||||
:multiple="false"
|
||||
:loading="foods_loading"
|
||||
@search-change="searchFoods">
|
||||
</multiselect>
|
||||
</div>
|
||||
|
||||
<div class="col col-md-1 my-auto text-right">
|
||||
<button class="btn btn-success btn-lg" @click="addEntry()"><i class="fa fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col" style="text-align: right; margin-top: 1vh">
|
||||
|
||||
<div class="form-group form-check form-group-lg">
|
||||
<input class="form-check-input" style="zoom:1.3;" type="checkbox"
|
||||
v-model="shopping_list.finished" id="id_finished">
|
||||
<label class="form-check-label" style="zoom:1.3;"
|
||||
for="id_finished"> {% trans 'Finished' %}</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col" style="margin-top: 1vh">
|
||||
<multiselect
|
||||
v-tabindex
|
||||
|
||||
v-model="shopping_list.shared"
|
||||
:options="users"
|
||||
:close-on-select="true"
|
||||
:clear-on-select="true"
|
||||
:allow-empty="true"
|
||||
:preserve-search="true"
|
||||
placeholder="{% trans 'Select User' %}"
|
||||
select-label="{% trans 'Select' %}"
|
||||
label="username"
|
||||
track-by="id"
|
||||
:multiple="true"
|
||||
:loading="users_loading"
|
||||
@search-change="searchUsers">
|
||||
</multiselect>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
|
||||
{% if request.user.userpreference.shopping_auto_sync > 0 %}
|
||||
<div class="row" v-if="!onLine">
|
||||
<div class="col col-md-12">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
{% trans 'You are offline, shopping list might not sync.' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row" style="margin-top: 8px">
|
||||
<div class="col col-md-12">
|
||||
<table class="table">
|
||||
<tr v-for="x in display_entries">
|
||||
<template v-if="!x.checked">
|
||||
<td><input type="checkbox" style="zoom:1.4;" v-model="x.checked"
|
||||
@change="entryChecked(x)">
|
||||
</td>
|
||||
<td>[[x.amount]]</td>
|
||||
<td>[[x.unit.name]]</td>
|
||||
<td>[[x.food.name]]</td>
|
||||
</template>
|
||||
</tr>
|
||||
|
||||
<tr v-for="x in display_entries" class="text-muted">
|
||||
<template v-if="x.checked">
|
||||
<td><input type="checkbox" style="zoom:1.4;" v-model="x.checked"
|
||||
@change="entryChecked(x)">
|
||||
</td>
|
||||
<td>[[x.amount]]</td>
|
||||
<td>[[x.unit.name]]</td>
|
||||
<td>[[x.food.name]]</td>
|
||||
</template>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="col" style="text-align: right">
|
||||
<b-button class="btn btn-info" v-b-modal.id_modal_export><i
|
||||
class="fas fa-file-export"></i> {% trans 'Export' %}</b-button>
|
||||
<button class="btn btn-success" @click="updateShoppingList()" v-if="edit_mode"><i
|
||||
class="fas fa-save"></i> {% trans 'Save' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<b-modal id="id_modal_export" title="{% trans 'Copy/Export' %}">
|
||||
<div class="row">
|
||||
<div class="col col-12">
|
||||
<label>
|
||||
{% trans 'List Prefix' %}
|
||||
<input class="form-control" v-model="export_text_prefix">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-12">
|
||||
<b-form-textarea class="form-control" max-rows="8" v-model="export_text">
|
||||
|
||||
</b-form-textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</b-modal>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script type="application/javascript">
|
||||
let csrftoken = Cookies.get('csrftoken');
|
||||
Vue.http.headers.common['X-CSRFToken'] = csrftoken;
|
||||
|
||||
Vue.component('vue-multiselect', window.VueMultiselect.default)
|
||||
|
||||
let app = new Vue({
|
||||
components: {
|
||||
Multiselect: window.VueMultiselect.default
|
||||
},
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#id_base_container',
|
||||
data: {
|
||||
shopping_list_id: {% if shopping_list_id %}{{ shopping_list_id }}{% else %}null{% endif %},
|
||||
loading: true,
|
||||
edit_mode: false,
|
||||
export_text_prefix: '', //TODO add userpreference
|
||||
recipe_query: '',
|
||||
recipes: [],
|
||||
shopping_list: undefined,
|
||||
new_entry: {
|
||||
unit: undefined,
|
||||
amount: undefined,
|
||||
food: undefined,
|
||||
},
|
||||
foods: [],
|
||||
foods_loading: false,
|
||||
units: [],
|
||||
units_loading: false,
|
||||
users: [],
|
||||
users_loading: false,
|
||||
onLine: navigator.onLine,
|
||||
},
|
||||
directives: {
|
||||
tabindex: {
|
||||
inserted(el) {
|
||||
el.setAttribute('tabindex', 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
multiplier_cache() {
|
||||
let cache = {}
|
||||
this.shopping_list.recipes.forEach((r) => {
|
||||
cache[r.id] = !(Number.isNaN(r.multiplier)) ? parseFloat(r.multiplier) : 1
|
||||
})
|
||||
return cache
|
||||
},
|
||||
display_entries() {
|
||||
let entries = []
|
||||
|
||||
//TODO merge multiple ingredients of same unit
|
||||
|
||||
this.shopping_list.entries.forEach(element => {
|
||||
let item = {}
|
||||
Object.assign(item, element);
|
||||
if (item.list_recipe !== null) {
|
||||
item.amount = item.amount * this.multiplier_cache[item.list_recipe]
|
||||
}
|
||||
item.unit = ((element.unit !== undefined && element.unit !== null) ? element.unit : {'name': ''})
|
||||
entries.push(item)
|
||||
});
|
||||
|
||||
return entries
|
||||
},
|
||||
export_text() {
|
||||
let text = ''
|
||||
for (let e of this.display_entries.filter(item => item.checked === false)) {
|
||||
text += `${this.export_text_prefix}${e.amount} ${e.unit.name} ${e.food.name} \n`
|
||||
}
|
||||
return text
|
||||
}
|
||||
},
|
||||
/*
|
||||
watch: {
|
||||
recipe: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.recipe_changed = this.recipe_changed !== undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
window.addEventListener('beforeunload', this.warnPageLeave)
|
||||
},
|
||||
*/
|
||||
mounted: function () {
|
||||
this.loadShoppingList()
|
||||
|
||||
{% if recipes %}
|
||||
this.loading = true
|
||||
this.edit_mode = true
|
||||
let loadingRecipes = []
|
||||
{% for r in recipes %}
|
||||
loadingRecipes.push(this.loadInitialRecipe({{ r.recipe }}, {{ r.multiplier }}))
|
||||
{% endfor %}
|
||||
|
||||
Promise.allSettled(loadingRecipes).then(() => {
|
||||
this.loading = false
|
||||
})
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.userpreference.shopping_auto_sync > 0 %}
|
||||
setInterval(() => {
|
||||
if ((this.shopping_list_id !== null) && !this.edit_mode && window.navigator.onLine) {
|
||||
this.loadShoppingList(true)
|
||||
}
|
||||
}, {% widthratio request.user.userpreference.shopping_auto_sync 1 1000 %})
|
||||
|
||||
window.addEventListener('online', this.updateOnlineStatus);
|
||||
window.addEventListener('offline', this.updateOnlineStatus);
|
||||
{% endif %}
|
||||
|
||||
this.searchUsers('')
|
||||
},
|
||||
methods: {
|
||||
updateOnlineStatus(e) {
|
||||
const {
|
||||
type
|
||||
} = e;
|
||||
this.onLine = type === 'online';
|
||||
},
|
||||
/*
|
||||
warnPageLeave: function (event) {
|
||||
if (this.recipe_changed) {
|
||||
event.returnValue = ''
|
||||
return ''
|
||||
}
|
||||
},
|
||||
*/
|
||||
makeToast: function (title, message, variant = null) {
|
||||
//TODO remove duplicate function in favor of central one
|
||||
this.$bvToast.toast(message, {
|
||||
title: title,
|
||||
variant: variant,
|
||||
toaster: 'b-toaster-top-center',
|
||||
solid: true
|
||||
})
|
||||
},
|
||||
loadInitialRecipe: function (recipe, multiplier) {
|
||||
return this.$http.get('{% url 'api:recipe-detail' 123456 %}'.replace('123456', recipe)).then((response) => {
|
||||
this.addRecipeToList(response.data, multiplier)
|
||||
}).catch((err) => {
|
||||
console.log("getRecipes error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
loadShoppingList: function (autosync = false) {
|
||||
|
||||
if (this.shopping_list_id) {
|
||||
this.$http.get("{% url 'api:shoppinglist-detail' 123456 %}".replace('123456', this.shopping_list_id) + ((autosync) ? '?autosync=true' : '')).then((response) => {
|
||||
if (!autosync) {
|
||||
this.shopping_list = response.body
|
||||
this.loading = false
|
||||
} else {
|
||||
let check_map = {}
|
||||
for (let e of response.body.entries) {
|
||||
check_map[e.id] = {checked: e.checked}
|
||||
}
|
||||
|
||||
for (let se of this.shopping_list.entries) {
|
||||
if (check_map[se.id] !== undefined) {
|
||||
se.checked = check_map[se.id].checked
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.shopping_list.entries.length === 0) {
|
||||
this.edit_mode = true
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
} else {
|
||||
this.shopping_list = {
|
||||
"recipes": [],
|
||||
"entries": [],
|
||||
"entries_display": [],
|
||||
"shared": [{% for u in request.user.userpreference.plan_share.all %}
|
||||
{'id': {{ u.pk }}, 'username': '{{ u.get_user_name }}'},
|
||||
{% endfor %}],
|
||||
"created_by": 1
|
||||
}
|
||||
this.loading = false
|
||||
|
||||
if (this.shopping_list.entries.length === 0) {
|
||||
this.edit_mode = true
|
||||
}
|
||||
}
|
||||
},
|
||||
updateShoppingList: function () {
|
||||
this.loading = true
|
||||
let recipe_promises = []
|
||||
|
||||
for (let i in this.shopping_list.recipes) {
|
||||
if (this.shopping_list.recipes[i].created) {
|
||||
console.log('updating recipe', this.shopping_list.recipes[i])
|
||||
recipe_promises.push(this.$http.post("{% url 'api:shoppinglistrecipe-list' %}", this.shopping_list.recipes[i], {}).then((response) => {
|
||||
let old_id = this.shopping_list.recipes[i].id
|
||||
console.log("list recipe create respose ", response.body)
|
||||
this.$set(this.shopping_list.recipes, i, response.body)
|
||||
for (let e of this.shopping_list.entries.filter(item => item.list_recipe === old_id)) {
|
||||
console.log("found recipe updating ID")
|
||||
e.list_recipe = this.shopping_list.recipes[i].id
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error updating a resource!' %}' + err.bodyText, 'danger')
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
Promise.allSettled(recipe_promises).then(() => {
|
||||
console.log("proceeding to update shopping list", this.shopping_list)
|
||||
|
||||
if (this.shopping_list_id === null) {
|
||||
return this.$http.post("{% url 'api:shoppinglist-list' %}", this.shopping_list, {}).then((response) => {
|
||||
console.log(response)
|
||||
this.makeToast('{% trans 'Updated' %}', '{% trans 'Object created successfully!' %}', 'success')
|
||||
this.loading = false
|
||||
|
||||
this.shopping_list = response.body
|
||||
this.shopping_list_id = this.shopping_list.id
|
||||
|
||||
window.history.pushState('shopping_list', '{% trans 'Shopping List' %}', "{% url 'view_shopping' 123456 %}".replace('123456', this.shopping_list_id));
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error creating a resource!' %}' + err.bodyText, 'danger')
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
return this.$http.put("{% url 'api:shoppinglist-detail' shopping_list_id %}", this.shopping_list, {}).then((response) => {
|
||||
console.log(response)
|
||||
this.shopping_list = response.body
|
||||
this.makeToast('{% trans 'Updated' %}', '{% trans 'Changes saved successfully!' %}', 'success')
|
||||
this.loading = false
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error updating a resource!' %}' + err.bodyText, 'danger')
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
},
|
||||
sortEntries: function () {
|
||||
this.display_entries.forEach((item, index) => {
|
||||
|
||||
})
|
||||
console.log("IMPLEMENT ME", this.display_entries)
|
||||
},
|
||||
entryChecked: function (entry) {
|
||||
console.log("checked entry: ", entry)
|
||||
this.shopping_list.entries.forEach((item) => {
|
||||
if (item.id === entry.id) { //TODO unwrap once same entries are merged
|
||||
item.checked = entry.checked
|
||||
this.$http.put("{% url 'api:shoppinglistentry-detail' 123456 %}".replace('123456', item.id), item, {}).then((response) => {
|
||||
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error updating a resource!' %}' + err.bodyText, 'danger')
|
||||
this.loading = false
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
addEntry: function () {
|
||||
if (this.new_entry.food !== undefined) {
|
||||
this.shopping_list.entries.push({
|
||||
'list_recipe': null,
|
||||
'food': this.new_entry.food,
|
||||
'unit': this.new_entry.unit,
|
||||
'amount': parseFloat(this.new_entry.amount),
|
||||
'order': 0,
|
||||
'checked': false
|
||||
})
|
||||
|
||||
this.new_entry = {
|
||||
unit: undefined,
|
||||
amount: undefined,
|
||||
food: undefined,
|
||||
}
|
||||
|
||||
this.$refs.new_entry_amount.focus();
|
||||
} else {
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'Please enter a valid food' %}', 'danger')
|
||||
}
|
||||
},
|
||||
getRecipes: function () {
|
||||
let url = "{% url 'api:recipe-list' %}?limit=5&internal=true"
|
||||
if (this.recipe_query !== '') {
|
||||
url += '&query=' + this.recipe_query;
|
||||
} else {
|
||||
this.recipes = []
|
||||
return
|
||||
}
|
||||
|
||||
this.$http.get(url).then((response) => {
|
||||
this.recipes = response.data;
|
||||
}).catch((err) => {
|
||||
console.log("getRecipes error: ", err);
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
getRecipeUrl: function (id) { //TODO generic function that can be reused else were
|
||||
return '{% url 'view_recipe' 123456 %}'.replace('123456', id)
|
||||
},
|
||||
addRecipeToList: function (recipe, multiplier = 1) {
|
||||
let slr = {
|
||||
"created": true,
|
||||
"id": Math.random() * 1000,
|
||||
"recipe": recipe.id,
|
||||
"recipe_name": recipe.name,
|
||||
"multiplier": multiplier
|
||||
}
|
||||
|
||||
this.shopping_list.recipes.push(slr)
|
||||
|
||||
for (let s of recipe.steps) {
|
||||
for (let i of s.ingredients) {
|
||||
if (!i.is_header && i.food !== null) {
|
||||
this.shopping_list.entries.push({
|
||||
'list_recipe': slr.id,
|
||||
'food': i.food,
|
||||
'unit': i.unit,
|
||||
'amount': i.amount,
|
||||
'order': 0
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
removeRecipeFromList: function (slr) {
|
||||
this.shopping_list.entries = this.shopping_list.entries.filter(item => item.list_recipe !== slr.id)
|
||||
this.shopping_list.recipes = this.shopping_list.recipes.filter(item => item !== slr)
|
||||
},
|
||||
searchKeywords: function (query) {
|
||||
this.keywords_loading = true
|
||||
this.$http.get("{% url 'api:keyword-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
this.keywords = response.data;
|
||||
this.keywords_loading = false
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
|
||||
searchUnits: function (query) { //TODO move to central component
|
||||
this.units_loading = true
|
||||
this.$http.get("{% url 'api:unit-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
this.units = response.data;
|
||||
this.units_loading = false
|
||||
}).catch((err) => {
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
searchFoods: function (query) { //TODO move to central component
|
||||
this.foods_loading = true
|
||||
this.$http.get("{% url 'api:food-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
this.foods = response.data
|
||||
this.foods_loading = false
|
||||
}).catch((err) => {
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
addFoodType: function (tag, index) { //TODO move to central component
|
||||
let new_food = {'name': tag}
|
||||
this.foods.push(new_food)
|
||||
this.new_entry.food = new_food
|
||||
},
|
||||
addUnitType: function (tag, index) { //TODO move to central component
|
||||
let new_unit = {'name': tag}
|
||||
this.units.push(new_unit)
|
||||
this.new_entry.unit = new_unit
|
||||
},
|
||||
searchUsers: function (query) { //TODO move to central component
|
||||
this.users_loading = true
|
||||
this.$http.get("{% url 'api:username-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
this.users = response.data
|
||||
this.users_loading = false
|
||||
}).catch((err) => {
|
||||
this.makeToast('{% trans 'Error' %}', '{% trans 'There was an error loading a resource!' %}' + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('online', this.updateOnlineStatus);
|
||||
window.removeEventListener('offline', this.updateOnlineStatus);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -16,8 +16,18 @@
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h3>{% trans 'Backup & Restore' %}</h3>
|
||||
<a href="{% url 'api_backup' %}" class="btn btn-success">{% trans 'Download Backup' %}</a>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h3>{% trans 'Invite Links' %}</h3>
|
||||
<a href="{% url 'list_invite_link' %}" class="btn btn-success">{% trans 'Show Links' %}</a>
|
||||
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h3>{% trans 'Backup & Restore' %}</h3>
|
||||
<a href="{% url 'api_backup' %}" class="btn btn-success">{% trans 'Download Backup' %}</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
@@ -59,7 +69,8 @@
|
||||
{% trans 'Warning' %}{% else %}{% trans 'Ok' %}{% endif %}</span></h4>
|
||||
{% if secret_key %}
|
||||
{% blocktrans %}
|
||||
You do not have a <code>SECRET_KEY</code> configured in your <code>.env</code> file. Django defaulted to the standard key
|
||||
You do not have a <code>SECRET_KEY</code> configured in your <code>.env</code> file. Django defaulted to the
|
||||
standard key
|
||||
provided with the installation which is publicly know and insecure! Please set
|
||||
<code>SECRET_KEY</code> int the <code>.env</code> configuration file.
|
||||
{% endblocktrans %}
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<div class="col-md-1">
|
||||
<input class="form-control" v-model="i.amount">
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="col-md-4">
|
||||
|
||||
<table class="table-layout:fixed">
|
||||
<col width="95%"/>
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="col-md-4">
|
||||
|
||||
<multiselect v-tabindex
|
||||
ref="ingredient"
|
||||
@@ -143,6 +143,9 @@
|
||||
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="text" placeholder="{% trans 'Note' %}" class="form-control" v-model="i.note">
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button class="btn btn-outline-danger btn-lg" type="button"
|
||||
@click="deleteIngredient(i)" tabindex="-1"><i
|
||||
@@ -345,7 +348,9 @@
|
||||
},
|
||||
openUnitSelect: function (id) {
|
||||
let index = id.replace('unit_', '')
|
||||
this.$set(app.$refs.unit[index].$data, 'search', this.recipe_data.recipeIngredient[index].unit.text)
|
||||
if (this.recipe_data.recipeIngredient[index].unit !== null) {
|
||||
this.$set(app.$refs.unit[index].$data, 'search', this.recipe_data.recipeIngredient[index].unit.text)
|
||||
}
|
||||
},
|
||||
openIngredientSelect: function (id) {
|
||||
let index = id.replace('ingredient_', '')
|
||||
@@ -367,7 +372,7 @@
|
||||
this.units = response.data.results;
|
||||
if (this.recipe_data !== undefined) {
|
||||
for (let x of Array.from(this.recipe_data.recipeIngredient)) {
|
||||
if (x.unit.text !== '') {
|
||||
if (x.unit !== null && x.unit.text !== '') {
|
||||
this.units = this.units.filter(item => item.text !== x.unit.text)
|
||||
this.units.push(x.unit)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user