mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	refactor(frontend): ♻️ rewrite search componenets to typescript
This commit is contained in:
		| @@ -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> | ||||
		Reference in New Issue
	
	Block a user