Line data Source code
1 : import * as React from 'react';
2 :
3 : import { useAppConfig, useAppReconfigure } from './app_config.js';
4 :
5 : /// The color scheme of the app, see docs/design/app_theme.md
6 : export type ColorScheme = 'light' | 'dark';
7 :
8 1 : const DARK_QUERY = '(prefers-color-scheme: dark)';
9 :
10 : /// The color scheme of the app settings, null to follow the OS
11 1 : let currentSetting: ColorScheme | null = null;
12 1 : const listeners = new Set<() => void>();
13 :
14 : /// Pin the document to the color scheme of the app settings, null follows the OS.
15 : /// Tells the native window of Electron too.
16 1 : export function applyColorSchemeToDocument(setting: ColorScheme | null, doc: Document = document) {
17 1 : if (setting == null) {
18 1 : delete doc.documentElement.dataset.colorScheme;
19 : } else {
20 1 : doc.documentElement.dataset.colorScheme = setting;
21 : }
22 1 : globalThis.dashqlElectron?.setColorScheme?.(setting ?? 'system').catch(() => { });
23 1 : if (setting !== currentSetting) {
24 1 : currentSetting = setting;
25 1 : for (const listener of listeners) {
26 1 : listener();
27 : }
28 1 : }
29 1 : }
30 :
31 1 : function subscribe(listener: () => void): () => void {
32 1 : listeners.add(listener);
33 1 : const media = globalThis.matchMedia?.(DARK_QUERY);
34 1 : media?.addEventListener('change', listener);
35 1 : return () => {
36 1 : listeners.delete(listener);
37 1 : media?.removeEventListener('change', listener);
38 1 : };
39 1 : }
40 :
41 1 : function resolveColorScheme(): ColorScheme {
42 1 : return currentSetting ?? (globalThis.matchMedia?.(DARK_QUERY).matches ? 'dark' : 'light');
43 1 : }
44 :
45 : /// The color scheme the document shows: the one of the app settings, or the one of the OS.
46 : /// Renders again when it changes, for colors that are not CSS, like the colors of charts drawn on a canvas.
47 1 : export function useColorScheme(): ColorScheme {
48 1 : return React.useSyncExternalStore(subscribe, resolveColorScheme, () => 'light');
49 1 : }
50 :
51 : /// Switch to the other color scheme and pin it in the app settings
52 1 : export function useColorSchemeToggle(): [ColorScheme, () => void] {
53 1 : const scheme = useColorScheme();
54 1 : const reconfigure = useAppReconfigure();
55 1 : const toggle = React.useCallback(() => {
56 1 : const next: ColorScheme = resolveColorScheme() === 'dark' ? 'light' : 'dark';
57 1 : reconfigure(value => value == null ? null : {
58 1 : ...value,
59 1 : settings: { ...value.settings, colorScheme: next },
60 1 : });
61 1 : }, [reconfigure]);
62 1 : return [scheme, toggle];
63 1 : }
64 :
65 : /// The color scheme of the app settings, null follows the OS, and a setter of it
66 1 : export function useColorSchemeSetting(): [ColorScheme | null, (setting: ColorScheme | null) => void] {
67 1 : const config = useAppConfig();
68 1 : const reconfigure = useAppReconfigure();
69 1 : const setting = config?.settings?.colorScheme ?? null;
70 1 : const set = React.useCallback((next: ColorScheme | null) => {
71 0 : reconfigure(value => {
72 0 : if (value == null) return null;
73 0 : const { colorScheme: _colorScheme, ...settings } = value.settings ?? {};
74 0 : return { ...value, settings: next == null ? settings : { ...settings, colorScheme: next } };
75 0 : });
76 1 : }, [reconfigure]);
77 1 : return [setting, set];
78 1 : }
|