Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add annot image display through a dialog and annot list in sidebar #3435

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions src/components/AnnotationImageDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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 PropTypes from 'prop-types';
import { DialogActions, Typography } from '@material-ui/core';
import Button from '@material-ui/core/Button';

/**
*/
export class AnnotationImageDialog extends Component {
/**
* render
* @return
*/
constructor() {
super();

this.closeDialog = this.closeDialog.bind(this);
this.getAnnotation = this.getAnnotation.bind(this);
this.state = { annotation: {}, isModalOpen: false };
}

/** */
componentDidUpdate(prevProps) {
const {
openedAnnotationImageId,
} = this.props;

if ((prevProps.openedAnnotationImageId !== openedAnnotationImageId)
&& openedAnnotationImageId) {
this.getAnnotation();
}
}

/** */
getAnnotation() {
const {
annotations, selectedAnnotationId,
} = this.props;

let annotation = {};
for (let i = 0; i < annotations.length; i += 1) {
annotation = annotations && annotations[i] && annotations[i].resources
&& annotations[i].resources.find(anno => anno.id === selectedAnnotationId);
if (annotation) {
this.setState({ annotation, isModalOpen: true });
break;
}
}
}

/** */
closeDialog() {
const {
toggleAnnotationImage, windowId,
} = this.props;

toggleAnnotationImage(windowId);
this.setState({ annotation: {}, isModalOpen: false });
}

/** */
render() {
const {
t,
} = this.props;
const {
isModalOpen, annotation,
} = this.state;

return isModalOpen ? (
<Dialog
aria-labelledby="annotation-dialog-title"
id="annotation-dialog"
open={isModalOpen}
>
<DialogTitle id="annotation-dialog-title" disableTypography>
<Typography variant="h2">Annotation Image</Typography>
</DialogTitle>
<DialogContent>
<div style={{ overflowX: 'scroll', overflowY: 'scroll' }}>
<img src={annotation.body[0].url} alt={t('annotationImage')} width={annotation.body[0].w} height={annotation.body[0].h} style={({ marginRight: '30' })} />
</div>
</DialogContent>
<DialogActions>
<Button onClick={this.closeDialog}>
{t('close')}
</Button>
</DialogActions>
</Dialog>
) : null;
}
}

AnnotationImageDialog.propTypes = {
annotations: PropTypes.arrayOf(PropTypes.object),
openedAnnotationImageId: PropTypes.string,
selectedAnnotationId: PropTypes.string,
t: PropTypes.func.isRequired,
toggleAnnotationImage: PropTypes.func.isRequired,
windowId: PropTypes.string.isRequired,
};

AnnotationImageDialog.defaultProps = {
annotations: [],
openedAnnotationImageId: undefined,
selectedAnnotationId: undefined,
};
47 changes: 38 additions & 9 deletions src/components/AnnotationSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import VisibilityIcon from '@material-ui/icons/VisibilitySharp';
import VisibilityOffIcon from '@material-ui/icons/VisibilityOffSharp';
import ImageIcon from '@material-ui/icons/Image';
import MiradorMenuButton from '../containers/MiradorMenuButton';

/**
Expand All @@ -14,26 +15,54 @@ export class AnnotationSettings extends Component {
*/
render() {
const {
displayAll, displayAllDisabled, t, toggleAnnotationDisplay,
annotations, displayAll, displayAllDisabled, toggleAnnotationImage,
selectedAnnotationId, t, toggleAnnotationDisplay, windowId,
} = this.props;

let annotation;
for (let i = 0; i < annotations.length; i += 1) {
annotation = annotations[i].resources.find(anno => anno.id === selectedAnnotationId);
if (annotation) {
break;
}
}

return (
<MiradorMenuButton
aria-label={t(displayAll ? 'displayNoAnnotations' : 'highlightAllAnnotations')}
onClick={toggleAnnotationDisplay}
disabled={displayAllDisabled}
size="small"
>
{ displayAll ? <VisibilityIcon /> : <VisibilityOffIcon /> }
</MiradorMenuButton>
<>
<MiradorMenuButton
aria-label={t(displayAll ? 'displayNoAnnotations' : 'highlightAllAnnotations')}
onClick={toggleAnnotationDisplay}
disabled={displayAllDisabled}
size="small"
>
{ displayAll ? <VisibilityIcon /> : <VisibilityOffIcon /> }
</MiradorMenuButton>
{ annotation && annotation.body && (annotation.body[0].type === 'ImageBody' || annotation.body[0].type.includes('image')) && (
<MiradorMenuButton
aria-label={t('displayAnnotationImage')}
onClick={() => { toggleAnnotationImage(windowId, annotation.id); }}
size="small"
>
<ImageIcon />
</MiradorMenuButton>
)}
</>
);
}
}

AnnotationSettings.propTypes = {
annotations: PropTypes.arrayOf(PropTypes.object),
displayAll: PropTypes.bool.isRequired,
displayAllDisabled: PropTypes.bool.isRequired,
selectedAnnotationId: PropTypes.string,
t: PropTypes.func.isRequired,
toggleAnnotationDisplay: PropTypes.func.isRequired,
toggleAnnotationImage: PropTypes.func.isRequired,
windowId: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
};

AnnotationSettings.defaultProps = {
annotations: [],
selectedAnnotationId: undefined,
};
89 changes: 56 additions & 33 deletions src/components/CanvasAnnotations.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ export class CanvasAnnotations extends Component {
hoverAnnotation(windowId, []);
}

/** */
getFullAnnotation(id) {
const {
fullAnnotations,
} = this.props;
let annotation;
for (let i = 0; i < fullAnnotations.length; i += 1) {
annotation = fullAnnotations[i].resources.find(anno => anno.id === id);
if (annotation) {
break;
}
}
return annotation;
}

/**
* Returns the rendered component
*/
Expand All @@ -69,40 +84,46 @@ export class CanvasAnnotations extends Component {
</Typography>
<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}
>
<ListItemText primaryTypographyProps={{ variant: 'body2' }}>
<SanitizedHtml
ruleSet={htmlSanitizationRuleSet}
htmlString={annotation.content}
/>
<div>
annotations.map(annotation => {
const fullAnnotation = this.getFullAnnotation(annotation.id);
const width = (fullAnnotation.body && fullAnnotation.body[0].imgWidth < 60) ? (fullAnnotation.body[0].imgWidth).toString() : '60';
const height = (fullAnnotation.body && fullAnnotation.body[0].imageHeight < 60) ? (fullAnnotation.body[0].imageHeight).toString() : '60';
return (
<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}
>
{fullAnnotation.body && fullAnnotation.body[0] && fullAnnotation.body[0] && fullAnnotation.body[0].type === 'ImageBody' && fullAnnotation.body[0].type.toLowerCase().includes('image') && fullAnnotation.body[0].url && (<img src={fullAnnotation.body[0].url} alt="Single Annotation" width={width} height={height} style={{ marginRight: '5%', minWidth: { width } }} />)}
<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>
);
})
}
</MenuList>
</>
Expand All @@ -119,6 +140,7 @@ CanvasAnnotations.propTypes = {
),
classes: PropTypes.objectOf(PropTypes.string),
deselectAnnotation: PropTypes.func.isRequired,
fullAnnotations: PropTypes.arrayOf(PropTypes.object),
hoverAnnotation: PropTypes.func.isRequired,
hoveredAnnotationIds: PropTypes.arrayOf(PropTypes.string),
htmlSanitizationRuleSet: PropTypes.string,
Expand All @@ -134,6 +156,7 @@ CanvasAnnotations.propTypes = {
CanvasAnnotations.defaultProps = {
annotations: [],
classes: {},
fullAnnotations: [],
hoveredAnnotationIds: [],
htmlSanitizationRuleSet: 'iiif',
listContainerComponent: 'li',
Expand Down
6 changes: 5 additions & 1 deletion src/components/Window.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import cn from 'classnames';
import Paper from '@material-ui/core/Paper';
import { MosaicWindowContext } from 'react-mosaic-component/lib/contextTypes';
import AnnotationImageDialog from '../containers/AnnotationImageDialog';
import ns from '../config/css-ns';
import WindowTopBar from '../containers/WindowTopBar';
import PrimaryWindow from '../containers/PrimaryWindow';
Expand Down Expand Up @@ -61,7 +62,7 @@ export class Window extends Component {
*/
render() {
const {
focusWindow, label, isFetching, maximized, sideBarOpen,
focusWindow, label, isFetching, maximized, openedAnnotationImageId, sideBarOpen,
view, windowId, classes, t,
manifestError,
} = this.props;
Expand Down Expand Up @@ -110,6 +111,7 @@ export class Window extends Component {
</div>
</div>
<CompanionArea windowId={windowId} position="far-bottom" />
<AnnotationImageDialog windowId={windowId} />
<PluginHook {...this.props} />
</Paper>
);
Expand All @@ -125,6 +127,7 @@ Window.propTypes = {
label: PropTypes.string,
manifestError: PropTypes.string,
maximized: PropTypes.bool,
openedAnnotationImageId: PropTypes.string,
sideBarOpen: PropTypes.bool,
t: PropTypes.func.isRequired,
view: PropTypes.string,
Expand All @@ -140,6 +143,7 @@ Window.defaultProps = {
label: null,
manifestError: null,
maximized: false,
openedAnnotationImageId: undefined,
sideBarOpen: false,
view: undefined,
windowDraggable: null,
Expand Down
55 changes: 55 additions & 0 deletions src/containers/AnnotationImageDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
compose,
} from 'redux';
import {
withTranslation,
} from 'react-i18next';
import {
connect,
} from 'react-redux';
import {
withPlugins,
} from '../extend/withPlugins';
import {
AnnotationImageDialog,
} from '../components/AnnotationImageDialog';
import * as actions from '../state/actions';
import {
getPresentAnnotationsOnSelectedCanvases,
getSelectedAnnotationId,
getWindow,
} from '../state/selectors';

/**
* Mapping redux state to component props using connect
*/
const mapStateToProps = (state, {
windowId,
}) => ({
annotations: getPresentAnnotationsOnSelectedCanvases(state, {
windowId,
}),
openedAnnotationImageId: getWindow(state, {
windowId,
}).openedAnnotationImageId,
selectedAnnotationId: getSelectedAnnotationId(state, {
windowId,
}),
});

/**
* Mapping redux action dispatches to component props using connect
*/
const mapDispatchToProps = (dispatch, { window }) => ({
toggleAnnotationImage: (windowId, annotationId) => {
dispatch(actions.toggleAnnotationImage(windowId, annotationId));
},
});

const enhance = compose(
withTranslation(),
connect(mapStateToProps, mapDispatchToProps),
withPlugins('AnnotationImageDialog'),
);

export default enhance(AnnotationImageDialog);
Loading