Added | System Dark mode. Streamlined themes

This commit is contained in:
zackbcom 2021-01-05 23:26:59 -06:00
parent de17085e04
commit b099da573c
3 changed files with 190 additions and 93 deletions

View File

@ -2,7 +2,7 @@
<v-app> <v-app>
<v-app-bar dense app color="primary" dark class="d-print-none"> <v-app-bar dense app color="primary" dark class="d-print-none">
<v-btn @click="$router.push('/')" icon class="d-flex align-center"> <v-btn @click="$router.push('/')" icon class="d-flex align-center">
<v-icon size="40" > <v-icon size="40">
mdi-silverware-variant mdi-silverware-variant
</v-icon> </v-icon>
</v-btn> </v-btn>
@ -44,32 +44,43 @@ export default {
Menu, Menu,
AddRecipe, AddRecipe,
SearchHeader, SearchHeader,
SnackBar, SnackBar
}, },
watch: { watch: {
$route() { $route() {
this.search = false; this.search = false;
}, }
}, },
mounted() { mounted() {
this.$store.dispatch("initCookies"); this.$store.dispatch("initCookies");
this.$store.dispatch("requestRecentRecipes"); this.$store.dispatch("requestRecentRecipes");
this.darkModeAddEventListener();
}, },
data: () => ({ data: () => ({
search: false, search: false
}), }),
methods: { methods: {
/**
* 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.$store.commit("setDarkMode", "system");
});
},
toggleSearch() { toggleSearch() {
if (this.search === true) { if (this.search === true) {
this.search = false; this.search = false;
} else { } else {
this.search = true; this.search = true;
} }
}, }
}, }
}; };
</script> </script>

View File

@ -1,69 +1,114 @@
<template> <template>
<v-card> <v-card>
<v-card-title class="secondary white--text"> Theme Settings </v-card-title> <v-card-title class="secondary white--text"> Theme Settings </v-card-title>
<v-card-text> <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="darkMode"
color="primary "
mandatory
@change="setDarkMode"
>
<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> <p>
Select a theme from the dropdown or create a new theme. Note that the 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 default theme will be served to all users who have not set a theme
preference. preference.
</p> </p>
<v-row dense align="center">
<v-col cols="12" md="2" sm="5"> <v-form ref="form" lazy-validation>
<v-switch <v-row dense align="center">
v-model="darkMode" <v-col cols="12" md="4" sm="3">
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-select <v-select
label="Saved Color Schemes" label="Saved Color Theme"
:items="availableThemes" :items="availableThemes"
item-text="name" item-text="name"
item-value="colors"
return-object return-object
v-model="selectedScheme" v-model="activeTheme"
@change="themeSelected" @change="themeSelected"
:rules="[(v) => !!v || 'Theme is required']" :rules="[v => !!v || 'Theme is required']"
required required
> >
</v-select> </v-select>
</v-form> </v-col>
</v-col> <v-col cols="12" sm="1">
<v-col cols="12" sm="1"> <NewTheme @new-theme="appendTheme" />
<NewTheme @new-theme="appendTheme" /> </v-col>
</v-col> <v-col cols="12" sm="1">
<v-col cols="12" sm="1"> <v-btn text color="error" @click="deleteSelectedThemeValidation">
<v-btn text color="error" @click="deleteSelected"> Delete </v-btn> Delete
</v-col> </v-btn>
</v-row> <Confirmation
<v-row dense align-content="center" v-if="activeTheme"> 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="activeTheme.colors">
<v-col> <v-col>
<ColorPicker button-text="Primary" v-model="activeTheme.primary" /> <ColorPicker
button-text="Primary"
v-model="activeTheme.colors.primary"
/>
</v-col> </v-col>
<v-col> <v-col>
<ColorPicker <ColorPicker
button-text="Secondary" button-text="Secondary"
v-model="activeTheme.secondary" v-model="activeTheme.colors.secondary"
/> />
</v-col> </v-col>
<v-col> <v-col>
<ColorPicker button-text="Accent" v-model="activeTheme.accent" /> <ColorPicker
button-text="Accent"
v-model="activeTheme.colors.accent"
/>
</v-col> </v-col>
<v-col> <v-col>
<ColorPicker button-text="Success" v-model="activeTheme.success" /> <ColorPicker
button-text="Success"
v-model="activeTheme.colors.success"
/>
</v-col> </v-col>
<v-col> <v-col>
<ColorPicker button-text="Info" v-model="activeTheme.info" /> <ColorPicker button-text="Info" v-model="activeTheme.colors.info" />
</v-col> </v-col>
<v-col> <v-col>
<ColorPicker button-text="Warning" v-model="activeTheme.warning" /> <ColorPicker
button-text="Warning"
v-model="activeTheme.colors.warning"
/>
</v-col> </v-col>
<v-col> <v-col>
<ColorPicker button-text="Error" v-model="activeTheme.error" /> <ColorPicker button-text="Error" v-model="activeTheme.colors.error" />
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
@ -84,73 +129,74 @@
import api from "../../api"; import api from "../../api";
import ColorPicker from "./ThemeUI/ColorPicker"; import ColorPicker from "./ThemeUI/ColorPicker";
import NewTheme from "./ThemeUI/NewTheme"; import NewTheme from "./ThemeUI/NewTheme";
import Confirmation from "../UI/Confirmation";
export default { export default {
components: { components: {
ColorPicker, ColorPicker,
NewTheme, Confirmation,
NewTheme
}, },
data() { data() {
return { return {
themes: null,
activeTheme: {}, activeTheme: {},
darkMode: false, darkMode: "system",
availableThemes: [], availableThemes: []
selectedScheme: "",
selectedLight: "",
}; };
}, },
async mounted() { async mounted() {
this.availableThemes = await api.themes.requestAll(); this.availableThemes = await api.themes.requestAll();
this.activeTheme = this.$store.getters.getActiveTheme;
this.darkMode = this.$store.getters.getDarkMode; this.darkMode = this.$store.getters.getDarkMode;
this.themes = this.$store.getters.getThemes;
this.setThemeEditor();
}, },
methods: { methods: {
async deleteSelected() { deleteSelectedThemeValidation() {
if (this.$refs.form.validate()) { if (this.$refs.form.validate()) {
if (this.selectedScheme === "default") { if (this.activeTheme.name === "default") {
// Notify User Can't Delete Default // Notify User Can't Delete Default
} else if (this.selectedScheme !== "") { } else if (this.activeTheme !== {}) {
api.themes.delete(this.selectedScheme.name); this.$refs.deleteThemeConfirm.open();
} }
this.availableThemes = await api.themes.requestAll(); }
},
async deleteSelectedTheme() {
api.themes.delete(this.activeTheme.name);
this.availableThemes = await api.themes.requestAll();
//Change to default if deleting current theme.
if (
!this.availableThemes.some(
theme => theme.name === this.activeTheme.name
)
) {
this.$store.commit("setActiveTheme", null);
this.activeTheme = this.$store.getters.getActiveTheme;
} }
}, },
async appendTheme(newTheme) { async appendTheme(newTheme) {
api.themes.create(newTheme); api.themes.create(newTheme);
this.availableThemes.push(newTheme); this.availableThemes.push(newTheme);
this.activeTheme = newTheme;
}, },
themeSelected() { themeSelected() {
this.activeTheme = this.selectedScheme.colors; console.log("this.activeTheme", this.activeTheme);
}, },
setThemeEditor() {
if (this.darkMode) {
this.activeTheme = this.themes.dark;
} else {
this.activeTheme = this.themes.light;
}
},
toggleDarkMode() {
this.$store.commit("setDarkMode", this.darkMode);
this.selectedScheme = "";
this.setThemeEditor(); setDarkMode() {
this.$store.commit("setDarkMode", this.darkMode);
}, },
saveThemes() { /**
* This will save the current colors and make the selected theme live.
*/
async saveThemes() {
if (this.$refs.form.validate()) { if (this.$refs.form.validate()) {
if (this.darkMode) { this.$store.commit("setActiveTheme", this.activeTheme);
this.themes.dark = this.activeTheme;
} else {
this.themes.light = this.activeTheme;
}
this.$store.commit("setThemes", this.themes);
this.$store.dispatch("initCookies"); this.$store.dispatch("initCookies");
api.themes.update(this.selectedScheme.name, this.activeTheme); api.themes.update(this.activeTheme.name, this.activeTheme);
} else; } else;
}, }
}, }
}; };
</script> </script>

View File

@ -17,25 +17,37 @@ const store = new Vuex.Store({
allRecipes: [], allRecipes: [],
// Site Settings // Site Settings
darkMode: false, darkMode: 'system',
activeTheme: {
name: 'default',
colors: {
primary: "#E58325",
accent: "#00457A",
secondary: "#973542",
success: "#5AB1BB",
info: "#4990BA",
warning: "#FF4081",
error: "#EF5350",
}
},
themes: { themes: {
light: { light: {
primary: "#E58325", primary: "#E58325",
accent: "#00457A", accent: "#00457A",
secondary: "#973542", secondary: "#973542",
success: "#43A047", success: "#5AB1BB",
info: "#FFFD99", info: "#4990BA",
warning: "#FF4081", warning: "#FF4081",
error: "#EF5350", error: "#EF5350",
}, },
dark: { dark: {
primary: "#4527A0", primary: "#E58325",
accent: "#FF4081", accent: "#00457A",
secondary: "#26C6DA", secondary: "#973542",
success: "#43A047", success: "#5AB1BB",
info: "#2196F3", info: "#4990BA",
warning: "#FB8C00", warning: "#FF4081",
error: "#FF5252", error: "#EF5350",
}, },
}, },
}, },
@ -55,35 +67,62 @@ const store = new Vuex.Store({
}, },
setDarkMode(state, payload) { setDarkMode(state, payload) {
let isDark;
state.darkMode = payload; state.darkMode = payload;
Vue.$cookies.set("darkMode", payload); Vue.$cookies.set("darkMode", payload);
Vuetify.framework.theme.dark = 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;
}, },
setThemes(state, payload) { setActiveTheme(state, payload) {
state.themes = payload; state.activeTheme = payload;
Vue.$cookies.set("themes", payload); Vue.$cookies.set("activeTheme", payload);
Vuetify.framework.theme.themes = 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() { async initCookies() {
if (!Vue.$cookies.isKey("themes")) { //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"); const DEFAULT_THEME = await api.themes.requestByName("default");
Vue.$cookies.set("themes", { Vue.$cookies.set("themes", {
light: DEFAULT_THEME.colors, light: DEFAULT_THEME.colors,
dark: DEFAULT_THEME.colors, dark: DEFAULT_THEME.colors,
}); });
Vue.$cookies.set("activeTheme", {
name: DEFAULT_THEME.name,
colors: DEFAULT_THEME.colors
});
} }
this.commit("setThemes", Vue.$cookies.get("themes")); 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 // Dark Mode
if (!Vue.$cookies.isKey("darkMode")) { if (!Vue.$cookies.isKey("darkMode")) {
Vue.$cookies.set("darkMode", false); Vue.$cookies.set("darkMode", 'system');
} }
this.commit("setDarkMode", JSON.parse(Vue.$cookies.get("darkMode"))); this.commit("setDarkMode", Vue.$cookies.get("darkMode"));
}, },
async requestRecentRecipes() { async requestRecentRecipes() {
@ -112,6 +151,7 @@ const store = new Vuex.Store({
// Site Settings // Site Settings
getDarkMode: (state) => state.darkMode, getDarkMode: (state) => state.darkMode,
getThemes: (state) => state.themes, getThemes: (state) => state.themes,
getActiveTheme: (state) => state.activeTheme
}, },
}); });