Get Recipes Route Rewrite (#339)

* deprecate old route

* auto-gen

* recipe card infinite scroll

* fix datatable

* set hard-limit option

* add loader

* set scroll on navigation

* add auto-import

* fix slow initial load

* remove console.logs

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-04-22 22:13:55 -08:00 committed by GitHub
parent 80f8806604
commit 8e4b951ecc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 128 additions and 73 deletions

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,6 @@ import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils"; import { apiReq } from "./api-utils";
import { store } from "../store"; import { store } from "../store";
import { router } from "../main"; import { router } from "../main";
import qs from "qs";
const prefix = baseURL + "recipes/"; const prefix = baseURL + "recipes/";
@ -78,22 +77,10 @@ export const recipeAPI = {
router.push(`/`); router.push(`/`);
}, },
async allByKeys(recipeKeys, num = 9999) { async allSummary(start = 0, limit = 9999) {
const response = await apiReq.get(recipeURLs.allRecipes, { const response = await apiReq.get(recipeURLs.summary, {
params: { params: { start: start, limit: limit },
keys: recipeKeys,
num: num,
},
paramsSerializer: params => {
return qs.stringify(params, { arrayFormat: "repeat" });
},
}); });
return response.data;
},
async allSummary() {
const response = await apiReq.get(recipeURLs.summary);
return response.data; return response.data;
}, },

View File

