Skip to content

Commit

Permalink
[CHANGE] Add API to customize annotation tooltips (#650) (#651)
Browse files Browse the repository at this point in the history
  • Loading branch information
bollain authored Jun 5, 2020
1 parent b0848ed commit 0b8b38f
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 13 deletions.
2 changes: 2 additions & 0 deletions src/apis/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ import selectThumbnailPages from './selectThumbnailPages';
import unselectThumbnailPages from './unselectThumbnailPages';
import setSearchResults from './setSearchResults';
import setActiveResult from './setActiveResult';
import setAnnotationContentOverlayHandler from './setAnnotationContentOverlayHandler';

export default store => {
window.readerControl = {
Expand Down Expand Up @@ -217,6 +218,7 @@ export default store => {
getSelectedThumbnailPageNumbers: getSelectedThumbnailPageNumbers(store),
selectThumbnailPages: selectThumbnailPages(store),
unselectThumbnailPages: unselectThumbnailPages(store),
setAnnotationContentOverlayHandler: setAnnotationContentOverlayHandler(store),

// undocumented and deprecated, to be removed in 7.0
closeElement: closeElement(store),
Expand Down
22 changes: 22 additions & 0 deletions src/apis/setAnnotationContentOverlayHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import action from 'actions';

/**
* Adds a custom overlay to annotations on mouseHover, overriding the existing overlay.
* @method WebViewerInstance#setAnnotationContentOverlayHandler
* @param {function} customOverlayHandler a function that takes an annotation and returns a DOM Element, which is rendered as a tooltip when hovering over the annotation
* * @example
WebViewer(...)
.then(function(instance) {
instance.setAnnotationContentOverlayHandler(annotation => {
const div = document.createElement('div');
div.appendChild(document.createTextNode(`Created by: ${annotation.Author}`));
div.appendChild(document.createElement('br'));
div.appendChild(document.createTextNode(`Created on ${annotation.DateCreated}`));
return div;
});
});
*/

export default store => annotationContentOverlayHandler => {
store.dispatch(action.setAnnotationContentOverlayHandler(annotationContentOverlayHandler));
};
67 changes: 55 additions & 12 deletions src/components/AnnotationContentOverlay/AnnotationContentOverlay.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable react/prop-types */
import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
Expand All @@ -8,6 +9,8 @@ import selectors from 'selectors';

import './AnnotationContentOverlay.scss';

import CustomElement from '../CustomElement';

const MAX_CHARACTERS = 100;

const AnnotationContentOverlay = () => {
Expand All @@ -21,6 +24,13 @@ const AnnotationContentOverlay = () => {
top: 0,
});

// Clients have the option to customize how the tooltip is rendered
// by passing a handler
const customHandler = useSelector(state =>
selectors.getAnnotationContentOverlayHandler(state),
);
const isUsingCustomHandler = customHandler !== null;

useEffect(() => {
const onMouseHover = e => {
const viewElement = core.getViewerElement();
Expand Down Expand Up @@ -55,25 +65,58 @@ const AnnotationContentOverlay = () => {
const contents = annotation?.getContents();
const numberOfReplies = annotation?.getReplies().length;

return isDisabled || isMobileDevice || !contents ? null : (
const OverlayWrapper = props => (
<div
className="Overlay AnnotationContentOverlay"
data-element="annotationContentOverlay"
style={{ ...overlayPosition }}
>
<div className="author">{core.getDisplayAuthor(annotation)}</div>
<div className="contents">
{contents.length > MAX_CHARACTERS
? `${contents.slice(0, MAX_CHARACTERS)}...`
: contents}
</div>
{numberOfReplies > 0 && (
<div className="replies">
{t('message.annotationReplyCount', { count: numberOfReplies })}
</div>
)}
{props.children}
</div>
);

const CustomOverlay = () => {
if (annotation) {
return (
<OverlayWrapper>
<CustomElement render={() => customHandler(annotation)} />
</OverlayWrapper>
);
} else {
return null;
}
};

const DefaultOverlay = () => {
if (contents) {
return (
<OverlayWrapper>
<div className="author">{core.getDisplayAuthor(annotation)}</div>
<div className="contents">
{contents.length > MAX_CHARACTERS
? `${contents.slice(0, MAX_CHARACTERS)}...`
: contents}
</div>
{numberOfReplies > 0 && (
<div className="replies">
{t('message.annotationReplyCount', { count: numberOfReplies })}
</div>
)}
</OverlayWrapper>
);
} else {
return null;
}
};

if (isDisabled || isMobileDevice) {
return null;
} else if (isUsingCustomHandler) {
return <CustomOverlay />;
} else {
return <DefaultOverlay />;
}

};

export default AnnotationContentOverlay;
6 changes: 5 additions & 1 deletion src/redux/actions/exposedActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,8 @@ export const setActiveResult = (activeResult, index) => ({
export const setActiveResultIndex = index => ({
type: 'SET_ACTIVE_RESULT_INDEX',
payload: { index },
});
});
export const setAnnotationContentOverlayHandler = annotationContentOverlayHandler => ({
type: 'SET_ANNOTATION_CONTENT_OVERLAY_HANDLER',
payload: { annotationContentOverlayHandler }
});
3 changes: 3 additions & 0 deletions src/redux/initialState.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ export default {
userData: [],
customMeasurementOverlay: [],
noteTransformFunction: null,
savedSignatures: [],
selectedSignatureIndex: 0,
annotationContentOverlayHandler: null,
},
search: {
listeners: [],
Expand Down
2 changes: 2 additions & 0 deletions src/redux/reducers/viewerReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ export default initialState => (state = initialState, action) => {
return { ...state, customElementOverrides: { ...state.customElementOverrides, [payload.dataElement]: payload.overrides } };
case 'SET_NOTE_TRANSFORM_FUNCTION':
return { ...state, noteTransformFunction: payload.noteTransformFunction };
case 'SET_ANNOTATION_CONTENT_OVERLAY_HANDLER':
return { ...state, annotationContentOverlayHandler: payload.annotationContentOverlayHandler };
default:
return state;
}
Expand Down
2 changes: 2 additions & 0 deletions src/redux/selectors/exposedSelectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ export const getAllowPageNavigation = state => state.viewer.allowPageNavigation;

export const getCustomMeasurementOverlay = state => state.viewer.customMeasurementOverlay;

export const getAnnotationContentOverlayHandler = state => state.viewer.annotationContentOverlayHandler;

// warning message
export const getWarningMessage = state => state.viewer.warning?.message || '';

Expand Down

0 comments on commit 0b8b38f

Please sign in to comment.