diff --git a/src/apis/index.js b/src/apis/index.js index 940cc8d86..442ca049f 100644 --- a/src/apis/index.js +++ b/src/apis/index.js @@ -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 = { @@ -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), diff --git a/src/apis/setAnnotationContentOverlayHandler.js b/src/apis/setAnnotationContentOverlayHandler.js new file mode 100644 index 000000000..14edfba8c --- /dev/null +++ b/src/apis/setAnnotationContentOverlayHandler.js @@ -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)); +}; \ No newline at end of file diff --git a/src/components/AnnotationContentOverlay/AnnotationContentOverlay.js b/src/components/AnnotationContentOverlay/AnnotationContentOverlay.js index 38a9bc814..c6db1b813 100644 --- a/src/components/AnnotationContentOverlay/AnnotationContentOverlay.js +++ b/src/components/AnnotationContentOverlay/AnnotationContentOverlay.js @@ -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'; @@ -8,6 +9,8 @@ import selectors from 'selectors'; import './AnnotationContentOverlay.scss'; +import CustomElement from '../CustomElement'; + const MAX_CHARACTERS = 100; const AnnotationContentOverlay = () => { @@ -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(); @@ -55,25 +65,58 @@ const AnnotationContentOverlay = () => { const contents = annotation?.getContents(); const numberOfReplies = annotation?.getReplies().length; - return isDisabled || isMobileDevice || !contents ? null : ( + const OverlayWrapper = props => (
-
{core.getDisplayAuthor(annotation)}
-
- {contents.length > MAX_CHARACTERS - ? `${contents.slice(0, MAX_CHARACTERS)}...` - : contents} -
- {numberOfReplies > 0 && ( -
- {t('message.annotationReplyCount', { count: numberOfReplies })} -
- )} + {props.children}
); + + const CustomOverlay = () => { + if (annotation) { + return ( + + customHandler(annotation)} /> + + ); + } else { + return null; + } + }; + + const DefaultOverlay = () => { + if (contents) { + return ( + +
{core.getDisplayAuthor(annotation)}
+
+ {contents.length > MAX_CHARACTERS + ? `${contents.slice(0, MAX_CHARACTERS)}...` + : contents} +
+ {numberOfReplies > 0 && ( +
+ {t('message.annotationReplyCount', { count: numberOfReplies })} +
+ )} +
+ ); + } else { + return null; + } + }; + + if (isDisabled || isMobileDevice) { + return null; + } else if (isUsingCustomHandler) { + return ; + } else { + return ; + } + }; export default AnnotationContentOverlay; diff --git a/src/redux/actions/exposedActions.js b/src/redux/actions/exposedActions.js index ca892f34c..7cc8dd09c 100644 --- a/src/redux/actions/exposedActions.js +++ b/src/redux/actions/exposedActions.js @@ -245,4 +245,8 @@ export const setActiveResult = (activeResult, index) => ({ export const setActiveResultIndex = index => ({ type: 'SET_ACTIVE_RESULT_INDEX', payload: { index }, -}); \ No newline at end of file +}); +export const setAnnotationContentOverlayHandler = annotationContentOverlayHandler => ({ + type: 'SET_ANNOTATION_CONTENT_OVERLAY_HANDLER', + payload: { annotationContentOverlayHandler } +}); diff --git a/src/redux/initialState.js b/src/redux/initialState.js index cf4b5287b..2cefdfe1f 100644 --- a/src/redux/initialState.js +++ b/src/redux/initialState.js @@ -209,6 +209,9 @@ export default { userData: [], customMeasurementOverlay: [], noteTransformFunction: null, + savedSignatures: [], + selectedSignatureIndex: 0, + annotationContentOverlayHandler: null, }, search: { listeners: [], diff --git a/src/redux/reducers/viewerReducer.js b/src/redux/reducers/viewerReducer.js index 8428ee77f..39716afda 100644 --- a/src/redux/reducers/viewerReducer.js +++ b/src/redux/reducers/viewerReducer.js @@ -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; } diff --git a/src/redux/selectors/exposedSelectors.js b/src/redux/selectors/exposedSelectors.js index 1a5be3f8b..bdd9e6cb6 100644 --- a/src/redux/selectors/exposedSelectors.js +++ b/src/redux/selectors/exposedSelectors.js @@ -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 || '';