diff --git a/__tests__/CanvasListItem.test.js b/__tests__/CanvasListItem.test.js index 58d2576758426ab20ab880800bdd6d0cede0cc20..ad88fd2e582aa807b82c613ed9972906afbe0baa 100644 --- a/__tests__/CanvasListItem.test.js +++ b/__tests__/CanvasListItem.test.js @@ -26,6 +26,7 @@ function createWrapper(props, context = {}) { canvases: [], receiveAnnotation, storageAdapter, + switchToSingleCanvasView: () => undefined, ...context, }} > diff --git a/__tests__/miradorAnnotationPlugin.test.js b/__tests__/miradorAnnotationPlugin.test.js index c77189fe2c07566acf90d7880f7fde7a0bd4bad7..2ea9032469f3da38279c6c0dc4c11b316ed62ab9 100644 --- a/__tests__/miradorAnnotationPlugin.test.js +++ b/__tests__/miradorAnnotationPlugin.test.js @@ -14,6 +14,8 @@ function createWrapper(props) { targetProps={{}} addCompanionWindow={jest.fn()} receiveAnnotation={jest.fn()} + switchToSingleCanvasView={jest.fn()} + windowViewType="single" {...props} />, ); @@ -40,6 +42,14 @@ describe('MiradorAnnotation', () => { }, ); }); + it('opens single canvas view dialog if not in single view', () => { + wrapper = createWrapper({ + windowViewType: 'book', + }); + expect(wrapper.instance().state.singleCanvasDialogOpen).toBe(false); + wrapper.find(MiradorMenuButton).simulate('click'); + expect(wrapper.instance().state.singleCanvasDialogOpen).toBe(true); + }); it('renders no export button if export or LocalStorageAdapter are not configured', () => { wrapper = createWrapper(); expect(wrapper.find(MiradorMenuButton).some({ 'aria-label': 'Export local annotations for visible items' })).toBe(false); diff --git a/src/CanvasListItem.js b/src/CanvasListItem.js index 24188a4e1cf63b28b54921d3816838ab97855ea0..f3a6526e50853a6efb6658c37f9a4716deca3232 100644 --- a/src/CanvasListItem.js +++ b/src/CanvasListItem.js @@ -86,6 +86,7 @@ class CanvasListItem extends Component { render() { const { children } = this.props; const { isHovering } = this.state; + const { windowViewType, toggleSingleCanvasDialogOpen } = this.context; return ( <div onMouseEnter={this.handleMouseHover} @@ -107,7 +108,11 @@ class CanvasListItem extends Component { right: 0, }} > - <ToggleButton aria-label="Edit" onClick={this.handleEdit} value="edit"> + <ToggleButton + aria-label="Edit" + onClick={windowViewType === 'single' ? this.handleEdit : toggleSingleCanvasDialogOpen} + value="edit" + > <EditIcon /> </ToggleButton> <ToggleButton aria-label="Delete" onClick={this.handleDelete} value="delete"> diff --git a/src/SingleCanvasDialog.js b/src/SingleCanvasDialog.js new file mode 100644 index 0000000000000000000000000000000000000000..75fe3924270974329801868a34e8ecf6ddd9346c --- /dev/null +++ b/src/SingleCanvasDialog.js @@ -0,0 +1,80 @@ +import React, { Component } from 'react'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Typography from '@material-ui/core/Typography'; +import PropTypes from 'prop-types'; + +/** + * Dialog to enforce single view for annotation creation / editing + */ +class SingleCanvasDialog extends Component { + /** */ + constructor(props) { + super(props); + this.confirm = this.confirm.bind(this); + } + + /** */ + confirm() { + const { + handleClose, + switchToSingleCanvasView, + } = this.props; + switchToSingleCanvasView(); + handleClose(); + } + + /** */ + render() { + const { + handleClose, + open, + } = this.props; + return ( + <Dialog + aria-labelledby="single-canvas-dialog-title" + fullWidth + maxWidth="sm" + onClose={handleClose} + onEscapeKeyDown={handleClose} + open={open} + > + <DialogTitle id="single-canvas-dialog-title" disableTypography> + <Typography variant="h2"> + Switch view type to single view? + </Typography> + </DialogTitle> + <DialogContent> + <DialogContentText variant="body1" color="inherit"> + Annotations can only be edited in single canvas view type. + Switch view type to single view now? + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={handleClose}> + Cancel + </Button> + <Button color="primary" onClick={this.confirm} variant="contained"> + Switch to single view + </Button> + </DialogActions> + </Dialog> + ); + } +} + +SingleCanvasDialog.propTypes = { + handleClose: PropTypes.func.isRequired, + open: PropTypes.bool, + switchToSingleCanvasView: PropTypes.func.isRequired, +}; + +SingleCanvasDialog.defaultProps = { + open: false, +}; + +export default SingleCanvasDialog; diff --git a/src/plugins/canvasAnnotationsPlugin.js b/src/plugins/canvasAnnotationsPlugin.js index 2b71eabb3d023741369c1bf987ef580aeb8918d7..9102709e50af23b914af0843aa28b7a5bd71998e 100644 --- a/src/plugins/canvasAnnotationsPlugin.js +++ b/src/plugins/canvasAnnotationsPlugin.js @@ -2,17 +2,37 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { getVisibleCanvases } from 'mirador/dist/es/src/state/selectors/canvases'; import * as actions from 'mirador/dist/es/src/state/actions'; +import { getWindowViewType } from 'mirador/dist/es/src/state/selectors'; import CanvasListItem from '../CanvasListItem'; import AnnotationActionsContext from '../AnnotationActionsContext'; +import SingleCanvasDialog from '../SingleCanvasDialog'; /** */ class CanvasAnnotationsWrapper extends Component { + /** */ + constructor(props) { + super(props); + this.state = { + singleCanvasDialogOpen: false, + }; + this.toggleSingleCanvasDialogOpen = this.toggleSingleCanvasDialogOpen.bind(this); + } + + /** */ + toggleSingleCanvasDialogOpen() { + const { singleCanvasDialogOpen } = this.state; + this.setState({ + singleCanvasDialogOpen: !singleCanvasDialogOpen, + }); + } + /** */ render() { const { - addCompanionWindow, canvases, config, receiveAnnotation, TargetComponent, - targetProps, annotationsOnCanvases, + addCompanionWindow, annotationsOnCanvases, canvases, config, receiveAnnotation, + switchToSingleCanvasView, TargetComponent, targetProps, windowViewType, } = this.props; + const { singleCanvasDialogOpen } = this.state; const props = { ...targetProps, listContainerComponent: CanvasListItem, @@ -26,12 +46,21 @@ class CanvasAnnotationsWrapper extends Component { config, receiveAnnotation, storageAdapter: config.annotation.adapter, + toggleSingleCanvasDialogOpen: this.toggleSingleCanvasDialogOpen, windowId: targetProps.windowId, + windowViewType, }} > <TargetComponent {...props} // eslint-disable-line react/jsx-props-no-spreading /> + {windowViewType !== 'single' && ( + <SingleCanvasDialog + handleClose={this.toggleSingleCanvasDialogOpen} + open={singleCanvasDialogOpen} + switchToSingleCanvasView={switchToSingleCanvasView} + /> + )} </AnnotationActionsContext.Provider> ); } @@ -49,11 +78,13 @@ CanvasAnnotationsWrapper.propTypes = { }), }).isRequired, receiveAnnotation: PropTypes.func.isRequired, + switchToSingleCanvasView: PropTypes.func.isRequired, TargetComponent: PropTypes.oneOfType([ PropTypes.func, PropTypes.node, ]).isRequired, targetProps: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + windowViewType: PropTypes.string.isRequired, }; CanvasAnnotationsWrapper.defaultProps = { @@ -65,6 +96,7 @@ CanvasAnnotationsWrapper.defaultProps = { function mapStateToProps(state, { targetProps: { windowId } }) { const canvases = getVisibleCanvases(state, { windowId }); const annotationsOnCanvases = {}; + canvases.forEach((canvas) => { const anno = state.annotations[canvas.id]; if (anno) { @@ -75,6 +107,7 @@ function mapStateToProps(state, { targetProps: { windowId } }) { annotationsOnCanvases, canvases, config: state.config, + windowViewType: getWindowViewType(state, { windowId }), }; } @@ -86,6 +119,9 @@ const mapDispatchToProps = (dispatch, props) => ({ receiveAnnotation: (targetId, id, annotation) => dispatch( actions.receiveAnnotation(targetId, id, annotation), ), + switchToSingleCanvasView: () => dispatch( + actions.setWindowViewType(props.targetProps.windowId, 'single'), + ), }); export default { diff --git a/src/plugins/miradorAnnotationPlugin.js b/src/plugins/miradorAnnotationPlugin.js index 56201337a95b04c71edc6d061b31e783a15b3091..e3a06fc3ddb7200ff602bc605ca96e28fa8c8f45 100644 --- a/src/plugins/miradorAnnotationPlugin.js +++ b/src/plugins/miradorAnnotationPlugin.js @@ -1,10 +1,12 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import * as actions from 'mirador/dist/es/src/state/actions'; +import { getWindowViewType } from 'mirador/dist/es/src/state/selectors'; import AddBoxIcon from '@material-ui/icons/AddBox'; import GetAppIcon from '@material-ui/icons/GetApp'; import { MiradorMenuButton } from 'mirador/dist/es/src/components/MiradorMenuButton'; import { getVisibleCanvases } from 'mirador/dist/es/src/state/selectors/canvases'; +import SingleCanvasDialog from '../SingleCanvasDialog'; import AnnotationExportDialog from '../AnnotationExportDialog'; import LocalStorageAdapter from '../LocalStorageAdapter'; @@ -15,9 +17,11 @@ class MiradorAnnotation extends Component { super(props); this.state = { annotationExportDialogOpen: false, + singleCanvasDialogOpen: false, }; this.openCreateAnnotationCompanionWindow = this.openCreateAnnotationCompanionWindow.bind(this); this.toggleCanvasExportDialog = this.toggleCanvasExportDialog.bind(this); + this.toggleSingleCanvasDialogOpen = this.toggleSingleCanvasDialogOpen.bind(this); } /** */ @@ -31,6 +35,14 @@ class MiradorAnnotation extends Component { }); } + /** */ + toggleSingleCanvasDialogOpen() { + const { singleCanvasDialogOpen } = this.state; + this.setState({ + singleCanvasDialogOpen: !singleCanvasDialogOpen, + }); + } + /** */ toggleCanvasExportDialog(e) { const { annotationExportDialogOpen } = this.state; @@ -43,9 +55,14 @@ class MiradorAnnotation extends Component { /** */ render() { const { - canvases, config, TargetComponent, targetProps, + canvases, + config, + switchToSingleCanvasView, + TargetComponent, + targetProps, + windowViewType, } = this.props; - const { annotationExportDialogOpen } = this.state; + const { annotationExportDialogOpen, singleCanvasDialogOpen } = this.state; const storageAdapter = config.annotation && config.annotation.adapter('poke'); const offerExportDialog = config.annotation && storageAdapter instanceof LocalStorageAdapter && config.annotation.exportLocalStorageAnnotations; @@ -56,11 +73,18 @@ class MiradorAnnotation extends Component { /> <MiradorMenuButton aria-label="Create new annotation" - onClick={this.openCreateAnnotationCompanionWindow} + onClick={windowViewType === 'single' ? this.openCreateAnnotationCompanionWindow : this.toggleSingleCanvasDialogOpen} size="small" > <AddBoxIcon /> </MiradorMenuButton> + { singleCanvasDialogOpen && ( + <SingleCanvasDialog + open={singleCanvasDialogOpen} + handleClose={this.toggleSingleCanvasDialogOpen} + switchToSingleCanvasView={switchToSingleCanvasView} + /> + )} { offerExportDialog && ( <MiradorMenuButton aria-label="Export local annotations for visible items" @@ -94,11 +118,13 @@ MiradorAnnotation.propTypes = { exportLocalStorageAnnotations: PropTypes.bool, }), }).isRequired, + switchToSingleCanvasView: PropTypes.func.isRequired, TargetComponent: PropTypes.oneOfType([ PropTypes.func, PropTypes.node, ]).isRequired, targetProps: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + windowViewType: PropTypes.string.isRequired, }; /** */ @@ -106,12 +132,16 @@ const mapDispatchToProps = (dispatch, props) => ({ addCompanionWindow: (content, additionalProps) => dispatch( actions.addCompanionWindow(props.targetProps.windowId, { content, ...additionalProps }), ), + switchToSingleCanvasView: () => dispatch( + actions.setWindowViewType(props.targetProps.windowId, 'single'), + ), }); /** */ const mapStateToProps = (state, { targetProps: { windowId } }) => ({ canvases: getVisibleCanvases(state, { windowId }), config: state.config, + windowViewType: getWindowViewType(state, { windowId }), }); export default {