Compare commits

...

23 Commits
2.3.0 ... 2.3.3

Author SHA1 Message Date
vabene1111
d22b5a4a39 Merge branch 'develop' 2025-10-15 15:18:13 +02:00
vabene1111
d09e629415 fixed input type 2025-10-15 15:10:59 +02:00
vabene1111
53ef2ef99f fixed ingredient input decimals 2025-10-15 15:02:19 +02:00
vabene1111
602f0a8bf0 Merge branch 'master' of https://github.com/TandoorRecipes/recipes 2025-10-11 11:59:11 +02:00
vabene1111
673d12d233 increased default max body size for file uploads to 512 MB 2025-10-11 11:57:32 +02:00
vabene1111
6359245925 Merge branch 'develop' of https://github.com/TandoorRecipes/recipes into develop 2025-10-11 11:56:51 +02:00
vabene1111
a7c4822322 fixed cannoit create steps if none are present 2025-10-11 11:56:47 +02:00
vabene1111
e94419f320 Merge pull request #4138 from KoMa1012/KoMa1012-gunicorn-timeoutconfig
Update boot.sh
2025-10-11 11:55:48 +02:00
vabene1111
01f46483ff clearer batch delete warning 2025-10-11 11:52:52 +02:00
vabene1111
d6da5688af fixed 0 servings 2025-10-11 11:47:10 +02:00
vabene1111
680ae39201 fixed import button not switching to loading in app import 2025-10-11 11:38:58 +02:00
vabene1111
2472ee9c26 fixed api setting example 2025-10-11 11:05:20 +02:00
vabene1111
4428b06d4a fixed properties edge case with missing conversions 2025-10-11 11:04:00 +02:00
vabene1111
e9c38d7d5e added bottom margin to properties editor 2025-10-11 09:22:43 +02:00
vabene1111
6f28d58807 fixed and imporved food properties 2025-10-11 09:20:58 +02:00
vabene1111
88db611f0a Merge branch 'develop' of https://github.com/TandoorRecipes/recipes into develop 2025-10-11 09:02:14 +02:00
vabene1111
f3302b4014 fixed start page and ingredient templater 2025-10-11 09:02:10 +02:00
vabene1111
d4bb161275 Merge pull request #4147 from TandoorRecipes/dependabot/pip/python-ldap-3.4.5
Bump python-ldap from 3.4.4 to 3.4.5
2025-10-11 08:36:03 +02:00
dependabot[bot]
32f1538938 Bump python-ldap from 3.4.4 to 3.4.5
Bumps [python-ldap](https://github.com/python-ldap/python-ldap) from 3.4.4 to 3.4.5.
- [Release notes](https://github.com/python-ldap/python-ldap/releases)
- [Changelog](https://github.com/python-ldap/python-ldap/blob/python-ldap-3.4.5/CHANGES)
- [Commits](https://github.com/python-ldap/python-ldap/compare/python-ldap-3.4.4...python-ldap-3.4.5)

---
updated-dependencies:
- dependency-name: python-ldap
  dependency-version: 3.4.5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-10 22:55:18 +00:00
KoMa1012
029baea4c7 Update configuration.md
added explanation for GUNICORN_TIMEOUT environmental variable
2025-10-09 12:51:25 +02:00
KoMa1012
38d1b7cef5 Update boot.sh
made gunicorn timout configurable via environmental variable GUNICORN_TIMEOUT this can help in scenarios where e.g. the LLM takes too much time to answer when using a local LLM
2025-10-08 20:09:51 +02:00
vabene1111
856f417d1b Merge branch 'develop' 2025-10-08 07:57:59 +02:00
vabene1111
85821bcc94 nginx config update 2025-10-08 07:42:09 +02:00
22 changed files with 106 additions and 51 deletions

View File

@@ -104,5 +104,5 @@ chmod -R 755 ${MEDIA_ROOT:-/opt/recipes/mediafiles}
ipv6_disable=$(cat /sys/module/ipv6/parameters/disable)
echo "Starting gunicorn"
exec gunicorn --bind unix:/run/tandoor.sock --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi
exec gunicorn --bind unix:/run/tandoor.sock --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --timeout ${GUNICORN_TIMEOUT:-30} --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi

View File

@@ -48,7 +48,7 @@ class FoodPropertyHelper:
found_property = False
# if food has a value for the given property type (no matter if conversion is possible)
has_property_value = False
if i.food.properties_food_amount == 0 or i.food.properties_food_unit is None and not (i.amount == 0 or i.no_amount): # if food is configured incorrectly
if (i.food.properties_food_amount == 0 or i.food.properties_food_unit is None) and not (i.amount == 0 or i.no_amount): # if food is configured incorrectly
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': None}
computed_properties[pt.id]['missing_value'] = True
else:
@@ -63,8 +63,9 @@ class FoodPropertyHelper:
computed_properties[p.property_type.id]['food_values'], c.food.id, (c.amount / i.food.properties_food_amount) * p.property_amount, c.food)
if not found_property:
# if no amount and food does not exist yet add it but don't count as missing
if i.amount == 0 or i.no_amount and i.food.id not in computed_properties[pt.id]['food_values']:
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': 0}
if i.amount == 0 or i.no_amount:
if i.food.id not in computed_properties[pt.id]['food_values']:
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': 0}
# if amount is present but unit is missing indicate it in the result
elif i.unit is None:
if i.food.id not in computed_properties[pt.id]['food_values']:
@@ -72,7 +73,8 @@ class FoodPropertyHelper:
computed_properties[pt.id]['food_values'][i.food.id]['missing_unit'] = True
else:
computed_properties[pt.id]['missing_value'] = True
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': None}
if i.food.id not in computed_properties[pt.id]['food_values']:
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': None}
if has_property_value and i.unit is not None:
computed_properties[pt.id]['food_values'][i.food.id]['missing_conversion'] = {'base_unit': {'id': i.unit.id, 'name': i.unit.name}, 'converted_unit': {'id': i.food.properties_food_unit.id, 'name': i.food.properties_food_unit.name}}
@@ -82,8 +84,12 @@ class FoodPropertyHelper:
# TODO move to central helper ? --> use defaultdict
@staticmethod
def add_or_create(d, key, value, food):
if key in d and d[key]['value']:
d[key]['value'] += value
if key in d:
# value can be None if a previous instance of the same food was missing a conversion
if d[key]['value']:
d[key]['value'] += value
else:
d[key]['value'] = value
else:
d[key] = {'id': food.id, 'food': {'id': food.id, 'name': food.name}, 'value': value}
return d

View File

@@ -72,7 +72,8 @@ def get_from_scraper(scrape, request):
# assign servings attributes
try:
# dont use scrape.yields() as this will always return "x servings" or "x items", should be improved in scrapers directly
servings = scrape.schema.data.get('recipeYield') or 1
# max(x,1) to prevent 0 servings which breaks scaling
servings = max(scrape.schema.data.get('recipeYield') or 1, 1)
except Exception:
servings = 1

View File

@@ -189,6 +189,19 @@ See [Gunicorn docs](https://docs.gunicorn.org/en/stable/design.html#how-many-wor
GUNICORN_THREADS=2
```
#### Gunicorn Timeout
> default `30` - options `1-X`
Set the timeout in seconds of gunicorn when starting using `boot.sh` (all container installations).
The default is likely appropriate for most installations. However, if you are using a LLM which high response times gunicornmight time out during the wait until the LLM finished, in such cases you might want to increase the timeout.
See [Gunicorn docs]([https://docs.gunicorn.org/en/stable/design.html#how-many-workers](https://docs.gunicorn.org/en/stable/settings.html#timeout)) for default settings.
```
GUNICORN_TIMEOUT=30
```
#### Gunicorn Media
> default `0` - options `0`, `1`

View File

@@ -3,7 +3,7 @@ server {
listen [::]:${TANDOOR_PORT} ipv6only=on;
server_name localhost;
client_max_body_size 128M;
client_max_body_size 512M;
# serve media files
location /media {
@@ -21,6 +21,9 @@ server {
proxy_set_header Host $http_host;
proxy_pass http://unix:/run/tandoor.sock;
# param needed by django allauth sessions to log IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# disabled for now because it redirects to the error page and not back, also not showing html
#error_page 502 /errors/http502.html;
}

View File

@@ -37,7 +37,7 @@ django-storages==1.14.6
boto3==1.28.75
django-prometheus==2.4.1
django-hCaptcha==0.2.0
python-ldap==3.4.4
python-ldap==3.4.5
django-auth-ldap==4.6.0
pyppeteer==2.0.0
pytubefix==9.2.2

View File

@@ -53,13 +53,13 @@
{{ fv.food.name }}
</span>
<template #append>
<v-chip v-if="fv.value != undefined">{{ $n(fv.value) }} {{ dialogProperty.unit }}</v-chip>
<v-chip color="create" v-else-if="fv.missing_conversion" class="cursor-pointer" prepend-icon="$create">
<v-chip color="create" v-if="fv.missing_conversion" class="cursor-pointer" prepend-icon="$create">
{{ $t('Conversion') }}: {{ fv.missing_conversion.base_unit.name }} <i class="fa-solid fa-arrow-right me-1 ms-1"></i>
{{ fv.missing_conversion.converted_unit.name }}
<model-edit-dialog model="UnitConversion" @create="refreshRecipe()"
:item-defaults="{baseAmount: 1, baseUnit: fv.missing_conversion.base_unit, convertedUnit: fv.missing_conversion.converted_unit, food: fv.food}"></model-edit-dialog>
</v-chip>
<v-chip v-else-if="fv.value != undefined">{{ $n(fv.value) }} {{ dialogProperty.unit }}</v-chip>
<v-chip color="warning" prepend-icon="$edit" class="cursor-pointer" :to="{name: 'ModelEditPage', params: {model: 'Recipe', id: recipe.id}}" v-else-if="fv.missing_unit">
{{ $t('NoUnit') }}
</v-chip>

View File

@@ -229,7 +229,7 @@ const selectedAiProvider = ref<undefined | AiProvider>(useUserPreferenceStore().
* factor for multiplying ingredient amounts based on recipe base servings and user selected servings
*/
const ingredientFactor = computed(() => {
return servings.value / ((recipe.value.servings != undefined) ? recipe.value.servings : 1)
return servings.value / ((recipe.value.servings != undefined) ? Math.max(recipe.value.servings, 1) : 1)
})
/**

View File

@@ -1,6 +1,6 @@
<template>
<v-btn-group density="compact">
<v-btn color="create" @click="editingObj.properties.push({} as Property)" prepend-icon="$create">{{ $t('Add') }}</v-btn>
<v-btn color="create" @click="editingObj.properties.push({} as Property); addPropertiesFoodUnit()" prepend-icon="$create">{{ $t('Add') }}</v-btn>
<v-btn color="secondary" @click="addAllProperties" prepend-icon="fa-solid fa-list">{{ $t('AddAll') }}</v-btn>
<ai-action-button color="info" @selected="propertiesFromAi" :loading="aiLoading" prepend-icon="$ai">{{ $t('AI') }}</ai-action-button>
</v-btn-group>
@@ -41,12 +41,13 @@
<script setup lang="ts">
import {ApiApi, Food, Property, Recipe} from "@/openapi";
import {ApiApi, Food, Property, Recipe, Unit} from "@/openapi";
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {computed, onMounted, ref} from "vue";
import {computed, nextTick, onMounted, ref} from "vue";
import AiActionButton from "@/components/buttons/AiActionButton.vue";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore.ts";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
const props = defineProps({
amountFor: {type: String, required: true},
@@ -78,9 +79,11 @@ function deleteProperty(property: Property) {
function addAllProperties() {
const api = new ApiApi()
if (editingObj.value.properties) {
editingObj.value.properties = []
}
// if (editingObj.value.properties) {
// editingObj.value.properties = []
// }
addPropertiesFoodUnit()
api.apiPropertyTypeList().then(r => {
r.results.forEach(pt => {
@@ -98,6 +101,9 @@ function propertiesFromAi(providerId: number) {
if (isFood.value) {
api.apiFoodAipropertiesCreate({id: editingObj.value.id!, food: editingObj.value, provider: providerId}).then(r => {
editingObj.value = r
nextTick(() => {
addPropertiesFoodUnit()
})
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
}).finally(() => {
@@ -115,6 +121,17 @@ function propertiesFromAi(providerId: number) {
}
/**
* if its empty add the properties food unit
*/
function addPropertiesFoodUnit(){
console.log('ADDING UNIT', !editingObj.value.propertiesFoodUnit)
if (isFood.value && !editingObj.value.propertiesFoodUnit) {
console.log('ADDING UNIT ACTUALLY')
editingObj.value.propertiesFoodUnit = (useUserPreferenceStore().defaultUnitObj != null) ? useUserPreferenceStore().defaultUnitObj! : {name: 'g'} as Unit
}
}
</script>

View File

@@ -67,13 +67,13 @@
</div>
<div class="d-flex flex-nowrap">
<div class="flex-col flex-grow-0 ma-1" style="min-width: 15%" v-if="!ingredient.isHeader">
<v-text-field :id="`id_input_amount_${props.stepIndex}_${index}`" :label="$t('Amount')" type="number" v-model="ingredient.amount" density="compact"
hide-details :disabled="ingredient.noAmount">
<v-number-input :id="`id_input_amount_${props.stepIndex}_${index}`" :label="$t('Amount')" v-model="ingredient.amount" density="compact"
hide-details control-variant="hidden" :disabled="ingredient.noAmount" :precision="useUserPreferenceStore().userSettings.ingredientDecimals">
<template #prepend>
<v-icon icon="$dragHandle" class="drag-handle cursor-grab"></v-icon>
</template>
</v-text-field>
</v-number-input>
</div>
<div class="flex-col flex-grow-0 ma-1" style="min-width: 15%" v-if="!ingredient.isHeader ">
<model-select model="Unit" v-model="ingredient.unit" density="compact" allow-create hide-details :disabled="ingredient.noAmount"></model-select>
@@ -195,7 +195,7 @@
<v-text-field :label="$t('Original_Text')" readonly v-model="step.ingredients[editingIngredientIndex].originalText"
v-if="step.ingredients[editingIngredientIndex].originalText"></v-text-field>
<v-number-input v-model="step.ingredients[editingIngredientIndex].amount" inset control-variant="stacked" autofocus :label="$t('Amount')"
:min="0" :precision="2" v-if="!step.ingredients[editingIngredientIndex].isHeader"></v-number-input>
:min="0" :precision="useUserPreferenceStore().userSettings.ingredientDecimals" v-if="!step.ingredients[editingIngredientIndex].isHeader"></v-number-input>
<model-select model="Unit" v-model="step.ingredients[editingIngredientIndex].unit" :label="$t('Unit')" v-if="!step.ingredients[editingIngredientIndex].isHeader"
allow-create></model-select>
<model-select model="Food" v-model="step.ingredients[editingIngredientIndex].food" :label="$t('Food')" v-if="!step.ingredients[editingIngredientIndex].isHeader"

View File

@@ -17,10 +17,10 @@
<v-list density="compact">
<v-list-subheader>{{$t('Ingredients')}}</v-list-subheader>
<v-list-item
v-for="template in templates"
@click="insertTextAtPosition(template.template + ' ')"
v-for="t in templates"
@click="insertTextAtPosition(t.template + ' ')"
>
<ingredient-string :ingredient="template.ingredient"></ingredient-string>
<ingredient-string :ingredient="t.ingredient"></ingredient-string>
</v-list-item>
</v-list>
</v-menu>
@@ -65,7 +65,7 @@ const templates = computed(() => {
function insertTextAtPosition(text: string){
let textarea = markdownEditor.value.getTextareaDom()
let position = textarea.selectionStart
if (step.value.instruction){
if (step.value.instruction != undefined){
step.value.instruction = step.value.instruction.slice(0, position) + text + step.value.instruction.slice(position)
nextTick(() => {

View File

@@ -226,7 +226,7 @@ function initializeEditor() {
setupState(props.item, props.itemId, {
newItemFunction: () => {
editingObj.value.propertiesFoodAmount = 100
editingObj.value.propertiesFoodUnit = {name: (useUserPreferenceStore().userSettings.defaultUnit != undefined ? useUserPreferenceStore().userSettings.defaultUnit : 'g')} as Unit
editingObj.value.propertiesFoodUnit = (useUserPreferenceStore().defaultUnitObj != null) ? useUserPreferenceStore().defaultUnitObj! : {name: 'g'} as Unit
},
itemDefaults: props.itemDefaults,
})

View File

@@ -87,6 +87,12 @@
</v-row>
<v-form :disabled="loading || fileApiLoading">
<v-row v-if="editingObj.steps.length == 0">
<v-col class="text-center">
<v-btn icon="$create" variant="outlined" size="x-small" @click="addStep(i+1)"></v-btn>
</v-col>
</v-row>
<v-row v-for="(s,i ) in editingObj.steps" :key="s.id" dense>
<v-col>
<step-editor v-model="editingObj.steps[i]" v-model:recipe="editingObj" :step-index="i" @delete="deleteStepAtIndex(i)" @move="dialogStepManager = true"></step-editor>

View File

@@ -26,7 +26,7 @@
Authentication works by proving the word <code>Bearer</code> followed by an API Token as a request Authorization
header as shown below. <br/>
<code>Authorization: Bearer TOKEN</code> -or-<br/>
<code>curl -X GET http://your.domain.com/api/recipes/ -H 'Authorization:
<code>curl -X GET http://your.domain.com/api/recipe/ -H 'Authorization:
Bearer TOKEN'</code>
<br/>

View File

@@ -124,6 +124,8 @@ export function useFileApi() {
* @returns Promise resolving to the import ID of the app import
*/
function doAppImport(files: File[], app: string, includeDuplicates: boolean, mealPlans: boolean = true, shoppingLists: boolean = true, nutritionPerServing: boolean = false,) {
fileApiLoading.value = true
let formData = new FormData()
formData.append('type', app);
formData.append('duplicates', includeDuplicates ? 'true' : 'false')

View File

@@ -57,7 +57,7 @@
"BaseUnit": "Basiseinheit",
"BaseUnitHelp": "Optionale Standardeinheit zur automatischen Umrechnung",
"Basics": "Grundlagen",
"BatchDeleteConfirm": "Möchtest du alle angezeigten Objekte löschen? Dies kann nicht rückgängig gemacht werden!",
"BatchDeleteConfirm": "Möchtest du alle angezeigten Objekte löschen? Dies kann nicht rückgängig gemacht werden! ACHTUNG: Es ist möglich das Objekte gelöscht werden die an anderen Stellen verwendet werden!",
"BatchDeleteHelp": "Wenn ein Objekt nicht gelöscht werden kann, wird es noch irgendwo verwendet. ",
"BatchEdit": "Massenbearbeitung",
"BatchEditUpdatingItemsCount": "Bearbeite {count} {type}",

View File

@@ -55,7 +55,7 @@
"BaseUnit": "Base Unit",
"BaseUnitHelp": "Standard unit for automatic unit conversion",
"Basics": "Basics",
"BatchDeleteConfirm": "Do you want to delete all shown items? This cannot be undone!",
"BatchDeleteConfirm": "Do you want to delete all shown items? This cannot be undone! WARNING: It is possible that this deletes objects that are used elsewhere. ",
"BatchDeleteHelp": "If an item cannot be deleted it is used somewhere. ",
"BatchEdit": "Batch Edit",
"BatchEditUpdatingItemsCount": "Editing {count} {type}",

View File

@@ -56,7 +56,7 @@
"BaseUnit": "Unità di base",
"BaseUnitHelp": "Unità standard per la conversione automatica di unità",
"Basics": "Informazioni di base",
"BatchDeleteConfirm": "Vuoi eliminare tutti gli elementi mostrati? Questo non può essere annullato!",
"BatchDeleteConfirm": "",
"BatchDeleteHelp": "Se un elemento non può essere eliminato, è utilizzato altrove. ",
"BatchEdit": "Modifica massiva",
"BatchEditUpdatingItemsCount": "Modifica di {count} {type}",

View File

@@ -26,6 +26,7 @@
"Auto_Sort_Help": "Mover todos os ingredientes para o passo mais indicado.",
"Automate": "Automatizar",
"Automation": "Automação",
"BatchDeleteConfirm": "",
"Books": "Livros",
"Calculator": "Calculadora",
"Calories": "Calorias",

View File

@@ -49,10 +49,10 @@
<td>
{{ ingredient.food.name }}
<!-- TODO weird mixture of using ingredients but not in the correct relation to the recipe not good, properly sort out and add easy unitconversion/food edit features -->
<!-- <v-btn variant="outlined" block>-->
<!-- {{ ingredient.food.name }}-->
<!-- <model-edit-dialog model="Food" :item="ingredient.food!" @save="args => ingredient.food = args"></model-edit-dialog>-->
<!-- </v-btn>-->
<!-- <v-btn variant="outlined" block>-->
<!-- {{ ingredient.food.name }}-->
<!-- <model-edit-dialog model="Food" :item="ingredient.food!" @save="args => ingredient.food = args"></model-edit-dialog>-->
<!-- </v-btn>-->
<!-- <v-chip v-if="ingredient.unit && ingredient.food.propertiesFoodUnit && ingredient.unit.id == ingredient.food.propertiesFoodUnit.id" color="success"-->
<!-- size="small">{{ ingredient.unit.name }}-->
<!-- </v-chip>-->
@@ -73,7 +73,8 @@
@click="fdcSelectedIngredient = ingredient; fdcDialog = true"></v-btn>
<v-btn @click="updateFoodFdcData(ingredient)" icon="fa-solid fa-arrows-rotate" size="small" density="compact" variant="plain"
v-if="ingredient.food.fdcId"></v-btn>
<v-btn @click="openFdcPage(ingredient.food.fdcId)" :href="`https://fdc.nal.usda.gov/food-details/${ingredient.food.fdcId}/nutrients`" target="_blank"
<v-btn @click="openFdcPage(ingredient.food.fdcId)" :href="`https://fdc.nal.usda.gov/food-details/${ingredient.food.fdcId}/nutrients`"
target="_blank"
icon="fa-solid fa-arrow-up-right-from-square"
size="small" variant="plain" v-if="ingredient.food.fdcId"></v-btn>
</template>
@@ -81,7 +82,7 @@
</td>
<td>
<v-number-input v-model="ingredient.food.propertiesFoodAmount" density="compact" hide-details @change="updateFood(ingredient)"
:loading="ingredient.loading" style="min-width: 100px" control-variant="hidden" :precision="2">
:loading="ingredient.loading" style="min-width: 100px" control-variant="hidden" :precision="2">
</v-number-input>
</td>
@@ -90,8 +91,10 @@
:loading="ingredient.loading"></model-select>
</td>
<td v-for="p in ingredient.food.properties" v-bind:key="`${ingredient.food.id}_${p.propertyType.id}`">
<v-number-input v-model="p.propertyAmount" density="compact" hide-details v-if="p.propertyAmount != null" @change="updateFood(ingredient)" :precision="2"
:loading="ingredient.loading" @click:clear="deleteFoodProperty(p, ingredient)" style="min-width: 120px" control-variant="hidden" clearable>
<v-number-input v-model="p.propertyAmount" density="compact" hide-details v-if="p.propertyAmount != null" @change="updateFood(ingredient)"
:precision="2"
:loading="ingredient.loading" @click:clear="deleteFoodProperty(p, ingredient)" style="min-width: 120px" control-variant="hidden"
clearable>
</v-number-input>
@@ -104,11 +107,10 @@
</td>
</tr>
</tbody>
<!-- TODO remove once append to body for model select is working properly -->
<v-spacer style="margin-top: 120px;"></v-spacer>
</v-table>
</v-col>
</v-row>
<v-row>
<v-col>
<v-card prepend-icon="fa-solid fa-calculator" :title="$t('Calculator')">
<v-card-text>
<v-row dense>

View File

@@ -34,13 +34,13 @@
</v-card-text>
</v-card>
<template v-if="totalRecipes > 0">
<horizontal-recipe-scroller :skeletons="4" mode="recent" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="new" v-if="totalRecipes > 1"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="recent" v-if="totalRecipes > 0"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="new" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="keyword" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="random" v-if="totalRecipes > 1"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="created_by" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="2" mode="rating" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="keyword" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="random" v-if="totalRecipes >= 5"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="created_by" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="2" mode="rating" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="keyword" v-if="totalRecipes > 25"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="random" v-if="totalRecipes > 25"></horizontal-recipe-scroller>
<v-row>

View File

@@ -23,7 +23,7 @@ export default createVuetify({
},
// always localize the date display of DateInputs
VDateInput: {
displayFormat : (date: Date) => DateTime.fromJSDate(date).toLocaleString()
displayFormat: (date: Date) => DateTime.fromJSDate(date).toLocaleString()
},
// always use color for switches to properly see if enabled or not
VSwitch: {
@@ -34,6 +34,10 @@ export default createVuetify({
decimalSeparator: 0.1.toLocaleString().replace(/\d/g, '')
}
},
// locale: {
// locale: 'de',
// fallback: 'en',
// },
theme: {
defaultTheme: 'light',
themes: {