box-annotations 4.0.0-beta.8 → 4.0.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +7 -57
  2. package/dist/annotations.css +1 -1
  3. package/dist/annotations.js +1 -1
  4. package/i18n/bn-IN.js +6 -0
  5. package/i18n/bn-IN.properties +12 -0
  6. package/i18n/da-DK.js +6 -0
  7. package/i18n/da-DK.properties +12 -0
  8. package/i18n/de-DE.js +6 -0
  9. package/i18n/de-DE.properties +12 -0
  10. package/i18n/en-AU.js +6 -0
  11. package/i18n/en-AU.properties +12 -0
  12. package/i18n/en-CA.js +6 -0
  13. package/i18n/en-CA.properties +12 -0
  14. package/i18n/en-GB.js +6 -0
  15. package/i18n/en-GB.properties +12 -0
  16. package/i18n/en-US.js +6 -0
  17. package/i18n/en-US.properties +12 -0
  18. package/i18n/en-x-pseudo.js +16 -9
  19. package/i18n/en-x-pseudo.properties +23 -9
  20. package/i18n/es-419.js +6 -0
  21. package/i18n/es-419.properties +12 -0
  22. package/i18n/es-ES.js +6 -0
  23. package/i18n/es-ES.properties +12 -0
  24. package/i18n/fi-FI.js +6 -0
  25. package/i18n/fi-FI.properties +12 -0
  26. package/i18n/fr-CA.js +6 -0
  27. package/i18n/fr-CA.properties +12 -0
  28. package/i18n/fr-FR.js +6 -0
  29. package/i18n/fr-FR.properties +12 -0
  30. package/i18n/hi-IN.js +6 -0
  31. package/i18n/hi-IN.properties +12 -0
  32. package/i18n/it-IT.js +6 -0
  33. package/i18n/it-IT.properties +12 -0
  34. package/i18n/ja-JP.js +6 -0
  35. package/i18n/ja-JP.properties +12 -0
  36. package/i18n/json/src/components/Popups/messages.json +1 -1
  37. package/i18n/ko-KR.js +6 -0
  38. package/i18n/ko-KR.properties +12 -0
  39. package/i18n/nb-NO.js +6 -0
  40. package/i18n/nb-NO.properties +12 -0
  41. package/i18n/nl-NL.js +6 -0
  42. package/i18n/nl-NL.properties +12 -0
  43. package/i18n/pl-PL.js +6 -0
  44. package/i18n/pl-PL.properties +12 -0
  45. package/i18n/pt-BR.js +6 -0
  46. package/i18n/pt-BR.properties +12 -0
  47. package/i18n/ru-RU.js +6 -0
  48. package/i18n/ru-RU.properties +12 -0
  49. package/i18n/sv-SE.js +6 -0
  50. package/i18n/sv-SE.properties +12 -0
  51. package/i18n/tr-TR.js +6 -0
  52. package/i18n/tr-TR.properties +12 -0
  53. package/i18n/zh-CN.js +6 -0
  54. package/i18n/zh-CN.properties +12 -0
  55. package/i18n/zh-TW.js +6 -0
  56. package/i18n/zh-TW.properties +12 -0
  57. package/package.json +29 -29
  58. package/src/@types/events.ts +8 -2
  59. package/src/@types/global.ts +1 -0
  60. package/src/@types/model.ts +20 -7
  61. package/src/BoxAnnotations.ts +16 -2
  62. package/src/common/BaseAnnotator.scss +0 -1
  63. package/src/common/BaseAnnotator.ts +125 -19
  64. package/src/common/BaseManager.ts +67 -4
  65. package/src/common/DeselectListener.tsx +21 -0
  66. package/src/common/DeselectManager.tsx +30 -0
  67. package/src/common/useIsListInteractive.ts +10 -0
  68. package/src/common/useMountId.ts +14 -0
  69. package/src/common/useOutsideEvent.ts +9 -4
  70. package/src/common/usePrevious.ts +13 -0
  71. package/src/components/ItemList/ItemList.scss +7 -2
  72. package/src/components/ItemList/ItemList.tsx +45 -26
  73. package/src/components/ItemList/ItemRow.scss +19 -7
  74. package/src/components/ItemList/ItemRow.tsx +33 -12
  75. package/src/components/PointerCapture/PointerCapture.tsx +116 -0
  76. package/src/components/PointerCapture/index.ts +2 -0
  77. package/src/components/Popups/Popper.ts +1 -0
  78. package/src/components/Popups/PopupArrow.scss +91 -0
  79. package/src/components/Popups/PopupBase.scss +12 -81
  80. package/src/components/Popups/PopupBase.tsx +0 -1
  81. package/src/components/Popups/PopupCursor.tsx +1 -1
  82. package/src/components/Popups/PopupDrawingToolbar.scss +76 -0
  83. package/src/components/Popups/PopupDrawingToolbar.tsx +109 -0
  84. package/src/components/Popups/PopupHighlight.scss +39 -0
  85. package/src/components/Popups/PopupHighlight.tsx +97 -0
  86. package/src/components/Popups/PopupHighlightError.scss +23 -0
  87. package/src/components/Popups/PopupHighlightError.tsx +74 -0
  88. package/src/components/Popups/PopupList.scss +1 -4
  89. package/src/components/Popups/PopupList.tsx +2 -2
  90. package/src/components/Popups/PopupReply.tsx +51 -6
  91. package/src/components/Popups/messages.ts +30 -0
  92. package/src/components/ReplyField/ReplyField.scss +6 -1
  93. package/src/components/ReplyField/ReplyField.tsx +43 -42
  94. package/src/components/ReplyField/ReplyFieldContainer.tsx +2 -1
  95. package/src/components/ReplyForm/ReplyButton.tsx +38 -0
  96. package/src/components/ReplyForm/ReplyForm.scss +3 -0
  97. package/src/components/ReplyForm/ReplyForm.tsx +49 -12
  98. package/src/components/ReplyForm/ReplyFormContainer.tsx +6 -4
  99. package/src/constants.js +1 -8
  100. package/src/document/DocumentAnnotator.scss +15 -0
  101. package/src/document/DocumentAnnotator.ts +110 -53
  102. package/src/document/docUtil.ts +61 -0
  103. package/src/drawing/DecoratedDrawingPath.scss +9 -0
  104. package/src/drawing/DecoratedDrawingPath.tsx +43 -0
  105. package/src/drawing/DrawingAnnotations.scss +39 -0
  106. package/src/drawing/DrawingAnnotations.tsx +157 -0
  107. package/src/drawing/DrawingAnnotationsContainer.tsx +66 -0
  108. package/src/drawing/DrawingCreator.scss +9 -0
  109. package/src/drawing/DrawingCreator.tsx +183 -0
  110. package/src/drawing/DrawingCursor.ts +23 -0
  111. package/src/drawing/DrawingList.tsx +60 -0
  112. package/src/drawing/DrawingManager.tsx +15 -0
  113. package/src/drawing/DrawingPath.tsx +17 -0
  114. package/src/drawing/DrawingPathGroup.tsx +52 -0
  115. package/src/drawing/DrawingSVG.tsx +34 -0
  116. package/src/drawing/DrawingSVGGroup.tsx +23 -0
  117. package/src/drawing/DrawingTarget.scss +11 -0
  118. package/src/drawing/DrawingTarget.tsx +100 -0
  119. package/src/drawing/SVGFilterContext.js +5 -0
  120. package/src/drawing/actions.ts +48 -0
  121. package/src/drawing/drawingUtil.ts +106 -0
  122. package/src/drawing/index.ts +2 -0
  123. package/src/highlight/HighlightAnnotations.scss +12 -0
  124. package/src/highlight/HighlightAnnotations.tsx +144 -0
  125. package/src/highlight/HighlightCanvas.scss +9 -0
  126. package/src/highlight/HighlightCanvas.tsx +109 -0
  127. package/src/highlight/HighlightContainer.tsx +64 -0
  128. package/src/highlight/HighlightCreator.scss +12 -0
  129. package/src/highlight/HighlightCreatorManager.ts +79 -0
  130. package/src/highlight/HighlightList.scss +13 -0
  131. package/src/highlight/HighlightList.tsx +92 -0
  132. package/src/highlight/HighlightListener.ts +37 -0
  133. package/src/highlight/HighlightManager.tsx +15 -0
  134. package/src/highlight/HighlightSvg.scss +7 -0
  135. package/src/highlight/HighlightSvg.tsx +18 -0
  136. package/src/highlight/HighlightTarget.scss +5 -0
  137. package/src/highlight/HighlightTarget.tsx +106 -0
  138. package/src/highlight/actions.ts +36 -0
  139. package/src/highlight/highlightUtil.ts +143 -0
  140. package/src/highlight/index.ts +4 -0
  141. package/src/image/ImageAnnotator.scss +20 -0
  142. package/src/image/ImageAnnotator.ts +137 -0
  143. package/src/polyfill.ts +19 -0
  144. package/src/popup/PopupContainer.tsx +50 -0
  145. package/src/popup/PopupLayer.scss +3 -0
  146. package/src/popup/PopupLayer.tsx +103 -0
  147. package/src/popup/PopupManager.tsx +15 -0
  148. package/src/region/RegionAnnotation.scss +4 -0
  149. package/src/region/RegionAnnotation.tsx +15 -16
  150. package/src/region/RegionAnnotations.scss +1 -15
  151. package/src/region/RegionAnnotations.tsx +14 -115
  152. package/src/region/RegionAnnotationsContainer.tsx +24 -0
  153. package/src/region/RegionCreation.scss +17 -0
  154. package/src/region/RegionCreation.tsx +79 -0
  155. package/src/region/RegionCreationContainer.tsx +43 -0
  156. package/src/region/RegionCreationManager.tsx +15 -0
  157. package/src/region/RegionCreator.scss +9 -2
  158. package/src/region/RegionCreator.tsx +50 -71
  159. package/src/region/RegionList.tsx +8 -12
  160. package/src/region/RegionManager.tsx +7 -39
  161. package/src/region/RegionRect.scss +41 -9
  162. package/src/region/RegionRect.tsx +9 -3
  163. package/src/region/index.ts +1 -0
  164. package/src/region/regionUtil.ts +1 -8
  165. package/src/region/transformUtil.ts +28 -0
  166. package/src/store/annotations/actions.ts +1 -0
  167. package/src/store/annotations/reducer.ts +9 -2
  168. package/src/store/annotations/selectors.ts +1 -0
  169. package/src/store/annotations/types.ts +1 -0
  170. package/src/store/common/actions.ts +1 -0
  171. package/src/store/common/reducer.ts +16 -10
  172. package/src/store/common/selectors.ts +1 -0
  173. package/src/store/common/types.ts +3 -0
  174. package/src/store/createRootReducer.ts +4 -0
  175. package/src/store/creator/actions.ts +2 -0
  176. package/src/store/creator/reducer.ts +22 -3
  177. package/src/store/creator/selectors.ts +9 -1
  178. package/src/store/creator/types.ts +18 -3
  179. package/src/store/drawing/actions.ts +10 -0
  180. package/src/store/drawing/index.ts +4 -0
  181. package/src/store/drawing/reducer.ts +50 -0
  182. package/src/store/drawing/selectors.ts +9 -0
  183. package/src/store/drawing/types.ts +7 -0
  184. package/src/store/eventing/active.ts +4 -2
  185. package/src/store/eventing/fetch.ts +8 -0
  186. package/src/store/eventing/init.ts +14 -0
  187. package/src/store/eventing/middleware.ts +21 -3
  188. package/src/store/eventing/mode.ts +7 -0
  189. package/src/store/eventing/staged.ts +56 -0
  190. package/src/store/eventing/status.ts +19 -0
  191. package/src/store/highlight/actions.ts +106 -0
  192. package/src/store/highlight/index.ts +4 -0
  193. package/src/store/highlight/reducer.ts +37 -0
  194. package/src/store/highlight/selectors.ts +12 -0
  195. package/src/store/highlight/types.ts +14 -0
  196. package/src/store/index.ts +2 -0
  197. package/src/store/options/actions.ts +2 -0
  198. package/src/store/options/reducer.ts +17 -1
  199. package/src/store/options/selectors.ts +8 -0
  200. package/src/store/options/types.ts +5 -0
  201. package/src/store/types.ts +5 -1
  202. package/src/store/users/actions.ts +3 -2
  203. package/src/utils/resin.ts +22 -0
  204. package/src/utils/rotate.ts +131 -0
  205. package/src/utils/scroll.ts +45 -0
  206. package/src/utils/util.ts +3 -0
  207. package/src/region/RegionContainer.tsx +0 -49
  208. package/src/utils/polyfill.js +0 -161
