mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
Added | Persistant Storage, final tweaks to themes
This commit is contained in:
parent
0cdf8b9faa
commit
3e65d2cc9c
@ -46,3 +46,5 @@ General
|
|||||||
- Improved documentation
|
- Improved documentation
|
||||||
- Fixed hot-reloading development environment - [grssmnn](https://github.com/grssmnn)
|
- Fixed hot-reloading development environment - [grssmnn](https://github.com/grssmnn)
|
||||||
- Added Confirmation component to deleting recipes - [zackbcom](https://github.com/zackbcom)
|
- Added Confirmation component to deleting recipes - [zackbcom](https://github.com/zackbcom)
|
||||||
|
- Added Persistent storage to vuex - [zackbcom](https://github.com/zackbcom)
|
||||||
|
- Updated Theme backend - [zackbcom](https://github.com/zackbcom)
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
"vue-html-to-paper": "^1.3.1",
|
"vue-html-to-paper": "^1.3.1",
|
||||||
"vue-router": "^3.4.9",
|
"vue-router": "^3.4.9",
|
||||||
"vuetify": "^2.4.1",
|
"vuetify": "^2.4.1",
|
||||||
"vuex": "^3.6.0"
|
"vuex": "^3.6.0",
|
||||||
|
"vuex-persistedstate": "^4.0.0-beta.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
|
@ -37,6 +37,7 @@ import Menu from "./components/UI/Menu";
|
|||||||
import SearchHeader from "./components/UI/SearchHeader";
|
import SearchHeader from "./components/UI/SearchHeader";
|
||||||
import AddRecipe from "./components/AddRecipe";
|
import AddRecipe from "./components/AddRecipe";
|
||||||
import SnackBar from "./components/UI/SnackBar";
|
import SnackBar from "./components/UI/SnackBar";
|
||||||
|
import Vuetify from "./plugins/vuetify";
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
|
|
||||||
@ -54,8 +55,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch("initCookies");
|
this.$store.dispatch("initTheme");
|
||||||
this.$store.dispatch("requestRecentRecipes");
|
this.$store.dispatch("requestRecentRecipes");
|
||||||
|
this.darkModeSystemCheck();
|
||||||
this.darkModeAddEventListener();
|
this.darkModeAddEventListener();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -63,16 +65,25 @@ export default {
|
|||||||
search: false
|
search: false
|
||||||
}),
|
}),
|
||||||
methods: {
|
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.
|
* This will monitor the OS level darkmode and call to update dark mode.
|
||||||
*/
|
*/
|
||||||
darkModeAddEventListener() {
|
darkModeAddEventListener() {
|
||||||
const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
darkMediaQuery.addEventListener("change", () => {
|
darkMediaQuery.addEventListener("change", () => {
|
||||||
this.$store.commit("setDarkMode", "system");
|
this.darkModeSystemCheck();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSearch() {
|
toggleSearch() {
|
||||||
if (this.search === true) {
|
if (this.search === true) {
|
||||||
this.search = false;
|
this.search = false;
|
||||||
|
@ -11,10 +11,10 @@
|
|||||||
<v-row dense align="center">
|
<v-row dense align="center">
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-btn-toggle
|
<v-btn-toggle
|
||||||
v-model="darkMode"
|
v-model="selectedDarkMode"
|
||||||
color="primary "
|
color="primary "
|
||||||
mandatory
|
mandatory
|
||||||
@change="setDarkMode"
|
@change="setStoresDarkMode"
|
||||||
>
|
>
|
||||||
<v-btn value="system">
|
<v-btn value="system">
|
||||||
Default to system
|
Default to system
|
||||||
@ -48,7 +48,7 @@
|
|||||||
:items="availableThemes"
|
:items="availableThemes"
|
||||||
item-text="name"
|
item-text="name"
|
||||||
return-object
|
return-object
|
||||||
v-model="activeTheme"
|
v-model="selectedTheme"
|
||||||
@change="themeSelected"
|
@change="themeSelected"
|
||||||
:rules="[v => !!v || 'Theme is required']"
|
:rules="[v => !!v || 'Theme is required']"
|
||||||
required
|
required
|
||||||
@ -73,42 +73,45 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-form>
|
</v-form>
|
||||||
<v-row dense align-content="center" v-if="activeTheme.colors">
|
<v-row dense align-content="center" v-if="selectedTheme.colors">
|
||||||
<v-col>
|
<v-col>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
button-text="Primary"
|
button-text="Primary"
|
||||||
v-model="activeTheme.colors.primary"
|
v-model="selectedTheme.colors.primary"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
button-text="Secondary"
|
button-text="Secondary"
|
||||||
v-model="activeTheme.colors.secondary"
|
v-model="selectedTheme.colors.secondary"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
button-text="Accent"
|
button-text="Accent"
|
||||||
v-model="activeTheme.colors.accent"
|
v-model="selectedTheme.colors.accent"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
button-text="Success"
|
button-text="Success"
|
||||||
v-model="activeTheme.colors.success"
|
v-model="selectedTheme.colors.success"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
<ColorPicker button-text="Info" v-model="activeTheme.colors.info" />
|
<ColorPicker button-text="Info" v-model="selectedTheme.colors.info" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
button-text="Warning"
|
button-text="Warning"
|
||||||
v-model="activeTheme.colors.warning"
|
v-model="selectedTheme.colors.warning"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
<ColorPicker button-text="Error" v-model="activeTheme.colors.error" />
|
<ColorPicker
|
||||||
|
button-text="Error"
|
||||||
|
v-model="selectedTheme.colors.error"
|
||||||
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
@ -118,7 +121,9 @@
|
|||||||
<v-col> </v-col>
|
<v-col> </v-col>
|
||||||
<v-col></v-col>
|
<v-col></v-col>
|
||||||
<v-col align="end">
|
<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-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
@ -139,62 +144,80 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeTheme: {},
|
selectedTheme: {},
|
||||||
darkMode: "system",
|
selectedDarkMode: "system",
|
||||||
availableThemes: []
|
availableThemes: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.availableThemes = await api.themes.requestAll();
|
this.availableThemes = await api.themes.requestAll();
|
||||||
this.activeTheme = this.$store.getters.getActiveTheme;
|
this.selectedTheme = this.$store.getters.getActiveTheme;
|
||||||
this.darkMode = this.$store.getters.getDarkMode;
|
this.selectedDarkMode = this.$store.getters.getDarkMode;
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* Open the delete confirmation.
|
||||||
|
*/
|
||||||
deleteSelectedThemeValidation() {
|
deleteSelectedThemeValidation() {
|
||||||
if (this.$refs.form.validate()) {
|
if (this.$refs.form.validate()) {
|
||||||
if (this.activeTheme.name === "default") {
|
if (this.selectedTheme.name === "default") {
|
||||||
// Notify User Can't Delete Default
|
// Notify User Can't Delete Default
|
||||||
} else if (this.activeTheme !== {}) {
|
} else if (this.selectedTheme !== {}) {
|
||||||
this.$refs.deleteThemeConfirm.open();
|
this.$refs.deleteThemeConfirm.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Delete the selected Theme
|
||||||
|
*/
|
||||||
async deleteSelectedTheme() {
|
async deleteSelectedTheme() {
|
||||||
api.themes.delete(this.activeTheme.name);
|
//Delete Theme from DB
|
||||||
this.availableThemes = await api.themes.requestAll();
|
await api.themes.delete(this.selectedTheme.name);
|
||||||
//Change to default if deleting current theme.
|
|
||||||
|
|
||||||
|
//Get the new list of available from DB
|
||||||
|
this.availableThemes = await api.themes.requestAll();
|
||||||
|
console.log("themes", this.availableThemes);
|
||||||
|
|
||||||
|
//Change to default if deleting current theme.
|
||||||
if (
|
if (
|
||||||
!this.availableThemes.some(
|
!this.availableThemes.some(
|
||||||
theme => theme.name === this.activeTheme.name
|
theme => theme.name === this.selectedTheme.name
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this.$store.commit("setActiveTheme", null);
|
console.log("hit");
|
||||||
this.activeTheme = this.$store.getters.getActiveTheme;
|
await this.$store.dispatch("resetTheme");
|
||||||
|
this.selectedTheme = this.$store.getters.getActiveTheme;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Create the new Theme and select it.
|
||||||
|
*/
|
||||||
async appendTheme(newTheme) {
|
async appendTheme(newTheme) {
|
||||||
api.themes.create(newTheme);
|
await api.themes.create(newTheme);
|
||||||
this.availableThemes.push(newTheme);
|
this.availableThemes.push(newTheme);
|
||||||
this.activeTheme = newTheme;
|
this.selectedTheme = newTheme;
|
||||||
},
|
|
||||||
themeSelected() {
|
|
||||||
console.log("this.activeTheme", this.activeTheme);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setDarkMode() {
|
themeSelected() {
|
||||||
this.$store.commit("setDarkMode", this.darkMode);
|
//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.
|
* This will save the current colors and make the selected theme live.
|
||||||
*/
|
*/
|
||||||
async saveThemes() {
|
async saveThemes() {
|
||||||
if (this.$refs.form.validate()) {
|
if (this.$refs.form.validate()) {
|
||||||
this.$store.commit("setActiveTheme", this.activeTheme);
|
this.$store.commit("setTheme", this.selectedTheme);
|
||||||
this.$store.dispatch("initCookies");
|
await api.themes.update(
|
||||||
api.themes.update(this.activeTheme.name, this.activeTheme);
|
this.selectedTheme.name,
|
||||||
} else;
|
this.selectedTheme.colors
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
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,16 @@
|
|||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import Vuex from "vuex";
|
import Vuex from "vuex";
|
||||||
import api from "../api";
|
import api from "../api";
|
||||||
import Vuetify from "../plugins/vuetify";
|
import createPersistedState from "vuex-persistedstate";
|
||||||
|
import userSettings from "./modules/userSettings";
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
|
plugins: [createPersistedState()],
|
||||||
|
modules: {
|
||||||
|
userSettings
|
||||||
|
},
|
||||||
state: {
|
state: {
|
||||||
// Snackbar
|
// Snackbar
|
||||||
snackActive: false,
|
snackActive: false,
|
||||||
@ -15,41 +20,6 @@ const store = new Vuex.Store({
|
|||||||
// All Recipe Data Store
|
// All Recipe Data Store
|
||||||
recentRecipes: [],
|
recentRecipes: [],
|
||||||
allRecipes: [],
|
allRecipes: [],
|
||||||
|
|
||||||
// Site Settings
|
|
||||||
darkMode: 'system',
|
|
||||||
activeTheme: {
|
|
||||||
name: 'default',
|
|
||||||
colors: {
|
|
||||||
primary: "#E58325",
|
|
||||||
accent: "#00457A",
|
|
||||||
secondary: "#973542",
|
|
||||||
success: "#5AB1BB",
|
|
||||||
info: "#4990BA",
|
|
||||||
warning: "#FF4081",
|
|
||||||
error: "#EF5350",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
themes: {
|
|
||||||
light: {
|
|
||||||
primary: "#E58325",
|
|
||||||
accent: "#00457A",
|
|
||||||
secondary: "#973542",
|
|
||||||
success: "#5AB1BB",
|
|
||||||
info: "#4990BA",
|
|
||||||
warning: "#FF4081",
|
|
||||||
error: "#EF5350",
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
primary: "#E58325",
|
|
||||||
accent: "#00457A",
|
|
||||||
secondary: "#973542",
|
|
||||||
success: "#5AB1BB",
|
|
||||||
info: "#4990BA",
|
|
||||||
warning: "#FF4081",
|
|
||||||
error: "#EF5350",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mutations: {
|
mutations: {
|
||||||
@ -65,65 +35,9 @@ const store = new Vuex.Store({
|
|||||||
setRecentRecipes(state, payload) {
|
setRecentRecipes(state, payload) {
|
||||||
state.recentRecipes = payload;
|
state.recentRecipes = payload;
|
||||||
},
|
},
|
||||||
|
|
||||||
setDarkMode(state, payload) {
|
|
||||||
let isDark;
|
|
||||||
state.darkMode = payload;
|
|
||||||
Vue.$cookies.set("darkMode", payload);
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
isDark = false;
|
|
||||||
|
|
||||||
Vuetify.framework.theme.dark = isDark;
|
|
||||||
},
|
|
||||||
|
|
||||||
setActiveTheme(state, payload) {
|
|
||||||
state.activeTheme = payload;
|
|
||||||
Vue.$cookies.set("activeTheme", payload);
|
|
||||||
|
|
||||||
const themes = payload ? { dark: payload.colors, light: payload.colors } : null
|
|
||||||
console.log("themes", themes)
|
|
||||||
state.themes = themes;
|
|
||||||
Vue.$cookies.set("themes", themes);
|
|
||||||
Vuetify.framework.theme.themes = themes;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
async initCookies() {
|
|
||||||
//TODO if has no value set to default.
|
|
||||||
if (!Vue.$cookies.isKey("themes") || !Vue.$cookies.isKey("activeTheme")) {
|
|
||||||
const DEFAULT_THEME = await api.themes.requestByName("default");
|
|
||||||
Vue.$cookies.set("themes", {
|
|
||||||
light: DEFAULT_THEME.colors,
|
|
||||||
dark: DEFAULT_THEME.colors,
|
|
||||||
});
|
|
||||||
Vue.$cookies.set("activeTheme", {
|
|
||||||
name: DEFAULT_THEME.name,
|
|
||||||
colors: DEFAULT_THEME.colors
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.commit("setActiveTheme", Vue.$cookies.get("activeTheme"));
|
|
||||||
|
|
||||||
//https://csabaszabo.dev/blog/dark-mode-for-website-with-nuxtjs-and-vuetify/
|
|
||||||
//https://github.com/settings/appearance
|
|
||||||
|
|
||||||
|
|
||||||
// Dark Mode
|
|
||||||
if (!Vue.$cookies.isKey("darkMode")) {
|
|
||||||
Vue.$cookies.set("darkMode", 'system');
|
|
||||||
}
|
|
||||||
this.commit("setDarkMode", Vue.$cookies.get("darkMode"));
|
|
||||||
},
|
|
||||||
|
|
||||||
async requestRecentRecipes() {
|
async requestRecentRecipes() {
|
||||||
const keys = [
|
const keys = [
|
||||||
@ -147,11 +61,6 @@ const store = new Vuex.Store({
|
|||||||
getSnackType: (state) => state.snackType,
|
getSnackType: (state) => state.snackType,
|
||||||
|
|
||||||
getRecentRecipes: (state) => state.recentRecipes,
|
getRecentRecipes: (state) => state.recentRecipes,
|
||||||
|
|
||||||
// Site Settings
|
|
||||||
getDarkMode: (state) => state.darkMode,
|
|
||||||
getThemes: (state) => state.themes,
|
|
||||||
getActiveTheme: (state) => state.activeTheme
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user