Skip to content

Commit

Permalink
Adds concept of alternate theme; adds 'mode' as a selector for dark v…
Browse files Browse the repository at this point in the history
…s light theme ; uses makeContrasting to generate a missing theme
  • Loading branch information
carolinebridge committed May 17, 2024
1 parent 2f0a7fa commit 906f6e4
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 90 deletions.
125 changes: 114 additions & 11 deletions packages/core/ui/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { createTheme, ThemeOptions } from '@mui/material/styles'
import type {
PaletteAugmentColorOptions,
PaletteColor,
SimplePaletteColorOptions,
} from '@mui/material/styles/createPalette'
import deepmerge from 'deepmerge'
import { makeContrasting } from '../util/color'

declare module '@mui/material/styles/createPalette' {
interface Palette {
Expand Down Expand Up @@ -113,6 +115,10 @@ const frames = [
const stopCodon = '#e22'
const startCodon = '#3e3'

interface JBrowseThemeOptions extends ThemeOptions {
alternate?: ThemeOptions
}

function stockTheme() {
return {
palette: {
Expand All @@ -139,7 +145,8 @@ function stockTheme() {
},
},
},
} satisfies ThemeOptions
alternate: getDarkStockTheme(),
} satisfies JBrowseThemeOptions
}

function getDefaultTheme() {
Expand All @@ -149,10 +156,10 @@ function getDefaultTheme() {
}
}

function getLightStockTheme() {
function getStockTheme() {
return {
...stockTheme(),
name: 'Light (stock)',
name: 'Stock',
}
}

Expand Down Expand Up @@ -208,7 +215,7 @@ function getDarkMinimalTheme() {

function getMinimalTheme() {
return {
name: 'Light (minimal)',
name: 'Minimal',
palette: {
primary: { main: grey[900] },
secondary: { main: grey[800] },
Expand All @@ -221,15 +228,14 @@ function getMinimalTheme() {
frames,
framesCDS,
},
} satisfies ThemeOptions & { name: string }
alternate: getDarkMinimalTheme(),
} satisfies JBrowseThemeOptions & { name: string }
}

export const defaultThemes = {
default: getDefaultTheme(),
lightStock: getLightStockTheme(),
lightMinimal: getMinimalTheme(),
darkStock: getDarkStockTheme(),
darkMinimal: getDarkMinimalTheme(),
stock: getStockTheme(),
minimal: getMinimalTheme(),
} as ThemeMap

function overwriteArrayMerge(_: unknown, sourceArray: unknown[]) {
Expand Down Expand Up @@ -455,24 +461,121 @@ export function createJBrowseBaseTheme(theme?: ThemeOptions): ThemeOptions {
return deepmerge(themeP, theme || {}, { arrayMerge: overwriteArrayMerge })
}

type ThemeMap = Record<string, ThemeOptions>
type ThemeMap = Record<string, JBrowseThemeOptions>

export function createJBrowseTheme(
configTheme: ThemeOptions = {},
themes = defaultThemes,
themeName = 'default',
mode = 'light',
) {
const themeMode = configTheme?.palette?.mode ?? 'light'
if (themeMode !== mode) {
configTheme = getAlternateTheme(configTheme)
}
return createTheme(
createJBrowseBaseTheme(
themeName === 'default'
? deepmerge(themes.default, augmentTheme(configTheme), {
arrayMerge: overwriteArrayMerge,
})
: augmentThemePlus(themes[themeName]) || themes.default,
: augmentThemePlus(configTheme) || themes.default,
),
)
}

function getAlternateTheme(theme: JBrowseThemeOptions = {}) {
const alternate = theme?.alternate ?? undefined
const themeMode = theme?.palette?.mode ?? 'light'
const altMode = alternate?.palette?.mode

// no alternate theme has been defined, or both themes are the same mode
if (!alternate || themeMode === altMode) {
return generateAltTheme(theme)
}

if (themeMode !== altMode) {
return {
...alternate,
}
}

return theme
}

function generateAltTheme(theme: ThemeOptions = {}) {
const altMode = theme.palette?.mode === 'dark' ? 'light' : 'dark'
const background = altMode === 'dark' ? 'black' : 'white'
const contrast = 4.5

if (theme?.palette) {
theme = deepmerge(theme, {
palette: {
mode: altMode,
},
})
}

if (theme?.palette?.primary) {
const contrastColor = {
main: makeContrasting(
(theme.palette.primary as SimplePaletteColorOptions).main,
background,
contrast,
),
}
theme = deepmerge(theme, {
palette: {
primary: refTheme.palette.augmentColor({ color: contrastColor }),
},
})
}
if (theme?.palette?.secondary) {
const contrastColor = {
main: makeContrasting(
(theme.palette.secondary as SimplePaletteColorOptions).main,
background,
contrast,
),
}
theme = deepmerge(theme, {
palette: {
secondary: refTheme.palette.augmentColor({ color: contrastColor }),
},
})
}
if (theme?.palette?.tertiary) {
const contrastColor = {
main: makeContrasting(
(theme.palette.tertiary as SimplePaletteColorOptions).main,
background,
contrast,
),
}
theme = deepmerge(theme, {
palette: {
tertiary: refTheme.palette.augmentColor({ color: contrastColor }),
},
})
}
if (theme?.palette?.quaternary) {
const contrastColor = {
main: makeContrasting(
(theme.palette.quaternary as SimplePaletteColorOptions).main,
background,
contrast,
),
}
theme = deepmerge(theme, {
palette: {
quaternary: refTheme.palette.augmentColor({ color: contrastColor }),
},
})
}

return theme
}

function augmentTheme(theme: ThemeOptions = {}) {
if (theme?.palette?.tertiary) {
theme = deepmerge(theme, {
Expand Down
107 changes: 31 additions & 76 deletions packages/product-core/src/Session/Themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import { autorun } from 'mobx'
// locals
import { BaseSession } from './BaseSession'

type ThemeMap = Record<string, ThemeOptions>
interface JBrowseThemeOptions extends ThemeOptions {
name?: string
alternate?: ThemeOptions
}

type ThemeMap = Record<string, JBrowseThemeOptions>

/**
* #stateModel ThemeManagerSessionMixin
Expand All @@ -26,87 +31,18 @@ export function ThemeManagerSessionMixin(_pluginManager: PluginManager) {
.volatile(() => ({
sessionThemeName: localStorageGetItem('themeName') || 'default',
prefersDarkMode: localStorageGetItem('prefersDarkMode') || 'false',
themeMode: localStorageGetItem('themeMode') || 'system',
}))
.views(s => ({
/**
* #getter
*/
get configTheme() {
const self = s as typeof s & BaseSession
const configTheme = getConf(self.jbrowse, 'theme')
// placeholder structure to identify the default config theme
return {
config: {
palette: {
...defaultThemes.default.palette,
...configTheme.palette,
},
name: 'config',
},
} as ThemeOptions
},
/**
* #getter
*/
get extraThemes() {
const self = s as typeof s & BaseSession
const extraThemes = getConf(self.jbrowse, 'extraThemes')
return extraThemes
},
/**
* #getter
*/
get lightTheme() {
const theme = Object.entries({
...this.configTheme,
...this.extraThemes,
...defaultThemes,
} as ThemeMap).find(
([_, theme]) =>
theme.palette?.mode === 'light' ||
theme.palette?.mode === undefined,
) ?? [undefined, undefined]

return theme ?? undefined
},
/**
* #getter
*/
get darkTheme() {
const theme = Object.entries({
...this.configTheme,
...this.extraThemes,
...defaultThemes,
} as ThemeMap).find(([_, theme]) => theme.palette?.mode === 'dark') ?? [
undefined,
undefined,
]

return theme ?? undefined
},
/**
* #getter
*/
get systemTheme() {
const [name, theme] =
s.prefersDarkMode === 'true' && this.darkTheme[1]
? this.darkTheme
: this.lightTheme

const sysTheme = {
...theme,
name: `Use system setting (${name})`,
}
return sysTheme as ThemeOptions
},
/**
* #method
*/
allThemes(): ThemeMap {
const self = s as typeof s & BaseSession
const extraThemes = getConf(self.jbrowse, 'extraThemes')
return {
...defaultThemes,
...this.extraThemes,
system: { ...this.systemTheme },
...extraThemes,
}
},
/**
Expand All @@ -122,9 +58,21 @@ export function ThemeManagerSessionMixin(_pluginManager: PluginManager) {
*/
get theme() {
const self = s as typeof s & BaseSession
const configTheme = getConf(self.jbrowse, 'theme')
const all = this.allThemes()
return createJBrowseTheme(configTheme, all, this.themeName)

const desiredMode =
s.themeMode === 'system'
? JSON.parse(s.prefersDarkMode)
? 'dark'
: 'light'
: s.themeMode

const theme =
this.themeName === 'default'
? getConf(self.jbrowse, 'theme')
: all[this.themeName]

return createJBrowseTheme(theme, all, this.themeName, desiredMode)
},
}))
.actions(self => ({
Expand All @@ -140,12 +88,19 @@ export function ThemeManagerSessionMixin(_pluginManager: PluginManager) {
setPrefersDarkMode(preference: string) {
self.prefersDarkMode = preference
},
/**
* #action
*/
setThemeMode(preference: 'light' | 'dark' | 'system') {
self.themeMode = preference
},
afterAttach() {
addDisposer(
self,
autorun(() => {
localStorageSetItem('themeName', self.themeName)
localStorageSetItem('prefersDarkMode', self.prefersDarkMode)
localStorageSetItem('themeMode', self.themeMode)
}),
)
},
Expand Down
14 changes: 14 additions & 0 deletions products/jbrowse-web/src/components/PreferencesDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { Dialog } from '@jbrowse/core/ui'
const useStyles = makeStyles()(() => ({
container: {
width: 800,
display: 'flex',
gap: '5px',
},
}))

Expand All @@ -25,6 +27,8 @@ export default function PreferencesDialog({
allThemes: () => Record<string, ThemeOptions & { name?: string }>
themeName?: string
setThemeName: (arg: string) => void
themeMode: string
setThemeMode: (arg: string) => void
}
}) {
const { classes } = useStyles()
Expand All @@ -43,6 +47,16 @@ export default function PreferencesDialog({
</MenuItem>
))}
</TextField>
<TextField
select
label="Mode"
value={session.themeMode}
onChange={event => session.setThemeMode(event.target.value)}
>
<MenuItem value={'light'}>Light</MenuItem>
<MenuItem value={'dark'}>Dark</MenuItem>
<MenuItem value={'system'}>System</MenuItem>
</TextField>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Close</Button>
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

0 comments on commit 906f6e4

Please sign in to comment.