feat(webui): change/restore theme even on login page

moved locale and theme from cookies to localStorage
This commit is contained in:
Gauthier Roebroeck 2021-03-26 15:48:15 +08:00
parent 6f5266a28c
commit 7f7c6c3e6f
8 changed files with 186 additions and 75 deletions

View file

@ -27,6 +27,7 @@
"vuelidate": "^0.7.5",
"vuetify": "^2.3.7",
"vuex": "^3.5.1",
"vuex-persistedstate": "^3.2.0",
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {
@ -6618,7 +6619,6 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -17559,6 +17559,11 @@
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
"dev": true
},
"node_modules/shvl": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.2.tgz",
"integrity": "sha512-G3KkIXPza3dgkt6Bo8zIl5K/KvAAhbG6o9KfAjhPvrIIzzAhnfc2ztv1i+iPTbNNM43MaBUqIaZwqVjkSgY/rw=="
},
"node_modules/sigmund": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
@ -19884,6 +19889,19 @@
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz",
"integrity": "sha512-w7oJzmHQs0FM9LXodfskhw9wgKBiaB+totOdb8sNzbTB2KDCEEwEs29NzBZFh/lmEK1t5tDmM1vtsO7ubG1DFw=="
},
"node_modules/vuex-persistedstate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-3.2.0.tgz",
"integrity": "sha512-1Q4zV9cNaJtl59jN6rXbndemEtXKywZr0OFZnqgpYdwvdyy+64KNsEltKldQW+i03st5LuDwHsdOEevXIZUgdg==",
"dependencies": {
"deepmerge": "^4.2.2",
"shvl": "^2.0.2"
},
"peerDependencies": {
"vue": "^2.0.0",
"vuex": "^2.0.0 || ^3.0.0"
}
},
"node_modules/vuex-router-sync": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/vuex-router-sync/-/vuex-router-sync-5.0.0.tgz",
@ -26653,8 +26671,7 @@
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
},
"default-gateway": {
"version": "5.0.5",
@ -35712,6 +35729,11 @@
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
"dev": true
},
"shvl": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.2.tgz",
"integrity": "sha512-G3KkIXPza3dgkt6Bo8zIl5K/KvAAhbG6o9KfAjhPvrIIzzAhnfc2ztv1i+iPTbNNM43MaBUqIaZwqVjkSgY/rw=="
},
"sigmund": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
@ -37677,6 +37699,15 @@
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz",
"integrity": "sha512-w7oJzmHQs0FM9LXodfskhw9wgKBiaB+totOdb8sNzbTB2KDCEEwEs29NzBZFh/lmEK1t5tDmM1vtsO7ubG1DFw=="
},
"vuex-persistedstate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-3.2.0.tgz",
"integrity": "sha512-1Q4zV9cNaJtl59jN6rXbndemEtXKywZr0OFZnqgpYdwvdyy+64KNsEltKldQW+i03st5LuDwHsdOEevXIZUgdg==",
"requires": {
"deepmerge": "^4.2.2",
"shvl": "^2.0.2"
}
},
"vuex-router-sync": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/vuex-router-sync/-/vuex-router-sync-5.0.0.tgz",

View file

@ -30,6 +30,7 @@
"vuelidate": "^0.7.5",
"vuetify": "^2.3.7",
"vuex": "^3.5.1",
"vuex-persistedstate": "^3.2.0",
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {

View file

@ -5,19 +5,69 @@
</template>
<script lang="ts">
import Vue from 'vue'
import {Theme} from "@/types/themes";
const cookieLocale = 'locale'
const cookieTheme = 'theme'
export default Vue.extend({
name: 'App',
created() {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', this.systemThemeChange)
// TODO: remove this after a few months
if (this.$cookies.isKey(cookieLocale)) {
const locale = this.$cookies.get(cookieLocale)
if (this.$i18n.availableLocales.includes(locale)) {
this.$i18n.locale = locale
this.$vuetify.rtl = (this.$t('common.locale_rtl') === 'true')
}
this.$store.commit('setLocale', this.$cookies.get(cookieLocale))
this.$cookies.remove(cookieLocale)
}
if (this.$cookies.isKey(cookieTheme)) {
this.$store.commit('setTheme', this.$cookies.get(cookieTheme))
this.$cookies.remove(cookieTheme)
}
},
beforeDestroy() {
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.systemThemeChange)
},
watch: {
"$store.state.persistedState.locale": {
handler(val) {
if (this.$i18n.availableLocales.includes(val)) {
this.$i18n.locale = val
this.$vuetify.rtl = (this.$t('common.locale_rtl') === 'true')
}
},
immediate: true,
},
"$store.state.persistedState.theme": {
handler(val) {
if (Object.values(Theme).includes(val)) {
this.changeTheme(val)
}
},
immediate: true,
},
},
methods: {
systemThemeChange() {
if (this.$store.state.persistedState.theme === Theme.SYSTEM) {
this.changeTheme(this.$store.state.persistedState.theme)
}
},
changeTheme(theme: Theme) {
switch (theme) {
case Theme.DARK:
this.$vuetify.theme.dark = true
break
case Theme.SYSTEM:
this.$vuetify.theme.dark = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
break
default:
this.$vuetify.theme.dark = false
break
}
},
},
})
</script>

