box-annotations 5.2.1-beta.2 → 5.2.1-beta.5
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 +113 -12
- 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 +48 -4
- package/src/icons/ChevronLeft.tsx +24 -0
- package/src/icons/ChevronRight.tsx +24 -0
- package/src/image/ImageAnnotator.ts +47 -1
- 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
|
@@ -8,15 +8,19 @@ import {
|
|
|
8
8
|
HighlightManager,
|
|
9
9
|
isHighlight,
|
|
10
10
|
} from '../highlight';
|
|
11
|
+
import { BoundingBoxHighlightManager } from '../boundingBoxHighlight';
|
|
11
12
|
import { centerRegion, isRegion, RegionCreationManager, RegionManager } from '../region';
|
|
12
13
|
import { Event } from '../@types';
|
|
13
14
|
import { getAnnotation } from '../store/annotations';
|
|
15
|
+
import { BoundingBox } from '../store/boundingBoxHighlights';
|
|
14
16
|
import { getSelection } from './docUtil';
|
|
15
17
|
import { Manager } from '../common/BaseManager';
|
|
16
|
-
import { getFileId, getIsCurrentFileVersion, Mode } from '../store';
|
|
18
|
+
import { getFileId, getIsCurrentFileVersion, getViewMode, Mode } from '../store';
|
|
17
19
|
import { scrollToLocation } from '../utils/scroll';
|
|
18
20
|
import './DocumentAnnotator.scss';
|
|
19
21
|
|
|
22
|
+
import type { ViewMode } from '../store/options/types';
|
|
23
|
+
|
|
20
24
|
// Pdf.js textLayer enhancement requires waiting for 300ms (they hardcode this magic number)
|
|
21
25
|
const TEXT_LAYER_ENHANCEMENT = 300;
|
|
22
26
|
|
|
@@ -27,6 +31,9 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
27
31
|
|
|
28
32
|
managers: Map<number, Set<Manager>> = new Map();
|
|
29
33
|
|
|
34
|
+
/** Tracks which view mode the current managers were created for. Used to destroy/recreate when switching. */
|
|
35
|
+
private managersViewMode: ViewMode | null = null;
|
|
36
|
+
|
|
30
37
|
constructor(options: Options) {
|
|
31
38
|
super(options);
|
|
32
39
|
|
|
@@ -53,13 +60,22 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
53
60
|
return this.containerEl?.querySelector('.bp-doc');
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
/** Destroys all managers and clears the cache. Call when switching view modes. */
|
|
64
|
+
private clearManagers(): void {
|
|
65
|
+
this.managers.forEach(managers => managers.forEach(manager => manager.destroy()));
|
|
66
|
+
this.managers.clear();
|
|
67
|
+
this.managersViewMode = null;
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
getPageManagers(pageEl: HTMLElement): Set<Manager> {
|
|
57
71
|
const fileId = getFileId(this.store.getState());
|
|
58
72
|
const isCurrentFileVersion = getIsCurrentFileVersion(this.store.getState());
|
|
59
73
|
const pageNumber = this.getPageNumber(pageEl);
|
|
60
74
|
const pageReferenceEl = this.getPageReference(pageEl);
|
|
61
|
-
const managers = this.managers.get(pageNumber) || new Set();
|
|
62
75
|
const resinTags = { fileid: fileId, iscurrent: isCurrentFileVersion };
|
|
76
|
+
const viewMode = getViewMode(this.store.getState());
|
|
77
|
+
|
|
78
|
+
const managers = this.managers.get(pageNumber) || new Set();
|
|
63
79
|
let destroyManagers = false;
|
|
64
80
|
|
|
65
81
|
// If a referenced page element doesn't exist, destroy all managers to keep them in sync
|
|
@@ -77,6 +93,12 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
77
93
|
|
|
78
94
|
// Lazily instantiate managers as pages are added or re-rendered
|
|
79
95
|
if (managers.size === 0) {
|
|
96
|
+
if (viewMode === 'boundingBoxes') {
|
|
97
|
+
managers.add(new BoundingBoxHighlightManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
|
|
98
|
+
return managers;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Annotations mode
|
|
80
102
|
managers.add(new PopupManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
|
|
81
103
|
managers.add(new DrawingManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
|
|
82
104
|
|
|
@@ -94,7 +116,6 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
94
116
|
}
|
|
95
117
|
|
|
96
118
|
managers.add(new HighlightManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
|
|
97
|
-
|
|
98
119
|
managers.add(new RegionManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
|
|
99
120
|
|
|
100
121
|
const canvasLayerEl = pageEl.querySelector<HTMLElement>('.canvasWrapper');
|
|
@@ -132,7 +153,7 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
132
153
|
}
|
|
133
154
|
|
|
134
155
|
handleChangeMode = ({ mode }: { mode: Mode }): void => {
|
|
135
|
-
if (!this.annotatedEl) {
|
|
156
|
+
if (!this.annotatedEl || getViewMode(this.store.getState()) === 'boundingBoxes') {
|
|
136
157
|
return;
|
|
137
158
|
}
|
|
138
159
|
|
|
@@ -146,6 +167,13 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
146
167
|
};
|
|
147
168
|
|
|
148
169
|
render(): void {
|
|
170
|
+
const viewMode = getViewMode(this.store.getState());
|
|
171
|
+
|
|
172
|
+
if (this.managersViewMode !== null && this.managersViewMode !== viewMode) {
|
|
173
|
+
this.clearManagers();
|
|
174
|
+
}
|
|
175
|
+
this.managersViewMode = viewMode;
|
|
176
|
+
|
|
149
177
|
this.getPages()
|
|
150
178
|
.filter(({ dataset }) => dataset.loaded && dataset.pageNumber)
|
|
151
179
|
.forEach(pageEl => this.renderPage(pageEl));
|
|
@@ -153,6 +181,18 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
153
181
|
this.postRender();
|
|
154
182
|
}
|
|
155
183
|
|
|
184
|
+
public postRender(): void {
|
|
185
|
+
// DeselectManager is only needed for annotation creation; skip in bounding box mode
|
|
186
|
+
if (getViewMode(this.store.getState()) === 'boundingBoxes') {
|
|
187
|
+
if (this.deselectManager) {
|
|
188
|
+
this.deselectManager.destroy();
|
|
189
|
+
this.deselectManager = null;
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
super.postRender();
|
|
194
|
+
}
|
|
195
|
+
|
|
156
196
|
renderPage(pageEl: HTMLElement): void {
|
|
157
197
|
const pageManagers = this.getPageManagers(pageEl);
|
|
158
198
|
const pageNumber = this.getPageNumber(pageEl);
|
|
@@ -194,4 +234,8 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
194
234
|
scrollToLocation(this.annotatedEl, annotationPageEl, { offsets });
|
|
195
235
|
}
|
|
196
236
|
}
|
|
237
|
+
|
|
238
|
+
protected getScrollReferenceForHighlight(highlight: BoundingBox): HTMLElement | undefined {
|
|
239
|
+
return this.getPage(highlight.pageNumber);
|
|
240
|
+
}
|
|
197
241
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
function ChevronLeft(props: React.SVGProps<SVGSVGElement>): JSX.Element {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
data-testid="ChevronLeft"
|
|
7
|
+
fill="none"
|
|
8
|
+
focusable="false"
|
|
9
|
+
height="1em"
|
|
10
|
+
role="img"
|
|
11
|
+
viewBox="0 0 24 24"
|
|
12
|
+
width="1em"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
<path
|
|
17
|
+
d="M13.293 4.293a1 1 0 1 1 1.414 1.414L8.414 12l6.293 6.293.068.076a1 1 0 0 1-1.406 1.406l-.076-.068L7 13.414a2 2 0 0 1 0-2.828l6.293-6.293Z"
|
|
18
|
+
fill="currentColor"
|
|
19
|
+
/>
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default ChevronLeft;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
function ChevronRight(props: React.SVGProps<SVGSVGElement>): JSX.Element {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
data-testid="ChevronRight"
|
|
7
|
+
fill="none"
|
|
8
|
+
focusable="false"
|
|
9
|
+
height="1em"
|
|
10
|
+
role="img"
|
|
11
|
+
viewBox="0 0 24 24"
|
|
12
|
+
width="1em"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
<path
|
|
17
|
+
d="M9.293 4.293a1 1 0 0 1 1.414 0L17 10.586a2 2 0 0 1 0 2.828l-6.293 6.293a1 1 0 0 1-1.414-1.414L15.586 12 9.293 5.707a1 1 0 0 1 0-1.414Z"
|
|
18
|
+
fill="currentColor"
|
|
19
|
+
/>
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default ChevronRight;
|
|
@@ -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;
|
|
@@ -8,13 +8,18 @@ import {
|
|
|
8
8
|
setActiveAnnotationIdAction,
|
|
9
9
|
setIsInitialized,
|
|
10
10
|
} from './actions';
|
|
11
|
+
import { setViewModeAction } from '../options/actions';
|
|
11
12
|
|
|
12
13
|
const activeAnnotationId = createReducer<AnnotationsState['activeId']>(null, builder =>
|
|
13
14
|
builder
|
|
14
15
|
/* Preview will set the active ID with a an event which will trigger the url change */
|
|
15
16
|
.addCase(createAnnotationAction.fulfilled, () => null)
|
|
16
17
|
.addCase(removeAnnotationAction, (state, { payload: id }) => (state === id ? null : state))
|
|
17
|
-
.addCase(setActiveAnnotationIdAction, (state, { payload: annotationId }) => annotationId)
|
|
18
|
+
.addCase(setActiveAnnotationIdAction, (state, { payload: annotationId }) => annotationId)
|
|
19
|
+
.addCase(setViewModeAction, (state, { payload }) => {
|
|
20
|
+
// Clear active annotation when switching to bounding boxes mode
|
|
21
|
+
return payload === 'boundingBoxes' ? null : state;
|
|
22
|
+
}),
|
|
18
23
|
);
|
|
19
24
|
|
|
20
25
|
const annotationsAllIds = createReducer<AnnotationsState['allIds']>([], builder =>
|
|
@@ -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,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;
|
|
@@ -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,
|
package/src/store/index.ts
CHANGED
|
@@ -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,11 +1,12 @@
|
|
|
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
12
|
export const initialState = {
|
|
@@ -16,6 +17,7 @@ export const initialState = {
|
|
|
16
17
|
permissions: {},
|
|
17
18
|
rotation: 0,
|
|
18
19
|
scale: 1,
|
|
20
|
+
viewMode: 'annotations' as ViewMode,
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
export default createReducer<OptionsState>(initialState, builder =>
|
|
@@ -34,5 +36,8 @@ export default createReducer<OptionsState>(initialState, builder =>
|
|
|
34
36
|
})
|
|
35
37
|
.addCase(setScaleAction, (state, { payload }) => {
|
|
36
38
|
state.scale = payload;
|
|
39
|
+
})
|
|
40
|
+
.addCase(setViewModeAction, (state, { payload }) => {
|
|
41
|
+
state.viewMode = payload;
|
|
37
42
|
}),
|
|
38
43
|
);
|
|
@@ -2,10 +2,12 @@ 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
|
|
|
8
9
|
export const getFeatures = (state: State): Features => state.options.features;
|
|
10
|
+
export const getViewMode = (state: State): ViewMode => state.options.viewMode;
|
|
9
11
|
export const getFileId = (state: State): string | null => state.options.fileId;
|
|
10
12
|
export const getFileVersionId = (state: State): string | null => state.options.fileVersionId;
|
|
11
13
|
export const getIsCurrentFileVersion = (state: State): boolean => state.options.isCurrentFileVersion;
|
|
@@ -1,6 +1,9 @@
|
|
|
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 = {
|
|
5
8
|
features: Features;
|
|
6
9
|
fileId: string | null;
|
|
@@ -9,4 +12,5 @@ export type OptionsState = {
|
|
|
9
12
|
permissions: Permissions;
|
|
10
13
|
rotation: number;
|
|
11
14
|
scale: number;
|
|
15
|
+
viewMode: ViewMode;
|
|
12
16
|
};
|
package/src/store/types.ts
CHANGED
|
@@ -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
|
};
|
package/src/utils/scroll.ts
CHANGED
|
@@ -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
|
-
|
|
44
|
-
|
|
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
|
}
|