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:
Miroito 2022-07-10 06:28:34 +02:00 committed by GitHub
parent 2809cef3b1
commit c64da1fdb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 95 additions and 142 deletions

View File

@ -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>

View File

@ -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,
}; };
}, },
}); });

View File

@ -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>

View File

@ -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,