mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-02 20:15:24 +02:00
Merge branch 'dev' of https://github.com/hay-kot/mealie into api-docs
This commit is contained in:
commit
e6b423cde0
10 changed files with 269 additions and 161 deletions
|
@ -13,11 +13,11 @@
|
|||
"qs": "^6.9.4",
|
||||
"v-jsoneditor": "^1.4.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-cookies": "^1.7.4",
|
||||
"vue-html-to-paper": "^1.3.1",
|
||||
"vue-router": "^3.4.9",
|
||||
"vuetify": "^2.4.1",
|
||||
"vuex": "^3.6.0"
|
||||
"vuex": "^3.6.0",
|
||||
"vuex-persistedstate": "^4.0.0-beta.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<v-app>
|
||||
<v-app-bar dense app color="primary" dark class="d-print-none">
|
||||
<v-btn @click="$router.push('/')" icon class="d-flex align-center">
|
||||
<v-icon size="40" >
|
||||
<v-icon size="40">
|
||||
mdi-silverware-variant
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
|
@ -37,6 +37,7 @@ import Menu from "./components/UI/Menu";
|
|||
import SearchHeader from "./components/UI/SearchHeader";
|
||||
import AddRecipe from "./components/AddRecipe";
|
||||
import SnackBar from "./components/UI/SnackBar";
|
||||
import Vuetify from "./plugins/vuetify";
|
||||
export default {
|
||||
name: "App",
|
||||
|
||||
|
@ -44,32 +45,53 @@ export default {
|
|||
Menu,
|
||||
AddRecipe,
|
||||
SearchHeader,
|
||||
SnackBar,
|
||||
SnackBar
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route() {
|
||||
this.search = false;
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$store.dispatch("initCookies");
|
||||
this.$store.dispatch("initTheme");
|
||||
this.$store.dispatch("requestRecentRecipes");
|
||||
this.darkModeSystemCheck();
|
||||
this.darkModeAddEventListener();
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
search: false,
|
||||
search: false
|
||||
}),
|
||||
methods: {
|
||||
/**
|
||||
* Checks if 'system' is set for dark mode and then sets the corrisponding value for vuetify
|
||||
*/
|
||||
darkModeSystemCheck() {
|
||||
if (this.$store.getters.getDarkMode === "system")
|
||||
Vuetify.framework.theme.dark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
},
|
||||
/**
|
||||
* This will monitor the OS level darkmode and call to update dark mode.
|
||||
*/
|
||||
darkModeAddEventListener() {
|
||||
const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
darkMediaQuery.addEventListener("change", () => {
|
||||
this.darkModeSystemCheck();
|
||||
});
|
||||
},
|
||||
|
||||
toggleSearch() {
|
||||
if (this.search === true) {
|
||||
this.search = false;
|
||||
} else {
|
||||
this.search = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ export default {
|
|||
|
||||
async requestByName(name) {
|
||||
let response = await apiReq.get(settingsURLs.specificTheme(name));
|
||||
console.log(response);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
|
|
@ -1,69 +1,117 @@
|
|||
<template>
|
||||
<v-card>
|
||||
<v-card-title class="secondary white--text"> Theme Settings </v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<h2 class="mt-4 mb-1">Dark Mode</h2>
|
||||
<p>
|
||||
Choose how Mealie looks to you. Set your theme preference to follow your
|
||||
system settings, or choose to use the light or dark theme.
|
||||
</p>
|
||||
<v-row dense align="center">
|
||||
<v-col cols="12">
|
||||
<v-btn-toggle
|
||||
v-model="selectedDarkMode"
|
||||
color="primary "
|
||||
mandatory
|
||||
@change="setStoresDarkMode"
|
||||
>
|
||||
<v-btn value="system">
|
||||
Default to system
|
||||
</v-btn>
|
||||
|
||||
<v-btn value="light">
|
||||
Light
|
||||
</v-btn>
|
||||
|
||||
<v-btn value="dark">
|
||||
Dark
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row></v-card-text
|
||||
>
|
||||
<v-divider class=""></v-divider>
|
||||
<v-card-text>
|
||||
<h2 class="mt-1 mb-1">Theme</h2>
|
||||
<p>
|
||||
Select a theme from the dropdown or create a new theme. Note that the
|
||||
default theme will be served to all users who have not set a theme
|
||||
preference.
|
||||
</p>
|
||||
<v-row dense align="center">
|
||||
<v-col cols="12" md="2" sm="5">
|
||||
<v-switch
|
||||
v-model="darkMode"
|
||||
inset
|
||||
label="Dark Mode"
|
||||
class="my-n3"
|
||||
@change="toggleDarkMode"
|
||||
></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4" sm="3">
|
||||
<v-form ref="form" lazy-validation>
|
||||
|
||||
<v-form ref="form" lazy-validation>
|
||||
<v-row dense align="center">
|
||||
<v-col cols="12" md="4" sm="3">
|
||||
<v-select
|
||||
label="Saved Color Schemes"
|
||||
label="Saved Color Theme"
|
||||
:items="availableThemes"
|
||||
item-text="name"
|
||||
item-value="colors"
|
||||
return-object
|
||||
v-model="selectedScheme"
|
||||
v-model="selectedTheme"
|
||||
@change="themeSelected"
|
||||
:rules="[(v) => !!v || 'Theme is required']"
|
||||
:rules="[v => !!v || 'Theme is required']"
|
||||
required
|
||||
>
|
||||
</v-select>
|
||||
</v-form>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="1">
|
||||
<NewTheme @new-theme="appendTheme" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="1">
|
||||
<v-btn text color="error" @click="deleteSelected"> Delete </v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense align-content="center" v-if="activeTheme">
|
||||
</v-col>
|
||||
<v-col cols="12" sm="1">
|
||||
<NewTheme @new-theme="appendTheme" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="1">
|
||||
<v-btn text color="error" @click="deleteSelectedThemeValidation">
|
||||
Delete
|
||||
</v-btn>
|
||||
<Confirmation
|
||||
title="Delete Theme"
|
||||
message="Are you sure you want to delete this theme?"
|
||||
color="error"
|
||||
icon="mdi-alert-circle"
|
||||
ref="deleteThemeConfirm"
|
||||
v-on:confirm="deleteSelectedTheme()"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
<v-row dense align-content="center" v-if="selectedTheme.colors">
|
||||
<v-col>
|
||||
<ColorPicker button-text="Primary" v-model="activeTheme.primary" />
|
||||
<ColorPicker
|
||||
button-text="Primary"
|
||||
v-model="selectedTheme.colors.primary"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<ColorPicker
|
||||
button-text="Secondary"
|
||||
v-model="activeTheme.secondary"
|
||||
v-model="selectedTheme.colors.secondary"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<ColorPicker button-text="Accent" v-model="activeTheme.accent" />
|
||||
<ColorPicker
|
||||
button-text="Accent"
|
||||
v-model="selectedTheme.colors.accent"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<ColorPicker button-text="Success" v-model="activeTheme.success" />
|
||||
<ColorPicker
|
||||
button-text="Success"
|
||||
v-model="selectedTheme.colors.success"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<ColorPicker button-text="Info" v-model="activeTheme.info" />
|
||||
<ColorPicker button-text="Info" v-model="selectedTheme.colors.info" />
|
||||
</v-col>
|
||||
<v-col>
|
||||
<ColorPicker button-text="Warning" v-model="activeTheme.warning" />
|
||||
<ColorPicker
|
||||
button-text="Warning"
|
||||
v-model="selectedTheme.colors.warning"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<ColorPicker button-text="Error" v-model="activeTheme.error" />
|
||||
<ColorPicker
|
||||
button-text="Error"
|
||||
v-model="selectedTheme.colors.error"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
|
@ -73,7 +121,9 @@
|
|||
<v-col> </v-col>
|
||||
<v-col></v-col>
|
||||
<v-col align="end">
|
||||
<v-btn text color="success" @click="saveThemes"> Save Theme </v-btn>
|
||||
<v-btn text color="success" @click="saveThemes">
|
||||
Save Colors and Apply Theme
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-actions>
|
||||
|
@ -84,73 +134,90 @@
|
|||
import api from "../../api";
|
||||
import ColorPicker from "./ThemeUI/ColorPicker";
|
||||
import NewTheme from "./ThemeUI/NewTheme";
|
||||
import Confirmation from "../UI/Confirmation";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColorPicker,
|
||||
NewTheme,
|
||||
Confirmation,
|
||||
NewTheme
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
themes: null,
|
||||
activeTheme: {},
|
||||
darkMode: false,
|
||||
availableThemes: [],
|
||||
selectedScheme: "",
|
||||
selectedLight: "",
|
||||
selectedTheme: {},
|
||||
selectedDarkMode: "system",
|
||||
availableThemes: []
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.availableThemes = await api.themes.requestAll();
|
||||
this.darkMode = this.$store.getters.getDarkMode;
|
||||
this.themes = this.$store.getters.getThemes;
|
||||
this.setThemeEditor();
|
||||
this.selectedTheme = this.$store.getters.getActiveTheme;
|
||||
this.selectedDarkMode = this.$store.getters.getDarkMode;
|
||||
},
|
||||
|
||||
methods: {
|
||||
async deleteSelected() {
|
||||
/**
|
||||
* Open the delete confirmation.
|
||||
*/
|
||||
deleteSelectedThemeValidation() {
|
||||
if (this.$refs.form.validate()) {
|
||||
if (this.selectedScheme === "default") {
|
||||
if (this.selectedTheme.name === "default") {
|
||||
// Notify User Can't Delete Default
|
||||
} else if (this.selectedScheme !== "") {
|
||||
api.themes.delete(this.selectedScheme.name);
|
||||
} else if (this.selectedTheme !== {}) {
|
||||
this.$refs.deleteThemeConfirm.open();
|
||||
}
|
||||
this.availableThemes = await api.themes.requestAll();
|
||||
}
|
||||
},
|
||||
async appendTheme(newTheme) {
|
||||
api.themes.create(newTheme);
|
||||
this.availableThemes.push(newTheme);
|
||||
},
|
||||
themeSelected() {
|
||||
this.activeTheme = this.selectedScheme.colors;
|
||||
},
|
||||
setThemeEditor() {
|
||||
if (this.darkMode) {
|
||||
this.activeTheme = this.themes.dark;
|
||||
} else {
|
||||
this.activeTheme = this.themes.light;
|
||||
}
|
||||
},
|
||||
toggleDarkMode() {
|
||||
this.$store.commit("setDarkMode", this.darkMode);
|
||||
this.selectedScheme = "";
|
||||
/**
|
||||
* Delete the selected Theme
|
||||
*/
|
||||
async deleteSelectedTheme() {
|
||||
//Delete Theme from DB
|
||||
await api.themes.delete(this.selectedTheme.name);
|
||||
|
||||
this.setThemeEditor();
|
||||
//Get the new list of available from DB
|
||||
this.availableThemes = await api.themes.requestAll();
|
||||
|
||||
//Change to default if deleting current theme.
|
||||
if (
|
||||
!this.availableThemes.some(
|
||||
theme => theme.name === this.selectedTheme.name
|
||||
)
|
||||
) {
|
||||
await this.$store.dispatch("resetTheme");
|
||||
this.selectedTheme = this.$store.getters.getActiveTheme;
|
||||
}
|
||||
},
|
||||
saveThemes() {
|
||||
/**
|
||||
* Create the new Theme and select it.
|
||||
*/
|
||||
async appendTheme(newTheme) {
|
||||
await api.themes.create(newTheme);
|
||||
this.availableThemes.push(newTheme);
|
||||
this.selectedTheme = newTheme;
|
||||
},
|
||||
|
||||
themeSelected() {
|
||||
//TODO Revamp Theme selection.
|
||||
//console.log("this.activeTheme", this.selectedTheme);
|
||||
},
|
||||
|
||||
setStoresDarkMode() {
|
||||
this.$store.commit("setDarkMode", this.selectedDarkMode);
|
||||
},
|
||||
/**
|
||||
* This will save the current colors and make the selected theme live.
|
||||
*/
|
||||
async saveThemes() {
|
||||
if (this.$refs.form.validate()) {
|
||||
if (this.darkMode) {
|
||||
this.themes.dark = this.activeTheme;
|
||||
} else {
|
||||
this.themes.light = this.activeTheme;
|
||||
}
|
||||
this.$store.commit("setThemes", this.themes);
|
||||
this.$store.dispatch("initCookies");
|
||||
api.themes.update(this.selectedScheme.name, this.activeTheme);
|
||||
} else;
|
||||
},
|
||||
},
|
||||
this.$store.commit("setTheme", this.selectedTheme);
|
||||
await api.themes.update(
|
||||
this.selectedTheme.name,
|
||||
this.selectedTheme.colors
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ export default {
|
|||
VJsoneditor,
|
||||
ViewRecipe,
|
||||
EditRecipe,
|
||||
ButtonRow,
|
||||
ButtonRow
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -66,7 +66,7 @@ export default {
|
|||
jsonEditorOptions: {
|
||||
mode: "code",
|
||||
search: false,
|
||||
mainMenuBar: false,
|
||||
mainMenuBar: false
|
||||
},
|
||||
// Recipe Details //
|
||||
recipeDetails: {
|
||||
|
@ -83,9 +83,9 @@ export default {
|
|||
categories: [],
|
||||
dateAdded: "",
|
||||
notes: [],
|
||||
rating: 0,
|
||||
rating: 0
|
||||
},
|
||||
imageKey: 1,
|
||||
imageKey: 1
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -93,9 +93,9 @@ export default {
|
|||
},
|
||||
|
||||
watch: {
|
||||
$route: function () {
|
||||
$route: function() {
|
||||
this.getRecipeDetails();
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -111,7 +111,7 @@ export default {
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getImageFile(fileObject) {
|
||||
|
@ -130,7 +130,6 @@ export default {
|
|||
api.recipes.delete(this.recipeDetails.slug);
|
||||
},
|
||||
async saveRecipe() {
|
||||
console.log(this.recipeDetails);
|
||||
await api.recipes.update(this.recipeDetails);
|
||||
|
||||
if (this.fileObject) {
|
||||
|
@ -143,8 +142,8 @@ export default {
|
|||
showForm() {
|
||||
this.form = true;
|
||||
this.jsonEditor = false;
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -4,11 +4,9 @@ import vuetify from "./plugins/vuetify";
|
|||
import store from "./store/store";
|
||||
import VueRouter from "vue-router";
|
||||
import { routes } from "./routes";
|
||||
import VueCookies from "vue-cookies";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(VueCookies);
|
||||
|
||||
const router = new VueRouter({
|
||||
routes,
|
||||
|
@ -23,7 +21,7 @@ new Vue({
|
|||
}).$mount("#app");
|
||||
|
||||
// Truncate
|
||||
let filter = function(text, length, clamp) {
|
||||
let filter = function (text, length, clamp) {
|
||||
clamp = clamp || "...";
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = text;
|
||||
|
|
70
frontend/src/store/modules/userSettings.js
Normal file
70
frontend/src/store/modules/userSettings.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
|
||||
import api from "../../api";
|
||||
import Vuetify from "../../plugins/vuetify";
|
||||
|
||||
const state = {
|
||||
activeTheme: {},
|
||||
darkMode: 'system'
|
||||
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
setTheme(state, payload) {
|
||||
Vuetify.framework.theme.themes.dark = payload.colors;
|
||||
Vuetify.framework.theme.themes.light = payload.colors;
|
||||
state.activeTheme = payload;
|
||||
},
|
||||
setDarkMode(state, payload) {
|
||||
let isDark;
|
||||
|
||||
if (payload === 'system') {
|
||||
//Get System Preference from browser
|
||||
const darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
isDark = darkMediaQuery.matches;
|
||||
}
|
||||
else if (payload === 'dark')
|
||||
isDark = true;
|
||||
else if (payload === 'light')
|
||||
isDark = false;
|
||||
|
||||
if (isDark !== null) {
|
||||
Vuetify.framework.theme.dark = isDark;
|
||||
state.darkMode = payload;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
async resetTheme({ commit }) {
|
||||
const defaultTheme = await api.themes.requestByName("default");
|
||||
if (defaultTheme.colors) {
|
||||
Vuetify.framework.theme.themes.dark = defaultTheme.colors;
|
||||
Vuetify.framework.theme.themes.light = defaultTheme.colors;
|
||||
commit('setTheme', defaultTheme)
|
||||
}
|
||||
},
|
||||
|
||||
async initTheme({ dispatch, getters }) {
|
||||
//If theme is empty resetTheme
|
||||
if (Object.keys(getters.getActiveTheme).length === 0) {
|
||||
await dispatch('resetTheme')
|
||||
}
|
||||
else {
|
||||
Vuetify.framework.theme.themes.dark = getters.getActiveTheme.colors;
|
||||
Vuetify.framework.theme.themes.light = getters.getActiveTheme.colors;
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getActiveTheme: (state) => state.activeTheme,
|
||||
getDarkMode: (state) => state.darkMode
|
||||
}
|
||||
|
||||
export default {
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
getters
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
import Vue from "vue";
|
||||
import Vuex from "vuex";
|
||||
import api from "../api";
|
||||
import Vuetify from "../plugins/vuetify";
|
||||
import createPersistedState from "vuex-persistedstate";
|
||||
import userSettings from "./modules/userSettings";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({
|
||||
plugins: [createPersistedState({
|
||||
paths: ['userSettings']
|
||||
})],
|
||||
modules: {
|
||||
userSettings
|
||||
},
|
||||
state: {
|
||||
// Snackbar
|
||||
snackActive: false,
|
||||
|
@ -15,29 +22,6 @@ const store = new Vuex.Store({
|
|||
// All Recipe Data Store
|
||||
recentRecipes: [],
|
||||
allRecipes: [],
|
||||
|
||||
// Site Settings
|
||||
darkMode: false,
|
||||
themes: {
|
||||
light: {
|
||||
primary: "#E58325",
|
||||
accent: "#00457A",
|
||||
secondary: "#973542",
|
||||
success: "#43A047",
|
||||
info: "#FFFD99",
|
||||
warning: "#FF4081",
|
||||
error: "#EF5350",
|
||||
},
|
||||
dark: {
|
||||
primary: "#4527A0",
|
||||
accent: "#FF4081",
|
||||
secondary: "#26C6DA",
|
||||
success: "#43A047",
|
||||
info: "#2196F3",
|
||||
warning: "#FB8C00",
|
||||
error: "#FF5252",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
mutations: {
|
||||
|
@ -53,38 +37,9 @@ const store = new Vuex.Store({
|
|||
setRecentRecipes(state, payload) {
|
||||
state.recentRecipes = payload;
|
||||
},
|
||||
|
||||
setDarkMode(state, payload) {
|
||||
state.darkMode = payload;
|
||||
Vue.$cookies.set("darkMode", payload);
|
||||
Vuetify.framework.theme.dark = payload;
|
||||
},
|
||||
|
||||
setThemes(state, payload) {
|
||||
state.themes = payload;
|
||||
Vue.$cookies.set("themes", payload);
|
||||
Vuetify.framework.theme.themes = payload;
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
async initCookies() {
|
||||
if (!Vue.$cookies.isKey("themes")) {
|
||||
const DEFAULT_THEME = await api.themes.requestByName("default");
|
||||
Vue.$cookies.set("themes", {
|
||||
light: DEFAULT_THEME.colors,
|
||||
dark: DEFAULT_THEME.colors,
|
||||
});
|
||||
}
|
||||
|
||||
this.commit("setThemes", Vue.$cookies.get("themes"));
|
||||
|
||||
// Dark Mode
|
||||
if (!Vue.$cookies.isKey("darkMode")) {
|
||||
Vue.$cookies.set("darkMode", false);
|
||||
}
|
||||
this.commit("setDarkMode", JSON.parse(Vue.$cookies.get("darkMode")));
|
||||
},
|
||||
|
||||
async requestRecentRecipes() {
|
||||
const keys = [
|
||||
|
@ -108,10 +63,6 @@ const store = new Vuex.Store({
|
|||
getSnackType: (state) => state.snackType,
|
||||
|
||||
getRecentRecipes: (state) => state.recentRecipes,
|
||||
|
||||
// Site Settings
|
||||
getDarkMode: (state) => state.darkMode,
|
||||
getThemes: (state) => state.themes,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue