feat(ui): Dark Mode (#2493)
This commit is contained in:
@@ -1,60 +1,89 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #ffc400">
|
<nav class="navbar navbar-light navbar-expand-lg navbar-background header">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/" title="Sunshine">
|
<a class="navbar-brand" href="/" title="Sunshine">
|
||||||
<img src="/images/logo-sunshine-45.png" height="45" alt="Sunshine">
|
<img src="/images/logo-sunshine-45.png" height="45" alt="Sunshine">
|
||||||
</a>
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/"><i class="fas fa-fw fa-home"></i> {{ $t('navbar.home') }}</a>
|
<a class="nav-link" href="/"><i class="fas fa-fw fa-home"></i> {{ $t('navbar.home') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/pin"><i class="fas fa-fw fa-unlock"></i> {{ $t('navbar.pin') }}</a>
|
<a class="nav-link" href="/pin"><i class="fas fa-fw fa-unlock"></i> {{ $t('navbar.pin') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/apps"><i class="fas fa-fw fa-stream"></i> {{ $t('navbar.applications') }}</a>
|
<a class="nav-link" href="/apps"><i class="fas fa-fw fa-stream"></i> {{ $t('navbar.applications') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/config"><i class="fas fa-fw fa-cog"></i> {{ $t('navbar.configuration') }}</a>
|
<a class="nav-link" href="/config"><i class="fas fa-fw fa-cog"></i> {{ $t('navbar.configuration') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/password"><i class="fas fa-fw fa-user-shield"></i> {{ $t('navbar.password') }}</a>
|
<a class="nav-link" href="/password"><i class="fas fa-fw fa-user-shield"></i> {{ $t('navbar.password') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/troubleshooting"><i class="fas fa-fw fa-info"></i> {{ $t('navbar.troubleshoot') }}</a>
|
<a class="nav-link" href="/troubleshooting"><i class="fas fa-fw fa-info"></i> {{ $t('navbar.troubleshoot') }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li class="nav-item">
|
||||||
</div>
|
<ThemeToggle/>
|
||||||
</div>
|
</li>
|
||||||
</nav>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ThemeToggle from './ThemeToggle.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
created() {
|
components: { ThemeToggle },
|
||||||
console.log("Header mounted!")
|
created() {
|
||||||
},
|
console.log("Header mounted!")
|
||||||
mounted() {
|
},
|
||||||
let el = document.querySelector("a[href='" + document.location.pathname + "']");
|
mounted() {
|
||||||
if (el) el.classList.add("active")
|
let el = document.querySelector("a[href='" + document.location.pathname + "']");
|
||||||
let discordWidget = document.createElement('script')
|
if (el) el.classList.add("active")
|
||||||
discordWidget.setAttribute('src', 'https://app.lizardbyte.dev/js/discord.js')
|
let discordWidget = document.createElement('script')
|
||||||
document.head.appendChild(discordWidget)
|
discordWidget.setAttribute('src', 'https://app.lizardbyte.dev/js/discord.js')
|
||||||
}
|
document.head.appendChild(discordWidget)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.nav-link.active {
|
.navbar-background {
|
||||||
font-weight: 500;
|
background-color: #ffc400
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .nav-link {
|
||||||
|
color: rgba(0, 0, 0, .65) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .nav-link.active {
|
||||||
|
color: rgb(0, 0, 0) !important;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .nav-link:hover {
|
||||||
|
color: rgb(0, 0, 0) !important;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .navbar-toggler {
|
||||||
|
color: rgba(var(--bs-dark-rgb), .65) !important;
|
||||||
|
border: var(--bs-border-width) solid rgba(var(--bs-dark-rgb), 0.15) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .navbar-toggler-icon {
|
||||||
|
--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control::placeholder {
|
.form-control::placeholder {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
46
src_assets/common/assets/web/ThemeToggle.vue
Normal file
46
src_assets/common/assets/web/ThemeToggle.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<script setup>
|
||||||
|
import { loadAutoTheme, setupThemeToggleListener } from './theme'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadAutoTheme()
|
||||||
|
setupThemeToggleListener()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="dropdown bd-mode-toggle">
|
||||||
|
<a class="nav-link dropdown-toggle align-items-center"
|
||||||
|
id="bd-theme"
|
||||||
|
type="button"
|
||||||
|
aria-expanded="false"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-label="{{ $t('navbar.toggle_theme') }} ({{ $t('navbar.theme_auto') }})">
|
||||||
|
<span class="bi my-1 theme-icon-active"><i class="fa-solid fa-circle-half-stroke"></i></span>
|
||||||
|
<span id="bd-theme-text">{{ $t('navbar.toggle_theme') }}</span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme-text">
|
||||||
|
<li>
|
||||||
|
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
|
||||||
|
<i class="bi me-2 theme-icon fas fa-fw fa-solid fa-sun"></i>
|
||||||
|
{{ $t('navbar.theme_light') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark" aria-pressed="false">
|
||||||
|
<i class="bi me-2 theme-icon fas fa-fw fa-solid fa-moon"></i>
|
||||||
|
{{ $t('navbar.theme_dark') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto" aria-pressed="true">
|
||||||
|
<i class="bi me-2 theme-icon fas fa-fw fa-solid fa-circle-half-stroke"></i>
|
||||||
|
{{ $t('navbar.theme_auto') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-bs-theme="auto">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<%- header %>
|
<%- header %>
|
||||||
@@ -355,10 +355,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue'
|
||||||
import { initApp } from './init'
|
import { initApp } from './init'
|
||||||
import Navbar from './Navbar.vue'
|
import Navbar from './Navbar.vue'
|
||||||
import {Dropdown} from 'bootstrap'
|
import { Dropdown } from 'bootstrap/dist/js/bootstrap'
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-bs-theme="auto">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<%- header %>
|
<%- header %>
|
||||||
@@ -13,12 +13,6 @@
|
|||||||
.buttons {
|
.buttons {
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ms-item {
|
|
||||||
background-color: #ccc;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -87,3 +87,6 @@ const config = ref(props.config)
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -65,3 +65,11 @@ const fpsIn = ref("")
|
|||||||
<div class="form-text">{{ $t('config.res_fps_desc') }}</div>
|
<div class="form-text">{{ $t('config.res_fps_desc') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ms-item {
|
||||||
|
background-color: var(--bs-dark-bg-subtle);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-bs-theme="auto">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<%- header %>
|
<%- header %>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import i18n from './locale'
|
import i18n from './locale'
|
||||||
|
|
||||||
|
// must import even if not implicitly using here
|
||||||
|
// https://github.com/aurelia/skeleton-navigation/issues/894
|
||||||
|
// https://discourse.aurelia.io/t/bootstrap-import-bootstrap-breaks-dropdown-menu-in-navbar/641/9
|
||||||
|
import 'bootstrap/dist/js/bootstrap'
|
||||||
|
|
||||||
export function initApp(app, config) {
|
export function initApp(app, config) {
|
||||||
//Wait for locale initialization, then render
|
//Wait for locale initialization, then render
|
||||||
i18n().then(i18n => {
|
i18n().then(i18n => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-bs-theme="auto">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<%- header %>
|
<%- header %>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-bs-theme="auto">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<%- header %>
|
<%- header %>
|
||||||
|
|||||||
@@ -2,3 +2,15 @@
|
|||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-bs-theme=dark] .element {
|
||||||
|
color: var(--bs-primary-text-emphasis);
|
||||||
|
background-color: var(--bs-primary-bg-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.element {
|
||||||
|
color: var(--bs-primary-text-emphasis);
|
||||||
|
background-color: var(--bs-primary-bg-subtle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -337,6 +337,10 @@
|
|||||||
"home": "Home",
|
"home": "Home",
|
||||||
"password": "Change Password",
|
"password": "Change Password",
|
||||||
"pin": "Pin",
|
"pin": "Pin",
|
||||||
|
"theme_auto": "Auto",
|
||||||
|
"theme_dark": "Dark",
|
||||||
|
"theme_light": "Light",
|
||||||
|
"toggle_theme": "Theme",
|
||||||
"troubleshoot": "Troubleshooting"
|
"troubleshoot": "Troubleshooting"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
|
|||||||
@@ -7,4 +7,3 @@
|
|||||||
<link href="@fortawesome/fontawesome-free/css/all.min.css" rel="stylesheet">
|
<link href="@fortawesome/fontawesome-free/css/all.min.css" rel="stylesheet">
|
||||||
<link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
|
<link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
<link href="/assets/css/sunshine.css" rel="stylesheet" />
|
<link href="/assets/css/sunshine.css" rel="stylesheet" />
|
||||||
<script type="module" src="bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
|
|||||||
84
src_assets/common/assets/web/theme.js
Normal file
84
src_assets/common/assets/web/theme.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
const getStoredTheme = () => localStorage.getItem('theme')
|
||||||
|
const setStoredTheme = theme => localStorage.setItem('theme', theme)
|
||||||
|
|
||||||
|
export const getPreferredTheme = () => {
|
||||||
|
const storedTheme = getStoredTheme()
|
||||||
|
if (storedTheme) {
|
||||||
|
return storedTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTheme = theme => {
|
||||||
|
if (theme === 'auto') {
|
||||||
|
document.documentElement.setAttribute(
|
||||||
|
'data-bs-theme',
|
||||||
|
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute('data-bs-theme', theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showActiveTheme = (theme, focus = false) => {
|
||||||
|
const themeSwitcher = document.querySelector('#bd-theme')
|
||||||
|
|
||||||
|
if (!themeSwitcher) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeSwitcherText = document.querySelector('#bd-theme-text')
|
||||||
|
const activeThemeIcon = document.querySelector('.theme-icon-active i')
|
||||||
|
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
|
||||||
|
const classListOfActiveBtn = btnToActive.querySelector('i').classList
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
|
||||||
|
element.classList.remove('active')
|
||||||
|
element.setAttribute('aria-pressed', 'false')
|
||||||
|
})
|
||||||
|
|
||||||
|
btnToActive.classList.add('active')
|
||||||
|
btnToActive.setAttribute('aria-pressed', 'true')
|
||||||
|
activeThemeIcon.classList.remove(...activeThemeIcon.classList.values())
|
||||||
|
activeThemeIcon.classList.add(...classListOfActiveBtn)
|
||||||
|
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.textContent.trim()})`
|
||||||
|
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
|
||||||
|
|
||||||
|
if (focus) {
|
||||||
|
themeSwitcher.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupThemeToggleListener() {
|
||||||
|
document.querySelectorAll('[data-bs-theme-value]')
|
||||||
|
.forEach(toggle => {
|
||||||
|
toggle.addEventListener('click', () => {
|
||||||
|
const theme = toggle.getAttribute('data-bs-theme-value')
|
||||||
|
setStoredTheme(theme)
|
||||||
|
setTheme(theme)
|
||||||
|
showActiveTheme(theme, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
showActiveTheme(getPreferredTheme(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadAutoTheme() {
|
||||||
|
(() => {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
setTheme(getPreferredTheme())
|
||||||
|
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||||
|
const storedTheme = getStoredTheme()
|
||||||
|
if (storedTheme !== 'light' && storedTheme !== 'dark') {
|
||||||
|
setTheme(getPreferredTheme())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
showActiveTheme(getPreferredTheme())
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-bs-theme="auto">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<%- header %>
|
<%- header %>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-bs-theme="auto">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<%- header %>
|
<%- header %>
|
||||||
|
|||||||
Reference in New Issue
Block a user