mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-07 06:27:36 +03:00
* Auto theme preference added * Fix lint * Add and use AUTO from consts * Add shared custom hook to get current theme * Moved up 'Auto' choice * AUTO -> AUTO_THEME_ID & extract useCurrentTheme to file * Liberalise theme setting * Add tests
This commit is contained in:
parent
fa479f0a9a
commit
3e0e11c01e
8 changed files with 235 additions and 8 deletions
81
ui/package-lock.json
generated
81
ui/package-lock.json
generated
|
@ -2150,6 +2150,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@testing-library/react-hooks": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-ChRyyA14e0CeVkWGp24v8q/IiWUqH+B8daRx4lGZme4dsudmMNWz+Qo2Q2NzbD2O5rAVXh2hSbS/KTKeqHYhkw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@types/react": ">=16.9.0",
|
||||||
|
"@types/react-dom": ">=16.9.0",
|
||||||
|
"@types/react-test-renderer": ">=16.9.0",
|
||||||
|
"filter-console": "^0.1.1",
|
||||||
|
"react-error-boundary": "^3.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.13.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
|
||||||
|
"integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.13.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.13.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||||
|
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@testing-library/user-event": {
|
"@testing-library/user-event": {
|
||||||
"version": "12.6.2",
|
"version": "12.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.2.tgz",
|
||||||
|
@ -2315,6 +2346,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"version": "17.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.2.tgz",
|
||||||
|
"integrity": "sha512-Icd9KEgdnFfJs39KyRyr0jQ7EKhq8U6CcHRMGAS45fp5qgUvxL3ujUCfWFttUK2UErqZNj97t9gsVPNAqcwoCg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/react-test-renderer": {
|
||||||
|
"version": "17.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz",
|
||||||
|
"integrity": "sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-transition-group": {
|
"@types/react-transition-group": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
|
||||||
|
@ -6668,6 +6717,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"filter-console": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/filter-console/-/filter-console-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"final-form": {
|
"final-form": {
|
||||||
"version": "4.20.1",
|
"version": "4.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.1.tgz",
|
||||||
|
@ -13423,6 +13478,32 @@
|
||||||
"prop-types": "^15.7.2"
|
"prop-types": "^15.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-error-boundary": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-W3xCd9zXnanqrTUeViceufD3mIW8Ut29BUD+S2f0eO2XCOU8b6UrJfY46RDGe5lxCJzfe4j0yvIfh0RbTZhKJw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.13.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
|
||||||
|
"integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.13.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.13.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||||
|
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-error-overlay": {
|
"react-error-overlay": {
|
||||||
"version": "6.0.7",
|
"version": "6.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
|
||||||
|
|
|
@ -30,7 +30,9 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.11.9",
|
"@testing-library/jest-dom": "^5.11.9",
|
||||||
"@testing-library/react": "^11.2.3",
|
"@testing-library/react": "^11.2.3",
|
||||||
|
"@testing-library/react-hooks": "^5.1.0",
|
||||||
"@testing-library/user-event": "^12.6.2",
|
"@testing-library/user-event": "^12.6.2",
|
||||||
|
"css-mediaquery": "^0.1.2",
|
||||||
"prettier": "^2.2.1"
|
"prettier": "^2.2.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -15,11 +15,11 @@ import {
|
||||||
setVolume,
|
setVolume,
|
||||||
clearQueue,
|
clearQueue,
|
||||||
} from '../actions'
|
} from '../actions'
|
||||||
import themes from '../themes'
|
|
||||||
import config from '../config'
|
import config from '../config'
|
||||||
import PlayerToolbar from './PlayerToolbar'
|
import PlayerToolbar from './PlayerToolbar'
|
||||||
import { sendNotification, baseUrl } from '../utils'
|
import { sendNotification, baseUrl } from '../utils'
|
||||||
import { keyMap } from '../hotkeys'
|
import { keyMap } from '../hotkeys'
|
||||||
|
import useCurrentTheme from '../themes/useCurrentTheme'
|
||||||
|
|
||||||
const useStyle = makeStyles((theme) => ({
|
const useStyle = makeStyles((theme) => ({
|
||||||
audioTitle: {
|
audioTitle: {
|
||||||
|
@ -58,8 +58,7 @@ const AudioTitle = React.memo(({ audioInfo, isMobile, className }) => {
|
||||||
|
|
||||||
const Player = () => {
|
const Player = () => {
|
||||||
const translate = useTranslate()
|
const translate = useTranslate()
|
||||||
const currentTheme = useSelector((state) => state.theme)
|
const theme = useCurrentTheme()
|
||||||
const theme = themes[currentTheme] || themes.DarkTheme
|
|
||||||
const playerTheme = (theme.player && theme.player.theme) || 'dark'
|
const playerTheme = (theme.player && theme.player.theme) || 'dark'
|
||||||
const dataProvider = useDataProvider()
|
const dataProvider = useDataProvider()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
export const REST_URL = '/app/api'
|
export const REST_URL = '/app/api'
|
||||||
|
|
||||||
export const M3U_MIME_TYPE = 'audio/x-mpegurl'
|
export const M3U_MIME_TYPE = 'audio/x-mpegurl'
|
||||||
|
|
||||||
|
export const AUTO_THEME_ID = 'AUTO_THEME_ID'
|
||||||
|
|
|
@ -6,14 +6,14 @@ import { HotKeys } from 'react-hotkeys'
|
||||||
import Menu from './Menu'
|
import Menu from './Menu'
|
||||||
import AppBar from './AppBar'
|
import AppBar from './AppBar'
|
||||||
import Notification from './Notification'
|
import Notification from './Notification'
|
||||||
import themes from '../themes'
|
import useCurrentTheme from '../themes/useCurrentTheme'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
root: { paddingBottom: (props) => (props.addPadding ? '80px' : 0) },
|
root: { paddingBottom: (props) => (props.addPadding ? '80px' : 0) },
|
||||||
})
|
})
|
||||||
|
|
||||||
export default (props) => {
|
export default (props) => {
|
||||||
const theme = useSelector((state) => themes[state.theme] || themes.DarkTheme)
|
const theme = useCurrentTheme()
|
||||||
const queue = useSelector((state) => state.queue)
|
const queue = useSelector((state) => state.queue)
|
||||||
const classes = useStyles({ addPadding: queue.queue.length > 0 })
|
const classes = useStyles({ addPadding: queue.queue.length > 0 })
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
|
@ -23,6 +23,7 @@ import themes from '../themes'
|
||||||
import { docsUrl } from '../utils'
|
import { docsUrl } from '../utils'
|
||||||
import { useGetLanguageChoices } from '../i18n'
|
import { useGetLanguageChoices } from '../i18n'
|
||||||
import albumLists, { defaultAlbumList } from '../album/albumLists'
|
import albumLists, { defaultAlbumList } from '../album/albumLists'
|
||||||
|
import { AUTO_THEME_ID } from '../consts'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
root: { marginTop: '1em' },
|
root: { marginTop: '1em' },
|
||||||
|
@ -77,9 +78,17 @@ const SelectTheme = (props) => {
|
||||||
const translate = useTranslate()
|
const translate = useTranslate()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const currentTheme = useSelector((state) => state.theme)
|
const currentTheme = useSelector((state) => state.theme)
|
||||||
const themeChoices = Object.keys(themes).map((key) => {
|
const themeChoices = [
|
||||||
return { id: key, name: themes[key].themeName }
|
{
|
||||||
})
|
id: AUTO_THEME_ID,
|
||||||
|
name: 'Auto',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
themeChoices.push(
|
||||||
|
...Object.keys(themes).map((key) => {
|
||||||
|
return { id: key, name: themes[key].themeName }
|
||||||
|
})
|
||||||
|
)
|
||||||
themeChoices.push({
|
themeChoices.push({
|
||||||
id: helpKey,
|
id: helpKey,
|
||||||
name: <HelpMsg caption={'Create your own'} />,
|
name: <HelpMsg caption={'Create your own'} />,
|
||||||
|
|
14
ui/src/themes/useCurrentTheme.js
Normal file
14
ui/src/themes/useCurrentTheme.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import useMediaQuery from '@material-ui/core/useMediaQuery'
|
||||||
|
import themes from './index'
|
||||||
|
import { AUTO_THEME_ID } from '../consts'
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const prefersLightMode = useMediaQuery('(prefers-color-scheme: light)')
|
||||||
|
return useSelector((state) => {
|
||||||
|
if (state.theme === AUTO_THEME_ID) {
|
||||||
|
return prefersLightMode ? themes.LightTheme : themes.DarkTheme
|
||||||
|
}
|
||||||
|
return themes[state.theme] || themes.DarkTheme
|
||||||
|
})
|
||||||
|
}
|
120
ui/src/themes/useCurrentTheme.test.js
Normal file
120
ui/src/themes/useCurrentTheme.test.js
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
|
import { createStore } from 'redux'
|
||||||
|
import mediaQuery from 'css-mediaquery'
|
||||||
|
import { renderHook } from '@testing-library/react-hooks'
|
||||||
|
import useCurrentTheme from './useCurrentTheme'
|
||||||
|
import { themeReducer } from '../reducers/themeReducer'
|
||||||
|
import { AUTO_THEME_ID } from '../consts'
|
||||||
|
|
||||||
|
function createMatchMedia(theme) {
|
||||||
|
return (query) => ({
|
||||||
|
matches: mediaQuery.match(query, { 'prefers-color-scheme': theme }),
|
||||||
|
addListener: () => {},
|
||||||
|
removeListener: () => {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('useCurrentTheme', () => {
|
||||||
|
describe('with user preference theme as light', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
window.matchMedia = createMatchMedia('light')
|
||||||
|
})
|
||||||
|
it('sets theme as light in auto mode', () => {
|
||||||
|
const { result } = renderHook(() => useCurrentTheme(), {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Provider store={createStore(themeReducer, { theme: AUTO_THEME_ID })}>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
expect(result.current.themeName).toMatch('Light')
|
||||||
|
})
|
||||||
|
it('sets theme as dark', () => {
|
||||||
|
const { result } = renderHook(() => useCurrentTheme(), {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Provider store={createStore(themeReducer, { theme: 'DarkTheme' })}>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.themeName).toMatch('Dark')
|
||||||
|
})
|
||||||
|
it('sets theme as light', () => {
|
||||||
|
const { result } = renderHook(() => useCurrentTheme(), {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Provider store={createStore(themeReducer, { theme: 'LightTheme' })}>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.themeName).toMatch('Light')
|
||||||
|
})
|
||||||
|
it('sets theme as spotify-ish', () => {
|
||||||
|
const { result } = renderHook(() => useCurrentTheme(), {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Provider
|
||||||
|
store={createStore(themeReducer, { theme: 'SpotifyTheme' })}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.themeName).toMatch('Spotify-ish')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('with user preference theme as dark', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
window.matchMedia = createMatchMedia('dark')
|
||||||
|
})
|
||||||
|
it('sets theme as dark in auto mode', () => {
|
||||||
|
const { result } = renderHook(() => useCurrentTheme(), {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Provider store={createStore(themeReducer, { theme: AUTO_THEME_ID })}>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.themeName).toMatch('Dark')
|
||||||
|
})
|
||||||
|
it('sets theme as dark', () => {
|
||||||
|
const { result } = renderHook(() => useCurrentTheme(), {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Provider store={createStore(themeReducer, { theme: 'DarkTheme' })}>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.themeName).toMatch('Dark')
|
||||||
|
})
|
||||||
|
it('sets theme as light', () => {
|
||||||
|
const { result } = renderHook(() => useCurrentTheme(), {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Provider store={createStore(themeReducer, { theme: 'LightTheme' })}>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.themeName).toMatch('Light')
|
||||||
|
})
|
||||||
|
it('sets theme as spotify-ish', () => {
|
||||||
|
const { result } = renderHook(() => useCurrentTheme(), {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Provider
|
||||||
|
store={createStore(themeReducer, { theme: 'SpotifyTheme' })}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.themeName).toMatch('Spotify-ish')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Add table
Add a link
Reference in a new issue