diff --git a/__tests__/src/components/CompanionWindow.test.js b/__tests__/src/components/CompanionWindow.test.js index 9c8896ccd93e9ac564c92d3a2d163c21093ebef2..3666ce275ee5e3ba83eb39dfee0b19bfc95c421f 100644 --- a/__tests__/src/components/CompanionWindow.test.js +++ b/__tests__/src/components/CompanionWindow.test.js @@ -72,4 +72,9 @@ describe('CompanionWindow', () => { expect(updateCompanionWindow).toHaveBeenCalledTimes(1); expect(updateCompanionWindow).toHaveBeenCalledWith('x', 'abc123', { position: 'right' }); }); + + it('renders title controls', () => { + companionWindow = createWrapper({ position: 'bottom', titleControls: <div className="xyz" /> }); + expect(companionWindow.find('div.xyz').length).toBe(1); + }); }); diff --git a/__tests__/src/components/WindowSideBarCanvasPanel.test.js b/__tests__/src/components/WindowSideBarCanvasPanel.test.js index 2d3f7f29faf096e945a196b3dbbba5d3cc9372e8..e01de7a023d63ddbb5747e67434e9a7e6450650e 100644 --- a/__tests__/src/components/WindowSideBarCanvasPanel.test.js +++ b/__tests__/src/components/WindowSideBarCanvasPanel.test.js @@ -5,40 +5,70 @@ import ListItem from '@material-ui/core/ListItem'; import Typography from '@material-ui/core/Typography'; import manifesto from 'manifesto.js'; import { WindowSideBarCanvasPanel } from '../../../src/components/WindowSideBarCanvasPanel'; +import { CanvasThumbnail } from '../../../src/components/CanvasThumbnail'; import manifestJson from '../../fixtures/version-2/019.json'; import { getIdAndLabelOfCanvases } from '../../../src/state/selectors'; +/** + * Helper function to create a shallow wrapper around WindowSideBarCanvasPanel + */ +function createWrapper(props) { + const canvases = manifesto.create(manifestJson).getSequences()[0].getCanvases(); + + return shallow( + <WindowSideBarCanvasPanel + id="asdf" + canvases={canvases} + classes={{}} + t={key => key} + windowId="xyz" + setCanvas={() => {}} + config={{ canvasNavigation: { height: 100 } }} + {...props} + />, + ); +} + describe('WindowSideBarCanvasPanel', () => { - let wrapper; let setCanvas; - let canvases; beforeEach(() => { setCanvas = jest.fn(); - canvases = manifesto.create(manifestJson).getSequences()[0].getCanvases(); - - wrapper = shallow( - <WindowSideBarCanvasPanel - id="asdf" - canvases={canvases} - classes={{}} - t={key => key} - windowId="xyz" - setCanvas={setCanvas} - config={{ canvasNavigation: { height: 100 } }} - />, - ); }); - it('renders all needed elements', () => { + it('renders all needed elements for the thumbnail view', () => { + const wrapper = createWrapper(); + expect(wrapper.props().title).toBe('canvasIndex'); + expect(wrapper.find(List).length).toBe(1); + expect(wrapper.find(ListItem).length).toBe(3); + expect(wrapper.find(ListItem).first().props().component).toEqual('li'); + expect(wrapper.find(List).find(Typography).length).toBe(3); + expect(wrapper.find(CanvasThumbnail).length).toBe(3); + }); + + describe('handleVariantChange', () => { + it('toggles state', () => { + const wrapper = createWrapper(); + wrapper.instance().handleVariantChange({ target: { value: 'compact' } }); + expect(wrapper.state().variant).toBe('compact'); + }); + }); + + it('renders all needed elements for the compact view', () => { + const wrapper = createWrapper(); + wrapper.setState({ variant: 'compact' }); expect(wrapper.props().title).toBe('canvasIndex'); expect(wrapper.find(List).length).toBe(1); expect(wrapper.find(ListItem).length).toBe(3); expect(wrapper.find(ListItem).first().props().component).toEqual('li'); expect(wrapper.find(List).find(Typography).length).toBe(3); + expect(wrapper.find(CanvasThumbnail).length).toBe(0); }); + it('should set the correct labels', () => { + const wrapper = createWrapper(); + const canvases = manifesto.create(manifestJson).getSequences()[0].getCanvases(); const idsAndLabels = getIdAndLabelOfCanvases(canvases); expect(wrapper .find(List) @@ -56,6 +86,7 @@ describe('WindowSideBarCanvasPanel', () => { }); it('should call the onClick handler of a list item', () => { + const wrapper = createWrapper({ setCanvas }); wrapper.find(ListItem).at(1).simulate('click'); expect(setCanvas).toHaveBeenCalledTimes(1); }); diff --git a/src/components/CompanionWindow.js b/src/components/CompanionWindow.js index 585009d68f2a16b48366b3dc2c0bf90a8255b5c0..86eb876822abe9362fb38b7741553253ab564e7a 100644 --- a/src/components/CompanionWindow.js +++ b/src/components/CompanionWindow.js @@ -21,7 +21,7 @@ export class CompanionWindow extends Component { render() { const { classes, paperClassName, id, onCloseClick, updateCompanionWindow, isDisplayed, - position, t, windowId, title, children, + position, t, windowId, title, children, titleControls, } = this.props; return ( @@ -35,10 +35,13 @@ export class CompanionWindow extends Component { component="aside" aria-label={title} > - <Toolbar variant="dense" className={[position === 'left' ? classes.leftPadding : undefined, ns('companion-window-header')].join(' ')} disableGutters> + <Toolbar className={[classes.toolbar, position === 'left' ? classes.leftPadding : undefined, ns('companion-window-header')].join(' ')} disableGutters> <Typography variant="h3" className={classes.windowSideBarTitle}> {title} </Typography> + <div className={ns('companion-window-title-controls')}> + {titleControls} + </div> { position === 'left' ? updateCompanionWindow @@ -94,6 +97,7 @@ CompanionWindow.propTypes = { isDisplayed: PropTypes.bool, t: PropTypes.func, title: PropTypes.string, + titleControls: PropTypes.node, windowId: PropTypes.string.isRequired, children: PropTypes.node, }; @@ -107,4 +111,5 @@ CompanionWindow.defaultProps = { title: null, t: key => key, children: undefined, + titleControls: null, }; diff --git a/src/components/WindowSideBarCanvasPanel.js b/src/components/WindowSideBarCanvasPanel.js index 1ddb7b55e691deabdf1781fb93872ef8ed0550f3..e21e817eb191147daf48d592b49beb85e6ba16c8 100644 --- a/src/components/WindowSideBarCanvasPanel.js +++ b/src/components/WindowSideBarCanvasPanel.js @@ -4,6 +4,10 @@ import classNames from 'classnames'; import Typography from '@material-ui/core/Typography'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; +import FilledInput from '@material-ui/core/FilledInput'; +import MenuItem from '@material-ui/core/MenuItem'; +import FormControl from '@material-ui/core/FormControl'; +import Select from '@material-ui/core/Select'; import { CanvasThumbnail } from './CanvasThumbnail'; import ManifestoCanvas from '../lib/ManifestoCanvas'; import CompanionWindow from '../containers/CompanionWindow'; @@ -13,23 +17,103 @@ import { getIdAndLabelOfCanvases } from '../state/selectors'; * a panel showing the canvases for a given manifest */ export class WindowSideBarCanvasPanel extends Component { + /** */ + constructor(props) { + super(props); + + this.state = { variant: 'thumbnail' }; + this.handleVariantChange = this.handleVariantChange.bind(this); + } + + /** */ + handleVariantChange(event) { + this.setState({ variant: event.target.value }); + } + + /** */ + renderCompact(canvas, otherCanvas) { + const { + classes, + } = this.props; + + return ( + <> + <Typography + className={classNames(classes.label)} + variant="body2" + > + {canvas.label} + </Typography> + </> + ); + } + + /** */ + renderThumbnail(canvas, otherCanvas) { + const { + classes, config, + } = this.props; + const { width, height } = config.canvasNavigation; + const manifestoCanvas = new ManifestoCanvas(otherCanvas); + + return ( + <> + <div style={{ minWidth: 50 }}> + <CanvasThumbnail + className={classNames(classes.clickable)} + isValid={manifestoCanvas.hasValidDimensions} + imageUrl={manifestoCanvas.thumbnail(width, height)} + maxHeight={config.canvasNavigation.height} + maxWidth={config.canvasNavigation.width} + aspectRatio={manifestoCanvas.aspectRatio} + /> + </div> + <Typography + className={classNames(classes.label)} + variant="body2" + > + {canvas.label} + </Typography> + </> + ); + } + /** * render */ render() { const { - canvases, classes, config, setCanvas, t, windowId, id, + canvases, setCanvas, t, windowId, id, } = this.props; + const { variant } = this.state; + + const canvasesIdAndLabel = getIdAndLabelOfCanvases(canvases); return ( - <CompanionWindow title={t('canvasIndex')} id={id} windowId={windowId}> + <CompanionWindow + title={t('canvasIndex')} + id={id} + windowId={windowId} + titleControls={( + <FormControl variant="filled"> + <Select + value={variant} + onChange={this.handleVariantChange} + name="variant" + autoWidth + variant="filled" + input={<FilledInput name="variant" />} + > + <MenuItem value="compact">Compact List</MenuItem> + <MenuItem value="thumbnail">Thumbnail List</MenuItem> + </Select> + </FormControl> + )} + > <List> { canvasesIdAndLabel.map((canvas, canvasIndex) => { - const { width, height } = config.canvasNavigation; - const manifestoCanvas = new ManifestoCanvas(canvases[canvasIndex]); - const isValid = manifestoCanvas.hasValidDimensions; const onClick = () => { setCanvas(windowId, canvasIndex); }; // eslint-disable-line require-jsdoc, max-len return ( @@ -40,22 +124,8 @@ export class WindowSideBarCanvasPanel extends Component { button component="li" > - <div style={{ minWidth: 50 }}> - <CanvasThumbnail - className={classNames(classes.clickable)} - isValid={isValid} - imageUrl={manifestoCanvas.thumbnail(width, height)} - maxHeight={config.canvasNavigation.height} - maxWidth={config.canvasNavigation.width} - aspectRatio={manifestoCanvas.aspectRatio} - /> - </div> - <Typography - className={classNames(classes.label)} - variant="body2" - > - {canvas.label} - </Typography> + {variant === 'compact' && this.renderCompact(canvas, canvases[canvasIndex])} + {variant === 'thumbnail' && this.renderThumbnail(canvas, canvases[canvasIndex])} </ListItem> ); }) diff --git a/src/containers/CompanionWindow.js b/src/containers/CompanionWindow.js index 3d7e6724df9b27c38e139c943d1770ec067c1d78..b49ca85c4803c54a6d2ce43a4e97ef6572a577b5 100644 --- a/src/containers/CompanionWindow.js +++ b/src/containers/CompanionWindow.js @@ -59,6 +59,9 @@ const styles = theme => ({ positionButton: { order: -100, }, + toolbar: { + minHeight: 'max-content', + }, leftPadding: { ...theme.mixins.gutters(), paddingRight: 0, diff --git a/src/styles/index.scss b/src/styles/index.scss index d7a3b3ea2a454126e5ee4f41179efae4983487fe..3ee4ebacd60d346ba7a792df99249db8630ccce5 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -20,6 +20,21 @@ } } + &-companion-window-header { + flex-wrap: wrap; + } + + &-companion-window-title-controls { + order: 1000; + flex-grow: 1; + } + + &-companion-window-bottom { + .mirador-companion-window-title-controls { + order: unset; + } + } + &-companion-window-header { background: $surface-dark; }