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,100 @@
1
+ import * as React from 'react';
2
+ import classNames from 'classnames';
3
+ import noop from 'lodash/noop';
4
+ import DecoratedDrawingPath from './DecoratedDrawingPath';
5
+ import DrawingPathGroup from './DrawingPathGroup';
6
+ import { DrawingSVGRef } from './DrawingSVG';
7
+ import { getCenter, getShape } from './drawingUtil';
8
+ import { MOUSE_PRIMARY } from '../constants';
9
+ import { TargetDrawing } from '../@types';
10
+ import './DrawingTarget.scss';
11
+
12
+ export type Props = {
13
+ annotationId: string;
14
+ className?: string;
15
+ isActive?: boolean;
16
+ onSelect?: (annotationId: string) => void;
17
+ rootEl: DrawingSVGRef | null;
18
+ target: TargetDrawing;
19
+ };
20
+
21
+ export type DrawingTargetRef = HTMLAnchorElement;
22
+
23
+ export const DrawingTarget = (props: Props, ref: React.Ref<DrawingTargetRef>): JSX.Element => {
24
+ const {
25
+ annotationId,
26
+ className,
27
+ isActive = false,
28
+ onSelect = noop,
29
+ rootEl,
30
+ target: { path_groups: pathGroups },
31
+ } = props;
32
+ const shape = getShape(pathGroups);
33
+ const { x: centerX, y: centerY } = getCenter(shape);
34
+
35
+ const handleFocus = (): void => {
36
+ onSelect(annotationId);
37
+ };
38
+ const handleMouseDown = (event: React.MouseEvent<DrawingTargetRef>): void => {
39
+ if (event.buttons !== MOUSE_PRIMARY) {
40
+ return;
41
+ }
42
+ const activeElement = document.activeElement as HTMLElement;
43
+
44
+ event.preventDefault(); // Prevents focus from leaving the button immediately in some browsers
45
+ event.stopPropagation(); // Prevents document-level deselect event handlers from executing
46
+
47
+ // IE11 won't apply the focus to the SVG anchor, so this workaround attempts to blur the existing active element.
48
+ // If we don't blur it, we cannot re-focus it next time, since it was already focused and never got blurred.
49
+ if (activeElement && activeElement !== event.currentTarget && activeElement.blur) {
50
+ activeElement.blur();
51
+ }
52
+
53
+ if (event.currentTarget.focus) {
54
+ // Buttons do not receive focus in Firefox and Safari on MacOS; triggers handleFocus
55
+ event.currentTarget.focus();
56
+ } else {
57
+ // IE11 does not support focus function; call handleFocus directly
58
+ handleFocus();
59
+ }
60
+ };
61
+
62
+ return (
63
+ // eslint-disable-next-line jsx-a11y/anchor-is-valid
64
+ <a
65
+ ref={ref}
66
+ className={classNames('ba-DrawingTarget', className, { 'is-active': isActive })}
67
+ data-resin-itemid={annotationId}
68
+ data-resin-target="highlightDrawing"
69
+ data-testid={`ba-AnnotationTarget-${annotationId}`}
70
+ href="#"
71
+ onFocus={handleFocus}
72
+ onMouseDown={handleMouseDown}
73
+ role="button"
74
+ tabIndex={0}
75
+ >
76
+ <rect
77
+ fill="transparent"
78
+ transform={`translate(-${centerX * 0.1}, -${centerY * 0.1}) scale(1.1)`}
79
+ {...shape}
80
+ />
81
+ {pathGroups.map(({ clientId: pathGroupClientId, paths, stroke }) => (
82
+ <DrawingPathGroup key={pathGroupClientId} isActive={isActive} rootEl={rootEl} stroke={stroke}>
83
+ {/* Use the children render function to pass down the calculated strokeWidthWithBorder value */}
84
+ {strokeWidthWithBorder =>
85
+ paths.map(({ clientId: pathClientId, points }) => (
86
+ <DecoratedDrawingPath
87
+ key={pathClientId}
88
+ borderStrokeWidth={strokeWidthWithBorder}
89
+ isDecorated
90
+ points={points}
91
+ />
92
+ ))
93
+ }
94
+ </DrawingPathGroup>
95
+ ))}
96
+ </a>
97
+ );
98
+ };
99
+
100
+ export default React.forwardRef(DrawingTarget);
@@ -0,0 +1,5 @@
1
+ import { createContext } from 'react';
2
+
3
+ const SVGFilterContext = createContext('');
4
+
5
+ export default SVGFilterContext;
@@ -0,0 +1,48 @@
1
+ import { AppThunkDispatch, AppState, getCreatorStatus, CreatorStatus } from '../store';
2
+ import { createAnnotationAction } from '../store/annotations';
3
+ import { getFileVersionId } from '../store/options';
4
+ import { PathGroup } from '../@types';
5
+ import { resetDrawingAction, setDrawingLocationAction } from '../store/drawing';
6
+
7
+ export type CreateArg = {
8
+ location: number;
9
+ message: string;
10
+ pathGroups: Array<PathGroup>;
11
+ };
12
+
13
+ export const createDrawingAction = (arg: CreateArg) => (dispatch: AppThunkDispatch, getState: () => AppState) => {
14
+ const { location, message, pathGroups } = 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
+ // eslint-disable-next-line @typescript-eslint/camelcase
31
+ path_groups: pathGroups,
32
+ type: 'drawing' as const,
33
+ },
34
+ };
35
+
36
+ return dispatch(createAnnotationAction(newAnnotation));
37
+ };
38
+
39
+ export const setupDrawingAction = (location: number) => (dispatch: AppThunkDispatch, getState: () => AppState) => {
40
+ const state = getState();
41
+ const creatorStatus = getCreatorStatus(state);
42
+
43
+ if (creatorStatus === CreatorStatus.staged) {
44
+ dispatch(resetDrawingAction());
45
+ }
46
+
47
+ return dispatch(setDrawingLocationAction(location));
48
+ };
@@ -0,0 +1,106 @@
1
+ import * as uuid from 'uuid';
2
+ import { Annotation, AnnotationDrawing, PathGroup, Position, Shape } from '../@types';
3
+
4
+ export function addClientIds({ paths, ...groupRest }: PathGroup): PathGroup {
5
+ return {
6
+ clientId: uuid.v4(),
7
+ paths: paths.map(path => ({
8
+ clientId: uuid.v4(),
9
+ ...path,
10
+ })),
11
+ ...groupRest,
12
+ };
13
+ }
14
+
15
+ export function formatDrawing(annotation: AnnotationDrawing): AnnotationDrawing {
16
+ const {
17
+ target: { path_groups: pathGroups, ...targetRest },
18
+ ...rest
19
+ } = annotation;
20
+
21
+ return {
22
+ target: {
23
+ // eslint-disable-next-line @typescript-eslint/camelcase
24
+ path_groups: pathGroups.map(pathGroup => addClientIds(pathGroup)),
25
+ ...targetRest,
26
+ },
27
+ ...rest,
28
+ };
29
+ }
30
+
31
+ export function getCenter({ height, width, x, y }: Shape): Position {
32
+ return { x: x + width / 2, y: y + height / 2 };
33
+ }
34
+
35
+ // We use cubic Bezier curves to generate path commands for better smoothing.
36
+ // For every data point, we generate extra two points using adjacent data points.
37
+ // Then we use the first generated point and the data point as two control points
38
+ // and the second generated point as the destination point to form a (C)urve command.
39
+ export const getPathCommands = (points?: Position[]): string => {
40
+ if (!points || !points.length) {
41
+ return '';
42
+ }
43
+
44
+ const d = points
45
+ .map(({ x, y }, index, array) => {
46
+ if (index === 0) {
47
+ return `M ${x} ${y}`;
48
+ }
49
+
50
+ const prevPoint = array[index - 1];
51
+ const nextPoint = array[index + 1];
52
+ if (!prevPoint || !nextPoint) {
53
+ return '';
54
+ }
55
+
56
+ const xc1 = (x + prevPoint.x) / 2;
57
+ const yc1 = (y + prevPoint.y) / 2;
58
+ const xc2 = (x + nextPoint.x) / 2;
59
+ const yc2 = (y + nextPoint.y) / 2;
60
+
61
+ return `C ${xc1} ${yc1}, ${x} ${y}, ${xc2} ${yc2}`;
62
+ })
63
+ .join(' ')
64
+ .trim();
65
+
66
+ return d;
67
+ };
68
+
69
+ export function getShape(pathGroups: PathGroup[]): Shape {
70
+ let maxX = 0;
71
+ let maxY = 0;
72
+ // Initialize mins with MAX_VALUE so that any possible
73
+ // input value of the first point can be captured by Math.min
74
+ let minX = Number.MAX_VALUE;
75
+ let minY = Number.MAX_VALUE;
76
+
77
+ pathGroups.forEach(({ paths }) => {
78
+ paths.forEach(({ points }) => {
79
+ points.forEach(({ x, y }) => {
80
+ maxX = Math.max(maxX, x);
81
+ maxY = Math.max(maxY, y);
82
+ minX = Math.min(minX, x);
83
+ minY = Math.min(minY, y);
84
+ });
85
+ });
86
+ });
87
+
88
+ return {
89
+ height: Math.max(0, maxY - minY),
90
+ width: Math.max(0, maxX - minX),
91
+ x: minX === Number.MAX_VALUE ? 0 : minX,
92
+ y: minY === Number.MAX_VALUE ? 0 : minY,
93
+ };
94
+ }
95
+
96
+ export function isDrawing(annotation: Annotation): annotation is AnnotationDrawing {
97
+ return annotation?.target?.type === 'drawing';
98
+ }
99
+
100
+ export function centerDrawing(pathGroups: PathGroup[]): Position {
101
+ return getCenter(getShape(pathGroups));
102
+ }
103
+
104
+ export function isVectorEffectSupported(): boolean {
105
+ return 'vector-effect' in document.body.style;
106
+ }
@@ -0,0 +1,2 @@
1
+ export * from './drawingUtil';
2
+ export { default as DrawingManager } from './DrawingManager';
@@ -0,0 +1,12 @@
1
+ .ba-HighlightAnnotations-creator {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ display: none;
6
+ width: 100%;
7
+ height: 100%;
8
+ }
9
+
10
+ .ba-HighlightAnnotations-popup {
11
+ pointer-events: auto;
12
+ }
@@ -0,0 +1,144 @@
1
+ import * as React from 'react';
2
+ import HighlightCanvas from './HighlightCanvas';
3
+ import HighlightList from './HighlightList';
4
+ import HighlightSvg from './HighlightSvg';
5
+ import HighlightTarget from './HighlightTarget';
6
+ import PopupHighlight from '../components/Popups/PopupHighlight';
7
+ import PopupHighlightError from '../components/Popups/PopupHighlightError';
8
+ import { AnnotationHighlight } from '../@types';
9
+ import { CreateArg } from './actions';
10
+ import { CreatorItemHighlight, CreatorStatus, SelectionArg, SelectionItem } from '../store';
11
+ import { getBoundingRect, getShapeRelativeToContainer } from './highlightUtil';
12
+ import './HighlightAnnotations.scss';
13
+
14
+ type Props = {
15
+ activeAnnotationId: string | null;
16
+ annotations: AnnotationHighlight[];
17
+ createHighlight?: (arg: CreateArg) => void;
18
+ isCreating: boolean;
19
+ isPending: boolean;
20
+ isPromoting: boolean;
21
+ isSelecting: boolean;
22
+ location: number;
23
+ selection: SelectionItem | null;
24
+ setActiveAnnotationId: (annotationId: string | null) => void;
25
+ setIsPromoting: (isPromoting: boolean) => void;
26
+ setReferenceId: (uuid: string) => void;
27
+ setSelection: (selection: SelectionArg | null) => void;
28
+ setStaged: (staged: CreatorItemHighlight | null) => void;
29
+ setStatus: (status: CreatorStatus) => void;
30
+ staged?: CreatorItemHighlight | null;
31
+ };
32
+
33
+ const HighlightAnnotations = (props: Props): JSX.Element => {
34
+ const {
35
+ activeAnnotationId,
36
+ annotations = [],
37
+ isCreating = false,
38
+ isPending = false,
39
+ isPromoting = false,
40
+ isSelecting = false,
41
+ selection,
42
+ setActiveAnnotationId,
43
+ setIsPromoting,
44
+ setReferenceId,
45
+ setSelection,
46
+ setStaged,
47
+ setStatus,
48
+ staged,
49
+ } = props;
50
+ const canCreate = isCreating || isPromoting;
51
+
52
+ const handleAnnotationActive = (annotationId: string | null): void => {
53
+ setActiveAnnotationId(annotationId);
54
+ };
55
+
56
+ const stageSelection = React.useCallback((): void => {
57
+ if (!selection) {
58
+ return;
59
+ }
60
+
61
+ const { containerRect, location, rects } = selection;
62
+
63
+ setStaged({
64
+ location,
65
+ shapes: rects.map(rect => ({
66
+ ...getShapeRelativeToContainer(rect, containerRect),
67
+ type: 'rect',
68
+ })),
69
+ });
70
+ setStatus(CreatorStatus.staged);
71
+ }, [selection, setStaged, setStatus]);
72
+
73
+ const handleCancel = (): void => {
74
+ setSelection(null);
75
+ };
76
+
77
+ const handlePromote = (): void => {
78
+ stageSelection();
79
+ setIsPromoting(true);
80
+ };
81
+
82
+ const handleStagedMount = (uuid: string): void => {
83
+ setReferenceId(uuid);
84
+ };
85
+
86
+ React.useEffect(() => {
87
+ if (!isSelecting || isPending) {
88
+ return;
89
+ }
90
+
91
+ setStaged(null);
92
+ setStatus(CreatorStatus.init);
93
+ }, [isSelecting, isPending, setStaged, setStatus]);
94
+
95
+ React.useEffect(() => {
96
+ if (!isCreating || !selection || selection.hasError || isPending) {
97
+ return;
98
+ }
99
+
100
+ stageSelection();
101
+ }, [isCreating, isPending, selection, stageSelection]);
102
+
103
+ return (
104
+ <>
105
+ {/* Layer 1: Saved annotations */}
106
+ <HighlightList activeId={activeAnnotationId} annotations={annotations} onSelect={handleAnnotationActive} />
107
+
108
+ {/* Layer 2: Staged (unsaved) highlight target, if any */}
109
+ {canCreate && staged && (
110
+ <div className="ba-HighlightAnnotations-target">
111
+ <HighlightCanvas shapes={staged.shapes} />
112
+ <HighlightSvg>
113
+ <HighlightTarget
114
+ annotationId="staged"
115
+ isActive
116
+ onMount={handleStagedMount}
117
+ shapes={staged.shapes}
118
+ />
119
+ </HighlightSvg>
120
+ </div>
121
+ )}
122
+
123
+ {/* Layer 3a: Annotations promoter to promote selection to staged */}
124
+ {!isCreating && selection && !selection.hasError && (
125
+ <div className="ba-HighlightAnnotations-popup">
126
+ <PopupHighlight
127
+ onCancel={handleCancel}
128
+ onSubmit={handlePromote}
129
+ shape={getBoundingRect(selection.rects)}
130
+ />
131
+ </div>
132
+ )}
133
+
134
+ {/* Layer 3b: Highlight error popup */}
135
+ {selection && selection.hasError && (
136
+ <div className="ba-HighlightAnnotations-popup">
137
+ <PopupHighlightError onCancel={handleCancel} shape={getBoundingRect(selection.rects)} />
138
+ </div>
139
+ )}
140
+ </>
141
+ );
142
+ };
143
+
144
+ export default HighlightAnnotations;
@@ -0,0 +1,9 @@
1
+ .ba-HighlightCanvas {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ mix-blend-mode: multiply;
8
+ pointer-events: none;
9
+ }
@@ -0,0 +1,109 @@
1
+ import * as React from 'react';
2
+ import { bdlYellow, black, white } from 'box-ui-elements/es/styles/variables';
3
+ import { Rect } from '../@types';
4
+ import './HighlightCanvas.scss';
5
+
6
+ export type CanvasShape = Rect & { isActive?: boolean; isHover?: boolean };
7
+
8
+ export type Props = {
9
+ shapes: CanvasShape[] | CanvasShape;
10
+ };
11
+
12
+ export default class HighlightCanvas extends React.PureComponent<Props> {
13
+ static defaultProps = {
14
+ shapes: [],
15
+ };
16
+
17
+ canvasRef: React.RefObject<HTMLCanvasElement> = React.createRef();
18
+
19
+ componentDidMount(): void {
20
+ this.scaleCanvas();
21
+ this.renderRects();
22
+ }
23
+
24
+ componentDidUpdate(): void {
25
+ this.clearCanvas();
26
+ this.renderRects();
27
+ }
28
+
29
+ getContext(): CanvasRenderingContext2D | null {
30
+ const { current: canvasRef } = this.canvasRef;
31
+ return canvasRef?.getContext('2d') ?? null;
32
+ }
33
+
34
+ scaleCanvas(): void {
35
+ const { current: canvasRef } = this.canvasRef;
36
+
37
+ if (!canvasRef) {
38
+ return;
39
+ }
40
+
41
+ canvasRef.width = canvasRef.offsetWidth;
42
+ canvasRef.height = canvasRef.offsetHeight;
43
+ }
44
+
45
+ clearCanvas(): void {
46
+ const { current: canvasRef } = this.canvasRef;
47
+ const context = canvasRef && canvasRef.getContext('2d');
48
+
49
+ if (!canvasRef || !context) {
50
+ return;
51
+ }
52
+
53
+ context.clearRect(0, 0, canvasRef.width, canvasRef.height);
54
+ }
55
+
56
+ renderRects(): void {
57
+ const { shapes } = this.props;
58
+ const { current: canvasRef } = this.canvasRef;
59
+ const context = canvasRef && canvasRef.getContext('2d');
60
+ const canvasHeight = canvasRef?.height ?? 0;
61
+ const canvasWidth = canvasRef?.width ?? 0;
62
+ const shapesArray = Array.isArray(shapes) ? shapes : [shapes];
63
+
64
+ if (!context) {
65
+ return;
66
+ }
67
+
68
+ shapesArray.forEach(rect => {
69
+ const { height, isActive, isHover, width, x, y } = rect;
70
+ const rectHeight = (height / 100) * canvasHeight;
71
+ const rectWidth = (width / 100) * canvasWidth;
72
+ const x1 = (x / 100) * canvasWidth;
73
+ const y1 = (y / 100) * canvasHeight;
74
+
75
+ context.save();
76
+
77
+ // Draw the highlight rect
78
+ context.fillStyle = bdlYellow;
79
+ context.globalAlpha = isActive || isHover ? 0.66 : 0.33;
80
+ context.fillRect(x1, y1, rectWidth, rectHeight);
81
+ context.restore();
82
+ context.save();
83
+
84
+ // Draw the white border
85
+ context.strokeStyle = white;
86
+ context.lineWidth = 1;
87
+ context.strokeRect(x1, y1, rectWidth, rectHeight);
88
+
89
+ // If annotation is active, apply a shadow
90
+ if (isActive) {
91
+ const imgData = context.getImageData(x1, y1, rectWidth, rectHeight);
92
+
93
+ context.save();
94
+ context.shadowColor = black;
95
+ context.shadowBlur = 10;
96
+
97
+ context.strokeRect(x1, y1, rectWidth, rectHeight);
98
+ context.putImageData(imgData, x1, y1);
99
+ context.restore();
100
+ }
101
+
102
+ context.restore();
103
+ });
104
+ }
105
+
106
+ render(): JSX.Element {
107
+ return <canvas ref={this.canvasRef} className="ba-HighlightCanvas" />;
108
+ }
109
+ }
@@ -0,0 +1,64 @@
1
+ import { connect } from 'react-redux';
2
+ import HighlightAnnotations from './HighlightAnnotations';
3
+ import withProviders from '../common/withProviders';
4
+ import { AnnotationHighlight } from '../@types';
5
+ import {
6
+ AppState,
7
+ CreatorItemHighlight,
8
+ CreatorStatus,
9
+ getActiveAnnotationId,
10
+ getAnnotationMode,
11
+ getAnnotationsForLocation,
12
+ getCreatorStagedForLocation,
13
+ getCreatorStatus,
14
+ getIsPromoting,
15
+ getIsSelecting,
16
+ getSelectionForLocation,
17
+ isCreatorStagedHighlight,
18
+ Mode,
19
+ SelectionItem,
20
+ setActiveAnnotationIdAction,
21
+ setIsPromotingAction,
22
+ setReferenceIdAction,
23
+ setSelectionAction,
24
+ setStagedAction,
25
+ setStatusAction,
26
+ } from '../store';
27
+ import { isHighlight } from './highlightUtil';
28
+
29
+ export type Props = {
30
+ activeAnnotationId: string | null;
31
+ annotations: AnnotationHighlight[];
32
+ isCreating: boolean;
33
+ isPending: boolean;
34
+ isPromoting: boolean;
35
+ isSelecting: boolean;
36
+ selection: SelectionItem | null;
37
+ staged: CreatorItemHighlight | null;
38
+ };
39
+
40
+ export const mapStateToProps = (state: AppState, { location }: { location: number }): Props => {
41
+ const staged = getCreatorStagedForLocation(state, location);
42
+
43
+ return {
44
+ activeAnnotationId: getActiveAnnotationId(state),
45
+ annotations: getAnnotationsForLocation(state, location).filter(isHighlight),
46
+ isCreating: getAnnotationMode(state) === Mode.HIGHLIGHT,
47
+ isPending: getCreatorStatus(state) === CreatorStatus.pending,
48
+ isPromoting: getIsPromoting(state),
49
+ isSelecting: getIsSelecting(state),
50
+ selection: getSelectionForLocation(state, location),
51
+ staged: isCreatorStagedHighlight(staged) ? staged : null,
52
+ };
53
+ };
54
+
55
+ export const mapDispatchToProps = {
56
+ setActiveAnnotationId: setActiveAnnotationIdAction,
57
+ setIsPromoting: setIsPromotingAction,
58
+ setReferenceId: setReferenceIdAction,
59
+ setSelection: setSelectionAction,
60
+ setStaged: setStagedAction,
61
+ setStatus: setStatusAction,
62
+ };
63
+
64
+ export default connect(mapStateToProps, mapDispatchToProps)(withProviders(HighlightAnnotations));
@@ -0,0 +1,12 @@
1
+ $text_cursor_32: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAACPTkDJAAABrklEQVRYCWNgGAWjITAaAqMhQCAEfv36NeX///9/gBgO/v379/f716/TCWglSpqRkKq/f//+tjAxYfn8+TNcKS8vL8OJM2f+MDMzs8IFacUA+RTkY5D3NVRUYKHwBxQytLITq7nIDgBa/g3mEmz0169fP3z58qUfKMeG1TAkQRYkNlHMT58+/Q3w8eF8/vw5TvXiEhL8nd3daTq6uiA1hTgVAiWY8Elik3v86NFnZMvZ2NgY/AMCGOISEhgEBAXBWl6+eMFQUVrKxcjImIjNDGQxkh2ArBnETkpJYdDS1mYwMjb+P3vu3N8w+RdAR3BxcfHD+Lhoih0wY9o0huVLlzIcO3KEUVtHh+QoJVkDuk8mTpnyhZOT89/ZM2f4gEFOMFuj66c4BGxsbVnu3r37WkxMDB786Jbg41McAknx8RzmFhbKRw8fZrh69SrcLgkJCYZv3759hAvgYJDsAFk5OV5JSUkGWE64eOECAwiDAMwBwGzI0NHdDSor5uOwl3RhUIEDKwl//vz5HcTHBUgpiAi6BLkygjlg5FZGSEFO/8qIYFyNKhgNgdEQGKohAAD+bzKe7UZRtgAAAABJRU5ErkJggg==';
2
+ $text_cursor_32_2x: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAQKADAAQAAAABAAAAQAAAAABlmWCKAAADvElEQVR4Ae2ZzUsbQRTA37a1BSkVhNjEg400h2ouQiWpaFIpftGmxP4FQqs9RLykiIj0JD3YIgVvFnvrUYppUq0kSk9Wi0URcrNgtGCKFyW9JKHZvrfNVjTJmt3R3bSdBy+zO/ve7rzfvJnMzgJw4QQ4AU6AE+AEOAFOgBPgBDgBToAT4AQ4AU5AJQFRFGtQw6hJ1JOEbMi2RuVjStc8G9BJgR+/Hi6ViATWhmBkSbzHxVuNjXBwcKB4u4qKClheXSWblCAIlxSNdbp4Tqfn/LuPKTQEbthsImkBKZkhcBoZ8Ai7N4KaKqKbyYZsyackhBkAjuVt1HYa06hKc8qXbMRtWMYKZIZSdQIvLqP2oSo9RxVYZgAqnnYTbS+qsD9uehkrnKivUCMIwXLcQMv5BS1Oan3SqZT4xO8XPi4uQjqdVusu2ZtMJnA4nTA4NCReNZvvYOUbhNCGSSdqumHWSZcMCMzMCOH5ec3BU1v39vbgfSgEXo9H+B6PU9AEoZeusYguAOLxOEsbj/jSWuPF2Jg8BzBPproMgSMR5Dmx1taCr78f6urrwVJdDVtbWzA1OQkf5ubyWAN8XlmR6+3ygdZSlwxQapzL7YZAMAj3vV4wVVWBmMmA3W6HlxMT0NHVldeVhkNWaGJkEsMBrK+twbtAAAZ8PqDldEtTE7ydnpaC6u3rYwquGGfDASQSCXg6MgKR8O/FYTKZhLnZWant1222YmJgsimJOYAisGGw7tZWuGa1ghWVpLy8XCrP8sdwAJWVldJ4p//4TCYjxmIxIYVZoJcYPgSej49LC5yN9fWNZqdTuNvRAc9GR/WKHwwH0NDQIAW7EInE9vf3pePmlhbdABg+BKLRqJQBvoGB23X490cTX7XF8hMJUOfIC54zA2J4BowMD8OnpSUK8IrL5YKvm5vwwOs9HwoGhfjubt7Aab2QlR/ygdbS8Az4trMDD3t6cto/6Pfn1MkVDodDPozKB1pLXTLAbDZrbV+OH+0r0hth9sLrHAOVFboA8HZ3i+2dnVBWVqayeYfm9Dp8z+OBQChEr8M0NyyiTh1alMgRvqNLcsKeoGymtVxAx1PZENElA7J9Q1tixewbFupKmvDoNfAxKm2E5J8hC3kXqGeeBLEn6CsPjUU3qtKWF22J/dkUxQC28dxwOY0MoODbUJWClwMlG7JlnrzkG7KWzAsNzAD+ZYi1F/5qf8wA+tqrVkrmyxAzfIz8//48zkyQ34AT4AQ4AU6AE+AEOAFOgBPgBDgBToAT4AT0JvAL80N/mFc/WlQAAAAASUVORK5CYII=';
3
+ $text_cursor_32_3x: 'data:image/png;base64,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';
4
+
5
+ @mixin ba-HighlightCreator-cursor {
6
+ /* The custom cursor focus point (10, 16) is not the center of the image (16, 16) */
7
+ $offset_x: 10;
8
+ $offset_y: 16;
9
+
10
+ cursor: url($text_cursor_32) $offset_x $offset_y, text; /* Legacy */
11
+ cursor: image-set(url($text_cursor_32) 1x, url($text_cursor_32_2x) 2x, url($text_cursor_32_3x) 3x) $offset_x $offset_y, text; /* Webkit */ /* stylelint-disable-line */
12
+ }