Added | Persistant Storage, final tweaks to themes

This commit is contained in:
zackbcom 2021-01-07 22:45:24 -06:00
parent 0cdf8b9faa
commit 3e65d2cc9c
6 changed files with 152 additions and 136 deletions

View File

@ -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)

View File

@ -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",

View File

@ -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;

View File

@ -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
);
}
} }
} }
}; };

View 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
}

View File

@ -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
}, },
}); });