mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
App Bar Rewrite (#347)
* Dummy Commit * consolidate sidebar and app bar * fix image error * consolidate sidebar * new icon for user menu * fixes #329 * fix double click on mobile * swap to computed properties * fix open/close bug * rewrite search for mobile * fix ingredient checkbox * cleanup console.logs * set default lang + bump version * draft changelog * reword * update env variables Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
7e6f3c9310
commit
d5a340bde1
30
docs/docs/changelog/template.md
Normal file
30
docs/docs/changelog/template.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# vx.x.x COOL TITLE GOES HERE
|
||||||
|
|
||||||
|
**App Version: vx.x.x**
|
||||||
|
|
||||||
|
**Database Version: vx.x.x**
|
||||||
|
|
||||||
|
## Breaking Changes
|
||||||
|
|
||||||
|
!!! error "Breaking Changes"
|
||||||
|
|
||||||
|
#### Database
|
||||||
|
|
||||||
|
#### ENV Variables
|
||||||
|
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
- Fixed ...
|
||||||
|
|
||||||
|
## Features and Improvements
|
||||||
|
|
||||||
|
### General
|
||||||
|
- New Thing 1
|
||||||
|
|
||||||
|
|
||||||
|
### UI Improvements
|
||||||
|
-
|
||||||
|
|
||||||
|
|
||||||
|
### Behind the Scenes
|
||||||
|
- Refactoring...
|
35
docs/docs/changelog/v0.5.0.md
Normal file
35
docs/docs/changelog/v0.5.0.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# v0.5.0 COOL TITLE GOES HERE
|
||||||
|
|
||||||
|
**App Version: v0.5.0**
|
||||||
|
|
||||||
|
**Database Version: v0.5.0**
|
||||||
|
|
||||||
|
## Breaking Changes
|
||||||
|
|
||||||
|
!!! error "Breaking Changes"
|
||||||
|
|
||||||
|
#### Database
|
||||||
|
Database version has been bumped from v0.4.x -> v0.5.0. You will need to export and import your data.
|
||||||
|
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
- Fixed #332 - Language settings are saved for one browser
|
||||||
|
- Fixes #281 - Slow Handling of Large Sets of Recipes
|
||||||
|
|
||||||
|
## Features and Improvements
|
||||||
|
|
||||||
|
### General
|
||||||
|
- More localization
|
||||||
|
- Start date for Week is now selectable
|
||||||
|
- Languages are now managed through Crowdin
|
||||||
|
- The main App bar went through a major overhaul
|
||||||
|
- Sidebar can now be toggled everywhere.
|
||||||
|
- New and improved mobile friendly bottom bar
|
||||||
|
- Improved styling for search bar in desktop
|
||||||
|
- Improved search layout on mobile
|
||||||
|
- Profile image now shown on all sidebars
|
||||||
|
|
||||||
|
|
||||||
|
### Behind the Scenes
|
||||||
|
- Unified Sidebar Components
|
||||||
|
- Refactor UI components to fit Vue best practices (WIP)
|
@ -10,12 +10,15 @@ To deploy docker on your local network it is highly recommended to use docker to
|
|||||||
- linux/arm/v7
|
- linux/arm/v7
|
||||||
- linux/arm64
|
- linux/arm64
|
||||||
|
|
||||||
!!! tip "Fix for linux/arm/v7 container on Raspberry Pi 4: 'Fatal Python error: init_interp_main: can't initialize time'"
|
!!! tip "Fatal Python error: init_interp_main: can't initialize time"
|
||||||
|
Some users experience an problem with running the linux/arm/v7 container on Raspberry Pi 4. This is not a problem with the Mealie container, but with a bug in the hosts Docker installation.
|
||||||
|
|
||||||
Update the host RP4 using [instructions](linuxserver/docker-papermerge#4 (comment)), summarized here:
|
Update the host RP4 using [instructions](linuxserver/docker-papermerge#4 (comment)), summarized here:
|
||||||
```shell
|
|
||||||
|
```shell
|
||||||
wget http://ftp.us.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.1-1_armhf.deb
|
wget http://ftp.us.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.1-1_armhf.deb
|
||||||
sudo dpkg -i libseccomp2_2.5.1-1_armhf.deb
|
sudo dpkg -i libseccomp2_2.5.1-1_armhf.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start - Docker CLI
|
## Quick Start - Docker CLI
|
||||||
Deployment with the Docker CLI can be done with `docker run` and specify the database type, in this case `sqlite`, setting the exposed port `9925`, mounting the current directory, and pull the latest image. After the image is up an running you can navigate to http://your.ip.addres:9925 and you'll should see mealie up and running!
|
Deployment with the Docker CLI can be done with `docker run` and specify the database type, in this case `sqlite`, setting the exposed port `9925`, mounting the current directory, and pull the latest image. After the image is up an running you can navigate to http://your.ip.addres:9925 and you'll should see mealie up and running!
|
||||||
@ -60,7 +63,7 @@ services:
|
|||||||
| ---------------- | ------------------ | ----------------------------------------------------------------------------------- |
|
| ---------------- | ------------------ | ----------------------------------------------------------------------------------- |
|
||||||
| DB_TYPE | sqlite | The database type to be used. Current Options 'sqlite' |
|
| DB_TYPE | sqlite | The database type to be used. Current Options 'sqlite' |
|
||||||
| DEFAULT_GROUP | Home | The default group for users |
|
| DEFAULT_GROUP | Home | The default group for users |
|
||||||
| DEFAULT_USERNAME | changeme@email.com | The default username for the superuser |
|
| DEFAULT_EMAIL | changeme@email.com | The default username for the superuser |
|
||||||
| DEFAULT_PASSWORD | MyPassword | The default password for the superuser |
|
| DEFAULT_PASSWORD | MyPassword | The default password for the superuser |
|
||||||
| TOKEN_TIME | 2 | The time in hours that a login/auth token is valid |
|
| TOKEN_TIME | 2 | The time in hours that a login/auth token is valid |
|
||||||
| API_PORT | 9000 | The port exposed by backend API. **do not change this if you're running in docker** |
|
| API_PORT | 9000 | The port exposed by backend API. **do not change this if you're running in docker** |
|
||||||
|
@ -77,6 +77,7 @@ nav:
|
|||||||
- Guidelines: "contributors/developers-guide/general-guidelines.md"
|
- Guidelines: "contributors/developers-guide/general-guidelines.md"
|
||||||
- Development Road Map: "roadmap.md"
|
- Development Road Map: "roadmap.md"
|
||||||
- Change Log:
|
- Change Log:
|
||||||
|
- v0.5.0 General Upgrades: "changelog/v0.5.0.md"
|
||||||
- v0.4.3 Hot Fix: "changelog/v0.4.3.md"
|
- v0.4.3 Hot Fix: "changelog/v0.4.3.md"
|
||||||
- v0.4.2 Backend/Migrations: "changelog/v0.4.2.md"
|
- v0.4.2 Backend/Migrations: "changelog/v0.4.2.md"
|
||||||
- v0.4.1 Frontend/UI: "changelog/v0.4.1.md"
|
- v0.4.1 Frontend/UI: "changelog/v0.4.1.md"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app>
|
<v-app>
|
||||||
|
<!-- Dummpy Comment -->
|
||||||
<TheAppBar />
|
<TheAppBar />
|
||||||
<v-main>
|
<v-main>
|
||||||
<v-banner v-if="demo" sticky
|
<v-banner v-if="demo" sticky
|
||||||
@ -7,10 +8,6 @@
|
|||||||
<b> This is a Demo</b> | Username: changeme@email.com | Password: demo
|
<b> This is a Demo</b> | Username: changeme@email.com | Password: demo
|
||||||
</div></v-banner
|
</div></v-banner
|
||||||
>
|
>
|
||||||
|
|
||||||
<v-slide-x-reverse-transition>
|
|
||||||
<TheRecipeFab v-if="loggedIn" />
|
|
||||||
</v-slide-x-reverse-transition>
|
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</v-main>
|
</v-main>
|
||||||
<FlashMessage :position="'right bottom'"></FlashMessage>
|
<FlashMessage :position="'right bottom'"></FlashMessage>
|
||||||
@ -19,7 +16,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TheAppBar from "@/components/UI/TheAppBar";
|
import TheAppBar from "@/components/UI/TheAppBar";
|
||||||
import TheRecipeFab from "@/components/UI/TheRecipeFab";
|
|
||||||
import Vuetify from "./plugins/vuetify";
|
import Vuetify from "./plugins/vuetify";
|
||||||
import { user } from "@/mixins/user";
|
import { user } from "@/mixins/user";
|
||||||
|
|
||||||
@ -28,7 +24,6 @@ export default {
|
|||||||
|
|
||||||
components: {
|
components: {
|
||||||
TheAppBar,
|
TheAppBar,
|
||||||
TheRecipeFab,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [user],
|
mixins: [user],
|
||||||
@ -40,14 +35,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
async created() {
|
|
||||||
window.addEventListener("keyup", e => {
|
|
||||||
if (e.key == "/" && !document.activeElement.id.startsWith("input")) {
|
|
||||||
this.search = !this.search;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.$store.dispatch("initTheme");
|
this.$store.dispatch("initTheme");
|
||||||
this.$store.dispatch("requestRecentRecipes");
|
this.$store.dispatch("requestRecentRecipes");
|
||||||
@ -58,6 +45,7 @@ export default {
|
|||||||
this.darkModeSystemCheck();
|
this.darkModeSystemCheck();
|
||||||
this.darkModeAddEventListener();
|
this.darkModeAddEventListener();
|
||||||
this.$store.dispatch("requestAppInfo");
|
this.$store.dispatch("requestAppInfo");
|
||||||
|
this.$store.dispatch("requestCustomPages");
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -3,23 +3,22 @@
|
|||||||
<h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2>
|
<h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
dense
|
dense
|
||||||
v-for="(ingredient, index) in displayIngredients"
|
v-for="(ingredient, index) in ingredients"
|
||||||
:key="generateKey('ingredient', index)"
|
:key="generateKey('ingredient', index)"
|
||||||
@click="ingredient.checked = !ingredient.checked"
|
@click="toggleChecked(index)"
|
||||||
>
|
>
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
hide-details
|
hide-details
|
||||||
v-model="ingredient.checked"
|
:value="checked[index]"
|
||||||
class="pt-0 my-auto py-auto"
|
class="pt-0 my-auto py-auto"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
:readonly="true"
|
|
||||||
>
|
>
|
||||||
</v-checkbox>
|
</v-checkbox>
|
||||||
|
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<vue-markdown
|
<vue-markdown
|
||||||
class="ma-0 pa-0 text-subtitle-1 dense-markdown"
|
class="ma-0 pa-0 text-subtitle-1 dense-markdown"
|
||||||
:source="ingredient.text"
|
:source="ingredient"
|
||||||
>
|
>
|
||||||
</vue-markdown>
|
</vue-markdown>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
@ -37,18 +36,21 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
ingredients: Array,
|
ingredients: Array,
|
||||||
},
|
},
|
||||||
computed: {
|
data() {
|
||||||
displayIngredients() {
|
return {
|
||||||
return this.ingredients.map(x => ({
|
checked: [],
|
||||||
text: x,
|
};
|
||||||
checked: false,
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.checked = this.ingredients.map(() => false);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
generateKey(item, index) {
|
generateKey(item, index) {
|
||||||
return utils.generateUniqueKey(item, index);
|
return utils.generateUniqueKey(item, index);
|
||||||
},
|
},
|
||||||
|
toggleChecked(index) {
|
||||||
|
this.$set(this.checked, index, !this.checked[index]);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -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>
|
|
@ -1,29 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-menu v-model="menuModel" offset-y readonly :width="maxWidth">
|
<v-menu
|
||||||
|
v-model="menuModel"
|
||||||
|
readonly
|
||||||
|
offset-y
|
||||||
|
offset-overflow
|
||||||
|
max-height="75vh"
|
||||||
|
>
|
||||||
<template #activator="{ attrs }">
|
<template #activator="{ attrs }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
class="mt-6"
|
ref="searchInput"
|
||||||
|
class="my-auto pt-1"
|
||||||
v-model="search"
|
v-model="search"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
:dense="dense"
|
:dense="dense"
|
||||||
light
|
light
|
||||||
:label="$t('search.search-mealie')"
|
dark
|
||||||
autofocus
|
flat
|
||||||
|
:placeholder="$t('search.search-mealie')"
|
||||||
|
background-color="primary lighten-1"
|
||||||
|
color="white"
|
||||||
:solo="solo"
|
:solo="solo"
|
||||||
:style="`max-width: ${maxWidth};`"
|
:style="`max-width: ${maxWidth};`"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
|
@blur="isFocused = false"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
:autofocus="autofocus"
|
||||||
>
|
>
|
||||||
|
<template #prepend-inner>
|
||||||
|
<v-icon color="grey lighten-3" size="29">
|
||||||
|
mdi-magnify
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</template>
|
</template>
|
||||||
<v-card v-if="showResults" max-height="500" :max-width="maxWidth">
|
<v-card
|
||||||
<v-card-text class="flex row mx-auto">
|
v-if="showResults"
|
||||||
|
max-height="75vh"
|
||||||
|
:max-width="maxWidth"
|
||||||
|
scrollable
|
||||||
|
>
|
||||||
|
<v-card-text class="flex row mx-auto ">
|
||||||
<div class="mr-auto">
|
<div class="mr-auto">
|
||||||
Results
|
Results
|
||||||
</div>
|
</div>
|
||||||
<router-link to="/search">
|
<router-link to="/search"> Advanced Search </router-link>
|
||||||
Advanced Search
|
|
||||||
</router-link>
|
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-list scrollable v-if="autoResults">
|
<v-list scrollable v-if="autoResults">
|
||||||
@ -77,21 +97,21 @@ export default {
|
|||||||
navOnClick: {
|
navOnClick: {
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
resetSearch: {
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
solo: {
|
solo: {
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
autofocus: {
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
isFocused: false,
|
||||||
searchSlug: "",
|
searchSlug: "",
|
||||||
search: "",
|
search: "",
|
||||||
menuModel: false,
|
menuModel: false,
|
||||||
result: [],
|
result: [],
|
||||||
fuseResults: [],
|
fuseResults: [],
|
||||||
isDark: false,
|
|
||||||
options: {
|
options: {
|
||||||
shouldSort: true,
|
shouldSort: true,
|
||||||
threshold: 0.6,
|
threshold: 0.6,
|
||||||
@ -105,8 +125,10 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.isDark = this.$store.getters.getIsDark;
|
document.addEventListener("keydown", this.onDocumentKeydown);
|
||||||
this.$store.dispatch("requestAllRecipes");
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
document.removeEventListener("keydown", this.onDocumentKeydown);
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
data() {
|
data() {
|
||||||
@ -124,11 +146,7 @@ export default {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
isSearching(val) {
|
isSearching(val) {
|
||||||
val ? (this.menuModel = true) : null;
|
val ? (this.menuModel = true) : this.resetSearch();
|
||||||
},
|
|
||||||
|
|
||||||
resetSearch(val) {
|
|
||||||
val ? (this.search = "") : null;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
search() {
|
search() {
|
||||||
@ -167,9 +185,26 @@ export default {
|
|||||||
this.$emit("selected", slug, name);
|
this.$emit("selected", slug, name);
|
||||||
},
|
},
|
||||||
async onFocus() {
|
async onFocus() {
|
||||||
clearTimeout(this.timeout);
|
this.$store.dispatch("requestAllRecipes");
|
||||||
this.isFocused = true;
|
this.isFocused = true;
|
||||||
},
|
},
|
||||||
|
resetSearch() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.search = "";
|
||||||
|
this.isFocused = false;
|
||||||
|
this.menuModel = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDocumentKeydown(e) {
|
||||||
|
if (
|
||||||
|
e.key === "/" &&
|
||||||
|
e.target !== this.$refs.searchInput.$refs.input &&
|
||||||
|
!document.activeElement.id.startsWith("input")
|
||||||
|
) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.$refs.searchInput.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -181,4 +216,9 @@ export default {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="sass" scoped>
|
<style lang="sass" scoped>
|
||||||
|
.v-menu__content
|
||||||
|
width: 100
|
||||||
|
&, & > *
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
</style>
|
</style>
|
@ -1,22 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-center ">
|
<div class="text-center ">
|
||||||
<v-dialog v-model="dialog" width="600px" height="0" :fullscreen="isMobile">
|
<v-dialog
|
||||||
|
v-model="dialog"
|
||||||
|
width="600px"
|
||||||
|
height="0"
|
||||||
|
:fullscreen="isMobile"
|
||||||
|
content-class="top-dialog"
|
||||||
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-app-bar dark color="primary">
|
<v-app-bar dark color="primary lighten-1" rounded="0">
|
||||||
<v-toolbar-title class="headline">Search a Recipe</v-toolbar-title>
|
|
||||||
</v-app-bar>
|
|
||||||
<v-card-text>
|
|
||||||
<SearchBar
|
<SearchBar
|
||||||
|
ref="mealSearchBar"
|
||||||
@results="updateResults"
|
@results="updateResults"
|
||||||
@selected="emitSelect"
|
@selected="emitSelect"
|
||||||
:show-results="!isMobile"
|
:show-results="!isMobile"
|
||||||
max-width="550px"
|
max-width="568"
|
||||||
:dense="false"
|
:dense="false"
|
||||||
:nav-on-click="false"
|
:nav-on-click="false"
|
||||||
:reset-search="dialog"
|
:autofocus="true"
|
||||||
:solo="false"
|
|
||||||
/>
|
/>
|
||||||
<div v-if="isMobile">
|
<v-btn icon @click="dialog = false" class="mt-1">
|
||||||
|
<v-icon> mdi-close </v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-app-bar>
|
||||||
|
<v-card-text v-if="isMobile">
|
||||||
<div v-for="recipe in searchResults.slice(0, 7)" :key="recipe.name">
|
<div v-for="recipe in searchResults.slice(0, 7)" :key="recipe.name">
|
||||||
<MobileRecipeCard
|
<MobileRecipeCard
|
||||||
class="ma-1 px-0"
|
class="ma-1 px-0"
|
||||||
@ -29,7 +36,6 @@
|
|||||||
@selected="dialog = false"
|
@selected="dialog = false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
@ -74,11 +80,12 @@ export default {
|
|||||||
},
|
},
|
||||||
open() {
|
open() {
|
||||||
this.dialog = true;
|
this.dialog = true;
|
||||||
this.$router.push("#mobile-search");
|
this.$refs.mealSearchBar.resetSearch();
|
||||||
|
this.$router.push("#search");
|
||||||
},
|
},
|
||||||
toggleDialog(open) {
|
toggleDialog(open) {
|
||||||
if (open) {
|
if (open) {
|
||||||
this.$router.push("#mobile-search");
|
this.$router.push("#search");
|
||||||
} else {
|
} else {
|
||||||
this.$router.back(); // 😎 back button click
|
this.$router.back(); // 😎 back button click
|
||||||
}
|
}
|
||||||
@ -92,4 +99,8 @@ export default {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-dialog {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -1,52 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<TheSidebar ref="theSidebar" />
|
||||||
<v-app-bar
|
<v-app-bar
|
||||||
v-if="!isMobile"
|
|
||||||
clipped-left
|
clipped-left
|
||||||
dense
|
dense
|
||||||
app
|
app
|
||||||
color="primary"
|
color="primary"
|
||||||
dark
|
dark
|
||||||
class="d-print-none"
|
class="d-print-none"
|
||||||
|
:bottom="isMobile"
|
||||||
>
|
>
|
||||||
<router-link v-if="!(isMobile && search)" to="/">
|
<v-btn icon @click="openSidebar">
|
||||||
<v-btn icon>
|
<v-icon> mdi-menu </v-icon>
|
||||||
<v-icon size="40"> mdi-silverware-variant </v-icon>
|
|
||||||
</v-btn>
|
</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>
|
|
||||||
<SearchBar
|
|
||||||
ref="mainSearchBar"
|
|
||||||
v-if="search"
|
|
||||||
:show-results="true"
|
|
||||||
@selected="navigateFromSearch"
|
|
||||||
:max-width="isMobile ? '100%' : '450px'"
|
|
||||||
/>
|
|
||||||
</v-expand-x-transition>
|
|
||||||
<v-btn icon @click="search = !search">
|
|
||||||
<v-icon>mdi-magnify</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<TheSiteMenu />
|
|
||||||
</v-app-bar>
|
|
||||||
<v-app-bar
|
|
||||||
v-else
|
|
||||||
bottom
|
|
||||||
clipped-left
|
|
||||||
dense
|
|
||||||
app
|
|
||||||
color="primary"
|
|
||||||
dark
|
|
||||||
class="d-print-none"
|
|
||||||
>
|
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<v-btn icon>
|
<v-btn icon>
|
||||||
<v-icon size="40"> mdi-silverware-variant </v-icon>
|
<v-icon size="40"> mdi-silverware-variant </v-icon>
|
||||||
@ -54,21 +20,34 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<div v-if="!isMobile" btn class="pl-2">
|
<div v-if="!isMobile" btn class="pl-2">
|
||||||
<v-toolbar-title style="cursor: pointer" @click="$router.push('/')"
|
<v-toolbar-title style="cursor: pointer" @click="$router.push('/')">
|
||||||
>Mealie
|
Mealie
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-expand-x-transition>
|
<SearchBar
|
||||||
<SearchDialog ref="mainSearchDialog" />
|
v-if="!isMobile"
|
||||||
</v-expand-x-transition>
|
:show-results="true"
|
||||||
<v-btn icon @click="$refs.mainSearchDialog.open()">
|
@selected="navigateFromSearch"
|
||||||
<v-icon>mdi-magnify</v-icon>
|
:max-width="isMobile ? '100%' : '450px'"
|
||||||
|
/>
|
||||||
|
<div v-else>
|
||||||
|
<v-btn icon @click="$refs.recipeSearch.open()">
|
||||||
|
<v-icon> mdi-magnify </v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<SearchDialog ref="recipeSearch"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<TheSiteMenu />
|
<TheSiteMenu />
|
||||||
|
|
||||||
|
<v-slide-x-reverse-transition>
|
||||||
|
<TheRecipeFab v-if="loggedIn && isMobile" />
|
||||||
|
</v-slide-x-reverse-transition>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
|
<v-slide-x-reverse-transition>
|
||||||
|
<TheRecipeFab v-if="loggedIn && !isMobile" :absolute="true" />
|
||||||
|
</v-slide-x-reverse-transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -76,39 +55,40 @@
|
|||||||
import TheSiteMenu from "@/components/UI/TheSiteMenu";
|
import TheSiteMenu from "@/components/UI/TheSiteMenu";
|
||||||
import SearchBar from "@/components/UI/Search/SearchBar";
|
import SearchBar from "@/components/UI/Search/SearchBar";
|
||||||
import SearchDialog from "@/components/UI/Search/SearchDialog";
|
import SearchDialog from "@/components/UI/Search/SearchDialog";
|
||||||
|
import TheRecipeFab from "@/components/UI/TheRecipeFab";
|
||||||
|
import TheSidebar from "@/components/UI/TheSidebar";
|
||||||
import { user } from "@/mixins/user";
|
import { user } from "@/mixins/user";
|
||||||
export default {
|
export default {
|
||||||
name: "AppBar",
|
name: "AppBar",
|
||||||
|
|
||||||
mixins: [user],
|
mixins: [user],
|
||||||
components: {
|
components: {
|
||||||
|
SearchDialog,
|
||||||
|
TheRecipeFab,
|
||||||
|
TheSidebar,
|
||||||
TheSiteMenu,
|
TheSiteMenu,
|
||||||
SearchBar,
|
SearchBar,
|
||||||
SearchDialog,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
search: false,
|
showSidebar: false,
|
||||||
isMobile: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
$route() {
|
|
||||||
this.search = false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
// isMobile() {
|
isMobile() {
|
||||||
// return this.$vuetify.breakpoint.name === "xs";
|
return this.$vuetify.breakpoint.name === "xs";
|
||||||
// },
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
navigateFromSearch(slug) {
|
navigateFromSearch(slug) {
|
||||||
this.$router.push(`/recipe/${slug}`);
|
this.$router.push(`/recipe/${slug}`);
|
||||||
},
|
},
|
||||||
|
openSidebar() {
|
||||||
|
this.$refs.theSidebar.toggleSidebar();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style scoped>
|
||||||
</style>
|
</style>
|
@ -54,16 +54,28 @@
|
|||||||
</v-form>
|
</v-form>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
<v-speed-dial v-model="fab" fixed right bottom open-on-hover>
|
<v-speed-dial
|
||||||
|
v-model="fab"
|
||||||
|
:open-on-hover="absolute"
|
||||||
|
:fixed="absolute"
|
||||||
|
:bottom="absolute"
|
||||||
|
:right="absolute"
|
||||||
|
>
|
||||||
<template v-slot:activator>
|
<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-icon> mdi-plus </v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-btn fab dark small color="primary" @click="addRecipe = true">
|
<v-btn fab dark small color="primary" @click="addRecipe = true">
|
||||||
<v-icon>mdi-link</v-icon>
|
<v-icon>mdi-link</v-icon>
|
||||||
</v-btn>
|
</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-icon>mdi-square-edit-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-speed-dial>
|
</v-speed-dial>
|
||||||
@ -74,6 +86,11 @@
|
|||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
absolute: {
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
error: false,
|
error: false,
|
||||||
@ -102,10 +119,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
navCreate() {
|
|
||||||
this.$router.push("/new");
|
|
||||||
},
|
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.fab = false;
|
this.fab = false;
|
||||||
this.error = false;
|
this.error = false;
|
||||||
|
@ -1,27 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<v-btn
|
<v-navigation-drawer v-model="showSidebar" width="180px" clipped app>
|
||||||
class="mt-9 ml-n1"
|
|
||||||
fixed
|
|
||||||
left
|
|
||||||
bottom
|
|
||||||
fab
|
|
||||||
small
|
|
||||||
color="primary"
|
|
||||||
@click="showSidebar = !showSidebar"
|
|
||||||
>
|
|
||||||
<v-icon>mdi-cog</v-icon></v-btn
|
|
||||||
>
|
|
||||||
|
|
||||||
<v-navigation-drawer
|
|
||||||
:value="mobile ? showSidebar : true"
|
|
||||||
v-model="showSidebar"
|
|
||||||
width="180px"
|
|
||||||
clipped
|
|
||||||
app
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-list-item two-line>
|
<v-list-item two-line v-if="isLoggedIn">
|
||||||
<v-list-item-avatar color="accent" class="white--text">
|
<v-list-item-avatar color="accent" class="white--text">
|
||||||
<img
|
<img
|
||||||
:src="userProfileImage"
|
:src="userProfileImage"
|
||||||
@ -41,12 +22,11 @@
|
|||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
|
|
||||||
<v-list nav dense>
|
<v-list nav dense>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="nav in baseLinks"
|
v-for="nav in effectiveMenu"
|
||||||
:key="nav.title"
|
:key="nav.title"
|
||||||
link
|
link
|
||||||
:to="nav.to"
|
:to="nav.to"
|
||||||
@ -58,22 +38,8 @@
|
|||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
|
|
||||||
<v-divider></v-divider>
|
<!-- Version List Item -->
|
||||||
<v-list nav dense v-if="user.admin">
|
<v-list nav dense class="fixedBottom" v-if="!isMain">
|
||||||
<v-list-item
|
|
||||||
v-for="nav in superLinks"
|
|
||||||
: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>
|
|
||||||
|
|
||||||
<v-list nav dense class="fixedBottom">
|
|
||||||
<v-list-item to="/admin/about">
|
<v-list-item to="/admin/about">
|
||||||
<v-list-item-icon class="mr-3 pt-1">
|
<v-list-item-icon class="mr-3 pt-1">
|
||||||
<v-icon :color="newVersionAvailable ? 'red--text' : ''">
|
<v-icon :color="newVersionAvailable ? 'red--text' : ''">
|
||||||
@ -104,20 +70,94 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { validators } from "@/mixins/validators";
|
|
||||||
import { initials } from "@/mixins/initials";
|
import { initials } from "@/mixins/initials";
|
||||||
import { user } from "@/mixins/user";
|
import { user } from "@/mixins/user";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
export default {
|
export default {
|
||||||
mixins: [validators, initials, user],
|
mixins: [initials, user],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
showSidebar: false,
|
||||||
|
links: [],
|
||||||
|
|
||||||
latestVersion: null,
|
latestVersion: null,
|
||||||
hideImage: false,
|
hideImage: false,
|
||||||
showSidebar: false,
|
};
|
||||||
mobile: false,
|
},
|
||||||
links: [],
|
mounted() {
|
||||||
superLinks: [
|
this.getVersion();
|
||||||
|
this.resetView();
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
isMain() {
|
||||||
|
const testVal = this.$route.path.split("/");
|
||||||
|
if (testVal[1] === "recipe") this.closeSidebar();
|
||||||
|
else this.resetView();
|
||||||
|
|
||||||
|
return !(testVal[1] === "admin");
|
||||||
|
},
|
||||||
|
baseMainLinks() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
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"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
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];
|
||||||
|
},
|
||||||
|
settingsLinks() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
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() {
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
icon: "mdi-cog",
|
icon: "mdi-cog",
|
||||||
to: "/admin/settings",
|
to: "/admin/settings",
|
||||||
@ -138,34 +178,20 @@ export default {
|
|||||||
to: "/admin/migrations",
|
to: "/admin/migrations",
|
||||||
title: this.$t("settings.migrations"),
|
title: this.$t("settings.migrations"),
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
baseLinks: [
|
|
||||||
{
|
|
||||||
icon: "mdi-account",
|
|
||||||
to: "/admin/profile",
|
|
||||||
title: this.$t("settings.profile"),
|
|
||||||
},
|
},
|
||||||
{
|
adminMenu() {
|
||||||
icon: "mdi-format-color-fill",
|
if (this.user.admin) {
|
||||||
to: "/admin/themes",
|
return [...this.settingsLinks, ...this.adminLinks];
|
||||||
title: this.$t("general.themes"),
|
} else {
|
||||||
|
return this.settingsLinks;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
effectiveMenu() {
|
||||||
icon: "mdi-food",
|
return this.isMain ? this.mainMenu : this.adminMenu;
|
||||||
to: "/admin/meal-planner",
|
|
||||||
title: this.$t("meal-plan.meal-planner"),
|
|
||||||
},
|
},
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
this.mobile = this.viewScale();
|
|
||||||
this.showSidebar = !this.viewScale();
|
|
||||||
this.getVersion();
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
userProfileImage() {
|
userProfileImage() {
|
||||||
|
this.resetImage();
|
||||||
return `api/users/${this.user.id}/image`;
|
return `api/users/${this.user.id}/image`;
|
||||||
},
|
},
|
||||||
newVersionAvailable() {
|
newVersionAvailable() {
|
||||||
@ -175,18 +201,26 @@ export default {
|
|||||||
const appInfo = this.$store.getters.getAppInfo;
|
const appInfo = this.$store.getters.getAppInfo;
|
||||||
return appInfo.version;
|
return appInfo.version;
|
||||||
},
|
},
|
||||||
|
isLoggedIn() {
|
||||||
|
return this.$store.getters.getIsLoggedIn;
|
||||||
|
},
|
||||||
|
isMobile() {
|
||||||
|
return this.$vuetify.breakpoint.name === "xs";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
viewScale() {
|
resetImage() {
|
||||||
switch (this.$vuetify.breakpoint.name) {
|
this.hideImage == false;
|
||||||
case "xs":
|
},
|
||||||
return true;
|
resetView() {
|
||||||
case "sm":
|
this.showSidebar = !this.isMobile;
|
||||||
return true;
|
},
|
||||||
default:
|
toggleSidebar() {
|
||||||
return false;
|
this.showSidebar = !this.showSidebar;
|
||||||
}
|
},
|
||||||
|
closeSidebar() {
|
||||||
|
this.showSidebar = false;
|
||||||
},
|
},
|
||||||
async getVersion() {
|
async getVersion() {
|
||||||
let response = await axios.get(
|
let response = await axios.get(
|
||||||
@ -198,6 +232,7 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.latestVersion = response.data.tag_name;
|
this.latestVersion = response.data.tag_name;
|
||||||
},
|
},
|
||||||
},
|
},
|
@ -11,7 +11,7 @@
|
|||||||
>
|
>
|
||||||
<template v-slot:activator="{ on, attrs }">
|
<template v-slot:activator="{ on, attrs }">
|
||||||
<v-btn v-bind="attrs" v-on="on" icon>
|
<v-btn v-bind="attrs" v-on="on" icon>
|
||||||
<v-icon>mdi-menu</v-icon>
|
<v-icon>mdi-account</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -145,15 +145,14 @@
|
|||||||
"view-recipe": "View Recipe"
|
"view-recipe": "View Recipe"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"and": "And",
|
"search-mealie": "Search Mealie (press /)",
|
||||||
|
"search-placeholder": "Search...",
|
||||||
|
"max-results": "Max Results",
|
||||||
"category-filter": "Category Filter",
|
"category-filter": "Category Filter",
|
||||||
"exclude": "Exclude",
|
"exclude": "Exclude",
|
||||||
"include": "Include",
|
"include": "Include",
|
||||||
"max-results": "Max Results",
|
|
||||||
"or": "Or",
|
"or": "Or",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"search-mealie": "Search Mealie",
|
|
||||||
"search-placeholder": "Search...",
|
|
||||||
"tag-filter": "Tag Filter"
|
"tag-filter": "Tag Filter"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
@ -4,19 +4,12 @@
|
|||||||
<v-slide-x-transition hide-on-leave>
|
<v-slide-x-transition hide-on-leave>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</v-slide-x-transition>
|
</v-slide-x-transition>
|
||||||
<AdminSidebar />
|
|
||||||
</v-container>
|
</v-container>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AdminSidebar from "@/components/Admin/AdminSidebar";
|
export default {};
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
AdminSidebar,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<CategorySidebar />
|
|
||||||
<CardSection
|
<CardSection
|
||||||
v-if="siteSettings.showRecent"
|
v-if="siteSettings.showRecent"
|
||||||
:title="$t('page.recent')"
|
:title="$t('page.recent')"
|
||||||
@ -23,11 +23,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import CardSection from "../components/UI/CardSection";
|
import CardSection from "../components/UI/CardSection";
|
||||||
import CategorySidebar from "../components/UI/CategorySidebar";
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CardSection,
|
CardSection,
|
||||||
CategorySidebar,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
class="headline font-weight-light white--text"
|
class="headline font-weight-light white--text"
|
||||||
>
|
>
|
||||||
<v-img :src="getImage(meal.image)"></v-img>
|
<v-img :src="getImage(meal.slug)"></v-img>
|
||||||
</v-list-item-avatar>
|
</v-list-item-avatar>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-list-item-title v-text="meal.name"></v-list-item-title>
|
<v-list-item-title v-text="meal.name"></v-list-item-title>
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col order-sm="0" :order-md="getOrder(index)" md="6" sm="12">
|
<v-col order-sm="0" :order-md="getOrder(index)" md="6" sm="12">
|
||||||
<v-card flat>
|
<v-card flat>
|
||||||
<v-img :src="getImage(meal.image)" max-height="300"> </v-img>
|
<v-img :src="getImage(meal.slug)" max-height="300"> </v-img>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<CategorySidebar />
|
|
||||||
<CardSection
|
<CardSection
|
||||||
:sortable="true"
|
:sortable="true"
|
||||||
:title="$t('page.all-recipes')"
|
:title="$t('page.all-recipes')"
|
||||||
@ -13,11 +12,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CardSection from "@/components/UI/CardSection";
|
import CardSection from "@/components/UI/CardSection";
|
||||||
import CategorySidebar from "@/components/UI/CategorySidebar";
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CardSection,
|
CardSection,
|
||||||
CategorySidebar,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<CategorySidebar />
|
|
||||||
<CardSection
|
<CardSection
|
||||||
:sortable="true"
|
:sortable="true"
|
||||||
:title="title"
|
:title="title"
|
||||||
@ -15,11 +14,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import CardSection from "@/components/UI/CardSection";
|
import CardSection from "@/components/UI/CardSection";
|
||||||
import CategorySidebar from "@/components/UI/CategorySidebar";
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CardSection,
|
CardSection,
|
||||||
CategorySidebar,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<CategorySidebar />
|
|
||||||
<v-card flat height="100%">
|
<v-card flat height="100%">
|
||||||
<v-app-bar flat>
|
<v-app-bar flat>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
@ -32,13 +31,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CardSection from "@/components/UI/CardSection";
|
import CardSection from "@/components/UI/CardSection";
|
||||||
import CategorySidebar from "@/components/UI/CategorySidebar";
|
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CardSection,
|
CardSection,
|
||||||
CategorySidebar,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<CategorySidebar />
|
|
||||||
<CardSection
|
<CardSection
|
||||||
:sortable="true"
|
:sortable="true"
|
||||||
:title="title"
|
:title="title"
|
||||||
@ -15,11 +14,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import CardSection from "@/components/UI/CardSection";
|
import CardSection from "@/components/UI/CardSection";
|
||||||
import CategorySidebar from "@/components/UI/CategorySidebar";
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CardSection,
|
CardSection,
|
||||||
CategorySidebar,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<CategorySidebar />
|
|
||||||
<v-card flat>
|
<v-card flat>
|
||||||
<v-row dense>
|
<v-row dense>
|
||||||
<v-col>
|
<v-col>
|
||||||
@ -79,14 +78,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import RecipeCard from "@/components/Recipe/RecipeCard";
|
import RecipeCard from "@/components/Recipe/RecipeCard";
|
||||||
import CategorySidebar from "@/components/UI/CategorySidebar";
|
|
||||||
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
|
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
|
||||||
import FilterSelector from "./FilterSelector.vue";
|
import FilterSelector from "./FilterSelector.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
RecipeCard,
|
RecipeCard,
|
||||||
CategorySidebar,
|
|
||||||
CategoryTagSelector,
|
CategoryTagSelector,
|
||||||
FilterSelector,
|
FilterSelector,
|
||||||
},
|
},
|
||||||
|
@ -10,6 +10,7 @@ const state = {
|
|||||||
cardsPerSection: 9,
|
cardsPerSection: 9,
|
||||||
categories: [],
|
categories: [],
|
||||||
},
|
},
|
||||||
|
customPages: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
@ -18,6 +19,9 @@ const mutations = {
|
|||||||
VueI18n.locale = payload.language;
|
VueI18n.locale = payload.language;
|
||||||
Vuetify.framework.lang.current = payload.language;
|
Vuetify.framework.lang.current = payload.language;
|
||||||
},
|
},
|
||||||
|
setCustomPages(state, payload) {
|
||||||
|
state.customPages = payload;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@ -25,11 +29,16 @@ const actions = {
|
|||||||
let settings = await api.siteSettings.get();
|
let settings = await api.siteSettings.get();
|
||||||
commit("setSettings", settings);
|
commit("setSettings", settings);
|
||||||
},
|
},
|
||||||
|
async requestCustomPages({commit }) {
|
||||||
|
const customPages = await api.siteSettings.getPages()
|
||||||
|
commit("setCustomPages", customPages)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
getActiveLang: state => state.siteSettings.language,
|
getActiveLang: state => state.siteSettings.language,
|
||||||
getSiteSettings: state => state.siteSettings,
|
getSiteSettings: state => state.siteSettings,
|
||||||
|
getCustomPages: state => state.customPages,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -6,8 +6,8 @@ from typing import Optional, Union
|
|||||||
import dotenv
|
import dotenv
|
||||||
from pydantic import BaseSettings, Field, validator
|
from pydantic import BaseSettings, Field, validator
|
||||||
|
|
||||||
APP_VERSION = "v0.4.3"
|
APP_VERSION = "v0.5.0beta"
|
||||||
DB_VERSION = "v0.4.0"
|
DB_VERSION = "v0.5.0"
|
||||||
|
|
||||||
CWD = Path(__file__).parent
|
CWD = Path(__file__).parent
|
||||||
BASE_DIR = CWD.parent.parent
|
BASE_DIR = CWD.parent.parent
|
||||||
|
@ -7,7 +7,7 @@ from slugify import slugify
|
|||||||
|
|
||||||
|
|
||||||
class SiteSettings(CamelModel):
|
class SiteSettings(CamelModel):
|
||||||
language: str = "en"
|
language: str = "en-US"
|
||||||
first_day_of_week: int = 0
|
first_day_of_week: int = 0
|
||||||
show_recent: bool = True
|
show_recent: bool = True
|
||||||
cards_per_section: int = 9
|
cards_per_section: int = 9
|
||||||
|
Loading…
x
Reference in New Issue
Block a user