mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 04:10:06 -05:00
Merge branch 'develop' into feature/importer_to_vue
# Conflicts: # vue/src/apps/RecipeEditView/RecipeEditView.vue # vue/src/utils/openapi/api.ts
This commit is contained in:
234
vue/src/apps/IngredientEditorView/IngredientEditorView.vue
Normal file
234
vue/src/apps/IngredientEditorView/IngredientEditorView.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<beta-warning></beta-warning>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col col-md-6">
|
||||
<generic-multiselect @change="food = $event.val; refreshList()"
|
||||
:model="Models.FOOD"
|
||||
:initial_single_selection="food"
|
||||
:multiple="false"></generic-multiselect>
|
||||
<b-button @click="show_food_delete=true" :disabled="food === null"><i class="fas fa-trash-alt"></i>
|
||||
</b-button>
|
||||
<b-button @click="generic_model = Models.FOOD; generic_action=Actions.MERGE" :disabled="food === null">
|
||||
<i class="fas fa-compress-arrows-alt"></i>
|
||||
</b-button>
|
||||
<generic-modal-form :model="Models.FOOD" :action="generic_action" :show="generic_model === Models.FOOD"
|
||||
:item1="food"
|
||||
@finish-action="food = null; generic_action=null; generic_model=null"/>
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
|
||||
<generic-multiselect
|
||||
@change="unit = $event.val; refreshList()"
|
||||
:model="Models.UNIT"
|
||||
:initial_single_selection="unit"
|
||||
:multiple="false"></generic-multiselect>
|
||||
|
||||
<b-button @click="generic_model = Models.UNIT; generic_action=Actions.DELETE" :disabled="unit === null">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</b-button>
|
||||
<b-button @click="generic_model = Models.UNIT; generic_action=Actions.MERGE" :disabled="unit === null">
|
||||
<i class="fas fa-compress-arrows-alt"></i>
|
||||
</b-button>
|
||||
<generic-modal-form :model="Models.UNIT" :action="generic_action" :show="generic_model === Models.UNIT"
|
||||
:item1="unit"
|
||||
@finish-action="unit = null; generic_action=null; generic_model=null"/>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col col-md-12">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('Amount') }}</th>
|
||||
<th>{{ $t('Unit') }}</th>
|
||||
<th>{{ $t('Food') }}</th>
|
||||
<th>{{ $t('Note') }}</th>
|
||||
<th>
|
||||
<b-button variant="success" @click="updateIngredient()"><i class="fas fa-save"></i>
|
||||
</b-button>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-if="loading">
|
||||
<td colspan="4">
|
||||
<loading-spinner></loading-spinner>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="!loading">
|
||||
<tbody v-for="i in ingredients" v-bind:key="i.id">
|
||||
<tr v-if="i.used_in_recipes.length > 0">
|
||||
<td colspan="5">
|
||||
<a v-for="r in i.used_in_recipes" :href="resolveDjangoUrl('view_recipe',r.id)"
|
||||
v-bind:key="r.id" target="_blank" rel="noreferrer nofollow">{{ r.name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 5vw">
|
||||
<input type="number" class="form-control" v-model="i.amount"
|
||||
@input="$set(i, 'changed', true)">
|
||||
</td>
|
||||
<td style="width: 30vw">
|
||||
<generic-multiselect @change="i.unit = $event.val; $set(i, 'changed', true)"
|
||||
:initial_single_selection="i.unit"
|
||||
:model="Models.UNIT"
|
||||
:search_on_load="false"
|
||||
:allow_create="true"
|
||||
:create_placeholder="$t('Create')"
|
||||
:multiple="false"></generic-multiselect>
|
||||
</td>
|
||||
<td style="width: 30vw">
|
||||
<generic-multiselect @change="i.food = $event.val; $set(i, 'changed', true)"
|
||||
:initial_single_selection="i.food"
|
||||
:model="Models.FOOD"
|
||||
:search_on_load="false"
|
||||
:allow_create="true"
|
||||
:create_placeholder="$t('Create')"
|
||||
:multiple="false"></generic-multiselect>
|
||||
</td>
|
||||
<td style="width: 30vw">
|
||||
<input class="form-control" v-model="i.note" @keydown="$set(i, 'changed', true)">
|
||||
|
||||
</td>
|
||||
<td style="width: 5vw">
|
||||
<b-button :disabled="i.changed !== true"
|
||||
:variant="(i.changed !== true) ? 'primary' : 'success'"
|
||||
@click="updateIngredient(i)">
|
||||
<i class="fas fa-save"></i>
|
||||
</b-button>
|
||||
<b-button variant="danger"
|
||||
@click="deleteIngredient(i)">
|
||||
<i class="fas fa-trash"></i>
|
||||
</b-button>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from "vue"
|
||||
import {BootstrapVue} from "bootstrap-vue"
|
||||
|
||||
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||
import {ApiMixin, ResolveUrlMixin, StandardToasts} from "@/utils/utils"
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
import GenericMultiselect from "@/components/GenericMultiselect";
|
||||
import GenericModalForm from "@/components/Modals/GenericModalForm";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import BetaWarning from "@/components/BetaWarning";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: "IngredientEditorView",
|
||||
mixins: [ApiMixin, ResolveUrlMixin],
|
||||
components: {BetaWarning, LoadingSpinner, GenericMultiselect, GenericModalForm},
|
||||
data() {
|
||||
return {
|
||||
ingredients: [],
|
||||
loading: false,
|
||||
food: null,
|
||||
unit: null,
|
||||
generic_action: null,
|
||||
generic_model: null,
|
||||
show_food_delete: false,
|
||||
show_unit_delete: false,
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||
if (window.DEFAULT_FOOD !== -1) {
|
||||
this.food = {id: window.DEFAULT_FOOD}
|
||||
let apiClient = new ApiApiFactory()
|
||||
apiClient.retrieveFood(this.food.id).then(r => {
|
||||
this.food = r.data
|
||||
})
|
||||
}
|
||||
if (window.DEFAULT_UNIT !== -1) {
|
||||
this.unit = {id: window.DEFAULT_UNIT}
|
||||
let apiClient = new ApiApiFactory()
|
||||
apiClient.retrieveUnit(this.unit.id).then(r => {
|
||||
this.unit = r.data
|
||||
})
|
||||
}
|
||||
this.refreshList()
|
||||
},
|
||||
methods: {
|
||||
refreshList: function () {
|
||||
if (this.food === null && this.unit === null) {
|
||||
this.ingredients = []
|
||||
} else {
|
||||
this.loading = true
|
||||
let apiClient = new ApiApiFactory()
|
||||
let params = {'query': {'simple': 1}}
|
||||
if (this.food !== null) {
|
||||
params.query.food = this.food.id
|
||||
}
|
||||
if (this.unit !== null) {
|
||||
params.query.unit = this.unit.id
|
||||
}
|
||||
apiClient.listIngredients(params).then(result => {
|
||||
this.ingredients = result.data
|
||||
this.loading = false
|
||||
}).catch((err) => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH)
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
updateIngredient: function (i) {
|
||||
let update_list = []
|
||||
if (i === undefined) {
|
||||
this.ingredients.forEach(x => {
|
||||
if (x.changed) {
|
||||
update_list.push(x)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
update_list = [i]
|
||||
}
|
||||
|
||||
update_list.forEach(i => {
|
||||
let apiClient = new ApiApiFactory()
|
||||
apiClient.updateIngredient(i.id, i).then(r => {
|
||||
this.$set(i, 'changed', false)
|
||||
}).catch((r, e) => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE)
|
||||
})
|
||||
})
|
||||
},
|
||||
deleteIngredient: function (i){
|
||||
if (confirm(this.$t('delete_confirmation', this.$t('Ingredient')))){
|
||||
let apiClient = new ApiApiFactory()
|
||||
apiClient.destroyIngredient(i.id).then(r => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)
|
||||
this.ingredients = this.ingredients.filter(li => li.id !== i.id)
|
||||
}).catch(e => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_DELETE)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
18
vue/src/apps/IngredientEditorView/main.js
Normal file
18
vue/src/apps/IngredientEditorView/main.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import Vue from 'vue'
|
||||
import App from './IngredientEditorView.vue'
|
||||
import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div id="app" style="margin-bottom: 4vh" v-if="this_model">
|
||||
<generic-modal-form v-if="this_model" :model="this_model" :action="this_action" :item1="this_item" :item2="this_target" :show="show_modal" @finish-action="finishAction" />
|
||||
<generic-modal-form v-if="this_model" :model="this_model" :action="this_action" :item1="this_item"
|
||||
:item2="this_target" :show="show_modal" @finish-action="finishAction"/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2 d-none d-md-block"></div>
|
||||
@@ -17,17 +18,20 @@
|
||||
<div class="col-md-9" style="margin-top: 1vh">
|
||||
<h3>
|
||||
<!-- <span><b-button variant="link" size="sm" class="text-dark shadow-none"><i class="fas fa-chevron-down"></i></b-button></span> -->
|
||||
<model-menu />
|
||||
<model-menu/>
|
||||
<span>{{ $t(this.this_model.name) }}</span>
|
||||
<span v-if="apiName !== 'Step' && apiName !== 'CustomFilter'">
|
||||
<b-button variant="link" @click="startAction({ action: 'new' })">
|
||||
<i class="fas fa-plus-circle fa-2x"></i>
|
||||
</b-button> </span
|
||||
><!-- TODO add proper field to model config to determine if create should be available or not -->
|
||||
>
|
||||
<!-- TODO add proper field to model config to determine if create should be available or not -->
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-md-3" style="position: relative; margin-top: 1vh">
|
||||
<b-form-checkbox v-model="show_split" name="check-button" v-if="paginated" class="shadow-none" style="position: relative; top: 50%; transform: translateY(-50%)" switch>
|
||||
<b-form-checkbox v-model="show_split" name="check-button" v-if="paginated"
|
||||
class="shadow-none"
|
||||
style="position: relative; top: 50%; transform: translateY(-50%)" switch>
|
||||
{{ $t("show_split_screen") }}
|
||||
</b-form-checkbox>
|
||||
</div>
|
||||
@@ -37,19 +41,29 @@
|
||||
<div class="col" :class="{ 'col-md-6': show_split }">
|
||||
<!-- model isn't paginated and loads in one API call -->
|
||||
<div v-if="!paginated">
|
||||
<generic-horizontal-card v-for="i in items_left" v-bind:key="i.id" :item="i" :model="this_model" @item-action="startAction($event, 'left')" @finish-action="finishAction" />
|
||||
<generic-horizontal-card v-for="i in items_left" v-bind:key="i.id" :item="i"
|
||||
:model="this_model" @item-action="startAction($event, 'left')"
|
||||
@finish-action="finishAction"/>
|
||||
</div>
|
||||
<!-- model is paginated and needs managed -->
|
||||
<generic-infinite-cards v-if="paginated" :card_counts="left_counts" :scroll="show_split" @search="getItems($event, 'left')" @reset="resetList('left')">
|
||||
<generic-infinite-cards v-if="paginated" :card_counts="left_counts" :scroll="show_split"
|
||||
@search="getItems($event, 'left')" @reset="resetList('left')">
|
||||
<template v-slot:cards>
|
||||
<generic-horizontal-card v-for="i in items_left" v-bind:key="i.id" :item="i" :model="this_model" @item-action="startAction($event, 'left')" @finish-action="finishAction" />
|
||||
<generic-horizontal-card v-for="i in items_left" v-bind:key="i.id" :item="i"
|
||||
:model="this_model"
|
||||
@item-action="startAction($event, 'left')"
|
||||
@finish-action="finishAction"/>
|
||||
</template>
|
||||
</generic-infinite-cards>
|
||||
</div>
|
||||
<div class="col col-md-6" v-if="show_split">
|
||||
<generic-infinite-cards v-if="this_model" :card_counts="right_counts" :scroll="show_split" @search="getItems($event, 'right')" @reset="resetList('right')">
|
||||
<generic-infinite-cards v-if="this_model" :card_counts="right_counts" :scroll="show_split"
|
||||
@search="getItems($event, 'right')" @reset="resetList('right')">
|
||||
<template v-slot:cards>
|
||||
<generic-horizontal-card v-for="i in items_right" v-bind:key="i.id" :item="i" :model="this_model" @item-action="startAction($event, 'right')" @finish-action="finishAction" />
|
||||
<generic-horizontal-card v-for="i in items_right" v-bind:key="i.id" :item="i"
|
||||
:model="this_model"
|
||||
@item-action="startAction($event, 'right')"
|
||||
@finish-action="finishAction"/>
|
||||
</template>
|
||||
</generic-infinite-cards>
|
||||
</div>
|
||||
@@ -62,18 +76,18 @@
|
||||
|
||||
<script>
|
||||
import Vue from "vue"
|
||||
import { BootstrapVue } from "bootstrap-vue"
|
||||
import {BootstrapVue} from "bootstrap-vue"
|
||||
|
||||
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||
|
||||
import { CardMixin, ApiMixin, getConfig } from "@/utils/utils"
|
||||
import { StandardToasts, ToastMixin } from "@/utils/utils"
|
||||
import {CardMixin, ApiMixin, getConfig, resolveDjangoUrl} from "@/utils/utils"
|
||||
import {StandardToasts, ToastMixin} from "@/utils/utils"
|
||||
|
||||
import GenericInfiniteCards from "@/components/GenericInfiniteCards"
|
||||
import GenericHorizontalCard from "@/components/GenericHorizontalCard"
|
||||
import GenericModalForm from "@/components/Modals/GenericModalForm"
|
||||
import ModelMenu from "@/components/ContextMenu/ModelMenu"
|
||||
import { ApiApiFactory } from "@/utils/openapi/api"
|
||||
import {ApiApiFactory} from "@/utils/openapi/api"
|
||||
//import StorageQuota from "@/components/StorageQuota";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
@@ -94,8 +108,8 @@ export default {
|
||||
// this.Models and this.Actions inherited from ApiMixin
|
||||
items_left: [],
|
||||
items_right: [],
|
||||
right_counts: { max: 9999, current: 0 },
|
||||
left_counts: { max: 9999, current: 0 },
|
||||
right_counts: {max: 9999, current: 0},
|
||||
left_counts: {max: 9999, current: 0},
|
||||
this_model: undefined,
|
||||
model_menu: undefined,
|
||||
this_action: undefined,
|
||||
@@ -127,7 +141,7 @@ export default {
|
||||
this.header_component_name = this.this_model?.list?.header_component?.name ?? undefined
|
||||
this.$nextTick(() => {
|
||||
if (!this.paginated) {
|
||||
this.getItems({ page: 1 }, "left")
|
||||
this.getItems({page: 1}, "left")
|
||||
}
|
||||
})
|
||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||
@@ -144,7 +158,6 @@ export default {
|
||||
let target = e?.target ?? undefined
|
||||
this.this_item = source
|
||||
this.this_target = target
|
||||
|
||||
switch (e.action) {
|
||||
case "delete":
|
||||
this.this_action = this.Actions.DELETE
|
||||
@@ -159,6 +172,16 @@ export default {
|
||||
this.this_action = this.Actions.UPDATE
|
||||
this.show_modal = true
|
||||
break
|
||||
case "ingredient-editor": {
|
||||
let url = resolveDjangoUrl("view_ingredient_editor")
|
||||
if (this.this_model === this.Models.FOOD) {
|
||||
window.location.href = url + '?food_id=' + e.source.id
|
||||
}
|
||||
if (this.this_model === this.Models.UNIT) {
|
||||
window.location.href = url + '?unit_id=' + e.source.id
|
||||
}
|
||||
break
|
||||
}
|
||||
case "move":
|
||||
if (target == null) {
|
||||
this.this_item = e.source
|
||||
@@ -236,7 +259,7 @@ export default {
|
||||
},
|
||||
getItems: function (params = {}, col) {
|
||||
let column = col || "left"
|
||||
params.options = { query: { extended: 1 } } // returns extended values in API response
|
||||
params.options = {query: {extended: 1}} // returns extended values in API response
|
||||
this.genericAPI(this.this_model, this.Actions.LIST, params)
|
||||
.then((result) => {
|
||||
let results = result.data?.results ?? result.data
|
||||
@@ -257,7 +280,7 @@ export default {
|
||||
})
|
||||
},
|
||||
getThis: function (id, callback) {
|
||||
return this.genericAPI(this.this_model, this.Actions.FETCH, { id: id })
|
||||
return this.genericAPI(this.this_model, this.Actions.FETCH, {id: id})
|
||||
},
|
||||
saveThis: function (item) {
|
||||
if (!item?.id) {
|
||||
@@ -268,7 +291,7 @@ export default {
|
||||
// then place all new items at the top of the list - could sort instead
|
||||
this.items_left = [result.data].concat(this.destroyCard(result?.data?.id, this.items_left))
|
||||
// this creates a deep copy to make sure that columns stay independent
|
||||
this.items_right = [{ ...result.data }].concat(this.destroyCard(result?.data?.id, this.items_right))
|
||||
this.items_right = [{...result.data}].concat(this.destroyCard(result?.data?.id, this.items_right))
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -291,10 +314,10 @@ export default {
|
||||
addShopping: function (food) {
|
||||
let api = new ApiApiFactory()
|
||||
food.shopping = true
|
||||
api.createShoppingListEntry({ food: food, amount: 1 }).then(() => {
|
||||
api.createShoppingListEntry({food: food, amount: 1}).then(() => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||
this.refreshCard(food, this.items_left)
|
||||
this.refreshCard({ ...food }, this.items_right)
|
||||
this.refreshCard({...food}, this.items_right)
|
||||
})
|
||||
},
|
||||
updateThis: function (item) {
|
||||
@@ -313,7 +336,7 @@ export default {
|
||||
this.clearState()
|
||||
return
|
||||
}
|
||||
this.genericAPI(this.this_model, this.Actions.MOVE, { source: source_id, target: target_id })
|
||||
this.genericAPI(this.this_model, this.Actions.MOVE, {source: source_id, target: target_id})
|
||||
.then((result) => {
|
||||
this.moveUpdateItem(source_id, target_id)
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_MOVE)
|
||||
@@ -395,8 +418,8 @@ export default {
|
||||
let params = {
|
||||
root: item.id,
|
||||
pageSize: 200,
|
||||
query: { extended: 1 },
|
||||
options: { query: { extended: 1 } },
|
||||
query: {extended: 1},
|
||||
options: {query: {extended: 1}},
|
||||
}
|
||||
this.genericAPI(this.this_model, this.Actions.LIST, params)
|
||||
.then((result) => {
|
||||
@@ -415,7 +438,7 @@ export default {
|
||||
getRecipes: function (col, item) {
|
||||
let parent = {}
|
||||
// TODO: make this generic
|
||||
let params = { pageSize: 50, random: true }
|
||||
let params = {pageSize: 50, random: true}
|
||||
params[this.this_recipe_param] = item.id
|
||||
this.genericAPI(this.Models.RECIPE, this.Actions.LIST, params)
|
||||
.then((result) => {
|
||||
@@ -434,7 +457,7 @@ export default {
|
||||
refreshThis: function (id) {
|
||||
this.getThis(id).then((result) => {
|
||||
this.refreshCard(result.data, this.items_left)
|
||||
this.refreshCard({ ...result.data }, this.items_right)
|
||||
this.refreshCard({...result.data}, this.items_right)
|
||||
})
|
||||
},
|
||||
deleteThis: function (id) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label for="id_name"> {{ $t("Name") }}</label>
|
||||
<input class="form-control" id="id_name" v-model="recipe.name" />
|
||||
<input class="form-control" id="id_name" v-model="recipe.name"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-2">
|
||||
@@ -19,14 +19,16 @@
|
||||
<label for="id_description">
|
||||
{{ $t("Description") }}
|
||||
</label>
|
||||
<textarea id="id_description" class="form-control" v-model="recipe.description" maxlength="512"></textarea>
|
||||
<textarea id="id_description" class="form-control" v-model="recipe.description"
|
||||
maxlength="512"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image and misc properties -->
|
||||
<div class="row pt-2">
|
||||
<div class="col-md-6" style="max-height: 50vh; min-height: 30vh">
|
||||
<input id="id_file_upload" ref="file_upload" type="file" hidden @change="uploadImage($event.target.files[0])" />
|
||||
<input id="id_file_upload" ref="file_upload" type="file" hidden
|
||||
@change="uploadImage($event.target.files[0])"/>
|
||||
|
||||
<div
|
||||
class="h-100 w-100 border border-primary rounded"
|
||||
@@ -35,26 +37,31 @@
|
||||
@dragover.prevent
|
||||
@click="$refs.file_upload.click()"
|
||||
>
|
||||
<i class="far fa-image fa-10x text-primary" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)" v-if="!recipe.image"></i>
|
||||
<i class="far fa-image fa-10x text-primary"
|
||||
style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)"
|
||||
v-if="!recipe.image"></i>
|
||||
|
||||
<img :src="recipe.image" id="id_image" class="img img-fluid img-responsive" style="object-fit: cover; height: 100%" v-if="recipe.image" />
|
||||
<img :src="recipe.image" id="id_image" class="img img-fluid img-responsive"
|
||||
style="object-fit: cover; height: 100%" v-if="recipe.image"/>
|
||||
</div>
|
||||
<button style="bottom: 10px; left: 30px; position: absolute" class="btn btn-danger" @click="deleteImage" v-if="recipe.image">{{ $t("Delete") }}</button>
|
||||
<button style="bottom: 10px; left: 30px; position: absolute" class="btn btn-danger"
|
||||
@click="deleteImage" v-if="recipe.image">{{ $t("Delete") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mt-1">
|
||||
<label for="id_name"> {{ $t("Preparation") }} {{ $t("Time") }} ({{ $t("min") }})</label>
|
||||
<input class="form-control" id="id_prep_time" v-model="recipe.working_time" type="number" />
|
||||
<br />
|
||||
<input class="form-control" id="id_prep_time" v-model="recipe.working_time" type="number"/>
|
||||
<br/>
|
||||
<label for="id_name"> {{ $t("Waiting") }} {{ $t("Time") }} ({{ $t("min") }})</label>
|
||||
<input class="form-control" id="id_wait_time" v-model="recipe.waiting_time" type="number" />
|
||||
<br />
|
||||
<input class="form-control" id="id_wait_time" v-model="recipe.waiting_time" type="number"/>
|
||||
<br/>
|
||||
<label for="id_name"> {{ $t("Servings") }}</label>
|
||||
<input class="form-control" id="id_servings" v-model="recipe.servings" type="number" />
|
||||
<br />
|
||||
<input class="form-control" id="id_servings" v-model="recipe.servings" type="number"/>
|
||||
<br/>
|
||||
<label for="id_name"> {{ $t("Servings") }} {{ $t("Text") }}</label>
|
||||
<input class="form-control" id="id_servings_text" v-model="recipe.servings_text" maxlength="32" />
|
||||
<br />
|
||||
<input class="form-control" id="id_servings_text" v-model="recipe.servings_text" maxlength="32"/>
|
||||
<br/>
|
||||
<label for="id_name"> {{ $t("Keywords") }}</label>
|
||||
<multiselect
|
||||
v-model="recipe.keywords"
|
||||
@@ -122,26 +129,31 @@
|
||||
<div class="card-body" v-if="recipe.nutrition !== null">
|
||||
<b-alert show>
|
||||
There is currently only very basic support for tracking nutritional information. A
|
||||
<a href="https://github.com/vabene1111/recipes/issues/896" target="_blank" rel="noreferrer nofollow">big update</a> is planned to improve on this in many different areas.
|
||||
<a href="https://github.com/vabene1111/recipes/issues/896" target="_blank"
|
||||
rel="noreferrer nofollow">big update</a> is planned to improve on this in many
|
||||
different areas.
|
||||
</b-alert>
|
||||
|
||||
<label for="id_name"> {{ $t(energy()) }}</label>
|
||||
|
||||
<input class="form-control" id="id_calories" v-model="recipe.nutrition.calories" />
|
||||
<input class="form-control" id="id_calories" v-model="recipe.nutrition.calories"/>
|
||||
|
||||
<label for="id_name"> {{ $t("Carbohydrates") }}</label>
|
||||
<input class="form-control" id="id_carbohydrates" v-model="recipe.nutrition.carbohydrates" />
|
||||
<input class="form-control" id="id_carbohydrates"
|
||||
v-model="recipe.nutrition.carbohydrates"/>
|
||||
|
||||
<label for="id_name"> {{ $t("Fats") }}</label>
|
||||
<input class="form-control" id="id_fats" v-model="recipe.nutrition.fats" />
|
||||
<input class="form-control" id="id_fats" v-model="recipe.nutrition.fats"/>
|
||||
|
||||
<label for="id_name"> {{ $t("Proteins") }}</label>
|
||||
<input class="form-control" id="id_proteins" v-model="recipe.nutrition.proteins" />
|
||||
<input class="form-control" id="id_proteins" v-model="recipe.nutrition.proteins"/>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
<b-card-header header-tag="header" class="p-1" role="tab">
|
||||
<b-button squared block v-b-toggle.additional_collapse class="text-left" variant="outline-primary">{{ $t("additional_options") }}</b-button>
|
||||
<b-button squared block v-b-toggle.additional_collapse class="text-left"
|
||||
variant="outline-primary">{{ $t("additional_options") }}
|
||||
</b-button>
|
||||
</b-card-header>
|
||||
<b-collapse id="additional_collapse" class="mt-2" v-model="additional_visible">
|
||||
<b-form-group>
|
||||
@@ -150,8 +162,12 @@
|
||||
<b-input-group-text squared>
|
||||
<b-form-checkbox v-model="recipe.create_food"></b-form-checkbox>
|
||||
</b-input-group-text>
|
||||
<b-input-group-text squared v-if="recipe.create_food"> {{ $t("Name") }}</b-input-group-text>
|
||||
<b-form-input squared v-if="recipe.create_food" v-model="recipe.food_name" id="food_name"></b-form-input>
|
||||
<b-input-group-text squared v-if="recipe.create_food"> {{
|
||||
$t("Name")
|
||||
}}
|
||||
</b-input-group-text>
|
||||
<b-form-input squared v-if="recipe.create_food" v-model="recipe.food_name"
|
||||
id="food_name"></b-form-input>
|
||||
</b-input-group-append>
|
||||
<em class="small text-muted">
|
||||
{{ $t("create_food_desc") }}
|
||||
@@ -162,7 +178,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Steps -->
|
||||
<draggable :list="recipe.steps" group="steps" :empty-insert-threshold="10" handle=".handle" @sort="sortSteps()">
|
||||
<draggable :list="recipe.steps" group="steps" :empty-insert-threshold="10" handle=".handle"
|
||||
@sort="sortSteps()">
|
||||
<div v-for="(step, step_index) in recipe.steps" v-bind:key="step_index">
|
||||
<div class="card mt-2 mb-2">
|
||||
<div class="card-body pr-2 pl-2 pr-md-5 pl-md-5" :id="`id_card_step_${step_index}`">
|
||||
@@ -176,26 +193,33 @@
|
||||
</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">
|
||||
<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> {{ $t("Delete") }}</button>
|
||||
<button class="dropdown-item" @click="removeStep(step)"><i
|
||||
class="fa fa-trash fa-fw"></i> {{ $t("Delete") }}
|
||||
</button>
|
||||
|
||||
<button type="button" class="dropdown-item" v-if="!step.show_as_header" @click="step.show_as_header = true">
|
||||
<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> {{ $t("Show_as_header") }}
|
||||
</button>
|
||||
|
||||
<button type="button" class="dropdown-item" v-if="step.show_as_header" @click="step.show_as_header = false">
|
||||
<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> {{ $t("Hide_as_header") }}
|
||||
</button>
|
||||
|
||||
<button class="dropdown-item" @click="moveStep(step, step_index - 1)" v-if="step_index > 0">
|
||||
<button class="dropdown-item" @click="moveStep(step, step_index - 1)"
|
||||
v-if="step_index > 0">
|
||||
<i class="fa fa-arrow-up fa-fw"></i>
|
||||
{{ $t("Move_Up") }}
|
||||
</button>
|
||||
<button class="dropdown-item" @click="moveStep(step, step_index + 1)" v-if="step_index !== recipe.steps.length - 1">
|
||||
<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> {{ $t("Move_Down") }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -206,30 +230,36 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label :for="'id_step_' + step.id + 'name'">{{ $t("Step_Name") }}</label>
|
||||
<input class="form-control" v-model="step.name" :id="'id_step_' + step.id + 'name'" />
|
||||
<input class="form-control" v-model="step.name"
|
||||
:id="'id_step_' + step.id + 'name'"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- step data visibility controller -->
|
||||
<div class="row pt-2">
|
||||
<div class="col col-md-12">
|
||||
<b-button pill variant="primary" size="sm" class="ml-1" @click="step.time_visible = true" v-if="!step.time_visible">
|
||||
<b-button pill variant="primary" size="sm" class="ml-1"
|
||||
@click="step.time_visible = true" v-if="!step.time_visible">
|
||||
<i class="fas fa-plus-circle"></i> {{ $t("Time") }}
|
||||
</b-button>
|
||||
|
||||
<b-button pill variant="primary" size="sm" class="ml-1" @click="step.ingredients_visible = true" v-if="!step.ingredients_visible">
|
||||
<b-button pill variant="primary" size="sm" class="ml-1"
|
||||
@click="step.ingredients_visible = true" v-if="!step.ingredients_visible">
|
||||
<i class="fas fa-plus-circle"></i> {{ $t("Ingredients") }}
|
||||
</b-button>
|
||||
|
||||
<b-button pill variant="primary" size="sm" class="ml-1" @click="step.instruction_visible = true" v-if="!step.instruction_visible">
|
||||
<b-button pill variant="primary" size="sm" class="ml-1"
|
||||
@click="step.instruction_visible = true" v-if="!step.instruction_visible">
|
||||
<i class="fas fa-plus-circle"></i> {{ $t("Instructions") }}
|
||||
</b-button>
|
||||
|
||||
<b-button pill variant="primary" size="sm" class="ml-1" @click="step.step_recipe_visible = true" v-if="!step.step_recipe_visible">
|
||||
<b-button pill variant="primary" size="sm" class="ml-1"
|
||||
@click="step.step_recipe_visible = true" v-if="!step.step_recipe_visible">
|
||||
<i class="fas fa-plus-circle"></i> {{ $t("Recipe") }}
|
||||
</b-button>
|
||||
|
||||
<b-button pill variant="primary" size="sm" class="ml-1" @click="step.file_visible = true" v-if="!step.file_visible">
|
||||
<b-button pill variant="primary" size="sm" class="ml-1"
|
||||
@click="step.file_visible = true" v-if="!step.file_visible">
|
||||
<i class="fas fa-plus-circle"></i> {{ $t("File") }}
|
||||
</b-button>
|
||||
<b-button
|
||||
@@ -250,7 +280,8 @@
|
||||
<div class="row pt-2" v-if="step.time_visible">
|
||||
<div class="col-md-12">
|
||||
<label :for="'id_step_' + step.id + '_time'">{{ $t("step_time_minutes") }}</label>
|
||||
<input class="form-control" v-model="step.time" :id="'id_step_' + step.id + '_time'" />
|
||||
<input class="form-control" v-model="step.time"
|
||||
:id="'id_step_' + step.id + '_time'"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -333,18 +364,23 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 pr-0 pl-0 pr-md-2 pl-md-2 mt-2">
|
||||
<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" />
|
||||
<!-- TODO improve rendering/add switch to toggle on/off -->
|
||||
<div class="small text-muted" v-if="ingredient.original_text">{{ingredient.original_text}}</div>
|
||||
<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 pr-0 pl-1 pr-md-2 pl-md-2"><i class="fas fa-arrows-alt-v"></i></button>
|
||||
<button type="button"
|
||||
class="btn btn-lg shadow-none pr-0 pl-1 pr-md-2 pl-md-2">
|
||||
<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">
|
||||
<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"
|
||||
@@ -355,7 +391,8 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-2 col-md-6 small-padding" v-if="!ingredient.is_header">
|
||||
<div class="col-lg-2 col-md-6 small-padding"
|
||||
v-if="!ingredient.is_header">
|
||||
<!-- search set to false to allow API to drive results & order -->
|
||||
<multiselect
|
||||
v-if="!ingredient.no_amount"
|
||||
@@ -382,10 +419,14 @@
|
||||
:loading="units_loading"
|
||||
@search-change="searchUnits"
|
||||
>
|
||||
<template v-slot:noOptions>{{ $t("empty_list") }}</template>
|
||||
<template v-slot:noOptions>{{
|
||||
$t("empty_list")
|
||||
}}
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 small-padding" v-if="!ingredient.is_header">
|
||||
<div class="col-lg-4 col-md-6 small-padding"
|
||||
v-if="!ingredient.is_header">
|
||||
<!-- search set to false to allow API to drive results & order -->
|
||||
|
||||
<multiselect
|
||||
@@ -412,10 +453,14 @@
|
||||
:loading="foods_loading"
|
||||
@search-change="searchFoods"
|
||||
>
|
||||
<template v-slot:noOptions>{{ $t("empty_list") }}</template>
|
||||
<template v-slot:noOptions>{{
|
||||
$t("empty_list")
|
||||
}}
|
||||
</template>
|
||||
</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 }">
|
||||
<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"
|
||||
maxlength="256"
|
||||
@@ -446,32 +491,43 @@
|
||||
<i class="fas fa-ellipsis-v text-muted"></i>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink2">
|
||||
<button type="button" class="dropdown-item" @click="removeIngredient(step, ingredient)">
|
||||
<div class="dropdown-menu dropdown-menu-right"
|
||||
aria-labelledby="dropdownMenuLink2">
|
||||
<button type="button" class="dropdown-item"
|
||||
@click="removeIngredient(step, ingredient)">
|
||||
<i class="fa fa-trash fa-fw"></i>
|
||||
{{ $t("Delete") }}
|
||||
</button>
|
||||
|
||||
<button type="button" class="dropdown-item" v-if="!ingredient.is_header" @click="ingredient.is_header = true">
|
||||
<button type="button" class="dropdown-item"
|
||||
v-if="!ingredient.is_header"
|
||||
@click="ingredient.is_header = true">
|
||||
<i class="fas fa-heading fa-fw"></i>
|
||||
{{ $t("Make_Header") }}
|
||||
</button>
|
||||
|
||||
<button type="button" class="dropdown-item" v-if="ingredient.is_header" @click="ingredient.is_header = false">
|
||||
<button type="button" class="dropdown-item"
|
||||
v-if="ingredient.is_header"
|
||||
@click="ingredient.is_header = false">
|
||||
<i class="fas fa-leaf fa-fw"></i>
|
||||
{{ $t("Make_Ingredient") }}
|
||||
</button>
|
||||
|
||||
<button type="button" class="dropdown-item" v-if="!ingredient.no_amount" @click="ingredient.no_amount = true">
|
||||
<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>
|
||||
{{ $t("Disable_Amount") }}
|
||||
</button>
|
||||
|
||||
<button type="button" class="dropdown-item" v-if="ingredient.no_amount" @click="ingredient.no_amount = false">
|
||||
<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>
|
||||
{{ $t("Enable_Amount") }}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item" @click="copyTemplateReference(index, ingredient)">
|
||||
<button type="button" class="dropdown-item"
|
||||
@click="copyTemplateReference(index, ingredient)">
|
||||
<i class="fas fa-code"></i>
|
||||
{{ $t("Copy_template_reference") }}
|
||||
</button>
|
||||
@@ -483,8 +539,10 @@
|
||||
</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 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>
|
||||
@@ -517,24 +575,39 @@
|
||||
{{ $t("Add_Step") }}
|
||||
</button>
|
||||
|
||||
<button type="button" v-b-modal:id_modal_sort class="btn btn-warning shadow-none"><i class="fas fa-sort-amount-down-alt fa-lg"></i></button>
|
||||
<button type="button" v-b-modal:id_modal_sort class="btn btn-warning shadow-none"><i
|
||||
class="fas fa-sort-amount-down-alt fa-lg"></i></button>
|
||||
</b-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<div class="row" v-if="recipe.steps.length === 0">
|
||||
<div class="col col-md-12 text-center">
|
||||
<b-button-group>
|
||||
<button type="button" @click="addStep(0)" class="btn btn-success shadow-none">
|
||||
{{ $t("Add_Step") }}
|
||||
</button>
|
||||
</b-button-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<!-- bottom buttons save/close/view -->
|
||||
<div class="row fixed-bottom p-2 b-2 border-top text-center" style="background: white" v-if="recipe !== undefined">
|
||||
<div class="row fixed-bottom p-2 b-2 border-top text-center" style="background: white"
|
||||
v-if="recipe !== undefined">
|
||||
<div class="col-md-3 col-6">
|
||||
<a :href="resolveDjangoUrl('delete_recipe', recipe.id)" class="btn btn-block btn-danger shadow-none">{{ $t("Delete") }}</a>
|
||||
<a :href="resolveDjangoUrl('delete_recipe', recipe.id)"
|
||||
class="btn btn-block btn-danger shadow-none">{{ $t("Delete") }}</a>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<a :href="resolveDjangoUrl('view_recipe', recipe.id)">
|
||||
@@ -542,12 +615,15 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<button type="button" @click="updateRecipe(false)" v-b-tooltip.hover :title="`${$t('Key_Ctrl')} + S`" class="btn btn-sm btn-block btn-info shadow-none">
|
||||
<button type="button" @click="updateRecipe(false)" v-b-tooltip.hover
|
||||
:title="`${$t('Key_Ctrl')} + S`" class="btn btn-sm btn-block btn-info shadow-none">
|
||||
{{ $t("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<button type="button" @click="updateRecipe(true)" v-b-tooltip.hover :title="`${$t('Key_Ctrl')} + ${$t('Key_Shift')} + S`" class="btn btn-sm btn-block btn-success shadow-none">
|
||||
<button type="button" @click="updateRecipe(true)" v-b-tooltip.hover
|
||||
:title="`${$t('Key_Ctrl')} + ${$t('Key_Shift')} + S`"
|
||||
class="btn btn-sm btn-block btn-success shadow-none">
|
||||
{{ $t("Save_and_View") }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -555,9 +631,11 @@
|
||||
|
||||
<!-- modal for sorting steps -->
|
||||
<b-modal id="id_modal_sort" v-bind:title="$t('Sort')" ok-only>
|
||||
<draggable :list="recipe.steps" group="step_sorter" :empty-insert-threshold="10" handle=".handle" @sort="sortSteps()" class="list-group" tag="ul">
|
||||
<draggable :list="recipe.steps" group="step_sorter" :empty-insert-threshold="10" handle=".handle"
|
||||
@sort="sortSteps()" class="list-group" tag="ul">
|
||||
<li class="list-group-item" v-for="(step, step_index) in recipe.steps" v-bind:key="step_index">
|
||||
<button type="button" class="btn btn-lg shadow-none handle"><i class="fas fa-arrows-alt-v"></i></button>
|
||||
<button type="button" class="btn btn-lg shadow-none handle"><i class="fas fa-arrows-alt-v"></i>
|
||||
</button>
|
||||
<template v-if="step.name !== ''">{{ step.name }}</template>
|
||||
<template v-else>{{ $t("Step") }} {{ step_index + 1 }}</template>
|
||||
</li>
|
||||
@@ -572,25 +650,34 @@
|
||||
@cancel="paste_ingredients = paste_step = undefined"
|
||||
@close="paste_ingredients = paste_step = undefined"
|
||||
>
|
||||
<b-form-textarea id="paste_ingredients" v-model="paste_ingredients" :placeholder="$t('paste_ingredients_placeholder')" rows="10"></b-form-textarea>
|
||||
<b-form-textarea id="paste_ingredients" v-model="paste_ingredients"
|
||||
:placeholder="$t('paste_ingredients_placeholder')" rows="10"></b-form-textarea>
|
||||
</b-modal>
|
||||
|
||||
<!-- form to create files on the fly -->
|
||||
<generic-modal-form :model="Models.USERFILE" :action="Actions.CREATE" :show="show_file_create" @finish-action="fileCreated" />
|
||||
<generic-modal-form :model="Models.USERFILE" :action="Actions.CREATE" :show="show_file_create"
|
||||
@finish-action="fileCreated"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from "vue"
|
||||
import { BootstrapVue } from "bootstrap-vue"
|
||||
import {BootstrapVue} from "bootstrap-vue"
|
||||
|
||||
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||
|
||||
import draggable from "vuedraggable"
|
||||
import { ApiMixin, resolveDjangoUrl, ResolveUrlMixin, StandardToasts, convertEnergyToCalories, energyHeading } from "@/utils/utils"
|
||||
import {
|
||||
ApiMixin,
|
||||
resolveDjangoUrl,
|
||||
ResolveUrlMixin,
|
||||
StandardToasts,
|
||||
convertEnergyToCalories,
|
||||
energyHeading
|
||||
} from "@/utils/utils"
|
||||
import Multiselect from "vue-multiselect"
|
||||
import { ApiApiFactory } from "@/utils/openapi/api"
|
||||
import {ApiApiFactory} from "@/utils/openapi/api"
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
|
||||
import VueMarkdownEditor from "@kangc/v-md-editor"
|
||||
@@ -615,7 +702,7 @@ Vue.use(BootstrapVue)
|
||||
export default {
|
||||
name: "RecipeEditView",
|
||||
mixins: [ResolveUrlMixin, ApiMixin],
|
||||
components: { Multiselect, LoadingSpinner, draggable, GenericModalForm },
|
||||
components: {Multiselect, LoadingSpinner, draggable, GenericModalForm},
|
||||
data() {
|
||||
return {
|
||||
recipe_id: window.RECIPE_ID,
|
||||
@@ -763,7 +850,10 @@ export default {
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_UPDATE)
|
||||
this.recipe_changed = false
|
||||
if (this.create_food) {
|
||||
apiFactory.createFood({ name: this.recipe.food_name, recipe: { id: this.recipe.id, name: this.recipe.name } })
|
||||
apiFactory.createFood({
|
||||
name: this.recipe.food_name,
|
||||
recipe: {id: this.recipe.id, name: this.recipe.name}
|
||||
})
|
||||
}
|
||||
if (view_after) {
|
||||
location.href = resolveDjangoUrl("view_recipe", this.recipe_id)
|
||||
@@ -852,12 +942,12 @@ export default {
|
||||
this.$nextTick(() => document.getElementById(`amount_${this.recipe.steps.indexOf(step)}_${step.ingredients.length - 1}`).select())
|
||||
},
|
||||
removeIngredient: function (step, ingredient) {
|
||||
if (confirm(this.$t("confirm_delete", { object: this.$t("Ingredient") }))) {
|
||||
if (confirm(this.$t("confirm_delete", {object: this.$t("Ingredient")}))) {
|
||||
step.ingredients = step.ingredients.filter((item) => item !== ingredient)
|
||||
}
|
||||
},
|
||||
removeStep: function (step) {
|
||||
if (confirm(this.$t("confirm_delete", { object: this.$t("Step") }))) {
|
||||
if (confirm(this.$t("confirm_delete", {object: this.$t("Step")}))) {
|
||||
this.recipe.steps = this.recipe.steps.filter((item) => item !== step)
|
||||
}
|
||||
},
|
||||
@@ -870,7 +960,7 @@ export default {
|
||||
let [tmp, step, id] = index.split("_")
|
||||
|
||||
let new_food = this.recipe.steps[step].ingredients[id]
|
||||
new_food.food = { name: tag }
|
||||
new_food.food = {name: tag}
|
||||
this.foods.push(new_food.food)
|
||||
this.recipe.steps[step].ingredients[id] = new_food
|
||||
},
|
||||
@@ -878,12 +968,12 @@ export default {
|
||||
let [tmp, step, id] = index.split("_")
|
||||
|
||||
let new_unit = this.recipe.steps[step].ingredients[id]
|
||||
new_unit.unit = { name: tag }
|
||||
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 }
|
||||
let new_keyword = {label: tag, name: tag}
|
||||
this.recipe.keywords.push(new_keyword)
|
||||
},
|
||||
searchKeywords: function (query) {
|
||||
@@ -906,7 +996,7 @@ export default {
|
||||
|
||||
this.files_loading = true
|
||||
apiFactory
|
||||
.listUserFiles({ query: { query: query } })
|
||||
.listUserFiles({query: {query: query}})
|
||||
.then((response) => {
|
||||
this.files = response.data
|
||||
this.files_loading = false
|
||||
@@ -918,7 +1008,7 @@ export default {
|
||||
},
|
||||
searchRecipes: function (query) {
|
||||
this.recipes_loading = true
|
||||
this.genericAPI(this.Models.RECIPE, this.Actions.LIST, { query: query })
|
||||
this.genericAPI(this.Models.RECIPE, this.Actions.LIST, {query: query})
|
||||
.then((result) => {
|
||||
this.recipes = result.data.results
|
||||
this.recipes_loading = false
|
||||
@@ -984,10 +1074,15 @@ export default {
|
||||
this.show_file_create = false
|
||||
},
|
||||
scrollToStep: function (step_index) {
|
||||
document.getElementById("id_step_" + step_index).scrollIntoView({ behavior: "smooth" })
|
||||
document.getElementById("id_step_" + step_index).scrollIntoView({behavior: "smooth"})
|
||||
},
|
||||
addNutrition: function () {
|
||||
this.recipe.nutrition = {}
|
||||
this.recipe.nutrition = {
|
||||
carbohydrates: 0,
|
||||
fats: 0,
|
||||
proteins: 0,
|
||||
calories: 0,
|
||||
}
|
||||
},
|
||||
removeNutrition: function () {
|
||||
this.recipe.nutrition = null
|
||||
@@ -1020,15 +1115,15 @@ export default {
|
||||
this.recipe.steps[step].ingredients_visible = true
|
||||
ing_list.forEach((ing) => {
|
||||
if (ing.trim() !== "") {
|
||||
this.genericPostAPI("api_ingredient_from_string", { text: ing }).then((result) => {
|
||||
this.genericPostAPI("api_ingredient_from_string", {text: ing}).then((result) => {
|
||||
let unit = null
|
||||
if (result.data.unit !== "") {
|
||||
unit = { name: result.data.unit }
|
||||
unit = {name: result.data.unit}
|
||||
}
|
||||
this.recipe.steps[step].ingredients.splice(order, 0, {
|
||||
amount: result.data.amount,
|
||||
unit: unit,
|
||||
food: { name: result.data.food },
|
||||
food: {name: result.data.food},
|
||||
note: result.data.note,
|
||||
original_text: ing,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div id="app" style="margin-bottom: 4vh">
|
||||
<RecipeSwitcher ref="ref_recipe_switcher" />
|
||||
<RecipeSwitcher ref="ref_recipe_switcher"/>
|
||||
<div class="row">
|
||||
<div class="col-12 col-xl-8 col-lg-10 offset-xl-2 offset-lg-1">
|
||||
<div class="row">
|
||||
@@ -8,15 +8,21 @@
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-10 col-xl-8 mt-3 mb-3">
|
||||
<b-input-group>
|
||||
<b-input class="form-control form-control-lg form-control-borderless form-control-search" v-model="search.search_input" v-bind:placeholder="$t('Search')"></b-input>
|
||||
<b-input
|
||||
class="form-control form-control-lg form-control-borderless form-control-search"
|
||||
v-model="search.search_input" v-bind:placeholder="$t('Search')"></b-input>
|
||||
<b-input-group-append>
|
||||
<b-button v-b-tooltip.hover :title="$t('show_sql')" @click="showSQL()" v-if="debug && ui.sql_debug">
|
||||
<b-button v-b-tooltip.hover :title="$t('show_sql')" @click="showSQL()"
|
||||
v-if="debug && ui.sql_debug">
|
||||
<i class="fas fa-bug" style="font-size: 1.5em"></i>
|
||||
</b-button>
|
||||
<b-button variant="light" v-b-tooltip.hover :title="$t('Random Recipes')" @click="openRandom()">
|
||||
<b-button variant="light" v-b-tooltip.hover :title="$t('Random Recipes')"
|
||||
@click="openRandom()">
|
||||
<i class="fas fa-dice-five" style="font-size: 1.5em"></i>
|
||||
</b-button>
|
||||
<b-button v-b-toggle.collapse_advanced_search v-b-tooltip.hover :title="$t('advanced_search_settings')" v-bind:variant="searchFiltered(true) ? 'danger' : 'primary'">
|
||||
<b-button v-b-toggle.collapse_advanced_search v-b-tooltip.hover
|
||||
:title="$t('advanced_search_settings')"
|
||||
v-bind:variant="searchFiltered(true) ? 'danger' : 'primary'">
|
||||
<!-- TODO consider changing this icon to a filter -->
|
||||
<i class="fas fa-caret-down" v-if="!search.advanced_search_visible"></i>
|
||||
<i class="fas fa-caret-up" v-if="search.advanced_search_visible"></i>
|
||||
@@ -26,15 +32,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-collapse id="collapse_advanced_search" class="mt-2 shadow-sm" v-model="search.advanced_search_visible">
|
||||
<b-collapse id="collapse_advanced_search" class="mt-2 shadow-sm"
|
||||
v-model="search.advanced_search_visible">
|
||||
<div class="card">
|
||||
<div class="card-body p-4">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<a class="btn btn-primary btn-block text-uppercase" :href="resolveDjangoUrl('new_recipe')">{{ $t("New_Recipe") }}</a>
|
||||
<a class="btn btn-primary btn-block text-uppercase"
|
||||
:href="resolveDjangoUrl('new_recipe')">{{ $t("New_Recipe") }}</a>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a class="btn btn-primary btn-block text-uppercase" :href="resolveDjangoUrl('data_import_url')">{{ $t("Import") }}</a>
|
||||
<a class="btn btn-primary btn-block text-uppercase"
|
||||
:href="resolveDjangoUrl('data_import_url')">{{ $t("Import") }}</a>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button
|
||||
@@ -53,99 +62,191 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<button id="id_settings_button" class="btn btn-primary btn-block text-uppercase"><i class="fas fa-cog fa-lg m-1"></i></button>
|
||||
<button id="id_settings_button"
|
||||
class="btn btn-primary btn-block text-uppercase"><i
|
||||
class="fas fa-cog fa-lg m-1"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-popover target="id_settings_button" triggers="click" placement="bottom">
|
||||
<b-tabs content-class="mt-1 text-nowrap" small>
|
||||
<b-tab :title="$t('Settings')" active :title-link-class="['mx-0']">
|
||||
<b-form-group v-bind:label="$t('Recently_Viewed')" label-for="popover-input-1" label-cols="8" class="mb-1">
|
||||
<b-form-input type="number" v-model="ui.recently_viewed" id="popover-input-1" size="sm" class="mt-1"></b-form-input>
|
||||
<b-form-group v-bind:label="$t('Recently_Viewed')"
|
||||
label-for="popover-input-1" label-cols="8" class="mb-1">
|
||||
<b-form-input type="number" v-model="ui.recently_viewed"
|
||||
id="popover-input-1" size="sm"
|
||||
class="mt-1"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group v-bind:label="$t('Recipes_per_page')" label-for="popover-input-page-count" label-cols="8" class="mb-1">
|
||||
<b-form-input type="number" v-model="ui.page_size" id="popover-input-page-count" size="sm" class="mt-1"></b-form-input>
|
||||
<b-form-group v-bind:label="$t('Recipes_per_page')"
|
||||
label-for="popover-input-page-count" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-input type="number" v-model="ui.page_size"
|
||||
id="popover-input-page-count" size="sm"
|
||||
class="mt-1"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group v-bind:label="$t('Meal_Plan')" label-for="popover-input-2" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_meal_plan" id="popover-input-2" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('Meal_Plan')" label-for="popover-input-2"
|
||||
label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_meal_plan"
|
||||
id="popover-input-2" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group v-if="ui.show_meal_plan" v-bind:label="$t('Meal_Plan_Days')" label-for="popover-input-5" label-cols="8" class="mb-1">
|
||||
<b-form-input type="number" v-model="ui.meal_plan_days" id="popover-input-5" size="sm" class="mt-1"></b-form-input>
|
||||
<b-form-group v-if="ui.show_meal_plan"
|
||||
v-bind:label="$t('Meal_Plan_Days')"
|
||||
label-for="popover-input-5" label-cols="8" class="mb-1">
|
||||
<b-form-input type="number" v-model="ui.meal_plan_days"
|
||||
id="popover-input-5" size="sm"
|
||||
class="mt-1"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group v-bind:label="$t('Sort_by_new')" label-for="popover-input-3" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.sort_by_new" id="popover-input-3" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('Sort_by_new')"
|
||||
label-for="popover-input-3" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.sort_by_new"
|
||||
id="popover-input-3" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<div class="row" style="margin-top: 1vh">
|
||||
<div class="col-12">
|
||||
<a :href="resolveDjangoUrl('view_settings') + '#search'">{{ $t("Search Settings") }}</a>
|
||||
<a :href="resolveDjangoUrl('view_settings') + '#search'">{{
|
||||
$t("Search Settings")
|
||||
}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab :title="$t('fields')" :title-link-class="['mx-0']">
|
||||
<b-form-group v-bind:label="$t('show_keywords')" label-for="popover-show_keywords" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_keywords" id="popover-show_keywords" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('show_keywords')"
|
||||
label-for="popover-show_keywords" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_keywords"
|
||||
id="popover-show_keywords" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('show_foods')" label-for="popover-show_foods" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_foods" id="popover-show_foods" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('show_foods')"
|
||||
label-for="popover-show_foods" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_foods"
|
||||
id="popover-show_foods" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('show_books')" label-for="popover-input-show_books" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_books" id="popover-input-show_books" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('show_books')"
|
||||
label-for="popover-input-show_books" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_books"
|
||||
id="popover-input-show_books" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('show_rating')" label-for="popover-show_rating" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_rating" id="popover-show_rating" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('show_rating')"
|
||||
label-for="popover-show_rating" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_rating"
|
||||
id="popover-show_rating" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('show_units')" label-for="popover-show_units" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_units" id="popover-show_units" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('show_units')"
|
||||
label-for="popover-show_units" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_units"
|
||||
id="popover-show_units" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('show_filters')" label-for="popover-show_filters" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_filters" id="popover-show_filters" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('show_filters')"
|
||||
label-for="popover-show_filters" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_filters"
|
||||
id="popover-show_filters" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('show_sortby')" label-for="popover-show_sortby" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_sortby" id="popover-show_sortby" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('show_sortby')"
|
||||
label-for="popover-show_sortby" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_sortby"
|
||||
id="popover-show_sortby" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('times_cooked')" label-for="popover-show_timescooked" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_timescooked" id="popover-show_cooked" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('times_cooked')"
|
||||
label-for="popover-show_timescooked" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_timescooked"
|
||||
id="popover-show_cooked" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('make_now')" label-for="popover-show_makenow" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_makenow" id="popover-show_makenow" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('make_now')"
|
||||
label-for="popover-show_makenow" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_makenow"
|
||||
id="popover-show_makenow" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('last_cooked')" label-for="popover-show_cookedon" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_cookedon" id="popover-show_cookedon" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('last_cooked')"
|
||||
label-for="popover-show_cookedon" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_cookedon"
|
||||
id="popover-show_cookedon" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('last_viewed')" label-for="popover-show_viewedon" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_viewedon" id="popover-show_viewedon" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('last_viewed')"
|
||||
label-for="popover-show_viewedon" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_viewedon"
|
||||
id="popover-show_viewedon" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('created_on')" label-for="popover-show_createdon" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_createdon" id="popover-show_createdon" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('created_on')"
|
||||
label-for="popover-show_createdon" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_createdon"
|
||||
id="popover-show_createdon" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('updatedon')" label-for="popover-show_updatedon" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_updatedon" id="popover-show_updatedon" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('updatedon')"
|
||||
label-for="popover-show_updatedon" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.show_updatedon"
|
||||
id="popover-show_updatedon" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
</b-tab>
|
||||
|
||||
<b-tab :title="$t('advanced')" :title-link-class="['mx-0']">
|
||||
<b-form-group v-bind:label="$t('remember_search')" label-for="popover-rem-search" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.remember_search" id="popover-rem-search" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('remember_search')"
|
||||
label-for="popover-rem-search" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.remember_search"
|
||||
id="popover-rem-search" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-if="ui.remember_search" v-bind:label="$t('remember_hours')" label-for="popover-input-rem-hours" label-cols="8" class="mb-1">
|
||||
<b-form-input type="number" v-model="ui.remember_hours" id="popover-rem-hours" size="sm" class="mt-1"></b-form-input>
|
||||
<b-form-group v-if="ui.remember_search"
|
||||
v-bind:label="$t('remember_hours')"
|
||||
label-for="popover-input-rem-hours" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-input type="number" v-model="ui.remember_hours"
|
||||
id="popover-rem-hours" size="sm"
|
||||
class="mt-1"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('tree_select')" label-for="popover-input-treeselect" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.tree_select" id="popover-input-treeselect" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-bind:label="$t('tree_select')"
|
||||
label-for="popover-input-treeselect" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.tree_select"
|
||||
id="popover-input-treeselect" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group v-if="debug" v-bind:label="$t('sql_debug')" label-for="popover-input-sqldebug" label-cols="8" class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.sql_debug" id="popover-input-sqldebug" size="sm" class="mt-2"></b-form-checkbox>
|
||||
<b-form-group v-if="debug" v-bind:label="$t('sql_debug')"
|
||||
label-for="popover-input-sqldebug" label-cols="8"
|
||||
class="mb-1">
|
||||
<b-form-checkbox switch v-model="ui.sql_debug"
|
||||
id="popover-input-sqldebug" size="sm"
|
||||
class="mt-2"></b-form-checkbox>
|
||||
</b-form-group>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
|
||||
<div class="row" style="margin-top: 1vh">
|
||||
<div class="col-12" style="text-align: right">
|
||||
<b-button size="sm" variant="secondary" style="margin-right: 8px" @click="$root.$emit('bv::hide::popover')">{{ $t("Close") }} </b-button>
|
||||
<b-button size="sm" variant="secondary" style="margin-right: 8px"
|
||||
@click="$root.$emit('bv::hide::popover')">{{ $t("Close") }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</b-popover>
|
||||
@@ -185,18 +286,23 @@
|
||||
<h6 class="mb-0" v-if="ui.expert_mode && search.keywords_fields > 1">
|
||||
{{ $t("Keywords") }}
|
||||
</h6>
|
||||
<span class="text-sm-left text-warning" v-if="ui.expert_mode && search.keywords_fields > 1 && hasDuplicateFilter(search.search_keywords, search.keywords_fields)">{{
|
||||
$t("warning_duplicate_filter")
|
||||
}}</span>
|
||||
<span class="text-sm-left text-warning"
|
||||
v-if="ui.expert_mode && search.keywords_fields > 1 && hasDuplicateFilter(search.search_keywords, search.keywords_fields)">{{
|
||||
$t("warning_duplicate_filter")
|
||||
}}</span>
|
||||
<div class="row" v-if="ui.show_keywords">
|
||||
<div class="col-12">
|
||||
<b-input-group class="mt-2" v-for="(k, a) in keywordFields" :key="a">
|
||||
<template #prepend v-if="ui.expert_mode">
|
||||
<b-input-group-text style="width: 3em" @click="addField('keywords', k)">
|
||||
<i class="fas fa-plus-circle text-primary" v-if="k == search.keywords_fields && k < 4" />
|
||||
<b-input-group-text style="width: 3em"
|
||||
@click="addField('keywords', k)">
|
||||
<i class="fas fa-plus-circle text-primary"
|
||||
v-if="k == search.keywords_fields && k < 4"/>
|
||||
</b-input-group-text>
|
||||
<b-input-group-text style="width: 3em" @click="removeField('keywords', k)">
|
||||
<i class="fas fa-minus-circle text-primary" v-if="k == search.keywords_fields && k > 1" />
|
||||
<b-input-group-text style="width: 3em"
|
||||
@click="removeField('keywords', k)">
|
||||
<i class="fas fa-minus-circle text-primary"
|
||||
v-if="k == search.keywords_fields && k > 1"/>
|
||||
</b-input-group-text>
|
||||
</template>
|
||||
<treeselect
|
||||
@@ -233,14 +339,20 @@
|
||||
switch
|
||||
style="width: 5em"
|
||||
>
|
||||
<span class="text-uppercase" v-if="search.search_keywords[a].operator">{{ $t("or") }}</span>
|
||||
<span class="text-uppercase"
|
||||
v-if="search.search_keywords[a].operator">{{
|
||||
$t("or")
|
||||
}}</span>
|
||||
<span class="text-uppercase" v-else>{{ $t("and") }}</span>
|
||||
</b-form-checkbox>
|
||||
</b-input-group-text>
|
||||
</b-input-group-append>
|
||||
<b-input-group-append v-if="ui.expert_mode">
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.search_keywords[a].not" name="check-button" @change="refreshData(false)" class="shadow-none">
|
||||
<b-form-checkbox v-model="search.search_keywords[a].not"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none">
|
||||
<span class="text-uppercase">{{ $t("not") }}</span>
|
||||
</b-form-checkbox>
|
||||
</b-input-group-text>
|
||||
@@ -253,18 +365,23 @@
|
||||
<h6 class="mt-2 mb-0" v-if="ui.expert_mode && search.foods_fields > 1">
|
||||
{{ $t("Foods") }}
|
||||
</h6>
|
||||
<span class="text-sm-left text-warning" v-if="ui.expert_mode && search.foods_fields > 1 && hasDuplicateFilter(search.search_foods, search.foods_fields)">{{
|
||||
$t("warning_duplicate_filter")
|
||||
}}</span>
|
||||
<span class="text-sm-left text-warning"
|
||||
v-if="ui.expert_mode && search.foods_fields > 1 && hasDuplicateFilter(search.search_foods, search.foods_fields)">{{
|
||||
$t("warning_duplicate_filter")
|
||||
}}</span>
|
||||
<div class="row" v-if="ui.show_foods">
|
||||
<div class="col-12">
|
||||
<b-input-group class="mt-2" v-for="(f, i) in foodFields" :key="i">
|
||||
<template #prepend v-if="ui.expert_mode">
|
||||
<b-input-group-text style="width: 3em" @click="addField('foods', f)">
|
||||
<i class="fas fa-plus-circle text-primary" v-if="f == search.foods_fields && f < 4" />
|
||||
<b-input-group-text style="width: 3em"
|
||||
@click="addField('foods', f)">
|
||||
<i class="fas fa-plus-circle text-primary"
|
||||
v-if="f == search.foods_fields && f < 4"/>
|
||||
</b-input-group-text>
|
||||
<b-input-group-text style="width: 3em" @click="removeField('foods', f)">
|
||||
<i class="fas fa-minus-circle text-primary" v-if="f == search.foods_fields && f > 1" />
|
||||
<b-input-group-text style="width: 3em"
|
||||
@click="removeField('foods', f)">
|
||||
<i class="fas fa-minus-circle text-primary"
|
||||
v-if="f == search.foods_fields && f > 1"/>
|
||||
</b-input-group-text>
|
||||
</template>
|
||||
<treeselect
|
||||
@@ -293,15 +410,24 @@
|
||||
/>
|
||||
<b-input-group-append>
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.search_foods[i].operator" name="check-button" @change="refreshData(false)" class="shadow-none" switch style="width: 5em">
|
||||
<span class="text-uppercase" v-if="search.search_foods[i].operator">{{ $t("or") }}</span>
|
||||
<b-form-checkbox v-model="search.search_foods[i].operator"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none" switch style="width: 5em">
|
||||
<span class="text-uppercase"
|
||||
v-if="search.search_foods[i].operator">{{
|
||||
$t("or")
|
||||
}}</span>
|
||||
<span class="text-uppercase" v-else>{{ $t("and") }}</span>
|
||||
</b-form-checkbox>
|
||||
</b-input-group-text>
|
||||
</b-input-group-append>
|
||||
<b-input-group-append v-if="ui.expert_mode">
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.search_foods[i].not" name="check-button" @change="refreshData(false)" class="shadow-none">
|
||||
<b-form-checkbox v-model="search.search_foods[i].not"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none">
|
||||
<span class="text-uppercase">{{ $t("not") }}</span>
|
||||
</b-form-checkbox>
|
||||
</b-input-group-text>
|
||||
@@ -314,18 +440,23 @@
|
||||
<h6 class="mt-2 mb-0" v-if="ui.expert_mode && search.books_fields > 1">
|
||||
{{ $t("Books") }}
|
||||
</h6>
|
||||
<span class="text-sm-left text-warning" v-if="ui.expert_mode && search.books_fields > 1 && hasDuplicateFilter(search.search_books, search.books_fields)">{{
|
||||
$t("warning_duplicate_filter")
|
||||
}}</span>
|
||||
<span class="text-sm-left text-warning"
|
||||
v-if="ui.expert_mode && search.books_fields > 1 && hasDuplicateFilter(search.search_books, search.books_fields)">{{
|
||||
$t("warning_duplicate_filter")
|
||||
}}</span>
|
||||
<div class="row" v-if="ui.show_books">
|
||||
<div class="col-12">
|
||||
<b-input-group class="mt-2" v-for="(b, i) in bookFields" :key="i">
|
||||
<template #prepend v-if="ui.expert_mode">
|
||||
<b-input-group-text style="width: 3em" @click="addField('books', b)">
|
||||
<i class="fas fa-plus-circle text-primary" v-if="b == search.books_fields && b < 4" />
|
||||
<b-input-group-text style="width: 3em"
|
||||
@click="addField('books', b)">
|
||||
<i class="fas fa-plus-circle text-primary"
|
||||
v-if="b == search.books_fields && b < 4"/>
|
||||
</b-input-group-text>
|
||||
<b-input-group-text style="width: 3em" @click="removeField('books', b)">
|
||||
<i class="fas fa-minus-circle text-primary" v-if="b == search.books_fields && b > 1" />
|
||||
<b-input-group-text style="width: 3em"
|
||||
@click="removeField('books', b)">
|
||||
<i class="fas fa-minus-circle text-primary"
|
||||
v-if="b == search.books_fields && b > 1"/>
|
||||
</b-input-group-text>
|
||||
</template>
|
||||
<generic-multiselect
|
||||
@@ -339,15 +470,24 @@
|
||||
></generic-multiselect>
|
||||
<b-input-group-append>
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.search_books[i].operator" name="check-button" @change="refreshData(false)" class="shadow-none" style="width: 5em" switch>
|
||||
<span class="text-uppercase" v-if="search.search_books[i].operator">{{ $t("or") }}</span>
|
||||
<b-form-checkbox v-model="search.search_books[i].operator"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none" style="width: 5em" switch>
|
||||
<span class="text-uppercase"
|
||||
v-if="search.search_books[i].operator">{{
|
||||
$t("or")
|
||||
}}</span>
|
||||
<span class="text-uppercase" v-else>{{ $t("and") }}</span>
|
||||
</b-form-checkbox>
|
||||
</b-input-group-text>
|
||||
</b-input-group-append>
|
||||
<b-input-group-append v-if="ui.expert_mode">
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.search_books[i].not" name="check-button" @change="refreshData(false)" class="shadow-none">
|
||||
<b-form-checkbox v-model="search.search_books[i].not"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none">
|
||||
<span class="text-uppercase">{{ $t("not") }}</span>
|
||||
</b-form-checkbox>
|
||||
</b-input-group-text>
|
||||
@@ -371,8 +511,12 @@
|
||||
/>
|
||||
<b-input-group-append>
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.search_rating_gte" name="check-button" @change="refreshData(false)" class="shadow-none" switch style="width: 5em">
|
||||
<span class="text-uppercase" v-if="search.search_rating_gte">>=</span>
|
||||
<b-form-checkbox v-model="search.search_rating_gte"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none" switch style="width: 5em">
|
||||
<span class="text-uppercase"
|
||||
v-if="search.search_rating_gte">>=</span>
|
||||
<span class="text-uppercase" v-else><=</span>
|
||||
</b-form-checkbox>
|
||||
</b-input-group-text>
|
||||
@@ -395,8 +539,13 @@
|
||||
></generic-multiselect>
|
||||
<b-input-group-append>
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.search_units_or" name="check-button" @change="refreshData(false)" class="shadow-none" style="width: 4em" switch>
|
||||
<span class="text-uppercase" v-if="search.search_units_or">{{ $t("or") }}</span>
|
||||
<b-form-checkbox v-model="search.search_units_or"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none" style="width: 4em" switch>
|
||||
<span class="text-uppercase" v-if="search.search_units_or">{{
|
||||
$t("or")
|
||||
}}</span>
|
||||
<span class="text-uppercase" v-else>{{ $t("and") }}</span>
|
||||
</b-form-checkbox>
|
||||
</b-input-group-text>
|
||||
@@ -406,17 +555,23 @@
|
||||
</div>
|
||||
|
||||
<!-- special switches -->
|
||||
<div class="row g-0" v-if="ui.show_timescooked || ui.show_makenow || ui.show_cookedon">
|
||||
<div class="row g-0"
|
||||
v-if="ui.show_timescooked || ui.show_makenow || ui.show_cookedon">
|
||||
<div class="col-12">
|
||||
<b-input-group class="mt-2">
|
||||
<!-- times cooked -->
|
||||
<b-input-group-prepend is-text v-if="ui.show_timescooked">
|
||||
{{ $t("times_cooked") }}
|
||||
</b-input-group-prepend>
|
||||
<b-form-input id="timescooked" type="number" min="0" v-model="search.timescooked" v-if="ui.show_timescooked"></b-form-input>
|
||||
<b-form-input id="timescooked" type="number" min="0"
|
||||
v-model="search.timescooked"
|
||||
v-if="ui.show_timescooked"></b-form-input>
|
||||
<b-input-group-append v-if="ui.show_timescooked">
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.timescooked_gte" name="check-button" @change="refreshData(false)" class="shadow-none" switch style="width: 4em">
|
||||
<b-form-checkbox v-model="search.timescooked_gte"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none" switch style="width: 4em">
|
||||
<span class="text-uppercase" v-if="search.timescooked_gte">>=</span>
|
||||
<span class="text-uppercase" v-else><=</span>
|
||||
</b-form-checkbox>
|
||||
@@ -435,7 +590,10 @@
|
||||
@input="refreshData(false)"
|
||||
/>
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.cookedon_gte" name="check-button" @change="refreshData(false)" class="shadow-none" switch style="width: 4em">
|
||||
<b-form-checkbox v-model="search.cookedon_gte"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none" switch style="width: 4em">
|
||||
<span class="text-uppercase" v-if="search.cookedon_gte">>=</span>
|
||||
<span class="text-uppercase" v-else><=</span>
|
||||
</b-form-checkbox>
|
||||
@@ -455,7 +613,10 @@
|
||||
@input="refreshData(false)"
|
||||
/>
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.createdon_gte" name="check-button" @change="refreshData(false)" class="shadow-none" switch style="width: 4em">
|
||||
<b-form-checkbox v-model="search.createdon_gte"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none" switch style="width: 4em">
|
||||
<span class="text-uppercase" v-if="search.createdon_gte">>=</span>
|
||||
<span class="text-uppercase" v-else><=</span>
|
||||
</b-form-checkbox>
|
||||
@@ -473,7 +634,10 @@
|
||||
@input="refreshData(false)"
|
||||
/>
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.viewedon_gte" name="check-button" @change="refreshData(false)" class="shadow-none" switch style="width: 4em">
|
||||
<b-form-checkbox v-model="search.viewedon_gte"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none" switch style="width: 4em">
|
||||
<span class="text-uppercase" v-if="search.viewedon_gte">>=</span>
|
||||
<span class="text-uppercase" v-else><=</span>
|
||||
</b-form-checkbox>
|
||||
@@ -491,7 +655,10 @@
|
||||
@input="refreshData(false)"
|
||||
/>
|
||||
<b-input-group-text>
|
||||
<b-form-checkbox v-model="search.updatedon_gte" name="check-button" @change="refreshData(false)" class="shadow-none" switch style="width: 4em">
|
||||
<b-form-checkbox v-model="search.updatedon_gte"
|
||||
name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none" switch style="width: 4em">
|
||||
<span class="text-uppercase" v-if="search.updatedon_gte">>=</span>
|
||||
<span class="text-uppercase" v-else><=</span>
|
||||
</b-form-checkbox>
|
||||
@@ -500,7 +667,9 @@
|
||||
<b-input-group-append v-if="ui.show_makenow">
|
||||
<b-input-group-text>
|
||||
{{ $t("make_now") }}
|
||||
<b-form-checkbox v-model="search.makenow" name="check-button" @change="refreshData(false)" class="shadow-none" switch style="width: 4em" />
|
||||
<b-form-checkbox v-model="search.makenow" name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none" switch style="width: 4em"/>
|
||||
</b-input-group-text>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
@@ -510,14 +679,16 @@
|
||||
<!-- Buttons -->
|
||||
<div class="row justify-content-end small">
|
||||
<div class="col-auto">
|
||||
<b-button class="my-0" variant="link" size="sm" @click="search.explain_visible = !search.explain_visible">
|
||||
<b-button class="my-0" variant="link" size="sm"
|
||||
@click="search.explain_visible = !search.explain_visible">
|
||||
<div v-if="!search.explain_visible">
|
||||
<i class="far fa-eye"></i>
|
||||
{{ $t("explain") }}
|
||||
</div>
|
||||
<div v-else><i class="far fa-eye-slash"></i> {{ $t("explain") }}</div>
|
||||
</b-button>
|
||||
<b-button class="my-0" variant="link" size="sm" @click="ui.expert_mode = !ui.expert_mode">
|
||||
<b-button class="my-0" variant="link" size="sm"
|
||||
@click="ui.expert_mode = !ui.expert_mode">
|
||||
<div v-if="!ui.expert_mode">
|
||||
<i class="fas fa-circle"></i>
|
||||
{{ $t("expert_mode") }}
|
||||
@@ -540,20 +711,21 @@
|
||||
<!-- TODO find a way to localize this that works without explaining localization to each language translator -->
|
||||
Show all recipes that are matched
|
||||
<span v-if="search.search_input">
|
||||
by <i>{{ search.search_input }}</i> <br />
|
||||
by <i>{{ search.search_input }}</i> <br/>
|
||||
</span>
|
||||
<span v-else> without any search term <br /> </span>
|
||||
<span v-else> without any search term <br/> </span>
|
||||
|
||||
<span v-if="search.search_internal"> and are <span class="text-success">internal</span> <br /></span>
|
||||
<span v-if="search.search_internal"> and are <span class="text-success">internal</span> <br/></span>
|
||||
|
||||
<span v-for="k in search.search_keywords" v-bind:key="k.id">
|
||||
<template v-if="k.items.length > 0">
|
||||
and
|
||||
<b v-if="k.not">don't</b>
|
||||
contain
|
||||
<b v-if="k.operator">any</b><b v-else>all</b> of the following <span class="text-success">keywords</span>:
|
||||
<b v-if="k.operator">any</b><b
|
||||
v-else>all</b> of the following <span class="text-success">keywords</span>:
|
||||
<i>{{ k.items.flatMap((x) => x.name).join(", ") }}</i>
|
||||
<br />
|
||||
<br/>
|
||||
</template>
|
||||
</span>
|
||||
|
||||
@@ -562,9 +734,11 @@
|
||||
and
|
||||
<b v-if="k.not">don't</b>
|
||||
contain
|
||||
<b v-if="k.operator">any</b><b v-else>all</b> of the following <span class="text-success">foods</span>:
|
||||
<b v-if="k.operator">any</b><b
|
||||
v-else>all</b> of the following <span
|
||||
class="text-success">foods</span>:
|
||||
<i>{{ k.items.flatMap((x) => x.name).join(", ") }}</i>
|
||||
<br />
|
||||
<br/>
|
||||
</template>
|
||||
</span>
|
||||
|
||||
@@ -573,40 +747,48 @@
|
||||
and
|
||||
<b v-if="k.not">don't</b>
|
||||
contain
|
||||
<b v-if="k.operator">any</b><b v-else>all</b> of the following <span class="text-success">books</span>:
|
||||
<b v-if="k.operator">any</b><b
|
||||
v-else>all</b> of the following <span
|
||||
class="text-success">books</span>:
|
||||
<i>{{ k.items.flatMap((x) => x.name).join(", ") }}</i>
|
||||
<br />
|
||||
<br/>
|
||||
</template>
|
||||
</span>
|
||||
|
||||
<span v-if="search.makenow"> and you can <span class="text-success">make right now</span> (based on the on hand flag) <br /></span>
|
||||
<span v-if="search.makenow"> and you can <span class="text-success">make right now</span> (based on the on hand flag) <br/></span>
|
||||
|
||||
<span v-if="search.search_units.length > 0">
|
||||
and contain <b v-if="search.search_units_or">any</b><b v-else>all</b> of the following <span class="text-success">units</span>:
|
||||
and contain <b v-if="search.search_units_or">any</b><b
|
||||
v-else>all</b> of the following <span
|
||||
class="text-success">units</span>:
|
||||
<i>{{ search.search_units.flatMap((x) => x.name).join(", ") }}</i
|
||||
><br />
|
||||
><br/>
|
||||
</span>
|
||||
|
||||
<span v-if="search.search_rating !== undefined">
|
||||
and have a <span class="text-success">rating</span> <template v-if="search.search_rating_gte">greater than</template><template v-else> less than</template> or
|
||||
equal to {{ search.search_rating }}<br />
|
||||
and have a <span class="text-success">rating</span> <template
|
||||
v-if="search.search_rating_gte">greater than</template><template
|
||||
v-else> less than</template> or
|
||||
equal to {{ search.search_rating }}<br/>
|
||||
</span>
|
||||
|
||||
<span v-if="search.lastcooked !== undefined">
|
||||
and have been <span class="text-success">last cooked</span> <template v-if="search.lastcooked_gte"> after</template><template v-else> before</template>
|
||||
and have been <span class="text-success">last cooked</span> <template
|
||||
v-if="search.lastcooked_gte"> after</template><template v-else> before</template>
|
||||
<i>{{ search.lastcooked }}</i
|
||||
><br />
|
||||
><br/>
|
||||
</span>
|
||||
|
||||
<span v-if="search.timescooked !== undefined">
|
||||
and have <span class="text-success">been cooked</span> <template v-if="search.timescooked_gte"> at least</template><template v-else> less than</template> or
|
||||
equal to<i>{{ search.timescooked }}</i> times <br />
|
||||
and have <span class="text-success">been cooked</span> <template
|
||||
v-if="search.timescooked_gte"> at least</template><template v-else> less than</template> or
|
||||
equal to<i>{{ search.timescooked }}</i> times <br/>
|
||||
</span>
|
||||
|
||||
<span v-if="search.sort_order.length > 0">
|
||||
<span class="text-success">order</span> by
|
||||
<i>{{ search.sort_order.flatMap((x) => x.text).join(", ") }}</i>
|
||||
<br />
|
||||
<br/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -617,33 +799,37 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="recipes.length > 0">
|
||||
<div class="row align-content-center">
|
||||
<div class="col col-md-6" style="margin-top: 2vh">
|
||||
<b-dropdown id="sortby" :text="sortByLabel" variant="link" toggle-class="text-decoration-none " class="m-0 p-0">
|
||||
<div v-for="o in sortOptions" :key="o.id">
|
||||
<b-dropdown-item
|
||||
v-on:click="
|
||||
<div class="row align-content-center">
|
||||
<div class="col col-md-6" style="margin-top: 2vh">
|
||||
<b-dropdown id="sortby" :text="sortByLabel" variant="link" toggle-class="text-decoration-none "
|
||||
class="m-0 p-0">
|
||||
<div v-for="o in sortOptions" :key="o.id">
|
||||
<b-dropdown-item
|
||||
v-on:click="
|
||||
search.sort_order = [o]
|
||||
refreshData(false)
|
||||
"
|
||||
>
|
||||
<span>{{ o.text }}</span>
|
||||
</b-dropdown-item>
|
||||
</div>
|
||||
</b-dropdown>
|
||||
</div>
|
||||
<div class="col col-md-6 text-right" style="margin-top: 2vh">
|
||||
>
|
||||
<span>{{ o.text }}</span>
|
||||
</b-dropdown-item>
|
||||
</div>
|
||||
</b-dropdown>
|
||||
</div>
|
||||
<div class="col col-md-6 text-right" style="margin-top: 2vh">
|
||||
<span class="text-muted">
|
||||
{{ $t("Page") }} {{ search.pagination_page }}/{{ Math.ceil(pagination_count / ui.page_size) }}
|
||||
{{ $t("Page") }} {{
|
||||
search.pagination_page
|
||||
}}/{{ Math.ceil(pagination_count / ui.page_size) }}
|
||||
<a href="#" @click="resetSearch()"><i class="fas fa-times-circle"></i> {{ $t("Reset") }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="recipes.length > 0">
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-gap: 0.8rem">
|
||||
<div
|
||||
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-gap: 0.8rem">
|
||||
<template v-if="!searchFiltered()">
|
||||
<recipe-card
|
||||
v-bind:key="`mp_${m.id}`"
|
||||
@@ -654,14 +840,17 @@
|
||||
footer_icon="far fa-calendar-alt"
|
||||
></recipe-card>
|
||||
</template>
|
||||
<recipe-card v-for="r in recipes" v-bind:key="r.id" :recipe="r" :footer_text="isRecentOrNew(r)[0]" :footer_icon="isRecentOrNew(r)[1]"></recipe-card>
|
||||
<recipe-card v-for="r in recipes" v-bind:key="r.id" :recipe="r"
|
||||
:footer_text="isRecentOrNew(r)[0]"
|
||||
:footer_icon="isRecentOrNew(r)[1]"></recipe-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh" v-if="!random_search">
|
||||
<div class="col col-md-12">
|
||||
<b-pagination pills v-model="search.pagination_page" :total-rows="pagination_count" :per-page="ui.page_size" @change="pageChange" align="center"></b-pagination>
|
||||
<b-pagination pills v-model="search.pagination_page" :total-rows="pagination_count"
|
||||
:per-page="ui.page_size" @change="pageChange" align="center"></b-pagination>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2 d-none d-md-block"></div>
|
||||
@@ -681,7 +870,9 @@
|
||||
{{ $t("search_import_help_text") }}
|
||||
</b-card-text>
|
||||
|
||||
<b-button variant="primary" :href="resolveDjangoUrl('data_import_url')"><i class="fas fa-file-import"></i> {{ $t("Import") }} </b-button>
|
||||
<b-button variant="primary" :href="resolveDjangoUrl('data_import_url')"><i
|
||||
class="fas fa-file-import"></i> {{ $t("Import") }}
|
||||
</b-button>
|
||||
</b-card>
|
||||
|
||||
<b-card v-bind:title="$t('Create')" class="text-center">
|
||||
@@ -689,7 +880,9 @@
|
||||
{{ $t("search_create_help_text") }}
|
||||
</b-card-text>
|
||||
|
||||
<b-button variant="primary" :href="resolveDjangoUrl('new_recipe')"><i class="fas fa-plus"></i> {{ $t("Create") }} </b-button>
|
||||
<b-button variant="primary" :href="resolveDjangoUrl('new_recipe')"><i
|
||||
class="fas fa-plus"></i> {{ $t("Create") }}
|
||||
</b-button>
|
||||
</b-card>
|
||||
</b-card-group>
|
||||
</div>
|
||||
@@ -702,17 +895,17 @@
|
||||
|
||||
<script>
|
||||
import Vue from "vue"
|
||||
import { BootstrapVue } from "bootstrap-vue"
|
||||
import {BootstrapVue} from "bootstrap-vue"
|
||||
import VueCookies from "vue-cookies"
|
||||
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||
|
||||
import moment from "moment"
|
||||
import _debounce from "lodash/debounce"
|
||||
import Multiselect from "vue-multiselect"
|
||||
import { Treeselect, LOAD_CHILDREN_OPTIONS } from "@riophae/vue-treeselect"
|
||||
import {Treeselect, LOAD_CHILDREN_OPTIONS} from "@riophae/vue-treeselect"
|
||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
|
||||
|
||||
import { ApiMixin, ResolveUrlMixin, StandardToasts, ToastMixin } from "@/utils/utils"
|
||||
import {ApiMixin, ResolveUrlMixin, StandardToasts, ToastMixin} from "@/utils/utils"
|
||||
import LoadingSpinner from "@/components/LoadingSpinner" // TODO: is this deprecated?
|
||||
import RecipeCard from "@/components/RecipeCard"
|
||||
import GenericMultiselect from "@/components/GenericMultiselect"
|
||||
@@ -727,13 +920,13 @@ let UI_COOKIE_NAME = "ui_search_settings"
|
||||
export default {
|
||||
name: "RecipeSearchView",
|
||||
mixins: [ResolveUrlMixin, ApiMixin, ToastMixin],
|
||||
components: { GenericMultiselect, RecipeCard, Treeselect, RecipeSwitcher, Multiselect },
|
||||
components: {GenericMultiselect, RecipeCard, Treeselect, RecipeSwitcher, Multiselect},
|
||||
data() {
|
||||
return {
|
||||
// this.Models and this.Actions inherited from ApiMixin
|
||||
recipes: [],
|
||||
recipes_loading: true,
|
||||
facets: { Books: [], Foods: [], Keywords: [] },
|
||||
facets: {Books: [], Foods: [], Keywords: []},
|
||||
meal_plans: [],
|
||||
last_viewed_recipes: [],
|
||||
sortMenu: false,
|
||||
@@ -744,22 +937,22 @@ export default {
|
||||
search_input: "",
|
||||
search_internal: false,
|
||||
search_keywords: [
|
||||
{ items: [], operator: true, not: false },
|
||||
{ items: [], operator: false, not: false },
|
||||
{ items: [], operator: true, not: true },
|
||||
{ items: [], operator: false, not: true },
|
||||
{items: [], operator: true, not: false},
|
||||
{items: [], operator: false, not: false},
|
||||
{items: [], operator: true, not: true},
|
||||
{items: [], operator: false, not: true},
|
||||
],
|
||||
search_foods: [
|
||||
{ items: [], operator: true, not: false },
|
||||
{ items: [], operator: false, not: false },
|
||||
{ items: [], operator: true, not: true },
|
||||
{ items: [], operator: false, not: true },
|
||||
{items: [], operator: true, not: false},
|
||||
{items: [], operator: false, not: false},
|
||||
{items: [], operator: true, not: true},
|
||||
{items: [], operator: false, not: true},
|
||||
],
|
||||
search_books: [
|
||||
{ items: [], operator: true, not: false },
|
||||
{ items: [], operator: false, not: false },
|
||||
{ items: [], operator: true, not: true },
|
||||
{ items: [], operator: false, not: true },
|
||||
{items: [], operator: true, not: false},
|
||||
{items: [], operator: false, not: false},
|
||||
{items: [], operator: true, not: true},
|
||||
{items: [], operator: false, not: true},
|
||||
],
|
||||
search_units: [],
|
||||
search_units_or: true,
|
||||
@@ -868,12 +1061,12 @@ export default {
|
||||
}
|
||||
|
||||
return [
|
||||
{ id: 5, label: "⭐⭐⭐⭐⭐" + ratingCount(this.facets.Ratings?.["5.0"] ?? 0) + label(5) },
|
||||
{ id: 4, label: "⭐⭐⭐⭐ " + ratingCount(this.facets.Ratings?.["4.0"] ?? 0) + label() },
|
||||
{ id: 3, label: "⭐⭐⭐ " + ratingCount(this.facets.Ratings?.["3.0"] ?? 0) + label() },
|
||||
{ id: 2, label: "⭐⭐ " + ratingCount(this.facets.Ratings?.["2.0"] ?? 0) + label() },
|
||||
{ id: 1, label: "⭐ " + ratingCount(this.facets.Ratings?.["1.0"] ?? 0) + label(1) },
|
||||
{ id: 0, label: this.$t("Unrated") + ratingCount(this.facets.Ratings?.["0.0"] ?? 0) },
|
||||
{id: 5, label: "⭐⭐⭐⭐⭐" + ratingCount(this.facets.Ratings?.["5.0"] ?? 0) + label(5)},
|
||||
{id: 4, label: "⭐⭐⭐⭐ " + ratingCount(this.facets.Ratings?.["4.0"] ?? 0) + label()},
|
||||
{id: 3, label: "⭐⭐⭐ " + ratingCount(this.facets.Ratings?.["3.0"] ?? 0) + label()},
|
||||
{id: 2, label: "⭐⭐ " + ratingCount(this.facets.Ratings?.["2.0"] ?? 0) + label()},
|
||||
{id: 1, label: "⭐ " + ratingCount(this.facets.Ratings?.["1.0"] ?? 0) + label(1)},
|
||||
{id: 0, label: this.$t("Unrated") + ratingCount(this.facets.Ratings?.["0.0"] ?? 0)},
|
||||
]
|
||||
},
|
||||
keywordFields: function () {
|
||||
@@ -936,22 +1129,22 @@ export default {
|
||||
this.facets.Keywords = []
|
||||
for (let x of urlParams.getAll("keyword")) {
|
||||
this.search.search_keywords[0].items.push(Number.parseInt(x))
|
||||
this.facets.Keywords.push({ id: x, name: "loading..." })
|
||||
this.facets.Keywords.push({id: x, name: "loading..."})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: figure out how to find nested items and load keyword/food children for that branch
|
||||
// probably a backend change in facets to pre-load children of nested items
|
||||
for (let x of this.search.search_foods.map((x) => x.items).flat()) {
|
||||
this.facets.Foods.push({ id: x, name: "loading..." })
|
||||
this.facets.Foods.push({id: x, name: "loading..."})
|
||||
}
|
||||
|
||||
for (let x of this.search.search_keywords.map((x) => x.items).flat()) {
|
||||
this.facets.Keywords.push({ id: x, name: "loading..." })
|
||||
this.facets.Keywords.push({id: x, name: "loading..."})
|
||||
}
|
||||
|
||||
for (let x of this.search.search_books.map((x) => x.items).flat()) {
|
||||
this.facets.Books.push({ id: x, name: "loading..." })
|
||||
this.facets.Books.push({id: x, name: "loading..."})
|
||||
}
|
||||
|
||||
this.loadMealPlan()
|
||||
@@ -1001,13 +1194,13 @@ export default {
|
||||
"ui.expert_mode": function (newVal, oldVal) {
|
||||
if (!newVal) {
|
||||
this.search.search_keywords = this.search.search_keywords.map((x) => {
|
||||
return { ...x, not: false }
|
||||
return {...x, not: false}
|
||||
})
|
||||
this.search.search_foods = this.search.search_foods.map((x) => {
|
||||
return { ...x, not: false }
|
||||
return {...x, not: false}
|
||||
})
|
||||
this.search.search_books = this.search.search_books.map((x) => {
|
||||
return { ...x, not: false }
|
||||
return {...x, not: false}
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -1077,13 +1270,13 @@ export default {
|
||||
},
|
||||
resetSearch: function (filter = undefined) {
|
||||
this.search.search_keywords = this.search.search_keywords.map((x) => {
|
||||
return { ...x, items: [] }
|
||||
return {...x, items: []}
|
||||
})
|
||||
this.search.search_foods = this.search.search_foods.map((x) => {
|
||||
return { ...x, items: [] }
|
||||
return {...x, items: []}
|
||||
})
|
||||
this.search.search_books = this.search.search_books.map((x) => {
|
||||
return { ...x, items: [] }
|
||||
return {...x, items: []}
|
||||
})
|
||||
this.search.search_input = filter?.query ?? ""
|
||||
this.search.search_internal = filter?.internal ?? false
|
||||
@@ -1138,12 +1331,12 @@ export default {
|
||||
if (!this.ui.tree_select) {
|
||||
return
|
||||
}
|
||||
let params = { hash: hash }
|
||||
let params = {hash: hash}
|
||||
if (facet) {
|
||||
params[facet] = id
|
||||
}
|
||||
return this.genericGetAPI("api_get_facets", params).then((response) => {
|
||||
this.facets = { ...this.facets, ...response.data.facets }
|
||||
this.facets = {...this.facets, ...response.data.facets}
|
||||
})
|
||||
},
|
||||
showSQL: function () {
|
||||
@@ -1154,14 +1347,14 @@ export default {
|
||||
})
|
||||
},
|
||||
// TODO refactor to combine with load KeywordChildren
|
||||
loadFoodChildren({ action, parentNode, callback }) {
|
||||
loadFoodChildren({action, parentNode, callback}) {
|
||||
if (action === LOAD_CHILDREN_OPTIONS) {
|
||||
if (this.facets?.cache_key) {
|
||||
this.getFacets(this.facets.cache_key, "food", parentNode.id).then(callback())
|
||||
}
|
||||
}
|
||||
},
|
||||
loadKeywordChildren({ action, parentNode, callback }) {
|
||||
loadKeywordChildren({action, parentNode, callback}) {
|
||||
if (action === LOAD_CHILDREN_OPTIONS) {
|
||||
if (this.facets?.cache_key) {
|
||||
this.getFacets(this.facets.cache_key, "keyword", parentNode.id).then(callback())
|
||||
@@ -1172,7 +1365,7 @@ export default {
|
||||
return
|
||||
},
|
||||
buildParams: function (random) {
|
||||
let params = { options: { query: {} }, page: this.search.pagination_page, pageSize: this.ui.page_size }
|
||||
let params = {options: {query: {}}, page: this.search.pagination_page, pageSize: this.ui.page_size}
|
||||
if (this.search.search_filter) {
|
||||
params.options.query.filter = this.search.search_filter.id
|
||||
return params
|
||||
@@ -1293,7 +1486,7 @@ export default {
|
||||
;["page", "pageSize"].forEach((key) => {
|
||||
delete search[key]
|
||||
})
|
||||
search = { ...search, ...search.options.query }
|
||||
search = {...search, ...search.options.query}
|
||||
console.log("after concat", search)
|
||||
let params = {
|
||||
name: filtername,
|
||||
|
||||
@@ -440,7 +440,6 @@
|
||||
:initial_selection="settings.shopping_share"
|
||||
label="username"
|
||||
:multiple="true"
|
||||
:allow_create="false"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
:placeholder="$t('User')"
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div v-if="recipes !== {}">
|
||||
<div id="switcher" class="align-center">
|
||||
<i class="btn btn-primary fas fa-receipt fa-xl fa-fw shadow-none btn-circle" v-b-toggle.related-recipes />
|
||||
<i class="btn btn-primary fas fa-receipt fa-xl fa-fw shadow-none btn-circle" v-b-toggle.related-recipes/>
|
||||
</div>
|
||||
<b-sidebar id="related-recipes" backdrop right bottom no-header shadow="sm" style="z-index: 10000" @shown="updatePinnedRecipes()">
|
||||
<b-sidebar id="related-recipes" backdrop right bottom no-header shadow="sm" style="z-index: 10000"
|
||||
@shown="updatePinnedRecipes()">
|
||||
<template #default="{ hide }">
|
||||
<div class="d-flex flex-column justify-content-end h-100 p-3 align-items-end">
|
||||
<h5>{{$t("Planned")}} <i class="fas fa-calendar fa-fw"></i></h5>
|
||||
<h5>{{ $t("Planned") }} <i class="fas fa-calendar fa-fw"></i></h5>
|
||||
|
||||
<div class="text-right">
|
||||
<template v-if="planned_recipes.length > 0">
|
||||
@@ -18,24 +19,25 @@
|
||||
hide()
|
||||
"
|
||||
href="javascript:void(0);"
|
||||
>{{ r.name }}</a
|
||||
>{{ r.name }}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-muted">{{$t("nothing_planned_today")}}</span>
|
||||
<span class="text-muted">{{ $t("nothing_planned_today") }}</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<h5>{{$t("Pinned")}} <i class="fas fa-thumbtack fa-fw"></i></h5>
|
||||
<h5>{{ $t("Pinned") }} <i class="fas fa-thumbtack fa-fw"></i></h5>
|
||||
|
||||
<template v-if="pinned_recipes.length > 0">
|
||||
<div class="text-right">
|
||||
<div v-for="r in pinned_recipes" :key="`pin${r.id}`">
|
||||
<b-row class="pb-1 pt-1">
|
||||
<b-col cols="2">
|
||||
<a href="javascript:void(0)" @click="unPinRecipe(r)" class="text-muted"><i class="fas fa-times"></i></a>
|
||||
<a href="javascript:void(0)" @click="unPinRecipe(r)" class="text-muted"><i
|
||||
class="fas fa-times"></i></a>
|
||||
</b-col>
|
||||
<b-col cols="10">
|
||||
<a
|
||||
@@ -45,7 +47,7 @@
|
||||
"
|
||||
href="javascript:void(0);"
|
||||
class="align-self-end"
|
||||
>{{ r.name }}
|
||||
>{{ r.name }}
|
||||
</a>
|
||||
</b-col>
|
||||
</b-row>
|
||||
@@ -53,7 +55,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-muted">{{$t("no_pinned_recipes")}}</span>
|
||||
<span class="text-muted">{{ $t("no_pinned_recipes") }}</span>
|
||||
</template>
|
||||
|
||||
<template v-if="related_recipes.length > 0">
|
||||
@@ -67,7 +69,7 @@
|
||||
hide()
|
||||
"
|
||||
href="javascript:void(0);"
|
||||
>{{ r.name }}</a
|
||||
>{{ r.name }}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,8 +79,8 @@
|
||||
</template>
|
||||
<template #footer="{ hide }">
|
||||
<div class="d-flex bg-dark text-light align-items-center px-3 py-2">
|
||||
<strong class="mr-auto">{{$t("Quick actions")}}</strong>
|
||||
<b-button size="sm" @click="hide">{{$t("Close")}}</b-button>
|
||||
<strong class="mr-auto">{{ $t("Quick actions") }}</strong>
|
||||
<b-button size="sm" @click="hide">{{ $t("Close") }}</b-button>
|
||||
</div>
|
||||
</template>
|
||||
</b-sidebar>
|
||||
@@ -86,14 +88,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { ApiApiFactory } = require("@/utils/openapi/api")
|
||||
import { ResolveUrlMixin } from "@/utils/utils"
|
||||
const {ApiApiFactory} = require("@/utils/openapi/api")
|
||||
import {ResolveUrlMixin} from "@/utils/utils"
|
||||
|
||||
export default {
|
||||
name: "RecipeSwitcher",
|
||||
mixins: [ResolveUrlMixin],
|
||||
props: {
|
||||
recipe: { type: Number, default: undefined },
|
||||
recipe: {type: Number, default: undefined},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -158,7 +160,12 @@ export default {
|
||||
// get related recipes and save them for later
|
||||
if (this.$parent.recipe) {
|
||||
this.related_recipes = [this.$parent.recipe]
|
||||
return apiClient.relatedRecipe(this.$parent.recipe.id, { query: { levels: 2, format: "json" } }).then((result) => {
|
||||
return apiClient.relatedRecipe(this.$parent.recipe.id, {
|
||||
query: {
|
||||
levels: 2,
|
||||
format: "json"
|
||||
}
|
||||
}).then((result) => {
|
||||
this.related_recipes = this.related_recipes.concat(result.data)
|
||||
})
|
||||
}
|
||||
@@ -172,16 +179,16 @@ export default {
|
||||
// TODO move to utility function moment is in maintenance mode https://momentjs.com/docs/
|
||||
var tzoffset = new Date().getTimezoneOffset() * 60000 //offset in milliseconds
|
||||
let today = new Date(Date.now() - tzoffset).toISOString().split("T")[0]
|
||||
return apiClient.listMealPlans({ query: { from_date: today, to_date: today } }).then((result) => {
|
||||
return apiClient.listMealPlans({query: {from_date: today, to_date: today}}).then((result) => {
|
||||
let promises = []
|
||||
result.data.forEach((mealplan) => {
|
||||
this.planned_recipes.push({ ...mealplan?.recipe, servings: mealplan?.servings })
|
||||
this.planned_recipes.push({...mealplan?.recipe, servings: mealplan?.servings})
|
||||
const serving_factor = (mealplan?.servings ?? mealplan?.recipe?.servings ?? 1) / (mealplan?.recipe?.servings ?? 1)
|
||||
promises.push(
|
||||
apiClient.relatedRecipe(mealplan?.recipe?.id, { query: { levels: 2 } }).then((r) => {
|
||||
apiClient.relatedRecipe(mealplan?.recipe?.id, {query: {levels: 2}}).then((r) => {
|
||||
// scale all recipes to mealplan servings
|
||||
r.data = r.data.map((x) => {
|
||||
return { ...x, factor: serving_factor }
|
||||
return {...x, factor: serving_factor}
|
||||
})
|
||||
this.planned_recipes = [...this.planned_recipes, ...r.data]
|
||||
})
|
||||
@@ -213,20 +220,28 @@ export default {
|
||||
z-index: 9000;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
#switcher .btn-circle {
|
||||
position: fixed;
|
||||
top: 9px;
|
||||
left: 80px;
|
||||
color: white;
|
||||
top: 12px;
|
||||
right: 79px;
|
||||
color: rgba(46, 46, 46, 0.5);
|
||||
width: 56px;
|
||||
height: 40px;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
background-color: transparent;
|
||||
border: 1px solid rgba(46, 46, 46, 0.5);
|
||||
border-radius: 0.1875rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 2000px) {
|
||||
@media (min-width: 992px) {
|
||||
#switcher .btn-circle {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
right: 50px;
|
||||
bottom: 40px;
|
||||
right: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
</template>
|
||||
<b-dropdown-item v-on:click="$emit('item-action', 'edit')" v-if="show_edit"> <i class="fas fa-pencil-alt fa-fw"></i> {{ $t("Edit") }} </b-dropdown-item>
|
||||
|
||||
|
||||
<b-dropdown-item v-on:click="$emit('item-action', 'delete')" v-if="show_delete"> <i class="fas fa-trash-alt fa-fw"></i> {{ $t("Delete") }} </b-dropdown-item>
|
||||
<b-dropdown-item v-on:click="$emit('item-action', 'ingredient-editor')" v-if="show_ingredient_editor"> <i class="fas fa-th-list fa-dw"></i> {{ $t("Ingredient Editor") }} </b-dropdown-item>
|
||||
|
||||
<b-dropdown-item v-on:click="$emit('item-action', 'add-shopping')" v-if="show_shopping">
|
||||
<i class="fas fa-cart-plus fa-fw"></i> {{ $t("Add_to_Shopping") }}
|
||||
</b-dropdown-item>
|
||||
@@ -24,8 +27,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ResolveUrlMixin} from "@/utils/utils";
|
||||
|
||||
export default {
|
||||
name: "GenericContextMenu",
|
||||
mixins: [ResolveUrlMixin],
|
||||
props: {
|
||||
show_edit: { type: Boolean, default: true },
|
||||
show_delete: { type: Boolean, default: true },
|
||||
@@ -33,6 +39,7 @@ export default {
|
||||
show_merge: { type: Boolean, default: false },
|
||||
show_shopping: { type: Boolean, default: false },
|
||||
show_onhand: { type: Boolean, default: false },
|
||||
show_ingredient_editor: { type: Boolean, default: false },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
:show_move="useMove"
|
||||
:show_shopping="useShopping"
|
||||
:show_onhand="useOnhand"
|
||||
:show_ingredient_editor="useIngredientEditor"
|
||||
@item-action="$emit('item-action', { action: $event, source: item })"
|
||||
>
|
||||
</generic-context-menu>
|
||||
@@ -132,11 +133,12 @@ import GenericOrderedPill from "@/components/GenericOrderedPill"
|
||||
import RecipeCard from "@/components/RecipeCard"
|
||||
import { mixin as clickaway } from "vue-clickaway"
|
||||
import { createPopper } from "@popperjs/core"
|
||||
import {ApiMixin} from "@/utils/utils";
|
||||
|
||||
export default {
|
||||
name: "GenericHorizontalCard",
|
||||
components: { GenericContextMenu, RecipeCard, Badges, GenericPill, GenericOrderedPill },
|
||||
mixins: [clickaway],
|
||||
mixins: [clickaway, ApiMixin],
|
||||
props: {
|
||||
item: { type: Object },
|
||||
model: { type: Object },
|
||||
@@ -186,6 +188,9 @@ export default {
|
||||
useDrag: function () {
|
||||
return this.useMove || this.useMerge
|
||||
},
|
||||
useIngredientEditor: function (){
|
||||
return (this.model === this.Models.FOOD || this.model === this.Models.UNIT)
|
||||
},
|
||||
itemTags: function () {
|
||||
return this.model?.tags ?? []
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
@search-change="search"
|
||||
@input="selectionChanged"
|
||||
@tag="addNew"
|
||||
@open="selectOpened()"
|
||||
>
|
||||
</multiselect>
|
||||
</template>
|
||||
@@ -26,11 +27,11 @@
|
||||
<script>
|
||||
import Vue from "vue"
|
||||
import Multiselect from "vue-multiselect"
|
||||
import { ApiMixin } from "@/utils/utils"
|
||||
import {ApiMixin, StandardToasts} from "@/utils/utils"
|
||||
|
||||
export default {
|
||||
name: "GenericMultiselect",
|
||||
components: { Multiselect },
|
||||
components: {Multiselect},
|
||||
mixins: [ApiMixin],
|
||||
data() {
|
||||
return {
|
||||
@@ -42,16 +43,16 @@ export default {
|
||||
}
|
||||
},
|
||||
props: {
|
||||
placeholder: { type: String, default: undefined },
|
||||
placeholder: {type: String, default: undefined},
|
||||
model: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
label: { type: String, default: "name" },
|
||||
parent_variable: { type: String, default: undefined },
|
||||
limit: { type: Number, default: 25 },
|
||||
label: {type: String, default: "name"},
|
||||
parent_variable: {type: String, default: undefined},
|
||||
limit: {type: Number, default: 25},
|
||||
sticky_options: {
|
||||
type: Array,
|
||||
default() {
|
||||
@@ -68,10 +69,11 @@ export default {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
multiple: { type: Boolean, default: true },
|
||||
allow_create: { type: Boolean, default: false },
|
||||
create_placeholder: { type: String, default: "You Forgot to Add a Tag Placeholder" },
|
||||
clear: { type: Number },
|
||||
search_on_load: {type: Boolean, default: true},
|
||||
multiple: {type: Boolean, default: true},
|
||||
allow_create: {type: Boolean, default: false},
|
||||
create_placeholder: {type: String, default: "You Forgot to Add a Tag Placeholder"},
|
||||
clear: {type: Number},
|
||||
},
|
||||
watch: {
|
||||
initial_selection: function (newVal, oldVal) {
|
||||
@@ -82,12 +84,12 @@ export default {
|
||||
empty[this.label] = `..${this.$t("loading")}..`
|
||||
this.selected_objects.forEach((x) => {
|
||||
if (typeof x !== "object") {
|
||||
this.selected_objects[this.selected_objects.indexOf(x)] = { ...empty, id: x }
|
||||
this.selected_objects[this.selected_objects.indexOf(x)] = {...empty, id: x}
|
||||
get_details.push(x)
|
||||
}
|
||||
})
|
||||
get_details.forEach((x) => {
|
||||
this.genericAPI(this.model, this.Actions.FETCH, { id: x })
|
||||
this.genericAPI(this.model, this.Actions.FETCH, {id: x})
|
||||
.then((result) => {
|
||||
// this.selected_objects[this.selected_objects.map((y) => y.id).indexOf(x)] = result.data
|
||||
Vue.set(this.selected_objects, this.selected_objects.map((y) => y.id).indexOf(x), result.data)
|
||||
@@ -103,8 +105,8 @@ export default {
|
||||
if (typeof this.selected_objects !== "object") {
|
||||
let empty = {}
|
||||
empty[this.label] = `..${this.$t("loading")}..`
|
||||
this.selected_objects = { ...empty, id: this.selected_objects }
|
||||
this.genericAPI(this.model, this.Actions.FETCH, { id: this.selected_objects })
|
||||
this.selected_objects = {...empty, id: this.selected_objects}
|
||||
this.genericAPI(this.model, this.Actions.FETCH, {id: this.selected_objects})
|
||||
.then((result) => {
|
||||
this.selected_objects = result.data
|
||||
})
|
||||
@@ -123,7 +125,9 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.id = Math.random()
|
||||
this.search("")
|
||||
if (this.search_on_load) {
|
||||
this.search("")
|
||||
}
|
||||
if (this.multiple || !this.initial_single_selection) {
|
||||
this.selected_objects = this.initial_selection
|
||||
} else {
|
||||
@@ -170,15 +174,30 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
selectOpened: function () {
|
||||
if (this.objects.length < 1) {
|
||||
this.search("")
|
||||
}
|
||||
},
|
||||
selectionChanged: function () {
|
||||
this.$emit("change", { var: this.parent_variable, val: this.selected_objects })
|
||||
this.$emit("change", {var: this.parent_variable, val: this.selected_objects})
|
||||
},
|
||||
addNew(e) {
|
||||
this.$emit("new", e)
|
||||
// could refactor as Promise - seems unnecessary
|
||||
setTimeout(() => {
|
||||
this.search("")
|
||||
}, 750)
|
||||
//TODO add ability to choose field name other than "name"
|
||||
console.log('CREATEING NEW with -> ' , e)
|
||||
this.genericAPI(this.model, this.Actions.CREATE, {name: e}).then(result => {
|
||||
let createdObj = result.data?.results ?? result.data
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||
if (this.multiple) {
|
||||
this.selected_objects.push(createdObj)
|
||||
} else {
|
||||
this.selected_objects = createdObj
|
||||
}
|
||||
this.objects.push(createdObj)
|
||||
this.selectionChanged()
|
||||
}).catch((r, err) => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
:initial_single_selection="entryEditing.meal_type"
|
||||
:allow_create="true"
|
||||
:create_placeholder="$t('Create_New_Meal_Type')"
|
||||
@new="createMealType"
|
||||
></generic-multiselect>
|
||||
<span class="text-danger" v-if="missing_meal_type">{{ $t("Meal_Type_Required") }}</span>
|
||||
<small tabindex="-1" class="form-text text-muted" v-if="!missing_meal_type">{{ $t("Meal_Type") }}</small>
|
||||
@@ -228,20 +227,6 @@ export default {
|
||||
this.entryEditing.meal_type = null
|
||||
}
|
||||
},
|
||||
createMealType(event) {
|
||||
if (event != "") {
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
apiClient
|
||||
.createMealType({ name: event })
|
||||
.then((e) => {
|
||||
this.$emit("reload-meal-types")
|
||||
})
|
||||
.catch((error) => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE)
|
||||
})
|
||||
}
|
||||
},
|
||||
selectRecipe(event) {
|
||||
this.missing_recipe = false
|
||||
if (event.val != null) {
|
||||
|
||||
@@ -176,8 +176,11 @@ export default {
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_DELETE)
|
||||
if (err.response.status === 403){
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_DELETE_PROTECTED)
|
||||
}else {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_DELETE)
|
||||
}
|
||||
this.$emit("finish-action", "cancel")
|
||||
})
|
||||
},
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
:label="list_label"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
:placeholder="modelName"
|
||||
@new="addNew"
|
||||
>
|
||||
</generic-multiselect>
|
||||
<em v-if="help" class="small text-muted">{{ help }}</em>
|
||||
@@ -119,19 +118,6 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addNew: function (e) {
|
||||
// if create a new item requires more than 1 parameter or the field 'name' is insufficient this will need reworked
|
||||
// in a perfect world this would trigger a new modal and allow editing all fields
|
||||
this.genericAPI(this.model, this.Actions.CREATE, { name: e })
|
||||
.then((result) => {
|
||||
this.new_value = result.data
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||
})
|
||||
},
|
||||
// ordered lookups have nested attributes that need flattened attributes to drive lookup
|
||||
flattenItems: function (itemlist) {
|
||||
let flat_items = []
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</template>
|
||||
<p class="mt-1">
|
||||
<last-cooked :recipe="recipe"></last-cooked>
|
||||
<keywords-component :recipe="recipe" style="margin-top: 4px"></keywords-component>
|
||||
<keywords-component :recipe="recipe" style="margin-top: 4px; position: relative; z-index: 3;"></keywords-component>
|
||||
</p>
|
||||
<transition name="fade" mode="in-out">
|
||||
<div class="row mt-3" v-if="show_detail">
|
||||
|
||||
@@ -206,11 +206,11 @@ export default {
|
||||
this.$bvModal.show(`shopping_${this.modal_id}`)
|
||||
},
|
||||
copyToNew: function () {
|
||||
let recipename = window.prompt(this.$t("copy_to_new"), this.$t("recipe_name"))
|
||||
let recipe_name = window.prompt(this.$t("copy_to_new"), this.$t("recipe_name"))
|
||||
|
||||
let apiClient = new ApiApiFactory()
|
||||
apiClient.retrieveRecipe(this.recipe.id).then((results) => {
|
||||
let recipe = { ...results.data, ...{ id: undefined, name: recipename } }
|
||||
let recipe = { ...results.data, ...{ id: undefined, name: recipe_name } }
|
||||
recipe.steps = recipe.steps.map((step) => {
|
||||
return {
|
||||
...step,
|
||||
@@ -222,12 +222,14 @@ export default {
|
||||
},
|
||||
}
|
||||
})
|
||||
console.log(recipe)
|
||||
if (recipe.nutrition !== null){
|
||||
delete recipe.nutrition.id
|
||||
}
|
||||
apiClient
|
||||
.createRecipe(recipe)
|
||||
.then((newrecipe) => {
|
||||
.then((new_recipe) => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||
window.open(this.resolveDjangoUrl("view_recipe", newrecipe.data.id))
|
||||
window.open(this.resolveDjangoUrl("view_recipe", new_recipe.data.id))
|
||||
})
|
||||
.catch((error) => {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"err_creating_resource": "There was an error creating a resource!",
|
||||
"err_updating_resource": "There was an error updating a resource!",
|
||||
"err_deleting_resource": "There was an error deleting a resource!",
|
||||
"err_deleting_protected_resource": "The object you are trying to delete is still used and can't be deleted.",
|
||||
"err_moving_resource": "There was an error moving a resource!",
|
||||
"err_merging_resource": "There was an error merging a resource!",
|
||||
"success_fetching_resource": "Successfully fetched a resource!",
|
||||
@@ -63,6 +64,7 @@
|
||||
"Make_Ingredient": "Make Ingredient",
|
||||
"Enable_Amount": "Enable Amount",
|
||||
"Disable_Amount": "Disable Amount",
|
||||
"Ingredient Editor": "Ingredient Editor",
|
||||
"Add_Step": "Add Step",
|
||||
"Keywords": "Keywords",
|
||||
"Books": "Books",
|
||||
@@ -87,6 +89,7 @@
|
||||
"Note": "Note",
|
||||
"Success": "Success",
|
||||
"Failure": "Failure",
|
||||
"Protected": "Protected",
|
||||
"Ingredients": "Ingredients",
|
||||
"Supermarket": "Supermarket",
|
||||
"Categories": "Categories",
|
||||
|
||||
@@ -285,5 +285,99 @@
|
||||
"shopping_add_onhand": "Automatisch op voorraad",
|
||||
"related_recipes": "Gerelateerde recepten",
|
||||
"today_recipes": "Recepten van vandaag",
|
||||
"Search Settings": "Zoekinstellingen"
|
||||
"Search Settings": "Zoekinstellingen",
|
||||
"enable_expert": "Schakel expertmodus in",
|
||||
"expert_mode": "Expertmodus",
|
||||
"simple_mode": "Eenvoudige modus",
|
||||
"advanced": "Geavanceerd",
|
||||
"fields": "Velden",
|
||||
"show_keywords": "Toon etiketten",
|
||||
"show_foods": "Toon ingrediënten",
|
||||
"show_books": "Toon boeken",
|
||||
"show_rating": "Toon waardering",
|
||||
"show_units": "Toon eenheden",
|
||||
"show_filters": "Toon filters",
|
||||
"not": "niet",
|
||||
"save_filter": "Bewaar filter",
|
||||
"filter_name": "Naam filter",
|
||||
"Custom Filter": "Aangepast filter",
|
||||
"shared_with": "Gedeeld met",
|
||||
"sort_by": "Sorteer op",
|
||||
"asc": "Oplopend",
|
||||
"desc": "Aflopend",
|
||||
"date_viewed": "Laatst bekeken",
|
||||
"last_cooked": "Laatst bereid",
|
||||
"times_cooked": "Keren bereid",
|
||||
"date_created": "Datum aangemaakt",
|
||||
"show_sortby": "Toon gesorteerd op",
|
||||
"search_rank": "Zoekrang",
|
||||
"make_now": "Maak nu",
|
||||
"recipe_filter": "Receptenfilter",
|
||||
"book_filter_help": "Voeg naast handmatig toegewezen recepten ook recepten uit het receptfilter toe.",
|
||||
"copy_to_new": "Kopieer naar nieuw recept",
|
||||
"recipe_name": "Naam recept",
|
||||
"paste_ingredients_placeholder": "Plak ingrediëntenlijst hier...",
|
||||
"paste_ingredients": "Plak ingrediënten",
|
||||
"ingredient_list": "Ingrediëntenlijst",
|
||||
"explain": "Leg uit",
|
||||
"filter": "Filter",
|
||||
"search_no_recipes": "Er zijn geen recepten gevonden!",
|
||||
"search_import_help_text": "Importeer een recept van een externe website of applicatie.",
|
||||
"search_create_help_text": "Maak direct een nieuw recept in Tandoor.",
|
||||
"warning_duplicate_filter": "Waarschuwing: door technische beperkingen kan het hebben van meerdere filters of dezelfde combinatie (en/of/niet) tot onverwachte resultaten leiden.",
|
||||
"reset_children": "Overerving van kinderen resetten",
|
||||
"reset_children_help": "Overschrijf alle kinderen met waarden van overgeërfde velden. Overgeërfde velden van kinderen worden ingesteld als velden erven tenzij kinderen erven velden ingesteld is.",
|
||||
"substitute_help": "Vervangers worden overwogen bij het zoeken naar recepten die kunnen worden gemaakt met beschikbare ingrediënten.",
|
||||
"substitute_siblings_help": "Alle ingrediënten die een ouder delen met dit ingrediënt worden als vervangers beschouwd.",
|
||||
"substitute_siblings": "Vervangers",
|
||||
"substitute_children": "Vervang kinderen",
|
||||
"ChildInheritFields_help": "Standaard erven kinderen deze velden.",
|
||||
"last_viewed": "Laatst bekeken",
|
||||
"created_on": "Aangemaakt op",
|
||||
"updatedon": "Geüpdatet op",
|
||||
"advanced_search_settings": "Geavanceerde zoekinstellingen",
|
||||
"nothing_planned_today": "Je hebt niks gepland voor vandaag!",
|
||||
"Planned": "Gepland",
|
||||
"Pinned": "Vastgepind",
|
||||
"Quick actions": "Snelle acties",
|
||||
"Ratings": "Waardering",
|
||||
"Units": "Eenheden",
|
||||
"Random Recipes": "Willekeurige recepten",
|
||||
"parameter_count": "Parameter {count}",
|
||||
"select_keyword": "Selecteer etiket",
|
||||
"add_keyword": "Voeg etiket toe",
|
||||
"select_file": "Selecteer bestand",
|
||||
"select_recipe": "Selecteer recept",
|
||||
"select_unit": "Selecteer eenheid",
|
||||
"select_food": "Selecteer ingrediënt",
|
||||
"remove_selection": "Deselecteren",
|
||||
"empty_list": "Lijst is leeg.",
|
||||
"Select": "Selecteer",
|
||||
"Supermarkets": "Supermarkten",
|
||||
"User": "Gebruiker",
|
||||
"Keyword": "Etiket",
|
||||
"Advanced": "Geavanceerd",
|
||||
"Page": "Pagina",
|
||||
"left_handed": "Linkshandige modus",
|
||||
"Pin": "Pin",
|
||||
"shopping_category_help": "Supermarkten kunnen gesorteerd en gefilterd worden per boodschappencategorie conform the indeling van de gangpaden.",
|
||||
"Foods": "Ingrediënten",
|
||||
"OnHand_help": "Ingrediënt is op voorraad en wordt niet automatisch aan een boodschappenlijstje toegevoegd. Voorraadstatus is gedeeld tussen gebruikers.",
|
||||
"ignore_shopping_help": "Voeg ingrediënt nooit toe aan boodschappenlijstjes (bijv. water)",
|
||||
"view_recipe": "Bekijk recept",
|
||||
"review_shopping": "Beoordeel items op het boodschappenlijstje voor opslaan",
|
||||
"and_down": "& omlaag",
|
||||
"remember_hours": "Te onthouden uren",
|
||||
"food_recipe_help": "Hier een recept koppelen voegt het gekoppelde recept toe in elk ander recept dat dit ingrediënt gebruikt",
|
||||
"left_handed_help": "Optimaliseert de gebruikersinterface voor linkshandig gebruik.",
|
||||
"substitute_children_help": "Alle ingrediënten die kinderen zijn van dit ingrediënt worden beschouwd als vervangers.",
|
||||
"SubstituteOnHand": "Je hebt een vervanger op voorraad.",
|
||||
"ChildInheritFields": "Kinderen erven velden",
|
||||
"InheritFields_help": "De waarden van deze velden worden geërfd van een ouder (uitzondering: lege boodschappencategorieën)",
|
||||
"no_pinned_recipes": "Je hebt geen vastgepinde recepten!",
|
||||
"Internal": "Interne",
|
||||
"Reset": "Herstel",
|
||||
"remember_search": "Onthoud zoekopdracht",
|
||||
"tree_select": "Gebruik boomselectie",
|
||||
"sql_debug": "SQL Debug"
|
||||
}
|
||||
|
||||
@@ -290,12 +290,98 @@
|
||||
"Foods": "Żywność",
|
||||
"view_recipe": "Zobacz przepis",
|
||||
"left_handed": "Tryb dla leworęcznych",
|
||||
"OnHand_help": "Żywność jest w spiżarni i nie zostanie automatycznie dodana do listy zakupów.",
|
||||
"OnHand_help": "Żywność jest w spiżarni i nie zostanie automatycznie dodana do listy zakupów. Status podręczny jest współdzielony z użytkownikami robiącymi zakupy.",
|
||||
"ignore_shopping_help": "Nigdy nie dodawaj żywności do listy zakupów (np. wody)",
|
||||
"shopping_category_help": "Z supermarketów można zamawiać i filtrować według kategorii zakupów zgodnie z układem alejek.",
|
||||
"review_shopping": "Przejrzyj wpisy zakupów przed zapisaniem",
|
||||
"sql_debug": "Debugowanie SQL",
|
||||
"remember_search": "Zapamiętaj wyszukiwanie",
|
||||
"remember_hours": "Godziny do zapamiętania",
|
||||
"tree_select": "Użyj drzewa wyboru"
|
||||
"tree_select": "Użyj drzewa wyboru",
|
||||
"Custom Filter": "Filtr niestandardowy",
|
||||
"date_viewed": "Ostatnio oglądane",
|
||||
"book_filter_help": "Uwzględnij przepisy z filtra przepisów oprócz ręcznie przypisanych.",
|
||||
"search_import_help_text": "Zaimportuj przepis z zewnętrznej strony internetowej lub aplikacji.",
|
||||
"warning_duplicate_filter": "Ostrzeżenie: Ze względu na ograniczenia techniczne posiadanie wielu filtrów o tej samej kombinacji (i/lub/nie) może dać nieoczekiwane wyniki.",
|
||||
"reset_children_help": "Zastąp wszystkie potomne wartościami z pól dziedziczonych. Dziedziczone pola potomnych zostaną ustawione na Dziedzicz pola, chyba że pole potomne jest ustawione.",
|
||||
"and_down": "& Dół",
|
||||
"enable_expert": "Włącz tryb eksperta",
|
||||
"expert_mode": "Tryb eksperta",
|
||||
"simple_mode": "Tryb prosty",
|
||||
"advanced": "Zaawansowany",
|
||||
"fields": "Pola",
|
||||
"show_keywords": "Pokaż słowa kluczowe",
|
||||
"show_foods": "Pokaż jedzenie",
|
||||
"show_books": "Pokaż książki",
|
||||
"show_rating": "Pokaż ocenę",
|
||||
"show_units": "Pokaż jednostki",
|
||||
"show_filters": "Pokaż filtry",
|
||||
"not": "nie",
|
||||
"save_filter": "Zapisz filtr",
|
||||
"filter_name": "Nazwa filtra",
|
||||
"shared_with": "Współdzielone z",
|
||||
"sort_by": "Sortuj według",
|
||||
"asc": "Rosnąco",
|
||||
"desc": "Malejąco",
|
||||
"last_cooked": "Ostatnio gotowane",
|
||||
"times_cooked": "Ile razy gotowano",
|
||||
"date_created": "Data utworzenia",
|
||||
"show_sortby": "Pokaż Sortuj według",
|
||||
"search_rank": "Szukaj w rankingu",
|
||||
"make_now": "Zrób teraz",
|
||||
"recipe_filter": "Filtr przepisów",
|
||||
"copy_to_new": "Kopiuj do nowego przepisu",
|
||||
"recipe_name": "Nazwa przepisu",
|
||||
"paste_ingredients_placeholder": "Tutaj wklej listę składników...",
|
||||
"paste_ingredients": "Wklej składniki",
|
||||
"ingredient_list": "Lista składników",
|
||||
"explain": "Wyjaśnij",
|
||||
"filter": "Filtr",
|
||||
"search_no_recipes": "Nie udało się znaleźć żadnych przepisów!",
|
||||
"search_create_help_text": "Utwórz nowy przepis bezpośrednio w Tandoor.",
|
||||
"reset_children": "Zresetuj dziedziczenie potomne (Child)",
|
||||
"substitute_help": "Zamienniki są brane pod uwagę przy wyszukiwaniu przepisów, które można przygotować z posiadanych składników.",
|
||||
"InheritFields_help": "Wartości tych pól będą dziedziczone z nadrzędnego (Wyjątek: puste kategorie zakupowe nie są dziedziczone)",
|
||||
"substitute_siblings_help": "Wszystkie produkty, które współdzielą rodzica tego produktu, uważane są za zamienniki.",
|
||||
"substitute_children_help": "Wszystkie produkty, które są potomnymi tego produktu, uważane są za zamienniki.",
|
||||
"substitute_siblings": "Bliźniacze zamienniki",
|
||||
"substitute_children": "Potomne zamienniki",
|
||||
"SubstituteOnHand": "Masz pod ręką zamienniki.",
|
||||
"ChildInheritFields": "Potomne dziedziczą pola",
|
||||
"ChildInheritFields_help": "Potomne domyślnie odziedziczą te pola.",
|
||||
"last_viewed": "Ostatnio oglądane",
|
||||
"created_on": "Utworzono dnia",
|
||||
"updatedon": "Zaktualizowano dnia",
|
||||
"advanced_search_settings": "Zaawansowane ustawienia wyszukiwania",
|
||||
"nothing_planned_today": "Na dziś nic nie planujesz!",
|
||||
"no_pinned_recipes": "Nie masz przypiętych przepisów!",
|
||||
"Planned": "Zaplanowane",
|
||||
"Pinned": "Przypięte",
|
||||
"Quick actions": "Szybkie akcje",
|
||||
"Ratings": "Oceny",
|
||||
"Internal": "Wewnętrzne",
|
||||
"Units": "Jednostki",
|
||||
"Random Recipes": "Losowe przepisy",
|
||||
"parameter_count": "Parametr {count}",
|
||||
"select_keyword": "Wybierz słowo kluczowe",
|
||||
"add_keyword": "Dodaj słowo kluczowe",
|
||||
"select_file": "Wybierz plik",
|
||||
"select_recipe": "Wybierz przepis",
|
||||
"select_unit": "Wybierz jednostkę",
|
||||
"select_food": "Wybierz jedzenie/produkt",
|
||||
"remove_selection": "Odznacz",
|
||||
"empty_list": "Lista jest pusta.",
|
||||
"Select": "Zaznacz",
|
||||
"Supermarkets": "Supermarkety",
|
||||
"User": "Użytkownik",
|
||||
"Keyword": "Słowo kluczowe",
|
||||
"Advanced": "Zaawansowany",
|
||||
"Page": "Strona",
|
||||
"Reset": "Resetowanie",
|
||||
"Create Food": "Twórz jedzenie",
|
||||
"create_food_desc": "Stwórz jedzenie i połącz je z tym przepisem.",
|
||||
"additional_options": "Opcje dodatkowe",
|
||||
"err_deleting_protected_resource": "Obiekt, który próbujesz usunąć, jest nadal używany i nie można go usunąć.",
|
||||
"Protected": "Chroniony",
|
||||
"Ingredient Editor": "Edytor składników"
|
||||
}
|
||||
|
||||
@@ -351,30 +351,32 @@
|
||||
"last_viewed": "",
|
||||
"created_on": "Criado em",
|
||||
"updatedon": "Atualizado em",
|
||||
"advanced_search_settings": "",
|
||||
"nothing_planned_today": "",
|
||||
"no_pinned_recipes": "",
|
||||
"Planned": "",
|
||||
"Pinned": "",
|
||||
"Quick actions": "",
|
||||
"Ratings": "",
|
||||
"advanced_search_settings": "Configurações Avançadas de Pesquisa",
|
||||
"nothing_planned_today": "Não Tem nada planeado para hoje!",
|
||||
"no_pinned_recipes": "Não Tem nenhuma receita marcada!",
|
||||
"Planned": "Planeado",
|
||||
"Pinned": "Marcado",
|
||||
"Quick actions": "Acções Rápidas",
|
||||
"Ratings": "Avaliações",
|
||||
"Internal": "Interno",
|
||||
"Units": "",
|
||||
"Random Recipes": "",
|
||||
"parameter_count": "",
|
||||
"select_keyword": "",
|
||||
"add_keyword": "",
|
||||
"select_file": "",
|
||||
"select_recipe": "",
|
||||
"select_unit": "",
|
||||
"select_food": "",
|
||||
"remove_selection": "",
|
||||
"empty_list": "",
|
||||
"Select": "",
|
||||
"Supermarkets": "",
|
||||
"Units": "Unidades",
|
||||
"Random Recipes": "Receitas Aleatórias",
|
||||
"parameter_count": "Parametro {count}",
|
||||
"select_keyword": "Selecionar Palavra Chave",
|
||||
"add_keyword": "Adicionar Palavra Chave",
|
||||
"select_file": "Selecionar Ficheiro",
|
||||
"select_recipe": "Selecionar Receita",
|
||||
"select_unit": "Selecionar Unidade",
|
||||
"select_food": "Selecionar Comida",
|
||||
"remove_selection": "Deselecionar",
|
||||
"empty_list": "Lista está Vazia.",
|
||||
"Select": "Selecionar",
|
||||
"Supermarkets": "Supermercados",
|
||||
"User": "Utilizador",
|
||||
"Keyword": "",
|
||||
"Advanced": "",
|
||||
"Page": "",
|
||||
"Reset": ""
|
||||
"Keyword": "Palavra Chave",
|
||||
"Advanced": "Avançado",
|
||||
"Page": "Página",
|
||||
"Reset": "Reiniciar",
|
||||
"Create Food": "Criar Comida",
|
||||
"create_food_desc": "Criar a comida e ligar a esta receita."
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
"Fats": "Жиры",
|
||||
"Carbohydrates": "Углеводы",
|
||||
"Calories": "Каллории",
|
||||
"Energy": "",
|
||||
"Energy": "Энергетическая ценность",
|
||||
"Nutrition": "Питательность",
|
||||
"Date": "Дата",
|
||||
"Share": "Поделиться",
|
||||
@@ -87,107 +87,107 @@
|
||||
"Category": "Категория",
|
||||
"Selected": "Выбрать",
|
||||
"min": "мин",
|
||||
"Servings": "",
|
||||
"Waiting": "",
|
||||
"Preparation": "",
|
||||
"External": "",
|
||||
"Size": "",
|
||||
"Files": "",
|
||||
"File": "",
|
||||
"Edit": "",
|
||||
"Image": "",
|
||||
"Delete": "",
|
||||
"Open": "",
|
||||
"Ok": "",
|
||||
"Save": "",
|
||||
"Step": "",
|
||||
"Search": "",
|
||||
"Import": "",
|
||||
"Print": "",
|
||||
"Settings": "",
|
||||
"or": "",
|
||||
"and": "",
|
||||
"Information": "",
|
||||
"Download": "",
|
||||
"Create": "",
|
||||
"Servings": "Порции",
|
||||
"Waiting": "Ожидание",
|
||||
"Preparation": "Приготовление",
|
||||
"External": "Внешний",
|
||||
"Size": "Размер",
|
||||
"Files": "Файлы",
|
||||
"File": "Файл",
|
||||
"Edit": "Редактировать",
|
||||
"Image": "Изображение",
|
||||
"Delete": "Удалить",
|
||||
"Open": "Открыть",
|
||||
"Ok": "Открыть",
|
||||
"Save": "Сохранить",
|
||||
"Step": "Шаг",
|
||||
"Search": "Поиск",
|
||||
"Import": "Импорт",
|
||||
"Print": "Распечатать",
|
||||
"Settings": "Настройки",
|
||||
"or": "или",
|
||||
"and": "и",
|
||||
"Information": "Информация",
|
||||
"Download": "Загрузить",
|
||||
"Create": "Создать",
|
||||
"Advanced Search Settings": "",
|
||||
"View": "",
|
||||
"Recipes": "",
|
||||
"Move": "",
|
||||
"Merge": "",
|
||||
"Parent": "",
|
||||
"delete_confirmation": "",
|
||||
"move_confirmation": "",
|
||||
"merge_confirmation": "",
|
||||
"create_rule": "",
|
||||
"move_selection": "",
|
||||
"merge_selection": "",
|
||||
"Root": "",
|
||||
"Ignore_Shopping": "",
|
||||
"Shopping_Category": "",
|
||||
"Edit_Food": "",
|
||||
"Move_Food": "",
|
||||
"New_Food": "",
|
||||
"Hide_Food": "",
|
||||
"Food_Alias": "",
|
||||
"Unit_Alias": "",
|
||||
"Keyword_Alias": "",
|
||||
"Delete_Food": "",
|
||||
"No_ID": "",
|
||||
"Meal_Plan_Days": "",
|
||||
"merge_title": "",
|
||||
"move_title": "",
|
||||
"Food": "",
|
||||
"Recipe_Book": "",
|
||||
"del_confirmation_tree": "",
|
||||
"delete_title": "",
|
||||
"create_title": "",
|
||||
"edit_title": "",
|
||||
"Name": "",
|
||||
"Type": "",
|
||||
"Description": "",
|
||||
"Recipe": "",
|
||||
"tree_root": "",
|
||||
"Icon": "",
|
||||
"Unit": "",
|
||||
"No_Results": "",
|
||||
"New_Unit": "",
|
||||
"Create_New_Shopping Category": "",
|
||||
"Create_New_Food": "",
|
||||
"Create_New_Keyword": "",
|
||||
"Create_New_Unit": "",
|
||||
"Create_New_Meal_Type": "",
|
||||
"and_up": "",
|
||||
"Instructions": "",
|
||||
"Unrated": "",
|
||||
"Automate": "",
|
||||
"Empty": "",
|
||||
"Key_Ctrl": "",
|
||||
"Key_Shift": "",
|
||||
"Time": "",
|
||||
"Text": "",
|
||||
"Shopping_list": "",
|
||||
"Create_Meal_Plan_Entry": "",
|
||||
"Edit_Meal_Plan_Entry": "",
|
||||
"Title": "",
|
||||
"Week": "",
|
||||
"Month": "",
|
||||
"Year": "",
|
||||
"Planner": "",
|
||||
"Planner_Settings": "",
|
||||
"Period": "",
|
||||
"Plan_Period_To_Show": "",
|
||||
"Periods": "",
|
||||
"Plan_Show_How_Many_Periods": "",
|
||||
"Starting_Day": "",
|
||||
"Meal_Types": "",
|
||||
"Meal_Type": "",
|
||||
"Clone": "",
|
||||
"Drag_Here_To_Delete": "",
|
||||
"Meal_Type_Required": "",
|
||||
"Title_or_Recipe_Required": "",
|
||||
"Color": "",
|
||||
"New_Meal_Type": "",
|
||||
"View": "Просмотр",
|
||||
"Recipes": "Рецепты",
|
||||
"Move": "Переместить",
|
||||
"Merge": "Объединить",
|
||||
"Parent": "Родитель",
|
||||
"delete_confirmation": "Вы уверены что хотите удалить {source}?",
|
||||
"move_confirmation": "Переместить <i>{child}</i> к родителю <i>{parent}</i>",
|
||||
"merge_confirmation": "Заменить <i>{source}</i> с <i>{target}</i>",
|
||||
"create_rule": "и создать автоматически",
|
||||
"move_selection": "Выбрать родителя {type} для перемещения в {source} .",
|
||||
"merge_selection": "Замените все вхождения {source} выбранным {type}.",
|
||||
"Root": "Корневой элемент",
|
||||
"Ignore_Shopping": "Игнорировать Покупки",
|
||||
"Shopping_Category": "Категория покупок",
|
||||
"Edit_Food": "Редактировать еду",
|
||||
"Move_Food": "Переместить еду",
|
||||
"New_Food": "Новая еда",
|
||||
"Hide_Food": "Скрыть еду",
|
||||
"Food_Alias": "Наименование еды",
|
||||
"Unit_Alias": "Единицы измерения",
|
||||
"Keyword_Alias": "Ключевые слова",
|
||||
"Delete_Food": "Удалить элемент",
|
||||
"No_ID": "ID не найден, удаление не возможно.",
|
||||
"Meal_Plan_Days": "Планы питания на будущее",
|
||||
"merge_title": "Объединить {type}",
|
||||
"move_title": "Переместить {type}",
|
||||
"Food": "Еда",
|
||||
"Recipe_Book": "Книга рецептов",
|
||||
"del_confirmation_tree": "Вы уверены что хотите удалить {source} и все его элементы?",
|
||||
"delete_title": "Удалить {type}",
|
||||
"create_title": "Новый {type}",
|
||||
"edit_title": "Редактировать {type}",
|
||||
"Name": "Наименование",
|
||||
"Type": "Тип",
|
||||
"Description": "Описание",
|
||||
"Recipe": "Рецепт",
|
||||
"tree_root": "Главный элемент",
|
||||
"Icon": "Иконка",
|
||||
"Unit": "Единица измерения",
|
||||
"No_Results": "Результаты отсутствуют",
|
||||
"New_Unit": "Новая единица",
|
||||
"Create_New_Shopping Category": "Создание новой категории покупок",
|
||||
"Create_New_Food": "Добавить новую еду",
|
||||
"Create_New_Keyword": "Добавить ключевое слово",
|
||||
"Create_New_Unit": "Добавить единицу измерения",
|
||||
"Create_New_Meal_Type": "Добавить тип еды",
|
||||
"and_up": "Вверх",
|
||||
"Instructions": "Инструкции",
|
||||
"Unrated": "Без рейтинга",
|
||||
"Automate": "Автоматизировать",
|
||||
"Empty": "Пустой",
|
||||
"Key_Ctrl": "Ctrl",
|
||||
"Key_Shift": "Shift",
|
||||
"Time": "Время",
|
||||
"Text": "Текст",
|
||||
"Shopping_list": "Лист покупок",
|
||||
"Create_Meal_Plan_Entry": "Создать плана питания",
|
||||
"Edit_Meal_Plan_Entry": "Редактировать план питания",
|
||||
"Title": "Заголовок",
|
||||
"Week": "Неделя",
|
||||
"Month": "Месяц",
|
||||
"Year": "Год",
|
||||
"Planner": "Планировщик",
|
||||
"Planner_Settings": "Настройки Планировщика",
|
||||
"Period": "Период",
|
||||
"Plan_Period_To_Show": "Показать недели, месяца или годы",
|
||||
"Periods": "Периоды",
|
||||
"Plan_Show_How_Many_Periods": "Сколько периодов показать",
|
||||
"Starting_Day": "Начальный день недели",
|
||||
"Meal_Types": "Типы питания",
|
||||
"Meal_Type": "Тип питания",
|
||||
"Clone": "Клонировать",
|
||||
"Drag_Here_To_Delete": "Переместить для удаления",
|
||||
"Meal_Type_Required": "Тип питания обязателен",
|
||||
"Title_or_Recipe_Required": "Требуется выбор названия или рецепта",
|
||||
"Color": "Цвет",
|
||||
"New_Meal_Type": "Новый тип питания",
|
||||
"Week_Numbers": "",
|
||||
"Show_Week_Numbers": "",
|
||||
"Export_As_ICal": "",
|
||||
@@ -199,5 +199,24 @@
|
||||
"Previous_Period": "",
|
||||
"Current_Period": "",
|
||||
"Next_Day": "",
|
||||
"Previous_Day": ""
|
||||
"Previous_Day": "",
|
||||
"Add_nutrition_recipe": "Добавьте питательные вещества в рецепт",
|
||||
"and_down": "Вниз",
|
||||
"Added_by": "Добавлено",
|
||||
"Added_on": "Добавлено на",
|
||||
"AddToShopping": "Добавить в лист покупок",
|
||||
"IngredientInShopping": "Этот ингредиент в вашем списке покупок.",
|
||||
"OnHand": "В Наличии",
|
||||
"FoodOnHand": "{food} у вас в наличии.",
|
||||
"FoodNotOnHand": "{food} отсутствует в наличии.",
|
||||
"Undefined": "Неизвестно",
|
||||
"AddFoodToShopping": "Добавить {food} в ваш список покупок",
|
||||
"success_moving_resource": "Успешное перемещение ресурса!",
|
||||
"success_merging_resource": "Ресурс успешно присоединен!",
|
||||
"Shopping_Categories": "Категории покупок",
|
||||
"Search Settings": "Искать настройки",
|
||||
"err_merging_resource": "Произошла ошибка при перемещении ресурса!",
|
||||
"Remove_nutrition_recipe": "Уберите питательные вещества из рецепта",
|
||||
"err_moving_resource": "Ошибка при перемещении ресурса!",
|
||||
"NotInShopping": "{food} отсутствует в вашем списке покупок."
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"External_Recipe_Image": "Extern receptbild",
|
||||
"Add_to_Book": "Lägg till i kokbok",
|
||||
"Add_to_Shopping": "Lägg till i inköpslista",
|
||||
"Add_to_Plan": "Lägg till i matsedel",
|
||||
"Add_to_Plan": "Lägg till i måltidsplan",
|
||||
"Step_start_time": "Steg starttid",
|
||||
"Select_Book": "Välj kokbok",
|
||||
"Recipe_Image": "Receptbild",
|
||||
@@ -128,7 +128,7 @@
|
||||
"Disable_Amount": "Inaktivera belopp",
|
||||
"move_title": "Flytta {type}",
|
||||
"merge_title": "Slå samman {type}",
|
||||
"Food": "Mat",
|
||||
"Food": "Livsmedel",
|
||||
"Key_Shift": "Shift",
|
||||
"Instructions": "Instruktioner",
|
||||
"and_down": "& up",
|
||||
@@ -217,5 +217,168 @@
|
||||
"Meal_Plan_Days": "Framtida måltidsplaner",
|
||||
"Automate": "Automatisera",
|
||||
"Shopping_Categories": "Shopping kategorier",
|
||||
"Unit_Alias": "Enhetsalias"
|
||||
"Unit_Alias": "Enhetsalias",
|
||||
"search_import_help_text": "Importera ett recept från en extern webbplats eller applikation.",
|
||||
"warning_duplicate_filter": "Varning: På grund av tekniska begränsningar kan flera filter av samma kombination (och/eller/inte) ge oväntade resultat.",
|
||||
"merge_confirmation": "Ersätt <i>{source}</i> med <i>{target}</i>",
|
||||
"Unrated": "Ej betygsatt",
|
||||
"New_Cookbook": "Ny kokbok",
|
||||
"Hide_Keyword": "Dölj nyckelord",
|
||||
"Clear": "Rensa",
|
||||
"Coming_Soon": "Kommer snart",
|
||||
"Shopping_list": "Inköpslista",
|
||||
"Added_on": "Tillagd på",
|
||||
"AddToShopping": "Lägg till i inköpslista",
|
||||
"IngredientInShopping": "Denna ingrediens finns i din inköpslista.",
|
||||
"NotInShopping": "{food} finns inte i din inköpslista.",
|
||||
"OnHand": "För närvarande till hands",
|
||||
"FoodOnHand": "Du har {food} hemma.",
|
||||
"FoodNotOnHand": "Du har inte {food} hemma.",
|
||||
"Planner": "Planerare",
|
||||
"Planner_Settings": "Planerare inställningar",
|
||||
"IgnoredFood": "{food} är inställd på att ignorera inköp.",
|
||||
"Add_Servings_to_Shopping": "Lägg till {servings} portioner till inköp",
|
||||
"Cannot_Add_Notes_To_Shopping": "Anteckningar kan inte läggas till inköpslistan",
|
||||
"Added_To_Shopping_List": "Lades till i inköpslistan",
|
||||
"Shopping_List_Empty": "Din inköpslista är för närvarande tom, du kan lägga till varor via snabbmenyn för en måltidsplan (högerklicka på kortet eller vänsterklicka på menyikonen)",
|
||||
"ShowUncategorizedFood": "Visa odefinierad",
|
||||
"MoveCategory": "Flytta till: ",
|
||||
"CountMore": "...+{count} fler",
|
||||
"IgnoreThis": "Lägg aldrig till {mat} automatiskt i inköpslista",
|
||||
"DelayFor": "Fördröjning på {hours} timmar",
|
||||
"ShowDelayed": "Visa fördröjda artiklar",
|
||||
"Completed": "Avslutad",
|
||||
"OfflineAlert": "Du är offline, inköpslistan kanske inte synkroniseras.",
|
||||
"shopping_share": "Dela inköpslista",
|
||||
"shopping_auto_sync": "Autosynk",
|
||||
"mealplan_autoinclude_related": "Lägg till relaterade recept",
|
||||
"default_delay": "Standardfördröjningstimmar",
|
||||
"err_move_self": "Kan inte flytta objektet till sig självt",
|
||||
"nothing": "Inget att göra",
|
||||
"err_merge_self": "Kan inte slå samman objektet med sig självt",
|
||||
"show_sql": "Visa SQL",
|
||||
"CategoryName": "Kategorinamn",
|
||||
"SupermarketName": "Mataffärens namn",
|
||||
"filter_to_supermarket": "Filter till mataffär",
|
||||
"download_pdf": "Ladda ner PDF",
|
||||
"download_csv": "Ladda ner CSV",
|
||||
"csv_delim_help": "Avgränsare att använda för CSV-export.",
|
||||
"csv_delim_label": "CSV-avgränsare",
|
||||
"SuccessClipboard": "Inköpslista kopierad till urklipp",
|
||||
"copy_to_clipboard": "Kopiera till urklipp",
|
||||
"csv_prefix_label": "Listprefix",
|
||||
"copy_markdown_table": "Kopiera som Markdown-tabell",
|
||||
"in_shopping": "I inköpslistan",
|
||||
"DelayUntil": "Fördröjning till",
|
||||
"enable_expert": "Aktivera expertläge",
|
||||
"expert_mode": "Expertläge",
|
||||
"simple_mode": "Enkelt läge",
|
||||
"advanced": "Avancerat",
|
||||
"fields": "Fält",
|
||||
"show_keywords": "Visa nyckelord",
|
||||
"show_books": "Visa böcker",
|
||||
"show_rating": "Visa betyg",
|
||||
"show_filters": "Visa filter",
|
||||
"not": "inte",
|
||||
"save_filter": "Spara filter",
|
||||
"filter_name": "Filternamn",
|
||||
"left_handed": "Vänsterhänt läge",
|
||||
"left_handed_help": "Kommer att optimera användargränssnittet för användning med din vänstra hand.",
|
||||
"Custom Filter": "Anpassat filter",
|
||||
"shared_with": "Delad med",
|
||||
"copy_to_new": "Kopiera till nytt recept",
|
||||
"recipe_name": "Receptnamn",
|
||||
"paste_ingredients_placeholder": "Klistra in ingredienslistan här...",
|
||||
"paste_ingredients": "Klistra in ingredienser",
|
||||
"ingredient_list": "Ingredienslista",
|
||||
"explain": "Förklara",
|
||||
"filter": "Filter",
|
||||
"search_no_recipes": "Hittade inga recept!",
|
||||
"search_create_help_text": "Skapa ett nytt recept direkt i Tandoor.",
|
||||
"substitute_help": "Ersättningar övervägs när man söker efter recept som kan göras med tillgängliga ingredienser.",
|
||||
"sort_by": "Sortera efter",
|
||||
"asc": "Stigande",
|
||||
"desc": "Fallande",
|
||||
"date_viewed": "Senast visad",
|
||||
"last_cooked": "Senast tillagad",
|
||||
"date_created": "Skapat datum",
|
||||
"show_sortby": "Visa sortera efter",
|
||||
"search_rank": "Sök rank",
|
||||
"make_now": "Gör nu",
|
||||
"recipe_filter": "Receptfilter",
|
||||
"created_on": "Skapat den",
|
||||
"updatedon": "Uppdaterad den",
|
||||
"advanced_search_settings": "Avancerade sökinställningar",
|
||||
"nothing_planned_today": "Du har ingenting planerat för idag!",
|
||||
"Planned": "Planerad",
|
||||
"Quick actions": "Snabba åtgärder",
|
||||
"Ratings": "Betyg",
|
||||
"Internal": "Intern",
|
||||
"Random Recipes": "Slumpmässiga recept",
|
||||
"parameter_count": "Parameter {count}",
|
||||
"ignore_shopping_help": "Lägg aldrig till ingrediens på inköpslistan (t.ex. vatten)",
|
||||
"review_shopping": "Granska inköpsposter innan du sparar",
|
||||
"view_recipe": "Visa recept",
|
||||
"del_confirmation_tree": "Är du säker på att du vill ta bort {source} och alla dess underordnade?",
|
||||
"today_recipes": "Dagens recept",
|
||||
"move_confirmation": "Flytta<i>{child}</i> till förälder <i>{parent}</i>",
|
||||
"create_shopping_new": "Lägg till i ny inköpslista",
|
||||
"csv_prefix_help": "Prefix att lägga till när listan kopieras till urklipp.",
|
||||
"show_units": "Visa enheter",
|
||||
"remember_search": "Kom ihåg sökning",
|
||||
"sql_debug": "SQL felsökning",
|
||||
"Create_New_Food": "Lägg till nytt livsmedel",
|
||||
"Pin": "Pin",
|
||||
"Edit_Food": "Redigera livsmedel",
|
||||
"Move_Food": "Flytta livsmedel",
|
||||
"Create_Meal_Plan_Entry": "Skapa en måltidsplan",
|
||||
"Edit_Meal_Plan_Entry": "Redigera matplansinlägg",
|
||||
"FoodInherit": "Ärftliga livsmedels fält",
|
||||
"SupermarketCategoriesOnly": "Endast mataffärskategorier",
|
||||
"InheritWarning": "{food} är inställd på att ärva, ändringar kanske inte kvarstår.",
|
||||
"mealplan_autoadd_shopping": "Lägg till måltidsplan automatiskt",
|
||||
"mealplan_autoexclude_onhand": "Uteslut livsmedel till hands",
|
||||
"shopping_share_desc": "Användare kommer att se alla varor du lägger till i din inköpslista. De måste lägga till dig för att se objekt på sin lista.",
|
||||
"shopping_auto_sync_desc": "Inställning satt till 0 inaktiverar automatisk synkronisering. När du tittar på en inköpslista uppdateras listan varje sekund för att synkronisera ändringar som någon annan kan ha gjort. Användbart när du handlar med flera personer men kommer att använda mobildata.",
|
||||
"mealplan_autoadd_shopping_desc": "Lägg automatiskt till måltidsplanens ingredienser till inköpslistan.",
|
||||
"mealplan_autoexclude_onhand_desc": "När du lägger till en måltidsplan till inköpslistan (manuellt eller automatiskt), uteslut ingredienser som för närvarande finns till hands.",
|
||||
"mealplan_autoinclude_related_desc": "När du lägger till en måltidsplan till inköpslistan (manuellt eller automatiskt), inkludera alla relaterade recept.",
|
||||
"default_delay_desc": "Förinställt antal timmar för att fördröja en inköpslista.",
|
||||
"filter_to_supermarket_desc": "Filtrera inköpslistan som standard så att den endast inkluderar kategorier för utvalda mataffärer.",
|
||||
"CategoryInstruction": "Dra kategorier för att ändra den ordning som kategorierna visas i inköpslistan.",
|
||||
"shopping_recent_days_desc": "Dagar av senaste inköpslistorna att visa.",
|
||||
"shopping_recent_days": "Senaste dagarna",
|
||||
"Foods": "Livsmedel",
|
||||
"show_foods": "Visa livsmedel",
|
||||
"times_cooked": "Antal gånger som tillagats",
|
||||
"book_filter_help": "Inkludera recept från receptfilter utöver de manuellt tilldelade.",
|
||||
"reset_children_help": "Skriv över alla underordnade med värden från ärvda fält. Ärvda fält av underordnade fält kommer att ställas in på ärvda fält om inte ärvda fält för underordnade är inställda.",
|
||||
"reset_children": "Återställ underordnades arv",
|
||||
"substitute_siblings_help": "All mat som delar en överordnad till detta livsmedel anses vara substitut.",
|
||||
"substitute_children_help": "All mat som är underordnat till detta livsmedel anses vara substitut.",
|
||||
"substitute_siblings": "Ersättande syskon",
|
||||
"substitute_children": "Ersättande underordnade",
|
||||
"SubstituteOnHand": "Du har ett substitut till hands.",
|
||||
"ChildInheritFields": "Underordnade ärver fält",
|
||||
"ChildInheritFields_help": "Underordnade kommer att ärva dessa fält som standard.",
|
||||
"InheritFields_help": "Värdena i dessa fält kommer att ärvas från förälder (Undantag: tomma shoppingkategorier ärvs inte)",
|
||||
"no_pinned_recipes": "Du har inga nålade recept!",
|
||||
"Pinned": "Nålad",
|
||||
"OnHand_help": "Livsmedel som finns i lager kommer inte automatiskt att läggas till på en inköpslista. Onhand-status delas med shoppinganvändare.",
|
||||
"shopping_category_help": "Mataffärer kan sorteras och filtreras efter Shopping-kategori enligt gångarnas layout.",
|
||||
"food_recipe_help": "Om du länkar ett recept här kommer det länkade receptet att inkluderas i alla andra recept som använder detta livsmedel",
|
||||
"New_Food": "Nytt livsmedel",
|
||||
"Hide_Food": "Dölj livsmedel",
|
||||
"Food_Alias": "Alias för livsmedel",
|
||||
"Delete_Food": "Ta bort livsmedel",
|
||||
"mark_complete": "Markera som färdig",
|
||||
"QuickEntry": "Snabbt inlägg",
|
||||
"shopping_add_onhand_desc": "Markera livsmedlet som \"till hands\" när den blir avbockad i inköpslistan.",
|
||||
"shopping_add_onhand": "Automatisk Till hands",
|
||||
"related_recipes": "Relaterade recept",
|
||||
"Create Food": "Skapa livsmedel",
|
||||
"create_food_desc": "Skapa ett livsmedel och länka det till det här receptet.",
|
||||
"additional_options": "Ytterligare alternativ",
|
||||
"remember_hours": "Timmar att komma ihåg",
|
||||
"tree_select": "Använd trädval"
|
||||
}
|
||||
|
||||
@@ -612,6 +612,18 @@ export class Models {
|
||||
list: {
|
||||
params: ["filter_list"],
|
||||
},
|
||||
create: {
|
||||
params: [["name",]],
|
||||
form: {
|
||||
name: {
|
||||
form_field: true,
|
||||
type: "text",
|
||||
field: "name",
|
||||
label: "Name",
|
||||
placeholder: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static MEAL_PLAN = {
|
||||
|
||||
@@ -46,6 +46,7 @@ export class StandardToasts {
|
||||
static FAIL_FETCH = "FAIL_FETCH"
|
||||
static FAIL_UPDATE = "FAIL_UPDATE"
|
||||
static FAIL_DELETE = "FAIL_DELETE"
|
||||
static FAIL_DELETE_PROTECTED = "FAIL_DELETE_PROTECTED"
|
||||
static FAIL_MOVE = "FAIL_MOVE"
|
||||
static FAIL_MERGE = "FAIL_MERGE"
|
||||
|
||||
@@ -81,6 +82,9 @@ export class StandardToasts {
|
||||
case StandardToasts.FAIL_DELETE:
|
||||
makeToast(i18n.tc("Failure"), i18n.tc("err_deleting_resource") + (err_details ? "\n" + err_details : ""), "danger")
|
||||
break
|
||||
case StandardToasts.FAIL_DELETE_PROTECTED:
|
||||
makeToast(i18n.tc("Protected"), i18n.tc("err_deleting_protected_resource"), "danger")
|
||||
break
|
||||
case StandardToasts.FAIL_MOVE:
|
||||
makeToast(i18n.tc("Failure"), i18n.tc("err_moving_resource") + (err_details ? "\n" + err_details : ""), "danger")
|
||||
break
|
||||
@@ -187,6 +191,10 @@ export function calculateAmount(amount, factor) {
|
||||
let return_string = ""
|
||||
let fraction = frac(amount * factor, 10, true)
|
||||
|
||||
if (fraction[0] === 0 && fraction[1] === 0 && fraction[2] === 1) {
|
||||
return roundDecimals(amount * factor)
|
||||
}
|
||||
|
||||
if (fraction[0] > 0) {
|
||||
return_string += fraction[0]
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ const pages = {
|
||||
entry: "./src/apps/MealPlanView/main.js",
|
||||
chunks: ["chunk-vendors"],
|
||||
},
|
||||
ingredient_editor_view: {
|
||||
entry: "./src/apps/IngredientEditorView/main.js",
|
||||
chunks: ["chunk-vendors"],
|
||||
},
|
||||
shopping_list_view: {
|
||||
entry: "./src/apps/ShoppingListView/main.js",
|
||||
chunks: ["chunk-vendors"],
|
||||
|
||||
Reference in New Issue
Block a user