box-annotations 5.2.1-beta.0 → 5.2.1-beta.11

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 (67) hide show
  1. package/dist/annotations.css +3 -4
  2. package/dist/annotations.js +1 -1
  3. package/dist/annotations.js.LICENSE.txt +3 -43
  4. package/i18n/en-US.js +4 -0
  5. package/i18n/en-US.properties +8 -0
  6. package/i18n/json/src/components/BoundingBoxHighlight/messages.json +1 -0
  7. package/i18n/json/src/components/Popups/messages.json +1 -1
  8. package/package.json +31 -15
  9. package/src/@types/events.ts +4 -0
  10. package/src/@types/global.ts +0 -9
  11. package/src/@types/model.ts +5 -0
  12. package/src/BoxAnnotations.ts +5 -2
  13. package/src/adapters/threadedAnnotationsAdapters.ts +155 -0
  14. package/src/api/types.ts +28 -1
  15. package/src/boundingBoxHighlight/BoundingBoxHighlightContainer.tsx +33 -0
  16. package/src/boundingBoxHighlight/BoundingBoxHighlightManager.tsx +19 -0
  17. package/src/boundingBoxHighlight/index.ts +1 -0
  18. package/src/common/BaseAnnotator.scss +2 -1
  19. package/src/common/BaseAnnotator.ts +129 -0
  20. package/src/common/DeselectListener.tsx +3 -1
  21. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightList.scss +8 -0
  22. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightList.tsx +67 -0
  23. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.scss +73 -0
  24. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.tsx +68 -0
  25. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.scss +22 -0
  26. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.tsx +77 -0
  27. package/src/components/BoundingBoxHighlight/index.ts +4 -0
  28. package/src/components/BoundingBoxHighlight/messages.ts +14 -0
  29. package/src/components/Popups/PopupReply.tsx +9 -11
  30. package/src/components/Popups/PopupV2.scss +28 -0
  31. package/src/components/Popups/PopupV2.tsx +322 -0
  32. package/src/components/Popups/messages.ts +10 -0
  33. package/src/document/DocumentAnnotator.ts +76 -8
  34. package/src/drawing/DrawingAnnotations.tsx +3 -0
  35. package/src/drawing/DrawingAnnotationsContainer.tsx +3 -0
  36. package/src/drawing/DrawingCreator.tsx +9 -16
  37. package/src/drawing/DrawingTarget.tsx +1 -0
  38. package/src/highlight/HighlightTarget.tsx +1 -0
  39. package/src/icons/ChevronLeft.tsx +24 -0
  40. package/src/icons/ChevronRight.tsx +24 -0
  41. package/src/image/ImageAnnotator.ts +48 -2
  42. package/src/media/MediaAnnotator.ts +1 -1
  43. package/src/popup/PopupContainer.tsx +14 -1
  44. package/src/popup/PopupLayer.tsx +68 -21
  45. package/src/popup/PopupManager.tsx +21 -3
  46. package/src/region/RegionAnnotation.tsx +1 -0
  47. package/src/region/RegionCreation.tsx +4 -7
  48. package/src/region/RegionCreationContainer.tsx +2 -2
  49. package/src/region/RegionCreator.tsx +9 -17
  50. package/src/store/annotations/actions.ts +78 -3
  51. package/src/store/annotations/reducer.ts +23 -1
  52. package/src/store/boundingBoxHighlights/actions.ts +6 -0
  53. package/src/store/boundingBoxHighlights/index.ts +4 -0
  54. package/src/store/boundingBoxHighlights/reducer.ts +33 -0
  55. package/src/store/boundingBoxHighlights/selectors.ts +12 -0
  56. package/src/store/boundingBoxHighlights/types.ts +13 -0
  57. package/src/store/createRootReducer.ts +2 -0
  58. package/src/store/eventing/boundingBoxHighlightNav.ts +13 -0
  59. package/src/store/eventing/middleware.ts +3 -0
  60. package/src/store/index.ts +1 -0
  61. package/src/store/options/actions.ts +2 -0
  62. package/src/store/options/reducer.ts +9 -2
  63. package/src/store/options/selectors.ts +4 -0
  64. package/src/store/options/types.ts +6 -0
  65. package/src/store/types.ts +2 -0
  66. package/src/utils/rotate.ts +43 -0
  67. package/src/utils/scroll.ts +8 -3
