mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 04:10:06 -05:00
basic property viewer
This commit is contained in:
@@ -40,7 +40,7 @@ const routes = [
|
||||
{path: '/view/recipe/:id', redirect: {name: 'RecipeViewPage'}}, // old Tandoor v1 url pattern
|
||||
|
||||
{path: '/list/:model?', component: () => import("@/pages/ModelListPage.vue"), props: true, name: 'ModelListPage'},
|
||||
{path: '/edit/:model/:id?', component: () => import("@/pages/ModelListPage.vue"), props: true, name: 'ModelEditPage'},
|
||||
{path: '/edit/:model/:id?', component: () => import("@/pages/ModelEditPage.vue"), props: true, name: 'ModelEditPage'},
|
||||
|
||||
{path: '/ingredient-editor', component: () => import("@/pages/IngredientEditorPage.vue"), name: 'IngredientEditorPage'},
|
||||
{path: '/property-editor', component: () => import("@/pages/PropertyEditorPage.vue"), name: 'PropertyEditorPage'},
|
||||
|
||||
@@ -1,21 +1,99 @@
|
||||
<template>
|
||||
{{hasFoodProperties}}
|
||||
{{hasRecipeProperties}}
|
||||
<v-card class="mt-2">
|
||||
<v-card-title>
|
||||
<v-icon icon="$properties"></v-icon>
|
||||
{{ $t('Properties') }}
|
||||
|
||||
<v-btn-toggle border divided density="compact" class="float-right" v-if="hasRecipeProperties && hasRecipeProperties" v-model="sourceSelectedToShow">
|
||||
<v-btn size="small" value="food">{{ $t('Food') }}</v-btn>
|
||||
<v-btn size="small" value="recipe">{{ $t('Recipe') }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-table density="compact" style="max-width: 800px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{ $t('per_serving') }}</th>
|
||||
<th>{{ $t('total') }}</th>
|
||||
<th v-if="sourceSelectedToShow == 'food'"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="p in propertyList" :key="p.id">
|
||||
<td>{{p.name}}</td>
|
||||
<td>{{$n(p.propertyAmountPerServing)}} {{p.unit}}</td>
|
||||
<td>{{$n(p.propertyAmountTotal)}} {{p.unit}}</td>
|
||||
<td v-if="sourceSelectedToShow == 'food'">
|
||||
<v-btn @click="dialogProperty = p; dialog = true" variant="plain" color="warning" icon="fa-solid fa-triangle-exclamation" size="small" v-if="p.missingValue"></v-btn>
|
||||
<v-btn @click="dialogProperty = p; dialog = true" variant="plain" icon="fa-solid fa-circle-info" size="small" v-if="!p.missingValue"></v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-dialog max-width="900px" v-model="dialog">
|
||||
<v-card v-if="dialogProperty">
|
||||
<v-closable-card-title :title="`${dialogProperty.propertyAmountTotal} ${dialogProperty.unit} ${dialogProperty.name}`" :sub-title="$t('total')" icon="$properties" v-model="dialog"></v-closable-card-title>
|
||||
<v-card-text>
|
||||
<v-list>
|
||||
<v-list-item border v-for="fv in dialogProperty.foodValues">
|
||||
<template #prepend>
|
||||
<v-progress-circular size="55" width="5" :model-value="(fv.value/dialogProperty.propertyAmountTotal)*100" :color="colorScale((fv.value/dialogProperty.propertyAmountTotal)*100)" v-if="fv.value != null">{{Math.round((fv.value/dialogProperty.propertyAmountTotal)*100)}}%</v-progress-circular>
|
||||
<v-progress-circular size="55" width="5" v-if="fv.value == null">?</v-progress-circular>
|
||||
</template>
|
||||
<span class="ms-2">
|
||||
{{ fv.food }}
|
||||
</span>
|
||||
<template #append>
|
||||
<v-chip v-if="fv.value">{{$n(fv.value)}} {{dialogProperty.unit}}</v-chip>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn :to="{ name: 'PropertyEditorPage', query: {recipe: recipe.id} }">{{$t('Property_Editor')}}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {computed, PropType} from "vue";
|
||||
import {Recipe} from "@/openapi";
|
||||
import {computed, onMounted, PropType, ref} from "vue";
|
||||
import {PropertyType, Recipe} from "@/openapi";
|
||||
import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue";
|
||||
|
||||
type PropertyWrapper = {
|
||||
id: number,
|
||||
name: string,
|
||||
description?: string,
|
||||
foodValues: [],
|
||||
propertyAmountPerServing: number,
|
||||
propertyAmountTotal: number,
|
||||
missingValue: boolean,
|
||||
unit?: string,
|
||||
type: PropertyType,
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
recipe: {type: {} as PropType<Recipe>, required: true}
|
||||
recipe: {type: {} as PropType<Recipe>, required: true},
|
||||
servings: { type: Number, required: true, },
|
||||
})
|
||||
|
||||
/**
|
||||
* determines if the recipe has properties on the recipe directly
|
||||
*/
|
||||
const hasRecipeProperties = computed(() => {
|
||||
return props.recipe.properties != undefined && props.recipe.properties.length > 0
|
||||
})
|
||||
|
||||
/**
|
||||
* determines if the recipe has calculated properties based on its foods
|
||||
*/
|
||||
const hasFoodProperties = computed(() => {
|
||||
let propertiesFound = false
|
||||
for (const [key, fp] of Object.entries(props.recipe.foodProperties)) {
|
||||
@@ -26,6 +104,88 @@ const hasFoodProperties = computed(() => {
|
||||
return propertiesFound
|
||||
})
|
||||
|
||||
/**
|
||||
* compute list of properties based on recipe or food, depending on what is selected
|
||||
*/
|
||||
const propertyList = computed(() => {
|
||||
let ptList = [] as PropertyWrapper[]
|
||||
if (sourceSelectedToShow.value == 'recipe') {
|
||||
if (hasRecipeProperties.value) {
|
||||
props.recipe.properties.forEach(rp => {
|
||||
ptList.push(
|
||||
{
|
||||
id: rp.propertyType.id!,
|
||||
name: rp.propertyType.name,
|
||||
description: rp.propertyType.description,
|
||||
foodValues: [],
|
||||
propertyAmountPerServing: rp.propertyAmount,
|
||||
propertyAmountTotal: rp.propertyAmount * props.recipe.servings * (props.servings / props.recipe.servings),
|
||||
missingValue: false,
|
||||
unit: rp.propertyType.unit,
|
||||
type: rp.propertyType,
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for (const [key, fp] of Object.entries(props.recipe.foodProperties)) {
|
||||
ptList.push(
|
||||
{
|
||||
id: fp.id,
|
||||
name: fp.name,
|
||||
description: fp.description,
|
||||
icon: fp.icon,
|
||||
foodValues: fp.food_values,
|
||||
propertyAmountPerServing: fp.total_value / props.recipe.servings,
|
||||
propertyAmountTotal: fp.total_value * (props.servings / props.recipe.servings),
|
||||
missingValue: fp.missing_value,
|
||||
unit: fp.unit,
|
||||
type: fp,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function compare(a, b) {
|
||||
if (a.type.order > b.type.order) {
|
||||
return 1
|
||||
}
|
||||
if (a.type.order < b.type.order) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
return ptList.sort(compare)
|
||||
})
|
||||
|
||||
const sourceSelectedToShow = ref<'recipe' | 'food'>("food")
|
||||
const dialog = ref(false)
|
||||
const dialogProperty = ref<undefined|PropertyWrapper>(undefined)
|
||||
|
||||
onMounted(() => {
|
||||
if (!hasFoodProperties) {
|
||||
sourceSelectedToShow.value = "recipe"
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* return a color based on the given number
|
||||
* used to color the percentage of each food contributing to the total value of a property
|
||||
* @param percentage
|
||||
*/
|
||||
function colorScale(percentage: number){
|
||||
if(percentage > 80){
|
||||
return 'error'
|
||||
}
|
||||
if(percentage > 50){
|
||||
return 'warning'
|
||||
}
|
||||
if(percentage > 30){
|
||||
return 'info'
|
||||
}
|
||||
return 'success'
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -72,11 +72,13 @@
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<property-view :recipe="recipe" :servings="servings"></property-view>
|
||||
|
||||
<v-expansion-panels class="mt-2">
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-title><v-icon icon="$properties" class="me-2"></v-icon> {{ $t('Properties') }}</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<property-view :recipe="recipe"></property-view>
|
||||
<property-view :recipe="recipe" :ingredient-factor="ingredientFactor"></property-view>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
|
||||
@@ -144,7 +146,6 @@ import RecipeImage from "@/components/display/RecipeImage.vue";
|
||||
import ExternalRecipeViewer from "@/components/display/ExternalRecipeViewer.vue";
|
||||
import {useWakeLock} from "@vueuse/core";
|
||||
import StepView from "@/components/display/StepView.vue";
|
||||
import IngredientsTable from "@/components/display/IngredientsTable.vue";
|
||||
import {DateTime} from "luxon";
|
||||
import PropertyView from "@/components/display/PropertyView.vue";
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
</v-tabs-window-item>
|
||||
<v-tabs-window-item value="properties">
|
||||
<v-form :disabled="loading || fileApiLoading">
|
||||
<v-alert class="mb-2" icon="$help">{{ $t('PropertiesFoodHelp') }}</v-alert>
|
||||
<closable-help-alert :text="$t('PropertiesFoodHelp')"></closable-help-alert>
|
||||
<properties-editor v-model="editingObj.properties" :amount-for="$t('Serving')"></properties-editor>
|
||||
</v-form>
|
||||
</v-tabs-window-item>
|
||||
|
||||
@@ -331,7 +331,7 @@
|
||||
"Private_Recipe_Help": "Dieses Rezept ist nur für dich und Personen mit denen du es geteilt hast sichtbar.",
|
||||
"Profile": "Profil",
|
||||
"Properties": "Eigenschaften",
|
||||
"PropertiesFoodHelp": "Eigenschaften können für Rezepte und Lebensmittel erfasst werden. Eigenschaften von Lebensmitteln werden entsprechend der Menge für das Rezept ausgerechnet und überschreiben die Rezepteigenschaften. ",
|
||||
"PropertiesFoodHelp": "Eigenschaften können für Rezepte und Lebensmittel erfasst werden. Eigenschaften von Lebensmitteln werden automatisch entsprechend der im Rezept enthaltenen Menge berechnet. ",
|
||||
"Properties_Food_Amount": "Eigenschaften: Lebensmittelmenge",
|
||||
"Properties_Food_Unit": "Eigenschaft Einheit",
|
||||
"Property": "Eigenschaft",
|
||||
|
||||
@@ -329,7 +329,7 @@
|
||||
"Private_Recipe_Help": "Recipe is only shown to you and people its shared with.",
|
||||
"Profile": "Profile",
|
||||
"Properties": "Properties",
|
||||
"PropertiesFoodHelp": "Properties can be added to Recipes and Foods. Properties on Foods are calculated according based on their amount in the recipe and override the recipe properties.",
|
||||
"PropertiesFoodHelp": "Properties can be added to Recipes and Foods. Properties on Foods are automatically calculated based on their amount in the recipe.",
|
||||
"Properties_Food_Amount": "Properties Food Amount",
|
||||
"Properties_Food_Unit": "Properties Food Unit",
|
||||
"Property": "Property",
|
||||
|
||||
Reference in New Issue
Block a user