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
@@ -0,0 +1,79 @@
1
+ import {
2
+ AppStore,
3
+ getIsSelecting,
4
+ SelectionArg as Selection,
5
+ setIsSelectingAction,
6
+ setSelectionAction,
7
+ } from '../store';
8
+ import { Manager, Options as BaseOptions } from '../common/BaseManager';
9
+ import { MOUSE_PRIMARY } from '../constants';
10
+
11
+ export type Options = {
12
+ getSelection: () => Selection | null;
13
+ selectionChangeDelay?: number;
14
+ store: AppStore;
15
+ } & BaseOptions;
16
+
17
+ export default class HighlightCreatorManager implements Manager {
18
+ referenceEl: HTMLElement;
19
+
20
+ getSelection: () => Selection | null;
21
+
22
+ selectionChangeDelay?: number;
23
+
24
+ selectionChangeTimer?: number;
25
+
26
+ store: AppStore;
27
+
28
+ constructor({ getSelection, referenceEl, selectionChangeDelay = 0, store }: Options) {
29
+ this.getSelection = getSelection;
30
+ this.referenceEl = referenceEl;
31
+ this.selectionChangeDelay = selectionChangeDelay;
32
+ this.store = store;
33
+ }
34
+
35
+ exists(parentEl: HTMLElement): boolean {
36
+ return parentEl.contains(this.referenceEl);
37
+ }
38
+
39
+ render(): void {
40
+ // Clear previous selection
41
+ this.store.dispatch(setSelectionAction(null));
42
+
43
+ this.referenceEl.addEventListener('mousedown', this.handleMouseDown);
44
+ this.referenceEl.addEventListener('mouseup', this.handleMouseUp);
45
+ }
46
+
47
+ style(styles: Partial<CSSStyleDeclaration>): CSSStyleDeclaration {
48
+ return Object.assign(this.referenceEl.style, styles);
49
+ }
50
+
51
+ destroy(): void {
52
+ clearTimeout(this.selectionChangeTimer);
53
+
54
+ this.referenceEl.removeEventListener('mousedown', this.handleMouseDown);
55
+ this.referenceEl.removeEventListener('mouseup', this.handleMouseUp);
56
+ }
57
+
58
+ handleMouseDown = ({ buttons }: MouseEvent): void => {
59
+ if (buttons !== MOUSE_PRIMARY) {
60
+ return;
61
+ }
62
+
63
+ clearTimeout(this.selectionChangeTimer);
64
+
65
+ this.store.dispatch(setIsSelectingAction(true));
66
+ this.store.dispatch(setSelectionAction(null));
67
+ };
68
+
69
+ handleMouseUp = (): void => {
70
+ if (!getIsSelecting(this.store.getState())) {
71
+ return;
72
+ }
73
+
74
+ this.selectionChangeTimer = window.setTimeout(() => {
75
+ this.store.dispatch(setSelectionAction(this.getSelection()));
76
+ this.store.dispatch(setIsSelectingAction(false));
77
+ }, this.selectionChangeDelay);
78
+ };
79
+ }
@@ -0,0 +1,13 @@
1
+ .ba-HighlightList {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+
8
+ .ba-HighlightSvg.is-listening {
9
+ .ba-HighlightTarget-rect {
10
+ pointer-events: visible;
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,92 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import HighlightCanvas, { CanvasShape } from './HighlightCanvas';
4
+ import HighlightSvg from './HighlightSvg';
5
+ import HighlightTarget from './HighlightTarget';
6
+ import useIsListInteractive from '../common/useIsListInteractive';
7
+ import { AnnotationHighlight, Rect, TargetHighlight } from '../@types';
8
+ import { checkValue } from '../utils/util';
9
+ import './HighlightList.scss';
10
+
11
+ export type Props = {
12
+ activeId?: string | null;
13
+ annotations: AnnotationHighlight[];
14
+ className?: string;
15
+ onSelect?: (annotationId: string | null) => void;
16
+ };
17
+
18
+ export function filterHighlight({ target }: { target: TargetHighlight }): boolean {
19
+ const { shapes = [] } = target;
20
+
21
+ return shapes.every(
22
+ ({ height, width, x, y }: Rect) => checkValue(height) && checkValue(width) && checkValue(x) && checkValue(y),
23
+ );
24
+ }
25
+
26
+ export function getHighlightArea(shapes: Rect[]): number {
27
+ return shapes.reduce((area, { height, width }) => area + height * width, 0);
28
+ }
29
+
30
+ export function sortHighlight(
31
+ { target: targetA }: { target: TargetHighlight },
32
+ { target: targetB }: { target: TargetHighlight },
33
+ ): number {
34
+ const { shapes: shapesA } = targetA;
35
+ const { shapes: shapesB } = targetB;
36
+
37
+ // Render the smallest highlights last to ensure they are always clickable
38
+ return getHighlightArea(shapesA) > getHighlightArea(shapesB) ? -1 : 1;
39
+ }
40
+
41
+ export function getHighlightShapesFromAnnotations(
42
+ annotations: AnnotationHighlight[],
43
+ activeId: string | null,
44
+ hoverId: string | null,
45
+ ): CanvasShape[] {
46
+ return annotations.reduce<CanvasShape[]>((currentShapes, annotation) => {
47
+ const {
48
+ id,
49
+ target: { shapes },
50
+ } = annotation;
51
+
52
+ return currentShapes.concat(
53
+ shapes.map(shape => ({
54
+ ...shape,
55
+ isActive: activeId === id,
56
+ isHover: hoverId === id,
57
+ })),
58
+ );
59
+ }, []);
60
+ }
61
+
62
+ export function HighlightList({ activeId = null, annotations, className, onSelect }: Props): JSX.Element {
63
+ const [hoverId, setHoverId] = React.useState<string | null>(null);
64
+ const svgElRef = React.createRef<SVGSVGElement>();
65
+ const isListening = useIsListInteractive();
66
+ const sortedAnnotations = annotations.filter(filterHighlight).sort(sortHighlight);
67
+ const canvasShapes = getHighlightShapesFromAnnotations(sortedAnnotations, activeId, hoverId);
68
+
69
+ const handleTargetHover = (annotationId: string | null): void => {
70
+ setHoverId(annotationId);
71
+ };
72
+
73
+ return (
74
+ <div className={classNames('ba-HighlightList', className)} data-resin-component="highlightList">
75
+ <HighlightCanvas shapes={canvasShapes} />
76
+ <HighlightSvg ref={svgElRef} className={classNames({ 'is-listening': isListening })}>
77
+ {sortedAnnotations.map(({ id, target }) => (
78
+ <HighlightTarget
79
+ key={id}
80
+ annotationId={id}
81
+ isActive={activeId === id}
82
+ onHover={handleTargetHover}
83
+ onSelect={onSelect}
84
+ shapes={target.shapes}
85
+ />
86
+ ))}
87
+ </HighlightSvg>
88
+ </div>
89
+ );
90
+ }
91
+
92
+ export default React.memo(HighlightList);
@@ -0,0 +1,37 @@
1
+ import debounce from 'lodash/debounce';
2
+ import { AppStore, getIsSelecting, SelectionArg as Selection, setSelectionAction } from '../store';
3
+
4
+ export type Options = {
5
+ getSelection: () => Selection | null;
6
+ store: AppStore;
7
+ };
8
+
9
+ // Debounce 500ms for keyboard selection
10
+ const SELECTION_CHANGE_DEBOUNCE = 500;
11
+
12
+ export default class HighlightListener {
13
+ getSelection: () => Selection | null;
14
+
15
+ store: AppStore;
16
+
17
+ constructor({ getSelection, store }: Options) {
18
+ this.getSelection = getSelection;
19
+ this.store = store;
20
+
21
+ document.addEventListener('selectionchange', this.debounceHandleSelectionChange);
22
+ }
23
+
24
+ destroy(): void {
25
+ document.removeEventListener('selectionchange', this.debounceHandleSelectionChange);
26
+ }
27
+
28
+ handleSelectionChange = (): void => {
29
+ if (getIsSelecting(this.store.getState())) {
30
+ return;
31
+ }
32
+
33
+ this.store.dispatch(setSelectionAction(this.getSelection()));
34
+ };
35
+
36
+ debounceHandleSelectionChange = debounce(this.handleSelectionChange, SELECTION_CHANGE_DEBOUNCE);
37
+ }
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ import * as ReactDOM from 'react-dom';
3
+ import BaseManager, { Props } from '../common/BaseManager';
4
+ import HighlightContainer from './HighlightContainer';
5
+
6
+ export default class HighlightManager extends BaseManager {
7
+ decorate(): void {
8
+ this.reactEl.classList.add('ba-Layer--highlight');
9
+ this.reactEl.dataset.testid = 'ba-Layer--highlight';
10
+ }
11
+
12
+ render(props: Props): void {
13
+ ReactDOM.render(<HighlightContainer location={this.location} {...props} />, this.reactEl);
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ .ba-HighlightSvg {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import './HighlightSvg.scss';
4
+
5
+ type Props = {
6
+ children?: React.ReactNode;
7
+ className?: string;
8
+ };
9
+
10
+ const HighlightSvg = ({ children, className }: Props, ref: React.Ref<SVGSVGElement>): JSX.Element => {
11
+ return (
12
+ <svg ref={ref} className={classNames('ba-HighlightSvg', className)}>
13
+ {children}
14
+ </svg>
15
+ );
16
+ };
17
+
18
+ export default React.forwardRef(HighlightSvg);
@@ -0,0 +1,5 @@
1
+ .ba-HighlightTarget {
2
+ &:focus {
3
+ outline: none;
4
+ }
5
+ }
@@ -0,0 +1,106 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import noop from 'lodash/noop';
4
+ import useMountId from '../common/useMountId';
5
+ import { MOUSE_PRIMARY } from '../constants';
6
+ import { Rect } from '../@types/model';
7
+ import './HighlightTarget.scss';
8
+
9
+ type Props = {
10
+ annotationId: string;
11
+ className?: string;
12
+ isActive?: boolean;
13
+ onHover?: (annotationId: string | null) => void;
14
+ onMount?: (uuid: string) => void;
15
+ onSelect?: (annotationId: string) => void;
16
+ shapes: Array<Rect>;
17
+ };
18
+
19
+ export type HighlightTargetRef = HTMLAnchorElement;
20
+
21
+ const HighlightTarget = (props: Props, ref: React.Ref<HighlightTargetRef>): JSX.Element => {
22
+ const { annotationId, className, isActive, onHover = noop, onMount = noop, onSelect = noop, shapes } = props;
23
+ const uuid = useMountId(onMount);
24
+
25
+ const handleClick = (event: React.MouseEvent<HighlightTargetRef>): void => {
26
+ // These are needed to prevent the anchor link from being followed and updating the url location
27
+ event.preventDefault();
28
+ event.stopPropagation();
29
+ };
30
+
31
+ const handleFocus = (): void => {
32
+ onSelect(annotationId);
33
+ };
34
+
35
+ const handleMouseEnter = (): void => {
36
+ onHover(annotationId);
37
+ };
38
+
39
+ const handleMouseLeave = (): void => {
40
+ onHover(null);
41
+ };
42
+
43
+ const handleMouseDown = (event: React.MouseEvent<HighlightTargetRef>): void => {
44
+ if (event.buttons !== MOUSE_PRIMARY) {
45
+ return;
46
+ }
47
+ const activeElement = document.activeElement as HTMLElement;
48
+
49
+ event.preventDefault(); // Prevents focus from leaving the anchor immediately in some browsers
50
+ event.stopPropagation(); // Prevents document-level deselect event handlers from executing
51
+
52
+ // IE11 won't apply the focus to the SVG anchor, so this workaround attempts to blur the existing active element.
53
+ // If we don't blur it, we cannot re-focus it next time, since it was already focused and never got blurred.
54
+ if (activeElement && activeElement !== event.currentTarget && activeElement.blur) {
55
+ activeElement.blur();
56
+ }
57
+
58
+ if (event.currentTarget.focus) {
59
+ // Buttons do not receive focus in Firefox and Safari on MacOS; triggers handleFocus
60
+ event.currentTarget.focus();
61
+ } else {
62
+ // IE11 does not support focus function; call handleFocus directly
63
+ handleFocus();
64
+ }
65
+ };
66
+
67
+ return (
68
+ // eslint-disable-next-line jsx-a11y/anchor-is-valid
69
+ <a
70
+ ref={ref}
71
+ className={classNames('ba-HighlightTarget', className, { 'is-active': isActive })}
72
+ data-ba-reference-id={uuid}
73
+ data-resin-itemid={annotationId}
74
+ data-resin-target="highlightText"
75
+ data-testid={`ba-AnnotationTarget-${annotationId}`}
76
+ href="#"
77
+ onClick={handleClick}
78
+ onFocus={handleFocus}
79
+ onMouseDown={handleMouseDown}
80
+ onMouseEnter={handleMouseEnter}
81
+ onMouseLeave={handleMouseLeave}
82
+ role="button"
83
+ tabIndex={0}
84
+ >
85
+ {shapes.map(rect => {
86
+ const { height, width, x, y } = rect;
87
+ return (
88
+ // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
89
+ <rect
90
+ key={`${annotationId}-${x}-${y}-${width}-${height}`}
91
+ className="ba-HighlightTarget-rect"
92
+ fill="none"
93
+ height={`${height}%`}
94
+ width={`${width}%`}
95
+ x={`${x}%`}
96
+ y={`${y}%`}
97
+ />
98
+ );
99
+ })}
100
+ </a>
101
+ );
102
+ };
103
+
104
+ export { HighlightTarget as HighlightTargetComponent };
105
+
106
+ export default React.forwardRef(HighlightTarget);
@@ -0,0 +1,36 @@
1
+ import { AppThunkDispatch, AppState } from '../store';
2
+ import { createAnnotationAction } from '../store/annotations';
3
+ import { getFileVersionId } from '../store/options';
4
+ import { Rect } from '../@types';
5
+
6
+ export type CreateArg = {
7
+ location: number;
8
+ message: string;
9
+ shapes: Rect[];
10
+ text?: string;
11
+ };
12
+
13
+ export const createHighlightAction = (arg: CreateArg) => (dispatch: AppThunkDispatch, getState: () => AppState) => {
14
+ const { location, message, shapes } = arg;
15
+ const state = getState();
16
+ const newAnnotation = {
17
+ description: {
18
+ message,
19
+ type: 'reply' as const,
20
+ },
21
+ // eslint-disable-next-line @typescript-eslint/camelcase
22
+ file_version: {
23
+ id: getFileVersionId(state),
24
+ },
25
+ target: {
26
+ location: {
27
+ type: 'page' as const,
28
+ value: location,
29
+ },
30
+ shapes,
31
+ type: 'highlight' as const,
32
+ },
33
+ };
34
+
35
+ return dispatch(createAnnotationAction(newAnnotation));
36
+ };
@@ -0,0 +1,143 @@
1
+ import { Annotation, AnnotationHighlight, Position, Shape, Type } from '../@types';
2
+
3
+ export const getBoundingRect = (shapes: Shape[]): Shape => {
4
+ let minX = Number.MAX_VALUE;
5
+ let minY = Number.MAX_VALUE;
6
+ let maxX = Number.NEGATIVE_INFINITY;
7
+ let maxY = Number.NEGATIVE_INFINITY;
8
+
9
+ shapes.forEach(({ height, width, x, y }) => {
10
+ const x2 = x + width;
11
+ const y2 = y + height;
12
+
13
+ if (x < minX) {
14
+ minX = x;
15
+ }
16
+
17
+ if (y < minY) {
18
+ minY = y;
19
+ }
20
+
21
+ if (x2 > maxX) {
22
+ maxX = x2;
23
+ }
24
+
25
+ if (y2 > maxY) {
26
+ maxY = y2;
27
+ }
28
+ });
29
+
30
+ return {
31
+ x: minX,
32
+ y: minY,
33
+ width: maxX - minX,
34
+ height: maxY - minY,
35
+ };
36
+ };
37
+
38
+ const centerShape = (shape: Shape): Position => {
39
+ const { height, width } = shape;
40
+
41
+ return {
42
+ x: width / 2,
43
+ y: height / 2,
44
+ };
45
+ };
46
+
47
+ export const centerHighlight = (shapes: Shape[]): Position => {
48
+ const boundingShape = getBoundingRect(shapes);
49
+ const { x: shapeX, y: shapeY } = boundingShape;
50
+ const { x: centerX, y: centerY } = centerShape(boundingShape);
51
+
52
+ return {
53
+ x: centerX + shapeX,
54
+ y: centerY + shapeY,
55
+ };
56
+ };
57
+
58
+ export function isHighlight(annotation: Annotation): annotation is AnnotationHighlight {
59
+ return annotation?.target?.type === Type.highlight;
60
+ }
61
+
62
+ export const getThreshold = (prev: number, curr: number, threshold: number): number =>
63
+ Math.max(Math.round(prev * threshold), Math.round(curr * threshold), 1);
64
+
65
+ export const dedupeRects = (rects: Shape[], threshold = 0.1): Shape[] => {
66
+ const dedupedRects: Shape[] = [];
67
+
68
+ rects.forEach(curr => {
69
+ const prev = dedupedRects.pop();
70
+ // empty list, push current
71
+ if (!prev) {
72
+ dedupedRects.push(curr);
73
+ // The return in forEach callback is acting like a continue in for loop
74
+ return;
75
+ }
76
+
77
+ const { width: prevWidth, height: prevHeight, x: prevX, y: prevY } = prev;
78
+ const { width, height, x, y } = curr;
79
+
80
+ const xThreshold = getThreshold(prevWidth, width, threshold);
81
+ const yThreshold = getThreshold(prevHeight, height, threshold);
82
+
83
+ if (Math.abs(prevX - x) <= xThreshold && Math.abs(prevY - y) <= yThreshold) {
84
+ // the same rect, push the larger one
85
+ dedupedRects.push(prevHeight * prevWidth > height * width ? prev : curr);
86
+ } else {
87
+ // different rects, push both
88
+ dedupedRects.push(prev);
89
+ dedupedRects.push(curr);
90
+ }
91
+ });
92
+
93
+ return dedupedRects;
94
+ };
95
+
96
+ export const combineRects = (rects: Shape[]): Shape[] => {
97
+ const result: Shape[] = [];
98
+
99
+ dedupeRects(rects).forEach(rect => {
100
+ const { height, width, x, y } = rect;
101
+ const lastRect = result.pop();
102
+
103
+ if (!lastRect) {
104
+ result.push(rect);
105
+ return;
106
+ }
107
+
108
+ // This algorithm employs a hueristic that assumes rects are returned in ascending y coordinate
109
+ // followed by ascending x coordinate.
110
+ // If there is already a previous rect, check first to see if the current rect is approximately
111
+ // close enough (in both the x and y coordinate) to be considered as part of the same rect
112
+ // If it is not close enough then add the current rect as a separate rect in the array
113
+ const { height: lastHeight, width: lastWidth, x: lastX, y: lastY } = lastRect;
114
+ const lastMaxX = lastX + lastWidth;
115
+ const thresholdX = getThreshold(lastWidth, width, 0.3);
116
+ const thresholdY = getThreshold(lastHeight, height, 0.6);
117
+ const isXCloseEnough = Math.abs(x - lastMaxX) < thresholdX;
118
+ const isYCloseEnough = Math.abs(y - lastY) < thresholdY;
119
+
120
+ // If rect is close enough, add the rect to the existing entry, otherwise add it as a new entry
121
+ if (isXCloseEnough && isYCloseEnough) {
122
+ result.push(getBoundingRect([lastRect, rect]));
123
+ } else {
124
+ result.push(lastRect);
125
+ result.push(rect);
126
+ }
127
+ });
128
+
129
+ return result;
130
+ };
131
+
132
+ export const getShapeRelativeToContainer = (shape: Shape, containerShape: Shape): Shape => {
133
+ const { height, width, x, y } = shape;
134
+ const { height: containerHeight, width: containerWidth, x: containerX, y: containerY } = containerShape;
135
+
136
+ // If for some reason the x or y turns out to be negative in relation to the container, assume 0 instead
137
+ return {
138
+ height: (height / containerHeight) * 100,
139
+ width: (width / containerWidth) * 100,
140
+ x: (Math.max(0, x - containerX) / containerWidth) * 100,
141
+ y: (Math.max(0, y - containerY) / containerHeight) * 100,
142
+ };
143
+ };
@@ -0,0 +1,4 @@
1
+ export * from './highlightUtil';
2
+ export { default as HighlightCreatorManager } from './HighlightCreatorManager';
3
+ export { default as HighlightListener } from './HighlightListener';
4
+ export { default as HighlightManager } from './HighlightManager';
@@ -0,0 +1,20 @@
1
+ .bp-image {
2
+ .ba-Layer {
3
+ position: absolute;
4
+ white-space: initial; // Overrides white-space: no-wrap inherited .bp-image
5
+ text-align: initial; // Overrides text-align: center inherited .bp-image
6
+ pointer-events: none;
7
+ }
8
+
9
+ // Applied to img element, which is managed by Preview SDK
10
+ .ba-is-drawing {
11
+ user-select: none; // Prevent visible selection highlight
12
+ }
13
+
14
+ // Raise the rendering order to override the stacking context created by the
15
+ // transform applied to each of the managers' react mount element to account for
16
+ // rotation
17
+ .ba-Layer--popup {
18
+ z-index: 1;
19
+ }
20
+ }