1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-02 20:15:24 +02:00

Feature/infinite scroll (#719)

* feat(frontend):  lazy-load all recipes page

* feat(frontend):  enable runtime theme through env-variables

* docs(docs): 📝 update v1 changelog

* bump version

Co-authored-by: Hayden <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-10-03 14:07:18 -08:00 committed by GitHub
parent 568215cf70
commit c0dd07f9e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 548 additions and 281 deletions

View file

@ -57,7 +57,7 @@
</v-app-bar>
<div v-if="recipes" class="mt-2">
<v-row v-if="!viewScale">
<v-col v-for="recipe in recipes" :key="recipe.name" :sm="6" :md="6" :lg="4" :xl="3">
<v-col v-for="(recipe, index) in recipes" :key="recipe.slug + index" :sm="6" :md="6" :lg="4" :xl="3">
<v-lazy>
<RecipeCard
:name="recipe.name"
@ -72,7 +72,7 @@
</v-row>
<v-row v-else dense>
<v-col
v-for="recipe in recipes.slice(0, cardLimit)"
v-for="recipe in recipes"
:key="recipe.name"
cols="12"
:sm="singleColumn ? '12' : '12'"
@ -93,11 +93,6 @@
</v-col>
</v-row>
</div>
<div v-intersect="bumpList" class="d-flex mt-5">
<v-fade-transition>
<AppLoader v-if="loading" :loading="loading" />
</v-fade-transition>
</div>
</div>
</template>
@ -150,7 +145,6 @@ export default {
data() {
return {
sortLoading: false,
cardLimit: 50,
loading: false,
EVENTS: {
az: "az",
@ -180,21 +174,8 @@ export default {
return this.icon || this.$globals.icons.tags;
},
},
watch: {
recipes() {
this.bumpList();
},
},
methods: {
bumpList() {
const newCardLimit = Math.min(this.cardLimit + 20, this.effectiveHardLimit);
if (this.loading === false && newCardLimit > this.cardLimit) {
this.setLoader();
}
this.cardLimit = newCardLimit;
},
async setLoader() {
this.loading = true;
// eslint-disable-next-line promise/param-names
@ -202,7 +183,7 @@ export default {
this.loading = false;
},
navigateRandom() {
const recipe = this.utils.recipe.randomRecipe(this.recipes);
const recipe = this.recipes[Math.floor(Math.random() * this.recipes.length)];
this.$router.push(`/recipe/${recipe.slug}`);
},
sortRecipes(sortType) {

View file

@ -18,7 +18,6 @@ export default defineComponent({
},
setup() {
const [state, toggle] = useToggle();
console.log(state, toggle);
return {
state,
toggle,

View file

@ -57,6 +57,26 @@ export const useSorter = () => {
};
};
export const useLazyRecipes = function () {
const api = useApiSingleton();
const recipes = ref<Recipe[] | null>([]);
async function fetchMore(start: number, limit: number) {
const { data } = await api.recipes.getAll(start, limit);
if (data) {
data.forEach((recipe) => {
recipes.value?.push(recipe);
});
}
}
return {
recipes,
fetchMore,
};
};
export const useRecipes = (all = false, fetchRecipes = true) => {
const api = useApiSingleton();

View file

@ -30,7 +30,7 @@ export default {
css: [{ src: "~/assets/main.css" }, { src: "~/assets/style-overrides.scss" }],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: ["~/plugins/globals.ts"],
plugins: ["~/plugins/globals.ts", "~/plugins/theme.ts"],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
@ -205,6 +205,26 @@ export default {
axios: {
browserBaseURL: process.env.SUB_PATH || "",
},
themes: {
dark: {
primary: process.env.THEME_DARK_PRIMARY || "#E58325",
accent: process.env.THEME_DARK_ACCENT || "#007A99",
secondary: process.env.THEME_DARK_SECONDARY || "#973542",
success: process.env.THEME_DARK_SUCCESS || "#43A047",
info: process.env.THEME_DARK_INFO || "#1976d2",
warning: process.env.THEME_DARK_WARNING || "#FF6D00",
error: process.env.THEME_DARK_ERROR || "#EF5350",
},
light: {
primary: process.env.THEME_LIGHT_PRIMARY || "#007A99",
accent: process.env.THEME_LIGHT_ACCENT || "#007A99",
secondary: process.env.THEME_DARK_SECONDARY || "#973542",
success: process.env.THEME_DARK_SUCCESS || "#43A047",
info: process.env.THEME_LIGHT_INFO || "#1976d2",
warning: process.env.THEME_LIGHT_WARNING || "#FF6D00",
error: process.env.THEME_LIGHT_ERROR || "#EF5350",
},
},
},
privateRuntimeConfig: {},
@ -249,6 +269,8 @@ export default {
customProperties: true,
},
dark: false,
// Theme Config set at runtime by /plugins/theme.ts
// This config doesn't do anything.
themes: {
dark: {
primary: "#E58325",

View file

@ -3,23 +3,49 @@
<RecipeCardSection
:icon="$globals.icons.primary"
:title="$t('page.all-recipes')"
:recipes="allRecipes"
@sort="assignSorted"
:recipes="recipes"
></RecipeCardSection>
<v-card v-intersect="infiniteScroll"></v-card>
<v-fade-transition>
<AppLoader v-if="loading" :loading="loading" />
</v-fade-transition>
</v-container>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
import { useThrottleFn } from "@vueuse/core";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
import { useRecipes, allRecipes } from "~/composables/use-recipes";
import { useLazyRecipes } from "~/composables/use-recipes";
export default defineComponent({
components: { RecipeCardSection },
setup() {
const { assignSorted } = useRecipes(true);
const start = ref(1);
const limit = ref(30);
const increment = ref(30);
const ready = ref(false);
const loading = ref(false);
return { allRecipes, assignSorted };
const { recipes, fetchMore } = useLazyRecipes();
onMounted(async () => {
await fetchMore(start.value, limit.value);
ready.value = true;
});
const infiniteScroll = useThrottleFn(() => {
if (!ready.value) {
return;
}
loading.value = true;
start.value = limit.value + 1;
limit.value = limit.value + increment.value;
fetchMore(start.value, limit.value);
loading.value = false;
}, 500);
return { recipes, infiniteScroll, loading };
},
});
</script>

View file

@ -58,8 +58,7 @@
<RecipeCardSection
class="mt-n5"
:title-icon="$globals.icons.magnify"
:recipes="showRecipes"
:hard-limit="maxResults"
:recipes="showRecipes.slice(0, maxResults)"
@sort="assignFuzzy"
/>
</v-container>

View file

@ -1,214 +1,4 @@
import {
mdiAccount,
mdiSilverwareVariant,
mdiPlus,
mdiPlusCircle,
mdiDelete,
mdiContentSave,
mdiContentSaveEdit,
mdiSquareEditOutline,
mdiClose,
mdiTagMultipleOutline,
mdiBookOutline,
mdiAccountCog,
mdiAccountGroup,
mdiHome,
mdiMagnify,
mdiTranslate,
mdiClockTimeFourOutline,
mdiImport,
mdiEmail,
mdiLock,
mdiEye,
mdiDrag,
mdiEyeOff,
mdiCalendarMinus,
mdiCalendar,
mdiDiceMultiple,
mdiAlertCircle,
mdiDotsVertical,
mdiPrinter,
mdiShareVariant,
mdiHeart,
mdiHeartOutline,
mdiDotsHorizontal,
mdiCheckboxBlankOutline,
mdiCommentTextMultipleOutline,
mdiDownload,
mdiFile,
mdiFilePdfBox,
mdiFileImage,
mdiCodeJson,
mdiCog,
mdiSort,
mdiOrderAlphabeticalAscending,
mdiStar,
mdiNewBox,
mdiShuffleVariant,
mdiAlert,
mdiCheckboxMarkedCircle,
mdiInformation,
mdiBellAlert,
mdiRefreshCircle,
mdiMenu,
mdiWeatherSunny,
mdiWeatherNight,
mdiLink,
mdiRobot,
mdiLinkVariant,
mdiViewModule,
mdiViewDashboard,
mdiTools,
mdiCalendarWeek,
mdiCalendarToday,
mdiCalendarMultiselect,
mdiFormatListChecks,
mdiLogout,
mdiContentCopy,
mdiClipboardCheck,
mdiCloudUpload,
mdiDatabase,
mdiGithub,
mdiFolderOutline,
mdiApi,
mdiTestTube,
mdiDevTo,
mdiBackupRestore,
mdiNotificationClearAll,
mdiFood,
mdiWebhook,
mdiFilter,
mdiAccountPlusOutline,
mdiDesktopTowerMonitor,
mdiFormatColorFill,
mdiFormSelect,
mdiPageLayoutBody,
mdiCalendarWeekBegin,
mdiOpenInNew,
mdiCheck,
mdiBroom,
mdiCartCheck,
mdiArrowLeftBold,
mdiMinus,
mdiWindowClose,
mdiFolderZipOutline,
mdiFoodApple,
mdiBeakerOutline,
mdiArrowLeftBoldOutline,
mdiArrowRightBoldOutline,
} from "@mdi/js";
const icons = {
// Primary
primary: mdiSilverwareVariant,
// General
foods: mdiFoodApple,
units: mdiBeakerOutline,
alert: mdiAlert,
alertCircle: mdiAlertCircle,
api: mdiApi,
arrowLeftBold: mdiArrowLeftBold,
arrowUpDown: mdiDrag,
backupRestore: mdiBackupRestore,
bellAlert: mdiBellAlert,
broom: mdiBroom,
calendar: mdiCalendar,
calendarMinus: mdiCalendarMinus,
calendarMultiselect: mdiCalendarMultiselect,
calendarToday: mdiCalendarToday,
calendarWeek: mdiCalendarWeek,
calendarWeekBegin: mdiCalendarWeekBegin,
cartCheck: mdiCartCheck,
check: mdiCheck,
checkboxBlankOutline: mdiCheckboxBlankOutline,
checkboxMarkedCircle: mdiCheckboxMarkedCircle,
clipboardCheck: mdiClipboardCheck,
clockOutline: mdiClockTimeFourOutline,
codeBraces: mdiCodeJson,
codeJson: mdiCodeJson,
cog: mdiCog,
commentTextMultipleOutline: mdiCommentTextMultipleOutline,
contentCopy: mdiContentCopy,
database: mdiDatabase,
desktopTowerMonitor: mdiDesktopTowerMonitor,
devTo: mdiDevTo,
diceMultiple: mdiDiceMultiple,
dotsHorizontal: mdiDotsHorizontal,
dotsVertical: mdiDotsVertical,
download: mdiDownload,
email: mdiEmail,
externalLink: mdiLinkVariant,
eye: mdiEye,
eyeOff: mdiEyeOff,
file: mdiFile,
fileImage: mdiFileImage,
filePDF: mdiFilePdfBox,
filter: mdiFilter,
folderOutline: mdiFolderOutline,
food: mdiFood,
formatColorFill: mdiFormatColorFill,
formatListCheck: mdiFormatListChecks,
formSelect: mdiFormSelect,
github: mdiGithub,
heart: mdiHeart,
heartOutline: mdiHeartOutline,
home: mdiHome,
import: mdiImport,
information: mdiInformation,
link: mdiLink,
lock: mdiLock,
logout: mdiLogout,
menu: mdiMenu,
newBox: mdiNewBox,
notificationClearAll: mdiNotificationClearAll,
openInNew: mdiOpenInNew,
orderAlphabeticalAscending: mdiOrderAlphabeticalAscending,
pageLayoutBody: mdiPageLayoutBody,
printer: mdiPrinter,
refreshCircle: mdiRefreshCircle,
robot: mdiRobot,
search: mdiMagnify,
shareVariant: mdiShareVariant,
shuffleVariant: mdiShuffleVariant,
sort: mdiSort,
star: mdiStar,
testTube: mdiTestTube,
tools: mdiTools,
translate: mdiTranslate,
upload: mdiCloudUpload,
viewDashboard: mdiViewDashboard,
viewModule: mdiViewModule,
weatherNight: mdiWeatherNight,
weatherSunny: mdiWeatherSunny,
webhook: mdiWebhook,
windowClose: mdiWindowClose,
zip: mdiFolderZipOutline,
// Crud
backArrow: mdiArrowLeftBoldOutline,
createAlt: mdiPlus,
create: mdiPlusCircle,
delete: mdiDelete,
save: mdiContentSave,
update: mdiContentSaveEdit,
edit: mdiSquareEditOutline,
close: mdiClose,
minus: mdiMinus,
// Organization
tags: mdiTagMultipleOutline,
pages: mdiBookOutline,
// Admin
user: mdiAccount,
admin: mdiAccountCog,
group: mdiAccountGroup,
accountPlusOutline: mdiAccountPlusOutline,
forward: mdiArrowRightBoldOutline,
back: mdiArrowLeftBoldOutline,
};
import { icons } from "~/utils/icons";
// eslint-disable-next-line no-empty-pattern
export default ({}, inject: any) => {

View file

@ -0,0 +1,3 @@
export default ({ $vuetify, $config }: any) => {
$vuetify.theme.themes = $config.themes;
};

View file

@ -1,3 +1,22 @@
GLOBAL_MIDDLEWARE=null # null or 'auth'
GLOBAL_MIDDLEWARE=null
BASE_URL = ""
ALLOW_SIGNUP=true
ALLOW_SIGNUP=true
# =====================================
# Light Mode Config
THEME_LIGHT_PRIMARY=#007A99
THEME_LIGHT_ACCENT=#007A99
THEME_LIGHT_SECONDARY=#973542
THEME_LIGHT_SUCCESS=#43A047
THEME_LIGHT_INFO=#1976D2
THEME_LIGHT_WARNING=#FF6D00
THEME_LIGHT_ERROR=#EF5350
# =====================================
# Light Mode Config
THEME_DARK_PRIMARY=#E58325
THEME_DARK_ACCENT=#007A99
THEME_DARK_SECONDARY=#973542
THEME_DARK_SUCCESS=#43A047
THEME_DARK_INFO=#1976D2
THEME_DARK_WARNING=#FF6D00
THEME_DARK_ERROR=#EF5350

View file

@ -1,5 +1,10 @@
import Vue from "vue";
import "@nuxt/types";
import { Icon } from "~/utils/icons/icon-type";
interface Globals {
icons: Icon;
}
declare module "vue/types/vue" {
interface Vue {
@ -9,9 +14,9 @@ declare module "vue/types/vue" {
declare module "vue/types/options" {
interface ComponentOptions<V extends Vue> {
$globals?: any;
$globals?: Globals;
}
interface ComponentOptions<V extends UseContextReturn> {
$globals?: any;
$globals?: Globals;
}
}

View file

@ -0,0 +1,111 @@
export interface Icon {
// Primary
primary: String;
// General
foods: String;
units: String;
alert: String;
alertCircle: String;
api: String;
arrowLeftBold: String;
arrowUpDown: String;
backupRestore: String;
bellAlert: String;
broom: String;
calendar: String;
calendarMinus: String;
calendarMultiselect: String;
calendarToday: String;
calendarWeek: String;
calendarWeekBegin: String;
cartCheck: String;
check: String;
checkboxBlankOutline: String;
checkboxMarkedCircle: String;
clipboardCheck: String;
clockOutline: String;
codeBraces: String;
codeJson: String;
cog: String;
commentTextMultipleOutline: String;
contentCopy: String;
database: String;
desktopTowerMonitor: String;
devTo: String;
diceMultiple: String;
dotsHorizontal: String;
dotsVertical: String;
download: String;
email: String;
externalLink: String;
eye: String;
eyeOff: String;
file: String;
fileImage: String;
filePDF: String;
filter: String;
folderOutline: String;
food: String;
formatColorFill: String;
formatListCheck: String;
formSelect: String;
github: String;
heart: String;
heartOutline: String;
home: String;
import: String;
information: String;
link: String;
lock: String;
logout: String;
menu: String;
newBox: String;
notificationClearAll: String;
openInNew: String;
orderAlphabeticalAscending: String;
pageLayoutBody: String;
printer: String;
refreshCircle: String;
robot: String;
search: String;
shareVariant: String;
shuffleVariant: String;
sort: String;
star: String;
testTube: String;
tools: String;
translate: String;
upload: String;
viewDashboard: String;
viewModule: String;
weatherNight: String;
weatherSunny: String;
webhook: String;
windowClose: String;
zip: String;
// Crud
backArrow: String;
createAlt: String;
create: String;
delete: String;
save: String;
update: String;
edit: String;
close: String;
minus: String;
// Organization
tags: String;
pages: String;
// Admin
user: String;
admin: String;
group: String;
accountPlusOutline: String;
forward: String;
back: String;
}

View file

@ -0,0 +1,211 @@
import {
mdiAccount,
mdiSilverwareVariant,
mdiPlus,
mdiPlusCircle,
mdiDelete,
mdiContentSave,
mdiContentSaveEdit,
mdiSquareEditOutline,
mdiClose,
mdiTagMultipleOutline,
mdiBookOutline,
mdiAccountCog,
mdiAccountGroup,
mdiHome,
mdiMagnify,
mdiTranslate,
mdiClockTimeFourOutline,
mdiImport,
mdiEmail,
mdiLock,
mdiEye,
mdiDrag,
mdiEyeOff,
mdiCalendarMinus,
mdiCalendar,
mdiDiceMultiple,
mdiAlertCircle,
mdiDotsVertical,
mdiPrinter,
mdiShareVariant,
mdiHeart,
mdiHeartOutline,
mdiDotsHorizontal,
mdiCheckboxBlankOutline,
mdiCommentTextMultipleOutline,
mdiDownload,
mdiFile,
mdiFilePdfBox,
mdiFileImage,
mdiCodeJson,
mdiCog,
mdiSort,
mdiOrderAlphabeticalAscending,
mdiStar,
mdiNewBox,
mdiShuffleVariant,
mdiAlert,
mdiCheckboxMarkedCircle,
mdiInformation,
mdiBellAlert,
mdiRefreshCircle,
mdiMenu,
mdiWeatherSunny,
mdiWeatherNight,
mdiLink,
mdiRobot,
mdiLinkVariant,
mdiViewModule,
mdiViewDashboard,
mdiTools,
mdiCalendarWeek,
mdiCalendarToday,
mdiCalendarMultiselect,
mdiFormatListChecks,
mdiLogout,
mdiContentCopy,
mdiClipboardCheck,
mdiCloudUpload,
mdiDatabase,
mdiGithub,
mdiFolderOutline,
mdiApi,
mdiTestTube,
mdiDevTo,
mdiBackupRestore,
mdiNotificationClearAll,
mdiFood,
mdiWebhook,
mdiFilter,
mdiAccountPlusOutline,
mdiDesktopTowerMonitor,
mdiFormatColorFill,
mdiFormSelect,
mdiPageLayoutBody,
mdiCalendarWeekBegin,
mdiOpenInNew,
mdiCheck,
mdiBroom,
mdiCartCheck,
mdiArrowLeftBold,
mdiMinus,
mdiWindowClose,
mdiFolderZipOutline,
mdiFoodApple,
mdiBeakerOutline,
mdiArrowLeftBoldOutline,
mdiArrowRightBoldOutline,
} from "@mdi/js";
export const icons = {
// Primary
primary: mdiSilverwareVariant,
// General
foods: mdiFoodApple,
units: mdiBeakerOutline,
alert: mdiAlert,
alertCircle: mdiAlertCircle,
api: mdiApi,
arrowLeftBold: mdiArrowLeftBold,
arrowUpDown: mdiDrag,
backupRestore: mdiBackupRestore,
bellAlert: mdiBellAlert,
broom: mdiBroom,
calendar: mdiCalendar,
calendarMinus: mdiCalendarMinus,
calendarMultiselect: mdiCalendarMultiselect,
calendarToday: mdiCalendarToday,
calendarWeek: mdiCalendarWeek,
calendarWeekBegin: mdiCalendarWeekBegin,
cartCheck: mdiCartCheck,
check: mdiCheck,
checkboxBlankOutline: mdiCheckboxBlankOutline,
checkboxMarkedCircle: mdiCheckboxMarkedCircle,
clipboardCheck: mdiClipboardCheck,
clockOutline: mdiClockTimeFourOutline,
codeBraces: mdiCodeJson,
codeJson: mdiCodeJson,
cog: mdiCog,
commentTextMultipleOutline: mdiCommentTextMultipleOutline,
contentCopy: mdiContentCopy,
database: mdiDatabase,
desktopTowerMonitor: mdiDesktopTowerMonitor,
devTo: mdiDevTo,
diceMultiple: mdiDiceMultiple,
dotsHorizontal: mdiDotsHorizontal,
dotsVertical: mdiDotsVertical,
download: mdiDownload,
email: mdiEmail,
externalLink: mdiLinkVariant,
eye: mdiEye,
eyeOff: mdiEyeOff,
file: mdiFile,
fileImage: mdiFileImage,
filePDF: mdiFilePdfBox,
filter: mdiFilter,
folderOutline: mdiFolderOutline,
food: mdiFood,
formatColorFill: mdiFormatColorFill,
formatListCheck: mdiFormatListChecks,
formSelect: mdiFormSelect,
github: mdiGithub,
heart: mdiHeart,
heartOutline: mdiHeartOutline,
home: mdiHome,
import: mdiImport,
information: mdiInformation,
link: mdiLink,
lock: mdiLock,
logout: mdiLogout,
menu: mdiMenu,
newBox: mdiNewBox,
notificationClearAll: mdiNotificationClearAll,
openInNew: mdiOpenInNew,
orderAlphabeticalAscending: mdiOrderAlphabeticalAscending,
pageLayoutBody: mdiPageLayoutBody,
printer: mdiPrinter,
refreshCircle: mdiRefreshCircle,
robot: mdiRobot,
search: mdiMagnify,
shareVariant: mdiShareVariant,
shuffleVariant: mdiShuffleVariant,
sort: mdiSort,
star: mdiStar,
testTube: mdiTestTube,
tools: mdiTools,
translate: mdiTranslate,
upload: mdiCloudUpload,
viewDashboard: mdiViewDashboard,
viewModule: mdiViewModule,
weatherNight: mdiWeatherNight,
weatherSunny: mdiWeatherSunny,
webhook: mdiWebhook,
windowClose: mdiWindowClose,
zip: mdiFolderZipOutline,
// Crud
backArrow: mdiArrowLeftBoldOutline,
createAlt: mdiPlus,
create: mdiPlusCircle,
delete: mdiDelete,
save: mdiContentSave,
update: mdiContentSaveEdit,
edit: mdiSquareEditOutline,
close: mdiClose,
minus: mdiMinus,
// Organization
tags: mdiTagMultipleOutline,
pages: mdiBookOutline,
// Admin
user: mdiAccount,
admin: mdiAccountCog,
group: mdiAccountGroup,
accountPlusOutline: mdiAccountPlusOutline,
forward: mdiArrowRightBoldOutline,
back: mdiArrowLeftBoldOutline,
};

View file

@ -0,0 +1 @@
export { icons } from "./icons";