@ -45,7 +45,7 @@
</template> </template>
<script> <script>
import DataTable from "@/components/ImportSummaryDialog"; import DataTable from "@/components/ImportSummaryDialog/DataTable";
export default { export default {
components: { components: {
DataTable, DataTable,

View File

@ -116,6 +116,7 @@ export default {
}, },
async mounted() { async mounted() {
await this.$store.dispatch("requestCurrentGroup"); await this.$store.dispatch("requestCurrentGroup");
await this.$store.dispatch("requestAllRecipes");
await this.buildMealStore(); await this.buildMealStore();
}, },
@ -151,6 +152,9 @@ export default {
const recipes = this.items.filter(x => !this.usedRecipes.includes(x)); const recipes = this.items.filter(x => !this.usedRecipes.includes(x));
return recipes.length > 0 ? recipes : this.items; return recipes.length > 0 ? recipes : this.items;
}, },
allRecipes() {
return this.$store.getters.getRecentRecipes;
},
}, },
methods: { methods: {
@ -159,15 +163,7 @@ export default {
this.items = await api.recipes.getAllByCategory(categories); this.items = await api.recipes.getAllByCategory(categories);
if (this.items.length === 0) { if (this.items.length === 0) {
const keys = [ this.items = this.allRecipes;
"name",
"slug",
"image",
"description",
"dateAdded",
"rating",
];
this.items = await api.recipes.allByKeys(keys);
} }
}, },
getRandom(list) { getRandom(list) {

View File

@ -9,7 +9,7 @@
> >
<ConfirmationDialog <ConfirmationDialog
:title="$t('recipe.delete-recipe')" :title="$t('recipe.delete-recipe')"
:message="$t('recipe.delete-ConfirmationDialog')" :message="$t('recipe.delete-confirmation')"
color="error" color="error"
icon="mdi-alert-circle" icon="mdi-alert-circle"
ref="deleteRecipieConfirm" ref="deleteRecipieConfirm"

View File

@ -33,11 +33,6 @@ export default {
totalTime: String, totalTime: String,
performTime: String, performTime: String,
}, },
watch: {
showCards(val) {
console.log(val);
},
},
computed: { computed: {
showCards() { showCards() {
return [this.prepTime, this.totalTime, this.performTime].some( return [this.prepTime, this.totalTime, this.performTime].some(

View File

@ -78,6 +78,16 @@
</v-col> </v-col>
</v-row> </v-row>
</div> </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>
</div>
</div> </div>
</template> </template>
@ -96,10 +106,16 @@ export default {
title: { title: {
default: null, default: null,
}, },
recipes: Array, hardLimit: {
cardLimit: { default: 99999,
default: 999,
}, },
recipes: Array,
},
data() {
return {
cardLimit: 30,
loading: false,
};
}, },
computed: { computed: {
viewScale() { viewScale() {
@ -113,6 +129,22 @@ export default {
} }
}, },
}, },
methods: {
bumpList() {
const newCardLimit = Math.min(this.cardLimit + 20, this.hardLimit);
if (this.loading === false && newCardLimit > this.cardLimit) {
this.setLoader();
}
this.cardLimit = newCardLimit;
},
async setLoader() {
this.loading = true;
await new Promise(r => setTimeout(r, 3000));
this.loading = false;
},
},
}; };
</script> </script>

View File

@ -89,7 +89,6 @@ export default {
searchSlug: "", searchSlug: "",
search: "", search: "",
menuModel: false, menuModel: false,
data: [],
result: [], result: [],
fuseResults: [], fuseResults: [],
isDark: false, isDark: false,
@ -107,9 +106,12 @@ export default {
}, },
mounted() { mounted() {
this.isDark = this.$store.getters.getIsDark; this.isDark = this.$store.getters.getIsDark;
this.data = this.$store.getters.getRecentRecipes; this.$store.dispatch("requestAllRecipes");
}, },
computed: { computed: {
data() {
return this.$store.getters.getRecentRecipes;
},
autoResults() { autoResults() {
return this.fuseResults.length > 1 ? this.fuseResults : this.results; return this.fuseResults.length > 1 ? this.fuseResults : this.results;
}, },

View File

@ -135,7 +135,6 @@ export default {
this.groupSettings.webhookUrls.splice(index, 1); this.groupSettings.webhookUrls.splice(index, 1);
}, },
async saveGroupSettings() { async saveGroupSettings() {
console.log(this.groupSettings);
await api.groups.update(this.groupSettings); await api.groups.update(this.groupSettings);
await this.$store.dispatch("requestCurrentGroup"); await this.$store.dispatch("requestCurrentGroup");
this.getSiteSettings(); this.getSiteSettings();

View File

@ -42,7 +42,7 @@
</template> </template>
<script> <script>
import DataTable from "@/components/ImportSummaryDialog"; import DataTable from "@/components/ImportSummaryDialog/DataTable";
export default { export default {
components: { components: {
DataTable, DataTable,

View File

@ -223,8 +223,7 @@ export default {
this.settings.categories.splice(index, 1); this.settings.categories.splice(index, 1);
}, },
async saveSettings() { async saveSettings() {
const newSettings = await api.siteSettings.update(this.settings); await api.siteSettings.update(this.settings);
console.log("New Settings", newSettings);
this.getOptions(); this.getOptions();
}, },
}, },

View File

@ -5,7 +5,7 @@
v-if="siteSettings.showRecent" v-if="siteSettings.showRecent"
:title="$t('page.recent')" :title="$t('page.recent')"
:recipes="recentRecipes" :recipes="recentRecipes"
:card-limit="siteSettings.cardsPerSection" :hard-limit="siteSettings.cardsPerSection"
/> />
<CardSection <CardSection
:sortable="true" :sortable="true"
@ -13,7 +13,7 @@
:key="section.name + section.position" :key="section.name + section.position"
:title="section.name" :title="section.name"
:recipes="section.recipes" :recipes="section.recipes"
:card-limit="siteSettings.cardsPerSection" :hard-limit="siteSettings.cardsPerSection"
@sort="sortAZ(index)" @sort="sortAZ(index)"
@sort-recent="sortRecent(index)" @sort-recent="sortRecent(index)"
/> />

View File

@ -5,7 +5,6 @@
:sortable="true" :sortable="true"
:title="$t('page.all-recipes')" :title="$t('page.all-recipes')"
:recipes="allRecipes" :recipes="allRecipes"
:card-limit="9999"
@sort="sortAZ" @sort="sortAZ"
@sort-recent="sortRecent" @sort-recent="sortRecent"
/> />
@ -23,6 +22,9 @@ export default {
data() { data() {
return {}; return {};
}, },
mounted() {
this.$store.dispatch("requestAllRecipes");
},
computed: { computed: {
allRecipes() { allRecipes() {
return this.$store.getters.getRecentRecipes; return this.$store.getters.getRecentRecipes;

View File

@ -26,7 +26,9 @@
<v-row dense class="mt-0 flex-row align-center justify-space-around"> <v-row dense class="mt-0 flex-row align-center justify-space-around">
<v-col> <v-col>
<h3 class="pl-2 text-center headline">{{$t('search.category-filter')}}</h3> <h3 class="pl-2 text-center headline">
{{ $t("search.category-filter") }}
</h3>
<FilterSelector class="mb-1" @update="updateCatParams" /> <FilterSelector class="mb-1" @update="updateCatParams" />
<CategoryTagSelector <CategoryTagSelector
:solo="true" :solo="true"
@ -36,7 +38,9 @@
/> />
</v-col> </v-col>
<v-col> <v-col>
<h3 class="pl-2 text-center headline">{{$t('search.tag-filter')}}</h3> <h3 class="pl-2 text-center headline">
{{ $t("search.tag-filter") }}
</h3>
<FilterSelector class="mb-1" @update="updateTagParams" /> <FilterSelector class="mb-1" @update="updateTagParams" />
<CategoryTagSelector <CategoryTagSelector
@ -113,6 +117,9 @@ export default {
}, },
}; };
}, },
mounted() {
this.$store.dispatch("requestAllRecipes");
},
computed: { computed: {
allRecipes() { allRecipes() {
return this.$store.getters.getRecentRecipes; return this.$store.getters.getRecentRecipes;

View File

@ -24,6 +24,9 @@ export const routes = [
const router = new VueRouter({ const router = new VueRouter({
routes, routes,
mode: process.env.NODE_ENV === "production" ? "history" : "hash", mode: process.env.NODE_ENV === "production" ? "history" : "hash",
scrollBehavior() {
return { x: 0, y: 0 };
},
}); });
const DEFAULT_TITLE = "Mealie"; const DEFAULT_TITLE = "Mealie";

View File

@ -53,19 +53,18 @@ const store = new Vuex.Store({
}, },
actions: { actions: {
async requestRecentRecipes() { async requestRecentRecipes({ getters }) {
// const keys = [ const payload = await api.recipes.allSummary(0, 30);
// "name", const recent = getters.getRecentRecipes;
// "slug", if (recent.length >= 30) return;
// "image",
// "description",
// "dateAdded",
// "rating",
// ];
const payload = await api.recipes.allSummary();
this.commit("setRecentRecipes", payload); this.commit("setRecentRecipes", payload);
}, },
async requestAllRecipes({ getters }) {
const recent = getters.getRecentRecipes;
const start = recent.length + 1;
const payload = await api.recipes.allSummary(start, 9999);
this.commit("setRecentRecipes", [...recent, ...payload]);
},
async requestCategories({ commit }) { async requestCategories({ commit }) {
const categories = await api.categories.getAll(); const categories = await api.categories.getAll();
commit("setAllCategories", categories); commit("setAllCategories", categories);
@ -74,7 +73,6 @@ const store = new Vuex.Store({
const tags = await api.tags.getAll(); const tags = await api.tags.getAll();
commit("setAllTags", tags); commit("setAllTags", tags);
}, },
async requestAppInfo({ commit }) { async requestAppInfo({ commit }) {
const response = await api.meta.getAppInfo(); const response = await api.meta.getAppInfo();
commit("setAppInfo", response); commit("setAppInfo", response);

View File

@ -16,10 +16,12 @@ class BaseDocument:
self.schema: BaseModel self.schema: BaseModel
# TODO: Improve Get All Query Functionality # TODO: Improve Get All Query Functionality
def get_all(self, session: Session, limit: int = None, order_by: str = None, override_schema=None) -> List[dict]: def get_all(
self, session: Session, limit: int = None, order_by: str = None, start=0, end=9999, override_schema=None
) -> List[dict]:
eff_schema = override_schema or self.schema eff_schema = override_schema or self.schema
return [eff_schema.from_orm(x) for x in session.query(self.sql_model).limit(limit).all()] return [eff_schema.from_orm(x) for x in session.query(self.sql_model).offset(start).limit(limit).all()]
def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]: def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]:
"""Queries the database for the selected model. Restricts return responses to the """Queries the database for the selected model. Restricts return responses to the

View File

@ -12,16 +12,26 @@ router = APIRouter(tags=["Query All Recipes"])
@router.get("/api/recipes/summary") @router.get("/api/recipes/summary")
async def get_recipe_summary( async def get_recipe_summary(
skip=0, start=0,
end=9999, limit=9999,
session: Session = Depends(generate_session), session: Session = Depends(generate_session),
): ):
""" Returns the summary data for recipes in the database """ """
Returns key the recipe summary data for recipes in the database. You can perform
slice operations to set the skip/end amounts for recipes. All recipes are sorted by the added date.
return db.recipes.get_all(session, limit=end, override_schema=RecipeSummary) **Query Parameters**
- skip: The database entry to start at. (0 Indexed)
- end: The number of entries to return.
skip=2, end=10 will return entries
"""
return db.recipes.get_all(session, limit=limit, start=start, override_schema=RecipeSummary)
@router.get("/api/recipes") @router.get("/api/recipes", deprecated=True)
def get_all_recipes( def get_all_recipes(
keys: Optional[List[str]] = Query(...), keys: Optional[List[str]] = Query(...),
num: Optional[int] = 100, num: Optional[int] = 100,
@ -54,7 +64,7 @@ def get_all_recipes(
return db.recipes.get_all_limit_columns(session, keys, limit=num) return db.recipes.get_all_limit_columns(session, keys, limit=num)
@router.post("/api/recipes") @router.post("/api/recipes", deprecated=True)
def get_all_recipes_post(body: AllRecipeRequest, session: Session = Depends(generate_session)): def get_all_recipes_post(body: AllRecipeRequest, session: Session = Depends(generate_session)):
""" """
Returns key data for all recipes based off the body data provided. Returns key data for all recipes based off the body data provided.

View File

@ -1,11 +1,34 @@
# Make .env in this folder if needed. # The Default Group Assigned to All Users
DEFAULT_GROUP=Home DEFAULT_GROUP=Home
PRODUCTION=False
API_PORT=9000 # The Default Credentials for the Super User
API_DOCS=True
DB_TYPE=sqlite
DEFAULT_EMAIL=changeme@email.com DEFAULT_EMAIL=changeme@email.com
DEFAULT_PASSWORD=MyPassword DEFAULT_PASSWORD=MyPassword
TOKEN_TIME=2
# Determines Production Mode, This will set the directory path to use for data storage
PRODUCTION=False
# API Port for Pythong Server
API_PORT=9000
# Exposes /docs and /redoc on the server
API_DOCS=True
# Sets the Database type to use. Currently the only supported options is 'sqlite'
DB_TYPE=sqlite
# Sets the token expiration time in hours.
TOKEN_TIME=24
# NOT USED
SFTP_USERNAME=None SFTP_USERNAME=None
SFTP_PASSWORD=None SFTP_PASSWORD=None
# NOT USED Auto Import Options
AUTO_IMPORT=True
AUTO_IMPORT_RECIPES=True
AUTO_IMPORT_SETTINGS=True
AUTO_IMPORT_PAGES=True
AUTO_IMPORT_THEMES=True
AUTO_IMPORT_USERS=True
AUTO_IMPORT_GROUPS=True