diff --git a/demo/src/index.js b/demo/src/index.js index 3f7793bdd0274e9d20e8096a9784c9ee96fa847c..d501089b0f113d226c66c6bbedfa278a2175b976 100644 --- a/demo/src/index.js +++ b/demo/src/index.js @@ -9,6 +9,7 @@ const config = { annotation: { adapter: (canvasId) => new LocalStorageAdapter(`localStorage://?canvasId=${canvasId}`), // adapter: (canvasId) => new AnnototAdapter(canvasId, endpointUrl), + // downloadCanvasAnnotations: true, }, id: 'demo', window: { diff --git a/src/AnnotationDownloadDialog.js b/src/AnnotationDownloadDialog.js new file mode 100644 index 0000000000000000000000000000000000000000..dc1241e188c9a14f9360a7f8040684c077ccda02 --- /dev/null +++ b/src/AnnotationDownloadDialog.js @@ -0,0 +1,110 @@ +import React, { Component } from 'react'; +import Dialog from '@material-ui/core/Dialog'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import GetAppIcon from '@material-ui/icons/GetApp'; +import List from '@material-ui/core/List' +import ListItem from '@material-ui/core/ListItem' +import ListItemIcon from '@material-ui/core/ListItemIcon' +import ListItemText from '@material-ui/core/ListItemText' +import Typography from '@material-ui/core/Typography'; +import PropTypes, { bool } from 'prop-types'; + +export class AnnotationDownloadDialog extends Component { + constructor(props) { + super(props); + this.state = { + downloadLinks: [], + } + } + + componentDidUpdate(prevProps) { + const { canvases, config, open } = this.props; + const { open: prevOpen } = prevProps || {}; + if (prevOpen !== open && open) { + const reducer = async (acc, canvas) => { + const store = config.annotation.adapter(canvas.id); + const _acc = await acc; + const content = await store.all(); + if (content + ) { + const label = canvas.__jsonld.label || canvas.id; + const data = new Blob([JSON.stringify(content)], { type: 'application/json' }) + const url = window.URL.createObjectURL(data); + return [..._acc, { + id: content.id || content['@id'], + canvasId: canvas.id, + label, + url, + }] + } + return _acc; + } + if (canvases && canvases.length > 0) { + canvases.reduce(reducer, []).then(downloadLinks => { + this.setState({ downloadLinks }) + }); + } else { + this.setState({ downloadLinks: [] }); + } + } + if (prevOpen !== open && !open) { + this.setState({ downloadLinks: [] }); + } + } + + render() { + const { canvases, config, handleClose, open } = this.props; + return ( + <Dialog + aria-labelledby="annotation-download-dialog-title" + id="annotation-download-dialog" + onClose={handleClose} + onEscapeKeyDown={handleClose} + open={open} + > + <DialogTitle id="annotation-download-dialog-title" disableTypography> + <Typography variant="h2">Download Annotations</Typography> + </DialogTitle> + <DialogContent> + { this.state.downloadLinks === undefined || this.state.downloadLinks.length === 0 ? ( + <Typography variant="body1">No annotations stored yet.</Typography> + ) : ( + <List> + {this.state.downloadLinks.map(dl => ( + <ListItem button + component="a" + key={dl.canvasId} + aria-label={`Download annotations for ${dl.label}`} + startIcon={<GetAppIcon/>} + href={dl.url} + download={`annotations-${dl.id}.json`} + > + <ListItemIcon> + <GetAppIcon/> + </ListItemIcon> + <ListItemText> + Download annotations for {dl.label} + </ListItemText> + </ListItem> + ))} + </List> + )} + </DialogContent> + </Dialog> + ) + } +} + +AnnotationDownloadDialog.propTypes = { + canvases: PropTypes.arrayOf( + PropTypes.shape({ id: PropTypes.string, index: PropTypes.number }), + ), + config: PropTypes.shape({ + annotation: PropTypes.shape({ + adapter: PropTypes.func, + }), + }), + handleClose: PropTypes.func.isRequired, + open: bool.isRequired, +} diff --git a/src/plugins/miradorAnnotationPlugin.js b/src/plugins/miradorAnnotationPlugin.js index 9ebeec69b9daceea4768575206e6f5ed08ede9f0..c4a60383aa16cf6b289135e417279f1748b6ab43 100644 --- a/src/plugins/miradorAnnotationPlugin.js +++ b/src/plugins/miradorAnnotationPlugin.js @@ -2,14 +2,21 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import * as actions from 'mirador/dist/es/src/state/actions'; 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 { AnnotationDownloadDialog } from '../AnnotationDownloadDialog'; /** */ class MiradorAnnotation extends Component { /** */ constructor(props) { super(props); + this.state = { + annotationDownloadDialogOpen: false, + }; this.openCreateAnnotationCompanionWindow = this.openCreateAnnotationCompanionWindow.bind(this); + this.toggleCanvasDownloadDialog = this.toggleCanvasDownloadDialog.bind(this); } /** */ @@ -23,9 +30,17 @@ class MiradorAnnotation extends Component { }); } + toggleCanvasDownloadDialog(e) { + const newState = { + annotationDownloadDialogOpen: !this.state.annotationDownloadDialogOpen, + } + this.setState(newState); + } + /** */ render() { - const { TargetComponent, targetProps } = this.props; + const { canvases, config, TargetComponent, targetProps } = this.props; + const showAnnotationDownloadDialog = config.annotation && config.annotation.downloadCanvasAnnotations; return ( <div> <TargetComponent @@ -38,6 +53,23 @@ class MiradorAnnotation extends Component { > <AddBoxIcon /> </MiradorMenuButton> + { showAnnotationDownloadDialog && ( + <MiradorMenuButton + aria-label="Download annotation page for canvas" + onClick={this.toggleCanvasDownloadDialog} + size="small" + > + <GetAppIcon /> + </MiradorMenuButton> + )} + { showAnnotationDownloadDialog && ( + <AnnotationDownloadDialog + canvases={canvases} + config={config} + handleClose={this.toggleCanvasDownloadDialog} + open={this.state.annotationDownloadDialogOpen} + /> + )} </div> ); } @@ -45,6 +77,15 @@ class MiradorAnnotation extends Component { MiradorAnnotation.propTypes = { addCompanionWindow: PropTypes.func.isRequired, + canvases: PropTypes.arrayOf( + PropTypes.shape({ id: PropTypes.string, index: PropTypes.number }), + ), + config: PropTypes.shape({ + annotation: PropTypes.shape({ + adapter: PropTypes.func, + downloadCanvasAnnotations: PropTypes.bool, + }), + }), TargetComponent: PropTypes.oneOfType([ PropTypes.func, PropTypes.node, @@ -59,9 +100,17 @@ const mapDispatchToProps = (dispatch, props) => ({ ), }); +const mapStateToProps = function mapStateToProps(state, { targetProps: { windowId } }) { + return { + canvases: getVisibleCanvases(state, { windowId }), + config: state.config, + }; +} + export default { component: MiradorAnnotation, mapDispatchToProps, + mapStateToProps, mode: 'wrap', target: 'AnnotationSettings', };