Skip to content

Commit

Permalink
[CHANGE] Adds 'dangerouslySetNoteTransformFunction' which can be used…
Browse files Browse the repository at this point in the history
… to transform content inside notes (#519)
  • Loading branch information
lbittner-pdftron authored Feb 25, 2020
1 parent a2161e7 commit 0e523e2
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/apis/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ import setSignatureFonts from './setSignatureFonts';
import disableReplyForAnnotations from './disableReplyForAnnotations';
import getCustomData from './getCustomData';
import setCustomMeasurementOverlayInfo from './setCustomMeasurementOverlayInfo';
import setNoteTransformFunction from './setNoteTransformFunction';
import selectThumbnailPages from './selectThumbnailPages';
import unselectThumbnailPages from './unselectThumbnailPages';

Expand Down Expand Up @@ -186,6 +187,7 @@ export default store => {
setSortStrategy: setSortStrategy(store),
setSwipeOrientation,
setTheme,
dangerouslySetNoteTransformFunction: setNoteTransformFunction(store),
setToolMode: setToolMode(store),
setZoomLevel,
setZoomList: setZoomList(store),
Expand Down
73 changes: 73 additions & 0 deletions src/apis/setNoteTransformFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import actions from 'actions';

/**
* @callback NoteTransformFunction
* @memberof WebViewerInstance
* @param {HTMLElement} wrapperElement A reference to the DOM node that wraps the note. You can use this to query select child elements to mutate (see the examples below)
* @param {object} state The state of the note. Contains two properties, 'annotation' and 'isSelected'
* @param {Annotations.Annotation} state.annotation A reference to the annotation object associated with the note
* @param {boolean} state.isSelected whether or not the note is currently expanded
* @param {function} createElement A utility function that should be used when creating DOM nodes. This is a replacement for `document.createElement`.
* Accepts the same parameters as `document.createElement`. Using document.createElement instead of this function will cause your DOM nodes to not be cleaned up on subsequent renders.
*/

/**
* Accepts a function that will be called every time a note in the left panel is rendered.
* This function can be used to add, edit or hide the contents of the note.
* <br><br>
* <span style='font-size: 18px'><b>Please carefully read the documentation and the notes below before using this API</b></span><br><br>
*
* <b>This API is experimental and should be used sparingly.</b> If you find you are heavily relying on this function,
* it is recommended that you <a href='https://www.pdftron.com/documentation/web/guides/advanced-customization/'>fork the UI repo</a> and make the changes directly in the source code (Note.js).
* <br><br>
*
*
* The structure of the HTML that is passed into this function may change may change without notice in any release. <b>Please make sure
* to test this function thoroughly when upgrading WebViewer versions.</b>
* <br><br>
*
*
* There may be unexpected behaviour when using this API. The HTML that is provided is controlled by React, and sometimes React will override any changes you make.
* If you find any unexpected behaviour when using this API, then this API probably won't work for your use case and you will have to make the changes directly in the source code.
* <br><br>
*
* <b>Do not use document.createElement to create DOM elements</b>. Instead, use the provided `createElement` utility function provided as the third parameter.
*
* <b>Do not use HTMLElement.removeChild or any other APIs that remove elements from the DOM.</b> Doing so will cause React to lose reference to this node, and will crash.
* If you need to hide an HTML element, set the style to `display: none` instead.
* <br><br>
* @method WebViewerInstance#dangerouslySetNoteTransformFunction
* @param {WebViewerInstance.NoteTransformFunction} noteTransformFunction The function that will be used to transform notes in the left panel
* @example
Webviewer(...)
.then(instance => {
instance.dangerouslySetNoteTransformFunction((wrapper, state, createElement) => {
// Change the title of every note
wrapper.querySelector('.title>span').innerHTML = 'My custom note title';
// Add a button that alerts the user when clicked
const button = createElement('button');
button.onmousedown = (e) => {
if(state.isSelected) {
alert('Hello world!');
} else {
alert('Goodbye world!');
}
};
button.innerHTML = 'Alert me'
wrapper.appendChild(button);
// Add a button that makes the annotation blue
const button = createElement('button');
button.onmousedown = (e) => {
state.annotation.StrokeColor = new instance.Annotations.Color(0, 0, 255);
instance.annotManager.redrawAnnotation(state.annotation)
};
button.innerHTML = 'Make me blue'
wrapper.appendChild(button);
})
});
*/
export default store => noteTransformFunction => {
store.dispatch(actions.setNoteTransformFunction(noteTransformFunction));
};
44 changes: 43 additions & 1 deletion src/components/Note/Note.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useEffect, useRef, useContext } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { useSelector, shallowEqual } from 'react-redux';

import ReplyArea from 'components/Note/ReplyArea';
import NoteContext from 'components/Note/Context';
import NoteContent from 'components/NoteContent';

import selectors from 'selectors';
import core from 'core';

import './Note.scss';
Expand All @@ -14,10 +15,20 @@ const propTypes = {
annotation: PropTypes.object.isRequired,
};

let currId = 0;

const Note = ({ annotation }) => {
const { isSelected, resize } = useContext(NoteContext);
const containerRef = useRef();
const containerHeightRef = useRef();
const ids = useRef([]);

const [noteTransformFunction] = useSelector(
state => [
selectors.getNoteTransformFunction(state)
],
shallowEqual,
);

useEffect(() => {
const prevHeight = containerHeightRef.current;
Expand All @@ -29,6 +40,37 @@ const Note = ({ annotation }) => {
}
});

useEffect(() => {
if (noteTransformFunction) {
ids.current.forEach(id => {
const child = document.querySelector(`[data-webviewer-custom-element='${id}']`);
if (child) {
child.parentNode.removeChild(child);
}
})

ids.current = [];

const state = {
annotation,
isSelected,
};

noteTransformFunction(containerRef.current, state, (...params) => {
const element = document.createElement(...params);
const id = `custom-element-${currId}`;
currId++;
ids.current.push(id);
element.setAttribute('data-webviewer-custom-element', id);
element.addEventListener('mousedown', (e) => {
e.stopPropagation();
});

return element;
})
}
})

const handleNoteClick = e => {
// stop bubbling up otherwise the note will be closed
// due to annotation deselection
Expand Down
4 changes: 4 additions & 0 deletions src/redux/actions/internalActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,7 @@ export const setIsProgrammaticSearchFull = isProgrammaticSearchFull => ({
type: 'SET_IS_PROG_SEARCH_FULL',
payload: { isProgrammaticSearchFull },
});
export const setNoteTransformFunction = noteTransformFunction => ({
type: 'SET_NOTE_TRANSFORM_FUNCTION',
payload: { noteTransformFunction },
})
1 change: 1 addition & 0 deletions src/redux/initialState.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ export default {
leftPanelWidth: 300,
isReplyDisabledFunc: null,
customMeasurementOverlay: [],
noteTransformFunction: 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 @@ -230,6 +230,8 @@ export default initialState => (state = initialState, action) => {
return { ...state, tab: { ...state.tab, [payload.id]: payload.dataElement } };
case 'SET_CUSTOM_ELEMENT_OVERRIDES':
return { ...state, customElementOverrides: { ...state.customElementOverrides, [payload.dataElement]: payload.overrides } };
case 'SET_NOTE_TRANSFORM_FUNCTION':
return { ...state, noteTransformFunction: payload.noteTransformFunction }
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 @@ -210,3 +210,5 @@ export const isProgrammaticSearch = state => state.search.isProgrammaticSearch;

export const isProgrammaticSearchFull = state =>
state.search.isProgrammaticSearchFull;

export const getNoteTransformFunction = state => state.viewer.noteTransformFunction;

0 comments on commit 0e523e2

Please sign in to comment.