@@ -1,14 +1,14 @@
1
1
  import React from 'react';
2
- import { getFormattedCommentText } from 'box-ui-elements/es/components/form-elements/draft-js-mention-selector';
3
- import Button from 'box-ui-elements/es/components/button';
4
- import PrimaryButton from 'box-ui-elements/es/components/primary-button';
2
+ import { getFormattedCommentText } from 'box-ui-elements/es/components/form-elements/draft-js-mention-selector/utils';
5
3
  import { KEYS } from 'box-ui-elements/es/constants';
6
4
  import { EditorState } from 'draft-js';
7
5
  import { Form, FormikProps } from 'formik';
8
6
  import { FormattedMessage, useIntl } from 'react-intl';
9
7
  import messages from '../Popups/messages';
8
+ import ReplyButton from './ReplyButton';
10
9
  import ReplyField from '../ReplyField';
11
10
  import { FormValues, PropsFromState as ContainerProps } from './ReplyFormContainer';
11
+ import './ReplyForm.scss';
12
12
 
13
13
  export type ReplyFormProps = {
14
14
  isPending: boolean;
@@ -22,30 +22,55 @@ export type Props = ReplyFormProps &
22
22
  ContainerProps &
23
23
  Pick<FormikProps<FormValues>, 'errors' | 'setFieldValue' | 'values'>;
24
24
 
25
- const ReplyForm = ({ errors, isPending, onCancel, onChange, setFieldValue, values }: Props): JSX.Element => {
25
+ const ReplyForm = (props: Props): JSX.Element => {
26
+ const { errors, isPending, onCancel, onChange, setFieldValue, values } = props;
27
+
28
+ const formRef = React.useRef<HTMLFormElement>(null);
26
29
  const intl = useIntl();
27
30
  const hasErrors = Object.keys(errors).length > 0;
28
31
  const { editorState } = values;
29
32
 
33
+ const handleCancel = (): void => {
34
+ onCancel(getFormattedCommentText(editorState).text);
35
+ };
30
36
  // Instead of deferring to the Formik handleChange helper, we need to use the setFieldValue helper
31
37
  // method in order for DraftJS to work correctly by setting back the whole editorState
32
38
  const handleChange = (nextEditorState: EditorState): void => {
33
39
  onChange(getFormattedCommentText(nextEditorState).text);
34
40
  setFieldValue('editorState', nextEditorState);
35
41
  };
36
- const handleKeyDown = (event: React.KeyboardEvent): void => {
42
+
43
+ const handleKeyDown = (event: KeyboardEvent): void => {
37
44
  if (event.key !== KEYS.escape) {
38
45
  return;
39
46
  }
40
47
 
48
+ event.preventDefault();
41
49
  event.stopPropagation();
42
- event.nativeEvent.stopImmediatePropagation();
43
50
 
44
- onCancel(getFormattedCommentText(editorState).text);
51
+ handleCancel();
45
52
  };
46
53
 
54
+ // The entire annotation mode keydown event listener is attached to real dom's document
55
+ // Have to attach this event listener to real dom too, in order to capture the keydown event
56
+ // before annotation mode event listner captures it
57
+ React.useEffect(() => {
58
+ const { current: formEl } = formRef;
59
+
60
+ if (formEl) {
61
+ // Add event listener to real DOM
62
+ formEl.addEventListener('keydown', handleKeyDown);
63
+ }
64
+
65
+ return () => {
66
+ if (formEl) {
67
+ formEl.removeEventListener('keydown', handleKeyDown);
68
+ }
69
+ };
70
+ }, [formRef]); // eslint-disable-line react-hooks/exhaustive-deps
71
+
47
72
  return (
48
- <Form className="ba-ReplyForm" data-testid="ba-ReplyForm" onKeyDown={handleKeyDown}>
73
+ <Form ref={formRef} className="ba-ReplyForm" data-testid="ba-ReplyForm">
49
74
  <div className="ba-Popup-main">
50
75
  <ReplyField
51
76
  className="ba-Popup-field"
@@ -57,12 +82,24 @@ const ReplyForm = ({ errors, isPending, onCancel, onChange, setFieldValue, value
57
82
  />
58
83
  </div>
59
84
  <div className="ba-Popup-footer">
60
- <Button data-testid="ba-Popup-cancel" isDisabled={isPending} onClick={onCancel} type="button">
85
+ <ReplyButton
86
+ data-resin-target="cancel"
87
+ data-testid="ba-Popup-cancel"
88
+ isDisabled={isPending}
89
+ onClick={handleCancel}
90
+ type="button"
91
+ >
61
92
  <FormattedMessage {...messages.buttonCancel} />
62
- </Button>
63
- <PrimaryButton data-testid="ba-Popup-submit" isDisabled={hasErrors || isPending} type="submit">
93
+ </ReplyButton>
94
+ <ReplyButton
95
+ data-resin-target="post"
96
+ data-testid="ba-Popup-submit"
97
+ isDisabled={hasErrors || isPending}
98
+ isPrimary
99
+ type="submit"
100
+ >
64
101
  <FormattedMessage {...messages.buttonPost} />
65
- </PrimaryButton>
102
+ </ReplyButton>
66
103
  </div>
67
104
  </Form>
68
105
  );
@@ -1,7 +1,5 @@
1
- import {
2
- createMentionSelectorState,
3
- getFormattedCommentText,
4
- } from 'box-ui-elements/es/components/form-elements/draft-js-mention-selector';
1
+ import createMentionSelectorState from 'box-ui-elements/es/components/form-elements/draft-js-mention-selector/createMentionSelectorState';
2
+ import { getFormattedCommentText } from 'box-ui-elements/es/components/form-elements/draft-js-mention-selector/utils';
5
3
  import { EditorState, SelectionState } from 'draft-js';
6
4
  import { FormikBag, withFormik } from 'formik';
7
5
  import { connect } from 'react-redux';
@@ -29,6 +27,8 @@ export const mapStateToProps = (state: AppState): PropsFromState => ({
29
27
  cursorPosition: getCreatorCursor(state),
30
28
  });
31
29
 
30
+ export const mapPropsToErrors = (): FormErrors => ({ editorState: 'initial' });
31
+
32
32
  export const mapPropsToValues = ({ cursorPosition: prevCursorPosition, value = '' }: Props): FormValues => {
33
33
  const mentionState = withMentionDecorator(createMentionSelectorState(value));
34
34
  const cursorPosition = value ? prevCursorPosition : 0;
@@ -68,8 +68,10 @@ export const handleSubmit = (
68
68
  const ReplyFormContainer = connect(mapStateToProps)(
69
69
  withFormik<Props, FormValues>({
70
70
  handleSubmit,
71
+ mapPropsToErrors,
71
72
  mapPropsToValues,
72
73
  validate,
74
+ validateOnMount: true,
73
75
  })(ReplyForm),
74
76
  );
75
77
 
package/src/constants.js CHANGED
@@ -1,6 +1,3 @@
1
- // Annotation CSS constants
2
- export const CLASS_ANNOTATIONS_LOADED = 'ba-annotations-loaded';
3
-
4
1
  export const ANNOTATOR_EVENT = {
5
2
  fetch: 'annotationsfetched',
6
3
  error: 'annotationerror',
@@ -8,8 +5,4 @@ export const ANNOTATOR_EVENT = {
8
5
  setVisibility: 'annotationsetvisibility',
9
6
  };
10
7
 
11
- export const PLACEHOLDER_USER = {
12
- type: 'user',
13
- id: '0',
14
- email: '',
15
- };
8
+ export const MOUSE_PRIMARY = 1;
@@ -1,3 +1,6 @@
1
+ @import '~box-ui-elements/es/styles/variables';
2
+ @import '../highlight/HighlightCreator';
3
+
1
4
  .bp-doc {
2
5
  .ba-Layer {
3
6
  position: absolute;
@@ -7,4 +10,16 @@
7
10
  width: 100%;
8
11
  pointer-events: none;
9
12
  }
13
+
14
+ &.ba-is-create--highlight {
15
+ .textLayer {
16
+ > span {
17
+ @include ba-HighlightCreator-cursor;
18
+ }
19
+
20
+ ::selection {
21
+ background-color: rgba($bdl-yellow, .33);
22
+ }
23
+ }
24
+ }
10
25
  }
@@ -1,32 +1,105 @@
1
- import BaseAnnotator from '../common/BaseAnnotator';
2
- import BaseManager from '../common/BaseManager';
3
- import { CLASS_ANNOTATIONS_LOADED } from '../constants';
1
+ import BaseAnnotator, { ANNOTATION_CLASSES, Options } from '../common/BaseAnnotator';
2
+ import PopupManager from '../popup/PopupManager';
3
+ import { centerDrawing, DrawingManager, isDrawing } from '../drawing';
4
+ import {
5
+ centerHighlight,
6
+ HighlightCreatorManager,
7
+ HighlightListener,
8
+ HighlightManager,
9
+ isHighlight,
10
+ } from '../highlight';
11
+ import { centerRegion, isRegion, RegionCreationManager, RegionManager } from '../region';
12
+ import { Event } from '../@types';
4
13
  import { getAnnotation } from '../store/annotations';
5
- import { centerRegion, isRegion, RegionManager } from '../region';
14
+ import { getSelection } from './docUtil';
15
+ import { Manager } from '../common/BaseManager';
16
+ import { getFileId, getIsCurrentFileVersion, Mode } from '../store';
17
+ import { scrollToLocation } from '../utils/scroll';
6
18
  import './DocumentAnnotator.scss';
7
19
 
8
- export const SCROLL_THRESHOLD = 1000; // pixels
20
+ // Pdf.js textLayer enhancement requires waiting for 300ms (they hardcode this magic number)
21
+ const TEXT_LAYER_ENHANCEMENT = 300;
9
22
 
10
23
  export default class DocumentAnnotator extends BaseAnnotator {
11
24
  annotatedEl?: HTMLElement;
12
25
 
13
- managers: Map<number, BaseManager[]> = new Map();
26
+ highlightListener?: HighlightListener;
14
27
 
15
- getPageManagers(pageEl: HTMLElement): BaseManager[] {
28
+ managers: Map<number, Set<Manager>> = new Map();
29
+
30
+ constructor(options: Options) {
31
+ super(options);
32
+
33
+ this.highlightListener = new HighlightListener({ getSelection, store: this.store });
34
+
35
+ this.addListener(Event.ANNOTATIONS_MODE_CHANGE, this.handleChangeMode);
36
+ }
37
+
38
+ destroy(): void {
39
+ this.removeListener(Event.ANNOTATIONS_MODE_CHANGE, this.handleChangeMode);
40
+
41
+ if (this.highlightListener) {
42
+ this.highlightListener.destroy();
43
+ }
44
+
45
+ if (this.managers) {
46
+ this.managers.forEach(managers => managers.forEach(manager => manager.destroy()));
47
+ }
48
+
49
+ super.destroy();
50
+ }
51
+
52
+ getAnnotatedElement(): HTMLElement | null | undefined {
53
+ return this.containerEl?.querySelector('.bp-doc');
54
+ }
55
+
56
+ getPageManagers(pageEl: HTMLElement): Set<Manager> {
57
+ const fileId = getFileId(this.store.getState());
58
+ const isCurrentFileVersion = getIsCurrentFileVersion(this.store.getState());
16
59
  const pageNumber = this.getPageNumber(pageEl);
17
60
  const pageReferenceEl = this.getPageReference(pageEl);
18
- const managers = this.managers.get(pageNumber) || [];
61
+ const managers = this.managers.get(pageNumber) || new Set();
62
+ const resinTags = { fileid: fileId, iscurrent: isCurrentFileVersion };
19
63
 
20
64
  // Destroy any managers that were attached to page elements that no longer exist
21
- if (managers.some(manager => !manager.exists(pageEl))) {
22
- managers.forEach(manager => manager.destroy());
23
- managers.length = 0;
24
- }
65
+ managers.forEach(manager => {
66
+ if (!manager.exists(pageEl)) {
67
+ manager.destroy();
68
+ managers.delete(manager);
69
+ }
70
+ });
25
71
 
26
72
  // Lazily instantiate managers as pages are added or re-rendered
27
- if (managers.length === 0) {
28
- // Add additional managers here for other annotation types
29
- managers.push(new RegionManager({ page: pageNumber, pageEl, referenceEl: pageReferenceEl }));
73
+ if (managers.size === 0) {
74
+ managers.add(new PopupManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
75
+ managers.add(new DrawingManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
76
+
77
+ const textLayer = pageEl.querySelector('.textLayer') as HTMLElement;
78
+
79
+ if (textLayer) {
80
+ managers.add(
81
+ new HighlightCreatorManager({
82
+ getSelection,
83
+ referenceEl: textLayer,
84
+ selectionChangeDelay: TEXT_LAYER_ENHANCEMENT,
85
+ store: this.store,
86
+ }),
87
+ );
88
+ }
89
+
90
+ managers.add(new HighlightManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
91
+
92
+ managers.add(new RegionManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
93
+
94
+ const canvasLayerEl = pageEl.querySelector<HTMLElement>('.canvasWrapper');
95
+
96
+ managers.add(
97
+ new RegionCreationManager({
98
+ location: pageNumber,
99
+ referenceEl: canvasLayerEl || pageReferenceEl,
100
+ resinTags,
101
+ }),
102
+ );
30
103
  }
31
104
 
32
105
  return managers;
@@ -52,23 +125,26 @@ export default class DocumentAnnotator extends BaseAnnotator {
52
125
  return this.annotatedEl ? Array.from(this.annotatedEl.querySelectorAll('.page')) : [];
53
126
  }
54
127
 
55
- init(scale: number): void {
56
- super.init(scale);
57
-
58
- if (!this.rootEl) {
128
+ handleChangeMode = ({ mode }: { mode: Mode }): void => {
129
+ if (!this.annotatedEl) {
59
130
  return;
60
131
  }
61
132
 
62
- this.annotatedEl = this.rootEl.querySelector('.bp-doc') as HTMLElement;
63
- this.annotatedEl.classList.add(CLASS_ANNOTATIONS_LOADED);
133
+ this.removeAnnotationClasses();
64
134
 
65
- this.render();
66
- }
135
+ const annotatedElement = this.annotatedEl;
136
+ const className = ANNOTATION_CLASSES[mode];
137
+ if (className) {
138
+ annotatedElement.classList.add(className);
139
+ }
140
+ };
67
141
 
68
142
  render(): void {
69
143
  this.getPages()
70
144
  .filter(({ dataset }) => dataset.loaded && dataset.pageNumber)
71
145
  .forEach(pageEl => this.renderPage(pageEl));
146
+
147
+ this.postRender();
72
148
  }
73
149
 
74
150
  renderPage(pageEl: HTMLElement): void {
@@ -92,43 +168,24 @@ export default class DocumentAnnotator extends BaseAnnotator {
92
168
  }
93
169
 
94
170
  const annotation = getAnnotation(this.store.getState(), annotationId);
95
- const annotationPage = annotation?.target.location.value;
171
+ const annotationPage = annotation?.target.location.value ?? 1;
172
+ const annotationPageEl = this.getPage(annotationPage);
96
173
 
97
- if (!annotation || !annotationPage) {
174
+ if (!annotation || !annotationPage || !annotationPageEl || !this.annotatedEl) {
98
175
  return;
99
176
  }
100
177
 
178
+ let offsets = null;
101
179
  if (isRegion(annotation)) {
102
- this.scrollToLocation(annotationPage, centerRegion(annotation.target.shape));
103
- }
104
- }
105
-
106
- scrollToLocation(page = 1, { x: offsetX, y: offsetY } = { x: 0, y: 0 }): void {
107
- const pageEl = this.getPage(page);
108
-
109
- if (!this.annotatedEl || !pageEl) {
110
- return;
180
+ offsets = centerRegion(annotation.target.shape);
181
+ } else if (isHighlight(annotation)) {
182
+ offsets = centerHighlight(annotation.target.shapes);
183
+ } else if (isDrawing(annotation)) {
184
+ offsets = centerDrawing(annotation.target.path_groups);
111
185
  }
112
186
 
113
- const canSmoothScroll = 'scrollBehavior' in this.annotatedEl.style;
114
- const parentCenterX = Math.round(this.annotatedEl.clientWidth / 2);
115
- const parentCenterY = Math.round(this.annotatedEl.clientHeight / 2);
116
- const offsetCenterX = Math.round(pageEl.clientWidth * (offsetX / 100));
117
- const offsetCenterY = Math.round(pageEl.clientHeight * (offsetY / 100));
118
- const offsetScrollLeft = pageEl.offsetLeft - parentCenterX + offsetCenterX;
119
- const offsetScrollTop = pageEl.offsetTop - parentCenterY + offsetCenterY;
120
- const scrollLeft = Math.max(0, Math.min(offsetScrollLeft, this.annotatedEl.scrollWidth));
121
- const scrollTop = Math.max(0, Math.min(offsetScrollTop, this.annotatedEl.scrollHeight));
122
-
123
- if (canSmoothScroll && Math.abs(this.annotatedEl.scrollTop - scrollTop) < SCROLL_THRESHOLD) {
124
- this.annotatedEl.scrollTo({
125
- behavior: 'smooth',
126
- left: scrollLeft,
127
- top: scrollTop,
128
- });
129
- } else {
130
- this.annotatedEl.scrollLeft = scrollLeft;
131
- this.annotatedEl.scrollTop = scrollTop;
187
+ if (offsets) {
188
+ scrollToLocation(this.annotatedEl, annotationPageEl, { offsets });
132
189
  }
133
190
  }
134
191
  }
@@ -0,0 +1,61 @@
1
+ type Selection = {
2
+ containerRect: DOMRect;
3
+ hasError?: boolean;
4
+ location: number;
5
+ range: Range;
6
+ };
7
+
8
+ /**
9
+ * Finds the closest ancestor DOM element with the specified class.
10
+ */
11
+ export function findClosestElWithClass(element: Element | null, className: string): Element | null {
12
+ for (let el = element; el && el !== document.body; el = el.parentNode as Element | null) {
13
+ if (el.classList && el.classList.contains(className)) {
14
+ return el;
15
+ }
16
+ }
17
+
18
+ return null;
19
+ }
20
+
21
+ /**
22
+ * Returns the page element and page number that the element is on.
23
+ */
24
+ export function getPageNumber(element: Element | null): number | undefined {
25
+ const pageEl = findClosestElWithClass(element, 'page');
26
+ const pageNumber = pageEl && pageEl.getAttribute('data-page-number');
27
+
28
+ return pageNumber ? parseInt(pageNumber, 10) : undefined;
29
+ }
30
+
31
+ export function getSelection(): Selection | null {
32
+ const selection = window.getSelection();
33
+
34
+ if (!selection || selection.isCollapsed || !selection.rangeCount || !selection.focusNode) {
35
+ return null;
36
+ }
37
+
38
+ // Firefox allows to select multiple ranges in the document by using Ctrl/Cmd+click
39
+ // We only care about the last range
40
+ let range = selection.getRangeAt(selection.rangeCount - 1);
41
+
42
+ const containerEl = findClosestElWithClass(range.endContainer as Element, 'textLayer');
43
+ const startPage = getPageNumber(range.startContainer as Element);
44
+ const endPage = getPageNumber(range.endContainer as Element);
45
+
46
+ if (!containerEl || !startPage || !endPage) {
47
+ return null;
48
+ }
49
+
50
+ const hasError = startPage !== endPage;
51
+ let location = endPage;
52
+
53
+ if (hasError) {
54
+ range = document.createRange();
55
+ range.setEnd(selection.focusNode, selection.focusOffset);
56
+ range.collapse(false); // Parameter toStart is not optional in IE
57
+ location = getPageNumber(selection.focusNode as Element) ?? endPage;
58
+ }
59
+
60
+ return { containerRect: containerEl.getBoundingClientRect(), hasError, location, range };
61
+ }
@@ -0,0 +1,9 @@
1
+ .ba-DecoratedDrawingPath-decoration {
2
+ opacity: 0;
3
+ transition: opacity 200ms ease;
4
+
5
+ .is-active & {
6
+ opacity: 1;
7
+ transition-delay: 25ms;
8
+ }
9
+ }
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { white } from 'box-ui-elements/es/styles/variables';
3
+ import DrawingPath from './DrawingPath';
4
+ import SVGFilterContext from './SVGFilterContext';
5
+ import { getPathCommands } from './drawingUtil';
6
+ import { Position } from '../@types';
7
+ import './DecoratedDrawingPath.scss';
8
+
9
+ export type Props = {
10
+ borderStrokeWidth?: number;
11
+ isDecorated?: boolean;
12
+ points?: Position[];
13
+ };
14
+
15
+ export default function DecoratedDrawingPath({
16
+ borderStrokeWidth = 0,
17
+ isDecorated = false,
18
+ points = [],
19
+ }: Props): JSX.Element {
20
+ const filterID = React.useContext(SVGFilterContext);
21
+ const pathCommands = getPathCommands(points);
22
+ return (
23
+ <g className="ba-DecoratedDrawingPath">
24
+ {isDecorated && (
25
+ <g className="ba-DecoratedDrawingPath-decoration">
26
+ <DrawingPath
27
+ className="ba-DecoratedDrawingPath-shadow"
28
+ filter={`url(#${filterID})`}
29
+ pathCommands={pathCommands}
30
+ />
31
+ <DrawingPath
32
+ className="ba-DecoratedDrawingPath-border"
33
+ fill="none"
34
+ pathCommands={pathCommands}
35
+ stroke={white}
36
+ strokeWidth={borderStrokeWidth}
37
+ />
38
+ </g>
39
+ )}
40
+ <DrawingPath pathCommands={pathCommands} />
41
+ </g>
42
+ );
43
+ }
@@ -0,0 +1,39 @@
1
+ .ba-DrawingAnnotations-list {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ pointer-events: none;
8
+
9
+ &.is-listening {
10
+ .ba-DrawingTarget {
11
+ pointer-events: auto; // Delegate event control to avoid re-rendering every target on mousedown/up
12
+ }
13
+ }
14
+ }
15
+
16
+ .ba-DrawingAnnotations-creator,
17
+ .ba-DrawingAnnotations-target {
18
+ position: absolute;
19
+ top: 0;
20
+ left: 0;
21
+ width: 100%;
22
+ height: 100%;
23
+ }
24
+
25
+ .ba-DrawingAnnotations-creator {
26
+ pointer-events: auto;
27
+ touch-action: none;
28
+ }
29
+
30
+ .ba-DrawingAnnotations-toolbar {
31
+ opacity: 1;
32
+ transition: opacity 200ms ease;
33
+ pointer-events: auto;
34
+
35
+ &.ba-is-drawing {
36
+ opacity: 0;
37
+ pointer-events: none;
38
+ }
39
+ }