diff --git a/__tests__/src/components/WindowSideBarCanvasPanel.test.js b/__tests__/src/components/WindowSideBarCanvasPanel.test.js index e01de7a023d63ddbb5747e67434e9a7e6450650e..cbd5a3ad1011b5d659eb4b57714c588229498ea1 100644 --- a/__tests__/src/components/WindowSideBarCanvasPanel.test.js +++ b/__tests__/src/components/WindowSideBarCanvasPanel.test.js @@ -3,6 +3,8 @@ 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 ExpansionPanel from '@material-ui/core/ExpansionPanel'; +import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; import manifesto from 'manifesto.js'; import { WindowSideBarCanvasPanel } from '../../../src/components/WindowSideBarCanvasPanel'; import { CanvasThumbnail } from '../../../src/components/CanvasThumbnail'; @@ -65,6 +67,57 @@ describe('WindowSideBarCanvasPanel', () => { expect(wrapper.find(CanvasThumbnail).length).toBe(0); }); + it('renders elements for the TOC view', () => { + const wrapper = createWrapper({ + structures: [{ + id: 'top', + getLabel: () => [{ value: 'top' }], + getCanvasIds: () => [], + getRanges: () => [ + { + id: '1', getLabel: () => [{ value: 'empty' }], getRanges: () => [], getCanvasIds: () => [], + }, + { + id: '2', + getLabel: () => [{ value: 'one-with-ranges' }], + getRanges: () => [ + { + id: '2.1', getLabel: () => [{ value: 'subrange' }], getRanges: () => [], getCanvasIds: () => [], + }, + ], + getCanvasIds: () => [], + }, + { + id: '3', + getLabel: () => [{ value: 'one-with-canvases' }], + getRanges: () => [], + getCanvasIds: () => [ + 'http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json', + ], + }, + ], + }], + }); + wrapper.setState({ variant: 'toc' }); + + expect(wrapper.find(ExpansionPanel).length).toBe(5); + + expect(wrapper + .find(ExpansionPanel) + .at(1) + .find(ExpansionPanelSummary) + .at(0) + .render() + .text()).toEqual('empty'); + + expect(wrapper.find(ExpansionPanel).at(2).find(List).find(ExpansionPanel).length).toEqual(1); + + expect(wrapper + .find(ExpansionPanel).at(4).find(List) + .find(Typography) + .render() + .text()).toEqual('Test 19 Canvas: 1'); + }); it('should set the correct labels', () => { const wrapper = createWrapper(); diff --git a/locales/de/translation.json b/locales/de/translation.json index 19413ee6acfeedec1225c39b990b3aa69b601d4d..372d6a2e29840b4d345fa05d5b224efede72eb8a 100644 --- a/locales/de/translation.json +++ b/locales/de/translation.json @@ -55,6 +55,7 @@ "thumbnailNavigation": "Miniaturansicht", "thumbnails": "Miniaturansicht", "thumbnailList": "Miniaturansicht", + "tocList": "Inhaltsverzeichnis", "toggleWindowSideBar": "Seitenleiste umschalten", "tryAgain": "Wiederholen", "untitled": "[Unbenannt]", diff --git a/locales/en/translation.json b/locales/en/translation.json index 6191ecc6b3f5a5745a6722d7e7c7da4e0a66b84f..d9bf52bf2979083549f6d92a9906517bc9f2b4d9 100644 --- a/locales/en/translation.json +++ b/locales/en/translation.json @@ -59,6 +59,7 @@ "thumbnailNavigation": "Thumbnail carousel", "thumbnails": "Thumbnails", "thumbnailList": "Thumbnail List", + "tocList": "Table of Contents", "toggleWindowSideBar": "Toggle window sidebar", "tryAgain": "Try again", "untitled": "[Untitled]", diff --git a/src/components/WindowSideBarCanvasPanel.js b/src/components/WindowSideBarCanvasPanel.js index 556255b1dedec39becf1339b4e91b67d0db5a2c9..8b9e5ff09107bd78fe6aec0cab7daabe9eda077d 100644 --- a/src/components/WindowSideBarCanvasPanel.js +++ b/src/components/WindowSideBarCanvasPanel.js @@ -8,6 +8,9 @@ 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 ExpansionPanel from '@material-ui/core/ExpansionPanel'; +import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; +import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; import { CanvasThumbnail } from './CanvasThumbnail'; import ManifestoCanvas from '../lib/ManifestoCanvas'; import CompanionWindow from '../containers/CompanionWindow'; @@ -78,18 +81,118 @@ export class WindowSideBarCanvasPanel extends Component { ); } + /** */ + renderToc(structures, defaultExpanded = false) { + const { + canvases, classes, setCanvas, windowId, + } = this.props; + + return ( + structures.map(canvasOrRange => ( + <ExpansionPanel + defaultExpanded={defaultExpanded} + key={canvasOrRange.id} + elevation={0} + square + > + <ExpansionPanelSummary style={{ backgroundColor: '#eee' }}> + {canvasOrRange.getLabel().map(label => label.value)[0]} + </ExpansionPanelSummary> + <ExpansionPanelDetails style={{ flexDirection: 'column', paddingRight: 0, paddingLeft: 8 }}> + { + canvasOrRange.getRanges().length > 0 + && ( + <List> + { + this.renderToc(canvasOrRange.getRanges()) + } + </List> + ) + } + <List> + { + canvasOrRange.getCanvasIds().map((canvasId) => { + const canvas = canvases.find(e => e.id === canvasId); + if (!canvas) return <></>; + + const onClick = () => { setCanvas(windowId, canvas.index); }; // eslint-disable-line require-jsdoc, max-len + + return ( + <ListItem + key={canvas.id} + alignItems="flex-start" + onClick={onClick} + button + component="li" + disableGutters + > + <Typography + className={classNames(classes.label)} + variant="body2" + > + {canvas.getLabel().map(label => label.value)[0]} + </Typography> + </ListItem> + ); + }) + } + </List> + </ExpansionPanelDetails> + </ExpansionPanel> + )) + ); + } + + /** */ + renderList() { + const { + canvases, structures, setCanvas, windowId, + } = this.props; + + const { variant } = this.state; + + const canvasesIdAndLabel = getIdAndLabelOfCanvases(canvases); + + switch (variant) { + case 'toc': + return this.renderToc(structures, true); + default: + return ( + <List> + { + canvasesIdAndLabel.map((canvas, canvasIndex) => { + const onClick = () => { setCanvas(windowId, canvasIndex); }; // eslint-disable-line require-jsdoc, max-len + + return ( + <ListItem + key={canvas.id} + alignItems="flex-start" + onClick={onClick} + button + component="li" + > + {variant === 'compact' && this.renderCompact(canvas, canvases[canvasIndex])} + {variant === 'thumbnail' && this.renderThumbnail(canvas, canvases[canvasIndex])} + </ListItem> + ); + }) + } + </List> + ); + } + } + /** * render */ render() { const { - canvases, setCanvas, t, windowId, id, + t, windowId, id, structures, } = this.props; const { variant } = this.state; - const canvasesIdAndLabel = getIdAndLabelOfCanvases(canvases); return ( <CompanionWindow title={t('canvasIndex')} @@ -107,30 +210,12 @@ export class WindowSideBarCanvasPanel extends Component { > <MenuItem value="compact">{ t('compactList') }</MenuItem> <MenuItem value="thumbnail">{ t('thumbnailList') }</MenuItem> + { structures.length > 0 && <MenuItem value="toc">{ t('tocList') }</MenuItem> } </Select> </FormControl> )} > - <List> - { - canvasesIdAndLabel.map((canvas, canvasIndex) => { - const onClick = () => { setCanvas(windowId, canvasIndex); }; // eslint-disable-line require-jsdoc, max-len - - return ( - <ListItem - key={canvas.id} - alignItems="flex-start" - onClick={onClick} - button - component="li" - > - {variant === 'compact' && this.renderCompact(canvas, canvases[canvasIndex])} - {variant === 'thumbnail' && this.renderThumbnail(canvas, canvases[canvasIndex])} - </ListItem> - ); - }) - } - </List> + { this.renderList() } </CompanionWindow> ); } @@ -138,6 +223,7 @@ export class WindowSideBarCanvasPanel extends Component { WindowSideBarCanvasPanel.propTypes = { canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types + structures: PropTypes.array, // eslint-disable-line react/forbid-prop-types classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types config: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types setCanvas: PropTypes.func.isRequired, @@ -145,3 +231,7 @@ WindowSideBarCanvasPanel.propTypes = { windowId: PropTypes.string.isRequired, id: PropTypes.string.isRequired, }; + +WindowSideBarCanvasPanel.defaultProps = { + structures: [], +}; diff --git a/src/containers/WindowSideBarCanvasPanel.js b/src/containers/WindowSideBarCanvasPanel.js index b45b5267f05d94e5a1b2177a1904b1db04b938ff..b92299b0e8eb5866bfd1664c2fdba11be93e9ad2 100644 --- a/src/containers/WindowSideBarCanvasPanel.js +++ b/src/containers/WindowSideBarCanvasPanel.js @@ -6,6 +6,7 @@ import * as actions from '../state/actions'; import { WindowSideBarCanvasPanel } from '../components/WindowSideBarCanvasPanel'; import { getManifestCanvases, + getManifestStructures, getWindowManifest, } from '../state/selectors'; @@ -15,10 +16,12 @@ import { const mapStateToProps = (state, { windowId }) => { const manifest = getWindowManifest(state, windowId); const canvases = getManifestCanvases(manifest); + const structures = getManifestStructures(manifest); const { config } = state; return { canvases, config, + structures, }; }; diff --git a/src/state/selectors/index.js b/src/state/selectors/index.js index f2940ef1181b82bf26602e6f08275a257bd05432..8635217c76724ac243fb591749a615b885c78c26 100644 --- a/src/state/selectors/index.js +++ b/src/state/selectors/index.js @@ -70,6 +70,21 @@ export function getManifestCanvases(manifest) { return manifest.manifestation.getSequences()[0].getCanvases(); } +/** + * Return the structures of a manifest or an empty Array + */ +export function getManifestStructures(manifest) { + if (!manifest.manifestation) { + return []; + } + + if (!manifest.manifestation.getTopRanges) { + return []; + } + + return manifest.manifestation.getTopRanges(); +} + /** * Return ids and labels of canvases * @ param {Array} canvases