Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added a dedicated cookmode dialog that allows for individual scrolling #4464

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9beba50
Added a dedicated cookmode dialog that allows for individual scrolling
codetakki Oct 29, 2024
9e784bb
Merge branch 'mealie-next' into feature-cookmode-dialog
codetakki Oct 29, 2024
cb65898
Added scale to cook mode dialog
codetakki Oct 29, 2024
043d8b4
Merge branch 'feature-cookmode-dialog' of https://github.com/codetakk…
codetakki Oct 29, 2024
556e53b
Removed unused time import
codetakki Oct 29, 2024
a847312
Adjusted layout to work better in portrait mobile mode
codetakki Oct 29, 2024
f214f24
Moved Ingredients to right in landscape mode
codetakki Oct 29, 2024
17bad66
Changes to layout based on feedback
codetakki Oct 29, 2024
f1704aa
Merge branch 'mealie-next' into feature-cookmode-dialog
codetakki Oct 30, 2024
f3233de
Design now matches non cookmode, and close button is always displayed…
codetakki Oct 31, 2024
db0f0e8
Fixed drop shadow and divider
codetakki Oct 31, 2024
aed8699
Merge branch 'mealie-next' into feature-cookmode-dialog
codetakki Oct 31, 2024
899b0a2
Made ingredients appear in sections connected to there steps
codetakki Oct 31, 2024
95f53bf
Merge branch 'feature-grouped-ingredients' into feature-cookmode-dialog
codetakki Oct 31, 2024
35bcdcc
Only sort ingredients per step if in cookmode
codetakki Oct 31, 2024
8c08241
Merge branch 'mealie-next' into feature-cookmode-dialog
codetakki Oct 31, 2024
2f8d87d
Merge branch 'mealie-next' into feature-cookmode-dialog
codetakki Nov 4, 2024
53b0b77
Merge branch 'mealie-next' into feature-cookmode-dialog
codetakki Nov 5, 2024
a2c9bab
Made different cook mode layouts based on linked ingredients
codetakki Nov 6, 2024
53e4628
Merge branch 'mealie-next' into feature-cookmode-dialog
codetakki Nov 6, 2024
3001380
Removed comment, changed margins
codetakki Nov 9, 2024
706424d
Changed margins to not cut drop shadow
codetakki Nov 9, 2024
0776c56
Added card with not link ingredients
codetakki Nov 9, 2024
d10c74d
Small changes after feedback
codetakki Nov 11, 2024
8dd8468
Merge branch 'mealie-next' into feature-cookmode-dialog
codetakki Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions frontend/components/Domain/Recipe/RecipeIngredients.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
<template>
<div v-if="value && value.length > 0">
<div class="d-flex justify-start">
<div v-if="!isCookMode" class="d-flex justify-start" >
<h2 class="mb-2 mt-1">{{ $t("recipe.ingredients") }}</h2>
<AppButtonCopy btn-class="ml-auto" :copy-text="ingredientCopyText" />
</div>
<div>
<div v-for="(ingredient, index) in value" :key="'ingredient' + index">
<h3 v-if="showTitleEditor[index]" class="mt-2">{{ ingredient.title }}</h3>
<v-divider v-if="showTitleEditor[index]"></v-divider>
<v-list-item dense @click="toggleChecked(index)">
<template v-if="!isCookMode">
<h3 v-if="showTitleEditor[index]" class="mt-2">{{ ingredient.title }}</h3>
<v-divider v-if="showTitleEditor[index]"></v-divider>
</template>
<v-list-item dense @click.stop="toggleChecked(index)">
Kuchenpirat marked this conversation as resolved.
Show resolved Hide resolved
<v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary" />
<v-list-item-content :key="ingredient.quantity">
<RecipeIngredientListItem :ingredient="ingredient" :disable-amount="disableAmount" :scale="scale" />
Expand Down Expand Up @@ -40,6 +42,10 @@ export default defineComponent({
type: Number,
default: 1,
},
isCookMode: {
type: Boolean,
default: false,
}
},
setup(props) {
function validateTitle(title?: string) {
Expand Down
210 changes: 142 additions & 68 deletions frontend/components/Domain/Recipe/RecipePage/RecipePage.vue
Original file line number Diff line number Diff line change
@@ -1,75 +1,135 @@
<template>
<v-container :class="{ 'pa-0': $vuetify.breakpoint.smAndDown }">
<v-card :flat="$vuetify.breakpoint.smAndDown" class="d-print-none">
<RecipePageHeader
:recipe="recipe"
:recipe-scale="scale"
:landscape="landscape"
@save="saveRecipe"
@delete="deleteRecipe"
/>
<LazyRecipeJsonEditor v-if="isEditJSON" v-model="recipe" class="mt-10" :options="EDITOR_OPTIONS" />
<v-card-text v-else>
<!--
This is where most of the main content is rendered. Some components include state for both Edit and View modes
which is why some have explicit v-if statements and others use the composition API to determine and manage
the shared state internally.

The global recipe object is shared down the tree of components and _is_ mutated by child components. This is
some-what of a hack of the system and goes against the principles of Vue, but it _does_ seem to work and streamline
a significant amount of prop management. When we move to Vue 3 and have access to some of the newer API's the plan to update this
data management and mutation system we're using.
-->
<RecipePageEditorToolbar v-if="isEditForm" :recipe="recipe" />
<RecipePageTitleContent :recipe="recipe" :landscape="landscape" />
<RecipePageIngredientEditor v-if="isEditForm" :recipe="recipe" />
<RecipePageScale :recipe="recipe" :scale.sync="scale" :landscape="landscape" />

<!--
This section contains the 2 column layout for the recipe steps and other content.
-->
<v-row>
<div>
<v-container v-show="!isCookMode" key="recipe-page" :class="{ 'pa-0': $vuetify.breakpoint.smAndDown }">
<v-card :flat="$vuetify.breakpoint.smAndDown" class="d-print-none">
<RecipePageHeader
:recipe="recipe"
:recipe-scale="scale"
:landscape="landscape"
@save="saveRecipe"
@delete="deleteRecipe"
/>
<LazyRecipeJsonEditor v-if="isEditJSON" v-model="recipe" class="mt-10" :options="EDITOR_OPTIONS" />
<v-card-text v-else>
<!--
The left column is conditionally rendered based on cook mode.
-->
<v-col v-if="!isCookMode || isEditForm" cols="12" sm="12" md="4" lg="4">
<RecipePageIngredientToolsView v-if="!isEditForm" :recipe="recipe" :scale="scale" />
<RecipePageOrganizers v-if="$vuetify.breakpoint.mdAndUp" :recipe="recipe" />
</v-col>
<v-divider v-if="$vuetify.breakpoint.mdAndUp && !isCookMode" class="my-divider" :vertical="true" />
This is where most of the main content is rendered. Some components include state for both Edit and View modes
which is why some have explicit v-if statements and others use the composition API to determine and manage
the shared state internally.

The global recipe object is shared down the tree of components and _is_ mutated by child components. This is
some-what of a hack of the system and goes against the principles of Vue, but it _does_ seem to work and streamline
a significant amount of prop management. When we move to Vue 3 and have access to some of the newer API's the plan to update this
data management and mutation system we're using.
-->
<RecipePageEditorToolbar v-if="isEditForm" :recipe="recipe" />
<RecipePageTitleContent :recipe="recipe" :landscape="landscape" />
<RecipePageIngredientEditor v-if="isEditForm" :recipe="recipe" />
<RecipePageScale :recipe="recipe" :scale.sync="scale" :landscape="landscape" />

<!--
the right column is always rendered, but it's layout width is determined by where the left column is
rendered.
-->
<v-col cols="12" sm="12" :md="8 + (isCookMode ? 1 : 0) * 4" :lg="8 + (isCookMode ? 1 : 0) * 4">
<RecipePageInstructions
v-model="recipe.recipeInstructions"
:assets.sync="recipe.assets"
:recipe="recipe"
This section contains the 2 column layout for the recipe steps and other content.
-->
<v-row>
<!--
The left column is conditionally rendered based on cook mode.
-->
<v-col v-if="!isCookMode || isEditForm" cols="12" sm="12" md="4" lg="4">
<RecipePageIngredientToolsView v-if="!isEditForm" :recipe="recipe" :scale="scale" />
<RecipePageOrganizers v-if="$vuetify.breakpoint.mdAndUp" :recipe="recipe" />
</v-col>
<v-divider v-if="$vuetify.breakpoint.mdAndUp && !isCookMode" class="my-divider" :vertical="true" />

<!--
the right column is always rendered, but it's layout width is determined by where the left column is
rendered.
-->
<v-col cols="12" sm="12" :md="8 + (isCookMode ? 1 : 0) * 4" :lg="8 + (isCookMode ? 1 : 0) * 4">
<RecipePageInstructions
v-model="recipe.recipeInstructions"
:assets.sync="recipe.assets"
:recipe="recipe"
:scale="scale"
/>
<div v-if="isEditForm" class="d-flex">
<RecipeDialogBulkAdd class="ml-auto my-2 mr-1" @bulk-data="addStep" />
<BaseButton class="my-2" @click="addStep()"> {{ $t("general.add") }}</BaseButton>
</div>
<div v-if="!$vuetify.breakpoint.mdAndUp">
<RecipePageOrganizers :recipe="recipe" />
</div>
<RecipeNotes v-model="recipe.notes" :edit="isEditForm" />
</v-col>
</v-row>
<RecipePageFooter :recipe="recipe" />
</v-card-text>
</v-card>
<WakelockSwitch/>
<RecipePageComments
v-if="!recipe.settings.disableComments && !isEditForm && !isCookMode"
:recipe="recipe"
class="px-1 my-4 d-print-none"
/>
<RecipePrintContainer :recipe="recipe" :scale="scale" />
</v-container>
<!-- Cook mode displayes two columns with ingredients and instructions side by side, each being scrolled individually, allowing to view both at the same timer -->
<v-sheet v-show="isCookMode && !hasLinkedIngredients" key="cookmode" :style="{height: $vuetify.breakpoint.smAndUp ? 'calc(100vh - 48px)' : ''}"> <!-- the calc is to account for the toolbar a more dynamic solution could be needed -->
<v-row style="height: 100%;" no-gutters class="overflow-hidden">
<v-col cols="12" sm="5" class="overflow-y-auto pl-4 pr-3 py-2" style="height: 100%;">
<div class="d-flex align-center">
<RecipePageScale :recipe="recipe" :scale.sync="scale" :landscape="landscape" />
</div>
<RecipePageIngredientToolsView v-if="!isEditForm" :recipe="recipe" :scale="scale" :is-cook-mode="isCookMode" />
<v-divider></v-divider>
</v-col>
<v-col class="overflow-y-auto py-2" style="height: 100%;" cols="12" sm="7">
<RecipePageInstructions
v-model="recipe.recipeInstructions"
class="overflow-y-hidden px-4"
:assets.sync="recipe.assets"
:recipe="recipe"
:scale="scale"
/>
</v-col>
</v-row>

</v-sheet>
<v-sheet v-show="isCookMode && hasLinkedIngredients">
<div class="mt-2 px-2 px-md-4">
<RecipePageScale :recipe="recipe" :scale.sync="scale" :landscape="landscape"/>
</div>
<RecipePageInstructions
v-model="recipe.recipeInstructions"
class="overflow-y-hidden mt-n5 px-2 px-md-4"
:assets.sync="recipe.assets"
:recipe="recipe"
:scale="scale"
/>
<v-divider></v-divider>
<div class="px-2 px-md-4 pb-4 ">
<v-card flat>
<v-card-title>{{ $t('recipe.not-linked-ingredients') }}</v-card-title>
<RecipeIngredients
:value="notLinkedIngredients"
:scale="scale"
/>
<div v-if="isEditForm" class="d-flex">
<RecipeDialogBulkAdd class="ml-auto my-2 mr-1" @bulk-data="addStep" />
<BaseButton class="my-2" @click="addStep()"> {{ $t("general.add") }}</BaseButton>
</div>
<div v-if="!$vuetify.breakpoint.mdAndUp">
<RecipePageOrganizers :recipe="recipe" />
</div>
<RecipeNotes v-model="recipe.notes" :edit="isEditForm" />
</v-col>
</v-row>
<RecipePageFooter :recipe="recipe" />
</v-card-text>
</v-card>
<WakelockSwitch/>
<RecipePageComments
v-if="!recipe.settings.disableComments && !isEditForm && !isCookMode"
:recipe="recipe"
class="px-1 my-4 d-print-none"
/>
<RecipePrintContainer :recipe="recipe" :scale="scale" />
</v-container>
:disable-amount="recipe.settings.disableAmount"
:is-cook-mode="isCookMode">

</RecipeIngredients>
</v-card>
</div>
</v-sheet>
<v-btn
v-if="isCookMode"
fab
small
color="primary"
style="position: fixed; right: 12px; top: 60px;"
@click="toggleCookMode()"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</div>

</template>

<script lang="ts">
Expand All @@ -84,6 +144,7 @@ import {
useRoute,
} from "@nuxtjs/composition-api";
import { invoke, until } from "@vueuse/core";
import RecipeIngredients from "../RecipeIngredients.vue";
import RecipePageEditorToolbar from "./RecipePageParts/RecipePageEditorToolbar.vue";
import RecipePageFooter from "./RecipePageParts/RecipePageFooter.vue";
import RecipePageHeader from "./RecipePageParts/RecipePageHeader.vue";
Expand Down Expand Up @@ -133,6 +194,7 @@ export default defineComponent({
RecipeNotes,
RecipePageInstructions,
RecipePageFooter,
RecipeIngredients
},
props: {
recipe: {
Expand All @@ -151,6 +213,13 @@ export default defineComponent({
const { pageMode, editMode, setMode, isEditForm, isEditJSON, isCookMode, isEditMode, toggleCookMode } =
usePageState(props.recipe.slug);
const { deactivateNavigationWarning } = useNavigationWarning();
const notLinkedIngredients = computed(() => {
console.log("inst",props.recipe.recipeInstruction);
return props.recipe.recipeIngredient.filter((ingredient) => {
return !props.recipe.recipeInstructions.some((step) => step.ingredientReferences?.map((ref) => ref.referenceId).includes(ingredient.referenceId));
})
})
console.log(notLinkedIngredients);

/** =============================================================
* Recipe Snapshot on Mount
Expand All @@ -176,11 +245,14 @@ export default defineComponent({
}
}
deactivateNavigationWarning();
toggleCookMode()

clearPageState(props.recipe.slug || "");
console.debug("reset RecipePage state during unmount");
});

const hasLinkedIngredients = computed(() => {
return props.recipe.recipeInstructions.some((step) => step.ingredientReferences && step.ingredientReferences.length > 0);
})
/** =============================================================
* Set State onMounted
*/
Expand Down Expand Up @@ -278,6 +350,8 @@ export default defineComponent({
saveRecipe,
deleteRecipe,
addStep,
hasLinkedIngredients,
notLinkedIngredients
};
},
head: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
:value="recipe.recipeIngredient"
:scale="scale"
:disable-amount="recipe.settings.disableAmount"
:is-cook-mode="isCookMode"
/>
<div v-if="!isEditMode && recipe.tools && recipe.tools.length > 0">
<h2 class="mb-2 mt-4">{{ $t('tool.required-tools') }}</h2>
Expand Down Expand Up @@ -46,6 +47,10 @@ export default defineComponent({
type: Number,
required: true,
},
isCookMode: {
type: Boolean,
default: false,
}
},
setup(props) {
const { isOwnGroup } = useLoggedInState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@
</v-dialog>

<div class="d-flex justify-space-between justify-start">
<h2 class="mb-4 mt-1">{{ $t("recipe.instructions") }}</h2>
<BaseButton v-if="!isEditForm && showCookMode" minor cancel color="primary" @click="toggleCookMode()">
<h2 v-if="!isCookMode" class="mb-4 mt-1">{{ $t("recipe.instructions") }}</h2>
<BaseButton v-if="!isEditForm && !isCookMode" minor cancel color="primary" @click="toggleCookMode()">
<template #icon>
{{ $globals.icons.primary }}
</template>
Expand Down Expand Up @@ -243,16 +243,31 @@
</DropZone>
<v-expand-transition>
<div v-show="!isChecked(index) && !isEditForm" class="m-0 p-0">

<v-card-text class="markdown">
<SafeMarkdown class="markdown" :source="step.text" />
<div v-if="isCookMode && step.ingredientReferences && step.ingredientReferences.length > 0">
<v-divider class="mb-2"></v-divider>
<RecipeIngredientHtml
v-for="ing in step.ingredientReferences"
:key="ing.referenceId"
:markup="getIngredientByRefId(ing.referenceId)"
/>
</div>
<v-row>
<v-col
v-if="isCookMode && step.ingredientReferences && step.ingredientReferences.length > 0"
cols="12"
sm="5"
>
<div class="ml-n4">
<RecipeIngredients
:value="recipe.recipeIngredient.filter((ing) => {
if(!step.ingredientReferences) return false
return step.ingredientReferences.map((ref) => ref.referenceId).includes(ing.referenceId || '')
})"
:scale="scale"
:disable-amount="recipe.settings.disableAmount"
:is-cook-mode="isCookMode"
/>
</div>
</v-col>
<v-divider v-if="isCookMode && step.ingredientReferences && step.ingredientReferences.length > 0 && $vuetify.breakpoint.smAndUp" vertical ></v-divider>
<v-col>
<SafeMarkdown class="markdown" :source="step.text" />
</v-col>
</v-row>
</v-card-text>
</div>
</v-expand-transition>
Expand All @@ -261,7 +276,7 @@
</div>
</TransitionGroup>
</draggable>
<v-divider class="mt-10 d-flex d-md-none"/>
<v-divider v-if="!isCookMode" class="mt-10 d-flex d-md-none"/>
</section>
</template>

Expand All @@ -287,7 +302,7 @@
import { useExtractIngredientReferences } from "~/composables/recipe-page/use-extract-ingredient-references";
import { NoUndefinedField } from "~/lib/api/types/non-generated";
import DropZone from "~/components/global/DropZone.vue";

import RecipeIngredients from "~/components/Domain/Recipe/RecipeIngredients.vue";
interface MergerHistory {
target: number;
source: number;
Expand All @@ -300,6 +315,7 @@
draggable,
RecipeIngredientHtml,
DropZone,
RecipeIngredients
},
props: {
value: {
Expand All @@ -322,7 +338,7 @@
},

setup(props, context) {
const { i18n, req } = useContext();
const { i18n, req, $vuetify } = useContext();

Check warning on line 341 in frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageInstructions.vue

View workflow job for this annotation

GitHub Actions / Frontend and End-to-End Tests / lint

'$vuetify' is assigned a value but never used
const BASE_URL = detectServerBaseUrl(req);

const { isCookMode, toggleCookMode, isEditForm } = usePageState(props.recipe.slug);
Expand Down
Loading
Loading