diff --git a/__tests__/integration/mirador/toc.html b/__tests__/integration/mirador/toc.html new file mode 100644 index 0000000000..875765b48b --- /dev/null +++ b/__tests__/integration/mirador/toc.html @@ -0,0 +1,29 @@ + + + + + + + Mirador - Table of contents + + + +
+ + + + diff --git a/__tests__/src/selectors/manifests.test.js b/__tests__/src/selectors/manifests.test.js index 3e4b4d9415..776d23df55 100644 --- a/__tests__/src/selectors/manifests.test.js +++ b/__tests__/src/selectors/manifests.test.js @@ -25,6 +25,7 @@ import { getManifestRenderings, getManifestUrl, getManifestViewingHint, + getManifestTreeStructure, getMetadataLocales, getRequiredStatement, getRights, diff --git a/src/components/SidebarIndexTableOfContents.js b/src/components/SidebarIndexTableOfContents.js new file mode 100644 index 0000000000..7eba105f4e --- /dev/null +++ b/src/components/SidebarIndexTableOfContents.js @@ -0,0 +1,66 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import TreeView from '@material-ui/lab/TreeView'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import TreeItem from '@material-ui/lab/TreeItem'; + +/** */ +export class SidebarIndexTableOfContents extends Component { + /** */ + selectTreeItem(node) { + const { setCanvas, windowId } = this.props; + // Do not select if there are child nodes + if (node.nodes.length > 0) { + return; + } + const canvas = node.data.getCanvasIds()[0]; + setCanvas(windowId, canvas); + } + + /** */ + buildTreeItems(nodes) { + return ( + nodes.map(node => ( + this.selectTreeItem(node)} + > + {node.nodes.length > 0 ? this.buildTreeItems(node.nodes) : null} + + )) + ); + } + + /** */ + render() { + const { + classes, treeStructure, + } = this.props; + + if (!treeStructure) { + return <>; + } + + return ( + <> + } + defaultExpandIcon={} + > + {this.buildTreeItems(treeStructure.nodes)} + + + ); + } +} + +SidebarIndexTableOfContents.propTypes = { + classes: PropTypes.objectOf(PropTypes.string).isRequired, + setCanvas: PropTypes.func.isRequired, + treeStructure: PropTypes.objectOf().isRequired, + windowId: PropTypes.string.isRequired, +}; diff --git a/src/components/WindowSideBarCanvasPanel.js b/src/components/WindowSideBarCanvasPanel.js index a3416e7b14..9371c93991 100644 --- a/src/components/WindowSideBarCanvasPanel.js +++ b/src/components/WindowSideBarCanvasPanel.js @@ -7,6 +7,7 @@ import RootRef from '@material-ui/core/RootRef'; import Select from '@material-ui/core/Select'; import CompanionWindow from '../containers/CompanionWindow'; import SidebarIndexList from '../containers/SidebarIndexList'; +import SidebarIndexTableOfContents from '../containers/SidebarIndexTableOfContents'; /** * a panel showing the canvases for a given manifest @@ -46,6 +47,24 @@ export class WindowSideBarCanvasPanel extends Component { } = this.props; const { variantSelectionOpened } = this.state; + let listComponent; + if (variant === 'tableOfContents') { + listComponent = ( + + ); + } else { + listComponent = ( + + ); + } return ( { t('compactList') } { t('thumbnailList') } + { t('tableOfContentsList') } )} > - + {listComponent} ); @@ -97,10 +117,10 @@ WindowSideBarCanvasPanel.propTypes = { t: PropTypes.func.isRequired, toggleDraggingEnabled: PropTypes.func.isRequired, updateVariant: PropTypes.func.isRequired, - variant: PropTypes.oneOf(['compact', 'thumbnail']), + variant: PropTypes.oneOf(['compact', 'thumbnail', 'tableOfContents']), windowId: PropTypes.string.isRequired, }; WindowSideBarCanvasPanel.defaultProps = { - variant: 'thumbnail', + variant: 'tableOfContents', }; diff --git a/src/containers/SidebarIndexTableOfContents.js b/src/containers/SidebarIndexTableOfContents.js new file mode 100644 index 0000000000..dc65dd848e --- /dev/null +++ b/src/containers/SidebarIndexTableOfContents.js @@ -0,0 +1,49 @@ +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 { SidebarIndexTableOfContents } from '../components/SidebarIndexTableOfContents'; +import { + getManifestoInstance, + getManifestTreeStructure, + getVisibleCanvases, +} from '../state/selectors'; +import * as actions from '../state/actions'; + + +/** + * mapStateToProps - to hook up connect + */ +const mapStateToProps = (state, { id, windowId }) => ({ + canvases: getVisibleCanvases(state, { windowId }), + manifesto: getManifestoInstance(state, { windowId }), + treeStructure: getManifestTreeStructure(state, { windowId }), +}); + +/** + * mapStateToProps - used to hook up connect to state + * @memberof SidebarIndexTableOfContents + * @private + */ +const mapDispatchToProps = (dispatch, { id, windowId }) => ({ + setCanvas: (...args) => dispatch(actions.setCanvas(...args)), +}); + +/** + * Styles for withStyles HOC + */ +const styles = theme => ({ + root: { + flexGrow: 1, + }, +}); + +const enhance = compose( + withStyles(styles), + withTranslation(), + connect(mapStateToProps, mapDispatchToProps), + withPlugins('SidebarIndexTableOfContents'), +); + +export default enhance(SidebarIndexTableOfContents); diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 46e1bf03b7..b45c8315fe 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -109,6 +109,7 @@ "single": "Single", "startHere": "Start Here", "suggestSearch": "Search this document for \"{{ query }}\"", + "tableOfContentsList": "Table of contents", "theme": "Theme", "thumbnailList": "Thumbnail list", "thumbnailNavigation": "Thumbnails", diff --git a/src/state/selectors/manifests.js b/src/state/selectors/manifests.js index e6fe70f08d..2889174595 100644 --- a/src/state/selectors/manifests.js +++ b/src/state/selectors/manifests.js @@ -432,3 +432,12 @@ export const getManifestAutocompleteService = createSelector( return autocompleteService && autocompleteService; }, ); + +/** */ +export const getManifestTreeStructure = createSelector( + [getManifestoInstance], + (manifest) => { + if (!manifest) return null; + return manifest.getDefaultTree(); + }, +);