diff --git a/__tests__/src/components/CanvasAnnotations.test.js b/__tests__/src/components/CanvasAnnotations.test.js index b68f0e367ff857dc0d02c82f1e855aab20a7c4a8..63f0674ddb3bcd05b49eb131005d812fd99ecb75 100644 --- a/__tests__/src/components/CanvasAnnotations.test.js +++ b/__tests__/src/components/CanvasAnnotations.test.js @@ -5,6 +5,7 @@ import Chip from '@material-ui/core/Chip'; import MenuList from '@material-ui/core/MenuList'; import MenuItem from '@material-ui/core/MenuItem'; import { CanvasAnnotations } from '../../../src/components/CanvasAnnotations'; +import { ScrollTo } from '../../../src/components/ScrollTo'; /** Utility function to wrap CanvasAnnotations */ function createWrapper(props) { @@ -62,6 +63,14 @@ describe('CanvasAnnotations', () => { expect(wrapper.find(MenuItem).length).toEqual(2); }); + it('scrolls to the selected annotation', () => { + wrapper = createWrapper({ annotations, selectedAnnotationId: 'abc123' }); + + expect(wrapper.find(ScrollTo).length).toEqual(2); + expect(wrapper.find(ScrollTo).first().prop('scrollTo')).toEqual(true); + expect(wrapper.find(ScrollTo).last().prop('scrollTo')).toEqual(false); + }); + it('renders a Chip for every tag', () => { wrapper = createWrapper({ annotations }); diff --git a/src/components/CanvasAnnotations.js b/src/components/CanvasAnnotations.js index 23150c6a39414a3d8026902e74f3e259677ad44a..137a4ef0bcf3f1ed42a78e90ef5df08073752b2e 100644 --- a/src/components/CanvasAnnotations.js +++ b/src/components/CanvasAnnotations.js @@ -7,6 +7,7 @@ import MenuItem from '@material-ui/core/MenuItem'; import ListItemText from '@material-ui/core/ListItemText'; import Typography from '@material-ui/core/Typography'; import SanitizedHtml from '../containers/SanitizedHtml'; +import { ScrollTo } from './ScrollTo'; /** * CanvasAnnotations ~ @@ -59,6 +60,7 @@ export class CanvasAnnotations extends Component { const { annotations, classes, index, label, selectedAnnotationId, t, totalSize, listContainerComponent, htmlSanitizationRuleSet, hoveredAnnotationIds, + containerRef, } = this.props; if (annotations.length === 0) return <></>; @@ -70,38 +72,45 @@ export class CanvasAnnotations extends Component { <MenuList autoFocusItem variant="selectedMenu"> { annotations.map(annotation => ( - <MenuItem - button - component={listContainerComponent} - className={clsx( - classes.annotationListItem, - { - [classes.hovered]: hoveredAnnotationIds.includes(annotation.id), - }, - )} - key={annotation.id} - annotationid={annotation.id} - selected={selectedAnnotationId === annotation.id} - onClick={e => this.handleClick(e, annotation)} - onFocus={() => this.handleAnnotationHover(annotation)} - onBlur={this.handleAnnotationBlur} - onMouseEnter={() => this.handleAnnotationHover(annotation)} - onMouseLeave={this.handleAnnotationBlur} + <ScrollTo + containerRef={containerRef} + key={`${annotation.id}-scroll`} + offsetTop={96} // offset for the height of the form above + scrollTo={selectedAnnotationId === annotation.id} > - <ListItemText primaryTypographyProps={{ variant: 'body2' }}> - <SanitizedHtml - ruleSet={htmlSanitizationRuleSet} - htmlString={annotation.content} - /> - <div> + <MenuItem + button + component={listContainerComponent} + className={clsx( + classes.annotationListItem, { - annotation.tags.map(tag => ( - <Chip size="small" variant="outlined" label={tag} id={tag} className={classes.chip} key={tag.toString()} /> - )) - } - </div> - </ListItemText> - </MenuItem> + [classes.hovered]: hoveredAnnotationIds.includes(annotation.id), + }, + )} + key={annotation.id} + annotationid={annotation.id} + selected={selectedAnnotationId === annotation.id} + onClick={e => this.handleClick(e, annotation)} + onFocus={() => this.handleAnnotationHover(annotation)} + onBlur={this.handleAnnotationBlur} + onMouseEnter={() => this.handleAnnotationHover(annotation)} + onMouseLeave={this.handleAnnotationBlur} + > + <ListItemText primaryTypographyProps={{ variant: 'body2' }}> + <SanitizedHtml + ruleSet={htmlSanitizationRuleSet} + htmlString={annotation.content} + /> + <div> + { + annotation.tags.map(tag => ( + <Chip size="small" variant="outlined" label={tag} id={tag} className={classes.chip} key={tag.toString()} /> + )) + } + </div> + </ListItemText> + </MenuItem> + </ScrollTo> )) } </MenuList> @@ -118,6 +127,10 @@ CanvasAnnotations.propTypes = { }), ), classes: PropTypes.objectOf(PropTypes.string), + containerRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({ current: PropTypes.instanceOf(Element) }), + ]), deselectAnnotation: PropTypes.func.isRequired, hoverAnnotation: PropTypes.func.isRequired, hoveredAnnotationIds: PropTypes.arrayOf(PropTypes.string), @@ -134,6 +147,7 @@ CanvasAnnotations.propTypes = { CanvasAnnotations.defaultProps = { annotations: [], classes: {}, + containerRef: undefined, hoveredAnnotationIds: [], htmlSanitizationRuleSet: 'iiif', listContainerComponent: 'li', diff --git a/src/components/WindowSideBarAnnotationsPanel.js b/src/components/WindowSideBarAnnotationsPanel.js index 94dd831454b04554b89ea6380e7f241204d0299d..f5a13189296267a8fb9aa9acb0b6b43cf41ed87f 100644 --- a/src/components/WindowSideBarAnnotationsPanel.js +++ b/src/components/WindowSideBarAnnotationsPanel.js @@ -10,6 +10,13 @@ import ns from '../config/css-ns'; * WindowSideBarAnnotationsPanel ~ */ export class WindowSideBarAnnotationsPanel extends Component { + /** */ + constructor(props) { + super(props); + + this.containerRef = React.createRef(); + } + /** * Returns the rendered component */ @@ -23,6 +30,8 @@ export class WindowSideBarAnnotationsPanel extends Component { paperClassName={ns('window-sidebar-annotation-panel')} windowId={windowId} id={id} + ref={this.containerRef} + otherRef={this.containerRef} titleControls={<AnnotationSettings windowId={windowId} />} > <div className={classes.section}> @@ -32,6 +41,7 @@ export class WindowSideBarAnnotationsPanel extends Component { {canvasIds.map((canvasId, index) => ( <CanvasAnnotations canvasId={canvasId} + containerRef={this.containerRef} key={canvasId} index={index} totalSize={canvasIds.length}