This commit is contained in:
vabene1111 2021-09-13 21:24:41 +02:00
parent 6350d0e9c5
commit c1161edc6d
19 changed files with 230 additions and 1038 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.small-padding{padding-left:2px;padding-right:2px;margin-top:2px}

View File

@ -0,0 +1 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Vue App</title><link href="css/chunk-vendors.css" rel="preload" as="style"><link href="css/edit_internal_recipe.css" rel="preload" as="style"><link href="js/chunk-vendors.js" rel="preload" as="script"><link href="js/edit_internal_recipe.js" rel="preload" as="script"><link href="css/chunk-vendors.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-title" content="Recipes"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><div id="app"></div><script src="js/chunk-vendors.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -152,13 +152,6 @@
class="far fa-file-alt fa-fw"></i> {% trans 'Discovered Recipes' %}</a> class="far fa-file-alt fa-fw"></i> {% trans 'Discovered Recipes' %}</a>
<a class="dropdown-item" href="{% url 'list_sync_log' %}"><i <a class="dropdown-item" href="{% url 'list_sync_log' %}"><i
class="fas fa-history fa-fw"></i> {% trans 'Discovery Log' %}</a> class="fas fa-history fa-fw"></i> {% trans 'Discovery Log' %}</a>
<a class="dropdown-item" href="{% url 'data_stats' %}"><i
class="fas fa-chart-line fa-fw"></i> {% trans 'Statistics' %}</a>
{% comment %} <a class="dropdown-item" href="{% url 'edit_food' %}"><i
class="fas fa-balance-scale fa-fw"></i> {% trans 'Units & Ingredients' %}</a> {% endcomment %}
<a class="dropdown-item" href="{% url 'data_import_url' %}"><i
class="fas fa-file-import"></i> {% trans 'Import Recipe' %}</a>
</div> </div>
</li> </li>
</ul> </ul>
@ -183,17 +176,12 @@
<a class="dropdown-item" href="{% url 'view_space' %}"><i <a class="dropdown-item" href="{% url 'view_space' %}"><i
class="fas fa-server fa-fw"></i> {% trans 'Space Settings' %}</a> class="fas fa-server fa-fw"></i> {% trans 'Space Settings' %}</a>
{% endif %} {% endif %}
{% if user.is_superuser %}
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'view_system' %}"><i <a class="dropdown-item" href="{% url 'view_system' %}"><i
class="fas fa-server fa-fw"></i> {% trans 'System' %}</a> class="fas fa-server fa-fw"></i> {% trans 'System' %}</a>
<a class="dropdown-item" href="{% url 'admin:index' %}"><i <a class="dropdown-item" href="{% url 'admin:index' %}"><i
class="fas fa-user-shield fa-fw"></i> {% trans 'Admin' %}</a> class="fas fa-user-shield fa-fw"></i> {% trans 'Admin' %}</a>
{% if user.is_superuser %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'list_keyword' %}"><i
class="fas fa-tags"></i> {% trans 'Keywords' %}</a>
<a class="dropdown-item" href="{% url 'admin:index' %}"><i
class="fas fa-user-shield fa-fw"></i> {% trans 'Admin' %}</a>
{% endif %} {% endif %}
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'docs_markdown' %}"><i <a class="dropdown-item" href="{% url 'docs_markdown' %}"><i

View File

@ -1,38 +0,0 @@
{% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% load static %}
{% load i18n %}
{% load l10n %}
{% block title %}{% trans 'Edit Recipe' %}{% endblock %}
{% block extra_head %}
{% endblock %}
{% block content %}
<div id="app">
<edit-internal-recipe></edit-internal-recipe>
</div>
{% endblock %}
{% block script %}
{% if debug %}
<script src="{% url 'js_reverse' %}"></script>
{% else %}
<script src="{% static 'django_js_reverse/reverse.js' %}"></script>
{% endif %}
<script type="application/javascript">
window.CUSTOM_LOCALE = '{{ request.LANGUAGE_CODE }}'
window.RECIPE_ID = {{ recipe.pk }}
</script>
{% render_bundle 'edit_internal_recipe' %}
{% endblock %}

View File

