box-annotations 5.2.1-beta.4 → 5.2.1-beta.6

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "box-annotations",
3
- "version": "5.2.1-beta.4",
3
+ "version": "5.2.1-beta.6",
4
4
  "description": "Box Annotations",
5
5
  "author": "Box (https://www.box.com/)",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -7,9 +7,10 @@ import EventEmitter from './EventEmitter';
7
7
  import i18n from '../utils/i18n';
8
8
  import messages from '../messages';
9
9
  import { Event, IntlOptions, LegacyEvent, Permissions } from '../@types';
10
- import { BoundingBox } from '../store/boundingBoxHighlights/types';
10
+ import { BoundingBox, getBoundingBoxHighlights } from '../store/boundingBoxHighlights';
11
11
  import { ViewMode } from '../store/options/types';
12
12
  import { Features } from '../BoxAnnotations';
13
+ import { scrollToLocation } from '../utils/scroll';
13
14
  import './BaseAnnotator.scss';
14
15
 
15
16
  export type Container = string | HTMLElement;
@@ -185,9 +186,43 @@ export default class BaseAnnotator extends EventEmitter {
185
186
  // Called by box-content-preview
186
187
  }
187
188
 
188
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
189
189
  public scrollToBoundingBoxHighlight(highlightId: string | null): void {
190
- // Implemented in DocumentAnnotator
190
+ if (!highlightId || !this.annotatedEl) {
191
+ return;
192
+ }
193
+
194
+ const highlights = getBoundingBoxHighlights(this.store.getState());
195
+ const highlight = highlights.find((h: BoundingBox) => h.id === highlightId);
196
+
197
+ if (!highlight) {
198
+ return;
199
+ }
200
+
201
+ const referenceEl = this.getScrollReferenceForHighlight(highlight);
202
+ if (!referenceEl) {
203
+ return;
204
+ }
205
+
206
+ const offsets = {
207
+ x: highlight.x + highlight.width / 2,
208
+ y: highlight.y + highlight.height / 2,
209
+ };
210
+
211
+ scrollToLocation(this.annotatedEl, referenceEl, {
212
+ offsets: this.getAdjustedScrollOffsets(offsets),
213
+ smooth: true,
214
+ });
215
+ }
216
+
217
+ /** Returns the element to scroll relative to for the given bounding box highlight. */
218
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
219
+ protected getScrollReferenceForHighlight(_highlight: BoundingBox): HTMLElement | null | undefined {
220
+ return undefined; // Must be implemented in child class
221
+ }
222
+
223
+ /** Adjusts scroll offsets before passing to scrollToLocation (e.g. to apply rotation). */
224
+ protected getAdjustedScrollOffsets(offsets: { x: number; y: number }): { x: number; y: number } {
225
+ return offsets;
191
226
  }
192
227
 
193
228
  /**
@@ -8,8 +8,8 @@ import './BoundingBoxHighlightNav.scss';
8
8
  type Props = {
9
9
  currentIndex: number;
10
10
  total: number;
11
- onPrev: (event: React.MouseEvent) => void;
12
- onNext: (event: React.MouseEvent) => void;
11
+ onPrev: (event: React.MouseEvent | KeyboardEvent) => void;
12
+ onNext: (event: React.MouseEvent | KeyboardEvent) => void;
13
13
  };
14
14
 
15
15
  const BoundingBoxHighlightNav = ({ currentIndex, total, onPrev, onNext }: Props): JSX.Element => {
@@ -17,6 +17,25 @@ const BoundingBoxHighlightNav = ({ currentIndex, total, onPrev, onNext }: Props)
17
17
  const isPrevDisabled = currentIndex === 0;
18
18
  const isNextDisabled = currentIndex === total - 1;
19
19
 
20
+ React.useEffect(() => {
21
+ const handleKeyDown = (event: KeyboardEvent): void => {
22
+ if (event.key === 'ArrowLeft') {
23
+ event.stopPropagation();
24
+ if (!isPrevDisabled) {
25
+ onPrev(event);
26
+ }
27
+ } else if (event.key === 'ArrowRight') {
28
+ event.stopPropagation();
29
+ if (!isNextDisabled) {
30
+ onNext(event);
31
+ }
32
+ }
33
+ };
34
+
35
+ document.addEventListener('keydown', handleKeyDown, { capture: true });
36
+ return () => document.removeEventListener('keydown', handleKeyDown, { capture: true });
37
+ }, [isPrevDisabled, isNextDisabled, onPrev, onNext]);
38
+
20
39
  return (
21
40
  <div className="ba-BoundingBoxHighlightNav" data-testid="ba-BoundingBoxHighlightNav">
22
41
  <button
@@ -40,14 +40,14 @@ const BoundingBoxHighlightRect = ({
40
40
  onSelect?.(id);
41
41
  };
42
42
 
43
- const handlePrev = (event: React.MouseEvent): void => {
43
+ const handlePrev = (event: React.MouseEvent | KeyboardEvent): void => {
44
44
  event.stopPropagation();
45
45
  if (prevId) {
46
46
  onNavigate?.(prevId);
47
47
  }
48
48
  };
49
49
 
50
- const handleNext = (event: React.MouseEvent): void => {
50
+ const handleNext = (event: React.MouseEvent | KeyboardEvent): void => {
51
51
  event.stopPropagation();
52
52
  if (nextId) {
53
53
  onNavigate?.(nextId);
@@ -12,7 +12,7 @@ import { BoundingBoxHighlightManager } from '../boundingBoxHighlight';
12
12
  import { centerRegion, isRegion, RegionCreationManager, RegionManager } from '../region';
13
13
  import { Event } from '../@types';
14
14
  import { getAnnotation } from '../store/annotations';
15
- import { getBoundingBoxHighlights, BoundingBox } from '../store/boundingBoxHighlights';
15
+ import { BoundingBox } from '../store/boundingBoxHighlights';
16
16
  import { getSelection } from './docUtil';
17
17
  import { Manager } from '../common/BaseManager';
18
18
  import { getFileId, getIsCurrentFileVersion, getViewMode, Mode } from '../store';
@@ -235,28 +235,7 @@ export default class DocumentAnnotator extends BaseAnnotator {
235
235
  }
236
236
  }
237
237
 
238
- scrollToBoundingBoxHighlight(highlightId: string | null): void {
239
- if (!highlightId || !this.annotatedEl) {
240
- return;
241
- }
242
-
243
- const highlights = getBoundingBoxHighlights(this.store.getState());
244
- const highlight = highlights.find((h: BoundingBox) => h.id === highlightId);
245
-
246
- if (!highlight) {
247
- return;
248
- }
249
-
250
- const pageEl = this.getPage(highlight.pageNumber);
251
- if (!pageEl) {
252
- return;
253
- }
254
-
255
- const offsets = {
256
- x: highlight.x + highlight.width / 2,
257
- y: highlight.y + highlight.height / 2,
258
- };
259
-
260
- scrollToLocation(this.annotatedEl, pageEl, { offsets, smooth: true });
238
+ protected getScrollReferenceForHighlight(highlight: BoundingBox): HTMLElement | undefined {
239
+ return this.getPage(highlight.pageNumber);
261
240
  }
262
241
  }
@@ -1,15 +1,18 @@
1
1
  import { Unsubscribe } from 'redux';
2
2
  import BaseAnnotator, { Options } from '../common/BaseAnnotator';
3
3
  import PopupManager from '../popup/PopupManager';
4
+ import { BoundingBoxHighlightManager } from '../boundingBoxHighlight';
4
5
  import { centerDrawing, DrawingManager, isDrawing } from '../drawing';
5
6
  import { centerRegion, isRegion, RegionCreationManager, RegionManager } from '../region';
6
7
  import { CreatorStatus, getCreatorStatus } from '../store/creator';
7
- import { getAnnotation, getFileId, getIsCurrentFileVersion, getRotation } from '../store';
8
+ import { getAnnotation, getFileId, getIsCurrentFileVersion, getRotation, getViewMode } from '../store';
8
9
  import { getRotatedPosition } from '../utils/rotate';
9
10
  import { Manager } from '../common/BaseManager';
10
11
  import { scrollToLocation } from '../utils/scroll';
11
12
  import './ImageAnnotator.scss';
12
13
 
14
+ import type { ViewMode } from '../store/options/types';
15
+
13
16
  export const CSS_IS_DRAWING_CLASS = 'ba-is-drawing';
14
17
 
15
18
  export default class ImageAnnotator extends BaseAnnotator {
@@ -17,6 +20,9 @@ export default class ImageAnnotator extends BaseAnnotator {
17
20
 
18
21
  managers: Set<Manager> = new Set();
19
22
 
23
+ /** Tracks which view mode the current managers were created for. Used to destroy/recreate when switching. */
24
+ private managersViewMode: ViewMode | null = null;
25
+
20
26
  storeHandler?: Unsubscribe;
