webapp: Implement dark theme
This commit is contained in:
parent
b91da0f681
commit
95741c7fa2
@ -12,7 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap": "^5.3.0-alpha1",
|
||||
"bootstrap-icons-vue": "^1.8.1",
|
||||
"mitt": "^3.0.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
|
||||
<nav class="navbar navbar-expand-md fixed-top bg-body-tertiary" data-bs-theme="dark">
|
||||
<div class="container-fluid">
|
||||
<router-link @click="onClick" class="navbar-brand" to="/">
|
||||
<span v-if="isXmas" class="text-success">
|
||||
@ -95,11 +95,14 @@
|
||||
<router-link @click="onClick" class="nav-link" to="/about">{{ $t('menu.About') }}</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="d-flex" role="search">
|
||||
<LocaleSwitcher class="me-2" />
|
||||
<button v-if="isLogged" class="btn btn-outline-danger" @click="signout">{{ $t('menu.Logout') }}</button>
|
||||
<button v-if="!isLogged" class="btn btn-outline-success" @click="signin">{{ $t('menu.Login') }}</button>
|
||||
</form>
|
||||
<ul class="navbar-nav flex-row flex-wrap ms-md-auto">
|
||||
<ThemeSwitcher class="me-2" />
|
||||
<form class="d-flex" role="search">
|
||||
<LocaleSwitcher class="me-2" />
|
||||
<button v-if="isLogged" class="btn btn-outline-danger" @click="signout">{{ $t('menu.Logout') }}</button>
|
||||
<button v-if="!isLogged" class="btn btn-outline-success" @click="signin">{{ $t('menu.Login') }}</button>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@ -110,6 +113,7 @@ import { isLoggedIn, logout } from '@/utils/authentication';
|
||||
import { BIconEgg, BIconSun, BIconTree } from 'bootstrap-icons-vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import LocaleSwitcher from './LocaleSwitcher.vue';
|
||||
import ThemeSwitcher from './ThemeSwitcher.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@ -117,6 +121,7 @@ export default defineComponent({
|
||||
BIconSun,
|
||||
BIconTree,
|
||||
LocaleSwitcher,
|
||||
ThemeSwitcher,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
106
webapp/src/components/ThemeSwitcher.vue
Normal file
106
webapp/src/components/ThemeSwitcher.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<li class="nav-item dropdown">
|
||||
<button class="btn btn-link nav-link py-2 px-0 px-lg-2 dropdown-toggle d-flex align-items-center" id="bd-theme"
|
||||
type="button" aria-expanded="false" data-bs-toggle="dropdown" data-bs-display="static"
|
||||
aria-label="Toggle theme (auto)">
|
||||
<BIconCircleHalf class="bi my-1 theme-icon-active" />
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light"
|
||||
aria-pressed="false">
|
||||
<BIconSunFill class="bi me-2 opacity-50 theme-icon" />
|
||||
{{ $t('localeswitcher.Light') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark"
|
||||
aria-pressed="false">
|
||||
<BIconMoonStarsFill class="bi me-2 opacity-50 theme-icon" />
|
||||
{{ $t('localeswitcher.Dark') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto"
|
||||
aria-pressed="true">
|
||||
<BIconCircleHalf class="bi me-2 opacity-50 theme-icon" />
|
||||
{{ $t('localeswitcher.Auto') }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
BIconCircleHalf,
|
||||
BIconSunFill,
|
||||
BIconMoonStarsFill,
|
||||
} from 'bootstrap-icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: "ThemeSwitcher",
|
||||
components: {
|
||||
BIconCircleHalf,
|
||||
BIconSunFill,
|
||||
BIconMoonStarsFill,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
storedTheme: 'auto',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getPreferredTheme() {
|
||||
if (this.storedTheme) {
|
||||
return this.storedTheme;
|
||||
}
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
},
|
||||
setTheme(theme: string) {
|
||||
if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-bs-theme', theme);
|
||||
}
|
||||
},
|
||||
showActiveTheme(theme: string) {
|
||||
const activeThemeIcon = document.querySelector('.theme-icon-active');
|
||||
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
|
||||
const svgOfActiveBtn = btnToActive?.querySelector('.theme-icon');
|
||||
|
||||
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
|
||||
element.classList.remove('active');
|
||||
})
|
||||
|
||||
btnToActive?.classList.add('active');
|
||||
|
||||
if (svgOfActiveBtn) {
|
||||
activeThemeIcon?.replaceChildren('*', svgOfActiveBtn?.cloneNode(true));
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.storedTheme = localStorage.getItem('theme') || 'auto';
|
||||
this.setTheme(this.getPreferredTheme());
|
||||
this.showActiveTheme(this.getPreferredTheme());
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
if (this.storedTheme !== 'light' && this.storedTheme !== 'dark') {
|
||||
this.setTheme(this.getPreferredTheme());
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-bs-theme-value]')
|
||||
.forEach(toggle => {
|
||||
toggle.addEventListener('click', () => {
|
||||
const theme = toggle.getAttribute('data-bs-theme-value') || 'auto';
|
||||
localStorage.setItem('theme', theme);
|
||||
this.setTheme(theme);
|
||||
this.showActiveTheme(theme);
|
||||
})
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -25,6 +25,11 @@
|
||||
"base": {
|
||||
"Loading": "Lade..."
|
||||
},
|
||||
"localeswitcher": {
|
||||
"Dark": "Dunkel",
|
||||
"Light": "Hell",
|
||||
"Auto": "Automatisch"
|
||||
},
|
||||
"apiresponse": {
|
||||
"1001": "Einstellungen gespeichert!",
|
||||
"1002": "Keine Werte gefunden!",
|
||||
|
||||
@ -25,6 +25,11 @@
|
||||
"base": {
|
||||
"Loading": "Loading..."
|
||||
},
|
||||
"localeswitcher": {
|
||||
"Dark": "Dark",
|
||||
"Light": "Light",
|
||||
"Auto": "Auto"
|
||||
},
|
||||
"apiresponse": {
|
||||
"1001": "Settings saved!",
|
||||
"1002": "No values found!",
|
||||
|
||||
@ -25,6 +25,11 @@
|
||||
"base": {
|
||||
"Loading": "Chargement..."
|
||||
},
|
||||
"localeswitcher": {
|
||||
"Dark": "Dark",
|
||||
"Light": "Light",
|
||||
"Auto": "Auto"
|
||||
},
|
||||
"apiresponse": {
|
||||
"1001": "Paramètres enregistrés !",
|
||||
"1002": "Aucune valeur trouvée !",
|
||||
|
||||
@ -701,10 +701,10 @@ bootstrap-icons-vue@^1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/bootstrap-icons-vue/-/bootstrap-icons-vue-1.8.1.tgz#ce4a0c1f6efe41dabcc1341f2cb191d307fbaf50"
|
||||
integrity sha512-uItRULwQz0epETi9x/RBEqfjHmTAmkIIczpH1R6L9T6yyaaijk0826PzTWnWNm15tw66JT/8GNuXjB0HI5PHLw==
|
||||
|
||||
bootstrap@^5.2.3:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.3.tgz#54739f4414de121b9785c5da3c87b37ff008322b"
|
||||
integrity sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==
|
||||
bootstrap@^5.3.0-alpha1:
|
||||
version "5.3.0-alpha1"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.0-alpha1.tgz#380629c4367893f02f7879a01ea3ae0f94e2e70e"
|
||||
integrity sha512-ABZpKK4ObS3kKlIqH+ZVDqoy5t/bhFG0oHTAzByUdon7YIom0lpCeTqRniDzJmbtcWkNe800VVPBiJgxSYTYew==
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user