From 1d24ebb0829a4e4d1e082a090e1f3132ad5da9ad Mon Sep 17 00:00:00 2001 From: Chris Beer <cabeer@stanford.edu> Date: Mon, 11 Mar 2019 14:45:07 -0700 Subject: [PATCH] Add toggle between compact + thumbnail canvas navigation --- .../src/components/CompanionWindow.test.js | 5 + .../WindowSideBarCanvasPanel.test.js | 63 +++++++--- src/components/CompanionWindow.js | 9 +- src/components/WindowSideBarCanvasPanel.js | 112 ++++++++++++++---- src/containers/CompanionWindow.js | 3 + src/styles/index.scss | 15 +++ 6 files changed, 168 insertions(+), 39 deletions(-) diff --git a/__tests__/src/components/CompanionWindow.test.js b/__tests__/src/components/CompanionWindow.test.js index 9c8896ccd..3666ce275 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 2d3f7f29f..e01de7a02 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 585009d68..86eb87682 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 1ddb7b55e..e21e817eb 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 3d7e6724d..b49ca85c4 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 d7a3b3ea2..3ee4ebacd 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; } -- GitLab