consolidate sidebar and app bar

This commit is contained in:
hay-kot 2021-04-24 14:35:15 -08:00
commit 66d97b7e48
13 changed files with 294 additions and 192 deletions

View file

@ -8,10 +8,6 @@
<b> This is a Demo</b> | Username: changeme@email.com | Password: demo
</div></v-banner
>
<v-slide-x-reverse-transition>
<TheRecipeFab v-if="loggedIn" />
</v-slide-x-reverse-transition>
<router-view></router-view>
</v-main>
<FlashMessage :position="'right bottom'"></FlashMessage>
@ -20,7 +16,6 @@
<script>
import TheAppBar from "@/components/UI/TheAppBar";
import TheRecipeFab from "@/components/UI/TheRecipeFab";
import Vuetify from "./plugins/vuetify";
import { user } from "@/mixins/user";
@ -29,7 +24,6 @@ export default {
components: {
TheAppBar,
TheRecipeFab,
},
mixins: [user],
@ -59,6 +53,7 @@ export default {
this.darkModeSystemCheck();
this.darkModeAddEventListener();
this.$store.dispatch("requestAppInfo");
this.$store.dispatch("requestCustomPages");
},
methods: {

View file

@ -1,110 +0,0 @@
<template>
<div>
<v-btn
class="mt-9 ml-n1"
fixed
left
bottom
fab
small
color="primary"
@click="showSidebar = !showSidebar"
>
<v-icon>mdi-tag</v-icon></v-btn
>
<v-navigation-drawer
:value="mobile ? showSidebar : true"
v-model="showSidebar"
width="175px"
clipped
app
>
<v-list nav dense>
<v-list-item v-for="nav in links" :key="nav.title" link :to="nav.to">
<v-list-item-icon>
<v-icon>{{ nav.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ nav.title | titleCase }}</v-list-item-title>
</v-list-item>
</v-list>
</v-navigation-drawer>
</div>
</template>
<script>
import { api } from "@/api";
export default {
data() {
return {
showSidebar: false,
mobile: false,
links: [],
baseLinks: [
{
icon: "mdi-home",
to: "/",
title: this.$t("page.home-page"),
},
{
icon: "mdi-view-module",
to: "/recipes/all",
title: this.$t("page.all-recipes"),
},
{
icon: "mdi-magnify",
to: "/search",
title: this.$t('search.search'),
},
],
};
},
mounted() {
this.buildSidebar();
this.mobile = this.viewScale();
this.showSidebar = !this.viewScale();
},
methods: {
async buildSidebar() {
this.links = [];
this.links.push(...this.baseLinks);
const pages = await api.siteSettings.getPages();
if(pages.length > 0) {
pages.sort((a, b) => a.position - b.position);
pages.forEach(async element => {
this.links.push({
title: element.name,
to: `/pages/${element.slug}`,
icon: "mdi-tag",
});
});
}
else {
const categories = await api.categories.getAll();
categories.forEach(async element => {
this.links.push({
title: element.name,
to: `/recipes/category/${element.slug}`,
icon: "mdi-tag",
});
});
}
},
viewScale() {
switch (this.$vuetify.breakpoint.name) {
case "xs":
return true;
case "sm":
return true;
default:
return false;
}
},
},
};
</script>
<style>
</style>

View file

@ -1,14 +1,18 @@
<template>
<div>
<TheSidebar ref="theSidebar" />
<v-app-bar
v-if="!isMobile"
clipped-left
dense
app
color="primary"
dark
class="d-print-none"
:bottom="isMobile"
>
<v-btn icon @click="openSidebar">
<v-icon> mdi-menu </v-icon>
</v-btn>
<router-link v-if="!(isMobile && search)" to="/">
<v-btn icon>
<v-icon size="40"> mdi-silverware-variant </v-icon>
@ -16,8 +20,8 @@
</router-link>
<div v-if="!isMobile" btn class="pl-2">
<v-toolbar-title style="cursor: pointer" @click="$router.push('/')"
>Mealie
<v-toolbar-title style="cursor: pointer" @click="$router.push('/')">
Mealie
</v-toolbar-title>
</div>
@ -36,60 +40,37 @@
</v-btn>
<TheSiteMenu />
<v-slide-x-reverse-transition>
<TheRecipeFab v-if="loggedIn && isMobile" />
</v-slide-x-reverse-transition>
</v-app-bar>
<v-app-bar
v-else
bottom
clipped-left
dense
app
color="primary"
dark
class="d-print-none"
>
<router-link to="/">
<v-btn icon>
<v-icon size="40"> mdi-silverware-variant </v-icon>
</v-btn>
</router-link>
<div v-if="!isMobile" btn class="pl-2">
<v-toolbar-title style="cursor: pointer" @click="$router.push('/')"
>Mealie
</v-toolbar-title>
</div>
<v-spacer></v-spacer>
<v-expand-x-transition>
<SearchDialog ref="mainSearchDialog" />
</v-expand-x-transition>
<v-btn icon @click="$refs.mainSearchDialog.open()">
<v-icon>mdi-magnify</v-icon>
</v-btn>
<TheSiteMenu />
</v-app-bar>
<v-slide-x-reverse-transition>
<TheRecipeFab v-if="loggedIn && !isMobile" :absolute="true" />
</v-slide-x-reverse-transition>
</div>
</template>
<script>
import TheSiteMenu from "@/components/UI/TheSiteMenu";
import SearchBar from "@/components/UI/Search/SearchBar";
import SearchDialog from "@/components/UI/Search/SearchDialog";
import TheRecipeFab from "@/components/UI/TheRecipeFab";
import TheSidebar from "@/components/UI/TheSidebar";
import { user } from "@/mixins/user";
export default {
name: "AppBar",
mixins: [user],
components: {
TheRecipeFab,
TheSidebar,
TheSiteMenu,
SearchBar,
SearchDialog,
},
data() {
return {
search: false,
isMobile: false,
showSidebar: false,
};
},
watch: {
@ -98,17 +79,24 @@ export default {
},
},
computed: {
// isMobile() {
// return this.$vuetify.breakpoint.name === "xs";
// },
isMobile() {
return this.$vuetify.breakpoint.name === "xs";
},
},
methods: {
navigateFromSearch(slug) {
this.$router.push(`/recipe/${slug}`);
},
openSidebar() {
this.$refs.theSidebar.forceOpen();
},
},
};
</script>
<style lang="scss" scoped>
<style scoped>
fab-position {
position: absolute;
bottom: 0;
}
</style>

View file

@ -54,16 +54,28 @@
</v-form>
</v-card>
</v-dialog>
<v-speed-dial v-model="fab" fixed right bottom open-on-hover>
<v-speed-dial
v-model="fab"
open-on-hover
:fixed="absolute"
:bottom="absolute"
:right="absolute"
>
<template v-slot:activator>
<v-btn v-model="fab" color="accent" dark fab>
<v-btn
v-model="fab"
:color="absolute ? 'accent' : 'white'"
dark
:icon="!absolute"
:fab="absolute"
>
<v-icon> mdi-plus </v-icon>
</v-btn>
</template>
<v-btn fab dark small color="primary" @click="addRecipe = true">
<v-icon>mdi-link</v-icon>
</v-btn>
<v-btn fab dark small color="accent" @click="navCreate">
<v-btn fab dark small color="accent" @click="$router.push('/new')">
<v-icon>mdi-square-edit-outline</v-icon>
</v-btn>
</v-speed-dial>
@ -74,6 +86,11 @@
import { api } from "@/api";
export default {
props: {
absolute: {
default: false,
},
},
data() {
return {
error: false,
@ -102,10 +119,6 @@ export default {
}
},
navCreate() {
this.$router.push("/new");
},
reset() {
this.fab = false;
this.error = false;

View file

@ -0,0 +1,229 @@
<template>
<div>
<v-navigation-drawer
:value="mobile ? showSidebar : true"
v-model="showSidebar"
width="175px"
clipped
app
>
<template v-slot:prepend>
<v-list-item two-line v-if="isLoggedIn">
<v-list-item-avatar color="accent" class="white--text">
<img
:src="userProfileImage"
v-if="!hideImage"
@error="hideImage = true"
/>
<div v-else>
{{ initials }}
</div>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title> {{ user.fullName }}</v-list-item-title>
<v-list-item-subtitle>
{{ user.admin ? "Admin" : "User" }}</v-list-item-subtitle
>
</v-list-item-content>
</v-list-item>
</template>
<v-divider></v-divider>
<v-list nav dense>
<v-list-item
v-for="nav in effectiveMenu"
:key="nav.title"
link
:to="nav.to"
>
<v-list-item-icon>
<v-icon>{{ nav.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ nav.title }}</v-list-item-title>
</v-list-item>
</v-list>
<!-- Version List Item -->
<v-list nav dense class="fixedBottom" v-if="!isMain">
<v-list-item to="/admin/about">
<v-list-item-icon class="mr-3 pt-1">
<v-icon :color="newVersionAvailable ? 'red--text' : ''">
mdi-information
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>
{{ $t("settings.current") }}
{{ appVersion }}
</v-list-item-title>
<v-list-item-subtitle>
<a
@click.prevent
href="https://github.com/hay-kot/mealie/releases/latest"
target="_blank"
:class="newVersionAvailable ? 'red--text' : 'green--text'"
>
{{ $t("settings.latest") }}
{{ latestVersion }}
</a>
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</div>
</template>
<script>
import { initials } from "@/mixins/initials";
import { user } from "@/mixins/user";
export default {
mixins: [initials, user],
data() {
return {
showSidebar: false,
mobile: false,
links: [],
baseMainLinks: [
{
icon: "mdi-home",
to: "/",
title: this.$t("page.home-page"),
},
{
icon: "mdi-view-module",
to: "/recipes/all",
title: this.$t("page.all-recipes"),
},
{
icon: "mdi-magnify",
to: "/search",
title: this.$t("search.search"),
},
],
latestVersion: null,
hideImage: false,
settingsLinks: [
{
icon: "mdi-account",
to: "/admin/profile",
title: this.$t("settings.profile"),
},
{
icon: "mdi-format-color-fill",
to: "/admin/themes",
title: this.$t("general.themes"),
},
{
icon: "mdi-food",
to: "/admin/meal-planner",
title: this.$t("meal-plan.meal-planner"),
},
],
adminLinks: [
{
icon: "mdi-cog",
to: "/admin/settings",
title: this.$t("settings.site-settings"),
},
{
icon: "mdi-account-group",
to: "/admin/manage-users",
title: this.$t("settings.manage-users"),
},
{
icon: "mdi-backup-restore",
to: "/admin/backups",
title: this.$t("settings.backup-and-exports"),
},
{
icon: "mdi-database-import",
to: "/admin/migrations",
title: this.$t("settings.migrations"),
},
],
};
},
mounted() {
console.log(this.$router);
this.mobile = this.viewScale();
this.showSidebar = !this.viewScale();
},
computed: {
isMain() {
const testVal = this.$route.path.split("/");
return !(testVal[1] === "admin");
},
customPages() {
const pages = this.$store.getters.getCustomPages;
if (pages.length > 0) {
pages.sort((a, b) => a.position - b.position);
return pages.map(x => ({
title: x.name,
to: `/pages/${x.slug}`,
icon: "mdi-tag",
}));
} else {
const categories = this.$store.getters.getAllCategories;
return categories.map(x => ({
title: x.name,
to: `/recipes/category/${x.slug}`,
icon: "mdi-tag",
}));
}
},
mainMenu() {
return [...this.baseMainLinks, ...this.customPages];
},
adminMenu() {
if (this.user.admin) {
return [...this.settingsLinks, ...this.adminLinks];
} else {
return this.settingsLinks;
}
},
effectiveMenu() {
return this.isMain ? this.mainMenu : this.adminMenu;
},
userProfileImage() {
return `api/users/${this.user.id}/image`;
},
newVersionAvailable() {
return this.latestVersion == this.appVersion ? false : true;
},
appVersion() {
const appInfo = this.$store.getters.getAppInfo;
return appInfo.version;
},
isLoggedIn() {
return this.$store.getters.getIsLoggedIn;
},
},
methods: {
forceOpen() {
this.showSidebar = !this.showSidebar;
},
viewScale() {
switch (this.$vuetify.breakpoint.name) {
case "xs":
return true;
case "sm":
return true;
default:
return false;
}
},
},
};
</script>
<style>
.fixedBottom {
position: fixed !important;
bottom: 0 !important;
width: 100%;
}
</style>

View file

@ -4,19 +4,12 @@
<v-slide-x-transition hide-on-leave>
<router-view></router-view>
</v-slide-x-transition>
<AdminSidebar />
</v-container>
</div>
</template>
<script>
import AdminSidebar from "@/components/Admin/AdminSidebar";
export default {
components: {
AdminSidebar,
},
};
export default {};
</script>
<style>

View file

@ -1,6 +1,6 @@
<template>
<v-container>
<CategorySidebar />
<CardSection
v-if="siteSettings.showRecent"
:title="$t('page.recent')"
@ -23,11 +23,10 @@
<script>
import { api } from "@/api";
import CardSection from "../components/UI/CardSection";
import CategorySidebar from "../components/UI/CategorySidebar";
export default {
components: {
CardSection,
CategorySidebar,
},
data() {
return {

View file

@ -1,6 +1,5 @@
<template>
<v-container>
<CategorySidebar />
<CardSection
:sortable="true"
:title="$t('page.all-recipes')"
@ -13,11 +12,10 @@
<script>
import CardSection from "@/components/UI/CardSection";
import CategorySidebar from "@/components/UI/CategorySidebar";
export default {
components: {
CardSection,
CategorySidebar,
},
data() {
return {};

View file

@ -1,6 +1,5 @@
<template>
<v-container>
<CategorySidebar />
<CardSection
:sortable="true"
:title="title"
@ -15,11 +14,9 @@
<script>
import { api } from "@/api";
import CardSection from "@/components/UI/CardSection";
import CategorySidebar from "@/components/UI/CategorySidebar";
export default {
components: {
CardSection,
CategorySidebar,
},
data() {
return {

View file

@ -1,6 +1,5 @@
<template>
<v-container>
<CategorySidebar />
<v-card flat height="100%">
<v-app-bar flat>
<v-spacer></v-spacer>
@ -32,13 +31,11 @@
<script>
import CardSection from "@/components/UI/CardSection";
import CategorySidebar from "@/components/UI/CategorySidebar";
import { api } from "@/api";
export default {
components: {
CardSection,
CategorySidebar,
},
data() {
return {

View file

@ -1,6 +1,5 @@
<template>
<v-container>
<CategorySidebar />
<CardSection
:sortable="true"
:title="title"
@ -15,11 +14,9 @@
<script>
import { api } from "@/api";
import CardSection from "@/components/UI/CardSection";
import CategorySidebar from "@/components/UI/CategorySidebar";
export default {
components: {
CardSection,
CategorySidebar,
},
data() {
return {

View file

@ -1,6 +1,5 @@
<template>
<v-container>
<CategorySidebar />
<v-card flat>
<v-row dense>
<v-col>
@ -79,14 +78,12 @@
<script>
import Fuse from "fuse.js";
import RecipeCard from "@/components/Recipe/RecipeCard";
import CategorySidebar from "@/components/UI/CategorySidebar";
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
import FilterSelector from "./FilterSelector.vue";
export default {
components: {
RecipeCard,
CategorySidebar,
CategoryTagSelector,
FilterSelector,
},

View file

@ -10,6 +10,7 @@ const state = {
cardsPerSection: 9,
categories: [],
},
customPages: [],
};
const mutations = {
@ -18,6 +19,9 @@ const mutations = {
VueI18n.locale = payload.language;
Vuetify.framework.lang.current = payload.language;
},
setCustomPages(state, payload) {
state.customPages = payload;
},
};
const actions = {
@ -25,11 +29,16 @@ const actions = {
let settings = await api.siteSettings.get();
commit("setSettings", settings);
},
async requestCustomPages({commit }) {
const customPages = await api.siteSettings.getPages()
commit("setCustomPages", customPages)
}
};
const getters = {
getActiveLang: state => state.siteSettings.language,
getSiteSettings: state => state.siteSettings,
getCustomPages: state => state.customPages,
};
export default {