Feature/ingredient sections (#624)

* add ingredient sections to UI

* update changelog

* move recipe favorite to action bar

* fix button position on meal-planner

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-07-09 14:33:23 -08:00 committed by GitHub
parent 9b5cf36981
commit 458ba2964f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 34 deletions

View file

@ -13,14 +13,16 @@
### General
- Recipe Instructions now collapse when checked
- Default recipe settings can be set through ENV variables
- Recipe Ingredient lists can now container ingredient sections.
### Localization
#### Huge thanks to [@sephrat](https://github.com/sephrat) for all his work on the project. He's very consistent in his contributions to the project and nearly every release has had some of his code in it! Here's some highlights from this release
Huge thanks to [@sephrat](https://github.com/sephrat) for all his work on the project. He's very consistent in his contributions to the project and nearly every release has had some of his code in it! Here's some highlights from this release
- Lazy Load Translations (Huge Performance Increase!)
- Tons of localization additions all around the site.
- Tons of localization additions all around the site.
- All of the work that goes into managing and making Crowdin a great feature the application
#### Here a list of contributors on Crowding who make Mealie possible in different locals

View file

@ -18,14 +18,16 @@
<v-hover v-slot="{ hover }" :open-delay="50">
<v-card :class="{ 'on-hover': hover }" :elevation="hover ? 12 : 2">
<CardImage large :slug="planDay.meals[0].slug" icon-size="200" @click="openSearch(index, modes.primary)">
<v-fade-transition>
<v-btn v-if="hover" small color="info" class="ma-1" @click.stop="addCustomItem(index, modes.primary)">
<v-icon left>
{{ $globals.icons.edit }}
</v-icon>
{{ $t('reicpe.no-recipe') }}
</v-btn>
</v-fade-transition>
<div>
<v-fade-transition>
<v-btn v-if="hover" small color="info" class="ma-1" @click.stop="addCustomItem(index, modes.primary)">
<v-icon left>
{{ $globals.icons.edit }}
</v-icon>
{{ $t("reicpe.no-recipe") }}
</v-btn>
</v-fade-transition>
</div>
</CardImage>
<v-card-title class="my-n3 mb-n6">
@ -40,14 +42,14 @@
<v-icon left>
{{ $globals.icons.edit }}
</v-icon>
{{ $t('reicpe.no-recipe') }}
{{ $t("reicpe.no-recipe") }}
</v-btn>
</v-fade-transition>
<v-btn color="info" outlined small @click="openSearch(index, modes.sides)">
<v-icon small class="mr-1">
{{ $globals.icons.create }}
</v-icon>
{{ $t('meal-plan.side') }}
{{ $t("meal-plan.side") }}
</v-btn>
</v-card-actions>
</v-hover>

View file

@ -13,7 +13,7 @@
<v-icon color="primary" class="icon-position" :size="iconSize">
{{ $globals.icons.primary }}
</v-icon>
<slot> </slot>
<slot> </slot>
</div>
</template>
@ -85,6 +85,7 @@ export default {
}
.icon-slot > div {
top: 0;
position: absolute;
z-index: 1;
}

View file

@ -1,17 +1,17 @@
<template>
<v-tooltip bottom nudge-right="50" :color="buttonStyle ? 'primary' : 'secondary'">
<v-tooltip bottom nudge-right="50" :color="buttonStyle ? 'info' : 'secondary'">
<template v-slot:activator="{ on, attrs }">
<v-btn
small
@click.prevent="toggleFavorite"
v-if="isFavorite || showAlways"
:color="buttonStyle ? 'primary' : 'secondary'"
:color="buttonStyle ? 'info' : 'secondary'"
:icon="!buttonStyle"
:fab="buttonStyle"
v-bind="attrs"
v-on="on"
>
<v-icon :small="!buttonStyle" color="secondary">
<v-icon :small="!buttonStyle" :color="buttonStyle ? 'white' : 'secondary'">
{{ isFavorite ? $globals.icons.heart : $globals.icons.heartOutline }}
</v-icon>
</v-btn>

View file

@ -1,11 +1,20 @@
<template>
<div v-if="edit || ( value && value.length > 0 )">
<div v-if="edit || (value && value.length > 0)">
<h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2>
<div v-if="edit">
<draggable :value="value" @input="updateIndex" @start="drag = true" @end="drag = false" handle=".handle">
<transition-group type="transition" :name="!drag ? 'flip-list' : null">
<div v-for="(ingredient, index) in value" :key="generateKey('ingredient', index)">
<v-row align="center">
<v-text-field
v-if="edit && showTitleEditor[index]"
class="mx-3 mt-3"
v-model="value[index].title"
dense
:label="$t('recipe.section-title')"
>
</v-text-field>
<v-textarea
class="mr-2"
:label="$t('recipe.ingredient')"
@ -15,6 +24,18 @@
dense
rows="1"
>
<template slot="append">
<v-tooltip right nudge-right="10">
<template v-slot:activator="{ on, attrs }">
<v-btn icon small class="mt-n1" v-bind="attrs" v-on="on" @click="toggleShowTitle(index)">
<v-icon>{{ showTitleEditor[index] ? $globals.icons.minus : $globals.icons.createAlt }}</v-icon>
</v-btn>
</template>
<span>{{
showTitleEditor[index] ? $t("recipe.remove-section") : $t("recipe.insert-section")
}}</span>
</v-tooltip>
</template>
<template slot="append-outer">
<v-icon class="handle">{{ $globals.icons.arrowUpDown }}</v-icon>
</template>
@ -35,18 +56,16 @@
</div>
</div>
<div v-else>
<v-list-item
dense
v-for="(ingredient, index) in value"
:key="generateKey('ingredient', index)"
@click="toggleChecked(index)"
>
<v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary"> </v-checkbox>
<v-list-item-content>
<vue-markdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredient.note"> </vue-markdown>
</v-list-item-content>
</v-list-item>
<div v-for="(ingredient, index) in value" :key="generateKey('ingredient', index)">
<h3 class="mt-2" v-if="showTitleEditor[index]">{{ ingredient.title }}</h3>
<v-divider v-if="showTitleEditor[index]"></v-divider>
<v-list-item dense @click="toggleChecked(index)">
<v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary"> </v-checkbox>
<v-list-item-content>
<vue-markdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredient.note"> </vue-markdown>
</v-list-item-content>
</v-list-item>
</div>
</div>
</div>
</template>
@ -76,10 +95,19 @@ export default {
return {
drag: false,
checked: [],
showTitleEditor: [],
};
},
mounted() {
this.checked = this.value.map(() => false);
this.showTitleEditor = this.value.map(x => this.validateTitle(x.title));
},
watch: {
value: {
handler() {
this.showTitleEditor = this.value.map(x => this.validateTitle(x.title));
},
},
},
methods: {
addIngredient(ingredients = null) {
@ -118,6 +146,16 @@ export default {
removeByIndex(list, index) {
list.splice(index, 1);
},
validateTitle(title) {
return !(title === null || title === "");
},
toggleShowTitle(index) {
const newVal = !this.showTitleEditor[index];
if (!newVal) {
this.value[index].title = "";
}
this.$set(this.showTitleEditor, index, newVal);
},
},
};
</script>

View file

@ -18,6 +18,7 @@
/>
<v-spacer></v-spacer>
<div v-if="!value" class="custom-btn-group ma-1">
<FavoriteBadge class="mx-1" color="info" button-style v-if="loggedIn" :slug="slug" show-always />
<v-tooltip bottom color="info">
<template v-slot:activator="{ on, attrs }">
<v-btn
@ -66,13 +67,15 @@
<script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog.vue";
import ContextMenu from "@/components/Recipe/ContextMenu.vue";
import FavoriteBadge from "@/components/Recipe/FavoriteBadge.vue";
const SAVE_EVENT = "save";
const DELETE_EVENT = "delete";
const CLOSE_EVENT = "close";
const JSON_EVENT = "json";
export default {
components: { ConfirmationDialog, ContextMenu },
components: { ConfirmationDialog, ContextMenu, FavoriteBadge },
props: {
slug: {
type: String,

View file

@ -12,7 +12,6 @@
class="d-print-none"
:key="imageKey"
>
<FavoriteBadge class="ma-1" button-style v-if="loggedIn" :slug="recipeDetails.slug" show-always />
<RecipeTimeCard
:class="isMobile ? undefined : 'force-bottom'"
:prepTime="recipeDetails.prepTime"
@ -69,7 +68,6 @@
<script>
import RecipePageActionMenu from "@/components/Recipe/RecipePageActionMenu.vue";
import { api } from "@/api";
import FavoriteBadge from "@/components/Recipe/FavoriteBadge";
import RecipeViewer from "@/components/Recipe/RecipeViewer";
import PrintView from "@/components/Recipe/PrintView";
import RecipeEditor from "@/components/Recipe/RecipeEditor";
@ -88,7 +86,6 @@ export default {
RecipePageActionMenu,
PrintView,
NoRecipe,
FavoriteBadge,
CommentsSection,
},
mixins: [user],
@ -233,7 +230,7 @@ export default {
async saveRecipe() {
if (this.validateRecipe()) {
let slug = await api.recipes.update(this.recipeDetails);
if(!slug) return;
if (!slug) return;
if (this.fileObject) {
this.saveImage(true);