From 3c9d9ea4025b2aed07b312f8038deb6804240dda Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 27 Oct 2022 17:35:36 -0600 Subject: [PATCH] Adding systemTheme to match the current system theme. darkTheme and lightTheme now override system preference. --- app/src/components/Common.js | 26 ++++++----- app/src/components/Theme.js | 84 ++++++++++++++++++++++++++++-------- 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/app/src/components/Common.js b/app/src/components/Common.js index 63247630..690ce0d9 100644 --- a/app/src/components/Common.js +++ b/app/src/components/Common.js @@ -4,7 +4,7 @@ import {Link, NavLink} from "react-router-dom"; import Message from "semantic-ui-react/dist/commonjs/collections/Message"; import {useDownloaders, useHotspot, useThrottle} from "../hooks/customHooks"; import {StatusContext, ThemeContext} from "../contexts/contexts"; -import {Button, CardIcon, darkTheme, Form, Header, Icon, Menu, Popup, Statistic} from "./Theme"; +import {Button, CardIcon, darkTheme, Form, Header, Icon, lightTheme, Menu, Popup, Statistic} from "./Theme"; export const API_URI = `http://${window.location.host}/api`; export const VIDEOS_API = `${API_URI}/videos`; @@ -708,21 +708,19 @@ export function LoadStatistic({label, value, cores, ...props}) { } export function DarkModeToggle() { - const {theme, setDarkTheme, setLightTheme} = useContext(ThemeContext); - const darkMode = theme === darkTheme; - - const toggleDarkMode = () => { - // Save the selection. The user manually chose the theme. - if (darkMode) { - setLightTheme(true); - } else { - setDarkTheme(true); - } + const {savedTheme, cycleSavedTheme} = useContext(ThemeContext); + let iconName = 'lightbulb outline'; + if (savedTheme === darkTheme) { + iconName = 'moon'; + } else if (savedTheme === lightTheme) { + iconName = 'sun'; } - return <> - - + return } diff --git a/app/src/components/Theme.js b/app/src/components/Theme.js index cfa5ed0f..c05494a3 100644 --- a/app/src/components/Theme.js +++ b/app/src/components/Theme.js @@ -25,6 +25,9 @@ import { export const darkTheme = 'dark'; export const lightTheme = 'light'; +export const defaultTheme = lightTheme; +export const systemTheme = 'system'; +export const themeSessionKey = 'color-scheme'; export function ThemeWrapper({children, ...props}) { if (Object.keys(props).length > 0) { @@ -32,11 +35,17 @@ export function ThemeWrapper({children, ...props}) { console.error('ThemeWrapper does not support props!'); } + // Properties to manipulate elements with theme. + // Example

This paragraph changes style

const [i, setI] = useState({}); const [s, setS] = useState({}); const [t, setT] = useState({}); const [inverted, setInverted] = useState(''); - const [theme, setTheme] = useState(); + + // theme can be one of [darkTheme, lightTheme] + const [theme, setTheme] = useState(defaultTheme); + // savedTheme can be one of [null, darkTheme, lightTheme, systemTheme] + const [savedTheme, setSavedTheme] = useState(localStorage.getItem('color-scheme')); const setDarkTheme = (save = false) => { console.debug('setDarkTheme'); @@ -64,26 +73,58 @@ export function ThemeWrapper({children, ...props}) { } } - const saveTheme = (value) => { - console.debug('saveTheme', value); - localStorage.setItem('color-scheme', value); + const setSystemTheme = () => { + console.debug('setSystemTheme'); + saveTheme(systemTheme); } - useEffect(() => { - const colorScheme = localStorage.getItem('color-scheme'); - if (colorScheme === darkTheme) { - // User saved dark theme. - setDarkTheme(); - } else if (colorScheme === lightTheme) { - // User saved light theme. - setLightTheme(); - } else { - // No saved theme, use the systems color scheme first, if no system theme, use dark. - window.matchMedia('(prefers-color-scheme: dark)').matches ? setDarkTheme() : setLightTheme(); + const saveTheme = (value) => { + setSavedTheme(value); + localStorage.setItem(themeSessionKey, value); + } + + const cycleSavedTheme = (e) => { + // Cycle: System -> Dark -> Light + if (e) { + e.preventDefault(); } - // Add listener to match the system theme. - window.matchMedia('(prefers-color-scheme: dark)').addEventListener( - 'change', (e) => e.matches ? setDarkTheme() : setLightTheme()); + if (savedTheme === systemTheme || savedTheme == null) { + saveTheme(darkTheme); + } else if (savedTheme === darkTheme) { + saveTheme(lightTheme); + } else if (savedTheme === lightTheme) { + saveTheme(systemTheme); + } else { + console.error(`Unknown theme! savedTheme=${savedTheme}`); + } + applyTheme(); + } + + const matchTheme = () => { + // Returns darkTheme if saved theme is dark, lightTheme if saved theme is dark, + // darkTheme if system prefers dark, otherwise lightTheme. + const colorScheme = localStorage.getItem(themeSessionKey); + setSavedTheme(colorScheme); + if (colorScheme === darkTheme) { + return darkTheme; + } else if (colorScheme === lightTheme) { + return lightTheme + } + return window.matchMedia('(prefers-color-scheme: dark)').matches ? darkTheme : lightTheme; + } + + const applyTheme = () => { + const match = matchTheme(); + if (match === darkTheme) { + setDarkTheme(); + } else { + setLightTheme(); + } + }; + + useEffect(() => { + applyTheme(); + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', applyTheme); }, []); const themeValue = { @@ -91,7 +132,12 @@ export function ThemeWrapper({children, ...props}) { t, // Used to invert text. s, // Used to invert the style some elements. inverted, // Used to add "invert" to className. - theme, setDarkTheme, setLightTheme + theme, + savedTheme, + setDarkTheme, + setLightTheme, + setSystemTheme, + cycleSavedTheme, }; return