Merge branch 'develop'
This commit is contained in:
2
.github/workflows/build-docker-open-data.yml
vendored
2
.github/workflows/build-docker-open-data.yml
vendored
@ -55,7 +55,7 @@ jobs:
|
|||||||
# Build Vue frontend
|
# Build Vue frontend
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
node-version: '18'
|
||||||
cache: yarn
|
cache: yarn
|
||||||
cache-dependency-path: vue/yarn.lock
|
cache-dependency-path: vue/yarn.lock
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
2
.github/workflows/build-docker.yml
vendored
2
.github/workflows/build-docker.yml
vendored
@ -53,7 +53,7 @@ jobs:
|
|||||||
# Build Vue frontend
|
# Build Vue frontend
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
node-version: '18'
|
||||||
cache: yarn
|
cache: yarn
|
||||||
cache-dependency-path: vue/yarn.lock
|
cache-dependency-path: vue/yarn.lock
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
# Build Vue frontend
|
# Build Vue frontend
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '18'
|
||||||
- name: Install Vue dependencies
|
- name: Install Vue dependencies
|
||||||
working-directory: ./vue
|
working-directory: ./vue
|
||||||
run: yarn install
|
run: yarn install
|
||||||
|
@ -409,7 +409,7 @@ def parse_keywords(keyword_json, space):
|
|||||||
# retrieve keyword automation cache if it exists, otherwise build from database
|
# retrieve keyword automation cache if it exists, otherwise build from database
|
||||||
KEYWORD_CACHE_KEY = f'automation_keyword_alias_{space.pk}'
|
KEYWORD_CACHE_KEY = f'automation_keyword_alias_{space.pk}'
|
||||||
if c := caches['default'].get(KEYWORD_CACHE_KEY, None):
|
if c := caches['default'].get(KEYWORD_CACHE_KEY, None):
|
||||||
self.food_aliases = c
|
keyword_aliases = c
|
||||||
caches['default'].touch(KEYWORD_CACHE_KEY, 30)
|
caches['default'].touch(KEYWORD_CACHE_KEY, 30)
|
||||||
else:
|
else:
|
||||||
for a in Automation.objects.filter(space=space, disabled=False, type=Automation.KEYWORD_ALIAS).only('param_1', 'param_2').order_by('order').all():
|
for a in Automation.objects.filter(space=space, disabled=False, type=Automation.KEYWORD_ALIAS).only('param_1', 'param_2').order_by('order').all():
|
||||||
|
@ -15,10 +15,10 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-05-18 14:28+0200\n"
|
"POT-Creation-Date: 2023-05-18 14:28+0200\n"
|
||||||
"PO-Revision-Date: 2023-02-09 13:55+0000\n"
|
"PO-Revision-Date: 2023-06-21 14:19+0000\n"
|
||||||
"Last-Translator: Marion Kämpfer <marion@murphyslantech.de>\n"
|
"Last-Translator: Tobias Huppertz <tobias.huppertz@mail.de>\n"
|
||||||
"Language-Team: German <http://translate.tandoor.dev/projects/tandoor/recipes-"
|
"Language-Team: German <http://translate.tandoor.dev/projects/tandoor/"
|
||||||
"backend/de/>\n"
|
"recipes-backend/de/>\n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@ -600,9 +600,8 @@ msgid "Imported %s recipes."
|
|||||||
msgstr "%s Rezepte importiert."
|
msgstr "%s Rezepte importiert."
|
||||||
|
|
||||||
#: .\cookbook\integration\openeats.py:26
|
#: .\cookbook\integration\openeats.py:26
|
||||||
#, fuzzy
|
|
||||||
msgid "Recipe source:"
|
msgid "Recipe source:"
|
||||||
msgstr "Rezept-Hauptseite"
|
msgstr "Rezept-Quelle:"
|
||||||
|
|
||||||
#: .\cookbook\integration\paprika.py:49
|
#: .\cookbook\integration\paprika.py:49
|
||||||
msgid "Notes"
|
msgid "Notes"
|
||||||
|
18
cookbook/migrations/0193_space_internal_note.py
Normal file
18
cookbook/migrations/0193_space_internal_note.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.1.9 on 2023-06-21 13:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0192_food_food_unique_open_data_slug_per_space_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='space',
|
||||||
|
name='internal_note',
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -270,6 +270,8 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
|||||||
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
|
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
|
||||||
show_facet_count = models.BooleanField(default=False)
|
show_facet_count = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
internal_note = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
def safe_delete(self):
|
def safe_delete(self):
|
||||||
"""
|
"""
|
||||||
Safely deletes a space by deleting all objects belonging to the space first and then deleting the space itself
|
Safely deletes a space by deleting all objects belonging to the space first and then deleting the space itself
|
||||||
|
@ -118,6 +118,10 @@
|
|||||||
<a class="nav-link" href="{% url 'view_books' %}"><i
|
<a class="nav-link" href="{% url 'view_books' %}"><i
|
||||||
class="fas fa-fw fa-book-open"></i> {% trans 'Books' %}</a>
|
class="fas fa-fw fa-book-open"></i> {% trans 'Books' %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% plugin_main_nav_templates as plugin_main_nav_templates %}
|
||||||
|
{% for pn in plugin_main_nav_templates %}
|
||||||
|
{% include pn %}
|
||||||
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
|
@ -140,6 +140,14 @@ def plugin_dropdown_nav_templates():
|
|||||||
templates.append(p['nav_dropdown'])
|
templates.append(p['nav_dropdown'])
|
||||||
return templates
|
return templates
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def plugin_main_nav_templates():
|
||||||
|
templates = []
|
||||||
|
for p in PLUGINS:
|
||||||
|
if p['nav_main']:
|
||||||
|
templates.append(p['nav_main'])
|
||||||
|
return templates
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def bookmarklet(request):
|
def bookmarklet(request):
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
"vue-multiselect": "^2.1.6",
|
"vue-multiselect": "^2.1.6",
|
||||||
"vue-property-decorator": "^9.1.2",
|
"vue-property-decorator": "^9.1.2",
|
||||||
"vue-sanitize": "^0.2.2",
|
"vue-sanitize": "^0.2.2",
|
||||||
"vue-simple-calendar": "5.0.1",
|
"vue-simple-calendar": "TandoorRecipes/vue-simple-calendar#lastvue2",
|
||||||
"vue-template-compiler": "2.7.14",
|
"vue-template-compiler": "2.7.14",
|
||||||
"vue2-touch-events": "^3.2.2",
|
"vue2-touch-events": "^3.2.2",
|
||||||
"vuedraggable": "^2.24.3",
|
"vuedraggable": "^2.24.3",
|
||||||
|
@ -1,157 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<template v-if="loading">
|
<recipe-view-component></recipe-view-component>
|
||||||
<loading-spinner></loading-spinner>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-if="!loading" style="padding-bottom: 60px">
|
|
||||||
<RecipeSwitcher ref="ref_recipe_switcher" @switch="quickSwitch($event)"/>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12" style="text-align: center">
|
|
||||||
<h3>{{ recipe.name }}</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row text-center">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
<recipe-rating :recipe="recipe"></recipe-rating>
|
|
||||||
<last-cooked :recipe="recipe" class="mt-2"></last-cooked>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-auto">
|
|
||||||
<div class="col-12" style="text-align: center">
|
|
||||||
<i>{{ recipe.description }}</i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="text-align: center">
|
|
||||||
<keywords-component :recipe="recipe"></keywords-component>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
<div class="row align-items-center">
|
|
||||||
<div class="col col-md-3">
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="my-auto mr-1">
|
|
||||||
<i class="fas fa-fw fa-user-clock fa-2x text-primary"></i>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto mr-1">
|
|
||||||
<span class="text-primary"><b>{{ $t("Preparation") }}</b></span><br/>
|
|
||||||
{{ working_time }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col col-md-3">
|
|
||||||
<div class="row d-flex">
|
|
||||||
<div class="my-auto mr-1">
|
|
||||||
<i class="far fa-fw fa-clock fa-2x text-primary"></i>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto mr-1">
|
|
||||||
<span class="text-primary"><b>{{ $t("Waiting") }}</b></span><br/>
|
|
||||||
{{ waiting_time }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col col-md-4 col-10 mt-2 mt-md-0">
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="my-auto mr-1">
|
|
||||||
<i class="fas fa-fw fa-pizza-slice fa-2x text-primary"></i>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto mr-1">
|
|
||||||
<CustomInputSpinButton v-model.number="servings"/>
|
|
||||||
</div>
|
|
||||||
<div class="my-auto mr-1">
|
|
||||||
<span class="text-primary">
|
|
||||||
<b>
|
|
||||||
<template v-if="recipe.servings_text === ''">{{ $t("Servings") }}</template>
|
|
||||||
<template v-else>{{ recipe.servings_text }}</template>
|
|
||||||
</b>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col col-md-2 col-2 mt-2 mt-md-0 text-right">
|
|
||||||
<recipe-context-menu v-bind:recipe="recipe" :servings="servings"
|
|
||||||
:disabled_options="{print:false}"></recipe-context-menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2"
|
|
||||||
v-if="recipe && ingredient_count > 0 && (recipe.show_ingredient_overview || recipe.steps.length < 2)">
|
|
||||||
<ingredients-card
|
|
||||||
:recipe="recipe.id"
|
|
||||||
:steps="recipe.steps"
|
|
||||||
:ingredient_factor="ingredient_factor"
|
|
||||||
:servings="servings"
|
|
||||||
:header="true"
|
|
||||||
id="ingredient_container"
|
|
||||||
@checked-state-changed="updateIngredientCheckedState"
|
|
||||||
@change-servings="servings = $event"
|
|
||||||
/>
|
|
||||||
</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">
|
|
||||||
<img class="img img-fluid rounded" :src="recipe.image" :alt="$t('Recipe_Image')"
|
|
||||||
v-if="recipe.image !== null" @load="onImgLoad"
|
|
||||||
:style="{ 'max-height': ingredient_height }"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-if="!recipe.internal">
|
|
||||||
<div v-if="recipe.file_path.includes('.pdf')">
|
|
||||||
<PdfViewer :recipe="recipe"></PdfViewer>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="recipe.file_path.includes('.png') || recipe.file_path.includes('.jpg') || recipe.file_path.includes('.jpeg') || recipe.file_path.includes('.gif')">
|
|
||||||
<ImageViewer :recipe="recipe"></ImageViewer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
|
|
||||||
<step-component
|
|
||||||
:recipe="recipe"
|
|
||||||
:step="s"
|
|
||||||
:ingredient_factor="ingredient_factor"
|
|
||||||
:index="index"
|
|
||||||
:start_time="start_time"
|
|
||||||
@update-start-time="updateStartTime"
|
|
||||||
@checked-state-changed="updateIngredientCheckedState"
|
|
||||||
></step-component>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="recipe.source_url !== null">
|
|
||||||
<h6 class="d-print-none"><i class="fas fa-file-import"></i> {{ $t("Imported_From") }}</h6>
|
|
||||||
<span class="text-muted mt-1"><a style="overflow-wrap: break-word;"
|
|
||||||
:href="recipe.source_url">{{ recipe.source_url }}</a></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row" style="margin-top: 2vh; ">
|
|
||||||
<div class="col-lg-6 offset-lg-3 col-12">
|
|
||||||
<property-view-component :recipe="recipe" :servings="servings" @foodUpdated="loadRecipe(recipe.id)"></property-view-component>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
|
|
||||||
|
|
||||||
<div class="row text-center d-print-none" style="margin-top: 3vh; margin-bottom: 3vh"
|
|
||||||
v-if="share_uid !== 'None' && !loading">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
<import-tandoor></import-tandoor> <br/>
|
|
||||||
<a :href="resolveDjangoUrl('view_report_share_abuse', share_uid)" class="mt-3">{{ $t("Report Abuse") }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<bottom-navigation-bar></bottom-navigation-bar>
|
<bottom-navigation-bar></bottom-navigation-bar>
|
||||||
</div>
|
</div>
|
||||||
@ -162,194 +11,28 @@ import Vue from "vue"
|
|||||||
import {BootstrapVue} from "bootstrap-vue"
|
import {BootstrapVue} from "bootstrap-vue"
|
||||||
import "bootstrap-vue/dist/bootstrap-vue.css"
|
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||||
|
|
||||||
import {apiLoadRecipe} from "@/utils/api"
|
import RecipeViewComponent from "@/components/RecipeViewComponent.vue";
|
||||||
|
|
||||||
import RecipeContextMenu from "@/components/RecipeContextMenu"
|
|
||||||
import {ResolveUrlMixin, ToastMixin, calculateHourMinuteSplit} from "@/utils/utils"
|
|
||||||
|
|
||||||
import PdfViewer from "@/components/PdfViewer"
|
|
||||||
import ImageViewer from "@/components/ImageViewer"
|
|
||||||
|
|
||||||
import moment from "moment"
|
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
|
||||||
import AddRecipeToBook from "@/components/Modals/AddRecipeToBook"
|
|
||||||
import RecipeRating from "@/components/RecipeRating"
|
|
||||||
import LastCooked from "@/components/LastCooked"
|
|
||||||
import IngredientsCard from "@/components/IngredientsCard"
|
|
||||||
import StepComponent from "@/components/StepComponent"
|
|
||||||
import KeywordsComponent from "@/components/KeywordsComponent"
|
|
||||||
import NutritionComponent from "@/components/NutritionComponent"
|
|
||||||
import RecipeSwitcher from "@/components/Buttons/RecipeSwitcher"
|
|
||||||
import CustomInputSpinButton from "@/components/CustomInputSpinButton"
|
|
||||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
|
||||||
import ImportTandoor from "@/components/Modals/ImportTandoor.vue";
|
|
||||||
import BottomNavigationBar from "@/components/BottomNavigationBar.vue";
|
|
||||||
import PropertyViewComponent from "@/components/PropertyViewComponent.vue";
|
|
||||||
|
|
||||||
Vue.prototype.moment = moment
|
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "RecipeView",
|
name: "RecipeView",
|
||||||
mixins: [ResolveUrlMixin, ToastMixin],
|
mixins: [],
|
||||||
components: {
|
components: {
|
||||||
ImportTandoor,
|
RecipeViewComponent
|
||||||
LastCooked,
|
|
||||||
RecipeRating,
|
|
||||||
PdfViewer,
|
|
||||||
ImageViewer,
|
|
||||||
IngredientsCard,
|
|
||||||
StepComponent,
|
|
||||||
RecipeContextMenu,
|
|
||||||
// NutritionComponent,
|
|
||||||
KeywordsComponent,
|
|
||||||
LoadingSpinner,
|
|
||||||
AddRecipeToBook,
|
|
||||||
RecipeSwitcher,
|
|
||||||
CustomInputSpinButton,
|
|
||||||
BottomNavigationBar,
|
|
||||||
PropertyViewComponent,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
ingredient_factor: function () {
|
|
||||||
return this.servings / this.recipe.servings
|
|
||||||
},
|
|
||||||
ingredient_count() {
|
|
||||||
return this.recipe?.steps.map((x) => x.ingredients).flat().length
|
|
||||||
},
|
|
||||||
working_time: function () {
|
|
||||||
return calculateHourMinuteSplit(this.recipe.working_time)
|
|
||||||
},
|
|
||||||
waiting_time: function () {
|
|
||||||
return calculateHourMinuteSplit(this.recipe.waiting_time)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
computed: {},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {}
|
||||||
loading: true,
|
|
||||||
recipe: undefined,
|
|
||||||
rootrecipe: undefined,
|
|
||||||
servings: 1,
|
|
||||||
servings_cache: {},
|
|
||||||
start_time: "",
|
|
||||||
share_uid: window.SHARE_UID,
|
|
||||||
wake_lock: null,
|
|
||||||
ingredient_height: '250',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
servings(newVal, oldVal) {
|
|
||||||
this.servings_cache[this.recipe.id] = this.servings
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadRecipe(window.RECIPE_ID)
|
|
||||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||||
this.requestWakeLock()
|
|
||||||
window.addEventListener('resize', this.handleResize);
|
|
||||||
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
this.destroyWakeLock()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
requestWakeLock: async function () {
|
|
||||||
if ('wakeLock' in navigator) {
|
|
||||||
try {
|
|
||||||
this.wake_lock = await navigator.wakeLock.request('screen')
|
|
||||||
document.addEventListener('visibilitychange', this.visibilityChange)
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleResize: function () {
|
|
||||||
if (document.getElementById('nutrition_container') !== null) {
|
|
||||||
this.ingredient_height = document.getElementById('ingredient_container').clientHeight - document.getElementById('nutrition_container').clientHeight
|
|
||||||
} else {
|
|
||||||
this.ingredient_height = document.getElementById('ingredient_container').clientHeight
|
|
||||||
}
|
|
||||||
},
|
|
||||||
destroyWakeLock: function () {
|
|
||||||
if (this.wake_lock != null) {
|
|
||||||
this.wake_lock.release()
|
|
||||||
.then(() => {
|
|
||||||
this.wake_lock = null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.removeEventListener('visibilitychange', this.visibilityChange)
|
|
||||||
},
|
|
||||||
visibilityChange: async function () {
|
|
||||||
if (this.wake_lock != null && document.visibilityState === 'visible') {
|
|
||||||
await this.requestWakeLock()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loadRecipe: function (recipe_id) {
|
|
||||||
apiLoadRecipe(recipe_id).then((recipe) => {
|
|
||||||
let total_time = 0
|
|
||||||
for (let step of recipe.steps) {
|
|
||||||
for (let ingredient of step.ingredients) {
|
|
||||||
this.$set(ingredient, "checked", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
step.time_offset = total_time
|
|
||||||
total_time += step.time
|
|
||||||
}
|
|
||||||
|
|
||||||
// set start time only if there are any steps with timers (otherwise no timers are rendered)
|
|
||||||
if (total_time > 0) {
|
|
||||||
this.start_time = moment().format("yyyy-MM-DDTHH:mm")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (recipe.image === null) this.printReady()
|
|
||||||
|
|
||||||
this.recipe = this.rootrecipe = recipe
|
|
||||||
this.servings = this.servings_cache[this.rootrecipe.id] = recipe.servings
|
|
||||||
this.loading = false
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.handleResize()
|
|
||||||
}, 100)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateStartTime: function (e) {
|
|
||||||
this.start_time = e
|
|
||||||
},
|
|
||||||
updateIngredientCheckedState: function (e) {
|
|
||||||
for (let step of this.recipe.steps) {
|
|
||||||
for (let ingredient of step.ingredients) {
|
|
||||||
if (ingredient.id === e.id) {
|
|
||||||
this.$set(ingredient, "checked", !ingredient.checked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quickSwitch: function (e) {
|
|
||||||
if (e === -1) {
|
|
||||||
this.recipe = this.rootrecipe
|
|
||||||
this.servings = this.servings_cache[this.rootrecipe?.id ?? 1]
|
|
||||||
} else {
|
|
||||||
this.recipe = e
|
|
||||||
this.servings = this.servings_cache?.[e.id] ?? e.servings
|
|
||||||
}
|
|
||||||
},
|
|
||||||
printReady: function () {
|
|
||||||
const template = document.createElement("template");
|
|
||||||
template.id = "printReady";
|
|
||||||
document.body.appendChild(template);
|
|
||||||
},
|
|
||||||
onImgLoad: function () {
|
|
||||||
this.printReady()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
methods: {},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#app > div > div {
|
|
||||||
break-inside: avoid;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
351
vue/src/components/RecipeViewComponent.vue
Normal file
351
vue/src/components/RecipeViewComponent.vue
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<template v-if="loading">
|
||||||
|
<loading-spinner></loading-spinner>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-if="!loading" style="padding-bottom: 60px">
|
||||||
|
<RecipeSwitcher ref="ref_recipe_switcher" @switch="quickSwitch($event)"/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12" style="text-align: center">
|
||||||
|
<h3>{{ recipe.name }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
<recipe-rating :recipe="recipe"></recipe-rating>
|
||||||
|
<last-cooked :recipe="recipe" class="mt-2"></last-cooked>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-auto">
|
||||||
|
<div class="col-12" style="text-align: center">
|
||||||
|
<i>{{ recipe.description }}</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center">
|
||||||
|
<keywords-component :recipe="recipe"></keywords-component>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col col-md-3">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="my-auto mr-1">
|
||||||
|
<i class="fas fa-fw fa-user-clock fa-2x text-primary"></i>
|
||||||
|
</div>
|
||||||
|
<div class="my-auto mr-1">
|
||||||
|
<span class="text-primary"><b>{{ $t("Preparation") }}</b></span><br/>
|
||||||
|
{{ working_time }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-md-3">
|
||||||
|
<div class="row d-flex">
|
||||||
|
<div class="my-auto mr-1">
|
||||||
|
<i class="far fa-fw fa-clock fa-2x text-primary"></i>
|
||||||
|
</div>
|
||||||
|
<div class="my-auto mr-1">
|
||||||
|
<span class="text-primary"><b>{{ $t("Waiting") }}</b></span><br/>
|
||||||
|
{{ waiting_time }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-md-4 col-10 mt-2 mt-md-0">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="my-auto mr-1">
|
||||||
|
<i class="fas fa-fw fa-pizza-slice fa-2x text-primary"></i>
|
||||||
|
</div>
|
||||||
|
<div class="my-auto mr-1">
|
||||||
|
<CustomInputSpinButton v-model.number="servings"/>
|
||||||
|
</div>
|
||||||
|
<div class="my-auto mr-1">
|
||||||
|
<span class="text-primary">
|
||||||
|
<b>
|
||||||
|
<template v-if="recipe.servings_text === ''">{{ $t("Servings") }}</template>
|
||||||
|
<template v-else>{{ recipe.servings_text }}</template>
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-md-2 col-2 mt-2 mt-md-0 text-right">
|
||||||
|
<recipe-context-menu v-bind:recipe="recipe" :servings="servings"
|
||||||
|
:disabled_options="{print:false}"></recipe-context-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2"
|
||||||
|
v-if="recipe && ingredient_count > 0 && (recipe.show_ingredient_overview || recipe.steps.length < 2)">
|
||||||
|
<ingredients-card
|
||||||
|
:recipe="recipe.id"
|
||||||
|
:steps="recipe.steps"
|
||||||
|
:ingredient_factor="ingredient_factor"
|
||||||
|
:servings="servings"
|
||||||
|
:header="true"
|
||||||
|
id="ingredient_container"
|
||||||
|
@checked-state-changed="updateIngredientCheckedState"
|
||||||
|
@change-servings="servings = $event"
|
||||||
|
/>
|
||||||
|
</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">
|
||||||
|
<img class="img img-fluid rounded" :src="recipe.image" :alt="$t('Recipe_Image')"
|
||||||
|
v-if="recipe.image !== null" @load="onImgLoad"
|
||||||
|
:style="{ 'max-height': ingredient_height }"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="!recipe.internal">
|
||||||
|
<div v-if="recipe.file_path.includes('.pdf')">
|
||||||
|
<PdfViewer :recipe="recipe"></PdfViewer>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="recipe.file_path.includes('.png') || recipe.file_path.includes('.jpg') || recipe.file_path.includes('.jpeg') || recipe.file_path.includes('.gif')">
|
||||||
|
<ImageViewer :recipe="recipe"></ImageViewer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
|
||||||
|
<step-component
|
||||||
|
:recipe="recipe"
|
||||||
|
:step="s"
|
||||||
|
:ingredient_factor="ingredient_factor"
|
||||||
|
:index="index"
|
||||||
|
:start_time="start_time"
|
||||||
|
@update-start-time="updateStartTime"
|
||||||
|
@checked-state-changed="updateIngredientCheckedState"
|
||||||
|
></step-component>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="recipe.source_url !== null">
|
||||||
|
<h6 class="d-print-none"><i class="fas fa-file-import"></i> {{ $t("Imported_From") }}</h6>
|
||||||
|
<span class="text-muted mt-1"><a style="overflow-wrap: break-word;"
|
||||||
|
:href="recipe.source_url">{{ recipe.source_url }}</a></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" style="margin-top: 2vh; ">
|
||||||
|
<div class="col-lg-6 offset-lg-3 col-12">
|
||||||
|
<property-view-component :recipe="recipe" :servings="servings" @foodUpdated="loadRecipe(recipe.id)"></property-view-component>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
|
||||||
|
|
||||||
|
<div class="row text-center d-print-none" style="margin-top: 3vh; margin-bottom: 3vh"
|
||||||
|
v-if="share_uid !== 'None' && !loading">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
<import-tandoor></import-tandoor> <br/>
|
||||||
|
<a :href="resolveDjangoUrl('view_report_share_abuse', share_uid)" class="mt-3">{{ $t("Report Abuse") }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Vue from "vue"
|
||||||
|
import {BootstrapVue} from "bootstrap-vue"
|
||||||
|
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||||
|
|
||||||
|
import {apiLoadRecipe} from "@/utils/api"
|
||||||
|
|
||||||
|
import RecipeContextMenu from "@/components/RecipeContextMenu"
|
||||||
|
import {ResolveUrlMixin, ToastMixin, calculateHourMinuteSplit} from "@/utils/utils"
|
||||||
|
|
||||||
|
import PdfViewer from "@/components/PdfViewer"
|
||||||
|
import ImageViewer from "@/components/ImageViewer"
|
||||||
|
|
||||||
|
import moment from "moment"
|
||||||
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
import AddRecipeToBook from "@/components/Modals/AddRecipeToBook"
|
||||||
|
import RecipeRating from "@/components/RecipeRating"
|
||||||
|
import LastCooked from "@/components/LastCooked"
|
||||||
|
import IngredientsCard from "@/components/IngredientsCard"
|
||||||
|
import StepComponent from "@/components/StepComponent"
|
||||||
|
import KeywordsComponent from "@/components/KeywordsComponent"
|
||||||
|
import RecipeSwitcher from "@/components/Buttons/RecipeSwitcher"
|
||||||
|
import CustomInputSpinButton from "@/components/CustomInputSpinButton"
|
||||||
|
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||||
|
import ImportTandoor from "@/components/Modals/ImportTandoor.vue";
|
||||||
|
import PropertyViewComponent from "@/components/PropertyViewComponent.vue";
|
||||||
|
|
||||||
|
Vue.prototype.moment = moment
|
||||||
|
|
||||||
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "RecipeView",
|
||||||
|
mixins: [ResolveUrlMixin, ToastMixin],
|
||||||
|
components: {
|
||||||
|
ImportTandoor,
|
||||||
|
LastCooked,
|
||||||
|
RecipeRating,
|
||||||
|
PdfViewer,
|
||||||
|
ImageViewer,
|
||||||
|
IngredientsCard,
|
||||||
|
StepComponent,
|
||||||
|
RecipeContextMenu,
|
||||||
|
KeywordsComponent,
|
||||||
|
LoadingSpinner,
|
||||||
|
AddRecipeToBook,
|
||||||
|
RecipeSwitcher,
|
||||||
|
CustomInputSpinButton,
|
||||||
|
PropertyViewComponent,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
ingredient_factor: function () {
|
||||||
|
return this.servings / this.recipe.servings
|
||||||
|
},
|
||||||
|
ingredient_count() {
|
||||||
|
return this.recipe?.steps.map((x) => x.ingredients).flat().length
|
||||||
|
},
|
||||||
|
working_time: function () {
|
||||||
|
return calculateHourMinuteSplit(this.recipe.working_time)
|
||||||
|
},
|
||||||
|
waiting_time: function () {
|
||||||
|
return calculateHourMinuteSplit(this.recipe.waiting_time)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
recipe: undefined,
|
||||||
|
rootrecipe: undefined,
|
||||||
|
servings: 1,
|
||||||
|
servings_cache: {},
|
||||||
|
start_time: "",
|
||||||
|
share_uid: window.SHARE_UID,
|
||||||
|
wake_lock: null,
|
||||||
|
ingredient_height: '250',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
servings(newVal, oldVal) {
|
||||||
|
this.servings_cache[this.recipe.id] = this.servings
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadRecipe(window.RECIPE_ID)
|
||||||
|
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||||
|
this.requestWakeLock()
|
||||||
|
window.addEventListener('resize', this.handleResize);
|
||||||
|
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
this.destroyWakeLock()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
requestWakeLock: async function () {
|
||||||
|
if ('wakeLock' in navigator) {
|
||||||
|
try {
|
||||||
|
this.wake_lock = await navigator.wakeLock.request('screen')
|
||||||
|
document.addEventListener('visibilitychange', this.visibilityChange)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleResize: function () {
|
||||||
|
if (document.getElementById('nutrition_container') !== null) {
|
||||||
|
this.ingredient_height = document.getElementById('ingredient_container').clientHeight - document.getElementById('nutrition_container').clientHeight
|
||||||
|
} else {
|
||||||
|
this.ingredient_height = document.getElementById('ingredient_container').clientHeight
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroyWakeLock: function () {
|
||||||
|
if (this.wake_lock != null) {
|
||||||
|
this.wake_lock.release()
|
||||||
|
.then(() => {
|
||||||
|
this.wake_lock = null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.removeEventListener('visibilitychange', this.visibilityChange)
|
||||||
|
},
|
||||||
|
visibilityChange: async function () {
|
||||||
|
if (this.wake_lock != null && document.visibilityState === 'visible') {
|
||||||
|
await this.requestWakeLock()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadRecipe: function (recipe_id) {
|
||||||
|
apiLoadRecipe(recipe_id).then((recipe) => {
|
||||||
|
let total_time = 0
|
||||||
|
for (let step of recipe.steps) {
|
||||||
|
for (let ingredient of step.ingredients) {
|
||||||
|
this.$set(ingredient, "checked", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
step.time_offset = total_time
|
||||||
|
total_time += step.time
|
||||||
|
}
|
||||||
|
|
||||||
|
// set start time only if there are any steps with timers (otherwise no timers are rendered)
|
||||||
|
if (total_time > 0) {
|
||||||
|
this.start_time = moment().format("yyyy-MM-DDTHH:mm")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (recipe.image === null) this.printReady()
|
||||||
|
|
||||||
|
this.recipe = this.rootrecipe = recipe
|
||||||
|
this.servings = this.servings_cache[this.rootrecipe.id] = recipe.servings
|
||||||
|
this.loading = false
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.handleResize()
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateStartTime: function (e) {
|
||||||
|
this.start_time = e
|
||||||
|
},
|
||||||
|
updateIngredientCheckedState: function (e) {
|
||||||
|
for (let step of this.recipe.steps) {
|
||||||
|
for (let ingredient of step.ingredients) {
|
||||||
|
if (ingredient.id === e.id) {
|
||||||
|
this.$set(ingredient, "checked", !ingredient.checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quickSwitch: function (e) {
|
||||||
|
if (e === -1) {
|
||||||
|
this.recipe = this.rootrecipe
|
||||||
|
this.servings = this.servings_cache[this.rootrecipe?.id ?? 1]
|
||||||
|
} else {
|
||||||
|
this.recipe = e
|
||||||
|
this.servings = this.servings_cache?.[e.id] ?? e.servings
|
||||||
|
}
|
||||||
|
},
|
||||||
|
printReady: function () {
|
||||||
|
const template = document.createElement("template");
|
||||||
|
template.id = "printReady";
|
||||||
|
document.body.appendChild(template);
|
||||||
|
},
|
||||||
|
onImgLoad: function () {
|
||||||
|
this.printReady()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#app > div > div {
|
||||||
|
break-inside: avoid;
|
||||||
|
}
|
||||||
|
</style>
|
@ -31,22 +31,22 @@
|
|||||||
"Step_start_time": "",
|
"Step_start_time": "",
|
||||||
"Sort_by_new": "",
|
"Sort_by_new": "",
|
||||||
"Table_of_Contents": "",
|
"Table_of_Contents": "",
|
||||||
"Recipes_per_page": "",
|
"Recipes_per_page": "Receptů na stránku",
|
||||||
"Show_as_header": "",
|
"Show_as_header": "",
|
||||||
"Hide_as_header": "",
|
"Hide_as_header": "",
|
||||||
"Add_nutrition_recipe": "",
|
"Add_nutrition_recipe": "Přidat nutriční hodnoty",
|
||||||
"Remove_nutrition_recipe": "",
|
"Remove_nutrition_recipe": "Smazat nutriční hodnoty",
|
||||||
"Copy_template_reference": "",
|
"Copy_template_reference": "",
|
||||||
"Save_and_View": "",
|
"Save_and_View": "Uložit & Zobrazit",
|
||||||
"Manage_Books": "",
|
"Manage_Books": "",
|
||||||
"Meal_Plan": "",
|
"Meal_Plan": "Jídelníček",
|
||||||
"Select_Book": "",
|
"Select_Book": "",
|
||||||
"Select_File": "",
|
"Select_File": "Vybrat soubor",
|
||||||
"Recipe_Image": "",
|
"Recipe_Image": "",
|
||||||
"Import_finished": "",
|
"Import_finished": "Import dokončen",
|
||||||
"View_Recipes": "",
|
"View_Recipes": "Zobrazit recepty",
|
||||||
"Log_Cooking": "",
|
"Log_Cooking": "",
|
||||||
"New_Recipe": "",
|
"New_Recipe": "Nový recept",
|
||||||
"Url_Import": "",
|
"Url_Import": "",
|
||||||
"Reset_Search": "",
|
"Reset_Search": "",
|
||||||
"Recently_Viewed": "",
|
"Recently_Viewed": "",
|
||||||
@ -54,21 +54,21 @@
|
|||||||
"New_Keyword": "",
|
"New_Keyword": "",
|
||||||
"Delete_Keyword": "",
|
"Delete_Keyword": "",
|
||||||
"Edit_Keyword": "",
|
"Edit_Keyword": "",
|
||||||
"Edit_Recipe": "",
|
"Edit_Recipe": "Upravit recept",
|
||||||
"Move_Keyword": "",
|
"Move_Keyword": "",
|
||||||
"Merge_Keyword": "",
|
"Merge_Keyword": "",
|
||||||
"Hide_Keywords": "",
|
"Hide_Keywords": "",
|
||||||
"Hide_Recipes": "",
|
"Hide_Recipes": "",
|
||||||
"Move_Up": "",
|
"Move_Up": "Nahoru",
|
||||||
"Move_Down": "",
|
"Move_Down": "Dolů",
|
||||||
"Step_Name": "",
|
"Step_Name": "Název kroku",
|
||||||
"Step_Type": "",
|
"Step_Type": "",
|
||||||
"Make_Header": "",
|
"Make_Header": "",
|
||||||
"Make_Ingredient": "",
|
"Make_Ingredient": "",
|
||||||
"Amount": "",
|
"Amount": "Množství",
|
||||||
"Enable_Amount": "",
|
"Enable_Amount": "Zobrazit množství",
|
||||||
"Disable_Amount": "",
|
"Disable_Amount": "Skrýt množství",
|
||||||
"Ingredient Editor": "",
|
"Ingredient Editor": "Editace ingrediencí",
|
||||||
"Description_Replace": "",
|
"Description_Replace": "",
|
||||||
"Instruction_Replace": "",
|
"Instruction_Replace": "",
|
||||||
"Auto_Sort": "",
|
"Auto_Sort": "",
|
||||||
@ -79,8 +79,8 @@
|
|||||||
"Add_Step": "",
|
"Add_Step": "",
|
||||||
"Keywords": "",
|
"Keywords": "",
|
||||||
"Books": "",
|
"Books": "",
|
||||||
"Proteins": "",
|
"Proteins": "Proteiny",
|
||||||
"Fats": "",
|
"Fats": "Tuky",
|
||||||
"Carbohydrates": "",
|
"Carbohydrates": "",
|
||||||
"Calories": "",
|
"Calories": "",
|
||||||
"Energy": "",
|
"Energy": "",
|
||||||
|
@ -481,5 +481,22 @@
|
|||||||
"Amount": "Menge",
|
"Amount": "Menge",
|
||||||
"Original_Text": "Originaler Text",
|
"Original_Text": "Originaler Text",
|
||||||
"Import Recipe": "Rezept importieren",
|
"Import Recipe": "Rezept importieren",
|
||||||
"Create Recipe": "Rezept erstellen"
|
"Create Recipe": "Rezept erstellen",
|
||||||
|
"recipe_property_info": "Sie können auch Eigenschaften zu Lebensmitteln hinzufügen, um sie automatisch auf der Grundlage Ihres Rezepts zu berechnen!",
|
||||||
|
"per_serving": "pro Portion",
|
||||||
|
"open_data_help_text": "Das Tandoor Open Data Projekt bietet von der Gemeinschaft bereitgestellte Daten für Tandoor. Dieses Feld wird beim Importieren automatisch ausgefüllt und ermöglicht künftige Aktualisierungen.",
|
||||||
|
"Open_Data_Import": "Datenimport öffnen",
|
||||||
|
"Update_Existing_Data": "Vorhandene Daten aktualisieren",
|
||||||
|
"Data_Import_Info": "Verbessern Sie Ihren Space, indem Sie eine von der Community kuratierte Liste von Lebensmitteln, Einheiten und mehr importieren, um Ihre Rezeptsammlung zu verbessern.",
|
||||||
|
"Learn_More": "Mehr erfahren",
|
||||||
|
"Use_Metric": "Metrische Einheiten verwenden",
|
||||||
|
"converted_unit": "Umgerechnete Einheit",
|
||||||
|
"converted_amount": "Umgerechneter Betrag",
|
||||||
|
"base_unit": "Basiseinheit",
|
||||||
|
"base_amount": "Grundbetrag",
|
||||||
|
"Datatype": "Datentyp",
|
||||||
|
"Number of Objects": "Anzahl von Objekten",
|
||||||
|
"Property": "Eigenschaft",
|
||||||
|
"Conversion": "Umrechnung",
|
||||||
|
"Properties": "Eigenschaften"
|
||||||
}
|
}
|
||||||
|
2397
vue/yarn.lock
2397
vue/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user