mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 20:28:46 -05:00
further improvements to search view
This commit is contained in:
@@ -15,7 +15,9 @@
|
||||
<b-input-group class="mt-3">
|
||||
<b-input class="form-control" v-model="settings.search_input" v-bind:placeholder="$t('Search')"></b-input>
|
||||
<b-input-group-append>
|
||||
<b-button v-b-toggle.collapse_advanced_search v-bind:class="{'btn-primary': !isAdvancedSettingsSet(), 'btn-danger': isAdvancedSettingsSet()}" class="shadow-none btn"><i
|
||||
<b-button v-b-toggle.collapse_advanced_search
|
||||
v-bind:class="{'btn-primary': !isAdvancedSettingsSet(), 'btn-danger': isAdvancedSettingsSet()}"
|
||||
class="shadow-none btn"><i
|
||||
class="fas fa-caret-down" v-if="!settings.advanced_search_visible"></i><i class="fas fa-caret-up"
|
||||
v-if="settings.advanced_search_visible"></i>
|
||||
</b-button>
|
||||
@@ -37,21 +39,16 @@
|
||||
:href="resolveDjangoUrl('data_import_url')">{{ $t('Import') }}</a>
|
||||
</div>
|
||||
<div class="col-md-3" style="margin-top: 1vh">
|
||||
<button class="btn btn-primary btn-block text-uppercase" @click="resetSearch">
|
||||
{{ $t('Reset_Search') }}
|
||||
<button 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}"
|
||||
@click="settings.search_internal = !settings.search_internal;refreshData()">
|
||||
{{ $t('Internal') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-2" style="position: relative; margin-top: 1vh">
|
||||
<b-form-checkbox v-model="settings.search_internal" name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none"
|
||||
style="position:relative;top: 50%; transform: translateY(-50%);" switch>
|
||||
{{ $t('show_only_internal') }}
|
||||
</b-form-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="col-md-1" style="position: relative; margin-top: 1vh">
|
||||
<button id="id_settings_button" class="btn btn-primary btn-block"><i class="fas fa-cog"></i>
|
||||
<div class="col-md-3" style="position: relative; margin-top: 1vh">
|
||||
<button id="id_settings_button" class="btn btn-primary btn-block text-uppercase"><i
|
||||
class="fas fa-cog"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -175,7 +172,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="row">
|
||||
<div class="col col-md-12 text-right" style="margin-top: 2vh">
|
||||
<span class="text-muted">
|
||||
{{ $t('Page') }} {{ settings.pagination_page }}/{{ pagination_count }} <a href="#" @click="resetSearch"><i
|
||||
class="fas fa-times-circle"></i> {{ $t('Reset') }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));grid-gap: 1rem;">
|
||||
|
||||
@@ -194,10 +200,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh; text-align: center">
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="col col-md-12">
|
||||
<b-button @click="loadMore()" class="btn-block btn-success" v-if="pagination_more">{{ $t('Load_More') }}
|
||||
</b-button>
|
||||
<b-pagination pills
|
||||
v-model="settings.pagination_page"
|
||||
:total-rows="pagination_count"
|
||||
per-page="25"
|
||||
@change="pageChange"
|
||||
align="center">
|
||||
|
||||
</b-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -233,6 +245,8 @@ import GenericMultiselect from "@/components/GenericMultiselect";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
let SETTINGS_COOKIE_NAME = 'search_settings'
|
||||
|
||||
export default {
|
||||
name: 'RecipeSearchView',
|
||||
mixins: [ResolveUrlMixin],
|
||||
@@ -243,6 +257,7 @@ export default {
|
||||
meal_plans: [],
|
||||
last_viewed_recipes: [],
|
||||
|
||||
settings_loaded: false,
|
||||
settings: {
|
||||
search_input: '',
|
||||
search_internal: false,
|
||||
@@ -256,19 +271,33 @@ export default {
|
||||
advanced_search_visible: false,
|
||||
show_meal_plan: true,
|
||||
recently_viewed: 5,
|
||||
|
||||
pagination_page: 1,
|
||||
},
|
||||
|
||||
pagination_more: true,
|
||||
pagination_page: 1,
|
||||
pagination_count: 0,
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(function () {
|
||||
if (this.$cookies.isKey('search_settings_v2')) {
|
||||
this.settings = this.$cookies.get("search_settings_v2")
|
||||
if (this.$cookies.isKey(SETTINGS_COOKIE_NAME)) {
|
||||
let cookie_val = this.$cookies.get(SETTINGS_COOKIE_NAME)
|
||||
for (let i of Object.keys(cookie_val)) {
|
||||
this.$set(this.settings, i, cookie_val[i])
|
||||
}
|
||||
//TODO i have no idea why the above code does not suffice to update the
|
||||
//TODO pagination UI element as $set should update all values reactively but it does not
|
||||
setTimeout(function () {
|
||||
this.$set(this.settings, 'pagination_page', 0)
|
||||
}.bind(this), 50)
|
||||
setTimeout(function () {
|
||||
this.$set(this.settings, 'pagination_page', cookie_val['pagination_page'])
|
||||
}.bind(this), 51)
|
||||
|
||||
}
|
||||
|
||||
|
||||
let urlParams = new URLSearchParams(window.location.search);
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
@@ -278,7 +307,7 @@ export default {
|
||||
let keyword = {id: x, name: 'loading'}
|
||||
this.settings.search_keywords.push(keyword)
|
||||
apiClient.retrieveKeyword(x).then(result => {
|
||||
this.$set(this.settings.search_keywords,this.settings.search_keywords.indexOf(keyword), result.data)
|
||||
this.$set(this.settings.search_keywords, this.settings.search_keywords.indexOf(keyword), result.data)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -293,7 +322,7 @@ export default {
|
||||
watch: {
|
||||
settings: {
|
||||
handler() {
|
||||
this.$cookies.set("search_settings_v2", this.settings, -1)
|
||||
this.$cookies.set(SETTINGS_COOKIE_NAME, this.settings, -1)
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
@@ -327,16 +356,11 @@ export default {
|
||||
|
||||
this.settings.search_internal,
|
||||
undefined,
|
||||
this.pagination_page,
|
||||
this.settings.pagination_page,
|
||||
).then(result => {
|
||||
this.pagination_more = (result.data.next !== null)
|
||||
if (page_load) {
|
||||
for (let x of result.data.results) {
|
||||
this.recipes.push(x)
|
||||
}
|
||||
} else {
|
||||
this.recipes = result.data.results
|
||||
}
|
||||
window.scrollTo(0, 0);
|
||||
this.pagination_count = result.data.count
|
||||
this.recipes = result.data.results
|
||||
})
|
||||
},
|
||||
loadMealPlan: function () {
|
||||
@@ -378,13 +402,14 @@ export default {
|
||||
this.settings.search_keywords = []
|
||||
this.settings.search_foods = []
|
||||
this.settings.search_books = []
|
||||
this.settings.pagination_page = 1
|
||||
this.refreshData(false)
|
||||
},
|
||||
loadMore: function (page) {
|
||||
this.pagination_page++
|
||||
this.refreshData(true)
|
||||
pageChange: function (page) {
|
||||
this.settings.pagination_page = page
|
||||
this.refreshData()
|
||||
},
|
||||
isAdvancedSettingsSet(){
|
||||
isAdvancedSettingsSet() {
|
||||
return ((this.settings.search_keywords.length + this.settings.search_foods.length + this.settings.search_books.length) > 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row text-center">
|
||||
<div class="col col-md-12">
|
||||
<recipe-rating :recipe="recipe"></recipe-rating> <br/>
|
||||
<last-cooked :recipe="recipe"></last-cooked>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-auto">
|
||||
<div class="col-12" style="text-align: center">
|
||||
<i>{{ recipe.description }}</i>
|
||||
@@ -166,6 +173,8 @@ import moment from 'moment'
|
||||
import Keywords from "@/components/Keywords";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import AddRecipeToBook from "@/components/AddRecipeToBook";
|
||||
import RecipeRating from "@/components/RecipeRating";
|
||||
import LastCooked from "@/components/LastCooked";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
@@ -178,6 +187,8 @@ export default {
|
||||
ToastMixin,
|
||||
],
|
||||
components: {
|
||||
LastCooked,
|
||||
RecipeRating,
|
||||
PdfViewer,
|
||||
ImageViewer,
|
||||
Ingredient,
|
||||
|
||||
28
vue/src/components/LastCooked.vue
Normal file
28
vue/src/components/LastCooked.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<span>
|
||||
<b-badge pill variant="primary" v-if="recipe.last_cooked !== null"><i class="fas fa-utensils"></i> {{
|
||||
formatDate(recipe.last_cooked)
|
||||
}}</b-badge>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from "moment/moment";
|
||||
|
||||
export default {
|
||||
name: "LastCooked",
|
||||
props: {
|
||||
recipe: Object
|
||||
},
|
||||
methods: {
|
||||
formatDate: function (datetime) {
|
||||
moment.locale(window.navigator.language);
|
||||
return moment(datetime).format('L')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -7,7 +7,9 @@
|
||||
top></b-card-img-lazy>
|
||||
<div class="card-img-overlay h-100 d-flex flex-column justify-content-right"
|
||||
style="float:right; text-align: right; padding-top: 10px; padding-right: 5px">
|
||||
<a><recipe-context-menu :recipe="recipe" style="float:right" v-if="recipe !== null"></recipe-context-menu></a>
|
||||
<a>
|
||||
<recipe-context-menu :recipe="recipe" style="float:right" v-if="recipe !== null"></recipe-context-menu>
|
||||
</a>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -21,12 +23,18 @@
|
||||
<b-card-text style="text-overflow: ellipsis;">
|
||||
<template v-if="recipe !== null">
|
||||
{{ recipe.description }}
|
||||
|
||||
<recipe-rating :recipe="recipe"></recipe-rating> <br/> <!-- TODO UGLY! -->
|
||||
<last-cooked :recipe="recipe"></last-cooked>
|
||||
<keywords :recipe="recipe" style="margin-top: 4px"></keywords>
|
||||
|
||||
|
||||
<b-badge pill variant="info" v-if="!recipe.internal">{{ $t('External') }}</b-badge>
|
||||
<b-badge pill variant="success"
|
||||
v-if="Date.parse(recipe.created_at) > new Date(Date.now() - (7 * (1000 * 60 * 60 * 24)))">
|
||||
{{ $t('New') }}
|
||||
</b-badge>
|
||||
|
||||
</template>
|
||||
<template v-else>{{ meal_plan.note }}</template>
|
||||
</b-card-text>
|
||||
@@ -44,13 +52,19 @@
|
||||
import RecipeContextMenu from "@/components/RecipeContextMenu";
|
||||
import Keywords from "@/components/Keywords";
|
||||
import {resolveDjangoUrl, ResolveUrlMixin} from "@/utils/utils";
|
||||
import RecipeRating from "@/components/RecipeRating";
|
||||
import moment from "moment/moment";
|
||||
import Vue from "vue";
|
||||
import LastCooked from "@/components/LastCooked";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
export default {
|
||||
name: "RecipeCard",
|
||||
mixins: [
|
||||
ResolveUrlMixin,
|
||||
],
|
||||
components: {Keywords, RecipeContextMenu},
|
||||
components: {LastCooked, RecipeRating, Keywords, RecipeContextMenu},
|
||||
props: {
|
||||
recipe: Object,
|
||||
meal_plan: Object,
|
||||
|
||||
24
vue/src/components/RecipeRating.vue
Normal file
24
vue/src/components/RecipeRating.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<span style="display: inline-block;" v-if="recipe.rating > 0">
|
||||
<i class="fas fa-star fa-xs" v-for="i in Math.floor(recipe.rating)" v-bind:key="i"></i>
|
||||
<i class="fas fa-star-half-alt fa-xs" v-if="recipe.rating % 1 > 0"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "RecipeRating",
|
||||
props: {
|
||||
recipe: Object
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -564,7 +564,19 @@ export interface MealPlanRecipe {
|
||||
* @type {string}
|
||||
* @memberof MealPlanRecipe
|
||||
*/
|
||||
file_path?: string;
|
||||
servings_text?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof MealPlanRecipe
|
||||
*/
|
||||
rating?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof MealPlanRecipe
|
||||
*/
|
||||
last_cooked?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -699,6 +711,18 @@ export interface Recipe {
|
||||
* @memberof Recipe
|
||||
*/
|
||||
servings_text?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Recipe
|
||||
*/
|
||||
rating?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Recipe
|
||||
*/
|
||||
last_cooked?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -956,7 +980,19 @@ export interface RecipeOverview {
|
||||
* @type {string}
|
||||
* @memberof RecipeOverview
|
||||
*/
|
||||
file_path?: string;
|
||||
servings_text?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof RecipeOverview
|
||||
*/
|
||||
rating?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof RecipeOverview
|
||||
*/
|
||||
last_cooked?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user