View file

@ -28,16 +28,16 @@ Vue.use(lineClamp)
Vue.use(VueCookies)
Vue.use(httpPlugin)
Vue.use(komgaFileSystem, { http: Vue.prototype.$http })
Vue.use(komgaSeries, { http: Vue.prototype.$http })
Vue.use(komgaCollections, { http: Vue.prototype.$http })
Vue.use(komgaReadLists, { http: Vue.prototype.$http })
Vue.use(komgaBooks, { http: Vue.prototype.$http })
Vue.use(komgaReferential, { http: Vue.prototype.$http })
Vue.use(komgaClaim, { http: Vue.prototype.$http })
Vue.use(komgaUsers, { store: store, http: Vue.prototype.$http })
Vue.use(komgaLibraries, { store: store, http: Vue.prototype.$http })
Vue.use(actuator, { http: Vue.prototype.$http })
Vue.use(komgaFileSystem, {http: Vue.prototype.$http})
Vue.use(komgaSeries, {http: Vue.prototype.$http})
Vue.use(komgaCollections, {http: Vue.prototype.$http})
Vue.use(komgaReadLists, {http: Vue.prototype.$http})
Vue.use(komgaBooks, {http: Vue.prototype.$http})
Vue.use(komgaReferential, {http: Vue.prototype.$http})
Vue.use(komgaClaim, {http: Vue.prototype.$http})
Vue.use(komgaUsers, {store: store, http: Vue.prototype.$http})
Vue.use(komgaLibraries, {store: store, http: Vue.prototype.$http})
Vue.use(actuator, {http: Vue.prototype.$http})
Vue.prototype.$_ = _
Vue.prototype.$eventHub = new Vue()

View file

@ -0,0 +1,17 @@
import {Module} from "vuex"
import {Theme} from "@/types/themes";
export const persistedModule: Module<any, any> = {
state: {
locale: '',
theme: Theme.LIGHT,
},
mutations: {
setLocale (state, val) {
state.locale = val
},
setTheme (state, val) {
state.theme = val
},
},
}

View file