@@ -0,0 +1,6 @@
1
+ import { createAction } from '@reduxjs/toolkit';
2
+ import { BoundingBox } from './types';
3
+
4
+ export const setBoundingBoxHighlightsAction = createAction<BoundingBox[]>('SET_BOUNDING_BOX_HIGHLIGHTS');
5
+ export const setSelectedBoundingBoxHighlightAction = createAction<string | null>('SET_SELECTED_BOUNDING_BOX_HIGHLIGHT');
6
+ export const navigateBoundingBoxHighlightAction = createAction<string>('NAVIGATE_BOUNDING_BOX_HIGHLIGHT');
@@ -0,0 +1,4 @@
1
+ export { default as boundingBoxHighlightsReducer } from './reducer';
2
+ export * from './actions';
3
+ export * from './selectors';
4
+ export * from './types';
@@ -0,0 +1,33 @@
1
+ import { createReducer } from '@reduxjs/toolkit';
2
+ import { BoundingBoxHighlightsState } from './types';
3
+ import {
4
+ navigateBoundingBoxHighlightAction,
5
+ setBoundingBoxHighlightsAction,
6
+ setSelectedBoundingBoxHighlightAction,
7
+ } from './actions';
8
+ import { setViewModeAction } from '../options/actions';
9
+
10
+ const initialState: BoundingBoxHighlightsState = {
11
+ boundingBoxes: [],
12
+ selectedId: null,
13
+ };
14
+
15
+ export default createReducer(initialState, builder => {
16
+ builder
17
+ .addCase(setBoundingBoxHighlightsAction, (state, { payload }) => {
18
+ state.boundingBoxes = payload;
19
+ state.selectedId = null;
20
+ })
21
+ .addCase(setSelectedBoundingBoxHighlightAction, (state, { payload }) => {
22
+ state.selectedId = payload;
23
+ })
24
+ .addCase(navigateBoundingBoxHighlightAction, (state, { payload }) => {
25
+ state.selectedId = payload;
26
+ })
27
+ .addCase(setViewModeAction, (state, { payload }) => {
28
+ // Clear selected bounding box when switching to annotations mode
29
+ if (payload === 'annotations') {
30
+ state.selectedId = null;
31
+ }
32
+ });
33
+ });
@@ -0,0 +1,12 @@
1
+ import { BoundingBox, BoundingBoxHighlightsState } from './types';
2
+
3
+ type State = { boundingBoxHighlights: BoundingBoxHighlightsState };
4
+
5
+ export const getBoundingBoxHighlights = ({ boundingBoxHighlights }: State): BoundingBox[] =>
6
+ boundingBoxHighlights.boundingBoxes;
7
+
8
+ export const getBoundingBoxHighlightsForPage = ({ boundingBoxHighlights }: State, pageNumber: number): BoundingBox[] =>
9
+ boundingBoxHighlights.boundingBoxes.filter(box => box.pageNumber === pageNumber);
10
+
11
+ export const getSelectedBoundingBoxHighlightId = ({ boundingBoxHighlights }: State): string | null =>
12
+ boundingBoxHighlights.selectedId;
@@ -0,0 +1,13 @@
1
+ export interface BoundingBox {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ pageNumber: number;
7
+ id: string;
8
+ }
9
+
10
+ export interface BoundingBoxHighlightsState {
11
+ boundingBoxes: BoundingBox[];
12
+ selectedId: string | null;
13
+ }
@@ -4,6 +4,7 @@ import { commonReducer } from './common';
4
4
  import { creatorReducer } from './creator';
5
5
  import { drawingReducer } from './drawing';
6
6
  import { highlightReducer } from './highlight';
7
+ import { boundingBoxHighlightsReducer } from './boundingBoxHighlights';
7
8
  import { optionsReducer } from './options';
8
9
  import { usersReducer } from './users';
9
10
 
@@ -14,6 +15,7 @@ const createRootReducer = (): Reducer =>
14
15
  creator: creatorReducer,
15
16
  drawing: drawingReducer,
16
17
  highlight: highlightReducer,
18
+ boundingBoxHighlights: boundingBoxHighlightsReducer,
17
19
  options: optionsReducer,
18
20
  users: usersReducer,
19
21
  });
@@ -0,0 +1,13 @@
1
+ import eventManager from '../../common/EventManager';
2
+ import { AppState } from '../types';
3
+ import { Event } from '../../@types';
4
+ import { getSelectedBoundingBoxHighlightId } from '../boundingBoxHighlights';
5
+
6
+ const handleNavigateBoundingBoxHighlight = (_prevState: AppState, nextState: AppState): void => {
7
+ const highlightId = getSelectedBoundingBoxHighlightId(nextState);
8
+ if (highlightId) {
9
+ eventManager.emit(Event.BOUNDING_BOX_HIGHLIGHT_NAVIGATE, highlightId);
10
+ }
11
+ };
12
+
13
+ export { handleNavigateBoundingBoxHighlight };
@@ -6,11 +6,13 @@ import {
6
6
  setActiveAnnotationIdAction,
7
7
  setIsInitialized,
8
8
  } from '../annotations/actions';
9
+ import { navigateBoundingBoxHighlightAction } from '../boundingBoxHighlights/actions';
9
10
  import { EventHandlerMap } from './types';
10
11
  import { handleActiveAnnotationEvents } from './active';
11
12
  import { handleAnnotationsInitialized } from './init';
12
13
  import { handleCreateErrorEvents, handleCreatePendingEvents, handleCreateSuccessEvents } from './create';
13
14
  import { handleFetchErrorEvents } from './fetch';
15
+ import { handleNavigateBoundingBoxHighlight } from './boundingBoxHighlightNav';
14
16
  import { handleResetCreatorAction, handleSetStagedAction } from './staged';
15
17
  import { handleSetStatusAction } from './status';
16
18
  import { handleToggleAnnotationModeAction } from './mode';
