Feature/recipe viewer (#244)

* fix dialog placement

* markdown support in ingredients

* fix line render issue

* fix tag rendering bug

* change ingredients to text area

* no slug error

* add tag pages

* remove console.logs

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-03-31 19:01:10 -08:00 committed by GitHub
parent 30510202df
commit 049c269f6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 144 additions and 40 deletions

View File

@ -21,7 +21,6 @@ export const metaAPI = {
async getIsDemo() { async getIsDemo() {
let response = await apiReq.get(debugURLs.demo); let response = await apiReq.get(debugURLs.demo);
console.log(response);
return response.data; return response.data;
}, },
}; };

View File

@ -273,7 +273,6 @@ export default {
await this.initialize(); await this.initialize();
}, },
resetPassword() { resetPassword() {
console.log(this.activeId);
api.users.resetPassword(this.editedItem.id); api.users.resetPassword(this.editedItem.id);
}, },
}, },

View File

@ -125,7 +125,6 @@ export default {
computed: { computed: {
groupSettings() { groupSettings() {
console.log(this.$store.getters.getCurrentGroup);
return this.$store.getters.getCurrentGroup; return this.$store.getters.getCurrentGroup;
}, },
actualStartDate() { actualStartDate() {

View File

@ -26,21 +26,16 @@
</v-card-title> </v-card-title>
<v-card-actions class=""> <v-card-actions class="">
<v-row dense align="center"> <v-rating
<v-col> class="mr-2"
<v-rating color="secondary"
class="mr-2" background-color="secondary lighten-3"
color="secondary" dense
background-color="secondary lighten-3" length="5"
dense size="15"
length="5" :value="rating"
size="15" ></v-rating>
:value="rating" <v-spacer></v-spacer>
></v-rating>
</v-col>
<v-col></v-col>
<v-col align="end"> </v-col>
</v-row>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-hover> </v-hover>
@ -55,6 +50,7 @@ export default {
description: String, description: String,
rating: Number, rating: Number,
image: String, image: String,
route: { route: {
default: true, default: true,
}, },

View File

@ -83,14 +83,16 @@
:key="generateKey('ingredient', index)" :key="generateKey('ingredient', index)"
> >
<v-row align="center"> <v-row align="center">
<v-text-field <v-textarea
class="mr-2" class="mr-2"
:label="$t('recipe.ingredient')" :label="$t('recipe.ingredient')"
v-model="value.recipeIngredient[index]" v-model="value.recipeIngredient[index]"
append-outer-icon="mdi-menu" append-outer-icon="mdi-menu"
mdi-move-resize mdi-move-resize
auto-grow
solo solo
dense dense
rows="2"
> >
<v-icon <v-icon
class="mr-n1" class="mr-n1"
@ -100,7 +102,7 @@
> >
mdi-delete mdi-delete
</v-icon> </v-icon>
</v-text-field> </v-textarea>
</v-row> </v-row>
</div> </div>
</transition-group> </transition-group>
@ -235,6 +237,7 @@
dense dense
v-model="value.recipeInstructions[index]['text']" v-model="value.recipeInstructions[index]['text']"
:key="generateKey('instructions', index)" :key="generateKey('instructions', index)"
rows="4"
> >
</v-textarea> </v-textarea>
</v-card-text> </v-card-text>

View File

@ -1,27 +1,52 @@
<template> <template>
<div> <div>
<h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2> <h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2>
<div <v-list-item
v-for="(ingredient, index) in ingredients" dense
v-for="(ingredient, index) in displayIngredients"
:key="generateKey('ingredient', index)" :key="generateKey('ingredient', index)"
@click="ingredient.checked = !ingredient.checked"
> >
<v-checkbox <v-checkbox
hide-details hide-details
class="ingredients" v-model="ingredient.checked"
:label="ingredient" class=" pt-0 ingredients my-auto py-auto"
color="secondary" color="secondary"
> >
</v-checkbox> </v-checkbox>
</div>
<v-list-item-content>
<vue-markdown
class="my-auto text-subtitle-1 mb-0"
:source="ingredient.text"
>
</vue-markdown>
</v-list-item-content>
</v-list-item>
</div> </div>
</template> </template>
<script> <script>
import VueMarkdown from "@adapttive/vue-markdown";
import utils from "@/utils"; import utils from "@/utils";
export default { export default {
components: {
VueMarkdown,
},
props: { props: {
ingredients: Array, ingredients: Array,
}, },
data() {
return {
displayIngredients: [],
};
},
mounted() {
this.displayIngredients = this.ingredients.map(x => ({
text: x,
checked: false,
}));
},
methods: { methods: {
generateKey(item, index) { generateKey(item, index) {
return utils.generateUniqueKey(item, index); return utils.generateUniqueKey(item, index);
@ -31,4 +56,11 @@ export default {
</script> </script>
<style> <style>
p {
margin-bottom: auto !important;
}
.my-card-text {
overflow-wrap: break-word;
}
</style> </style>

View File

@ -2,12 +2,12 @@
<div v-if="items && items.length > 0"> <div v-if="items && items.length > 0">
<h2 class="mt-4">{{ title }}</h2> <h2 class="mt-4">{{ title }}</h2>
<v-chip <v-chip
:to="`/recipes/${getSlug(category)}`"
label label
class="ma-1" class="ma-1"
color="accent" color="accent"
dark dark
v-for="category in items" v-for="category in items"
:to="`/recipes/${urlParam}/${getSlug(category)}`"
:key="category" :key="category"
> >
{{ category }} {{ category }}
@ -20,7 +20,7 @@ export default {
props: { props: {
items: Array, items: Array,
title: String, title: String,
category: { isCategory: {
default: true, default: true,
}, },
}, },
@ -28,11 +28,23 @@ export default {
allCategories() { allCategories() {
return this.$store.getters.getAllCategories; return this.$store.getters.getAllCategories;
}, },
allTags() {
return this.$store.getters.getAllTags;
},
urlParam() {
return this.isCategory ? 'category' : 'tag'
}
}, },
methods: { methods: {
getSlug(name) { getSlug(name) {
if (this.category) { if (!name) return;
return this.allCategories.filter(x => x.name == name)[0].slug;
if (this.isCategory) {
const matches = this.allCategories.filter(x => x.name == name);
if (matches.length > 0) return matches[0].slug;
} else {
const matches = this.allTags.filter(x => x.name == name);
if (matches.length > 0) return matches[0].slug;
} }
}, },
}, },

View File

@ -34,7 +34,11 @@
<Ingredients :ingredients="ingredients" /> <Ingredients :ingredients="ingredients" />
<div v-if="medium"> <div v-if="medium">
<RecipeChips :title="$t('recipe.categories')" :items="categories" /> <RecipeChips :title="$t('recipe.categories')" :items="categories" />
<RecipeChips :title="$t('recipe.tags')" :items="tags" /> <RecipeChips
:title="$t('recipe.tags')"
:items="tags"
:isCategory="false"
/>
<Notes :notes="notes" /> <Notes :notes="notes" />
</div> </div>
</v-col> </v-col>

View File

@ -154,7 +154,6 @@ export default {
return utils.getImageURL(image); return utils.getImageURL(image);
}, },
selected(slug, name) { selected(slug, name) {
console.log("Selected", slug, name);
this.$emit("selected", slug, name); this.$emit("selected", slug, name);
}, },
async onFocus() { async onFocus() {

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="text-center"> <div class="text-center ">
<v-dialog v-model="dialog" width="600px" height="0"> <v-dialog v-model="dialog" class="search-dialog" width="600px" height="0">
<v-card> <v-card>
<v-app-bar dark color="primary"> <v-app-bar dark color="primary">
<v-toolbar-title class="headline">Search a Recipe</v-toolbar-title> <v-toolbar-title class="headline">Search a Recipe</v-toolbar-title>
@ -49,8 +49,8 @@ export default {
}; };
</script> </script>
<style> <style scope>
.v-dialog__content { .search-dialog {
margin-top: 10%; margin-top: 10%;
align-items: flex-start; align-items: flex-start;
justify-content: center; justify-content: center;

View File

@ -36,7 +36,6 @@ export default {
}, },
computed: { computed: {
siteSettings() { siteSettings() {
console.log(this.$store.getters.getSiteSettings);
return this.$store.getters.getSiteSettings; return this.$store.getters.getSiteSettings;
}, },
recentRecipes() { recentRecipes() {
@ -54,7 +53,6 @@ export default {
this.siteSettings.categories.forEach(async element => { this.siteSettings.categories.forEach(async element => {
let recipes = await this.getRecipeByCategory(element.slug); let recipes = await this.getRecipeByCategory(element.slug);
if (recipes.recipes.length < 0) recipes.recipes = []; if (recipes.recipes.length < 0) recipes.recipes = [];
console.log(recipes);
this.recipeByCategory.push(recipes); this.recipeByCategory.push(recipes);
}); });
}, },

View File

@ -0,0 +1,60 @@
<template>
<v-container>
<CategorySidebar />
<CardSection
:sortable="true"
:title="title"
:recipes="recipes"
:card-limit="9999"
@sort="sortAZ"
@sort-recent="sortRecent"
/>
</v-container>
</template>
<script>
import { api } from "@/api";
import CardSection from "@/components/UI/CardSection";
import CategorySidebar from "@/components/UI/CategorySidebar";
export default {
components: {
CardSection,
CategorySidebar,
},
data() {
return {
title: "",
recipes: [],
};
},
computed: {
currentTag() {
return this.$route.params.tag;
},
},
watch: {
async currentCategory() {
this.getRecipes();
},
},
mounted() {
this.getRecipes();
},
methods: {
async getRecipes() {
let data = await api.tags.getRecipesInTag(this.currentTag);
this.title = data.name;
this.recipes = data.recipes;
},
sortAZ() {
this.recipes.sort((a, b) => (a.name > b.name ? 1 : -1));
},
sortRecent() {
this.recipes.sort((a, b) => (a.dateAdded > b.dateAdded ? -1 : 1));
},
},
};
</script>
<style>
</style>

View File

@ -6,6 +6,7 @@ import NewRecipe from "@/pages/Recipe/NewRecipe";
import CustomPage from "@/pages/Recipes/CustomPage"; import CustomPage from "@/pages/Recipes/CustomPage";
import AllRecipes from "@/pages/Recipes/AllRecipes"; import AllRecipes from "@/pages/Recipes/AllRecipes";
import CategoryPage from "@/pages/Recipes/CategoryPage"; import CategoryPage from "@/pages/Recipes/CategoryPage";
import TagPage from "@/pages/Recipes/TagPage";
import Planner from "@/pages/MealPlan/Planner"; import Planner from "@/pages/MealPlan/Planner";
import Debug from "@/pages/Debug"; import Debug from "@/pages/Debug";
import LoginPage from "@/pages/LoginPage"; import LoginPage from "@/pages/LoginPage";
@ -33,7 +34,8 @@ export const routes = [
{ path: "/search", component: SearchPage }, { path: "/search", component: SearchPage },
{ path: "/recipes/all", component: AllRecipes }, { path: "/recipes/all", component: AllRecipes },
{ path: "/pages/:customPage", component: CustomPage }, { path: "/pages/:customPage", component: CustomPage },
{ path: "/recipes/:category", component: CategoryPage }, { path: "/recipes/tag/:tag", component: TagPage },
{ path: "/recipes/category/:category", component: CategoryPage },
{ path: "/recipe/:recipe", component: ViewRecipe }, { path: "/recipe/:recipe", component: ViewRecipe },
{ path: "/new/", component: NewRecipe }, { path: "/new/", component: NewRecipe },
{ path: "/meal-plan/planner", component: Planner }, { path: "/meal-plan/planner", component: Planner },

View File

@ -2,6 +2,7 @@ from fastapi import APIRouter, Depends
from mealie.db.database import db from mealie.db.database import db
from mealie.db.db_setup import generate_session from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user from mealie.routes.deps import get_current_user
from mealie.schema.category import RecipeTagResponse
from mealie.schema.snackbar import SnackResponse from mealie.schema.snackbar import SnackResponse
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -19,7 +20,7 @@ async def get_all_recipe_tags(session: Session = Depends(generate_session)):
return db.tags.get_all_limit_columns(session, ["slug", "name"]) return db.tags.get_all_limit_columns(session, ["slug", "name"])
@router.get("/{tag}") @router.get("/{tag}", response_model=RecipeTagResponse)
def get_all_recipes_by_tag(tag: str, session: Session = Depends(generate_session)): def get_all_recipes_by_tag(tag: str, session: Session = Depends(generate_session)):
""" Returns a list of recipes associated with the provided tag. """ """ Returns a list of recipes associated with the provided tag. """
return db.tags.get(session, tag) return db.tags.get(session, tag)

View File

@ -27,5 +27,5 @@ class TagBase(CategoryBase):
pass pass
class RecipeTagResponse(TagBase): class RecipeTagResponse(RecipeCategoryResponse):
pass pass