@ -1,10 +1,16 @@
import Vue from 'vue'
import Vuex from 'vuex'
import { BookDto } from '@/types/komga-books'
import {SeriesDto} from "@/types/komga-series";
import {BookDto} from '@/types/komga-books'
import {SeriesDto} from "@/types/komga-series"
import createPersistedState from "vuex-persistedstate"
import {persistedModule} from './plugins/persisted-state'
Vue.use(Vuex)
const persistedState = createPersistedState({
paths: ['persistedState'],
})
export default new Vuex.Store({
state: {
// collections
@ -181,4 +187,8 @@ export default new Vuex.Store({
commit('setUpdateSeriesDialog', value)
},
},
modules: {
persistedState: persistedModule,
},
plugins: [persistedState],
})

View file

@ -102,9 +102,7 @@
<v-list>
<v-list-item>
<v-list-item-icon>
<v-icon v-if="theme === Theme.LIGHT">mdi-brightness-7</v-icon>
<v-icon v-if="theme === Theme.DARK">mdi-brightness-3</v-icon>
<v-icon v-if="theme === Theme.SYSTEM">mdi-brightness-auto</v-icon>
<v-icon>{{ themeIcon }}</v-icon>
</v-list-item-icon>
<v-select
v-model="theme"
@ -152,9 +150,6 @@ import SearchBox from '@/components/SearchBox.vue'
import {Theme} from '@/types/themes'
import Vue from 'vue'
const cookieTheme = 'theme'
const cookieLocale = 'locale'
export default Vue.extend({
name: 'home',
components: {LibraryActionsMenu, SearchBox, Dialogs},
@ -162,10 +157,6 @@ export default Vue.extend({
return {
drawerVisible: this.$vuetify.breakpoint.lgAndUp,
info: {} as ActuatorInfo,
settings: {
theme: Theme.LIGHT,
},
Theme,
locales: this.$i18n.availableLocales.map((x: any) => ({text: this.$i18n.t('common.locale_name', x), value: x})),
}
},
@ -173,18 +164,6 @@ export default Vue.extend({
if (this.isAdmin) {
this.info = await this.$actuator.getInfo()
}
if (this.$cookies.isKey(cookieTheme)) {
const theme = this.$cookies.get(cookieTheme)
if (Object.values(Theme).includes(theme)) {
this.theme = theme as Theme
}
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', this.systemThemeChange)
},
async beforeDestroy() {
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.systemThemeChange)
},
computed: {
libraries(): LibraryDto[] {
@ -200,16 +179,25 @@ export default Vue.extend({
{text: this.$i18n.t(Theme.SYSTEM), value: Theme.SYSTEM},
]
},
themeIcon(): string {
switch (this.theme) {
case Theme.LIGHT:
return 'mdi-brightness-7'
case Theme.DARK:
return 'mdi-brightness-3'
case Theme.SYSTEM:
return 'mdi-brightness-auto'
}
return ''
},
theme: {
get: function (): Theme {
return this.settings.theme
return this.$store.state.persistedState.theme
},
set: function (theme: Theme): void {
if (Object.values(Theme).includes(theme)) {
this.settings.theme = theme
this.changeTheme(theme)
this.$cookies.set(cookieTheme, theme, Infinity)
this.$store.commit('setTheme', theme)
}
},
},
@ -219,9 +207,7 @@ export default Vue.extend({
},
set: function (locale: string): void {
if (this.$i18n.availableLocales.includes(locale)) {
this.$i18n.locale = locale
this.$cookies.set(cookieLocale, locale, Infinity)
this.$vuetify.rtl = (this.$t('common.locale_rtl') === 'true')
this.$store.commit('setLocale', locale)
}
},
},
@ -230,26 +216,6 @@ export default Vue.extend({
toggleDrawer() {
this.drawerVisible = !this.drawerVisible
},
systemThemeChange() {
if (this.theme === Theme.SYSTEM) {
this.changeTheme(this.theme)
}
},
changeTheme(theme: Theme) {
switch (theme) {
case Theme.DARK:
this.$vuetify.theme.dark = true
break
case Theme.SYSTEM:
this.$vuetify.theme.dark = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
break
default:
this.$vuetify.theme.dark = false
break
}
},
logout() {
this.$store.dispatch('logout')
this.$router.push({name: 'login'})

View file

@ -64,7 +64,7 @@
</v-row>
<v-row justify="center">
<v-col cols="12" sm="6" md="4" lg="2" xl="2">
<v-col cols="6" sm="4" md="3" lg="2" xl="1">
<v-select v-model="locale"
:items="locales"
:label="$t('home.translation')"
@ -72,6 +72,15 @@
>
</v-select>
</v-col>
<v-col cols="6" sm="4" md="3" lg="2" xl="1">
<v-select v-model="theme"
:items="themes"
:label="$t('home.theme')"
:prepend-icon="themeIcon"
>
</v-select>
</v-col>
</v-row>
</form>
@ -93,8 +102,7 @@
<script lang="ts">
import Vue from 'vue'
import {email, required} from "vuelidate/lib/validators";
const cookieLocale = 'locale'
import {Theme} from "@/types/themes";
export default Vue.extend({
name: 'Login',
@ -139,9 +147,37 @@ export default Vue.extend({
},
set: function (locale: string): void {
if (this.$i18n.availableLocales.includes(locale)) {
this.$i18n.locale = locale
this.$cookies.set(cookieLocale, locale, Infinity)
this.$vuetify.rtl = (this.$t('common.locale_rtl') === 'true')
this.$store.commit('setLocale', locale)
}
},
},
themes(): object[] {
return [
{text: this.$i18n.t(Theme.LIGHT), value: Theme.LIGHT},
{text: this.$i18n.t(Theme.DARK), value: Theme.DARK},
{text: this.$i18n.t(Theme.SYSTEM), value: Theme.SYSTEM},
]
},
themeIcon(): string {
switch (this.theme) {
case Theme.LIGHT:
return 'mdi-brightness-7'
case Theme.DARK:
return 'mdi-brightness-3'
case Theme.SYSTEM:
return 'mdi-brightness-auto'
}
return ''
},
theme: {
get: function (): Theme {
return this.$store.state.persistedState.theme
},
set: function (theme: Theme): void {
if (Object.values(Theme).includes(theme)) {
this.$store.commit('setTheme', theme)
}
},
},