mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-01-25 18:13:11 -05:00
feature/mobile-layout (#431)
* lazy load cards * shopping list recipe search bug * admin layout fluid * site loader * username support * mobile tabs * set username at signup * update user tests * patch bug on shopping list * public mealplan links * support link (I'm a monster) * icon only on mobile * padding Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-tabs v-model="tab">
|
||||
<v-tabs v-model="tab" show-arrows="">
|
||||
<v-tab>{{ $t("general.recipes") }}</v-tab>
|
||||
<v-tab>{{ $t("general.themes") }}</v-tab>
|
||||
<v-tab>{{ $t("general.settings") }}</v-tab>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
v-model="user.email"
|
||||
prepend-icon="mdi-email"
|
||||
validate-on-blur
|
||||
:label="$t('user.email')"
|
||||
:label="`${$t('user.email')} or ${$t('user.username')} `"
|
||||
type="email"
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
|
||||
@@ -21,8 +21,15 @@
|
||||
:prepend-icon="$globals.icons.user"
|
||||
validate-on-blur
|
||||
:rules="[existsRule]"
|
||||
:label="$t('signup.display-name')"
|
||||
type="email"
|
||||
:label="$t('user.full-name')"
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
v-model="user.username"
|
||||
light="light"
|
||||
:prepend-icon="$globals.icons.user"
|
||||
validate-on-blur
|
||||
:rules="[existsRule]"
|
||||
:label="$t('user.username')"
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
v-model="user.email"
|
||||
@@ -111,6 +118,7 @@ export default {
|
||||
|
||||
const userData = {
|
||||
fullName: this.user.name,
|
||||
username: this.user.username,
|
||||
email: this.user.email,
|
||||
group: "default",
|
||||
password: this.user.password,
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<v-icon left dark>
|
||||
mdi-clipboard-check
|
||||
</v-icon>
|
||||
{{ $t("general.coppied") }}!
|
||||
<slot> {{ $t("general.coppied") }}! </slot>
|
||||
</span>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<template>
|
||||
<div v-if="recipes">
|
||||
<v-app-bar color="transparent" flat class="mt-n1 rounded" v-if="!disableToolbar">
|
||||
<v-app-bar color="transparent" flat class="mt-n1 flex-sm-wrap rounded " v-if="!disableToolbar">
|
||||
<v-icon large left v-if="title">
|
||||
{{ displayTitleIcon }}
|
||||
</v-icon>
|
||||
<v-toolbar-title class="headline"> {{ title }} </v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn text @click="navigateRandom">
|
||||
<v-icon left>
|
||||
<v-btn :icon="$vuetify.breakpoint.xsOnly" text @click="navigateRandom">
|
||||
<v-icon :left="!$vuetify.breakpoint.xsOnly">
|
||||
mdi-dice-multiple
|
||||
</v-icon>
|
||||
{{ $t("general.random") }}
|
||||
{{ $vuetify.breakpoint.xsOnly ? null : $t("general.random") }}
|
||||
</v-btn>
|
||||
<v-menu offset-y left v-if="$listeners.sort">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn text v-bind="attrs" v-on="on" :loading="sortLoading">
|
||||
<v-icon left>
|
||||
<v-btn text :icon="$vuetify.breakpoint.xsOnly" v-bind="attrs" v-on="on" :loading="sortLoading">
|
||||
<v-icon :left="!$vuetify.breakpoint.xsOnly">
|
||||
mdi-sort
|
||||
</v-icon>
|
||||
{{ $t("general.sort") }}
|
||||
{{ $vuetify.breakpoint.xsOnly ? null : $t("general.sort") }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
@@ -58,14 +58,16 @@
|
||||
<div v-if="recipes" class="mt-2">
|
||||
<v-row v-if="!viewScale">
|
||||
<v-col :sm="6" :md="6" :lg="4" :xl="3" v-for="recipe in recipes.slice(0, cardLimit)" :key="recipe.name">
|
||||
<RecipeCard
|
||||
:name="recipe.name"
|
||||
:description="recipe.description"
|
||||
:slug="recipe.slug"
|
||||
:rating="recipe.rating"
|
||||
:image="recipe.image"
|
||||
:tags="recipe.tags"
|
||||
/>
|
||||
<v-lazy>
|
||||
<RecipeCard
|
||||
:name="recipe.name"
|
||||
:description="recipe.description"
|
||||
:slug="recipe.slug"
|
||||
:rating="recipe.rating"
|
||||
:image="recipe.image"
|
||||
:tags="recipe.tags"
|
||||
/>
|
||||
</v-lazy>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-else dense>
|
||||
@@ -78,33 +80,29 @@
|
||||
v-for="recipe in recipes.slice(0, cardLimit)"
|
||||
:key="recipe.name"
|
||||
>
|
||||
<MobileRecipeCard
|
||||
:name="recipe.name"
|
||||
:description="recipe.description"
|
||||
:slug="recipe.slug"
|
||||
:rating="recipe.rating"
|
||||
:image="recipe.image"
|
||||
:tags="recipe.tags"
|
||||
/>
|
||||
<v-lazy>
|
||||
<MobileRecipeCard
|
||||
:name="recipe.name"
|
||||
:description="recipe.description"
|
||||
:slug="recipe.slug"
|
||||
:rating="recipe.rating"
|
||||
:image="recipe.image"
|
||||
:tags="recipe.tags"
|
||||
/>
|
||||
</v-lazy>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
<div v-intersect="bumpList" class="d-flex">
|
||||
<v-expand-x-transition>
|
||||
<v-progress-circular
|
||||
v-if="loading"
|
||||
class="mx-auto mt-1"
|
||||
:size="50"
|
||||
:width="7"
|
||||
color="primary"
|
||||
indeterminate
|
||||
></v-progress-circular>
|
||||
<SiteLoader v-if="loading" :loading="loading" :size="150" />
|
||||
</v-expand-x-transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SiteLoader from "@/components/UI/SiteLoader";
|
||||
import RecipeCard from "../Recipe/RecipeCard";
|
||||
import MobileRecipeCard from "@/components/Recipe/MobileRecipeCard";
|
||||
import { utils } from "@/utils";
|
||||
@@ -114,6 +112,7 @@ export default {
|
||||
components: {
|
||||
RecipeCard,
|
||||
MobileRecipeCard,
|
||||
SiteLoader,
|
||||
},
|
||||
props: {
|
||||
disableToolbar: {
|
||||
@@ -139,7 +138,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
sortLoading: false,
|
||||
cardLimit: 30,
|
||||
cardLimit: 50,
|
||||
loading: false,
|
||||
EVENTS: {
|
||||
az: "az",
|
||||
|
||||
@@ -43,25 +43,31 @@
|
||||
</div>
|
||||
<router-link to="/search"> Advanced Search </router-link>
|
||||
</v-card-actions>
|
||||
<MobileRecipeCard
|
||||
v-for="(recipe, index) in results.slice(0, 10)"
|
||||
:tabindex="index"
|
||||
:key="index"
|
||||
class="ma-1 arrow-nav"
|
||||
:name="recipe.name"
|
||||
:description="recipe.description"
|
||||
:slug="recipe.slug"
|
||||
:rating="recipe.rating"
|
||||
:image="recipe.image"
|
||||
:route="true"
|
||||
v-on="$listeners.selected ? { selected: () => grabRecipe(recipe) } : {}"
|
||||
/>
|
||||
<v-card-actions v-if="loading">
|
||||
<SiteLoader :loading="loading" />
|
||||
</v-card-actions>
|
||||
<div v-else>
|
||||
<MobileRecipeCard
|
||||
v-for="(recipe, index) in results.slice(0, 10)"
|
||||
:tabindex="index"
|
||||
:key="index"
|
||||
class="ma-1 arrow-nav"
|
||||
:name="recipe.name"
|
||||
:description="recipe.description"
|
||||
:slug="recipe.slug"
|
||||
:rating="recipe.rating"
|
||||
:image="recipe.image"
|
||||
:route="true"
|
||||
v-on="$listeners.selected ? { selected: () => grabRecipe(recipe) } : {}"
|
||||
/>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SiteLoader from "@/components/UI/SiteLoader";
|
||||
const SELECTED_EVENT = "selected";
|
||||
import FuseSearchBar from "@/components/UI/Search/FuseSearchBar";
|
||||
import MobileRecipeCard from "@/components/Recipe/MobileRecipeCard";
|
||||
@@ -69,9 +75,11 @@ export default {
|
||||
components: {
|
||||
FuseSearchBar,
|
||||
MobileRecipeCard,
|
||||
SiteLoader,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
selectedIndex: -1,
|
||||
dialog: false,
|
||||
searchString: "",
|
||||
@@ -82,14 +90,17 @@ export default {
|
||||
$route() {
|
||||
this.dialog = false;
|
||||
},
|
||||
dialog(val) {
|
||||
async dialog(val) {
|
||||
if (!val) {
|
||||
this.resetSelected();
|
||||
} else if (this.allItems.length <= 0) {
|
||||
this.loading = true;
|
||||
await this.$store.dispatch("requestAllRecipes");
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch("requestAllRecipes");
|
||||
document.addEventListener("keydown", this.onUpDown);
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
||||
25
frontend/src/components/UI/SiteLoader.vue
Normal file
25
frontend/src/components/UI/SiteLoader.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<v-progress-circular class="mx-auto" :width="size / 20" :size="size" color="primary lighten-2" indeterminate>
|
||||
<div class="text-center">
|
||||
<v-icon :size="size / 2" color="primary lighten-2">
|
||||
{{ $globals.icons.primary }}
|
||||
</v-icon>
|
||||
<div>
|
||||
Loading Recipes
|
||||
</div>
|
||||
</div>
|
||||
</v-progress-circular>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
loading: {
|
||||
default: true,
|
||||
},
|
||||
size: {
|
||||
default: 200,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -18,12 +18,9 @@
|
||||
</div>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<SearchBar
|
||||
v-if="!isMobile"
|
||||
:show-results="true"
|
||||
@selected="navigateFromSearch"
|
||||
:max-width="isMobile ? '100%' : '450px'"
|
||||
/>
|
||||
<div v-if="!isMobile" style="width: 350px;">
|
||||
<SearchBar :show-results="true" @selected="navigateFromSearch" :max-width="isMobile ? '100%' : '450px'" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-btn icon @click="$refs.recipeSearch.open()">
|
||||
<v-icon> mdi-magnify </v-icon>
|
||||
|
||||
@@ -29,6 +29,14 @@
|
||||
|
||||
<!-- Version List Item -->
|
||||
<v-list nav dense class="fixedBottom" v-if="!isMain">
|
||||
<v-list-item href="https://github.com/sponsors/hay-kot" target="_target">
|
||||
<v-list-item-icon >
|
||||
<v-icon color="pink">
|
||||
mdi-heart
|
||||
</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title> Support </v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item to="/admin/about">
|
||||
<v-list-item-icon class="mr-3 pt-1">
|
||||
<v-icon :color="newVersionAvailable ? 'red--text' : ''">
|
||||
|
||||
Reference in New Issue
Block a user