diff --git a/__tests__/src/components/OpenSeadragonViewer.test.js b/__tests__/src/components/OpenSeadragonViewer.test.js index 72fa4d36133e12143b78f9dd86c0dab5239984bf..b09d238145c49394749394e081cfe0c2b82d5faf 100644 --- a/__tests__/src/components/OpenSeadragonViewer.test.js +++ b/__tests__/src/components/OpenSeadragonViewer.test.js @@ -163,6 +163,25 @@ describe('OpenSeadragonViewer', () => { }); }); + /** + * + * @param props + * @returns {*} + */ + const createWrapper = props => shallow( + <OpenSeadragonViewer + tileSources={[{ '@id': 'http://foo' }]} + windowId="base" + viewer={{ x: 1, y: 0, zoom: 0.5 }} + config={props} + updateViewport={updateViewport} + canvasWorld={new CanvasWorld([])} + t={k => k} + > + <div className="foo" /> + </OpenSeadragonViewer>, + ); + describe('componentDidMount', () => { let panTo; let zoomTo; @@ -172,20 +191,7 @@ describe('OpenSeadragonViewer', () => { panTo = jest.fn(); zoomTo = jest.fn(); addHandler = jest.fn(); - wrapper = shallow( - <OpenSeadragonViewer - tileSources={[{ '@id': 'http://foo' }]} - windowId="base" - viewer={{ x: 1, y: 0, zoom: 0.5 }} - config={{}} - updateViewport={updateViewport} - canvasWorld={new CanvasWorld([])} - t={k => k} - > - <div className="foo" /> - </OpenSeadragonViewer>, - ); - + wrapper = createWrapper(); wrapper.instance().ref = { current: true }; OpenSeadragon.mockImplementation(() => ({ @@ -223,20 +229,45 @@ describe('OpenSeadragonViewer', () => { wrapper.instance().componentDidMount(); expect(addHandler).toHaveBeenCalledWith('update-viewport', expect.anything()); }); + }); - it('sets up a listener on canvas-click', () => { + describe('canvas-click handlers', () => { + const addHandler = jest.fn(); + const panTo = jest.fn(); + const zoomTo = jest.fn(); + it('conditionally sets up "disable zoom action" handler on canvas-click', () => { + wrapper = createWrapper({ isCanvasClickZoomActionDisabled: true }); + wrapper.instance().ref = { current: true }; + OpenSeadragon.mockImplementation(() => ({ + addHandler, + viewport: { panTo, zoomTo }, + })); wrapper.instance().componentDidMount(); - expect(addHandler).toHaveBeenNthCalledWith(5, 'canvas-click', OpenSeadragonViewer.onCanvasClick); + expect(addHandler).toHaveBeenCalledWith('canvas-click', OpenSeadragonViewer.onCanvasClick); }); - }); - - describe('onCanvasClick', () => { - it('sets preventDefaultAction', () => { + it('sets preventDefaultAction on CanvasClick', () => { const event = { preventDefaultAction: () => {} }; jest.spyOn(event, 'preventDefaultAction'); OpenSeadragonViewer.onCanvasClick(event); expect(event.preventDefaultAction).toBe(true); }); + it('conditionally sets up "after focus, enable click zoom action" handler on canvas-click', () => { + wrapper = createWrapper({ isCanvasClickZoomActionAfterFocusEnabled: true }); + wrapper.instance().ref = { current: true }; + OpenSeadragon.mockImplementation(() => ({ + addHandler, + viewport: { panTo, zoomTo }, + })); + wrapper.instance().componentDidMount(); + expect(addHandler).toHaveBeenCalledWith('canvas-click', wrapper.instance().onCanvasFocus); + }); + it('sets preventDefaultAction on CanvasFocus', () => { + const event = { preventDefaultAction: () => {} }; + jest.spyOn(event, 'preventDefaultAction'); + wrapper.instance().justReceivedFocus = true; + wrapper.instance().onCanvasFocus(event); + expect(event.preventDefaultAction).toBe(true); + }); }); describe('componentDidUpdate', () => { diff --git a/src/components/OpenSeadragonViewer.js b/src/components/OpenSeadragonViewer.js index 17f7582095649c9b84073940b45434de5a8d6f8a..42b7771d166565e73cb9964178f9a41a1a8c6f84 100644 --- a/src/components/OpenSeadragonViewer.js +++ b/src/components/OpenSeadragonViewer.js @@ -53,17 +53,22 @@ export class OpenSeadragonViewer extends Component { // An initial value for the updateCanvas method this.updateCanvas = () => {}; this.ref = React.createRef(); + this.onCanvasFocus = this.onCanvasFocus.bind(this); this.onUpdateViewport = this.onUpdateViewport.bind(this); this.onViewportChange = this.onViewportChange.bind(this); this.zoomToWorld = this.zoomToWorld.bind(this); this.osdUpdating = false; + this.justReceivedFocus = false; } /** * React lifecycle event */ componentDidMount() { - const { viewer } = this.props; + const { + config, + viewer, + } = this.props; if (!this.ref.current) { return; } @@ -75,7 +80,6 @@ export class OpenSeadragonViewer extends Component { preserveImageSizeOnResize: true, preserveViewport: true, showNavigationControl: false, - }); this.osdCanvasOverlay = new OpenSeadragonCanvasOverlay(this.viewer); @@ -88,7 +92,11 @@ export class OpenSeadragonViewer extends Component { this.viewer.addHandler('animation-finish', () => { this.osdUpdating = false; }); - this.viewer.addHandler('canvas-click', OpenSeadragonViewer.onCanvasClick); + if (config && config.isCanvasClickZoomActionDisabled) { + this.viewer.addHandler('canvas-click', OpenSeadragonViewer.onCanvasClick); + } else if (config && config.isCanvasClickZoomActionAfterFocusEnabled) { + this.viewer.addHandler('canvas-click', this.onCanvasFocus); + } if (viewer) { this.viewer.viewport.panTo(viewer, true); this.viewer.viewport.zoomTo(viewer.zoom, viewer, true); @@ -104,7 +112,7 @@ export class OpenSeadragonViewer extends Component { */ componentDidUpdate(prevProps) { const { - viewer, highlightedAnnotations, selectedAnnotations, + focused, highlightedAnnotations, selectedAnnotations, viewer, } = this.props; const highlightsUpdated = !OpenSeadragonViewer.annotationsMatch( highlightedAnnotations, prevProps.highlightedAnnotations, @@ -125,6 +133,11 @@ export class OpenSeadragonViewer extends Component { this.viewer.forceRedraw(); } + if (focused && prevProps.focused !== focused) { + this.justReceivedFocus = true; + setTimeout(() => { this.justReceivedFocus = false; }, 50); + } + if (!this.tileSourcesMatch(prevProps.tileSources)) { this.viewer.close(); this.addAllTileSources(); @@ -148,6 +161,15 @@ export class OpenSeadragonViewer extends Component { this.viewer.removeAllHandlers(); } + /** + * + * @param e + */ + onCanvasFocus(e) { + e.preventDefaultAction = this.justReceivedFocus; + this.justReceivedFocus = false; + } + /** * onUpdateViewport - fires during OpenSeadragon render method. */ @@ -293,17 +315,20 @@ export class OpenSeadragonViewer extends Component { OpenSeadragonViewer.defaultProps = { children: null, + config: null, + focused: false, highlightedAnnotations: [], label: null, selectedAnnotations: [], tileSources: [], viewer: null, - }; OpenSeadragonViewer.propTypes = { canvasWorld: PropTypes.instanceOf(CanvasWorld).isRequired, children: PropTypes.node, + config: PropTypes.object, // eslint-disable-line react/forbid-prop-types + focused: PropTypes.bool, highlightedAnnotations: PropTypes.arrayOf(PropTypes.object), label: PropTypes.string, selectedAnnotations: PropTypes.arrayOf(PropTypes.object), diff --git a/src/config/settings.js b/src/config/settings.js index 3095aa803fdda9bc963c48a270b26ec6e2168c5b..f343c5fbe8a31b1dc3c6329a2e08030830a731b9 100644 --- a/src/config/settings.js +++ b/src/config/settings.js @@ -198,6 +198,10 @@ export default { displayAllAnnotations: false, // Configure if annotations to be displayed on the canvas by default when fetched translations: { // Translations can be added to inject new languages or override existing labels }, + viewer: { + isCanvasClickZoomActionDisabled: false, + isCanvasClickZoomActionAfterFocusEnabled: true, + }, window: { allowClose: true, // Configure if windows can be closed or not allowMaximize: true, // Configure if windows can be maximized or not diff --git a/src/containers/OpenSeadragonViewer.js b/src/containers/OpenSeadragonViewer.js index ea42bd87b5bde1948869ca3689a4c0c447605bdb..6e3d8bb0972279e0751167e519cd60ef0e66e8a5 100644 --- a/src/containers/OpenSeadragonViewer.js +++ b/src/containers/OpenSeadragonViewer.js @@ -20,6 +20,8 @@ import { */ const mapStateToProps = (state, { windowId }) => ({ canvasWorld: new CanvasWorld(getSelectedCanvases(state, { windowId })), + config: state.config.viewer, + focused: state.workspace.focusedWindowId === windowId, highlightedAnnotations: getHighlightedAnnotationsOnCanvases(state, { windowId }), label: getCanvasLabel(state, { windowId }), selectedAnnotations: getSelectedAnnotationsOnCanvases(state, { windowId }),