added loading spinner and made new view the main recipe view
This commit is contained in:
@ -1,444 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
{% load static %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% load custom_tags %}
|
||||
|
||||
{% block title %}{{ recipe.name }}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
|
||||
{% include 'include/vue_base.html' %}
|
||||
<script src="{% static 'js/moment-with-locales.min.js' %}"></script>
|
||||
|
||||
<script src="{% static 'js/frac.js' %}"></script>
|
||||
|
||||
<link rel="stylesheet" href="{% static 'css/pretty-checkbox.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'custom/css/markdown_blockquote.css' %}">
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-8">
|
||||
{% recipe_rating recipe request.user as rating %}
|
||||
<h3>{{ recipe.name }} {{ rating|safe }}</h3>
|
||||
</div>
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="col col-md-4 d-print-none" style="text-align: right">
|
||||
<div class="dropdown">
|
||||
<a class="btn shadow-none" href="#" role="button" id="dropdownMenuLink"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</a>
|
||||
{% recipe_rating recipe request.user as rating %}
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'edit_recipe' recipe.pk %}"><i
|
||||
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>
|
||||
|
||||
<a class="dropdown-item" v-bind:href="shopping_url" 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
|
||||
class="fas fa-clipboard-list fa-fw"></i> {% trans 'Log Cooking' %}</button>
|
||||
<button class="dropdown-item" onclick="window.print()"><i
|
||||
class="fas fa-print fa-fw"></i> {% trans 'Print' %}</button>
|
||||
<a class="dropdown-item" href="{% url 'view_export' %}?r={{ recipe.pk }}" target="_blank"
|
||||
rel="noopener noreferrer"><i class="fas fa-file-export fa-fw"></i> {% trans 'Export' %}</a>
|
||||
{% if recipe.internal %}
|
||||
<a class="dropdown-item" href="{% url 'new_share_link' recipe.pk %}" target="_blank"
|
||||
rel="noopener noreferrer"><i class="fas fa-share-alt fa-fw"></i> {% trans 'Share' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="app">
|
||||
<recipe-view recipe_id="5"></recipe-view>
|
||||
</div>
|
||||
|
||||
{% if recipe.storage %}
|
||||
<small>{% trans 'in' %} <a
|
||||
href="{% url 'edit_storage' recipe.storage.pk %}">{{ recipe.storage.name }}</a></small><br/>
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.internal %}
|
||||
<small>{% trans 'by' %} {{ recipe.created_by.get_user_name }}<br/></small>
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.keywords %}
|
||||
{% for x in recipe.keywords.all %}
|
||||
<span class="badge badge-pill badge-light">{{ x }}</span>
|
||||
{% endfor %}
|
||||
<br/>
|
||||
<br/>
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.working_time and recipe.working_time != 0 %}
|
||||
<span class="badge badge-secondary"><i
|
||||
class="fas fa-user-clock"></i> {% trans 'Preparation time ~' %} {{ recipe.working_time }} min </span>
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.waiting_time and recipe.waiting_time != 0 %}
|
||||
<span
|
||||
class="badge badge-secondary"><i
|
||||
class="far fa-clock"></i> {% trans 'Waiting time ~' %} {{ recipe.waiting_time }} min </span>
|
||||
{% endif %}
|
||||
{% recipe_last recipe request.user as last_cooked %}
|
||||
{% if last_cooked %}
|
||||
<span class="badge badge-primary">{% trans 'Last cooked' %} {{ last_cooked|date }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.waiting_time and recipe.waiting_time != 0 or recipe.working_time and recipe.working_time != 0 or last_cooked %}
|
||||
<br/>
|
||||
<br/>
|
||||
{% endif %}
|
||||
|
||||
<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">
|
||||
<div class="col col-md-9">
|
||||
<h4 class="card-title">{% trans 'Ingredients' %}</h4>
|
||||
</div>
|
||||
<div class="col col-md-3">
|
||||
|
||||
<div class="input-group d-print-none">
|
||||
<input type="number" value="1" maxlength="3" class="form-control" style="min-width: 2vw"
|
||||
v-model="servings"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text"><i class="fas fa-calculator"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-sm">
|
||||
<template v-for="s in recipe.steps">
|
||||
<template v-if="s.name != '' && s.show_as_header && s.ingredients.length > 0">
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>[[s.name]]</b>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-for="i in s.ingredients">
|
||||
<template v-if="i.is_header">
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>[[i.note]]</b>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-else>
|
||||
<tr>
|
||||
<td style="vertical-align: middle!important;">
|
||||
<div class="pretty p-default p-curve">
|
||||
<input type="checkbox" v-model="i.checked"/>
|
||||
<div class="state p-success">
|
||||
<label>
|
||||
<template v-if="i.no_amount">
|
||||
<span>⁣</span>
|
||||
</template>
|
||||
<template v-if="!i.no_amount">
|
||||
<span v-html="calculateAmount(i.amount)"></span>
|
||||
{# Allow for amounts without units, such as "2 eggs" #}
|
||||
<template v-if="i.unit">
|
||||
[[i.unit.name]]
|
||||
</template>
|
||||
</template>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
<td style="vertical-align: middle!important;">
|
||||
<template v-if="i.food && i.food.recipe">
|
||||
<a v-bind:href='"{% url 'view_recipe' 12345 %}".replace(/12345/, i.food.recipe)'
|
||||
target="_blank" rel="noopener noreferrer">[[i.food.name]]</a>
|
||||
</template>
|
||||
<template v-else-if="i.food">
|
||||
[[i.food.name]]
|
||||
</template>
|
||||
</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>
|
||||
|
||||
<div class="d-none d-print-block">
|
||||
<i class="far fa-comment-alt"></i> [[i.note]]
|
||||
</div>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- Bottom border -->
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if recipe.image %}
|
||||
<div class="col-12 order-1 col-sm-12 order-sm-1 col-md-6 order-md-2" style="text-align: center">
|
||||
<img class="img img-fluid rounded" src="{{ recipe.image.url }}" style="max-height: 30vh;"
|
||||
alt="{% trans 'Recipe Image' %}">
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if recipe.nutrition %}
|
||||
<div class="row mt-5">
|
||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{% trans 'Nutrition' %}</h4>
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>{% trans 'Calories' %}</b>
|
||||
</td>
|
||||
<td style="text-align: right">{{ recipe.nutrition.calories|floatformat:2 }}</td>
|
||||
<td>kcal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>{% trans 'Carbohydrates' %}</b>
|
||||
</td>
|
||||
<td style="text-align: right">{{ recipe.nutrition.carbohydrates|floatformat:2 }}</td>
|
||||
<td>g</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>{% trans 'Fats' %}</b>
|
||||
</td>
|
||||
<td style="text-align: right">{{ recipe.nutrition.fats|floatformat:2 }}</td>
|
||||
<td>g</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>{% trans 'Proteins' %}</b>
|
||||
</td>
|
||||
<td style="text-align: right">{{ recipe.nutrition.proteins|floatformat:2 }}</td>
|
||||
<td>g</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
{% if recipe.nutrition.source %}
|
||||
Source: {{ recipe.nutrition.source }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div v-if="recipe !== undefined && recipe.steps.length > 0">
|
||||
<hr>
|
||||
<h3>{% trans 'Instructions' %}</h3>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div style="font-size: large;" v-if="recipe">
|
||||
{% for s in recipe.steps.all %}
|
||||
<div style="margin-top: 1vh" class="card">
|
||||
<div class="card-body">
|
||||
{% if recipe.steps.all|length > 1 %}
|
||||
<div class="card-title">
|
||||
|
||||
<div class="row d-flex">
|
||||
<div class="col-md-8 text-muted ">
|
||||
{% if s.type == 'TEXT' %}
|
||||
<i class="fas fa-paragraph fa-fw"></i>
|
||||
{% elif s.type == 'TIME' %}
|
||||
<i class="fas fa-clock fa-fw"></i>
|
||||
{% endif %}
|
||||
{% if s.name %}{{ s.name }}{% else %}{% trans 'Step' %}
|
||||
{{ forloop.counter }}{% endif %}
|
||||
{% if s.time != 0 %}
|
||||
- {{ s.time }} {% trans 'Minutes' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4 col-12 justify-content-end" v-if="has_times">
|
||||
<input type="datetime-local" class="form-control"
|
||||
@change="updateTimes(recipe.steps[{{ forloop.counter0 }}])"
|
||||
v-model="recipe.steps[{{ forloop.counter0 }}].time_finished">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="recipe.steps[{{ forloop.counter0 }}].ingredients.length > 0"
|
||||
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-if="i.is_header">
|
||||
<tr>
|
||||
<td style="padding-top: 8px!important; ">
|
||||
<b>[[i.note]]</b>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-else>
|
||||
<tr>
|
||||
<td style="vertical-align: middle!important;">
|
||||
<div class="pretty p-default p-curve">
|
||||
<input type="checkbox" v-model="i.checked"/>
|
||||
<div class="state p-success">
|
||||
<label>
|
||||
<template v-if="i.no_amount">
|
||||
<span>⁣</span>
|
||||
</template>
|
||||
<template v-if="!i.no_amount">
|
||||
<span v-html="calculateAmount(i.amount)"></span>
|
||||
{# Allow for amounts without units, such as "2 eggs" #}
|
||||
<template v-if="i.unit">
|
||||
[[i.unit.name]]
|
||||
</template>
|
||||
</template>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
<td style="vertical-align: middle!important;">
|
||||
<template v-if="i.food && i.food.recipe">
|
||||
<a v-bind:href='"{% url 'view_recipe' 12345 %}".replace(/12345/, i.food.recipe)'
|
||||
target="_blank" rel="noopener noreferrer">[[i.food.name]]</a>
|
||||
</template>
|
||||
<template v-else-if="i.food">
|
||||
[[i.food.name]]
|
||||
</template>
|
||||
</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>
|
||||
<div class="d-none d-print-block">
|
||||
<i class="far fa-comment-alt"></i> [[i.note]]
|
||||
</div>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
<!-- Bottom border -->
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<div class="row" style="margin-top: 8px">
|
||||
<div class="col-md-12">
|
||||
{% if s.instruction %}
|
||||
{{ s.get_instruction_render | safe }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if recipe.storage %}
|
||||
<div class="row">
|
||||
{% if recipe.internal %}
|
||||
<div class="col col-12" style="margin-top: 2vh">
|
||||
<a href='#' onClick='openRecipe({{ recipe.id }})'
|
||||
class="d-print-none">{% trans 'View external recipe' %} <i class="fas fa-external-link-alt"></i></a>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
{% if '.pdf' in recipe.file_path %}
|
||||
<div class="col col-12">
|
||||
<iframe src="{% static 'pdfjs/viewer.html' %}?file={% url 'api_get_recipe_file' recipe.id %}"
|
||||
width="100%"
|
||||
height="700px"
|
||||
style="border: none;"></iframe>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if '.jpg' in recipe.file_path or '.png' in recipe.file_path or '.jpeg' in recipe.file_path %}
|
||||
<div class="col-md-12" style="text-align: center">
|
||||
<img class="img img-fluid" src="{% url 'api_get_recipe_file' recipe.id %}"
|
||||
alt="{% trans 'External recipe image' %}">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col col-12" style="margin-top: 1vh">
|
||||
<div class="card border-info">
|
||||
<div class="card-body text-info">
|
||||
<h5 class="card-title">{% trans 'External recipe' %}</h5>
|
||||
<p class="card-text">
|
||||
{% blocktrans %}
|
||||
This is an external recipe, which means you can only view it by opening the link
|
||||
above.
|
||||
You can convert this recipe to a fancy recipe by pressing the convert button. The
|
||||
original
|
||||
file
|
||||
will still be accessible.
|
||||
{% endblocktrans %}.
|
||||
<br/>
|
||||
<br/>
|
||||
<a href="{% url 'edit_convert_recipe' recipe.pk %}"
|
||||
class="card-link btn btn-info">{% trans 'Convert now!' %}</a>
|
||||
<a href='#' onClick='openRecipe({{ recipe.id }})'
|
||||
class="d-print-none btn btn-warning">{% trans 'View external recipe' %} <i
|
||||
class="fas fa-external-link-alt"></i></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.userpreference.comments %}
|
||||
<br/>
|
||||
<br/>
|
||||
@ -474,144 +49,27 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.storage %}
|
||||
{% include 'include/recipe_open_modal.html' %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Bookmark Modal -->
|
||||
<div class="modal fade" id="bookmarkModal" tabindex="-1" role="dialog" aria-labelledby="bookmarkModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="bookmarkModalLabel">Modal title</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form method="POST" class="post-form">
|
||||
<div class="modal-body">
|
||||
|
||||
{% csrf_token %}
|
||||
{{ bookmark_form|crispy }}
|
||||
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<input type="submit" value="{% trans 'Save' %}" class="btn btn-success">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'include/log_cooking.html' %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block script %}
|
||||
<script src="{% url 'javascript-catalog' %}"></script>
|
||||
|
||||
{% if debug %}
|
||||
<script src="{% url 'js_reverse' %}"></script>
|
||||
{% else %}
|
||||
<script src="{% static 'django_js_reverse/js/reverse.js' %}"></script>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
let csrftoken = Cookies.get('csrftoken');
|
||||
Vue.http.headers.common['X-CSRFToken'] = csrftoken;
|
||||
|
||||
{% if user_servings %}
|
||||
const recipe_servings = {{ user_servings|floatformat:0 }}
|
||||
{% else %}
|
||||
const recipe_servings = {{ recipe.servings }}
|
||||
{% endif %}
|
||||
|
||||
|
||||
let app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#id_base_container',
|
||||
data: {
|
||||
recipe: undefined,
|
||||
has_ingredients: false,
|
||||
has_times: false,
|
||||
servings: recipe_servings,
|
||||
},
|
||||
computed: {
|
||||
ingredient_factor: function () {
|
||||
return this.servings / recipe_servings
|
||||
},
|
||||
shopping_url: function () {
|
||||
return `{% url 'view_shopping' %}?r=[${this.recipe.id},${this.servings}]`
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
this.loadRecipe()
|
||||
},
|
||||
methods: {
|
||||
loadRecipe: function () {
|
||||
this.$http.get("{% url 'api:recipe-detail' recipe.pk %}" {% if share %}
|
||||
+ "?share={{ share }}"{% endif %}).then((response) => {
|
||||
this.recipe = response.data;
|
||||
this.loading = false;
|
||||
|
||||
for (let step of this.recipe.steps) {
|
||||
if (step.ingredients.length > 0) {
|
||||
this.has_ingredients = true
|
||||
}
|
||||
if (step.time !== 0) {
|
||||
this.has_times = true
|
||||
}
|
||||
this.$set(step, 'time_finished', undefined);
|
||||
for (let i of step.ingredients) {
|
||||
this.$set(i, 'checked', false)
|
||||
}
|
||||
}
|
||||
|
||||
}).catch((err) => {
|
||||
this.error = err.data;
|
||||
this.loading = false;
|
||||
console.log(err)
|
||||
})
|
||||
},
|
||||
roundDecimals: function (num) {
|
||||
let decimals = {% if request.user.userpreference.ingredient_decimals %}
|
||||
{{ request.user.userpreference.ingredient_decimals }} {% else %} 2; {% endif %}
|
||||
return +(Math.round(num + `e+${decimals}`) + `e-${decimals}`);
|
||||
},
|
||||
updateTimes: function (step) {
|
||||
let time_diff_first = 0;
|
||||
for (let s of this.recipe.steps) {
|
||||
if (this.recipe.steps.indexOf(s) < this.recipe.steps.indexOf(step)) {
|
||||
time_diff_first += s.time
|
||||
}
|
||||
}
|
||||
|
||||
this.recipe.steps[0].time_finished = moment(step.time_finished).subtract(time_diff_first, 'minutes').format(moment.HTML5_FMT.DATETIME_LOCAL);
|
||||
|
||||
let time_diff = 0;
|
||||
for (let s of this.recipe.steps) {
|
||||
s.time_finished = moment(this.recipe.steps[0].time_finished).add(time_diff, 'minutes').format(moment.HTML5_FMT.DATETIME_LOCAL);
|
||||
time_diff += s.time
|
||||
}
|
||||
|
||||
},
|
||||
calculateAmount: function (amount) {
|
||||
{% if request.user.userpreference.use_fractions %}
|
||||
let return_string = ''
|
||||
let fraction = frac.cont((amount * this.ingredient_factor), 9, true)
|
||||
|
||||
if (fraction[0] > 0) {
|
||||
return_string += fraction[0]
|
||||
}
|
||||
|
||||
if (fraction[1] > 0) {
|
||||
return_string += ` <sup>${(fraction[1])}</sup>⁄<sub>${(fraction[2])}</sub>`
|
||||
}
|
||||
|
||||
return return_string
|
||||
{% else %}
|
||||
return this.roundDecimals(amount * this.ingredient_factor)
|
||||
{% endif %}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
window.RECIPE_ID = {{recipe.pk}};
|
||||
window.USER_PREF = {
|
||||
'use_fractions': {% if request.user.userpreference.use_fractions %} true {% else %} false {% endif %},
|
||||
'ingredient_decimals': {{ request.user.userpreference.ingredient_decimals }},
|
||||
}
|
||||
</script>
|
||||
|
||||
{% render_bundle 'chunk-vendors' %}
|
||||
{% render_bundle 'recipe_view' %}
|
||||
{% endblock %}
|
@ -1,97 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% load custom_tags %}
|
||||
|
||||
{% block title %}{{ recipe.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% recipe_rating recipe request.user as rating %}
|
||||
<!--
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>{{ recipe.name }} {{ rating|safe }}</h3>
|
||||
|
||||
{% if recipe.storage %}
|
||||
<small>{% trans 'in' %} <a
|
||||
href="{% url 'edit_storage' recipe.storage.pk %}">{{ recipe.storage.name }}</a></small><br/>
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.internal %}
|
||||
<small>{% trans 'by' %} {{ recipe.created_by.get_user_name }}<br/></small>
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.keywords %}
|
||||
{% for x in recipe.keywords.all %}
|
||||
<span class="badge badge-pill badge-light">{{ x }}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div id="app">
|
||||
<recipe-view recipe_id="5"></recipe-view>
|
||||
</div>
|
||||
|
||||
{% if request.user.userpreference.comments %}
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h5 {% if not comments %}class="d-print-none" {% endif %}><i class="far fa-comments"></i> {% trans 'Comments' %}
|
||||
</h5>
|
||||
{% for c in comments %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<small class="card-title">{{ c.updated_at }} {% trans 'by' %} {{ c.created_by.username }}</small> <a
|
||||
href="{% url 'edit_comment' c.pk %}" class="d-print-none"><i
|
||||
class="fas fa-pencil-alt"></i></a><br/>
|
||||
{{ c.text }}
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
{% endfor %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="d-print-none">
|
||||
|
||||
<form method="POST" class="post-form">
|
||||
{% csrf_token %}
|
||||
<div class="input-group mb-3">
|
||||
<textarea name="comment-text" cols="15" rows="2" class="textarea form-control" required
|
||||
id="comment-id_text"></textarea>
|
||||
<div class="input-group-append">
|
||||
<input type="submit" value="{% trans 'Comment' %}" class="btn btn-success">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block script %}
|
||||
<script src="{% url 'javascript-catalog' %}"></script>
|
||||
|
||||
{% if debug %}
|
||||
<script src="{% url 'js_reverse' %}"></script>
|
||||
{% else %}
|
||||
<script src="{% static 'django_js_reverse/js/reverse.js' %}"></script>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<script type="application/javascript">
|
||||
window.RECIPE_ID = {{recipe.pk}};
|
||||
window.USER_PREF = {
|
||||
'use_fractions': {% if request.user.userpreference.use_fractions %} true {% else %} false {% endif %},
|
||||
'ingredient_decimals': {{ request.user.userpreference.ingredient_decimals }},
|
||||
}
|
||||
</script>
|
||||
|
||||
{% render_bundle 'chunk-vendors' %}
|
||||
{% render_bundle 'recipe_view' %}
|
||||
{% endblock %}
|
@ -153,7 +153,6 @@ def recipe_view(request, pk, share=None):
|
||||
)
|
||||
|
||||
comment_form = CommentForm()
|
||||
bookmark_form = RecipeBookEntryForm()
|
||||
|
||||
user_servings = None
|
||||
if request.user.is_authenticated:
|
||||
@ -172,18 +171,7 @@ def recipe_view(request, pk, share=None):
|
||||
.exists():
|
||||
ViewLog.objects.create(recipe=recipe, created_by=request.user)
|
||||
|
||||
return render(
|
||||
request,
|
||||
'recipe_view.html',
|
||||
{
|
||||
'recipe': recipe,
|
||||
'comments': comments,
|
||||
'comment_form': comment_form,
|
||||
'bookmark_form': bookmark_form,
|
||||
'share': share,
|
||||
'user_servings': user_servings
|
||||
}
|
||||
)
|
||||
return render(request, 'recipe_view.html', {'recipe': recipe, 'comments': comments, 'comment_form': comment_form, 'share': share, 'user_servings': user_servings})
|
||||
|
||||
|
||||
@group_required('user')
|
||||
|
@ -1,132 +1,136 @@
|
||||
<template>
|
||||
<div id="app" v-if="!loading">
|
||||
<div id="app">
|
||||
<template v-if="loading">
|
||||
<loading-spinner></loading-spinner>
|
||||
</template>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12" style="text-align: center">
|
||||
<h3>{{ recipe.name }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center">
|
||||
<keywords :recipe="recipe"></keywords>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 8px">
|
||||
<div class="col-12" style="text-align: center">
|
||||
<i>{{ recipe.description }}</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col col-md-3">
|
||||
<div class="row d-flex" style="padding-left: 16px;height: 100%">
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<i class="fas fa-user-clock fa-2x text-primary"></i>
|
||||
</div>
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<span class="text-primary"><b>{{ _('Preparation') }}</b></span><br/>
|
||||
{{ recipe.working_time }} {{ _('min') }}
|
||||
</div>
|
||||
<div v-if="!loading">
|
||||
<div class="row">
|
||||
<div class="col-12" style="text-align: center">
|
||||
<h3>{{ recipe.name }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-md-3">
|
||||
<div class="row d-flex" style="height: 100%">
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<i class="far fa-clock fa-2x text-primary"></i>
|
||||
</div>
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<span class="text-primary"><b>{{ _('Waiting') }}</b></span><br/>
|
||||
{{ recipe.waiting_time }} {{ _('min') }}
|
||||
</div>
|
||||
<div style="text-align: center">
|
||||
<keywords :recipe="recipe"></keywords>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 8px">
|
||||
<div class="col-12" style="text-align: center">
|
||||
<i>{{ recipe.description }}</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-md-4 col-10">
|
||||
<div class="row d-flex" style="padding-left: 16px;height: 100%">
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<i class="fas fa-pizza-slice fa-2x text-primary"></i>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col col-md-3">
|
||||
<div class="row d-flex" style="padding-left: 16px;height: 100%">
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<i class="fas fa-user-clock fa-2x text-primary"></i>
|
||||
</div>
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<span class="text-primary"><b>{{ _('Preparation') }}</b></span><br/>
|
||||
{{ recipe.working_time }} {{ _('min') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<input dir="rtl"
|
||||
</div>
|
||||
|
||||
<div class="col col-md-3">
|
||||
<div class="row d-flex" style="height: 100%">
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<i class="far fa-clock fa-2x text-primary"></i>
|
||||
</div>
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<span class="text-primary"><b>{{ _('Waiting') }}</b></span><br/>
|
||||
{{ recipe.waiting_time }} {{ _('min') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-md-4 col-10">
|
||||
<div class="row d-flex" style="padding-left: 16px;height: 100%">
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<i class="fas fa-pizza-slice fa-2x text-primary"></i>
|
||||
</div>
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<input dir="rtl"
|
||||
style="border-width:0px;border:none; padding:0px; padding-left: 0.5vw; padding-right: 8px; max-width: 80px"
|
||||
value="1" maxlength="3"
|
||||
type="number" class="form-control form-control-lg" v-model.number="servings"/>
|
||||
</div>
|
||||
<div class="my-auto">
|
||||
<b>{{ _('Servings') }}</b>
|
||||
</div>
|
||||
<div class="my-auto">
|
||||
<b>{{ _('Servings') }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-md-2 col-2 my-auto" style="text-align: right; padding-right: 1vw">
|
||||
<recipe-context-menu v-bind:recipe="recipe" :servings="servings"></recipe-context-menu>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
<div class="col col-md-2 col-2 my-auto" style="text-align: right; padding-right: 1vw">
|
||||
<recipe-context-menu v-bind:recipe="recipe" :servings="servings"></recipe-context-menu>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2" v-if="recipe && ingredient_count > 0">
|
||||
|
||||
<div class="card border-primary">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col col-md-8">
|
||||
<h4 class="card-title"><i class="fas fa-pepper-hot"></i> {{ _('Ingredients') }}</h4>
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2" v-if="recipe && ingredient_count > 0"
|
||||
style="margin-top: 2vh">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col col-md-8">
|
||||
<h4 class="card-title"><i class="fas fa-pepper-hot"></i> {{ _('Ingredients') }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-sm">
|
||||
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
|
||||
<template v-for="s in recipe.steps">
|
||||
<template v-for="i in s.ingredients">
|
||||
<Ingredient v-bind:ingredient="i" v-bind:servings="servings" :key="i.id"
|
||||
@checked-state-changed="updateIngredientCheckedState"></Ingredient>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-sm">
|
||||
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
|
||||
<template v-for="s in recipe.steps">
|
||||
<template v-for="i in s.ingredients">
|
||||
<Ingredient v-bind:ingredient="i" v-bind:servings="servings" :key="i.id"
|
||||
@checked-state-changed="updateIngredientCheckedState"></Ingredient>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<!-- eslint-enable vue/no-v-for-template-key-on-child -->
|
||||
</table>
|
||||
<!-- eslint-enable vue/no-v-for-template-key-on-child -->
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 order-1 col-sm-12 order-sm-1 col-md-6 order-md-2">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="col-12 order-1 col-sm-12 order-sm-1 col-md-6 order-md-2">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
|
||||
<img class="img img-fluid rounded" :src="recipe.image" style="max-height: 30vh;"
|
||||
:alt="_( 'Recipe Image')" v-if="recipe.image !== null">
|
||||
<img class="img img-fluid rounded" :src="recipe.image" style="max-height: 30vh;"
|
||||
:alt="_( 'Recipe Image')" v-if="recipe.image !== null">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="col-12">
|
||||
<Nutrition :recipe="recipe" :servings="servings"></Nutrition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="col-12">
|
||||
<Nutrition :recipe="recipe" :servings="servings"></Nutrition>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<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')">
|
||||
<ImageViewer :recipe="recipe"></ImageViewer>
|
||||
</div>
|
||||
|
||||
<div v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
|
||||
<Step :recipe="recipe" :step="s" :servings="servings" :index="index" :start_time="start_time"
|
||||
@update-start-time="updateStartTime" @checked-state-changed="updateIngredientCheckedState"></Step>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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')">
|
||||
<ImageViewer :recipe="recipe"></ImageViewer>
|
||||
</div>
|
||||
|
||||
<div v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
|
||||
<Step :recipe="recipe" :step="s" :servings="servings" :index="index" :start_time="start_time"
|
||||
@update-start-time="updateStartTime" @checked-state-changed="updateIngredientCheckedState"></Step>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -149,6 +153,7 @@ import Nutrition from "@/components/Nutrition";
|
||||
|
||||
import moment from 'moment'
|
||||
import Keywords from "@/components/Keywords";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
@ -168,6 +173,7 @@ export default {
|
||||
RecipeContextMenu,
|
||||
Nutrition,
|
||||
Keywords,
|
||||
LoadingSpinner,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -191,7 +197,7 @@ export default {
|
||||
this.ingredient_count += step.ingredients.length
|
||||
|
||||
for (let ingredient of step.ingredients) {
|
||||
this.$set(ingredient, 'checked', false)
|
||||
this.$set(ingredient, 'checked', false)
|
||||
}
|
||||
|
||||
step.time_offset = total_time
|
||||
|
17
vue/src/components/LoadingSpinner.vue
Normal file
17
vue/src/components/LoadingSpinner.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col" style="text-align: center">
|
||||
<i class="fas fa-spinner fa-spin fa-10x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'LoadingSpinner',
|
||||
props: {
|
||||
recipe: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
@ -1 +1 @@
|
||||
{"status":"done","publicPath":"http://localhost:8080/","chunks":{"chunk-vendors":[{"name":"js/chunk-vendors.js","publicPath":"http://localhost:8080/js/chunk-vendors.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\chunk-vendors.js"}],"recipe_view":[{"name":"js/recipe_view.js","publicPath":"http://localhost:8080/js/recipe_view.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\recipe_view.js"},{"name":"recipe_view.6a32bc3201ad26dbc898.hot-update.js","publicPath":"http://localhost:8080/recipe_view.6a32bc3201ad26dbc898.hot-update.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\recipe_view.6a32bc3201ad26dbc898.hot-update.js"}]},"error":"ModuleError","message":"Module Error (from ./node_modules/eslint-loader/index.js):\n\nF:\\Developement\\Django\\recipes\\vue\\src\\components\\Keywords.vue\n 3:7 error Elements in iteration expect to have 'v-bind:key' directives vue/require-v-for-key\n\n✖ 1 problem (1 error, 0 warnings)\n"}
|
||||
{"status":"done","publicPath":"http://localhost:8080/","chunks":{"chunk-vendors":[{"name":"js/chunk-vendors.js","publicPath":"http://localhost:8080/js/chunk-vendors.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\chunk-vendors.js"}],"recipe_view":[{"name":"js/recipe_view.js","publicPath":"http://localhost:8080/js/recipe_view.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\recipe_view.js"},{"name":"recipe_view.cfc8fcd2004c1b08df3e.hot-update.js","publicPath":"http://localhost:8080/recipe_view.cfc8fcd2004c1b08df3e.hot-update.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\recipe_view.cfc8fcd2004c1b08df3e.hot-update.js"}]},"error":"ModuleError","message":"Module Error (from ./node_modules/eslint-loader/index.js):\n\nF:\\Developement\\Django\\recipes\\vue\\src\\components\\Keywords.vue\n 3:7 error Elements in iteration expect to have 'v-bind:key' directives vue/require-v-for-key\n\n✖ 1 problem (1 error, 0 warnings)\n"}
|
Reference in New Issue
Block a user