mirror of
https://github.com/gotson/komga.git
synced 2025-12-20 23:45:11 +01:00
feat(webui): change/restore theme even on login page
moved locale and theme from cookies to localStorage
This commit is contained in:
parent
6f5266a28c
commit
7f7c6c3e6f
8 changed files with 186 additions and 75 deletions
37
komga-webui/package-lock.json
generated
37
komga-webui/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
17
komga-webui/src/plugins/persisted-state.ts
Normal file
17
komga-webui/src/plugins/persisted-state.ts
Normal 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
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -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],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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'})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue