improved code quality of start page scrollers

This commit is contained in:
vabene1111
2024-12-19 21:16:15 +01:00
parent 1c9d19fc76
commit 3e3780028b
37 changed files with 200 additions and 447 deletions

View File

@@ -6,6 +6,15 @@
</router-link>
<v-spacer></v-spacer>
<global-search-dialog></global-search-dialog>
<v-btn icon="$add">
<v-icon icon="$add"></v-icon>
<v-menu activator="parent">
<v-list>
<v-list-item prepend-icon="$add" :to="{ name: 'ModelEditPage', params: {model: 'Recipe'} }">{{ $t('Create Recipe') }}</v-list-item>
<v-list-item prepend-icon="fa-solid fa-globe" :to="{ name: 'RecipeImportPage', params: {} }">{{ $t('Import Recipe') }}</v-list-item>
</v-list>
</v-menu>
</v-btn>
<v-avatar color="primary" class="me-2">{{ useUserPreferenceStore().userSettings.user.displayName.charAt(0) }}
<v-menu activator="parent">

View File

@@ -1,71 +1,159 @@
<template>
<v-row justify="space-between">
<v-col>
<h4><i v-if="icon != 'undefined'" :class="icon + ' fa-fw'"></i> {{ title }}</h4>
</v-col>
</v-row>
<template v-if="loading || recipes.length > 0">
<v-row justify="space-between">
<v-col>
<h4><i :class="icon + ' fa-fw'"></i> {{ title }}</h4>
</v-col>
</v-row>
<v-row class="mt-0" v-if="recipeWindows.length > 0">
<v-col>
<v-window show-arrows>
<v-window-item v-for="w in recipeWindows" class="pt-1 pb-1">
<v-row>
<v-col v-for="r in w" :key="r.id">
<recipe-card :recipe="r" :show_description="true" :show_keywords="true" style="height: 20vh"></recipe-card>
</v-col>
</v-row>
</v-window-item>
</v-window>
</v-col>
</v-row>
<v-row v-if="recipeWindows.length == 0 && skeletons > 0">
<v-col>
<v-window>
<v-window-item>
<v-row>
<v-col v-for="n in skeletons">
<v-skeleton-loader :elevation="3" type="card"></v-skeleton-loader>
</v-col>
</v-row>
</v-window-item>
</v-window>
</v-col>
</v-row>
<v-row class="mt-0" v-if="recipeWindows.length > 0">
<v-col>
<v-window show-arrows>
<v-window-item v-for="w in recipeWindows" class="pt-1 pb-1">
<v-row>
<v-col v-for="r in w" :key="r.id">
<recipe-card :recipe="r" :show_description="true" :show_keywords="true" style="height: 20vh"></recipe-card>
</v-col>
</v-row>
</v-window-item>
</v-window>
</v-col>
</v-row>
<v-row v-if="skeletons > 0 && loading">
<v-col>
<v-window>
<v-window-item>
<v-row>
<v-col v-for="n in skeletons">
<v-skeleton-loader :elevation="3" type="card"></v-skeleton-loader>
</v-col>
</v-row>
</v-window-item>
</v-window>
</v-col>
</v-row>
</template>
</template>
<script lang="ts" setup>
import {computed, PropType, toRefs} from 'vue'
import {computed, onMounted, PropType, ref, toRefs} from 'vue'
import RecipeCard from "@/components/display/RecipeCard.vue";
import {DisplayBreakpoint, useDisplay} from "vuetify";
import {Recipe, RecipeOverview} from "@/openapi";
import {ApiApi, ApiRecipeListRequest, Keyword, Recipe, RecipeOverview} from "@/openapi";
import {homePageCols} from "@/utils/breakpoint_utils";
import {useI18n} from "vue-i18n";
//TODO mode ideas "last year/month/cooked long ago"
const props = defineProps(
{
title: {type: String as PropType<undefined | String>, required: true},
icon: {type: String, required: false},
mode: {type: String as PropType<'recent' | 'new' | 'keyword' | 'rating'>, required: true},
skeletons: {type: Number, default: 0},
recipes: {
type: Array as PropType<Recipe[] | RecipeOverview[]>,
required: true
},
}
)
const {title, recipes} = toRefs(props)
let numberOfCols = computed(() => {
const {name} = useDisplay()
const {t} = useI18n()
const {name} = useDisplay()
const loading = ref(true)
const recipes = ref([] as Recipe[] | RecipeOverview[])
const keyword = ref({} as Keyword)
/**
* determine title based on type
*/
const title = computed(() => {
switch (props.mode) {
case 'recent':
return t('Recently_Viewed')
case 'new':
return t('New')
case 'rating':
return t('Rating')
case 'keyword':
if (Object.keys(keyword.value).length > 0) {
return keyword.value.label
}
return t('Keyword')
}
})
/**
* determine icon based on type
*/
const icon = computed(() => {
switch (props.mode) {
case 'recent':
return 'fa-solid fa-eye'
case 'new':
return 'fa-solid fa-calendar-alt'
case 'rating':
return 'fa-solid fa-star'
case 'keyword':
return 'fa-solid fa-tags'
}
})
/**
* number of columns to show depending on display size
*/
const numberOfCols = computed(() => {
return homePageCols(name.value)
})
type CustomWindow = {
id: number,
recipes: Array<Recipe | RecipeOverview>
onMounted(() => {
loadRecipes()
})
/**
* load recipes depending on type by creating request parameters and executing request function
*/
function loadRecipes() {
let api = new ApiApi()
let requestParameters = {pageSize: 16} as ApiRecipeListRequest
switch (props.mode) {
case 'recent':
// TODO implement correct parameter
requestParameters._new = 'true'
break;
case 'new':
requestParameters._new = 'true'
break;
case 'rating':
requestParameters.rating = 4
break;
case 'keyword':
api.apiKeywordList({random: "true", limit: "1"}).then((r) => {
if (r.count > 0) {
keyword.value = r.results[0]
requestParameters.keywords = [keyword.value.id!]
doRecipeRequest(requestParameters)
}
})
return;
}
doRecipeRequest(requestParameters)
}
/**
* actual request function requesting the recipes from the server based on the given parameters
* @param params
*/
function doRecipeRequest(params: ApiRecipeListRequest) {
let api = new ApiApi()
api.apiRecipeList(params).then((r) => {
recipes.value = r.results
}).finally(() => {
loading.value = false
})
}
/**
* split the list of recipes into windows for display based on column count
*/
let recipeWindows = computed(() => {
let windows = []
let current_window = []

View File

@@ -113,6 +113,7 @@
"Food_Alias": "",
"Foods": "",
"Friday": "",
"GettingStarted": "",
"GroupBy": "",
"Hide_Food": "",
"Hide_Keyword": "",

View File

@@ -110,6 +110,7 @@
"Food_Alias": "Псевдоним на храната",
"Foods": "Храни",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Групирай по",
"Hide_Food": "Скриване на храна",
"Hide_Keyword": "Скриване на ключови думи",

View File

@@ -153,6 +153,7 @@
"Food_Replace": "",
"Foods": "",
"Friday": "",
"GettingStarted": "",
"GroupBy": "",
"Hide_Food": "",
"Hide_Keyword": "",

View File

@@ -153,6 +153,7 @@
"Food_Replace": "Nahrazení v potravině",
"Foods": "Potraviny",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Seskupit podle",
"Hide_Food": "Skrýt potravinu",
"Hide_Keyword": "Skrýt štítky",

View File

@@ -141,6 +141,7 @@
"Food_Replace": "Erstat ingrediens",
"Foods": "Mad",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Grupper efter",
"Hide_Food": "Skjul mad",
"Hide_Keyword": "Skjul nøgleord",

View File

@@ -155,6 +155,7 @@
"Food_Replace": "Essen Ersetzen",
"Foods": "Lebensmittel",
"Friday": "Freitag",
"GettingStarted": "Erste Schritte",
"GroupBy": "Gruppieren nach",
"Hide_Food": "Lebensmittel verbergen",
"Hide_Keyword": "Schlüsselwörter verbergen",

View File

@@ -136,6 +136,7 @@
"Food_Alias": "Ψευδώνυμο φαγητού",
"Foods": "Φαγητά",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Ομαδοποίηση κατά",
"Hide_Food": "Απόκρυψη φαγητού",
"Hide_Keyword": "Απόκρυψη λέξεων-κλειδί",

View File

@@ -154,6 +154,7 @@
"Food_Replace": "Food Replace",
"Foods": "Foods",
"Friday": "Friday",
"GettingStarted": "Getting Started",
"GroupBy": "Group By",
"Hide_Food": "Hide Food",
"Hide_Keyword": "Hide keywords",

View File

@@ -154,6 +154,7 @@
"Food_Replace": "Sustituir Alimento",
"Foods": "Comida",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Agrupar por",
"Hide_Food": "Esconder ingrediente",
"Hide_Keyword": "Esconder Palabras Clave",

View File

@@ -83,6 +83,7 @@
"Food": "Ruoka",
"Food_Alias": "Ruoan nimimerkki",
"Friday": "",
"GettingStarted": "",
"Hide_Food": "Piilota ruoka",
"Hide_Keyword": "Piilota avainsana",
"Hide_Keywords": "Piilota Avainsana",

View File

@@ -153,6 +153,7 @@
"Food_Replace": "Remplacer l'aliment",
"Foods": "Aliments",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Grouper par",
"Hide_Food": "Cacher laliment",
"Hide_Keyword": "masquer les mots clefs",

View File

@@ -154,6 +154,7 @@
"Food_Replace": "החלף אוכל",
"Foods": "מאכלים",
"Friday": "",
"GettingStarted": "",
"GroupBy": "אסוף לפי",
"Hide_Food": "הסתר אוכל",
"Hide_Keyword": "הסתר מילות מפתח",

View File

@@ -137,6 +137,7 @@
"Food_Replace": "Étel cseréje",
"Foods": "Alapanyagok",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Csoportosítva",
"Hide_Food": "Alapanyag elrejtése",
"Hide_Keyword": "Kulcsszavak elrejtése",

View File

@@ -62,6 +62,7 @@
"Files": "",
"Food": "Սննդամթերք",
"Friday": "",
"GettingStarted": "",
"Hide_Food": "Թաքցնել սննդամթերքը",
"Hide_Keywords": "Թաքցնել բանալի բառը",
"Hide_Recipes": "Թաքցնել բաղադրատոմսերը",

View File

@@ -125,6 +125,7 @@
"Food_Alias": "",
"Foods": "",
"Friday": "",
"GettingStarted": "",
"GroupBy": "",
"Hide_Food": "",
"Hide_Keyword": "",

View File

@@ -153,6 +153,7 @@
"Food_Replace": "",
"Foods": "",
"Friday": "",
"GettingStarted": "",
"GroupBy": "",
"Hide_Food": "",
"Hide_Keyword": "",

View File

@@ -130,6 +130,7 @@
"Food_Alias": "Alias Alimento",
"Foods": "Alimenti",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Raggruppa per",
"Hide_Food": "Nascondi alimento",
"Hide_Keyword": "Nascondi parole chiave",

View File

@@ -139,6 +139,7 @@
"Food_Replace": "",
"Foods": "",
"Friday": "",
"GettingStarted": "",
"GroupBy": "",
"Hide_Food": "",
"Hide_Keyword": "",

View File

@@ -134,6 +134,7 @@
"Food_Alias": "Matrett Alias",
"Foods": "",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Grupér",
"Hide_Food": "Skjul Matrett",
"Hide_Keyword": "Skjul nøkkelord",

View File

@@ -138,6 +138,7 @@
"Food_Alias": "Eten Alias",
"Foods": "Ingrediënten",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Groepeer per",
"Hide_Food": "Verberg Eten",
"Hide_Keyword": "Verberg etiketten",

View File

@@ -155,6 +155,7 @@
"Food_Replace": "Zastąp produkt",
"Foods": "Żywność",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Grupuj według",
"Hide_Food": "Ukryj żywność",
"Hide_Keyword": "Ukryj słowa kluczowe",

View File

@@ -112,6 +112,7 @@
"Food_Alias": "Alcunha da comida",
"Foods": "",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Agrupar por",
"Hide_Food": "Esconder comida",
"Hide_Keyword": "",

View File

@@ -149,6 +149,7 @@
"Food_Replace": "Substituir Alimento",
"Foods": "Alimentos",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Agrupar Por",
"Hide_Food": "Esconder Comida",
"Hide_Keyword": "Oculta palavras-chave",

View File

@@ -132,6 +132,7 @@
"Food_Alias": "Pseudonim mâncare",
"Foods": "Alimente",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Grupat de",
"Hide_Food": "Ascunde mâncare",
"Hide_Keyword": "Ascunde cuvintele cheie",

View File

@@ -101,6 +101,7 @@
"FoodOnHand": "{food} у вас в наличии.",
"Food_Alias": "Наименование еды",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Сгруппировать по",
"Hide_Food": "Скрыть еду",
"Hide_Keyword": "Скрыть ключевые слова",

View File

@@ -101,6 +101,7 @@
"FoodOnHand": "Imaš {food} v roki.",
"Food_Alias": "Vzdevek hrane",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Združi po",
"Hide_Food": "Skrij hrano",
"Hide_Keyword": "Skrij ključne besede",

View File

@@ -155,6 +155,7 @@
"Food_Replace": "Ersätt ingrediens",
"Foods": "Livsmedel",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Gruppera enligt",
"Hide_Food": "Dölj livsmedel",
"Hide_Keyword": "Dölj nyckelord",

View File

@@ -154,6 +154,7 @@
"Food_Replace": "Yiyecek Değiştir",
"Foods": "Yiyecekler",
"Friday": "",
"GettingStarted": "",
"GroupBy": "Gruplandırma Ölçütü",
"Hide_Food": "Yiyeceği Gizle",
"Hide_Keyword": "Anahtar kelimeleri gizle",

View File

@@ -119,6 +119,7 @@
"Food_Alias": "",
"Foods": "",
"Friday": "",
"GettingStarted": "",
"GroupBy": "По Групі",
"Hide_Food": "Сховати Їжу",
"Hide_Keyword": "",

View File

@@ -150,6 +150,7 @@
"Food_Replace": "食物替换",
"Foods": "食物",
"Friday": "",
"GettingStarted": "",
"GroupBy": "分组",
"Hide_Food": "隐藏食物",
"Hide_Keyword": "隐藏关键词",

View File

@@ -50,6 +50,7 @@
"File": "",
"Files": "",
"Friday": "",
"GettingStarted": "",
"Hide_as_header": "隱藏為標題",
"History": "",
"HostedFreeVersion": "",

View File

@@ -109,7 +109,7 @@
<vue-draggable v-model="s.ingredients" group="ingredients" drag-class="drag-handle">
<v-list-item v-for="i in s.ingredients" border>
<v-icon size="small" class="drag-handle cursor-grab" icon="$dragHandle"></v-icon>
{{ i.amount }} {{ i.unit.name }} {{ i.food.name }}
{{ i.amount }} <span v-if="i.unit">{{ i.unit.name }}</span> <span v-if="i.food">{{ i.food.name }}</span>
<template #append>
<v-btn size="small" color="edit" @click="editingIngredient = i; dialog=true">
<v-icon icon="$edit"></v-icon>

View File

@@ -2,74 +2,38 @@
<v-container>
<horizontal-meal-plan-window></horizontal-meal-plan-window>
<!--TODO ideas for "start page": new recipes, meal plan, "last year/month/cooked long ago", high rated, random keyword -->
<!--TODO if nothing comes up for a category, hide the element, probably move fetch logic into component -->
<horizontal-recipe-scroller title="New Recipes" :skeletons="4" :recipes="new_recipes" icon="fas fa-calendar-alt"></horizontal-recipe-scroller>
<horizontal-recipe-scroller title="Top Rated" :skeletons="2" :recipes="high_rated_recipes" icon="fas fa-star"></horizontal-recipe-scroller>
<horizontal-recipe-scroller
:title="random_keyword.label"
:skeletons="4"
:recipes="random_keyword_recipes"
icon="fas fa-tags"
v-if="random_keyword.label"
></horizontal-recipe-scroller>
<v-card v-if="totalRecipes == 0" class="mt-5 mb-5">
<v-card-title><i class="fa-solid fa-eye-slash"></i> {{ $t('search_no_recipes') }}</v-card-title>
<v-card-text>
<v-btn-group divided>
<v-btn size="large" color="success" prepend-icon="$create" :to="{ name: 'ModelEditPage', params: {model: 'Recipe'} }">{{ $t('Create Recipe') }}</v-btn>
<v-btn size="large" color="primary" prepend-icon="fa-solid fa-globe" :to="{ name: 'RecipeImportPage', params: {} }">{{ $t('Import Recipe') }}</v-btn>
</v-btn-group>
</v-card-text>
</v-card>
<horizontal-recipe-scroller :skeletons="4" mode="recent"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="new"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="2" mode="rating"></horizontal-recipe-scroller>
<horizontal-recipe-scroller :skeletons="4" mode="keyword"></horizontal-recipe-scroller>
</v-container>
</template>
<script lang="ts">
import {defineComponent, ref} from "vue"
import {ApiApi, Keyword, Recipe, RecipeOverview} from "@/openapi"
import KeywordsComponent from "@/components/display/KeywordsBar.vue"
import RecipeCardComponent from "@/components/display/RecipeCard.vue"
import GlobalSearchDialog from "@/components/inputs/GlobalSearchDialog.vue"
import RecipeCard from "@/components/display/RecipeCard.vue"
<script setup lang="ts">
import {onMounted, ref} from "vue"
import {ApiApi} from "@/openapi"
import HorizontalRecipeScroller from "@/components/display/HorizontalRecipeWindow.vue"
import HorizontalMealPlanWindow from "@/components/display/HorizontalMealPlanWindow.vue"
import ModelSelect from "@/components/inputs/ModelSelect.vue"
export default defineComponent({
name: "StartPage",
components: {ModelSelect, HorizontalMealPlanWindow, HorizontalRecipeScroller, RecipeCard, GlobalSearchDialog, RecipeCardComponent, KeywordsComponent},
computed: {},
data() {
return {
recipes: [] as Recipe[],
items: Array.from({length: 50}, (k, v) => v + 1),
const totalRecipes = ref(-1)
new_recipes: [] as RecipeOverview[],
high_rated_recipes: [] as RecipeOverview[],
random_keyword: {} as Keyword,
random_keyword_recipes: [] as RecipeOverview[],
}
},
mounted() {
const api = new ApiApi()
onMounted(() => {
const api = new ApiApi()
api.apiRecipeList({_new: "true", pageSize: 16}).then((r) => {
if (r.results != undefined) {
// TODO openapi generator makes arrays nullable for some reason
this.new_recipes = r.results
}
})
api.apiRecipeList({rating: 4, pageSize: 16}).then((r) => {
if (r.results != undefined) {
this.high_rated_recipes = r.results
}
})
api.apiKeywordList({random: "true", limit: "1"}).then((r) => {
if (r.results != undefined && r.results.length > 0) {
this.random_keyword = r.results[0]
api.apiRecipeList({keywords: r.results[0].id}).then((r) => {
if (r.results != undefined) {
this.random_keyword_recipes = r.results
}
})
}
})
},
methods: {},
api.apiRecipeList({pageSize: 1}).then((r) => {
totalRecipes.value = r.count
})
})
</script>