mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-06-03 13:44:55 -04:00
Feature: Toggle display of ingredient references in recipe instructions (#1268)
* Better cooking mode * Fix wrong event sent * feat/cookmode recipe page integration * implement scaling in cook mode + minor padding Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
parent
2809cef3b1
commit
c64da1fdb7
@ -24,7 +24,7 @@
|
|||||||
<RecipeFavoriteBadge v-if="loggedIn" class="mx-1" color="info" button-style :slug="slug" show-always />
|
<RecipeFavoriteBadge v-if="loggedIn" class="mx-1" color="info" button-style :slug="slug" show-always />
|
||||||
<v-tooltip v-if="!locked" bottom color="info">
|
<v-tooltip v-if="!locked" bottom color="info">
|
||||||
<template #activator="{ on, attrs }">
|
<template #activator="{ on, attrs }">
|
||||||
<v-btn fab small class="mx-1" color="info" v-bind="attrs" v-on="on" @click="$emit('input', true)">
|
<v-btn fab small class="mx-1" color="info" v-bind="attrs" v-on="on" @click="$emit('edit', true)">
|
||||||
<v-icon> {{ $globals.icons.edit }} </v-icon>
|
<v-icon> {{ $globals.icons.edit }} </v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
@ -58,13 +58,13 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
<div class="d-flex justify-space-between justify-start">
|
<div v-if="showCookMode" class="d-flex justify-space-between justify-start">
|
||||||
<h2 class="mb-4 mt-1">{{ $t("recipe.instructions") }}</h2>
|
<h2 class="mb-4 mt-1">{{ $t("recipe.instructions") }}</h2>
|
||||||
<BaseButton v-if="!public" minor :to="$router.currentRoute.path + '/cook'" cancel color="primary">
|
<BaseButton v-if="!public && !edit" minor cancel color="primary" @click="toggleCookMode()">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
{{ $globals.icons.primary }}
|
{{ $globals.icons.primary }}
|
||||||
</template>
|
</template>
|
||||||
Cook
|
Cook Mode
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
<draggable
|
<draggable
|
||||||
@ -198,6 +198,14 @@
|
|||||||
<div v-show="!isChecked(index) && !edit" class="m-0 p-0">
|
<div v-show="!isChecked(index) && !edit" class="m-0 p-0">
|
||||||
<v-card-text class="markdown">
|
<v-card-text class="markdown">
|
||||||
<VueMarkdown class="markdown" :source="step.text"> </VueMarkdown>
|
<VueMarkdown class="markdown" :source="step.text"> </VueMarkdown>
|
||||||
|
<div v-if="cookMode && step.ingredientReferences && step.ingredientReferences.length > 0">
|
||||||
|
<v-divider class="mb-2"></v-divider>
|
||||||
|
<div
|
||||||
|
v-for="ing in step.ingredientReferences"
|
||||||
|
:key="ing.referenceId"
|
||||||
|
v-html="getIngredientByRefId(ing.referenceId)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</div>
|
</div>
|
||||||
</v-expand-transition>
|
</v-expand-transition>
|
||||||
@ -213,7 +221,16 @@
|
|||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
// @ts-ignore vue-markdown has no types
|
// @ts-ignore vue-markdown has no types
|
||||||
import VueMarkdown from "@adapttive/vue-markdown";
|
import VueMarkdown from "@adapttive/vue-markdown";
|
||||||
import { ref, toRefs, reactive, defineComponent, watch, onMounted, useContext } from "@nuxtjs/composition-api";
|
import {
|
||||||
|
ref,
|
||||||
|
toRefs,
|
||||||
|
reactive,
|
||||||
|
defineComponent,
|
||||||
|
watch,
|
||||||
|
onMounted,
|
||||||
|
useContext,
|
||||||
|
computed,
|
||||||
|
} from "@nuxtjs/composition-api";
|
||||||
import { RecipeStep, IngredientReferences, RecipeIngredient, RecipeAsset } from "~/types/api-types/recipe";
|
import { RecipeStep, IngredientReferences, RecipeIngredient, RecipeAsset } from "~/types/api-types/recipe";
|
||||||
import { parseIngredientText } from "~/composables/recipes";
|
import { parseIngredientText } from "~/composables/recipes";
|
||||||
import { uuid4, detectServerBaseUrl } from "~/composables/use-utils";
|
import { uuid4, detectServerBaseUrl } from "~/composables/use-utils";
|
||||||
@ -264,6 +281,14 @@ export default defineComponent({
|
|||||||
type: Array as () => RecipeAsset[],
|
type: Array as () => RecipeAsset[],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
cookMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
@ -313,13 +338,20 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showCookMode = ref(false);
|
||||||
|
|
||||||
// Eliminate state with an eager call to watcher?
|
// Eliminate state with an eager call to watcher?
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
props.value.forEach((element) => {
|
props.value.forEach((element: RecipeStep) => {
|
||||||
if (element.id !== undefined) {
|
if (element.id !== undefined) {
|
||||||
showTitleEditor.value[element.id] = validateTitle(element.title);
|
showTitleEditor.value[element.id] = validateTitle(element.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// showCookMode.value = false;
|
||||||
|
if (showCookMode.value === false && element.ingredientReferences && element.ingredientReferences.length > 0) {
|
||||||
|
showCookMode.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
showTitleEditor.value = { ...showTitleEditor.value };
|
showTitleEditor.value = { ...showTitleEditor.value };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -376,6 +408,14 @@ export default defineComponent({
|
|||||||
referenceId: ref,
|
referenceId: ref,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update the visibility of the cook mode button
|
||||||
|
showCookMode.value = false;
|
||||||
|
props.value.forEach((element) => {
|
||||||
|
if (showCookMode.value === false && element.ingredientReferences && element.ingredientReferences.length > 0) {
|
||||||
|
showCookMode.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
state.dialog = false;
|
state.dialog = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,12 +486,27 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIngredientByRefId(refId: string) {
|
const ingredientLookup = computed(() => {
|
||||||
const ing = props.ingredients.find((ing) => ing.referenceId === refId) || "";
|
const results: { [key: string]: RecipeIngredient } = {};
|
||||||
|
return props.ingredients.reduce((prev, ing) => {
|
||||||
|
if (ing.referenceId === undefined) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
prev[ing.referenceId] = ing;
|
||||||
|
return prev;
|
||||||
|
}, results);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getIngredientByRefId(refId: string | undefined) {
|
||||||
|
if (refId === undefined) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const ing = ingredientLookup.value[refId] ?? "";
|
||||||
if (ing === "") {
|
if (ing === "") {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return parseIngredientText(ing, props.disableAmount);
|
return parseIngredientText(ing, props.disableAmount, props.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===============================================================
|
// ===============================================================
|
||||||
@ -571,6 +626,10 @@ export default defineComponent({
|
|||||||
props.value[index].text += text;
|
props.value[index].text += text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleCookMode() {
|
||||||
|
context.emit("cookModeToggle");
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Image Uploader
|
// Image Uploader
|
||||||
toggleDragMode,
|
toggleDragMode,
|
||||||
@ -598,6 +657,8 @@ export default defineComponent({
|
|||||||
updateIndex,
|
updateIndex,
|
||||||
autoSetReferences,
|
autoSetReferences,
|
||||||
parseIngredientText,
|
parseIngredientText,
|
||||||
|
toggleCookMode,
|
||||||
|
showCookMode,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,120 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container
|
|
||||||
v-if="recipe"
|
|
||||||
:class="{
|
|
||||||
'pa-0': $vuetify.breakpoint.smAndDown,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<v-card-title>
|
|
||||||
<h1 class="headline">{{ recipe.name }}</h1>
|
|
||||||
</v-card-title>
|
|
||||||
|
|
||||||
<v-stepper v-model="activeStep" flat>
|
|
||||||
<v-toolbar class="ma-1 elevation-2 rounded">
|
|
||||||
<v-toolbar-title class="headline">
|
|
||||||
Step {{ activeStep }} of {{ recipe.recipeInstructions.length }}</v-toolbar-title
|
|
||||||
>
|
|
||||||
</v-toolbar>
|
|
||||||
<div class="d-flex mt-3 px-2">
|
|
||||||
<BaseButton color="primary" @click="$router.go(-1)">
|
|
||||||
<template #icon> {{ $globals.icons.arrowLeftBold }}</template>
|
|
||||||
To Recipe
|
|
||||||
</BaseButton>
|
|
||||||
<v-btn rounded icon color="primary" class="ml-auto" small @click="scale > 1 ? scale-- : null">
|
|
||||||
<v-icon>
|
|
||||||
{{ $globals.icons.minus }}
|
|
||||||
</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn rounded color="primary" small> Scale: {{ scale }} </v-btn>
|
|
||||||
<v-btn rounded icon color="primary" small @click="scale++">
|
|
||||||
<v-icon>
|
|
||||||
{{ $globals.icons.createAlt }}
|
|
||||||
</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
<v-stepper-items>
|
|
||||||
<template v-for="(step, index) in recipe.recipeInstructions">
|
|
||||||
<v-stepper-content :key="index + 1 + '-content'" :step="index + 1" class="pa-0 mt-2 elevation-0">
|
|
||||||
<v-card class="ma-2">
|
|
||||||
<v-card-text>
|
|
||||||
<h2 class="mb-4">{{ $t("recipe.instructions") }}</h2>
|
|
||||||
<VueMarkdown :source="step.text"> </VueMarkdown>
|
|
||||||
<template v-if="step.ingredientReferences.length > 0">
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<div>
|
|
||||||
<h2 class="mb-4 mt-4">{{ $t("recipe.ingredients") }}</h2>
|
|
||||||
<div
|
|
||||||
v-for="ing in step.ingredientReferences"
|
|
||||||
:key="ing.referenceId"
|
|
||||||
v-html="getIngredientByRefId(ing.referenceId)"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
<v-card-actions class="justify-center">
|
|
||||||
<BaseButton color="primary" :disabled="index == 0" @click="activeStep = activeStep - 1">
|
|
||||||
<template #icon> {{ $globals.icons.arrowLeftBold }}</template>
|
|
||||||
Back
|
|
||||||
</BaseButton>
|
|
||||||
|
|
||||||
<BaseButton
|
|
||||||
icon-right
|
|
||||||
:disabled="index + 1 == recipe.recipeInstructions.length"
|
|
||||||
color="primary"
|
|
||||||
@click="activeStep = activeStep + 1"
|
|
||||||
>
|
|
||||||
<template #icon> {{ $globals.icons.arrowRightBold }}</template>
|
|
||||||
Next
|
|
||||||
</BaseButton>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-stepper-content>
|
|
||||||
</template>
|
|
||||||
</v-stepper-items>
|
|
||||||
</v-stepper>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, useRoute, ref } from "@nuxtjs/composition-api";
|
|
||||||
// @ts-ignore vue-markdown has no types
|
|
||||||
import VueMarkdown from "@adapttive/vue-markdown";
|
|
||||||
import { useStaticRoutes } from "~/composables/api";
|
|
||||||
import { parseIngredientText, useRecipe } from "~/composables/recipes";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { VueMarkdown },
|
|
||||||
setup() {
|
|
||||||
const route = useRoute();
|
|
||||||
const slug = route.value.params.slug;
|
|
||||||
const activeStep = ref(1);
|
|
||||||
const scale = ref(1);
|
|
||||||
|
|
||||||
const { recipe } = useRecipe(slug);
|
|
||||||
|
|
||||||
const { recipeImage } = useStaticRoutes();
|
|
||||||
|
|
||||||
function getIngredientByRefId(refId: string) {
|
|
||||||
if (!recipe.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ing = recipe?.value.recipeIngredient?.find((ing) => ing.referenceId === refId) || "";
|
|
||||||
if (ing === "") {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseIngredientText(ing, recipe?.value?.settings?.disableAmount || false, scale.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
scale,
|
|
||||||
getIngredientByRefId,
|
|
||||||
activeStep,
|
|
||||||
slug,
|
|
||||||
recipe,
|
|
||||||
recipeImage,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -53,10 +53,7 @@
|
|||||||
class="ml-auto mt-n8 pb-4"
|
class="ml-auto mt-n8 pb-4"
|
||||||
@close="closeEditor"
|
@close="closeEditor"
|
||||||
@json="toggleJson"
|
@json="toggleJson"
|
||||||
@edit="
|
@edit="toggleEdit"
|
||||||
jsonEditor = false;
|
|
||||||
form = true;
|
|
||||||
"
|
|
||||||
@save="updateRecipe(recipe.slug, recipe)"
|
@save="updateRecipe(recipe.slug, recipe)"
|
||||||
@delete="deleteRecipe(recipe.slug)"
|
@delete="deleteRecipe(recipe.slug)"
|
||||||
@print="printRecipe"
|
@print="printRecipe"
|
||||||
@ -202,7 +199,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="12" md="4" lg="4">
|
<v-col v-if="!cookModeToggle || form" cols="12" sm="12" md="4" lg="4">
|
||||||
<RecipeIngredients
|
<RecipeIngredients
|
||||||
v-if="!form"
|
v-if="!form"
|
||||||
:value="recipe.recipeIngredient"
|
:value="recipe.recipeIngredient"
|
||||||
@ -291,17 +288,24 @@
|
|||||||
</client-only>
|
</client-only>
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-divider v-if="$vuetify.breakpoint.mdAndUp" class="my-divider" :vertical="true"></v-divider>
|
<v-divider
|
||||||
|
v-if="$vuetify.breakpoint.mdAndUp && !cookModeToggle"
|
||||||
|
class="my-divider"
|
||||||
|
:vertical="true"
|
||||||
|
></v-divider>
|
||||||
|
|
||||||
<v-col cols="12" sm="12" md="8" lg="8">
|
<v-col cols="12" sm="12" :md="8 + (cookModeToggle ? 1 : 0) * 4" :lg="8 + (cookModeToggle ? 1 : 0) * 4">
|
||||||
<RecipeInstructions
|
<RecipeInstructions
|
||||||
v-model="recipe.recipeInstructions"
|
v-model="recipe.recipeInstructions"
|
||||||
|
:assets.sync="recipe.assets"
|
||||||
:ingredients="recipe.recipeIngredient"
|
:ingredients="recipe.recipeIngredient"
|
||||||
:disable-amount="recipe.settings.disableAmount"
|
:disable-amount="recipe.settings.disableAmount"
|
||||||
:edit="form"
|
:edit="form"
|
||||||
:recipe-id="recipe.id"
|
:recipe-id="recipe.id"
|
||||||
:recipe-slug="recipe.slug"
|
:recipe-slug="recipe.slug"
|
||||||
:assets.sync="recipe.assets"
|
:cook-mode="cookModeToggle"
|
||||||
|
:scale="scale"
|
||||||
|
@cookModeToggle="cookModeToggle = !cookModeToggle"
|
||||||
/>
|
/>
|
||||||
<div v-if="form" class="d-flex">
|
<div v-if="form" class="d-flex">
|
||||||
<RecipeDialogBulkAdd class="ml-auto my-2 mr-1" @bulk-data="addStep" />
|
<RecipeDialogBulkAdd class="ml-auto my-2 mr-1" @bulk-data="addStep" />
|
||||||
@ -357,14 +361,14 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<RecipeNutrition
|
<RecipeNutrition
|
||||||
v-if="recipe.settings.showNutrition"
|
v-if="recipe.settings.showNutrition && !cookModeToggle"
|
||||||
v-model="recipe.nutrition"
|
v-model="recipe.nutrition"
|
||||||
class="mt-10"
|
class="mt-10"
|
||||||
:edit="form"
|
:edit="form"
|
||||||
/>
|
/>
|
||||||
<client-only>
|
<client-only>
|
||||||
<RecipeAssets
|
<RecipeAssets
|
||||||
v-if="recipe.settings.showAssets"
|
v-if="recipe.settings.showAssets && !cookModeToggle"
|
||||||
v-model="recipe.assets"
|
v-model="recipe.assets"
|
||||||
:edit="form"
|
:edit="form"
|
||||||
:slug="recipe.slug"
|
:slug="recipe.slug"
|
||||||
@ -373,7 +377,7 @@
|
|||||||
</client-only>
|
</client-only>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RecipeNotes v-model="recipe.notes" :edit="form" />
|
<RecipeNotes v-if="!cookModeToggle" v-model="recipe.notes" :edit="form" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
@ -385,7 +389,7 @@
|
|||||||
:label="$t('recipe.original-url')"
|
:label="$t('recipe.original-url')"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
<v-btn
|
<v-btn
|
||||||
v-else-if="recipe.orgURL"
|
v-else-if="recipe.orgURL && !cookModeToggle"
|
||||||
dense
|
dense
|
||||||
small
|
small
|
||||||
:hover="false"
|
:hover="false"
|
||||||
@ -438,7 +442,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RecipeComments
|
<RecipeComments
|
||||||
v-if="recipe && !recipe.settings.disableComments && !form"
|
v-if="recipe && !recipe.settings.disableComments && !form && !cookModeToggle"
|
||||||
v-model="recipe.comments"
|
v-model="recipe.comments"
|
||||||
:slug="recipe.slug"
|
:slug="recipe.slug"
|
||||||
:recipe-id="recipe.id"
|
:recipe-id="recipe.id"
|
||||||
@ -603,6 +607,7 @@ export default defineComponent({
|
|||||||
search: false,
|
search: false,
|
||||||
mainMenuBar: false,
|
mainMenuBar: false,
|
||||||
},
|
},
|
||||||
|
cookModeToggle: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { recipe, loading, fetchRecipe } = useRecipe(slug);
|
const { recipe, loading, fetchRecipe } = useRecipe(slug);
|
||||||
@ -642,6 +647,12 @@ export default defineComponent({
|
|||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Button Click Event Handlers
|
// Button Click Event Handlers
|
||||||
|
|
||||||
|
function toggleEdit() {
|
||||||
|
state.jsonEditor = false;
|
||||||
|
state.cookModeToggle = false;
|
||||||
|
state.form = true;
|
||||||
|
}
|
||||||
|
|
||||||
async function updateRecipe(slug: string, recipe: Recipe) {
|
async function updateRecipe(slug: string, recipe: Recipe) {
|
||||||
const { data } = await api.recipes.updateOne(slug, recipe);
|
const { data } = await api.recipes.updateOne(slug, recipe);
|
||||||
state.form = false;
|
state.form = false;
|
||||||
@ -862,6 +873,7 @@ export default defineComponent({
|
|||||||
deleteRecipe,
|
deleteRecipe,
|
||||||
printRecipe,
|
printRecipe,
|
||||||
closeEditor,
|
closeEditor,
|
||||||
|
toggleEdit,
|
||||||
updateRecipe,
|
updateRecipe,
|
||||||
uploadImage,
|
uploadImage,
|
||||||
validators,
|
validators,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user