21
27
 
22
28
  constructor(options: Options) {
@@ -41,10 +47,18 @@ export default class ImageAnnotator extends BaseAnnotator {
41
47
  return this.containerEl?.querySelector('.bp-image');
42
48
  }
43
49
 
50
+ /** Destroys all managers and clears the cache. Call when switching view modes. */
51
+ private clearManagers(): void {
52
+ this.managers.forEach(manager => manager.destroy());
53
+ this.managers.clear();
54
+ this.managersViewMode = null;
55
+ }
56
+
44
57
  getManagers(parentEl: HTMLElement, referenceEl: HTMLElement): Set<Manager> {
45
58
  const fileId = getFileId(this.store.getState());
46
59
  const isCurrentFileVersion = getIsCurrentFileVersion(this.store.getState());
47
60
  const resinTags = { fileid: fileId, iscurrent: isCurrentFileVersion };
61
+ const viewMode = getViewMode(this.store.getState());
48
62
 
49
63
  this.managers.forEach(manager => {
50
64
  if (!manager.exists(parentEl)) {
@@ -54,6 +68,11 @@ export default class ImageAnnotator extends BaseAnnotator {
54
68
  });
55
69
 
56
70
  if (this.managers.size === 0) {
71
+ if (viewMode === 'boundingBoxes') {
72
+ this.managers.add(new BoundingBoxHighlightManager({ location: 1, referenceEl, resinTags }));
73
+ return this.managers;
74
+ }
75
+
57
76
  this.managers.add(new PopupManager({ referenceEl, resinTags }));
58
77
  this.managers.add(new DrawingManager({ referenceEl, resinTags }));
59
78
  this.managers.add(new RegionManager({ referenceEl, resinTags }));
@@ -84,11 +103,17 @@ export default class ImageAnnotator extends BaseAnnotator {
84
103
  render(): void {
85
104
  const referenceEl = this.getReference();
86
105
  const rotation = getRotation(this.store.getState()) || 0;
106
+ const viewMode = getViewMode(this.store.getState());
87
107
 
88
108
  if (!this.annotatedEl || !referenceEl) {
89
109
  return;
90
110
  }
91
111
 
112
+ if (this.managersViewMode !== null && this.managersViewMode !== viewMode) {
113
+ this.clearManagers();
114
+ }
115
+ this.managersViewMode = viewMode;
116
+
92
117
  this.getManagers(this.annotatedEl, referenceEl).forEach(manager => {
93
118
  manager.style({
94
119
  height: `${referenceEl.offsetHeight}px`,
@@ -107,6 +132,27 @@ export default class ImageAnnotator extends BaseAnnotator {
107
132
  this.postRender();
108
133
  }
109
134
 
135
+ public postRender(): void {
136
+ // DeselectManager is only needed for annotation creation; skip in bounding box mode.
137
+ if (getViewMode(this.store.getState()) === 'boundingBoxes') {
138
+ if (this.deselectManager) {
139
+ this.deselectManager.destroy();
140
+ this.deselectManager = null;
141
+ }
142
+ return;
143
+ }
144
+ super.postRender();
145
+ }
146
+
147
+ protected getScrollReferenceForHighlight(): HTMLElement | null | undefined {
148
+ return this.getReference();
149
+ }
150
+
151
+ protected getAdjustedScrollOffsets(offsets: { x: number; y: number }): { x: number; y: number } {
152
+ const rotation = getRotation(this.store.getState()) || 0;
153
+ return getRotatedPosition(offsets, rotation);
154
+ }
155
+
110
156
  scrollToAnnotation(annotationId: string | null): void {
111
157
  if (!annotationId) {
112
158
  return;