diff --git a/__tests__/src/components/App.test.js b/__tests__/src/components/App.test.js index 77bf22bb5c09ced33816758d39402a94461d8371..145241c5e9499b3ffb0a7aac8a51a78e22fd59a0 100644 --- a/__tests__/src/components/App.test.js +++ b/__tests__/src/components/App.test.js @@ -4,14 +4,26 @@ import App from '../../../src/components/App'; describe('App', () => { it('renders without an error', () => { - const wrapper = shallow(<App manifests={[]} workspace={{}} />); - expect(wrapper.find('div.mirador-app').length).toBe(1); + const wrapper = shallow( + <App + manifests={[]} + workspace={{}} + config={{ theme: 'light' }} + />, + ); + expect(wrapper.dive().find('div.mirador-app').length).toBe(1); }); describe('FullScreen', () => { - it('is enabled by the workspace.isFullscreenEnabled state', () => { - const wrapper = shallow(<App manifests={[]} workspace={{ isFullscreenEnabled: true }} />); - expect(wrapper.find('FullScreen').first().prop('enabled')).toEqual(true); + it('is enabled by the workspace.fullscreen state', () => { + const wrapper = shallow( + <App + manifests={[]} + workspace={{ isFullscreenEnabled: true }} + config={{ theme: 'light' }} + />, + ); + expect(wrapper.dive().find('FullScreen').first().prop('enabled')).toEqual(true); }); }); }); diff --git a/__tests__/src/components/WorkspaceSettings.test.js b/__tests__/src/components/WorkspaceSettings.test.js index 339cc3425280627ddddd2205106bd039c9827d8d..9a265c0565b4bb680890af68e1b890a5734bdf35 100644 --- a/__tests__/src/components/WorkspaceSettings.test.js +++ b/__tests__/src/components/WorkspaceSettings.test.js @@ -5,19 +5,28 @@ import WorkspaceSettings from '../../../src/components/WorkspaceSettings'; describe('WorkspaceSettings', () => { let wrapper; let handleClose; + let updateConfig; beforeEach(() => { handleClose = jest.fn(); + updateConfig = jest.fn(); wrapper = shallow( <WorkspaceSettings open handleClose={handleClose} + updateConfig={updateConfig} + theme="light" />, ); }); it('renders without an error', () => { expect(wrapper.find('WithStyles(Dialog)').length).toBe(1); + expect(wrapper.find('WithStyles(FormControl)').length).toBe(1); + }); + it('calls updateConfig when selected', () => { + wrapper.instance().handleThemeChange({ target: { value: 'foo' } }); + expect(updateConfig).toHaveBeenCalled(); }); }); diff --git a/src/components/App.js b/src/components/App.js index 86b1f1d8550e0d369391937839595f7fdf7c3082..05b03c507cdb06412824ea44fb36eab0c445973e 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import CssBaseline from '@material-ui/core/CssBaseline'; +import classNames from 'classnames'; +import { MuiThemeProvider, createMuiTheme, withStyles } from '@material-ui/core/styles'; import Fullscreen from 'react-fullscreen-crossbrowser'; import WorkspaceControlPanel from './WorkspaceControlPanel'; import Workspace from '../containers/Workspace'; @@ -16,24 +17,38 @@ class App extends Component { * @return {String} - HTML markup for the component */ render() { - const { workspace, setWorkspaceFullscreen } = this.props; + const { + workspace, setWorkspaceFullscreen, config, classes, + } = this.props; + const theme = createMuiTheme({ + palette: { + type: config.theme, + }, + typography: { + useNextVariants: true, + }, + }); + return ( - <div className={ns('app')}> - <CssBaseline /> - <Fullscreen - enabled={workspace.isFullscreenEnabled} - onChange={isFullscreenEnabled => setWorkspaceFullscreen(isFullscreenEnabled)} - > - <Workspace /> - </Fullscreen> - <WorkspaceControlPanel /> + <div className={classNames(classes.background, ns('app'))}> + <MuiThemeProvider theme={theme}> + <Fullscreen + enabled={workspace.isFullscreenEnabled} + onChange={isFullscreenEnabled => setWorkspaceFullscreen(isFullscreenEnabled)} + > + <Workspace /> + </Fullscreen> + <WorkspaceControlPanel /> + </MuiThemeProvider> </div> ); } } App.propTypes = { + config: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types workspace: PropTypes.object, // eslint-disable-line react/forbid-prop-types + classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types, setWorkspaceFullscreen: PropTypes.func, }; @@ -42,4 +57,14 @@ App.defaultProps = { setWorkspaceFullscreen: () => {}, }; -export default App; +/** + Material UI style overrides + @private + */ +const styles = theme => ({ + background: { + background: theme.palette.background.default, + }, +}); + +export default withStyles(styles)(App); diff --git a/src/components/WorkspaceMenu.js b/src/components/WorkspaceMenu.js index 3b8b74a215e2907070ea19c1c0a3c8a901489bb9..bcb016dab8b2d5372b2d0925a270edb1daf4dd57 100644 --- a/src/components/WorkspaceMenu.js +++ b/src/components/WorkspaceMenu.js @@ -9,7 +9,7 @@ import SettingsIcon from '@material-ui/icons/Settings'; import ViewHeadlineIcon from '@material-ui/icons/ViewHeadline'; import PropTypes from 'prop-types'; import WindowList from '../containers/WindowList'; -import WorkspaceSettings from './WorkspaceSettings'; +import WorkspaceSettings from '../containers/WorkspaceSettings'; import WorkspaceExport from '../containers/WorkspaceExport'; /** diff --git a/src/components/WorkspaceSettings.js b/src/components/WorkspaceSettings.js index 55aa12ffd23a5e5752d52b27869fec297a7f0231..fbd7c3f2c8f3c35a2c3f2eac1451571b84ca31a2 100644 --- a/src/components/WorkspaceSettings.js +++ b/src/components/WorkspaceSettings.js @@ -2,24 +2,59 @@ import React, { Component } from 'react'; import Dialog from '@material-ui/core/Dialog'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; +import FormControl from '@material-ui/core/FormControl'; +import InputLabel from '@material-ui/core/InputLabel'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; import PropTypes from 'prop-types'; /** */ class WorkspaceSettings extends Component { + /** + * constructor - + */ + constructor(props) { + super(props); + this.handleThemeChange = this.handleThemeChange.bind(this); + } + + /** + * Propagate theme selection into the global state + */ + handleThemeChange(event) { + const { updateConfig } = this.props; + + updateConfig({ theme: event.target.value }); + } + /** * render * @return */ render() { const { - handleClose, open, children, + handleClose, open, children, theme, } = this.props; return ( <Dialog id="workspace-settings" open={open} onClose={handleClose}> <DialogTitle id="form-dialog-title">Settings</DialogTitle> <DialogContent> {children} + <FormControl> + <InputLabel htmlFor="theme-simple">Theme</InputLabel> + <Select + value={theme} + onChange={this.handleThemeChange} + inputProps={{ + name: 'theme', + id: 'theme-simple', + }} + > + <MenuItem value="light">Light</MenuItem> + <MenuItem value="dark">Dark</MenuItem> + </Select> + </FormControl> </DialogContent> </Dialog> ); @@ -30,6 +65,8 @@ WorkspaceSettings.propTypes = { handleClose: PropTypes.func.isRequired, open: PropTypes.bool, // eslint-disable-line react/forbid-prop-types children: PropTypes.node, + updateConfig: PropTypes.func.isRequired, + theme: PropTypes.string.isRequired, }; WorkspaceSettings.defaultProps = { diff --git a/src/config/settings.js b/src/config/settings.js index fe3312aa23bd651b817acb62eb2f9f24c60954a1..c8c6f943ced85764dd02bb890ec27ba9e0fc2141 100644 --- a/src/config/settings.js +++ b/src/config/settings.js @@ -1,4 +1,5 @@ export default { + theme: 'light', // dark also available windows: [], thumbnailNavigation: { defaultPosition: 'bottom', diff --git a/src/containers/App.js b/src/containers/App.js index dd22661a5eea6af1fd5b908b38e069cb1ce22124..aa351221c3c86f47cff6e27be3c63c9b170aacd9 100644 --- a/src/containers/App.js +++ b/src/containers/App.js @@ -10,6 +10,7 @@ import App from '../components/App'; */ const mapStateToProps = state => ( { + config: state.config, workspace: state.workspace, manifests: state.manifests, } diff --git a/src/containers/WorkspaceSettings.js b/src/containers/WorkspaceSettings.js new file mode 100644 index 0000000000000000000000000000000000000000..a52b463006165a812e01d231e9144683fc600838 --- /dev/null +++ b/src/containers/WorkspaceSettings.js @@ -0,0 +1,31 @@ +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import WorkspaceSettings from '../components/WorkspaceSettings'; +import * as actions from '../state/actions'; + +/** + * mapDispatchToProps - used to hook up connect to action creators + * @memberof ManifestListItem + * @private + */ +const mapDispatchToProps = { + updateConfig: actions.updateConfig, +}; + +/** + * mapStateToProps - to hook up connect + * @memberof Workspace + * @private + */ +const mapStateToProps = state => ( + { + theme: state.config.theme, + } +); + +const enhance = compose( + connect(mapStateToProps, mapDispatchToProps), + // further HOC go here +); + +export default enhance(WorkspaceSettings); diff --git a/src/styles/index.scss b/src/styles/index.scss index 5056437008e94385d19f50f23e3284cd8a90ceee..c7932e0ad740fb41574ea3cab8fa01bfc4cff5ba 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -1,10 +1,5 @@ @import 'variables'; -body { - background: $white; - height: 100%; -} - .mirador { &-workspace { bottom: 0;