@ -1,830 +1,38 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load render_bundle from webpack_loader %}
{% load custom_tags %}
{% load theming_tags %}
{% load static %} {% load static %}
{% load i18n %}
{% load l10n %}
{% block title %}{% trans 'Edit Recipe' %}{% endblock %} {% block title %}{% trans 'Edit Recipe' %}{% endblock %}
{% block extra_head %} {% block extra_head %}
{% 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>
<script src="{% static 'js/vue-scrollto.js' %}"></script>
<script src="{% static 'js/portal-vue.umd.min.js' %}"></script>
<style>
.small-padding {
padding-left: 2px;
padding-right: 2px;
margin-top: 2px;
}
</style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h3>{% trans 'Edit Recipe' %}</h3>
<div v-if="!recipe" class="text-center">
<br/>
{% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
<img class="spinner-tandoor"/>
{% else %}
<i class="fas fa-spinner fa-spin fa-8x"></i>
{% endif %}
</div>
<div v-if="recipe">
<div class="row">
<div class="col-md-12">
<label for="id_name"> {% trans 'Name' %}</label>
<input class="form-control" id="id_name" v-model="recipe.name">
</div>
</div>
<div class="row">
<div class="col-12">
<label for="id_description">
{% trans 'Description' %}
</label>
<textarea id="id_description" class="form-control" v-model="recipe.description"
maxlength="512"></textarea>
</div>
</div>
<br/>
<div class="row">
<div class="col-md-6">
<img src="{% if recipe.image %}{{ recipe.image.url }}{% endif %}" id="id_image"
class="img img-fluid img-responsive"
style="max-height: 20vh">
<input class="mt-2" type="file" @change="imageChanged">
</div>
<div class="col-md-6">
<label for="id_name"> {% trans 'Preparation Time' %}</label>
<input class="form-control" id="id_prep_time" v-model="recipe.working_time">
<br/>
<label for="id_name"> {% trans 'Waiting Time' %}</label>
<input class="form-control" id="id_wait_time" v-model="recipe.waiting_time">
<br/>
<label for="id_name"> {% trans 'Servings' %}</label>
<input class="form-control" id="id_servings" v-model="recipe.servings">
<br/>
<label for="id_name"> {% trans 'Servings Text' %}</label>
<input class="form-control" id="id_servings_text" v-model="recipe.servings_text" maxlength="32">
<br/>
<label for="id_name"> {% trans 'Keywords' %}</label>
<multiselect
v-model="recipe.keywords"
:options="keywords"
:close-on-select="false"
:clear-on-select="true"
:hide-selected="true"
:preserve-search="true"
placeholder="{% trans 'Select Keywords' %}"
tag-placeholder="{% trans 'Add Keyword' %}"
:taggable="true"
@tag="addKeyword"
label="label"
track-by="id"
id="id_keywords"
:multiple="true"
:loading="keywords_loading"
@search-change="searchKeywords">
</multiselect>
</div>
</div>
<template v-if="recipe !== undefined">
<div class="row" v-if="recipe.nutrition" style="margin-top: 1vh">
<div class="col-md-12">
<div class="card border-grey">
<div class="card-body">
<h4 class="card-title">{% trans 'Nutrition' %}</h4>
<div class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownMenuLink">
<button class="dropdown-item" @click="removeStep(step)"><i
class="fa fa-trash fa-fw"></i> {% trans 'Delete Step' %}</button>
</div>
<label for="id_name"> {% trans 'Calories' %}</label>
<input class="form-control" id="id_calories" v-model="recipe.nutrition.calories">
<label for="id_name"> {% trans 'Carbohydrates' %}</label>
<input class="form-control" id="id_carbohydrates" v-model="recipe.nutrition.carbohydrates">
<label for="id_name"> {% trans 'Fats' %}</label>
<input class="form-control" id="id_fats" v-model="recipe.nutrition.fats">
<label for="id_name"> {% trans 'Proteins' %}</label>
<input class="form-control" id="id_proteins" v-model="recipe.nutrition.proteins">
<br/>
</div>
</div>
</div>
</div>
</template>
<draggable :list="recipe.steps" group="steps"
:empty-insert-threshold="10" handle=".handle" @sort="sortSteps()">
<div v-for="step, step_index in recipe.steps" style="margin-top: 1vh" class="card">
<div class="card-body" :id="`id_card_step_${step_index}`">
<div class="row">
<div class="col-11">
<h4 class="handle" :id="'id_step_' + step_index">
<i class="fas fa-paragraph" v-if="step.type == 'TEXT'"></i>
<i class="fas fa-clock" v-if="step.type == 'TIME'"></i>
<template v-if="step.name !== ''">[[step.name]]</template>
<template v-else>{% trans 'Step' %} [[step_index+1]]</template>
</h4>
</div>
<div class="col-1" style="text-align: right">
<a class="btn shadow-none btn-lg" href="#" role="button"
id="dropdownMenuLink"
data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
<i class="fas fa-ellipsis-v text-muted"></i>
</a>
<div class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownMenuLink">
<button class="dropdown-item" @click="removeStep(step)"><i
class="fa fa-trash fa-fw"></i> {% trans 'Delete Step' %}</button>
<button type="button" class="dropdown-item"
v-if="!step.show_as_header"
@click="step.show_as_header = true"><i
class="fas fa-eye fa-fw"></i> {% trans 'Show as header' %}
</button>
<button type="button" class="dropdown-item"
v-if="step.show_as_header"
@click="step.show_as_header = false"><i
class="fas fa-eye-slash fa-fw"></i> {% trans 'Hide as header' %}
</button>
<button class="dropdown-item" @click="moveStep(step, (step_index - 1))"
v-if="step_index > 0">
<i class="fa fa-arrow-up fa-fw"></i> {% trans 'Move Up' %}
</button>
<button class="dropdown-item"
@click="moveStep(step, (step_index + 1))"
v-if="step_index != (recipe.steps.length - 1)">
<i class="fa fa-arrow-down fa-fw"></i> {% trans 'Move Down' %}
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8">
<label :for="'id_step_' + step.id + 'name'">{% trans 'Step Name' %}</label>
<input class="form-control" v-model="step.name" :id="'id_step_' + step.id + 'name'">
</div>
<div class="col-md-4">
<label for="id_type"> {% trans 'Step Type' %}</label>
<select class="form-control" id="id_type" v-model="step.type">
<option value="TEXT">{% trans 'Text' %}</option>
<option value="TIME">{% trans 'Time' %}</option>
<option value="FILE">{% trans 'File' %}</option>
<option value="RECIPE">{% trans 'Recipe' %}</option>
</select>
</div>
</div>
<div class="row" style="margin-top: 12px">
<div class="col-md-3">
<label :for="'id_step_' + step.id + '_time'">{% trans 'Step time in Minutes' %}</label>
<input class="form-control" v-model="step.time"
:id="'id_step_' + step.id + '_time'">
</div>
<div class="col-md-9" v-if="step.type === 'FILE'">
<label :for="'id_step_' + step.id + '_file'">{% trans 'File' %}</label>
<multiselect
v-tabindex
ref="file"
v-model="step.file"
:options="files"
:close-on-select="true"
:clear-on-select="true"
:allow-empty="true"
:preserve-search="true"
placeholder="{% trans 'Select File' %}"
select-label="{% trans 'Select' %}"
:id="'id_step_' + step.id + '_file'"
label="name"
track-by="name"
:multiple="false"
:loading="files_loading"
@search-change="searchFiles">
</multiselect>
</div>
<div class="col-md-9" v-if="step.type === 'RECIPE'">
<label :for="'id_step_' + step.id + '_recipe'">{% trans 'Recipe' %}</label>
<multiselect
v-tabindex
ref="step_recipe"
v-model="step.step_recipe"
:options="recipes.map(recipe => recipe.id)"
:close-on-select="true"
:clear-on-select="true"
:allow-empty="true"
:preserve-search="true"
placeholder="{% trans 'Select Recipe' %}"
select-label="{% trans 'Select' %}"
:id="'id_step_' + step.id + '_recipe'"
:custom-label="opt => recipes.find(x => x.id == opt).name"
:multiple="false"
:loading="recipes_loading"
@search-change="searchRecipes">
</multiselect>
</div>
</div>
<template v-if="step.type == 'TEXT'">
<div class="row" style="margin-top: 12px">
<div class="col-md-12">
<div class="jumbotron" style="padding: 16px">
<div class="row">
<div class="col-md-12">
<h4>{% trans 'Ingredients' %}</h4>
</div>
</div>
<div class="row">
<div class="col-md-12" style="margin-top: 8px">
<draggable :list="step.ingredients" group="ingredients"
:empty-insert-threshold="10" handle=".handle"
@sort="sortIngredients(step)">
<div v-for="ingredient, index in step.ingredients"
:key="ingredient.id">
<hr class="d-md-none"/>
<div class="d-flex">
<div class="flex-grow-0 handle align-self-start">
<button type="button" class="btn btn-lg shadow-none"><i
class="fas fa-arrows-alt-v "></i></button>
</div>
<div class="flex-fill row"
style="margin-left: 4px; margin-right: 4px">
<div class="col-lg-2 col-md-6 small-padding"
v-if="!ingredient.is_header">
<input class="form-control"
v-model="ingredient.amount"
type="number" step="any"
v-if="!ingredient.no_amount"
:id="`amount_${step_index}_${index}`">
</div>
<div class="col-lg-2 col-md-6 small-padding"
v-if="!ingredient.is_header">
<multiselect
v-if="!ingredient.no_amount"
v-tabindex
ref="unit"
v-model="ingredient.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"
:id="`unit_${step_index}_${index}`"
label="name"
track-by="name"
:multiple="false"
:loading="units_loading"
@search-change="searchUnits">
</multiselect>
</div>
<div class="col-lg-4 col-md-6 small-padding"
v-if="!ingredient.is_header">
<multiselect
v-tabindex
ref="food"
v-model="ingredient.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"
:id="`ingredient_${step_index}_${index}`"
label="name"
track-by="name"
:multiple="false"
:loading="foods_loading"
@search-change="searchFoods">
</multiselect>
</div>
<div class="small-padding"
v-bind:class="{ 'col-lg-4 col-md-6': !ingredient.is_header, 'col-lg-12 col-md-12': ingredient.is_header }">
<input class="form-control"
v-model="ingredient.note"
placeholder="{% trans 'Note' %}">
</div>
</div>
<div class="flex-grow-0 small-padding">
<a class="btn shadow-none btn-lg" href="#" role="button"
id="dropdownMenuLink"
data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
<i class="fas fa-ellipsis-v text-muted"></i>
</a>
<div class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownMenuLink">
<button type="button" class="dropdown-item"
@click="removeIngredient(step, ingredient)">
<i
class="fa fa-trash fa-fw"></i> {% trans 'Delete Ingredient' %}
</button>
<button type="button" class="dropdown-item"
v-if="!ingredient.is_header "
@click="ingredient.is_header = true"><i
class="fas fa-heading fa-fw"></i> {% trans 'Make Header' %}
</button>
<button type="button" class="dropdown-item"
v-if="ingredient.is_header "
@click="ingredient.is_header = false"><i
class="fas fa-leaf fa-fw"></i> {% trans 'Make Ingredient' %}
</button>
<button type="button" class="dropdown-item"
v-if="!ingredient.no_amount "
@click="ingredient.no_amount = true"><i
class="fas fa-balance-scale-right fa-fw"></i> {% trans 'Disable Amount' %}
</button>
<button type="button" class="dropdown-item"
v-if="ingredient.no_amount "
@click="ingredient.no_amount = false"><i
class="fas fa-balance-scale-right fa-fw"></i> {% trans 'Enable Amount' %}
</button>
<button type="button" class="dropdown-item"
@click="copyTemplateReference(index, ingredient)">
<i
class="fas fa-code"></i> {% trans 'Copy Template Reference' %}
</button>
</div>
</div>
</div>
</div>
</draggable>
</div>
</div>
<div class="row">
<div class="col-md-2 offset-md-5"
style="text-align: center; margin-top: 8px;">
<button class="btn btn-success btn-block" @click="addIngredient(step)"><i
class="fa fa-plus"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<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="20" v-model="step.instruction"
:id="'id_instruction_' + step.id"></b-form-textarea>
{% markdown_link as markdown_link %}
<small class="text-muted">{{ markdown_link|safe }}</small>
</div>
</div>
</div>
</div>
</draggable>
<div class="row" style="margin-top: 1vh; margin-bottom: 8vh" v-if="recipe !== undefined">
<div class="col-12">
<button type="button" @click="updateRecipe(true)"
class="btn btn-success shadow-none">{% trans 'Save & View' %}</button>
<button type="button" @click="updateRecipe(false)"
class="btn btn-info shadow-none">{% trans 'Save' %}</button>
<button type="button" @click="addStep()"
class="btn btn-primary shadow-none">{% trans 'Add Step' %}</button>
<button type="button" @click="addNutrition()"
class="btn btn-primary shadow-none"
v-if="recipe.nutrition === null">{% trans 'Add Nutrition' %}</button>
<button type="button" @click="removeNutrition()" v-if="recipe.nutrition !== null"
class="btn btn-warning shadow-none">{% trans 'Remove Nutrition' %}</button>
<a href="{% url 'view_recipe' recipe.pk %}" @click="addStep()"
class="btn btn-secondary shadow-none">{% trans 'View Recipe' %}</a>
<a href="{% url 'delete_recipe' recipe.pk %}"
class="btn btn-danger shadow-none">{% trans 'Delete Recipe' %}</a>
</div>
</div>
<div id="app">
<edit-internal-recipe></edit-internal-recipe>
</div> </div>
{% endblock %} {% endblock %}
{% block content_xl_right %}
<div class="sticky-top" style="top: 2vh; z-index: 100;" v-if="recipe !== undefined">
<br/>
<br/>
<br/>
<div class="row">
<div class="col-md-11">
<button type="button" @click="updateRecipe(true)"
class="btn btn-success btn-block shadow-none">{% trans 'Save & View' %}</button>
<button type="button" @click="updateRecipe(false)"
class="btn btn-info btn-block shadow-none">{% trans 'Save' %}</button>
<button type="button" @click="addStep()"
class="btn btn-primary btn-block shadow-none">{% trans 'Add Step' %}</button>
<button type="button" @click="addNutrition()"
class="btn btn-primary btn-block shadow-none"
v-if="recipe.nutrition === null">{% trans 'Add Nutrition' %}</button>
<button type="button" @click="removeNutrition()" v-if="recipe.nutrition !== null"
class="btn btn-warning btn-block shadow-none">{% trans 'Remove Nutrition' %}</button>
<a href="{% url 'view_recipe' recipe.pk %}"
class="btn btn-secondary btn-block shadow-none">{% trans 'View Recipe' %}</a>
<a href="{% url 'delete_recipe' recipe.pk %}"
class="btn btn-danger btn-block shadow-none">{% trans 'Delete Recipe' %}</a>
</div>
</div>
<br/>
<div class="row" v-if="recipe !== undefined">
<div class="col-md-11">
<h5><i class="fas fa-sort-amount-down-alt"></i> {% trans 'Steps' %}</h5>
<draggable :list="recipe.steps" group="steps_sorter"
:empty-insert-threshold="10" handle=".handle" @sort="sortSteps()" class="list-group"
style="margin-top: 1vh">
<div v-for="step, step_index in recipe.steps" class="list-group-item">
<div class="d-flex justify-content-center align-items-center">
<div class="flex-grow-0 text-muted">
<i class="fas fa-paragraph fa-fw" v-if="step.type == 'TEXT'"></i>
<i class="fas fa-clock fa-fw" v-if="step.type == 'TIME'"></i>
</div>
<div class="flex-fill" style="padding-left: 4px">
<a href="#" v-scroll-to="'#id_step_' + step_index">
<template v-if="step.name">[[step.name]]</template>
<template v-else>{% trans 'Step' %} [[step_index + 1]]</template>
</a>
</div>
<div class="handle flex-grow-0 align-content-end">
<i class="fas fa-sort "></i>
</div>
</div>
</div>
</draggable>
</div>
</div>
</div>
{% endblock %}
{% block script %} {% block script %}
{% if debug %}
<script src="{% url 'js_reverse' %}"></script>
{% else %}
<script src="{% static 'django_js_reverse/reverse.js' %}"></script>
{% endif %}
<script src="{% url 'javascript-catalog' %}"></script>
<script type="application/javascript"> <script type="application/javascript">
let csrftoken = Cookies.get('csrftoken');
Vue.http.headers.common['X-CSRFToken'] = csrftoken;
Vue.component('vue-multiselect', window.VueMultiselect.default) window.CUSTOM_LOCALE = '{{ request.LANGUAGE_CODE }}'
window.RECIPE_ID = {{ recipe.pk }}
VueScrollTo.setDefaults({
offset: -40,
})
let app = new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
delimiters: ['[[', ']]'],
el: '#id_base_container',
data: {
recipe: undefined,
recipe_changed: undefined,
keywords: [],
keywords_loading: false,
foods: [],
foods_loading: false,
units: [],
units_loading: false,
files: [],
files_loading: false,
recipes: [],
recipes_loading: false,
message: '',
},
directives: {
tabindex: {
inserted(el) {
el.setAttribute('tabindex', 0);
}
}
},
watch: {
recipe: {
deep: true,
handler() {
this.recipe_changed = this.recipe_changed !== undefined;
}
}
},
created() {
window.addEventListener('beforeunload', this.warnPageLeave)
},
mounted: function () {
this.loadRecipe()
this.searchUnits('')
this.searchFoods('')
this.searchKeywords('')
this.searchFiles('')
this.searchRecipes('')
this._keyListener = function (e) {
if (e.code === "Space" && e.ctrlKey) {
e.preventDefault(); // present "Save Page" from getting triggered.
for (el of e.path) {
if (el.id !== undefined && el.id.includes('id_card_step_')) {
let step = this.recipe.steps[el.id.replace('id_card_step_', '')]
this.addIngredient(step)
}
}
}
};
document.addEventListener('keydown', this._keyListener.bind(this));
},
beforeDestroy() {
document.removeEventListener('keydown', this._keyListener);
},
methods: {
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
})
},
warnPageLeave: function (event) {
if (this.recipe_changed) {
event.returnValue = ''
return ''
}
},
loadRecipe: function () {
this.$http.get("{% url 'api:recipe-detail' recipe.pk %}").then((response) => {
this.recipe = response.data;
this.loading = false
//TODO workaround function until view is properly refactored, loads name of selected sub recipe so the input can find its label
this.recipe.steps.forEach(s => {
if (s.step_recipe != null) {
this.recipes.push(s.step_recipe_data)
}
})
}).catch((err) => {
this.loading = false
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading the recipe!') + err.bodyText, 'danger')
})
},
updateRecipe: function (view_after) {
this.sortSteps()
for (let s of this.recipe.steps) {
this.sortIngredients(s)
}
this.$http.put("{% url 'api:recipe-detail' recipe.pk %}", this.recipe,
{}).then((response) => {
console.log(response)
this.makeToast(gettext('Updated'), gettext('Changes saved successfully!'), 'success')
this.recipe_changed = false
if (view_after) {
location.href = "{% url 'view_recipe' 12345 %}".replace(/12345/, this.recipe.id);
}
}).catch((err) => {
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error updating the recipe!') + err.bodyText, 'danger')
})
},
imageChanged: function (event) {
if (event.target.files && event.target.files[0]) {
let fd = new FormData()
fd.append('image', event.target.files[0])
this.$http.put("{% url 'api:recipe-detail' recipe.pk %}" + 'image/', fd,
{headers: {'Content-Type': 'multipart/form-data'}}).then((response) => {
console.log(response)
this.makeToast(gettext('Updated'), gettext('Changes saved successfully!'), 'success')
}).catch((err) => {
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error updating the recipe!') + err.bodyText, 'danger')
})
let reader = new FileReader();
reader.onload = function (e) {
$('#id_image').attr('src', e.target.result);
}
reader.readAsDataURL(event.target.files[0]);
}
},
addStep: function () { //TODO see if default can be generated from options request
this.recipe.steps.push(
{'instruction': '', ingredients: [], type: 'TEXT', show_as_header: true}
)
},
sortSteps: function () {
this.recipe.steps.forEach(function (element, index) {
element.order = index
});
},
sortIngredients: function (step) {
step.ingredients.forEach(function (element, index) {
element.order = index
});
},
addIngredient: function (step) { //TODO see if default can be generated from options request
step.ingredients.push({
'food': null,
'unit': {
'name': '{{request.user.userpreference.default_unit}}'
},
'amount': 0,
'note': '',
'order': 0,
'is_header': false,
'no_amount': false
})
this.sortIngredients(step)
this.$nextTick(() => document.getElementById(`amount_${this.recipe.steps.indexOf(step)}_${step.ingredients.length - 1}`).focus())
},
removeIngredient: function (step, ingredient) {
if (confirm(gettext('Are you sure that you want to delete this ingredient?'))) {
step.ingredients = step.ingredients.filter(item => item !== ingredient)
}
},
removeStep: function (step) {
if (confirm(gettext('Are you sure that you want to delete this step?'))) {
this.recipe.steps = this.recipe.steps.filter(item => item !== step)
}
},
moveStep: function (step, new_index) {
this.recipe.steps.splice(this.recipe.steps.indexOf(step), 1);
this.recipe.steps.splice((new_index < 0 ? 0 : new_index), 0, step);
this.sortSteps()
},
addFoodType: function (tag, index) {
let [tmp, step, id] = index.split('_')
let new_food = this.recipe.steps[step].ingredients[id]
new_food.food = {'name': tag}
this.foods.push(new_food.food)
this.recipe.steps[step].ingredients[id] = new_food
},
addUnitType: function (tag, index) {
let [tmp, step, id] = index.split('_')
let new_unit = this.recipe.steps[step].ingredients[id]
new_unit.unit = {'name': tag}
this.units.push(new_unit.unit)
this.recipe.steps[step].ingredients[id] = new_unit
},
addKeyword: function (tag) {
let new_keyword = {'label': tag, 'name': tag}
this.recipe.keywords.push(new_keyword)
},
searchKeywords: function (query) {
this.keywords_loading = true
this.$http.get("{% url 'api:keyword-list' %}" + '?query=' + query + '&limit=10').then((response) => {
this.keywords = response.data.results;
this.keywords_loading = false
}).catch((err) => {
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
searchFiles: function (query) {
this.files_loading = true
this.$http.get("{% url 'api:userfile-list' %}" + '?query=' + query + '&limit=10').then((response) => {
this.files = response.data
this.files_loading = false
}).catch((err) => {
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
searchRecipes: function (query) {
this.recipes_loading = true
this.$http.get("{% url 'api:recipe-list' %}" + '?query=' + query + '&limit=10').then((response) => {
this.recipes = response.data.results
this.recipes_loading = false
}).catch((err) => {
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
searchUnits: function (query) {
this.units_loading = true
this.$http.get("{% url 'api:unit-list' %}" + '?query=' + query + '&limit=10').then((response) => {
this.units = response.data.results;
if (this.recipe !== undefined) {
for (let s of this.recipe.steps) {
for (let i of s.ingredients) {
if (i.unit !== null && i.unit.id === undefined) {
this.units.push(i.unit)
}
}
}
}
this.units_loading = false
}).catch((err) => {
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
searchFoods: function (query) {
this.foods_loading = true
this.$http.get("{% url 'api:food-list' %}" + '?query=' + query + '&limit=10').then((response) => {
this.foods = response.data.results
if (this.recipe !== undefined) {
for (let s of this.recipe.steps) {
for (let i of s.ingredients) {
if (i.food !== null && i.food.id === undefined) {
this.foods.push(i.food)
}
}
}
}
this.foods_loading = false
}).catch((err) => {
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
scrollToStep: function (step_index) {
document.getElementById('id_step_' + step_index).scrollIntoView({behavior: 'smooth'});
},
addNutrition: function () {
this.recipe.nutrition = {}
},
removeNutrition: function () {
this.recipe.nutrition = null
},
copyTemplateReference: function (index, ingredient) {
const el = document.createElement('textarea');
let tag = `\u007B\u007B ingredients[${index}] \u007D\u007D`;
if (ingredient.food !== null) {
tag += `\u007B# ${ingredient.food.name} #\u007D`
}
el.value = tag
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
}
});
</script> </script>
{% render_bundle 'edit_internal_recipe' %}
{% endblock %} {% endblock %}

File diff suppressed because one or more lines are too long

View File

@ -56,9 +56,7 @@ def internal_recipe_update(request, pk):
recipe_instance = get_object_or_404(Recipe, pk=pk, space=request.space) recipe_instance = get_object_or_404(Recipe, pk=pk, space=request.space)
return render( return render(request, 'forms/edit_internal_recipe.html', {'recipe': recipe_instance})
request, 'edit_internal_recipe_v2.html', {'recipe': recipe_instance}
)
class SpaceFormMixing(FormMixin): class SpaceFormMixing(FormMixin):

View File

@ -1,130 +1,155 @@
{ {
"status": "done", "status": "done",
"assets": { "assets": {
"../../templates/sw.js": {
"name": "../../templates/sw.js",
"path": "..\\..\\templates\\sw.js"
},
"css/chunk-vendors.css": {
"name": "css/chunk-vendors.css",
"path": "css\\chunk-vendors.css"
},
"js/chunk-vendors.js": { "js/chunk-vendors.js": {
"name": "js/chunk-vendors.js", "name": "js/chunk-vendors.js",
"path": "js\\chunk-vendors.js", "path": "js\\chunk-vendors.js"
"publicPath": "http://localhost:8080/js/chunk-vendors.js" },
"css/cookbook_view.css": {
"name": "css/cookbook_view.css",
"path": "css\\cookbook_view.css"
},
"js/cookbook_view.js": {
"name": "js/cookbook_view.js",
"path": "js\\cookbook_view.js"
},
"css/edit_internal_recipe.css": {
"name": "css/edit_internal_recipe.css",
"path": "css\\edit_internal_recipe.css"
}, },
"js/edit_internal_recipe.js": { "js/edit_internal_recipe.js": {
"name": "js/edit_internal_recipe.js", "name": "js/edit_internal_recipe.js",
"path": "js\\edit_internal_recipe.js", "path": "js\\edit_internal_recipe.js"
"publicPath": "http://localhost:8080/js/edit_internal_recipe.js"
}, },
"js/import_response_view.js": { "js/import_response_view.js": {
"name": "js/import_response_view.js", "name": "js/import_response_view.js",
"path": "js\\import_response_view.js", "path": "js\\import_response_view.js"
"publicPath": "http://localhost:8080/js/import_response_view.js" },
"css/model_list_view.css": {
"name": "css/model_list_view.css",
"path": "css\\model_list_view.css"
}, },
"js/model_list_view.js": { "js/model_list_view.js": {
"name": "js/model_list_view.js", "name": "js/model_list_view.js",
"path": "js\\model_list_view.js", "path": "js\\model_list_view.js"
"publicPath": "http://localhost:8080/js/model_list_view.js"
}, },
"js/offline_view.js": { "js/offline_view.js": {
"name": "js/offline_view.js", "name": "js/offline_view.js",
"path": "js\\offline_view.js", "path": "js\\offline_view.js"
"publicPath": "http://localhost:8080/js/offline_view.js"
}, },
"js/recipe_search_view.js": { "js/recipe_search_view.js": {
"name": "js/recipe_search_view.js", "name": "js/recipe_search_view.js",
"path": "js\\recipe_search_view.js", "path": "js\\recipe_search_view.js"
"publicPath": "http://localhost:8080/js/recipe_search_view.js"
}, },
"js/recipe_view.js": { "js/recipe_view.js": {
"name": "js/recipe_view.js", "name": "js/recipe_view.js",
"path": "js\\recipe_view.js", "path": "js\\recipe_view.js"
"publicPath": "http://localhost:8080/js/recipe_view.js"
}, },
"js/supermarket_view.js": { "js/supermarket_view.js": {
"name": "js/supermarket_view.js", "name": "js/supermarket_view.js",
"path": "js\\supermarket_view.js", "path": "js\\supermarket_view.js"
"publicPath": "http://localhost:8080/js/supermarket_view.js"
}, },
"js/user_file_view.js": { "js/user_file_view.js": {
"name": "js/user_file_view.js", "name": "js/user_file_view.js",
"path": "js\\user_file_view.js", "path": "js\\user_file_view.js"
"publicPath": "http://localhost:8080/js/user_file_view.js"
}, },
"recipe_search_view.html": { "recipe_search_view.html": {
"name": "recipe_search_view.html", "name": "recipe_search_view.html",
"path": "recipe_search_view.html", "path": "recipe_search_view.html"
"publicPath": "http://localhost:8080/recipe_search_view.html"
}, },
"recipe_view.html": { "recipe_view.html": {
"name": "recipe_view.html", "name": "recipe_view.html",
"path": "recipe_view.html", "path": "recipe_view.html"
"publicPath": "http://localhost:8080/recipe_view.html"
}, },
"offline_view.html": { "offline_view.html": {
"name": "offline_view.html", "name": "offline_view.html",
"path": "offline_view.html", "path": "offline_view.html"
"publicPath": "http://localhost:8080/offline_view.html"
}, },
"import_response_view.html": { "import_response_view.html": {
"name": "import_response_view.html", "name": "import_response_view.html",
"path": "import_response_view.html", "path": "import_response_view.html"
"publicPath": "http://localhost:8080/import_response_view.html"
}, },
"supermarket_view.html": { "supermarket_view.html": {
"name": "supermarket_view.html", "name": "supermarket_view.html",
"path": "supermarket_view.html", "path": "supermarket_view.html"
"publicPath": "http://localhost:8080/supermarket_view.html"
}, },
"user_file_view.html": { "user_file_view.html": {
"name": "user_file_view.html", "name": "user_file_view.html",
"path": "user_file_view.html", "path": "user_file_view.html"
"publicPath": "http://localhost:8080/user_file_view.html"
}, },
"model_list_view.html": { "model_list_view.html": {
"name": "model_list_view.html", "name": "model_list_view.html",
"path": "model_list_view.html", "path": "model_list_view.html"
"publicPath": "http://localhost:8080/model_list_view.html"
}, },
"edit_internal_recipe.html": { "edit_internal_recipe.html": {
"name": "edit_internal_recipe.html", "name": "edit_internal_recipe.html",
"path": "edit_internal_recipe.html", "path": "edit_internal_recipe.html"
"publicPath": "http://localhost:8080/edit_internal_recipe.html" },
"cookbook_view.html": {
"name": "cookbook_view.html",
"path": "cookbook_view.html"
}, },
"manifest.json": { "manifest.json": {
"name": "manifest.json", "name": "manifest.json",
"path": "manifest.json", "path": "manifest.json"
"publicPath": "http://localhost:8080/manifest.json"
} }
}, },
"chunks": { "chunks": {
"recipe_search_view": [ "recipe_search_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"js/recipe_search_view.js" "js/recipe_search_view.js"
], ],
"recipe_view": [ "recipe_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"js/recipe_view.js" "js/recipe_view.js"
], ],
"offline_view": [ "offline_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"js/offline_view.js" "js/offline_view.js"
], ],
"import_response_view": [ "import_response_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"js/import_response_view.js" "js/import_response_view.js"
], ],
"supermarket_view": [ "supermarket_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"js/supermarket_view.js" "js/supermarket_view.js"
], ],
"user_file_view": [ "user_file_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"js/user_file_view.js" "js/user_file_view.js"
], ],
"model_list_view": [ "model_list_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"css/model_list_view.css",
"js/model_list_view.js" "js/model_list_view.js"
], ],
"edit_internal_recipe": [ "edit_internal_recipe": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"css/edit_internal_recipe.css",
"js/edit_internal_recipe.js" "js/edit_internal_recipe.js"
],
"cookbook_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js",
"css/cookbook_view.css",
"js/cookbook_view.js"
] ]
}, }
"publicPath": "http://localhost:8080/"
} }