Skip to content

Commit

Permalink
feat(ui): Dark Mode (LizardByte#2493)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hazer authored and KuleRucket committed Jun 6, 2024
1 parent c5d318c commit 5101ca7
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 62 deletions.
121 changes: 75 additions & 46 deletions src_assets/common/assets/web/Navbar.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 46 additions & 0 deletions src_assets/common/assets/web/ThemeToggle.vue
Original file line number Diff line number Diff line change
@@ -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>
6 changes: 3 additions & 3 deletions src_assets/common/assets/web/apps.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-bs-theme="auto">

<head>
<%- header %>
Expand Down Expand Up @@ -355,10 +355,10 @@ <h4>{{ $t('apps.env_vars_about') }}</h4>
</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: {
Expand Down
8 changes: 1 addition & 7 deletions src_assets/common/assets/web/config.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-bs-theme="auto">

<head>
<%- header %>
Expand All @@ -13,12 +13,6 @@
.buttons {
padding: 1em 0;
}

.ms-item {
background-color: #ccc;
font-size: 12px;
font-weight: bold;
}
</style>
</head>

Expand Down
3 changes: 3 additions & 0 deletions src_assets/common/assets/web/configs/tabs/AudioVideo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,6 @@ const config = ref(props.config)

</div>
</template>

<style scoped>
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -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>
2 changes: 1 addition & 1 deletion src_assets/common/assets/web/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-bs-theme="auto">

<head>
<%- header %>
Expand Down
5 changes: 5 additions & 0 deletions src_assets/common/assets/web/init.js
Original file line number Diff line number Diff line change
@@ -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 => {
Expand Down
2 changes: 1 addition & 1 deletion src_assets/common/assets/web/password.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-bs-theme="auto">

<head>
<%- header %>
Expand Down
2 changes: 1 addition & 1 deletion src_assets/common/assets/web/pin.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-bs-theme="auto">

<head>
<%- header %>
Expand Down
12 changes: 12 additions & 0 deletions src_assets/common/assets/web/public/assets/css/sunshine.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
4 changes: 4 additions & 0 deletions src_assets/common/assets/web/public/assets/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
1 change: 0 additions & 1 deletion src_assets/common/assets/web/template_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 84 additions & 0 deletions src_assets/common/assets/web/theme.js
Original file line number Diff line number Diff line change
@@ -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())
})
})()
}
2 changes: 1 addition & 1 deletion src_assets/common/assets/web/troubleshooting.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-bs-theme="auto">

<head>
<%- header %>
Expand Down
Loading

0 comments on commit 5101ca7

Please sign in to comment.