unified category/tag selector

This commit is contained in:
hay-kot 2021-04-05 20:58:09 -08:00
commit e17a42900a
9 changed files with 71 additions and 138 deletions

View file

@ -44,6 +44,11 @@ export const tagAPI = {
let response = await apiReq.get(tagURLs.getAll);
return response.data;
},
async create(name) {
let response = await apiReq.post(tagURLs.getAll, { name: name });
store.dispatch("requestTags");
return response.data;
},
async getRecipesInTag(tag) {
let response = await apiReq.get(tagURLs.getTag(tag));
return response.data;

View file

@ -133,7 +133,7 @@
import { api } from "@/api";
import LanguageMenu from "@/components/UI/LanguageMenu";
import draggable from "vuedraggable";
import NewCategoryDialog from "./NewCategoryDialog.vue";
import NewCategoryDialog from "@/components/UI/Dialogs/NewCategoryDialog.vue";
export default {
components: {

View file

@ -1,78 +0,0 @@
<template>
<div>
<v-btn icon @click="dialog = true">
<v-icon color="white">mdi-plus</v-icon>
</v-btn>
<v-dialog v-model="dialog" width="500">
<v-card>
<v-app-bar dense dark color="primary mb-2">
<v-icon large left class="mt-1">
mdi-tag
</v-icon>
<v-toolbar-title class="headline">
Create a Category
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-card-title> </v-card-title>
<v-form @submit.prevent="select">
<v-card-text>
<v-text-field
dense
label="Category Name"
v-model="categoryName"
:rules="[rules.required]"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="dialog = false">
{{ $t("general.cancel") }}
</v-btn>
<v-btn color="success" text type="submit" :disabled="!categoryName">
{{ $t("general.create") }}
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { api } from "@/api";
export default {
props: {
buttonText: String,
value: String,
},
data() {
return {
dialog: false,
categoryName: "",
rules: {
required: val =>
!!val || this.$t("settings.theme.theme-name-is-required"),
},
};
},
watch: {
dialog(val) {
if (!val) this.categoryName = "";
},
},
methods: {
async select() {
await api.categories.create(this.categoryName);
this.$emit("new-category", this.categoryName);
this.dialog = false;
},
},
};
</script>
<style>
</style>

View file

@ -1,7 +1,8 @@
<template>
<v-select
<v-autocomplete
:items="activeItems"
v-model="selected"
:value="value"
:label="inputLabel"
chips
deletable-chips
@ -13,7 +14,7 @@
:solo="solo"
:return-object="returnObject"
:flat="flat"
@change="emitChange"
@input="emitChange"
>
<template v-slot:selection="data">
<v-chip
@ -24,16 +25,28 @@
label
color="accent"
dark
:key="data.index"
>
{{ data.item.name }}
{{ data.item.name || data.item }}
</v-chip>
</template>
</v-select>
<template v-slot:append-outer="">
<NewCategoryDialog
v-if="showAdd"
:tag-dialog="tagSelector"
@created-item="pushToItem"
/>
</template>
</v-autocomplete>
</template>
<script>
import NewCategoryDialog from "@/components/UI/Dialogs/NewCategoryDialog";
const MOUNTED_EVENT = "mounted";
export default {
components: {
NewCategoryDialog,
},
props: {
value: Array,
solo: {
@ -51,6 +64,12 @@ export default {
hint: {
default: null,
},
showAdd: {
default: false,
},
showLabel: {
default: true,
},
},
data() {
return {
@ -59,6 +78,7 @@ export default {
},
mounted() {
this.$emit(MOUNTED_EVENT);
this.setInit(this.value);
},
watch: {
@ -69,12 +89,18 @@ export default {
computed: {
inputLabel() {
if (!this.showLabel) return null;
return this.tagSelector ? "Tags" : "Categories";
},
activeItems() {
if (this.tagSelector) return this.$store.getters.getAllTags;
let ItemObjects = [];
if (this.tagSelector) ItemObjects = this.$store.getters.getAllTags;
else {
return this.$store.getters.getAllCategories;
ItemObjects = this.$store.getters.getAllCategories;
}
if (this.returnObject) return ItemObjects;
else {
return ItemObjects.map(x => x.name);
}
},
flat() {
@ -91,6 +117,10 @@ export default {
removeByIndex(index) {
this.selected.splice(index, 1);
},
pushToItem(createdItem) {
createdItem = this.returnObject ? createdItem : createdItem.name;
this.selected.push(createdItem);
},
},
};
</script>

View file

@ -114,60 +114,21 @@
<BulkAdd @bulk-data="appendIngredients" />
<h2 class="mt-6">{{ $t("recipe.categories") }}</h2>
<v-combobox
dense
multiple
chips
item-color="secondary"
deletable-chips
<CategoryTagSelector
:return-object="false"
v-model="value.recipeCategory"
hide-selected
:items="allCategories"
text="name"
:search-input.sync="categoriesSearchInput"
@change="categoriesSearchInput = ''"
>
<template v-slot:selection="data">
<v-chip
class="ma-1"
:input-value="data.selected"
close
@click:close="removeCategory(data.index)"
label
color="accent"
dark
>
{{ data.item }}
</v-chip>
</template>
</v-combobox>
:show-add="true"
:show-label="false"
/>
<h2 class="mt-4">{{ $t("recipe.tags") }}</h2>
<v-combobox
dense
multiple
chips
deletable-chips
<CategoryTagSelector
:return-object="false"
v-model="value.tags"
hide-selected
:items="allTags"
:search-input.sync="tagsSearchInput"
@change="tagssSearchInput = ''"
>
<template v-slot:selection="data">
<v-chip
class="ma-1"
:input-value="data.selected"
close
label
@click:close="removeTags(data.index)"
color="accent"
dark
>
{{ data.item }}
</v-chip>
</template>
</v-combobox>
:show-add="true"
:tag-selector="true"
:show-label="false"
/>
<h2 class="my-4">{{ $t("recipe.notes") }}</h2>
<v-card
@ -265,11 +226,13 @@ import { api } from "@/api";
import utils from "@/utils";
import BulkAdd from "./BulkAdd";
import ExtrasEditor from "./ExtrasEditor";
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
export default {
components: {
BulkAdd,
ExtrasEditor,
draggable,
CategoryTagSelector,
},
props: {
value: Object,

View file

@ -13,6 +13,7 @@
:dense="false"
v-model="groupSettings.categories"
:return-object="true"
:show-add="true"
:hint="
$t(
'meal-plan.only-recipes-with-these-categories-will-be-used-in-meal-plans'

View file

@ -1,7 +1,7 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from mealie.db.models.model_base import SqlAlchemyBase
from fastapi.logger import logger
from mealie.db.models.model_base import SqlAlchemyBase
from slugify import slugify
from sqlalchemy.orm import validates
@ -25,7 +25,7 @@ class Tag(SqlAlchemyBase):
assert name != ""
return name
def __init__(self, name) -> None:
def __init__(self, name, session=None) -> None:
self.name = name.strip()
self.slug = slugify(self.name)

View file

@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends
from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.category import RecipeTagResponse
from mealie.schema.category import RecipeTagResponse, TagIn
from mealie.schema.snackbar import SnackResponse
from sqlalchemy.orm.session import Session
@ -19,6 +19,14 @@ async def get_all_recipe_tags(session: Session = Depends(generate_session)):
""" Returns a list of available tags in the database """
return db.tags.get_all_limit_columns(session, ["slug", "name"])
@router.post("")
async def create_recipe_tag(
tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
):
""" Creates a Tag in the database """
return db.tags.create(session, tag.dict())
@router.get("/{tag}", response_model=RecipeTagResponse)
def get_all_recipes_by_tag(tag: str, session: Session = Depends(generate_session)):

View file

@ -23,6 +23,10 @@ class RecipeCategoryResponse(CategoryBase):
schema_extra = {"example": {"id": 1, "name": "dinner", "recipes": [{}]}}
class TagIn(CategoryIn):
pass
class TagBase(CategoryBase):
pass