Skip to content
Snippets Groups Projects
Unverified Commit dc0b5e79 authored by Camille Villa's avatar Camille Villa Committed by GitHub
Browse files

Refactor canvas nav (#2857)

Refactor canvas nav
parents 48a06a3f 332e46a0
No related branches found
No related tags found
No related merge requests found
Showing with 426 additions and 189 deletions
import React from 'react';
import { shallow } from 'enzyme';
import Typography from '@material-ui/core/Typography';
import { SidebarIndexCompact } from '../../../src/components/SidebarIndexCompact';
/** */
function createWrapper(props) {
return shallow(
<SidebarIndexCompact
canvas={{ label: 'yolo' }}
classes={{}}
{...props}
/>,
);
}
describe('SidebarIndexCompact', () => {
it('creates Typography with a canvas label', () => {
const wrapper = createWrapper();
expect(wrapper.find(Typography).length).toBe(1);
expect(wrapper.text()).toBe('yolo');
});
});
import React from 'react';
import { shallow } from 'enzyme';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import manifesto from 'manifesto.js';
import { SidebarIndexList } from '../../../src/components/SidebarIndexList';
import SidebarIndexThumbnail from '../../../src/containers/SidebarIndexThumbnail';
import SidebarIndexCompact from '../../../src/containers/SidebarIndexCompact';
import manifestJson from '../../fixtures/version-2/019.json';
/**
* Helper function to create a shallow wrapper around SidebarIndexList
*/
function createWrapper(props) {
const canvases = manifesto.create(manifestJson).getSequences()[0].getCanvases();
return shallow(
<SidebarIndexList
id="asdf"
canvases={canvases}
classes={{}}
t={key => key}
windowId="xyz"
setCanvas={() => {}}
config={{ canvasNavigation: { height: 100 } }}
updateVariant={() => {}}
selectedCanvases={[canvases[1]]}
{...props}
/>,
);
}
describe('SidebarIndexList', () => {
let setCanvas;
beforeEach(() => {
setCanvas = jest.fn();
});
it('renders all needed elements for the thumbnail view', () => {
const wrapper = createWrapper();
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(ListItem).at(1).props().selected).toBe(true);
expect(wrapper.find(List).find(SidebarIndexThumbnail).length).toBe(3);
});
it('renders all needed elements for the compact view', () => {
const wrapper = createWrapper({ variant: 'compact' });
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(SidebarIndexCompact).length).toBe(3);
});
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);
});
describe('getIdAndLabelOfCanvases', () => {
it('should return id and label of each canvas in manifest', () => {
const canvases = manifesto
.create(manifestJson)
.getSequences()[0]
.getCanvases();
const wrapper = createWrapper({ canvases });
const received = wrapper.instance().getIdAndLabelOfCanvases(canvases);
const expected = [
{
id: 'http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json',
label: 'Test 19 Canvas: 1',
},
{
id: 'https://purl.stanford.edu/fr426cg9537/iiif/canvas/fr426cg9537_1',
label: 'Image 1',
},
{
id: 'https://purl.stanford.edu/rz176rt6531/iiif/canvas/rz176rt6531_1',
label: 'Image 2',
},
];
expect(received).toEqual(expected);
});
it('should return empty array if canvas if empty', () => {
const wrapper = createWrapper({ canvases: [] });
const received = wrapper.instance().getIdAndLabelOfCanvases([]);
expect(received).toEqual([]);
});
});
});
import React from 'react';
import { shallow } from 'enzyme';
import Typography from '@material-ui/core/Typography';
import manifesto from 'manifesto.js';
import fixture from '../../fixtures/version-2/019.json';
import { SidebarIndexThumbnail } from '../../../src/components/SidebarIndexThumbnail';
import { CanvasThumbnail } from '../../../src/components/CanvasThumbnail';
/** */
function createWrapper(props) {
return shallow(
<SidebarIndexThumbnail
canvas={{ label: 'yolo' }}
otherCanvas={manifesto.create(fixture).getSequences()[0].getCanvases()[1]}
classes={{}}
config={{ canvasNavigation: { height: 200, width: 100 } }}
{...props}
/>,
);
}
describe('SidebarIndexThumbnail', () => {
it('creates Typography with a canvas label', () => {
const wrapper = createWrapper();
expect(wrapper.find(Typography).length).toBe(1);
expect(wrapper.text()).toEqual(expect.stringContaining('yolo'));
});
it('contains a CanvasThumbnail', () => {
const wrapper = createWrapper();
expect(wrapper.find(CanvasThumbnail).length).toBe(1);
});
});
......@@ -2,10 +2,9 @@ import React from 'react';
import { shallow } from 'enzyme';
import List from '@material-ui/core/List';
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 SidebarIndexList from '../../../src/containers/SidebarIndexList';
import CompanionWindow from '../../../src/containers/CompanionWindow';
import manifestJson from '../../fixtures/version-2/019.json';
......@@ -32,21 +31,10 @@ function createWrapper(props) {
}
describe('WindowSideBarCanvasPanel', () => {
let setCanvas;
beforeEach(() => {
setCanvas = jest.fn();
});
it('renders all needed elements for the thumbnail view', () => {
it('renders SidebarIndexList', () => {
const wrapper = createWrapper();
expect(wrapper.find(CompanionWindow).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(ListItem).at(1).props().selected).toBe(true);
expect(wrapper.find(List).find(Typography).length).toBe(3);
expect(wrapper.find(CanvasThumbnail).length).toBe(3);
expect(wrapper.find(SidebarIndexList).length).toBe(1);
});
describe('handleVariantChange', () => {
......@@ -57,71 +45,4 @@ describe('WindowSideBarCanvasPanel', () => {
expect(updateVariant).toHaveBeenCalledWith('compact');
});
});
it('renders all needed elements for the compact view', () => {
const wrapper = createWrapper({ variant: 'compact' });
expect(wrapper.find(CompanionWindow).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();
expect(wrapper
.find(List)
.find(Typography)
.at(0)
.render()
.text()).toBe('Test 19 Canvas: 1');
expect(wrapper
.find(List)
.find(Typography)
.at(1)
.render()
.text()).toBe('Image 1');
});
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);
});
describe('getIdAndLabelOfCanvases', () => {
it('should return id and label of each canvas in manifest', () => {
const canvases = manifesto
.create(manifestJson)
.getSequences()[0]
.getCanvases();
const wrapper = createWrapper({ canvases });
const received = wrapper.instance().getIdAndLabelOfCanvases(canvases);
const expected = [
{
id: 'http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json',
label: 'Test 19 Canvas: 1',
},
{
id: 'https://purl.stanford.edu/fr426cg9537/iiif/canvas/fr426cg9537_1',
label: 'Image 1',
},
{
id: 'https://purl.stanford.edu/rz176rt6531/iiif/canvas/rz176rt6531_1',
label: 'Image 2',
},
];
expect(received).toEqual(expected);
});
it('should return empty array if canvas if empty', () => {
const wrapper = createWrapper({ canvases: [] });
const received = wrapper.instance().getIdAndLabelOfCanvases([]);
expect(received).toEqual([]);
});
});
});
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Typography from '@material-ui/core/Typography';
import classNames from 'classnames';
/** */
export class SidebarIndexCompact extends Component {
/** */
render() {
const {
classes, canvas,
} = this.props;
return (
<>
<Typography
className={classNames(classes.label)}
variant="body1"
>
{canvas.label}
</Typography>
</>
);
}
}
SidebarIndexCompact.propTypes = {
canvas: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
classes: PropTypes.objectOf(PropTypes.string).isRequired,
};
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import { ScrollTo } from './ScrollTo';
import ManifestoCanvas from '../lib/ManifestoCanvas';
import SidebarIndexCompact from '../containers/SidebarIndexCompact';
import SidebarIndexThumbnail from '../containers/SidebarIndexThumbnail';
/** */
export class SidebarIndexList extends Component {
/** @private */
getIdAndLabelOfCanvases() {
const { canvases } = this.props;
return canvases.map((canvas, index) => ({
id: canvas.id,
label: new ManifestoCanvas(canvas).getLabel(),
}));
}
/** */
render() {
const {
canvases,
classes,
containerRef,
selectedCanvases,
setCanvas,
variant,
windowId,
} = this.props;
const canvasesIdAndLabel = this.getIdAndLabelOfCanvases(canvases);
return (
<List>
{
canvasesIdAndLabel.map((canvas, canvasIndex) => {
const onClick = () => { setCanvas(windowId, canvas.id); }; // eslint-disable-line require-jsdoc, max-len
return (
<ScrollTo
containerRef={containerRef}
key={`${canvas.id}-${variant}`}
offsetTop={96} // offset for the height of the form above
scrollTo={!!selectedCanvases.find(c => c.id === canvas.id)}
>
<ListItem
key={canvas.id}
className={classes.listItem}
alignItems="flex-start"
onClick={onClick}
button
component="li"
selected={!!selectedCanvases.find(c => c.id === canvas.id)}
>
{variant === 'compact' && <SidebarIndexCompact canvas={canvas} />}
{variant === 'thumbnail' && <SidebarIndexThumbnail canvas={canvas} otherCanvas={canvases[canvasIndex]} />}
</ListItem>
</ScrollTo>
);
})
}
</List>
);
}
}
SidebarIndexList.propTypes = {
canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
classes: PropTypes.objectOf(PropTypes.string).isRequired,
containerRef: PropTypes.oneOf([PropTypes.func, PropTypes.object]).isRequired,
selectedCanvases: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string })),
setCanvas: PropTypes.func.isRequired,
variant: PropTypes.oneOf(['compact', 'thumbnail']),
windowId: PropTypes.string.isRequired,
};
SidebarIndexList.defaultProps = {
selectedCanvases: [],
variant: 'thumbnail',
};
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Typography from '@material-ui/core/Typography';
import classNames from 'classnames';
import ManifestoCanvas from '../lib/ManifestoCanvas';
import { CanvasThumbnail } from './CanvasThumbnail';
/** */
export class SidebarIndexThumbnail extends Component {
/** */
render() {
const {
classes, config, otherCanvas, canvas,
} = 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="body1"
>
{canvas.label}
</Typography>
</>
);
}
}
SidebarIndexThumbnail.propTypes = {
canvas: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
classes: PropTypes.objectOf(PropTypes.string).isRequired,
config: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
otherCanvas: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
import React, { Component } from 'react';
import PropTypes from 'prop-types';
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 MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import RootRef from '@material-ui/core/RootRef';
import Select from '@material-ui/core/Select';
import { CanvasThumbnail } from './CanvasThumbnail';
import { ScrollTo } from './ScrollTo';
import ManifestoCanvas from '../lib/ManifestoCanvas';
import CompanionWindow from '../containers/CompanionWindow';
import SidebarIndexList from '../containers/SidebarIndexList';
/**
* a panel showing the canvases for a given manifest
......@@ -29,16 +24,6 @@ export class WindowSideBarCanvasPanel extends Component {
this.containerRef = React.createRef();
}
/** @private */
getIdAndLabelOfCanvases() {
const { canvases } = this.props;
return canvases.map((canvas, index) => ({
id: canvas.id,
label: new ManifestoCanvas(canvas).getLabel(),
}));
}
/** @private */
handleVariantChange(event) {
const { updateVariant } = this.props;
......@@ -47,64 +32,13 @@ export class WindowSideBarCanvasPanel extends Component {
this.setState({ variantSelectionOpened: false });
}
/** */
renderCompact(canvas, otherCanvas) {
const {
classes,
} = this.props;
return (
<>
<Typography
className={classNames(classes.label)}
variant="body1"
>
{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="body1"
>
{canvas.label}
</Typography>
</>
);
}
/**
* render
*/
render() {
const {
canvases,
classes,
id,
selectedCanvases,
setCanvas,
t,
toggleDraggingEnabled,
variant,
......@@ -112,7 +46,6 @@ export class WindowSideBarCanvasPanel extends Component {
} = this.props;
const { variantSelectionOpened } = this.state;
const canvasesIdAndLabel = this.getIdAndLabelOfCanvases(canvases);
return (
<RootRef rootRef={this.containerRef}>
<CompanionWindow
......@@ -151,35 +84,7 @@ export class WindowSideBarCanvasPanel extends Component {
</FormControl>
)}
>
<List>
{
canvasesIdAndLabel.map((canvas, canvasIndex) => {
const onClick = () => { setCanvas(windowId, canvas.id); }; // eslint-disable-line require-jsdoc, max-len
return (
<ScrollTo
containerRef={this.containerRef}
key={`${canvas.id}-${variant}`}
offsetTop={96} // offset for the height of the form above
scrollTo={!!selectedCanvases.find(c => c.id === canvas.id)}
>
<ListItem
key={canvas.id}
className={classes.listItem}
alignItems="flex-start"
onClick={onClick}
button
component="li"
selected={!!selectedCanvases.find(c => c.id === canvas.id)}
>
{variant === 'compact' && this.renderCompact(canvas, canvases[canvasIndex])}
{variant === 'thumbnail' && this.renderThumbnail(canvas, canvases[canvasIndex])}
</ListItem>
</ScrollTo>
);
})
}
</List>
<SidebarIndexList id={id} containerRef={this.containerRef} windowId={windowId} />
</CompanionWindow>
</RootRef>
);
......@@ -187,12 +92,8 @@ export class WindowSideBarCanvasPanel extends Component {
}
WindowSideBarCanvasPanel.propTypes = {
canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
classes: PropTypes.objectOf(PropTypes.string).isRequired,
config: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
id: PropTypes.string.isRequired,
selectedCanvases: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string })),
setCanvas: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
toggleDraggingEnabled: PropTypes.func.isRequired,
updateVariant: PropTypes.func.isRequired,
......@@ -201,6 +102,5 @@ WindowSideBarCanvasPanel.propTypes = {
};
WindowSideBarCanvasPanel.defaultProps = {
selectedCanvases: [],
variant: 'thumbnail',
};
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles';
import { withPlugins } from '../extend/withPlugins';
import { SidebarIndexCompact } from '../components/SidebarIndexCompact';
/**
* Styles for withStyles HOC
*/
const styles = theme => ({
label: {
paddingLeft: theme.spacing(1),
},
});
const enhance = compose(
withStyles(styles),
withTranslation(),
connect(null, null),
withPlugins('SidebarIndexCompact'),
);
export default enhance(SidebarIndexCompact);
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles';
import { withPlugins } from '../extend/withPlugins';
import * as actions from '../state/actions';
import {
getCompanionWindow,
getManifestCanvases,
getVisibleCanvases,
} from '../state/selectors';
import { SidebarIndexList } from '../components/SidebarIndexList';
/**
* mapStateToProps - to hook up connect
*/
const mapStateToProps = (state, { id, windowId }) => {
const canvases = getManifestCanvases(state, { windowId });
return {
canvases,
selectedCanvases: getVisibleCanvases(state, { windowId }),
variant: getCompanionWindow(state, { companionWindowId: id, windowId }).variant,
};
};
/**
* mapStateToProps - used to hook up connect to state
* @memberof SidebarIndexList
* @private
*/
const mapDispatchToProps = (dispatch, { id, windowId }) => ({
setCanvas: (...args) => dispatch(actions.setCanvas(...args)),
});
/**
* Styles for withStyles HOC
*/
const styles = theme => ({
label: {
paddingLeft: theme.spacing(1),
},
listItem: {
borderBottom: `0.5px solid ${theme.palette.divider}`,
paddingRight: theme.spacing(1),
},
});
const enhance = compose(
withStyles(styles),
withTranslation(),
connect(mapStateToProps, mapDispatchToProps),
withPlugins('SidebarIndexList'),
);
export default enhance(SidebarIndexList);
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles';
import { withPlugins } from '../extend/withPlugins';
import { SidebarIndexThumbnail } from '../components/SidebarIndexThumbnail';
/**
* mapStateToProps - used to hook up state to props
* @memberof SidebarIndexThumbnail
* @private
*/
const mapStateToProps = (state, { data }) => ({
config: state.config,
});
/**
* Styles for withStyles HOC
*/
const styles = theme => ({
label: {
paddingLeft: theme.spacing(1),
},
});
const enhance = compose(
withStyles(styles),
withTranslation(),
connect(mapStateToProps, null),
withPlugins('SidebarIndexThumbnail'),
);
export default enhance(SidebarIndexThumbnail);
......@@ -41,16 +41,11 @@ const mapDispatchToProps = (dispatch, { id, windowId }) => ({
/**
*
* @param theme
* @returns {label: {paddingLeft: number}}}
*/
const styles = theme => ({
label: {
paddingLeft: theme.spacing(1),
},
listItem: {
borderBottom: `0.5px solid ${theme.palette.divider}`,
paddingRight: theme.spacing(1),
},
select: {
'&:focus': {
backgroundColor: theme.palette.background.paper,
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment