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/dist/annotations.css +1 -1
- package/dist/annotations.js +1 -1
- package/package.json +1 -1
- package/src/common/BaseAnnotator.ts +38 -3
- package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.tsx +21 -2
- package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.tsx +2 -2
- package/src/document/DocumentAnnotator.ts +3 -24
- package/src/image/ImageAnnotator.ts +47 -1
package/package.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
239
|
-
|
|
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;
|