mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-24 15:49:42 +02:00
refactor(frontend): ♻️ rewrite search componenets to typescript
This commit is contained in:
parent
1981e191be
commit
bde885dc84
25 changed files with 826 additions and 113 deletions
|
@ -126,7 +126,7 @@ export default {
|
|||
default: null,
|
||||
},
|
||||
hardLimit: {
|
||||
type: Number,
|
||||
type: [String, Number],
|
||||
default: 99999,
|
||||
},
|
||||
mobileCards: {
|
||||
|
|
111
frontend/components/Domain/Recipe/RecipeCategoryTagDialog.vue
Normal file
111
frontend/components/Domain/Recipe/RecipeCategoryTagDialog.vue
Normal file
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<div>
|
||||
<slot>
|
||||
<v-btn icon class="mt-n1" @click="dialog = true">
|
||||
<v-icon :color="color">{{ $globals.icons.create }}</v-icon>
|
||||
</v-btn>
|
||||
</slot>
|
||||
<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">
|
||||
{{ $globals.icons.tags }}
|
||||
</v-icon>
|
||||
|
||||
<v-toolbar-title class="headline">
|
||||
{{ title }}
|
||||
</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 v-model="itemName" dense :label="inputLabel" :rules="[rules.required]"></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<BaseButton cancel @click="dialog = false" />
|
||||
<v-spacer></v-spacer>
|
||||
<BaseButton type="submit" create :disabled="!itemName" />
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue-demi";
|
||||
import { useApiSingleton } from "~/composables/use-api";
|
||||
const CREATED_ITEM_EVENT = "created-item";
|
||||
export default defineComponent({
|
||||
props: {
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: "Add",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
tagDialog: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const api = useApiSingleton();
|
||||
|
||||
return { api };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
itemName: "",
|
||||
rules: {
|
||||
required: (val) => !!val || "A Name is Required",
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return this.tagDialog ? "Create a Tag" : "Create a Category";
|
||||
},
|
||||
inputLabel() {
|
||||
return this.tagDialog ? "Tag Name" : "Category Name";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
dialog(val) {
|
||||
if (!val) this.itemName = "";
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
open() {
|
||||
this.dialog = true;
|
||||
},
|
||||
async select() {
|
||||
const newItem = await (async () => {
|
||||
if (this.tagDialog) {
|
||||
const newItem = await this.api.tags.createOne({ name: this.itemName });
|
||||
return newItem;
|
||||
} else {
|
||||
const newItem = await this.api.categories.createOne({ name: this.itemName });
|
||||
return newItem;
|
||||
}
|
||||
})();
|
||||
|
||||
this.$emit(CREATED_ITEM_EVENT, newItem);
|
||||
this.dialog = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style></style>
|
150
frontend/components/Domain/Recipe/RecipeCategoryTagSelector.vue
Normal file
150
frontend/components/Domain/Recipe/RecipeCategoryTagSelector.vue
Normal file
|
@ -0,0 +1,150 @@
|
|||
<template>
|
||||
<v-autocomplete
|
||||
v-model="selected"
|
||||
:items="activeItems"
|
||||
:value="value"
|
||||
:label="inputLabel"
|
||||
chips
|
||||
deletable-chips
|
||||
:dense="dense"
|
||||
item-text="name"
|
||||
persistent-hint
|
||||
multiple
|
||||
:hint="hint"
|
||||
:solo="solo"
|
||||
:return-object="returnObject"
|
||||
:flat="flat"
|
||||
@input="emitChange"
|
||||
>
|
||||
<template #selection="data">
|
||||
<v-chip
|
||||
:key="data.index"
|
||||
class="ma-1"
|
||||
:input-value="data.selected"
|
||||
close
|
||||
label
|
||||
color="accent"
|
||||
dark
|
||||
@click:close="removeByIndex(data.index)"
|
||||
>
|
||||
{{ data.item.name || data.item }}
|
||||
</v-chip>
|
||||
</template>
|
||||
<template #append-outer="">
|
||||
<RecipeCategoryTagDialog v-if="showAdd" :tag-dialog="tagSelector" @created-item="pushToItem" />
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RecipeCategoryTagDialog from "./RecipeCategoryTagDialog";
|
||||
import { useApiSingleton } from "~/composables/use-api";
|
||||
import { useTags, useCategories } from "~/composables/use-tags-categories";
|
||||
const MOUNTED_EVENT = "mounted";
|
||||
export default {
|
||||
components: {
|
||||
RecipeCategoryTagDialog,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
solo: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
dense: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
returnObject: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
tagSelector: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
showAdd: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showLabel: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const api = useApiSingleton();
|
||||
|
||||
const { allTags, useAsyncGetAll: getAllTags } = useTags();
|
||||
const { allCategories, useAsyncGetAll: getAllCategories } = useCategories();
|
||||
getAllCategories();
|
||||
getAllTags();
|
||||
|
||||
return { api, allTags, allCategories };
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selected: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
inputLabel() {
|
||||
if (!this.showLabel) return null;
|
||||
return this.tagSelector ? this.$t("tag.tags") : this.$t("recipe.categories");
|
||||
},
|
||||
activeItems() {
|
||||
let ItemObjects = [];
|
||||
if (this.tagSelector) ItemObjects = this.allTags;
|
||||
else {
|
||||
ItemObjects = this.allCategories;
|
||||
}
|
||||
if (this.returnObject) return ItemObjects;
|
||||
else {
|
||||
return ItemObjects.map((x) => x.name);
|
||||
}
|
||||
},
|
||||
flat() {
|
||||
if (this.selected) {
|
||||
return this.selected.length > 0 && this.solo;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
this.selected = val;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$emit(MOUNTED_EVENT);
|
||||
this.setInit(this.value);
|
||||
},
|
||||
methods: {
|
||||
emitChange() {
|
||||
this.$emit("input", this.selected);
|
||||
},
|
||||
setInit(val) {
|
||||
this.selected = val;
|
||||
},
|
||||
removeByIndex(index) {
|
||||
this.selected.splice(index, 1);
|
||||
},
|
||||
pushToItem(createdItem) {
|
||||
createdItem = this.returnObject ? createdItem : createdItem.name;
|
||||
this.selected.push(createdItem);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<v-toolbar dense flat>
|
||||
<v-btn-toggle v-model="selected" tile group color="primary accent-3" mandatory @change="emitMulti">
|
||||
<v-btn small :value="false">
|
||||
{{ $t("search.include") }}
|
||||
</v-btn>
|
||||
|
||||
<v-btn small :value="true">
|
||||
{{ $t("search.exclude") }}
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn-toggle v-model="match" tile group color="primary accent-3" mandatory @change="emitMulti">
|
||||
<v-btn small :value="false">
|
||||
{{ $t("search.and") }}
|
||||
</v-btn>
|
||||
<v-btn small :value="true">
|
||||
{{ $t("search.or") }}
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-toolbar>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue-demi";
|
||||
|
||||
type SelectionValue = "include" | "exclude" | "any";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: String as () => SelectionValue,
|
||||
default: "include",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selected: false,
|
||||
match: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
emitChange() {
|
||||
this.$emit("input", this.selected);
|
||||
},
|
||||
emitMulti() {
|
||||
const updateData = {
|
||||
exclude: this.selected,
|
||||
matchAny: this.match,
|
||||
};
|
||||
this.$emit("update", updateData);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
Loading…
Add table
Add a link
Reference in a new issue