box-annotations 5.2.1-beta.1 → 5.2.1-beta.4
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/i18n/en-US.js +2 -0
- package/i18n/en-US.properties +4 -0
- package/i18n/json/src/components/BoundingBoxHighlight/messages.json +1 -0
- package/package.json +1 -1
- package/src/@types/events.ts +4 -0
- package/src/boundingBoxHighlight/BoundingBoxHighlightContainer.tsx +33 -0
- package/src/boundingBoxHighlight/BoundingBoxHighlightManager.tsx +19 -0
- package/src/boundingBoxHighlight/index.ts +1 -0
- package/src/common/BaseAnnotator.ts +78 -0
- package/src/components/BoundingBoxHighlight/BoundingBoxHighlightList.scss +8 -0
- package/src/components/BoundingBoxHighlight/BoundingBoxHighlightList.tsx +67 -0
- package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.scss +73 -0
- package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.tsx +49 -0
- package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.scss +22 -0
- package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.tsx +77 -0
- package/src/components/BoundingBoxHighlight/index.ts +4 -0
- package/src/components/BoundingBoxHighlight/messages.ts +14 -0
- package/src/document/DocumentAnnotator.ts +69 -4
- package/src/icons/ChevronLeft.tsx +24 -0
- package/src/icons/ChevronRight.tsx +24 -0
- package/src/store/annotations/reducer.ts +6 -1
- package/src/store/boundingBoxHighlights/actions.ts +6 -0
- package/src/store/boundingBoxHighlights/index.ts +4 -0
- package/src/store/boundingBoxHighlights/reducer.ts +33 -0
- package/src/store/boundingBoxHighlights/selectors.ts +12 -0
- package/src/store/boundingBoxHighlights/types.ts +13 -0
- package/src/store/createRootReducer.ts +2 -0
- package/src/store/eventing/boundingBoxHighlightNav.ts +13 -0
- package/src/store/eventing/middleware.ts +3 -0
- package/src/store/index.ts +1 -0
- package/src/store/options/actions.ts +2 -0
- package/src/store/options/reducer.ts +6 -1
- package/src/store/options/selectors.ts +2 -0
- package/src/store/options/types.ts +4 -0
- package/src/store/types.ts +2 -0
- package/src/utils/scroll.ts +8 -3
package/i18n/en-US.js
CHANGED
|
@@ -4,6 +4,8 @@ export default {
|
|
|
4
4
|
"ba.annotationsLoadError": "We’re sorry, the annotations failed to load for this file.",
|
|
5
5
|
"ba.annotationsPost": "Post",
|
|
6
6
|
"ba.annotationsSave": "Save",
|
|
7
|
+
"ba.boundingBoxHighlight.viewNextReference": "View next reference",
|
|
8
|
+
"ba.boundingBoxHighlight.viewPrevReference": "View previous reference",
|
|
7
9
|
"ba.popup.reply.comment": "Comment",
|
|
8
10
|
"ba.popup.reply.field": "Type a comment",
|
|
9
11
|
"ba.popups.cancel": "Cancel",
|
package/i18n/en-US.properties
CHANGED
|
@@ -8,6 +8,10 @@ ba.annotationsLoadError = We’re sorry, the annotations failed to load for this
|
|
|
8
8
|
ba.annotationsPost = Post
|
|
9
9
|
# Label for the save button
|
|
10
10
|
ba.annotationsSave = Save
|
|
11
|
+
# Aria label for the button to view the next reference
|
|
12
|
+
ba.boundingBoxHighlight.viewNextReference = View next reference
|
|
13
|
+
# Aria label for the button to view the previous reference
|
|
14
|
+
ba.boundingBoxHighlight.viewPrevReference = View previous reference
|
|
11
15
|
# Aria label description for reply popup
|
|
12
16
|
ba.popup.reply.comment = Comment
|
|
13
17
|
# Aria label description for annotation comment field
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"ba.boundingBoxHighlight.viewNextReference","description":"Aria label for the button to view the next reference","defaultMessage":"View next reference"},{"id":"ba.boundingBoxHighlight.viewPrevReference","description":"Aria label for the button to view the previous reference","defaultMessage":"View previous reference"}]
|
package/package.json
CHANGED
package/src/@types/events.ts
CHANGED
|
@@ -9,7 +9,11 @@ enum Event {
|
|
|
9
9
|
ANNOTATIONS_INITIALIZED = 'annotations_initialized',
|
|
10
10
|
ANNOTATIONS_MODE_CHANGE = 'annotations_mode_change',
|
|
11
11
|
COLOR_SET = 'annotations_color_set',
|
|
12
|
+
BOUNDING_BOX_HIGHLIGHTS_SET = 'bounding_box_highlights_set',
|
|
13
|
+
BOUNDING_BOX_HIGHLIGHT_NAVIGATE = 'bounding_box_highlight_navigate',
|
|
14
|
+
BOUNDING_BOX_HIGHLIGHT_SELECT = 'bounding_box_highlight_select',
|
|
12
15
|
VISIBLE_SET = 'annotations_visible_set',
|
|
16
|
+
VIEW_MODE_SET = 'view_mode_set',
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
// Existing legacy events, don't rename
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { connect } from 'react-redux';
|
|
2
|
+
import { BoundingBoxHighlightList } from '../components/BoundingBoxHighlight';
|
|
3
|
+
import withProviders from '../common/withProviders';
|
|
4
|
+
import {
|
|
5
|
+
AppState,
|
|
6
|
+
BoundingBox,
|
|
7
|
+
getBoundingBoxHighlights,
|
|
8
|
+
getBoundingBoxHighlightsForPage,
|
|
9
|
+
getSelectedBoundingBoxHighlightId,
|
|
10
|
+
navigateBoundingBoxHighlightAction,
|
|
11
|
+
setSelectedBoundingBoxHighlightAction,
|
|
12
|
+
} from '../store';
|
|
13
|
+
|
|
14
|
+
export type Props = {
|
|
15
|
+
location: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const mapStateToProps = (state: AppState, { location }: Props): {
|
|
19
|
+
allBoundingBoxes: BoundingBox[];
|
|
20
|
+
boundingBoxes: BoundingBox[];
|
|
21
|
+
selectedId: string | null;
|
|
22
|
+
} => ({
|
|
23
|
+
allBoundingBoxes: getBoundingBoxHighlights(state),
|
|
24
|
+
boundingBoxes: getBoundingBoxHighlightsForPage(state, location),
|
|
25
|
+
selectedId: getSelectedBoundingBoxHighlightId(state),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const mapDispatchToProps = {
|
|
29
|
+
onNavigate: navigateBoundingBoxHighlightAction,
|
|
30
|
+
onSelect: setSelectedBoundingBoxHighlightAction,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default connect(mapStateToProps, mapDispatchToProps)(withProviders(BoundingBoxHighlightList));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as ReactDOM from 'react-dom/client';
|
|
3
|
+
import BaseManager, { Props } from '../common/BaseManager';
|
|
4
|
+
import BoundingBoxHighlightContainer from './BoundingBoxHighlightContainer';
|
|
5
|
+
|
|
6
|
+
export default class BoundingBoxHighlightManager extends BaseManager {
|
|
7
|
+
decorate(): void {
|
|
8
|
+
this.reactEl.classList.add('ba-Layer--boundingBoxHighlight');
|
|
9
|
+
this.reactEl.dataset.testid = 'ba-Layer--boundingBoxHighlight';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
render(props: Props): void {
|
|
13
|
+
if (!this.root) {
|
|
14
|
+
this.root = ReactDOM.createRoot(this.reactEl);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.root.render(<BoundingBoxHighlightContainer location={this.location} {...props} />);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as BoundingBoxHighlightManager } from './BoundingBoxHighlightManager';
|
|
@@ -7,6 +7,8 @@ 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';
|
|
11
|
+
import { ViewMode } from '../store/options/types';
|
|
10
12
|
import { Features } from '../BoxAnnotations';
|
|
11
13
|
import './BaseAnnotator.scss';
|
|
12
14
|
|
|
@@ -37,6 +39,8 @@ export type Options = {
|
|
|
37
39
|
hasTouch?: boolean;
|
|
38
40
|
initialColor?: string;
|
|
39
41
|
initialMode?: store.Mode;
|
|
42
|
+
/** View mode: annotations and bounding boxes are mutually exclusive. Defaults to 'annotations'. */
|
|
43
|
+
initialViewMode?: ViewMode;
|
|
40
44
|
intl: IntlOptions;
|
|
41
45
|
locale?: string;
|
|
42
46
|
token: string;
|
|
@@ -72,6 +76,7 @@ export default class BaseAnnotator extends EventEmitter {
|
|
|
72
76
|
fileOptions,
|
|
73
77
|
initialColor,
|
|
74
78
|
initialMode,
|
|
79
|
+
initialViewMode = 'annotations',
|
|
75
80
|
intl,
|
|
76
81
|
token,
|
|
77
82
|
}: Options) {
|
|
@@ -93,6 +98,7 @@ export default class BaseAnnotator extends EventEmitter {
|
|
|
93
98
|
fileVersionId: fileOptionsVersionId ?? fileVersionId,
|
|
94
99
|
isCurrentFileVersion: !fileOptionsVersionId || fileOptionsVersionId === currentFileVersionId,
|
|
95
100
|
permissions: file.permissions,
|
|
101
|
+
viewMode: initialViewMode,
|
|
96
102
|
},
|
|
97
103
|
};
|
|
98
104
|
|
|
@@ -109,6 +115,10 @@ export default class BaseAnnotator extends EventEmitter {
|
|
|
109
115
|
this.addListener(Event.ANNOTATION_REMOVE, this.handleRemove);
|
|
110
116
|
this.addListener(Event.COLOR_SET, this.handleSetColor);
|
|
111
117
|
this.addListener(Event.VISIBLE_SET, this.handleSetVisible);
|
|
118
|
+
this.addListener(Event.BOUNDING_BOX_HIGHLIGHTS_SET, this.handleSetBoundingBoxHighlights);
|
|
119
|
+
this.addListener(Event.BOUNDING_BOX_HIGHLIGHT_NAVIGATE, this.handleNavigateBoundingBoxHighlight);
|
|
120
|
+
this.addListener(Event.BOUNDING_BOX_HIGHLIGHT_SELECT, this.handleSelectBoundingBoxHighlight);
|
|
121
|
+
this.addListener(Event.VIEW_MODE_SET, this.handleSetViewMode);
|
|
112
122
|
|
|
113
123
|
// Load any required data at startup
|
|
114
124
|
this.hydrate();
|
|
@@ -134,6 +144,10 @@ export default class BaseAnnotator extends EventEmitter {
|
|
|
134
144
|
this.removeListener(Event.ANNOTATION_REMOVE, this.handleRemove);
|
|
135
145
|
this.removeListener(Event.COLOR_SET, this.handleSetColor);
|
|
136
146
|
this.removeListener(Event.VISIBLE_SET, this.handleSetVisible);
|
|
147
|
+
this.removeListener(Event.BOUNDING_BOX_HIGHLIGHTS_SET, this.handleSetBoundingBoxHighlights);
|
|
148
|
+
this.removeListener(Event.BOUNDING_BOX_HIGHLIGHT_NAVIGATE, this.handleNavigateBoundingBoxHighlight);
|
|
149
|
+
this.removeListener(Event.BOUNDING_BOX_HIGHLIGHT_SELECT, this.handleSelectBoundingBoxHighlight);
|
|
150
|
+
this.removeListener(Event.VIEW_MODE_SET, this.handleSetViewMode);
|
|
137
151
|
}
|
|
138
152
|
|
|
139
153
|
public init(scale = 1, rotation = 0): void {
|
|
@@ -171,6 +185,54 @@ export default class BaseAnnotator extends EventEmitter {
|
|
|
171
185
|
// Called by box-content-preview
|
|
172
186
|
}
|
|
173
187
|
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
189
|
+
public scrollToBoundingBoxHighlight(highlightId: string | null): void {
|
|
190
|
+
// Implemented in DocumentAnnotator
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Sets the bounding box highlights to display. This replaces all existing bounding boxes.
|
|
195
|
+
* Triggers a re-render.
|
|
196
|
+
*
|
|
197
|
+
* Note: Bounding boxes are only visible when viewMode is set to 'boundingBoxes'.
|
|
198
|
+
*
|
|
199
|
+
* @param {BoundingBox[]} boundingBoxes - Array of bounding box definitions with coordinates and page numbers
|
|
200
|
+
*/
|
|
201
|
+
public setBoundingBoxHighlights(boundingBoxes: BoundingBox[]): void {
|
|
202
|
+
this.store.dispatch(store.setBoundingBoxHighlightsAction(boundingBoxes));
|
|
203
|
+
this.render();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Sets the view mode for the annotator. Annotations and bounding boxes are mutually exclusive.
|
|
208
|
+
* Triggers a re-render.
|
|
209
|
+
*
|
|
210
|
+
* Side effects:
|
|
211
|
+
* - Switching to 'boundingBoxes': Clears active annotation ID
|
|
212
|
+
* - Switching to 'annotations': Clears selected bounding box ID
|
|
213
|
+
*
|
|
214
|
+
* @param {ViewMode} viewMode - Either 'annotations' (default) or 'boundingBoxes'
|
|
215
|
+
*/
|
|
216
|
+
public setViewMode(viewMode: ViewMode): void {
|
|
217
|
+
this.store.dispatch(store.setViewModeAction(viewMode));
|
|
218
|
+
this.render();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Selects a bounding box highlight and scrolls to it. Pass null to deselect.
|
|
223
|
+
*
|
|
224
|
+
* Note: For programmatic navigation (prev/next), use the navigateBoundingBoxHighlightAction
|
|
225
|
+
* which emits BOUNDING_BOX_HIGHLIGHT_NAVIGATE events.
|
|
226
|
+
*
|
|
227
|
+
* @param {string | null} highlightId - The ID of the bounding box to select, or null to clear selection
|
|
228
|
+
*/
|
|
229
|
+
public selectBoundingBoxHighlight(highlightId: string | null): void {
|
|
230
|
+
this.store.dispatch(store.setSelectedBoundingBoxHighlightAction(highlightId));
|
|
231
|
+
if (highlightId) {
|
|
232
|
+
this.scrollToBoundingBoxHighlight(highlightId);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
174
236
|
public setActiveId(annotationId: string | null): void {
|
|
175
237
|
this.store.dispatch(store.setActiveAnnotationIdAction(annotationId));
|
|
176
238
|
}
|
|
@@ -223,6 +285,22 @@ export default class BaseAnnotator extends EventEmitter {
|
|
|
223
285
|
this.setColor(color);
|
|
224
286
|
};
|
|
225
287
|
|
|
288
|
+
protected handleSetBoundingBoxHighlights = (boundingBoxes: BoundingBox[]): void => {
|
|
289
|
+
this.setBoundingBoxHighlights(boundingBoxes);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
protected handleNavigateBoundingBoxHighlight = (highlightId: string): void => {
|
|
293
|
+
this.scrollToBoundingBoxHighlight(highlightId);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
protected handleSelectBoundingBoxHighlight = (highlightId: string | null): void => {
|
|
297
|
+
this.selectBoundingBoxHighlight(highlightId);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
protected handleSetViewMode = ({ viewMode }: { viewMode: ViewMode }): void => {
|
|
301
|
+
this.setViewMode(viewMode);
|
|
302
|
+
};
|
|
303
|
+
|
|
226
304
|
protected hydrate(): void {
|
|
227
305
|
// Redux dispatch method signature doesn't seem to like async actions
|
|
228
306
|
this.store.dispatch<any>(store.fetchAnnotationsAction()); // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import BoundingBoxHighlightRect from './BoundingBoxHighlightRect';
|
|
3
|
+
import { BoundingBox } from '../../store/boundingBoxHighlights/types';
|
|
4
|
+
import './BoundingBoxHighlightList.scss';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
/**
|
|
8
|
+
* All bounding box highlights in the document.
|
|
9
|
+
*/
|
|
10
|
+
allBoundingBoxes: BoundingBox[];
|
|
11
|
+
/**
|
|
12
|
+
* Bounding boxes on the current page.
|
|
13
|
+
*/
|
|
14
|
+
boundingBoxes: BoundingBox[];
|
|
15
|
+
/**
|
|
16
|
+
* Callback invoked to navigate to the previous or next bounding box highlight.
|
|
17
|
+
*/
|
|
18
|
+
onNavigate?: (id: string) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Callback invoked when the user clicks on a bounding box highlight.
|
|
21
|
+
*/
|
|
22
|
+
onSelect?: (id: string) => void;
|
|
23
|
+
/**
|
|
24
|
+
* The ID of the selected bounding box highlight.
|
|
25
|
+
*/
|
|
26
|
+
selectedId: string | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const BoundingBoxHighlightList = ({
|
|
30
|
+
allBoundingBoxes,
|
|
31
|
+
boundingBoxes,
|
|
32
|
+
onNavigate,
|
|
33
|
+
onSelect,
|
|
34
|
+
selectedId,
|
|
35
|
+
}: Props): React.ReactElement | null => {
|
|
36
|
+
const total = allBoundingBoxes.length;
|
|
37
|
+
|
|
38
|
+
if (total === 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const selectedIndex = allBoundingBoxes.findIndex(h => h.id === selectedId);
|
|
43
|
+
const prevId = selectedIndex > 0 ? allBoundingBoxes[selectedIndex - 1].id : undefined;
|
|
44
|
+
const nextId = selectedIndex < total - 1 ? allBoundingBoxes[selectedIndex + 1].id : undefined;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="ba-BoundingBoxHighlightList" data-testid="ba-BoundingBoxHighlightList">
|
|
48
|
+
{boundingBoxes.map(bbox => {
|
|
49
|
+
return (
|
|
50
|
+
<BoundingBoxHighlightRect
|
|
51
|
+
key={bbox.id}
|
|
52
|
+
boundingBox={bbox}
|
|
53
|
+
currentIndex={selectedIndex}
|
|
54
|
+
isSelected={selectedId === bbox.id}
|
|
55
|
+
nextId={nextId}
|
|
56
|
+
onNavigate={onNavigate}
|
|
57
|
+
onSelect={onSelect}
|
|
58
|
+
prevId={prevId}
|
|
59
|
+
total={total}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
})}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default React.memo(BoundingBoxHighlightList);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
$ba-BoundingBoxHighlightNav-bg: #fff;
|
|
2
|
+
$ba-BoundingBoxHighlightNav-shadow: 0 6px 20px rgba(0, 0, 0, .1);
|
|
3
|
+
$ba-BoundingBoxHighlightNav-arrow: #909090;
|
|
4
|
+
$ba-BoundingBoxHighlightNav-border: #e8e8e8;
|
|
5
|
+
$ba-BoundingBoxHighlightNav-counter: #222;
|
|
6
|
+
|
|
7
|
+
.ba-BoundingBoxHighlightNav {
|
|
8
|
+
position: absolute;
|
|
9
|
+
bottom: 100%;
|
|
10
|
+
left: 50%;
|
|
11
|
+
z-index: 1;
|
|
12
|
+
display: flex;
|
|
13
|
+
gap: 4px;
|
|
14
|
+
align-items: center;
|
|
15
|
+
margin-bottom: 6px;
|
|
16
|
+
padding: 3px;
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
background: $ba-BoundingBoxHighlightNav-bg;
|
|
19
|
+
border: 1px solid $ba-BoundingBoxHighlightNav-border;
|
|
20
|
+
border-radius: 20px;
|
|
21
|
+
box-shadow: $ba-BoundingBoxHighlightNav-shadow;
|
|
22
|
+
transform: translateX(-50%);
|
|
23
|
+
cursor: default;
|
|
24
|
+
pointer-events: auto;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.ba-BoundingBoxHighlightNav-btn {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
width: 32px;
|
|
32
|
+
height: 32px;
|
|
33
|
+
padding: 0;
|
|
34
|
+
line-height: 1;
|
|
35
|
+
background: transparent;
|
|
36
|
+
border: none;
|
|
37
|
+
border-radius: 50%;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
transition: background-color 150ms ease;
|
|
40
|
+
|
|
41
|
+
svg {
|
|
42
|
+
path {
|
|
43
|
+
fill: $ba-BoundingBoxHighlightNav-arrow;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&:hover:not(:disabled) {
|
|
48
|
+
background: rgba(0, 0, 0, .06);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&:active:not(:disabled) {
|
|
52
|
+
background: rgba(0, 0, 0, .12);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&:disabled {
|
|
56
|
+
cursor: default;
|
|
57
|
+
|
|
58
|
+
svg {
|
|
59
|
+
path {
|
|
60
|
+
fill: $ba-BoundingBoxHighlightNav-border;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.ba-BoundingBoxHighlightNav-counter {
|
|
67
|
+
padding: 0 4px;
|
|
68
|
+
color: $ba-BoundingBoxHighlightNav-counter;
|
|
69
|
+
font-weight: normal;
|
|
70
|
+
font-size: 15px;
|
|
71
|
+
font-family: Lato, Arial, sans-serif;
|
|
72
|
+
user-select: none;
|
|
73
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
3
|
+
import ChevronLeft from '../../icons/ChevronLeft';
|
|
4
|
+
import ChevronRight from '../../icons/ChevronRight';
|
|
5
|
+
import messages from './messages';
|
|
6
|
+
import './BoundingBoxHighlightNav.scss';
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
currentIndex: number;
|
|
10
|
+
total: number;
|
|
11
|
+
onPrev: (event: React.MouseEvent) => void;
|
|
12
|
+
onNext: (event: React.MouseEvent) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const BoundingBoxHighlightNav = ({ currentIndex, total, onPrev, onNext }: Props): JSX.Element => {
|
|
16
|
+
const intl = useIntl();
|
|
17
|
+
const isPrevDisabled = currentIndex === 0;
|
|
18
|
+
const isNextDisabled = currentIndex === total - 1;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="ba-BoundingBoxHighlightNav" data-testid="ba-BoundingBoxHighlightNav">
|
|
22
|
+
<button
|
|
23
|
+
aria-label={intl.formatMessage(messages.ariaLabelViewPrevReference)}
|
|
24
|
+
className="ba-BoundingBoxHighlightNav-btn"
|
|
25
|
+
data-testid="ba-BoundingBoxHighlightNav-prev"
|
|
26
|
+
disabled={isPrevDisabled}
|
|
27
|
+
onClick={onPrev}
|
|
28
|
+
type="button"
|
|
29
|
+
>
|
|
30
|
+
<ChevronLeft width={20} height={20} />
|
|
31
|
+
</button>
|
|
32
|
+
<span className="ba-BoundingBoxHighlightNav-counter">
|
|
33
|
+
{currentIndex + 1} / {total}
|
|
34
|
+
</span>
|
|
35
|
+
<button
|
|
36
|
+
aria-label={intl.formatMessage(messages.ariaLabelViewNextReference)}
|
|
37
|
+
className="ba-BoundingBoxHighlightNav-btn"
|
|
38
|
+
data-testid="ba-BoundingBoxHighlightNav-next"
|
|
39
|
+
disabled={isNextDisabled}
|
|
40
|
+
onClick={onNext}
|
|
41
|
+
type="button"
|
|
42
|
+
>
|
|
43
|
+
<ChevronRight width={20} height={20} />
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default BoundingBoxHighlightNav;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
$ba-BoundingBoxHighlight-color: #0061d5;
|
|
2
|
+
$ba-BoundingBoxHighlight-bgColor--default: rgba($ba-BoundingBoxHighlight-color, .05);
|
|
3
|
+
$ba-BoundingBoxHighlight-bgColor--hover: rgba($ba-BoundingBoxHighlight-color, .16);
|
|
4
|
+
$ba-BoundingBoxHighlight-bgColor--selected: rgba($ba-BoundingBoxHighlight-color, .1);
|
|
5
|
+
|
|
6
|
+
.ba-BoundingBoxHighlightRect {
|
|
7
|
+
position: absolute;
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
background-color: $ba-BoundingBoxHighlight-bgColor--default;
|
|
10
|
+
border: 2px solid rgba($ba-BoundingBoxHighlight-color, .2);
|
|
11
|
+
border-radius: 8px;
|
|
12
|
+
outline: none;
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
transition: background-color 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
|
|
15
|
+
pointer-events: auto;
|
|
16
|
+
|
|
17
|
+
&.is-selected {
|
|
18
|
+
background-color: $ba-BoundingBoxHighlight-bgColor--selected;
|
|
19
|
+
border-color: $ba-BoundingBoxHighlight-color;
|
|
20
|
+
box-shadow: 0 0 0 2px rgba($ba-BoundingBoxHighlight-color, .3), 0 4px 12px rgba(0, 0, 0, .15);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import BoundingBoxHighlightNav from './BoundingBoxHighlightNav';
|
|
4
|
+
import { BoundingBox } from '../../store/boundingBoxHighlights/types';
|
|
5
|
+
import './BoundingBoxHighlightRect.scss';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
boundingBox: BoundingBox;
|
|
9
|
+
currentIndex: number;
|
|
10
|
+
isSelected?: boolean;
|
|
11
|
+
onNavigate?: (id: string) => void;
|
|
12
|
+
onSelect?: (id: string) => void;
|
|
13
|
+
total: number;
|
|
14
|
+
prevId?: string;
|
|
15
|
+
nextId?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const BoundingBoxHighlightRect = ({
|
|
19
|
+
boundingBox,
|
|
20
|
+
currentIndex,
|
|
21
|
+
isSelected,
|
|
22
|
+
onNavigate,
|
|
23
|
+
onSelect,
|
|
24
|
+
total,
|
|
25
|
+
prevId,
|
|
26
|
+
nextId,
|
|
27
|
+
}: Props): JSX.Element => {
|
|
28
|
+
const { id, x, y, width, height } = boundingBox;
|
|
29
|
+
|
|
30
|
+
const style: React.CSSProperties = {
|
|
31
|
+
display: 'block',
|
|
32
|
+
left: `${x}%`,
|
|
33
|
+
top: `${y}%`,
|
|
34
|
+
width: `${width}%`,
|
|
35
|
+
height: `${height}%`,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const handleClick = (event: React.MouseEvent): void => {
|
|
39
|
+
event.stopPropagation();
|
|
40
|
+
onSelect?.(id);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handlePrev = (event: React.MouseEvent): void => {
|
|
44
|
+
event.stopPropagation();
|
|
45
|
+
if (prevId) {
|
|
46
|
+
onNavigate?.(prevId);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleNext = (event: React.MouseEvent): void => {
|
|
51
|
+
event.stopPropagation();
|
|
52
|
+
if (nextId) {
|
|
53
|
+
onNavigate?.(nextId);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
className={classNames('ba-BoundingBoxHighlightRect', { 'is-selected': isSelected })}
|
|
60
|
+
data-testid={`ba-BoundingBoxHighlightRect-${id}`}
|
|
61
|
+
onClick={handleClick}
|
|
62
|
+
role="presentation"
|
|
63
|
+
style={style}
|
|
64
|
+
>
|
|
65
|
+
{isSelected && total > 1 && (
|
|
66
|
+
<BoundingBoxHighlightNav
|
|
67
|
+
currentIndex={currentIndex}
|
|
68
|
+
onNext={handleNext}
|
|
69
|
+
onPrev={handlePrev}
|
|
70
|
+
total={total}
|
|
71
|
+
/>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default BoundingBoxHighlightRect;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as BoundingBoxHighlightList } from './BoundingBoxHighlightList';
|
|
2
|
+
export { default as BoundingBoxHighlightNav } from './BoundingBoxHighlightNav';
|
|
3
|
+
export { default as BoundingBoxHighlightRect } from './BoundingBoxHighlightRect';
|
|
4
|
+
export type { BoundingBox } from '../../store/boundingBoxHighlights/types';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineMessages } from 'react-intl';
|
|
2
|
+
|
|
3
|
+
export default defineMessages({
|
|
4
|
+
ariaLabelViewNextReference: {
|
|
5
|
+
id: 'ba.boundingBoxHighlight.viewNextReference',
|
|
6
|
+
description: 'Aria label for the button to view the next reference',
|
|
7
|
+
defaultMessage: 'View next reference',
|
|
8
|
+
},
|
|
9
|
+
ariaLabelViewPrevReference: {
|
|
10
|
+
id: 'ba.boundingBoxHighlight.viewPrevReference',
|
|
11
|
+
description: 'Aria label for the button to view the previous reference',
|
|
12
|
+
defaultMessage: 'View previous reference',
|
|
13
|
+
},
|
|
14
|
+
});
|