diff --git a/gui/src/components/OnnxWeb.tsx b/gui/src/components/OnnxWeb.tsx index 6eee4329..8fec1da6 100644 --- a/gui/src/components/OnnxWeb.tsx +++ b/gui/src/components/OnnxWeb.tsx @@ -1,9 +1,13 @@ -import { doesExist } from '@apextoaster/js-utils'; +import { mustExist } from '@apextoaster/js-utils'; import { TabContext, TabList, TabPanel } from '@mui/lab'; -import { Box, Container, Divider, Tab } from '@mui/material'; +import { Box, Container, Divider, PaletteMode, Tab, useMediaQuery, CssBaseline } from '@mui/material'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; import * as React from 'react'; +import { useContext, useMemo } from 'react'; import { useHash } from 'react-use/lib/useHash'; +import { useStore } from 'zustand'; +import { StateContext } from '../state.js'; import { ModelControl } from './control/ModelControl.js'; import { ImageHistory } from './ImageHistory.js'; import { Logo } from './Logo.js'; @@ -13,50 +17,68 @@ import { Inpaint } from './tab/Inpaint.js'; import { Settings } from './tab/Settings.js'; import { Txt2Img } from './tab/Txt2Img.js'; import { Upscale } from './tab/Upscale.js'; -import { getTab, TAB_LABELS } from './utils.js'; +import { getTheme, getTab, TAB_LABELS } from './utils.js'; export function OnnxWeb() { + /* checks for system light/dark mode preference */ + const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); + const state = useStore(mustExist(useContext(StateContext))); + + const theme = useMemo( + () => createTheme({ + palette: { + mode: getTheme(state.theme, prefersDarkMode) as PaletteMode + } + }), + [prefersDarkMode, state.theme], + ); + const [hash, setHash] = useHash(); return ( - - - - - - - - - - { - setHash(idx); - }}> - {TAB_LABELS.map((name) => )} - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + { + setHash(idx); + }}> + {TAB_LABELS.map((name) => )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/gui/src/components/tab/Settings.tsx b/gui/src/components/tab/Settings.tsx index 030a8880..5f09c1a2 100644 --- a/gui/src/components/tab/Settings.tsx +++ b/gui/src/components/tab/Settings.tsx @@ -1,6 +1,6 @@ import { doesExist, mustExist } from '@apextoaster/js-utils'; import { Refresh } from '@mui/icons-material'; -import { Alert, Button, Stack, TextField } from '@mui/material'; +import { Alert, Button, FormControlLabel, Stack, Switch, TextField, useMediaQuery } from '@mui/material'; import * as React from 'react'; import { useContext, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -8,6 +8,7 @@ import { useStore } from 'zustand'; import { getApiRoot } from '../../config.js'; import { ConfigContext, StateContext, STATE_KEY } from '../../state.js'; +import { getTheme } from '../utils.js'; import { NumericField } from '../input/NumericField.js'; function removeBlobs(key: string, value: unknown): unknown { @@ -28,8 +29,10 @@ function removeBlobs(key: string, value: unknown): unknown { } export function Settings() { + const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); const config = mustExist(useContext(ConfigContext)); const state = useStore(mustExist(useContext(StateContext))); + const theme = getTheme(state.theme, prefersDarkMode); const [json, setJson] = useState(JSON.stringify(state, removeBlobs)); const [root, setRoot] = useState(getApiRoot(config)); @@ -80,6 +83,17 @@ export function Settings() { {t('setting.loadState')} + { + if (theme === 'light') { + state.setTheme('dark'); + } else { + state.setTheme('light'); + } + }} + /> + } label={t('setting.darkMode')} /> diff --git a/gui/src/components/utils.ts b/gui/src/components/utils.ts index db59ffbd..2dc64cd1 100644 --- a/gui/src/components/utils.ts +++ b/gui/src/components/utils.ts @@ -17,3 +17,13 @@ export function getTab(hash: string): string { return TAB_LABELS[0]; } + +export function getTheme(currentTheme: string, preferDark: boolean): string { + if (currentTheme === '') { + if (preferDark) { + return 'dark'; + } + return 'light'; + } + return currentTheme; +} diff --git a/gui/src/state.ts b/gui/src/state.ts index 73123454..07184332 100644 --- a/gui/src/state.ts +++ b/gui/src/state.ts @@ -43,8 +43,10 @@ interface BrushSlice { interface DefaultSlice { defaults: TabState; + theme: string; setDefaults(param: Partial): void; + setTheme(theme: string): void; } interface HistorySlice { @@ -490,6 +492,7 @@ export function createStateSlices(server: ServerParams) { defaults: { ...base, }, + theme: '', setDefaults(params) { set((prev) => ({ defaults: { @@ -498,6 +501,11 @@ export function createStateSlices(server: ServerParams) { } })); }, + setTheme(theme) { + set((prev) => ({ + theme, + })); + } }); const createModelSlice: Slice = (set) => ({ diff --git a/gui/src/strings/de.ts b/gui/src/strings/de.ts index 6b8a0fcd..92b8b138 100644 --- a/gui/src/strings/de.ts +++ b/gui/src/strings/de.ts @@ -164,6 +164,7 @@ export const I18N_STRINGS_DE = { scheduler: 'Standardplaner', server: 'API-Server', state: 'Kundenstatus', + darkMode: 'Dunkelmodus', }, sourceFilter: { none: '', diff --git a/gui/src/strings/en.ts b/gui/src/strings/en.ts index a7f9a644..ee382f78 100644 --- a/gui/src/strings/en.ts +++ b/gui/src/strings/en.ts @@ -226,6 +226,7 @@ export const I18N_STRINGS_EN = { scheduler: 'Default Scheduler', server: 'API Server', state: 'Client State', + darkMode: 'Dark Mode', }, scheduler: { 'ddim': 'DDIM', diff --git a/gui/src/strings/es.ts b/gui/src/strings/es.ts index 1bc0de07..e46fc0a8 100644 --- a/gui/src/strings/es.ts +++ b/gui/src/strings/es.ts @@ -164,6 +164,7 @@ export const I18N_STRINGS_ES = { scheduler: 'Programador predeterminado', server: 'Servidor API', state: 'Estado del cliente', + darkMode: 'Modo Oscuro', }, sourceFilter: { none: '', diff --git a/gui/src/strings/fr.ts b/gui/src/strings/fr.ts index 3d983f0a..1c6a7e80 100644 --- a/gui/src/strings/fr.ts +++ b/gui/src/strings/fr.ts @@ -164,6 +164,7 @@ export const I18N_STRINGS_FR = { scheduler: '', server: '', state: '', + darkMode: 'Mode Sombre', }, sourceFilter: { none: '',