mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-03-29 22:20:24 +00:00
feat(ui): Dark Mode (#2493)
This commit is contained in:
parent
2b18e4c73d
commit
4a9130126c
@ -1,60 +1,89 @@
|
||||
<template>
|
||||
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #ffc400">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/" title="Sunshine">
|
||||
<img src="/images/logo-sunshine-45.png" height="45" alt="Sunshine">
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/"><i class="fas fa-fw fa-home"></i> {{ $t('navbar.home') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/pin"><i class="fas fa-fw fa-unlock"></i> {{ $t('navbar.pin') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/apps"><i class="fas fa-fw fa-stream"></i> {{ $t('navbar.applications') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config"><i class="fas fa-fw fa-cog"></i> {{ $t('navbar.configuration') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/password"><i class="fas fa-fw fa-user-shield"></i> {{ $t('navbar.password') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/troubleshooting"><i class="fas fa-fw fa-info"></i> {{ $t('navbar.troubleshoot') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-light navbar-expand-lg navbar-background header">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/" title="Sunshine">
|
||||
<img src="/images/logo-sunshine-45.png" height="45" alt="Sunshine">
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/"><i class="fas fa-fw fa-home"></i> {{ $t('navbar.home') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/pin"><i class="fas fa-fw fa-unlock"></i> {{ $t('navbar.pin') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/apps"><i class="fas fa-fw fa-stream"></i> {{ $t('navbar.applications') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config"><i class="fas fa-fw fa-cog"></i> {{ $t('navbar.configuration') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/password"><i class="fas fa-fw fa-user-shield"></i> {{ $t('navbar.password') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/troubleshooting"><i class="fas fa-fw fa-info"></i> {{ $t('navbar.troubleshoot') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<ThemeToggle/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ThemeToggle from './ThemeToggle.vue'
|
||||
|
||||
export default {
|
||||
created() {
|
||||
console.log("Header mounted!")
|
||||
},
|
||||
mounted() {
|
||||
let el = document.querySelector("a[href='" + document.location.pathname + "']");
|
||||
if (el) el.classList.add("active")
|
||||
let discordWidget = document.createElement('script')
|
||||
discordWidget.setAttribute('src', 'https://app.lizardbyte.dev/js/discord.js')
|
||||
document.head.appendChild(discordWidget)
|
||||
}
|
||||
components: { ThemeToggle },
|
||||
created() {
|
||||
console.log("Header mounted!")
|
||||
},
|
||||
mounted() {
|
||||
let el = document.querySelector("a[href='" + document.location.pathname + "']");
|
||||
if (el) el.classList.add("active")
|
||||
let discordWidget = document.createElement('script')
|
||||
discordWidget.setAttribute('src', 'https://app.lizardbyte.dev/js/discord.js')
|
||||
document.head.appendChild(discordWidget)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.nav-link.active {
|
||||
font-weight: 500;
|
||||
.navbar-background {
|
||||
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 {
|
||||
opacity: 0.5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</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>
|
||||
<html lang="en">
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
@ -355,10 +355,10 @@
|
||||
</div>
|
||||
</body>
|
||||
<script type="module">
|
||||
import { createApp } from 'vue';
|
||||
import { createApp } from 'vue'
|
||||
import { initApp } from './init'
|
||||
import Navbar from './Navbar.vue'
|
||||
import {Dropdown} from 'bootstrap'
|
||||
import { Dropdown } from 'bootstrap/dist/js/bootstrap'
|
||||
|
||||
const app = createApp({
|
||||
components: {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
@ -13,12 +13,6 @@
|
||||
.buttons {
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
.ms-item {
|
||||
background-color: #ccc;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
@ -87,3 +87,6 @@ const config = ref(props.config)
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
@ -65,3 +65,11 @@ const fpsIn = ref("")
|
||||
<div class="form-text">{{ $t('config.res_fps_desc') }}</div>
|
||||
</div>
|
||||
</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>
|
||||
<html lang="en">
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
|
@ -1,5 +1,10 @@
|
||||
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) {
|
||||
//Wait for locale initialization, then render
|
||||
i18n().then(i18n => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
|
@ -2,3 +2,15 @@
|
||||
[v-cloak] {
|
||||
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",
|
||||
"password": "Change Password",
|
||||
"pin": "Pin",
|
||||
"theme_auto": "Auto",
|
||||
"theme_dark": "Dark",
|
||||
"theme_light": "Light",
|
||||
"toggle_theme": "Theme",
|
||||
"troubleshoot": "Troubleshooting"
|
||||
},
|
||||
"password": {
|
||||
|
@ -7,4 +7,3 @@
|
||||
<link href="@fortawesome/fontawesome-free/css/all.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/dist/css/bootstrap.min.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>
|
||||
<html lang="en">
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
|
Loading…
x
Reference in New Issue
Block a user