Skip to content

Commit 0b8b38f

Browse files
authored
[CHANGE] Add API to customize annotation tooltips (#650) (#651)
1 parent b0848ed commit 0b8b38f

File tree

7 files changed

+91
-13
lines changed

7 files changed

+91
-13
lines changed

src/apis/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ import selectThumbnailPages from './selectThumbnailPages';
136136
import unselectThumbnailPages from './unselectThumbnailPages';
137137
import setSearchResults from './setSearchResults';
138138
import setActiveResult from './setActiveResult';
139+
import setAnnotationContentOverlayHandler from './setAnnotationContentOverlayHandler';
139140

140141
export default store => {
141142
window.readerControl = {
@@ -217,6 +218,7 @@ export default store => {
217218
getSelectedThumbnailPageNumbers: getSelectedThumbnailPageNumbers(store),
218219
selectThumbnailPages: selectThumbnailPages(store),
219220
unselectThumbnailPages: unselectThumbnailPages(store),
221+
setAnnotationContentOverlayHandler: setAnnotationContentOverlayHandler(store),
220222

221223
// undocumented and deprecated, to be removed in 7.0
222224
closeElement: closeElement(store),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import action from 'actions';
2+
3+
/**
4+
* Adds a custom overlay to annotations on mouseHover, overriding the existing overlay.
5+
* @method WebViewerInstance#setAnnotationContentOverlayHandler
6+
* @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
7+
* * @example
8+
WebViewer(...)
9+
.then(function(instance) {
10+
instance.setAnnotationContentOverlayHandler(annotation => {
11+
const div = document.createElement('div');
12+
div.appendChild(document.createTextNode(`Created by: ${annotation.Author}`));
13+
div.appendChild(document.createElement('br'));
14+
div.appendChild(document.createTextNode(`Created on ${annotation.DateCreated}`));
15+
return div;
16+
});
17+
});
18+
*/
19+
20+
export default store => annotationContentOverlayHandler => {
21+
store.dispatch(action.setAnnotationContentOverlayHandler(annotationContentOverlayHandler));
22+
};

src/components/AnnotationContentOverlay/AnnotationContentOverlay.js

+55-12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable react/prop-types */
12
import React, { useState, useEffect } from 'react';
23
import { useSelector } from 'react-redux';
34
import { useTranslation } from 'react-i18next';
@@ -8,6 +9,8 @@ import selectors from 'selectors';
89

910
import './AnnotationContentOverlay.scss';
1011

12+
import CustomElement from '../CustomElement';
13+
1114
const MAX_CHARACTERS = 100;
1215

1316
const AnnotationContentOverlay = () => {
@@ -21,6 +24,13 @@ const AnnotationContentOverlay = () => {
2124
top: 0,
2225
});
2326

27+
// Clients have the option to customize how the tooltip is rendered
28+
// by passing a handler
29+
const customHandler = useSelector(state =>
30+
selectors.getAnnotationContentOverlayHandler(state),
31+
);
32+
const isUsingCustomHandler = customHandler !== null;
33+
2434
useEffect(() => {
2535
const onMouseHover = e => {
2636
const viewElement = core.getViewerElement();
@@ -55,25 +65,58 @@ const AnnotationContentOverlay = () => {
5565
const contents = annotation?.getContents();
5666
const numberOfReplies = annotation?.getReplies().length;
5767

58-
return isDisabled || isMobileDevice || !contents ? null : (
68+
const OverlayWrapper = props => (
5969
<div
6070
className="Overlay AnnotationContentOverlay"
6171
data-element="annotationContentOverlay"
6272
style={{ ...overlayPosition }}
6373
>
64-
<div className="author">{core.getDisplayAuthor(annotation)}</div>
65-
<div className="contents">
66-
{contents.length > MAX_CHARACTERS
67-
? `${contents.slice(0, MAX_CHARACTERS)}...`
68-
: contents}
69-
</div>
70-
{numberOfReplies > 0 && (
71-
<div className="replies">
72-
{t('message.annotationReplyCount', { count: numberOfReplies })}
73-
</div>
74-
)}
74+
{props.children}
7575
</div>
7676
);
77+
78+
const CustomOverlay = () => {
79+
if (annotation) {
80+
return (
81+
<OverlayWrapper>
82+
<CustomElement render={() => customHandler(annotation)} />
83+
</OverlayWrapper>
84+
);
85+
} else {
86+
return null;
87+
}
88+
};
89+
90+
const DefaultOverlay = () => {
91+
if (contents) {
92+
return (
93+
<OverlayWrapper>
94+
<div className="author">{core.getDisplayAuthor(annotation)}</div>
95+
<div className="contents">
96+
{contents.length > MAX_CHARACTERS
97+
? `${contents.slice(0, MAX_CHARACTERS)}...`
98+
: contents}
99+
</div>
100+
{numberOfReplies > 0 && (
101+
<div className="replies">
102+
{t('message.annotationReplyCount', { count: numberOfReplies })}
103+
</div>
104+
)}
105+
</OverlayWrapper>
106+
);
107+
} else {
108+
return null;
109+
}
110+
};
111+
112+
if (isDisabled || isMobileDevice) {
113+
return null;
114+
} else if (isUsingCustomHandler) {
115+
return <CustomOverlay />;
116+
} else {
117+
return <DefaultOverlay />;
118+
}
119+
77120
};
78121

79122
export default AnnotationContentOverlay;

src/redux/actions/exposedActions.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,8 @@ export const setActiveResult = (activeResult, index) => ({
245245
export const setActiveResultIndex = index => ({
246246
type: 'SET_ACTIVE_RESULT_INDEX',
247247
payload: { index },
248-
});
248+
});
249+
export const setAnnotationContentOverlayHandler = annotationContentOverlayHandler => ({
250+
type: 'SET_ANNOTATION_CONTENT_OVERLAY_HANDLER',
251+
payload: { annotationContentOverlayHandler }
252+
});

src/redux/initialState.js

+3
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ export default {
209209
userData: [],
210210
customMeasurementOverlay: [],
211211
noteTransformFunction: null,
212+
savedSignatures: [],
213+
selectedSignatureIndex: 0,
214+
annotationContentOverlayHandler: null,
212215
},
213216
search: {
214217
listeners: [],

src/redux/reducers/viewerReducer.js

+2
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ export default initialState => (state = initialState, action) => {
232232
return { ...state, customElementOverrides: { ...state.customElementOverrides, [payload.dataElement]: payload.overrides } };
233233
case 'SET_NOTE_TRANSFORM_FUNCTION':
234234
return { ...state, noteTransformFunction: payload.noteTransformFunction };
235+
case 'SET_ANNOTATION_CONTENT_OVERLAY_HANDLER':
236+
return { ...state, annotationContentOverlayHandler: payload.annotationContentOverlayHandler };
235237
default:
236238
return state;
237239
}

src/redux/selectors/exposedSelectors.js

+2
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ export const getAllowPageNavigation = state => state.viewer.allowPageNavigation;
145145

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

148+
export const getAnnotationContentOverlayHandler = state => state.viewer.annotationContentOverlayHandler;
149+
148150
// warning message
149151
export const getWarningMessage = state => state.viewer.warning?.message || '';
150152

0 commit comments

Comments
 (0)