@@ -23,6 +25,7 @@ const eventHandlers: EventHandlerMap = {
23
25
  [createAnnotationAction.pending.toString()]: handleCreatePendingEvents,
24
26
  [createAnnotationAction.rejected.toString()]: handleCreateErrorEvents,
25
27
  [fetchAnnotationsAction.rejected.toString()]: handleFetchErrorEvents,
28
+ [navigateBoundingBoxHighlightAction.toString()]: handleNavigateBoundingBoxHighlight,
26
29
  [resetCreatorAction.toString()]: handleResetCreatorAction,
27
30
  [setActiveAnnotationIdAction.toString()]: handleActiveAnnotationEvents,
28
31
  [setIsInitialized.toString()]: handleAnnotationsInitialized,
@@ -5,6 +5,7 @@ export * from './annotations';
5
5
  export * from './common';
6
6
  export * from './creator';
7
7
  export * from './highlight';
8
+ export * from './boundingBoxHighlights';
8
9
  export * from './options';
9
10
  export * from './types';
10
11
  export * from './users';
@@ -1,8 +1,10 @@
1
1
  import { createAction } from '@reduxjs/toolkit';
2
2
  import { Permissions } from '../../@types';
3
+ import { ViewMode } from './types';
3
4
 
4
5
  export const setFileIdAction = createAction<string | null>('SET_FIlE_ID');
5
6
  export const setFileVersionIdAction = createAction<string | null>('SET_FILE_VERSION_ID');
6
7
  export const setPermissionsAction = createAction<Permissions>('SET_PERMISSIONS');
7
8
  export const setRotationAction = createAction<number>('SET_ROTATION');
8
9
  export const setScaleAction = createAction<number>('SET_SCALE');
10
+ export const setViewModeAction = createAction<ViewMode>('SET_VIEW_MODE');
@@ -1,14 +1,16 @@
1
1
  import { createReducer } from '@reduxjs/toolkit';
2
- import { OptionsState } from './types';
2
+ import { OptionsState, ViewMode } from './types';
3
3
  import {
4
4
  setFileIdAction,
5
5
  setFileVersionIdAction,
6
6
  setPermissionsAction,
7
7
  setRotationAction,
8
8
  setScaleAction,
9
+ setViewModeAction,
9
10
  } from './actions';
10
11
 
11
- export const initialState = {
12
+ export const initialState: OptionsState = {
13
+ apiHost: '',
12
14
  features: {},
13
15
  fileId: null,
14
16
  fileVersionId: null,
@@ -16,6 +18,8 @@ export const initialState = {
16
18
  permissions: {},
17
19
  rotation: 0,
18
20
  scale: 1,
21
+ token: '',
22
+ viewMode: 'annotations' as ViewMode,
19
23
  };
20
24
 
21
25
  export default createReducer<OptionsState>(initialState, builder =>
@@ -34,5 +38,8 @@ export default createReducer<OptionsState>(initialState, builder =>
34
38
  })
35
39
  .addCase(setScaleAction, (state, { payload }) => {
36
40
  state.scale = payload;
41
+ })
42
+ .addCase(setViewModeAction, (state, { payload }) => {
43
+ state.viewMode = payload;
37
44
  }),
38
45
  );
@@ -2,15 +2,19 @@ import getProp from 'lodash/get';
2
2
  import { AppState } from '../types';
3
3
  import { Permissions } from '../../@types';
4
4
  import { Features } from '../../BoxAnnotations';
5
+ import { ViewMode } from './types';
5
6
 
6
7
  type State = Pick<AppState, 'options'>;
7
8
 
9
+ export const getApiHost = (state: State): string => state.options.apiHost;
8
10
  export const getFeatures = (state: State): Features => state.options.features;
11
+ export const getViewMode = (state: State): ViewMode => state.options.viewMode;
9
12
  export const getFileId = (state: State): string | null => state.options.fileId;
10
13
  export const getFileVersionId = (state: State): string | null => state.options.fileVersionId;
11
14
  export const getIsCurrentFileVersion = (state: State): boolean => state.options.isCurrentFileVersion;
12
15
  export const getPermissions = (state: State): Permissions => state.options.permissions;
13
16
  export const getRotation = (state: State): number => state.options.rotation;
14
17
  export const getScale = (state: State): number => state.options.scale;
18
+ export const getToken = (state: State): string => state.options.token;
15
19
  export const isFeatureEnabled = (state: State, featurename: string): boolean =>
16
20
  getProp(getFeatures(state), featurename, false);
@@ -1,7 +1,11 @@
1
1
  import { Features } from '../../BoxAnnotations';
2
2
  import { Permissions } from '../../@types';
3
3
 
4
+ /** View mode: annotations and bounding boxes are mutually exclusive. */
5
+ export type ViewMode = 'annotations' | 'boundingBoxes';
6
+
4
7
  export type OptionsState = {
8
+ apiHost: string;
5
9
  features: Features;
6
10
  fileId: string | null;
7
11
  fileVersionId: string | null;
@@ -9,4 +13,6 @@ export type OptionsState = {
9
13
  permissions: Permissions;
10
14
  rotation: number;
11
15
  scale: number;
16
+ token: string;
17
+ viewMode: ViewMode;
12
18
  };
@@ -6,6 +6,7 @@ import { CommonState } from './common';
6
6
  import { CreatorState } from './creator';
7
7
  import { DrawingState } from './drawing';
8
8
  import { HighlightState } from './highlight';
9
+ import { BoundingBoxHighlightsState } from './boundingBoxHighlights';
9
10
  import { OptionsState } from './options';
10
11
  import { UsersState } from './users';
11
12
 
@@ -15,6 +16,7 @@ export type AppState = {
15
16
  creator: CreatorState;
16
17
  drawing: DrawingState;
17
18
  highlight: HighlightState;
19
+ boundingBoxHighlights: BoundingBoxHighlightsState;
18
20
  options: OptionsState;
19
21
  users: UsersState;
20
22
  };
@@ -129,3 +129,46 @@ export function getRotatedPosition({ x, y }: Position, rotation: number): Positi
129
129
  const { x: rotatedX, y: rotatedY } = getRotatedShape({ height: 0, width: 0, x, y }, rotation);
130
130
  return { x: rotatedX, y: rotatedY };
131
131
  }
132
+
133
+ /**
134
+ * Converts a mouse position (in browser window coordinates) to a position relative to
135
+ * a CSS-rotated element's own top-left corner.
136
+ *
137
+ * When an element has a CSS rotation, its internal axes are rotated with it.
138
+ * This function maps window coordinates to element-local coordinates.
139
+ *
140
+ * If element is null, returns the raw coordinates as a passthrough.
141
+ *
142
+ * Assumes transform-origin: center (the CSS default), which matches how
143
+ * box-content-preview applies rotation to annotation layers.
144
+ */
145
+ export function getElementLocalPosition(
146
+ clientX: number,
147
+ clientY: number,
148
+ element: HTMLElement | null,
149
+ rotation: number,
150
+ ): [number, number] {
151
+ if (!element) {
152
+ return [clientX, clientY];
153
+ }
154
+
155
+ const rect = element.getBoundingClientRect();
156
+
157
+ if (!rotation) {
158
+ return [clientX - rect.left, clientY - rect.top];
159
+ }
160
+
161
+ // 1. Find the element's center (rotation doesn't move the center)
162
+ const centerX = rect.left + rect.width / 2;
163
+ const centerY = rect.top + rect.height / 2;
164
+
165
+ // 2. Get the mouse position relative to the center (in browser window coordinates)
166
+ const relX = clientX - centerX;
167
+ const relY = clientY - centerY;
168
+
169
+ // 3. Apply inverse rotation to obtain the mouse position from center in element-local coordinates
170
+ const local = rotatePoint({ x: relX, y: relY }, -rotation);
171
+
172
+ // 4. Convert coordinates from center-relative to top-left-relative
173
+ return [local.x + element.offsetWidth / 2, local.y + element.offsetHeight / 2];
174
+ }
@@ -2,13 +2,14 @@ import { Position } from '../@types';
2
2
 
3
3
  export type ScrollOptions = {
4
4
  offsets?: Position;
5
+ smooth?: boolean;
5
6
  threshold?: number;
6
7
  };
7
8
 
8
9
  const DEFAULT_OFFSETS = { x: 0, y: 0 };
9
10
 
10
11
  export function scrollToLocation(parentEl: HTMLElement, referenceEl: HTMLElement, options: ScrollOptions = {}): void {
11
- const { offsets = DEFAULT_OFFSETS } = options;
12
+ const { offsets = DEFAULT_OFFSETS, smooth = false } = options;
12
13
  const { x: offsetXPercentage, y: offsetYPercentage } = offsets;
13
14
  // Get the bounding client rects so that the offsetLeft of the reference element can be calculated --
14
15
  // even if it has been transformed via rotation
@@ -40,6 +41,10 @@ export function scrollToLocation(parentEl: HTMLElement, referenceEl: HTMLElement
40
41
  const scrollLeft = Math.max(0, Math.min(parentEl.scrollLeft + offsetScrollLeft, parentEl.scrollWidth));
41
42
  const scrollTop = Math.max(0, Math.min(parentEl.scrollTop + offsetScrollTop, parentEl.scrollHeight));
42
43
 
43
- parentEl.scrollLeft = scrollLeft;
44
- parentEl.scrollTop = scrollTop;
44
+ if (smooth) {
45
+ parentEl.scrollTo({ left: scrollLeft, top: scrollTop, behavior: 'smooth' });
46
+ } else {
47
+ parentEl.scrollLeft = scrollLeft;
48
+ parentEl.scrollTop = scrollTop;
49
+ }
45
50
  }