mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-01-07 09:31:22 -05:00
feature/category-tag-crud (#354)
* update tag route * search.and * offset for mobile * relative imports * get settings * new page * category/tag CRUD * bulk assign frontend * Bulk assign * debounce search * remove dev data * recipe store refactor * fix mobile view * fix failing tests * commit test data Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
<v-card-title class=" headline">
|
||||
{{ $t("meal-plan.create-a-new-meal-plan") }}
|
||||
<v-btn color="info" class="ml-auto" @click="setQuickWeek()">
|
||||
<v-icon left>mdi-calendar-minus</v-icon> {{$t('meal-plan.quick-week')}}
|
||||
<v-icon left>mdi-calendar-minus</v-icon>
|
||||
{{ $t("meal-plan.quick-week") }}
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
@@ -153,7 +154,7 @@ export default {
|
||||
return recipes.length > 0 ? recipes : this.items;
|
||||
},
|
||||
allRecipes() {
|
||||
return this.$store.getters.getRecentRecipes;
|
||||
return this.$store.getters.getAllRecipes;
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -60,10 +60,10 @@
|
||||
<v-row v-else dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="12"
|
||||
md="6"
|
||||
lg="4"
|
||||
xl="3"
|
||||
:sm="singleColumn ? '12' : '12'"
|
||||
:md="singleColumn ? '12' : '6'"
|
||||
:lg="singleColumn ? '12' : '4'"
|
||||
:xl="singleColumn ? '12' : '3'"
|
||||
v-for="recipe in recipes.slice(0, cardLimit)"
|
||||
:key="recipe.name"
|
||||
>
|
||||
@@ -79,14 +79,16 @@
|
||||
</v-row>
|
||||
</div>
|
||||
<div v-intersect="bumpList" class="d-flex">
|
||||
<v-progress-circular
|
||||
v-if="loading"
|
||||
class="mx-auto mt-1"
|
||||
:size="50"
|
||||
:width="7"
|
||||
color="primary"
|
||||
indeterminate
|
||||
></v-progress-circular>
|
||||
<v-expand-x-transition>
|
||||
<v-progress-circular
|
||||
v-if="loading"
|
||||
class="mx-auto mt-1"
|
||||
:size="50"
|
||||
:width="7"
|
||||
color="primary"
|
||||
indeterminate
|
||||
></v-progress-circular>
|
||||
</v-expand-x-transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -109,6 +111,12 @@ export default {
|
||||
hardLimit: {
|
||||
default: 99999,
|
||||
},
|
||||
mobileCards: {
|
||||
default: false,
|
||||
},
|
||||
singleColumn: {
|
||||
defualt: false,
|
||||
},
|
||||
recipes: Array,
|
||||
},
|
||||
data() {
|
||||
@@ -117,8 +125,14 @@ export default {
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
recipes() {
|
||||
this.bumpList();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
viewScale() {
|
||||
if (this.mobileCards) return true;
|
||||
switch (this.$vuetify.breakpoint.name) {
|
||||
case "xs":
|
||||
return true;
|
||||
@@ -128,10 +142,16 @@ export default {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
effectiveHardLimit() {
|
||||
return Math.min(this.hardLimit, this.recipes.length);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
bumpList() {
|
||||
const newCardLimit = Math.min(this.cardLimit + 20, this.hardLimit);
|
||||
const newCardLimit = Math.min(
|
||||
this.cardLimit + 20,
|
||||
this.effectiveHardLimit
|
||||
);
|
||||
|
||||
if (this.loading === false && newCardLimit > this.cardLimit) {
|
||||
this.setLoader();
|
||||
@@ -141,7 +161,7 @@ export default {
|
||||
},
|
||||
async setLoader() {
|
||||
this.loading = true;
|
||||
await new Promise(r => setTimeout(r, 3000));
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
this.loading = false;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,21 +1,38 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-dialog v-model="dialog" :width="modalWidth + 'px'">
|
||||
<v-app-bar dark :color="color" class="mt-n1 mb-2">
|
||||
<v-icon large left v-if="!loading">
|
||||
{{ titleIcon }}
|
||||
</v-icon>
|
||||
<v-progress-circular
|
||||
v-else
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
:width="modalWidth + 'px'"
|
||||
:content-class="top ? 'top-dialog' : undefined"
|
||||
>
|
||||
<v-card class="pb-10" height="100%">
|
||||
<v-app-bar dark :color="color" class="mt-n1 mb-2">
|
||||
<v-icon large left>
|
||||
{{ titleIcon }}
|
||||
</v-icon>
|
||||
<v-toolbar-title class="headline"> {{ title }} </v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
</v-app-bar>
|
||||
<v-progress-linear
|
||||
v-if="loading"
|
||||
indeterminate
|
||||
color="white"
|
||||
large
|
||||
class="mr-2"
|
||||
>
|
||||
</v-progress-circular>
|
||||
<v-toolbar-title class="headline"> {{ title }} </v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
</v-app-bar>
|
||||
color="primary"
|
||||
></v-progress-linear>
|
||||
<slot> </slot>
|
||||
<v-card-actions>
|
||||
<slot name="card-actions">
|
||||
<v-btn text color="grey" @click="dialog = false">
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="success" @click="$emit('submit')">
|
||||
Submit
|
||||
</v-btn>
|
||||
</slot>
|
||||
</v-card-actions>
|
||||
|
||||
<slot name="below-actions"> </slot>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -35,6 +52,12 @@ export default {
|
||||
modalWidth: {
|
||||
default: "500",
|
||||
},
|
||||
loading: {
|
||||
default: false,
|
||||
},
|
||||
top: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -45,9 +68,15 @@ export default {
|
||||
open() {
|
||||
this.dialog = true;
|
||||
},
|
||||
close() {
|
||||
this.dialog = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.top-dialog {
|
||||
align-self: flex-start;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-btn icon @click="dialog = true" class="mt-n1">
|
||||
<v-icon :color="color">mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
<slot>
|
||||
<v-btn icon @click="dialog = true" class="mt-n1">
|
||||
<v-icon :color="color">mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</slot>
|
||||
<v-dialog v-model="dialog" width="500">
|
||||
<v-card>
|
||||
<v-app-bar dense dark color="primary mb-2">
|
||||
@@ -80,6 +82,9 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
open() {
|
||||
this.dialog = true;
|
||||
},
|
||||
async select() {
|
||||
const newItem = await (async () => {
|
||||
if (this.tagDialog) {
|
||||
|
||||
79
frontend/src/components/UI/Search/FuseSearchBar.vue
Normal file
79
frontend/src/components/UI/Search/FuseSearchBar.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div>
|
||||
<slot> </slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const RESULTS_EVENT = "results";
|
||||
import Fuse from "fuse.js";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
search: {
|
||||
default: "",
|
||||
},
|
||||
rawData: {
|
||||
default: true,
|
||||
},
|
||||
/** Defaults to Show All Results */
|
||||
showAll: {
|
||||
default: true,
|
||||
},
|
||||
keys: {
|
||||
type: Array,
|
||||
default: () => ["name"],
|
||||
},
|
||||
defaultOptions: {
|
||||
default: () => ({
|
||||
shouldSort: true,
|
||||
threshold: 0.6,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
findAllMatches: true,
|
||||
maxPatternLength: 32,
|
||||
minMatchCharLength: 2,
|
||||
}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
results: [],
|
||||
fuseResults: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
return { ...this.defaultOptions, ...{ keys: this.keys } };
|
||||
},
|
||||
autoResults() {
|
||||
return this.fuseResults.length > 1 ? this.fuseResults : this.results;
|
||||
},
|
||||
fuse() {
|
||||
return new Fuse(this.rawData, this.options);
|
||||
},
|
||||
isSearching() {
|
||||
return this.search && this.search.length > 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
search() {
|
||||
try {
|
||||
this.results = this.fuse.search(this.search.trim());
|
||||
} catch {
|
||||
this.results = this.rawData
|
||||
.map(x => ({ item: x }))
|
||||
.sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
}
|
||||
this.$emit(RESULTS_EVENT, this.results);
|
||||
|
||||
if (this.showResults === true) {
|
||||
this.fuseResults = this.results;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -132,7 +132,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
data() {
|
||||
return this.$store.getters.getRecentRecipes;
|
||||
return this.$store.getters.getAllRecipes;
|
||||
},
|
||||
autoResults() {
|
||||
return this.fuseResults.length > 1 ? this.fuseResults : this.results;
|
||||
|
||||
@@ -163,6 +163,11 @@ export default {
|
||||
to: "/admin/settings",
|
||||
title: this.$t("settings.site-settings"),
|
||||
},
|
||||
{
|
||||
icon: "mdi-tools",
|
||||
to: "/admin/toolbox",
|
||||
title: this.$t("settings.toolbox.toolbox"),
|
||||
},
|
||||
{
|
||||
icon: "mdi-account-group",
|
||||
to: "/admin/manage-users",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
bottom
|
||||
right
|
||||
offset-y
|
||||
offset-overflow
|
||||
open-on-hover
|
||||
close-delay="200"
|
||||
>
|
||||
@@ -72,7 +73,7 @@ export default {
|
||||
},
|
||||
{
|
||||
icon: "mdi-logout",
|
||||
title: this.$t('user.logout'),
|
||||
title: this.$t("user.logout"),
|
||||
restricted: true,
|
||||
nav: "/logout",
|
||||
},
|
||||
@@ -85,7 +86,6 @@ export default {
|
||||
],
|
||||
};
|
||||
},
|
||||
mounted() {},
|
||||
computed: {
|
||||
filteredItems() {
|
||||
if (this.loggedIn) {
|
||||
|
||||
Reference in New Issue
Block a user