diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index 2d9641128..fb074b2ae 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -653,7 +653,7 @@ class RecipeFacet(): if not self._request.space.demo and self._request.space.show_facet_count: return queryset.annotate(count=Coalesce(Subquery(self._recipe_count_queryset('keywords', depth, steplen)), 0) ).filter(depth=depth, count__gt=0 - ).values('id', 'name', 'count', 'numchild').order_by('name') + ).values('id', 'name', 'count', 'numchild').order_by('name')[:200] else: return queryset.filter(depth=depth).values('id', 'name', 'numchild').order_by('name') @@ -664,7 +664,7 @@ class RecipeFacet(): if not self._request.space.demo and self._request.space.show_facet_count: return queryset.annotate(count=Coalesce(Subquery(self._recipe_count_queryset('steps__ingredients__food', depth, steplen)), 0) ).filter(depth__lte=depth, count__gt=0 - ).values('id', 'name', 'count', 'numchild').order_by('name') + ).values('id', 'name', 'count', 'numchild').order_by('name')[:200] else: return queryset.filter(depth__lte=depth).values('id', 'name', 'numchild').order_by('name') diff --git a/cookbook/templates/url_import.html b/cookbook/templates/url_import.html index df03abf03..1b6de7102 100644 --- a/cookbook/templates/url_import.html +++ b/cookbook/templates/url_import.html @@ -668,7 +668,6 @@ Vue.component('vue-multiselect', window.VueMultiselect.default) - let app = new Vue({ components: { Multiselect: window.VueMultiselect.default diff --git a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue index 4172e7795..8eae19eda 100644 --- a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue +++ b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue @@ -8,25 +8,25 @@
- + - + - + - - + +
- +
@@ -41,12 +41,13 @@ class="btn btn-block text-uppercase" v-b-tooltip.hover :title="$t('show_only_internal')" - v-bind:class="{ 'btn-success': settings.search_internal, 'btn-primary': !settings.search_internal }" + v-bind:class="{ 'btn-success': search.search_internal, 'btn-primary': !search.search_internal }" @click=" - settings.search_internal = !settings.search_internal + search.search_internal = !search.search_internal refreshData() " > + {{ $t("Internal") }}
@@ -56,33 +57,50 @@
- -
- - - + + + + + + - - - + + + - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - -
-
{{ $t("Close") }} @@ -95,7 +113,8 @@
+ - - {{ $t("or") }} + + {{ $t("or") }} {{ $t("and") }} @@ -124,7 +153,8 @@
+ - - {{ $t("or") }} + + {{ $t("or") }} {{ $t("and") }} @@ -155,7 +195,7 @@ - - {{ $t("or") }} + + {{ $t("or") }} {{ $t("and") }} @@ -178,7 +218,7 @@
- {{ $t("Page") }} {{ settings.pagination_page }}/{{ Math.ceil(pagination_count / settings.page_count) }} + {{ $t("Page") }} {{ search.pagination_page }}/{{ Math.ceil(pagination_count / ui.page_size) }} {{ $t("Reset") }}
@@ -220,7 +260,7 @@
- +
@@ -247,13 +287,14 @@ import LoadingSpinner from "@/components/LoadingSpinner" // TODO: is this deprec import RecipeCard from "@/components/RecipeCard" import GenericMultiselect from "@/components/GenericMultiselect" -import { Treeselect, LOAD_CHILDREN_OPTIONS } from "@riophae/vue-treeselect" -import "@riophae/vue-treeselect/dist/vue-treeselect.css" +import { Treeselect, LOAD_CHILDREN_OPTIONS } from "@riophae/vue-treeselect" //TODO: delete +import "@riophae/vue-treeselect/dist/vue-treeselect.css" //TODO: delete import RecipeSwitcher from "@/components/Buttons/RecipeSwitcher" Vue.use(BootstrapVue) -let SETTINGS_COOKIE_NAME = "search_settings" +let SEARCH_COOKIE_NAME = "search_settings" +let UI_COOKIE_NAME = "_uisearch_settings" export default { name: "RecipeSearchView", @@ -267,27 +308,30 @@ export default { meal_plans: [], last_viewed_recipes: [], - settings_loaded: false, - settings: { + search: { search_input: "", search_internal: false, search_keywords: [], search_foods: [], search_books: [], search_ratings: undefined, - search_keywords_or: true, search_foods_or: true, search_books_or: true, + pagination_page: 1, + }, + ui: { advanced_search_visible: false, show_meal_plan: true, meal_plan_days: 0, recently_viewed: 5, sort_by_new: true, - pagination_page: 1, - page_count: 25, + page_size: 25, + remember_search: true, + remember_hours: 4, + sql_debug: false, + tree_select: false, }, - pagination_count: 0, random_search: false, debug: false, @@ -314,47 +358,35 @@ export default { { id: 0, label: this.$t("Unrated") + ratingCount(this.facets.Ratings?.["0.0"] ?? 0) }, ] }, - searchFiltered: function () { - if ( - this.settings?.search_input === "" && - this.settings?.search_keywords?.length === 0 && - this.settings?.search_foods?.length === 0 && - this.settings?.search_books?.length === 0 && - // this.settings?.pagination_page === 1 && - !this.random_search && - this.settings?.search_ratings === undefined - ) { - return false - } else { - return true - } - }, }, mounted() { this.$nextTick(function () { - if (this.$cookies.isKey(SETTINGS_COOKIE_NAME)) { - this.settings = Object.assign({}, this.settings, this.$cookies.get(SETTINGS_COOKIE_NAME)) + if (this.$cookies.isKey(UI_COOKIE_NAME)) { + this.ui = Object.assign({}, this.ui, this.$cookies.get(UI_COOKIE_NAME)) + } + if (this.ui.remember_search && this.$cookies.isKey(SEARCH_COOKIE_NAME)) { + this.search = Object.assign({}, this.search, this.$cookies.get(SEARCH_COOKIE_NAME), `${this.ui.remember_hours}h`) } let urlParams = new URLSearchParams(window.location.search) if (urlParams.has("keyword")) { - this.settings.search_keywords = [] + this.search.search_keywords = [] this.facets.Keywords = [] for (let x of urlParams.getAll("keyword")) { - this.settings.search_keywords.push(Number.parseInt(x)) + this.search.search_keywords.push(Number.parseInt(x)) this.facets.Keywords.push({ id: x, name: "loading..." }) } } this.facets.Foods = [] - for (let x of this.settings.search_foods) { + for (let x of this.search.search_foods) { this.facets.Foods.push({ id: x, name: "loading..." }) } this.facets.Keywords = [] - for (let x of this.settings.search_keywords) { + for (let x of this.search.search_keywords) { this.facets.Keywords.push({ id: x, name: "loading..." }) } this.facets.Books = [] - for (let x of this.settings.search_books) { + for (let x of this.search.search_books) { this.facets.Books.push({ id: x, name: "loading..." }) } this.loadMealPlan() @@ -364,27 +396,39 @@ export default { this.debug = localStorage.getItem("DEBUG") == "True" || false }, watch: { - settings: { + search: { handler() { - this.$cookies.set(SETTINGS_COOKIE_NAME, this.settings, "4h") + this.$cookies.set(SEARCH_COOKIE_NAME, this.search, `${this.ui.remember_hours}h`) }, deep: true, }, - "settings.show_meal_plan": function () { + ui: { + handler() { + this.$cookies.set(UI_COOKIE_NAME, this.ui) + }, + deep: true, + }, + "ui.show_meal_plan": function () { this.loadMealPlan() }, - "settings.meal_plan_days": function () { + "ui.meal_plan_days": function () { this.loadMealPlan() }, - "settings.recently_viewed": function () { + "ui.recently_viewed": function () { this.refreshData(false) }, - "settings.search_input": _debounce(function () { - this.settings.pagination_page = 1 + "ui.tree_select": function () { + if (this.ui.tree_select && !this.facets?.Keywords && !this.facets?.Foods) { + console.log("i changed to true") + this.getFacets(this.facets?.hash) + } + }, + "search.search_input": _debounce(function () { + this.search.pagination_page = 1 this.pagination_count = 0 this.refreshData(false) }, 300), - "settings.page_count": _debounce(function () { + "ui.page_size": _debounce(function () { this.refreshData(false) }, 300), }, @@ -393,24 +437,24 @@ export default { refreshData: function (random) { this.random_search = random let params = { - query: this.settings.search_input, - keywords: this.settings.search_keywords, - foods: this.settings.search_foods, - rating: this.settings.search_ratings, - books: this.settings.search_books.map(function (A) { + query: this.search.search_input, + keywords: this.search.search_keywords, + foods: this.search.search_foods, + rating: this.search.search_ratings, + books: this.search.search_books.map(function (A) { return A["id"] }), - keywordsOr: this.settings.search_keywords_or, - foodsOr: this.settings.search_foods_or, - booksOr: this.settings.search_books_or, - internal: this.settings.search_internal, + keywordsOr: this.search.search_keywords_or, + foodsOr: this.search.search_foods_or, + booksOr: this.search.search_books_or, + internal: this.search.search_internal, random: this.random_search, - _new: this.settings.sort_by_new, - page: this.settings.pagination_page, - pageSize: this.settings.page_count, + _new: this.ui.sort_by_new, + page: this.search.pagination_page, + pageSize: this.search.page_size, } if (!this.searchFiltered) { - params.options = { query: { last_viewed: this.settings.recently_viewed } } + params.options = { query: { last_viewed: this.ui.recently_viewed } } } this.genericAPI(this.Models.RECIPE, this.Actions.LIST, params) .then((result) => { @@ -418,9 +462,6 @@ export default { this.pagination_count = result.data.count this.facets = result.data.facets - // if (this.facets?.cache_key) { - // this.getFacets(this.facets.cache_key) - // } this.recipes = this.removeDuplicates(result.data.results, (recipe) => recipe.id) if (!this.searchFiltered) { // if meal plans are being shown - filter out any meal plan recipes from the recipe list @@ -442,12 +483,12 @@ export default { return [...new Map(data.map((item) => [key(item), item])).values()] }, loadMealPlan: function () { - if (this.settings.show_meal_plan) { + if (this.ui.show_meal_plan) { let params = { options: { query: { from_date: moment().format("YYYY-MM-DD"), - to_date: moment().add(this.settings.meal_plan_days, "days").format("YYYY-MM-DD"), + to_date: moment().add(this.ui.meal_plan_days, "days").format("YYYY-MM-DD"), }, }, } @@ -459,26 +500,24 @@ export default { } }, genericSelectChanged: function (obj) { - this.settings[obj.var] = obj.val + this.search[obj.var] = obj.val this.refreshData(false) }, resetSearch: function () { - this.settings.search_input = "" - this.settings.search_internal = false - this.settings.search_keywords = [] - this.settings.search_foods = [] - this.settings.search_books = [] - this.settings.search_ratings = undefined - this.settings.pagination_page = 1 + this.search.search_input = "" + this.search.search_internal = false + this.search.search_keywords = [] + this.search.search_foods = [] + this.search.search_books = [] + this.search.search_ratings = undefined + this.search.pagination_page = 1 this.refreshData(false) }, pageChange: function (page) { - this.settings.pagination_page = page + this.search.pagination_page = page this.refreshData(false) }, - isAdvancedSettingsSet() { - return this.settings.search_keywords.length + this.settings.search_foods.length + this.settings.search_books.length > 0 - }, + normalizer(node) { let count = node?.count ? " (" + node.count + ")" : "" return { @@ -500,6 +539,9 @@ export default { } }, getFacets: function (hash, facet, id) { + if (!this.ui.tree_select) { + return + } let params = { hash: hash } if (facet) { params[facet] = id @@ -511,51 +553,59 @@ export default { showSQL: function () { // TODO refactor this so that it isn't a total copy of refreshData let params = { - query: this.settings.search_input, - keywords: this.settings.search_keywords, - foods: this.settings.search_foods, - rating: this.settings.search_ratings, - books: this.settings.search_books.map(function (A) { + query: this.search.search_input, + keywords: this.search.search_keywords, + foods: this.search.search_foods, + rating: this.search.search_ratings, + books: this.search.search_books.map(function (A) { return A["id"] }), - keywordsOr: this.settings.search_keywords_or, - foodsOr: this.settings.search_foods_or, - booksOr: this.settings.search_books_or, - internal: this.settings.search_internal, + keywordsOr: this.search.search_keywords_or, + foodsOr: this.search.search_foods_or, + booksOr: this.search.search_books_or, + internal: this.search.search_internal, random: this.random_search, - _new: this.settings.sort_by_new, - page: this.settings.pagination_page, - pageSize: this.settings.page_count, + _new: this.ui.sort_by_new, + page: this.search.pagination_page, + pageSize: this.ui.page_size, } if (!this.searchFiltered) { - params.options = { query: { last_viewed: this.settings.recently_viewed, debug: true } } + params.options = { query: { last_viewed: this.ui.recently_viewed, debug: true } } } else { params.options = { query: { debug: true } } } this.genericAPI(this.Models.RECIPE, this.Actions.LIST, params).then((result) => {}) }, + // TODO remove loadFoodChildren({ action, parentNode, callback }) { - // Typically, do the AJAX stuff here. - // Once the server has responded, - // assign children options to the parent node & call the callback. - if (action === LOAD_CHILDREN_OPTIONS) { if (this.facets?.cache_key) { this.getFacets(this.facets.cache_key, "food", parentNode.id).then(callback()) } } }, + // TODO remove loadKeywordChildren({ action, parentNode, callback }) { - // Typically, do the AJAX stuff here. - // Once the server has responded, - // assign children options to the parent node & call the callback. - if (action === LOAD_CHILDREN_OPTIONS) { if (this.facets?.cache_key) { this.getFacets(this.facets.cache_key, "keyword", parentNode.id).then(callback()) } } }, + searchFiltered: function (ignore_string = false) { + let filtered = + this.search?.search_keywords?.length === 0 && + this.search?.search_foods?.length === 0 && + this.search?.search_books?.length === 0 && + // this.settings?.pagination_page === 1 && + !this.random_search && + this.search?.search_ratings === undefined + if (ignore_string) { + return filtered + } else { + return filtered && this.search?.search_input === "" + } + }, }, } diff --git a/vue/src/components/GenericMultiselect.vue b/vue/src/components/GenericMultiselect.vue index 125034407..7e11467e1 100644 --- a/vue/src/components/GenericMultiselect.vue +++ b/vue/src/components/GenericMultiselect.vue @@ -7,7 +7,7 @@ :hide-selected="multiple" :preserve-search="true" :internal-search="false" - :limit="options_limit" + :limit="limit" :placeholder="lookupPlaceholder" :label="label" track-by="id" @@ -36,7 +36,6 @@ export default { loading: false, objects: [], selected_objects: [], - options_limit: 25, } }, props: { @@ -90,9 +89,9 @@ export default { search: function (query) { let options = { page: 1, - pageSize: 10, + pageSize: this.limit, query: query, - limit: this.options_limit, + limit: this.limit, } this.genericAPI(this.model, this.Actions.LIST, options).then((result) => { this.objects = this.sticky_options.concat(result.data?.results ?? result.data) diff --git a/vue/src/locales/en.json b/vue/src/locales/en.json index 180354df8..96b10040d 100644 --- a/vue/src/locales/en.json +++ b/vue/src/locales/en.json @@ -283,5 +283,9 @@ "shopping_add_onhand_desc": "Mark food 'On Hand' when checked off shopping list.", "shopping_add_onhand": "Auto On Hand", "related_recipes": "Related Recipes", - "today_recipes": "Today's Recipes" + "today_recipes": "Today's Recipes", + "sql_debug": "SQL Debug", + "remember_search": "Remember Search", + "remember_hours": "Hours to Remember", + "tree_select": "Use Tree Selection" }