box-annotations 5.2.1-beta.8 → 5.3.1-beta.0
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/dist/annotations.js.LICENSE.txt +1 -10
- package/i18n/bn-IN.js +6 -0
- package/i18n/bn-IN.properties +12 -0
- package/i18n/da-DK.js +6 -0
- package/i18n/da-DK.properties +12 -0
- package/i18n/de-DE.js +6 -0
- package/i18n/de-DE.properties +12 -0
- package/i18n/en-AU.js +6 -0
- package/i18n/en-AU.properties +12 -0
- package/i18n/en-CA.js +6 -0
- package/i18n/en-CA.properties +12 -0
- package/i18n/en-GB.js +6 -0
- package/i18n/en-GB.properties +12 -0
- package/i18n/es-419.js +6 -0
- package/i18n/es-419.properties +12 -0
- package/i18n/es-ES.js +6 -0
- package/i18n/es-ES.properties +12 -0
- package/i18n/fi-FI.js +6 -0
- package/i18n/fi-FI.properties +12 -0
- package/i18n/fr-CA.js +6 -0
- package/i18n/fr-CA.properties +12 -0
- package/i18n/fr-FR.js +6 -0
- package/i18n/fr-FR.properties +12 -0
- package/i18n/hi-IN.js +6 -0
- package/i18n/hi-IN.properties +12 -0
- package/i18n/it-IT.js +6 -0
- package/i18n/it-IT.properties +12 -0
- package/i18n/ja-JP.js +7 -1
- package/i18n/ja-JP.properties +13 -1
- package/i18n/ko-KR.js +6 -0
- package/i18n/ko-KR.properties +12 -0
- package/i18n/nb-NO.js +6 -0
- package/i18n/nb-NO.properties +12 -0
- package/i18n/nl-NL.js +6 -0
- package/i18n/nl-NL.properties +12 -0
- package/i18n/pl-PL.js +6 -0
- package/i18n/pl-PL.properties +12 -0
- package/i18n/pt-BR.js +6 -0
- package/i18n/pt-BR.properties +12 -0
- package/i18n/ru-RU.js +6 -0
- package/i18n/ru-RU.properties +12 -0
- package/i18n/sv-SE.js +6 -0
- package/i18n/sv-SE.properties +12 -0
- package/i18n/tr-TR.js +6 -0
- package/i18n/tr-TR.properties +12 -0
- package/i18n/zh-CN.js +6 -0
- package/i18n/zh-CN.properties +12 -0
- package/i18n/zh-TW.js +6 -0
- package/i18n/zh-TW.properties +12 -0
- package/index.js +1 -1
- package/package.json +54 -42
- package/src/@types/api.ts +3 -2
- package/src/@types/events.ts +27 -10
- package/src/@types/global.ts +0 -9
- package/src/@types/model.ts +9 -0
- package/src/BoxAnnotations.ts +6 -2
- package/src/adapters/threadedAnnotationsAdapters.ts +49 -28
- package/src/api/APIFactory.ts +6 -1
- package/src/api/types.ts +23 -1
- package/src/boundingBoxHighlight/BoundingBoxHighlightContainer.tsx +4 -1
- package/src/common/AnnotationCallbacksContext.ts +7 -0
- package/src/common/BaseAnnotator.scss +12 -0
- package/src/common/BaseAnnotator.ts +32 -2
- package/src/common/BaseManager.ts +2 -0
- package/src/common/DeselectListener.tsx +1 -1
- package/src/common/withProviders.tsx +6 -2
- package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.scss +3 -3
- package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.scss +11 -6
- package/src/components/Popups/PopupBase.scss +2 -2
- package/src/components/Popups/PopupDrawingToolbar.scss +4 -4
- package/src/components/Popups/PopupDrawingToolbar.tsx +0 -7
- package/src/components/Popups/PopupList.scss +1 -1
- package/src/components/Popups/PopupV2.scss +2 -2
- package/src/components/Popups/PopupV2.tsx +128 -72
- package/src/components/ReplyForm/ReplyForm.tsx +1 -8
- package/src/constants.ts +2 -3
- package/src/document/DocumentAnnotator.ts +29 -5
- package/src/document/docUtil.ts +8 -1
- package/src/drawing/DrawingAnnotations.tsx +30 -21
- package/src/drawing/DrawingAnnotationsContainer.tsx +3 -0
- package/src/drawing/DrawingCreator.tsx +9 -16
- package/src/drawing/DrawingCursor.ts +2 -1
- package/src/drawing/DrawingManager.tsx +21 -2
- package/src/highlight/HighlightAnnotations.tsx +28 -14
- package/src/highlight/HighlightContainer.tsx +3 -0
- package/src/highlight/HighlightCreator.scss +9 -2
- package/src/highlight/HighlightCreatorManager.ts +8 -5
- package/src/highlight/HighlightListener.ts +4 -2
- package/src/highlight/HighlightManager.tsx +13 -2
- package/src/image/ImageAnnotator.ts +2 -1
- package/src/media/MediaAnnotator.ts +39 -16
- package/src/popup/PopupLayer.tsx +38 -11
- package/src/popup/PopupManager.tsx +4 -2
- package/src/region/RegionAnnotations.tsx +2 -3
- package/src/region/RegionCreation.tsx +15 -9
- package/src/region/RegionCreationContainer.tsx +2 -2
- package/src/region/RegionCreationManager.tsx +8 -1
- package/src/region/RegionCreator.scss +9 -3
- package/src/region/RegionCreator.tsx +9 -17
- package/src/region/RegionManager.tsx +8 -1
- package/src/region/RegionRect.scss +17 -9
- package/src/store/annotations/actions.ts +104 -33
- package/src/store/annotations/reducer.ts +49 -1
- package/src/store/creator/selectors.ts +6 -6
- package/src/store/eventing/create.ts +6 -4
- package/src/store/eventing/delete.ts +26 -0
- package/src/store/eventing/middleware.ts +53 -15
- package/src/store/eventing/replyCreate.ts +30 -0
- package/src/store/eventing/replyDelete.ts +31 -0
- package/src/store/eventing/replyUpdate.ts +35 -0
- package/src/store/eventing/types.ts +4 -4
- package/src/store/eventing/update.ts +28 -0
- package/src/store/highlight/actions.ts +50 -19
- package/src/store/highlight/types.ts +1 -0
- package/src/store/options/selectors.ts +2 -2
- package/src/store/options/types.ts +2 -2
- package/src/test-utils/videoAnnotations.tsx +11 -11
- package/src/utils/rotate.ts +43 -0
- package/src/utils/useVideoTiming.ts +9 -13
|
@@ -4,6 +4,7 @@ import PointerCapture, { Status as DrawingStatus } from '../components/PointerCa
|
|
|
4
4
|
import PopupCursor from '../components/Popups/PopupCursor';
|
|
5
5
|
import RegionRect, { RegionRectRef } from './RegionRect';
|
|
6
6
|
import useAutoScroll from '../common/useAutoScroll';
|
|
7
|
+
import { getElementLocalPosition } from '../utils/rotate';
|
|
7
8
|
import { Rect } from '../@types';
|
|
8
9
|
import { styleShape } from './regionUtil';
|
|
9
10
|
import './RegionCreator.scss';
|
|
@@ -13,6 +14,7 @@ type Props = {
|
|
|
13
14
|
onAbort: () => void;
|
|
14
15
|
onStart: () => void;
|
|
15
16
|
onStop: (shape: Rect) => void;
|
|
17
|
+
rotation?: number;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
const MIN_X = 0; // Minimum region x position must be positive
|
|
@@ -22,7 +24,7 @@ const MIN_SIZE = 10; // Minimum region size must be large enough to be clickable
|
|
|
22
24
|
const isValid = (x1: number, y1: number, x2: number, y2: number): boolean =>
|
|
23
25
|
Math.abs(x2 - x1) >= MIN_SIZE || Math.abs(y2 - y1) >= MIN_SIZE;
|
|
24
26
|
|
|
25
|
-
export default function RegionCreator({ className, onAbort, onStart, onStop }: Props): JSX.Element {
|
|
27
|
+
export default function RegionCreator({ className, onAbort, onStart, onStop, rotation = 0 }: Props): JSX.Element {
|
|
26
28
|
const [drawingStatus, setDrawingStatus] = React.useState<DrawingStatus>(DrawingStatus.init);
|
|
27
29
|
const [isHovering, setIsHovering] = React.useState<boolean>(false);
|
|
28
30
|
const creatorElRef = React.useRef<HTMLDivElement>(null);
|
|
@@ -34,18 +36,6 @@ export default function RegionCreator({ className, onAbort, onStart, onStop }: P
|
|
|
34
36
|
const regionRectRef = React.useRef<RegionRectRef>(null);
|
|
35
37
|
const renderHandleRef = React.useRef<number | null>(null);
|
|
36
38
|
|
|
37
|
-
// Drawing Helpers
|
|
38
|
-
const getPosition = (x: number, y: number): [number, number] => {
|
|
39
|
-
const { current: creatorEl } = creatorElRef;
|
|
40
|
-
|
|
41
|
-
if (!creatorEl) {
|
|
42
|
-
return [x, y];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Calculate the new position based on the mouse position less the page offset
|
|
46
|
-
const { left, top } = creatorEl.getBoundingClientRect();
|
|
47
|
-
return [x - left, y - top];
|
|
48
|
-
};
|
|
49
39
|
const getShape = (): Rect | null => {
|
|
50
40
|
const { current: creatorEl } = creatorElRef;
|
|
51
41
|
const { current: x1 } = positionX1Ref;
|
|
@@ -57,7 +47,9 @@ export default function RegionCreator({ className, onAbort, onStart, onStop }: P
|
|
|
57
47
|
return null;
|
|
58
48
|
}
|
|
59
49
|
|
|
60
|
-
|
|
50
|
+
// Get the element's dimensions (before any rotation is applied)
|
|
51
|
+
const width = creatorEl.offsetWidth;
|
|
52
|
+
const height = creatorEl.offsetHeight;
|
|
61
53
|
const MAX_HEIGHT = height - MIN_SIZE;
|
|
62
54
|
const MAX_WIDTH = width - MIN_SIZE;
|
|
63
55
|
const MAX_X = Math.max(0, width - MIN_X);
|
|
@@ -88,7 +80,7 @@ export default function RegionCreator({ className, onAbort, onStart, onStop }: P
|
|
|
88
80
|
|
|
89
81
|
// Drawing Lifecycle Callbacks
|
|
90
82
|
const startDraw = (x: number, y: number): void => {
|
|
91
|
-
const [x1, y1] =
|
|
83
|
+
const [x1, y1] = getElementLocalPosition(x, y, creatorElRef.current, rotation);
|
|
92
84
|
|
|
93
85
|
setDrawingStatus(DrawingStatus.dragging);
|
|
94
86
|
|
|
@@ -119,7 +111,7 @@ export default function RegionCreator({ className, onAbort, onStart, onStop }: P
|
|
|
119
111
|
|
|
120
112
|
const updateDraw = React.useCallback(
|
|
121
113
|
(x: number, y: number): void => {
|
|
122
|
-
const [x2, y2] =
|
|
114
|
+
const [x2, y2] = getElementLocalPosition(x, y, creatorElRef.current, rotation);
|
|
123
115
|
const { current: x1 } = positionX1Ref;
|
|
124
116
|
const { current: y1 } = positionY1Ref;
|
|
125
117
|
const { current: prevX2 } = positionX2Ref;
|
|
@@ -138,7 +130,7 @@ export default function RegionCreator({ className, onAbort, onStart, onStop }: P
|
|
|
138
130
|
onStart();
|
|
139
131
|
}
|
|
140
132
|
},
|
|
141
|
-
[drawingStatus, onStart, setDrawingStatus],
|
|
133
|
+
[drawingStatus, onStart, rotation, setDrawingStatus],
|
|
142
134
|
);
|
|
143
135
|
|
|
144
136
|
// Event Handlers
|
|
@@ -14,6 +14,13 @@ export default class RegionListManager extends BaseManager {
|
|
|
14
14
|
this.root = ReactDOM.createRoot(this.reactEl);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
this.root.render(
|
|
17
|
+
this.root.render(
|
|
18
|
+
<RegionAnnotationsContainer
|
|
19
|
+
location={this.location}
|
|
20
|
+
referenceEl={this.referenceEl}
|
|
21
|
+
targetType={this.targetType}
|
|
22
|
+
{...props}
|
|
23
|
+
/>,
|
|
24
|
+
);
|
|
18
25
|
}
|
|
19
26
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
$ba-RegionRect-color: $bdl-yellorange;
|
|
4
4
|
$ba-RegionRect-bgColor--default: rgba($ba-RegionRect-color, 0); // Default color uses zero alpha value for transition
|
|
5
|
-
$ba-RegionRect-bgColor--hover: rgba($ba-RegionRect-color, .1);
|
|
5
|
+
$ba-RegionRect-bgColor--hover: rgba($ba-RegionRect-color, 0.1);
|
|
6
6
|
$ba-RegionRect-bgColor--active: rgba($ba-RegionRect-color, 0);
|
|
7
7
|
$ba-RegionRect-transitionDelay: 25ms;
|
|
8
8
|
$ba-RegionRect-transitionDuration: 200ms;
|
|
@@ -21,29 +21,37 @@ $ba-RegionRect-transitionDuration: 200ms;
|
|
|
21
21
|
|
|
22
22
|
&::after {
|
|
23
23
|
position: absolute;
|
|
24
|
-
|
|
25
|
-
right: $border-size-outer;
|
|
26
|
-
bottom: $border-size-outer;
|
|
27
|
-
left: $border-size-outer;
|
|
24
|
+
inset: $border-size-outer;
|
|
28
25
|
display: block;
|
|
29
26
|
background-color: $ba-RegionRect-bgColor--default;
|
|
30
27
|
border-radius: $border-radius;
|
|
31
|
-
box-shadow:
|
|
32
|
-
|
|
28
|
+
box-shadow:
|
|
29
|
+
0 0 0 $border-size-inner rgba($white, 1),
|
|
30
|
+
0 0 0 $border-size-outer rgba($ba-RegionRect-color, 0.6),
|
|
31
|
+
0 0 0 0 rgb(0 0 0 / 0%);
|
|
32
|
+
transition:
|
|
33
|
+
background-color $ba-RegionRect-transitionDuration ease,
|
|
34
|
+
box-shadow $ba-RegionRect-transitionDuration ease;
|
|
33
35
|
content: '';
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
&:hover {
|
|
37
39
|
&::after {
|
|
38
40
|
background-color: $ba-RegionRect-bgColor--hover;
|
|
39
|
-
box-shadow:
|
|
41
|
+
box-shadow:
|
|
42
|
+
0 0 0 $border-size-inner rgba($white, 1),
|
|
43
|
+
0 0 0 $border-size-outer rgba($ba-RegionRect-color, 1),
|
|
44
|
+
0 8px 16px 0 rgb(0 0 0 / 10%);
|
|
40
45
|
}
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
&.is-active {
|
|
44
49
|
&::after {
|
|
45
50
|
background-color: $ba-RegionRect-bgColor--active;
|
|
46
|
-
box-shadow:
|
|
51
|
+
box-shadow:
|
|
52
|
+
0 0 0 $border-size-inner rgba($ba-RegionRect-color, 1),
|
|
53
|
+
0 0 0 $border-size-outer rgba($ba-RegionRect-color, 1),
|
|
54
|
+
0 8px 52px 0 rgb(0 0 0 / 20%);
|
|
47
55
|
transition-delay: $ba-RegionRect-transitionDelay;
|
|
48
56
|
}
|
|
49
57
|
}
|
|
@@ -53,27 +53,24 @@ export const createReplyAction = createAsyncThunk<
|
|
|
53
53
|
{ annotationId: string; reply: Reply },
|
|
54
54
|
{ annotationId: string; message: string },
|
|
55
55
|
AppThunkAPI
|
|
56
|
-
>(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const annotation = getAnnotation(state, annotationId);
|
|
64
|
-
const permissions = { ...filePermissions, ...annotation?.permissions };
|
|
56
|
+
>('CREATE_REPLY', async ({ annotationId, message }, { extra, getState, signal }) => {
|
|
57
|
+
const client = extra.api.getAnnotationsAPI();
|
|
58
|
+
const state = getState();
|
|
59
|
+
const fileId = getFileId(state);
|
|
60
|
+
const filePermissions = getPermissions(state);
|
|
61
|
+
const annotation = getAnnotation(state, annotationId);
|
|
62
|
+
const permissions = { ...filePermissions, ...annotation?.permissions };
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
signal.addEventListener('abort', () => {
|
|
65
|
+
client.destroy();
|
|
66
|
+
});
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
const reply = await new Promise<Reply>((resolve, reject) => {
|
|
69
|
+
client.createAnnotationReply(fileId, annotationId, permissions, message, resolve, reject);
|
|
70
|
+
});
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
);
|
|
72
|
+
return { annotationId, reply };
|
|
73
|
+
});
|
|
77
74
|
|
|
78
75
|
export const deleteAnnotationAction = createAsyncThunk<string, string, AppThunkAPI>(
|
|
79
76
|
'DELETE_ANNOTATION',
|
|
@@ -101,25 +98,99 @@ export const updateAnnotationAction = createAsyncThunk<
|
|
|
101
98
|
Annotation,
|
|
102
99
|
{ annotationId: string; payload: { message?: string; status?: string } },
|
|
103
100
|
AppThunkAPI
|
|
104
|
-
>(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
101
|
+
>('UPDATE_ANNOTATION', async ({ annotationId, payload }, { extra, getState, signal }) => {
|
|
102
|
+
const client = extra.api.getAnnotationsAPI();
|
|
103
|
+
const state = getState();
|
|
104
|
+
const fileId = getFileId(state);
|
|
105
|
+
const filePermissions = getPermissions(state);
|
|
106
|
+
const existingAnnotation = getAnnotation(state, annotationId);
|
|
107
|
+
const permissions = { ...filePermissions, ...existingAnnotation?.permissions };
|
|
108
|
+
|
|
109
|
+
signal.addEventListener('abort', () => {
|
|
110
|
+
client.destroy();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return new Promise<Annotation>((resolve, reject) => {
|
|
114
|
+
client.updateAnnotation(fileId, annotationId, permissions, payload, resolve, reject);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export const updateReplyAction = createAsyncThunk<
|
|
119
|
+
{ annotationId: string; reply: Reply },
|
|
120
|
+
{ annotationId: string; replyId: string; payload: { message?: string; status?: string } },
|
|
121
|
+
AppThunkAPI
|
|
122
|
+
>('UPDATE_REPLY', async ({ annotationId, replyId, payload }, { extra, getState, signal }) => {
|
|
123
|
+
const client = extra.api.getThreadedCommentsAPI();
|
|
124
|
+
const state = getState();
|
|
125
|
+
const fileId = getFileId(state);
|
|
126
|
+
const annotation = getAnnotation(state, annotationId);
|
|
127
|
+
const reply = annotation?.replies?.find(r => r.id === replyId);
|
|
128
|
+
|
|
129
|
+
if (!reply) {
|
|
130
|
+
throw new Error(`updateReplyAction: reply ${replyId} not found on annotation ${annotationId}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
signal.addEventListener('abort', () => {
|
|
134
|
+
client.destroy();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const updated = await new Promise<Reply>((resolve, reject) => {
|
|
138
|
+
client.updateComment({
|
|
139
|
+
commentId: replyId,
|
|
140
|
+
errorCallback: reject,
|
|
141
|
+
fileId,
|
|
142
|
+
message: payload.message,
|
|
143
|
+
permissions: reply.permissions ?? {},
|
|
144
|
+
status: payload.status,
|
|
145
|
+
successCallback: resolve,
|
|
116
146
|
});
|
|
147
|
+
});
|
|
117
148
|
|
|
118
|
-
|
|
119
|
-
|
|
149
|
+
return { annotationId, reply: updated };
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export const deleteReplyAction = createAsyncThunk<
|
|
153
|
+
{ annotationId: string; replyId: string },
|
|
154
|
+
{ annotationId: string; replyId: string },
|
|
155
|
+
AppThunkAPI
|
|
156
|
+
>('DELETE_REPLY', async ({ annotationId, replyId }, { extra, getState, signal }) => {
|
|
157
|
+
const client = extra.api.getThreadedCommentsAPI();
|
|
158
|
+
const state = getState();
|
|
159
|
+
const fileId = getFileId(state);
|
|
160
|
+
const annotation = getAnnotation(state, annotationId);
|
|
161
|
+
const reply = annotation?.replies?.find(r => r.id === replyId);
|
|
162
|
+
|
|
163
|
+
if (!reply) {
|
|
164
|
+
throw new Error(`deleteReplyAction: reply ${replyId} not found on annotation ${annotationId}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
signal.addEventListener('abort', () => {
|
|
168
|
+
client.destroy();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await new Promise<void>((resolve, reject) => {
|
|
172
|
+
client.deleteComment({
|
|
173
|
+
commentId: replyId,
|
|
174
|
+
errorCallback: reject,
|
|
175
|
+
fileId,
|
|
176
|
+
permissions: reply.permissions ?? {},
|
|
177
|
+
successCallback: resolve,
|
|
120
178
|
});
|
|
121
|
-
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return { annotationId, replyId };
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
export type SidebarAnnotationUpdatePayload = Partial<Annotation> & { id: string };
|
|
185
|
+
export type SidebarReplyMutationPayload = { annotationId: string; reply: Reply };
|
|
186
|
+
export type SidebarReplyDeletePayload = { annotationId: string; replyId: string };
|
|
187
|
+
|
|
188
|
+
export const applySidebarAnnotationUpdateAction = createAction<SidebarAnnotationUpdatePayload>(
|
|
189
|
+
'APPLY_SIDEBAR_ANNOTATION_UPDATE',
|
|
122
190
|
);
|
|
191
|
+
export const applySidebarReplyCreateAction = createAction<SidebarReplyMutationPayload>('APPLY_SIDEBAR_REPLY_CREATE');
|
|
192
|
+
export const applySidebarReplyDeleteAction = createAction<SidebarReplyDeletePayload>('APPLY_SIDEBAR_REPLY_DELETE');
|
|
193
|
+
export const applySidebarReplyUpdateAction = createAction<SidebarReplyMutationPayload>('APPLY_SIDEBAR_REPLY_UPDATE');
|
|
123
194
|
|
|
124
195
|
export const removeAnnotationAction = createAction<string>('REMOVE_ANNOTATION');
|
|
125
196
|
export const setActiveAnnotationIdAction = createAction<string | null>('SET_ACTIVE_ANNOTATION_ID');
|
|
@@ -2,14 +2,20 @@ import { createReducer, combineReducers } from '@reduxjs/toolkit';
|
|
|
2
2
|
import { formatDrawing, isDrawing } from '../../drawing/drawingUtil';
|
|
3
3
|
import { AnnotationsState } from './types';
|
|
4
4
|
import {
|
|
5
|
+
applySidebarAnnotationUpdateAction,
|
|
6
|
+
applySidebarReplyCreateAction,
|
|
7
|
+
applySidebarReplyDeleteAction,
|
|
8
|
+
applySidebarReplyUpdateAction,
|
|
5
9
|
createAnnotationAction,
|
|
6
10
|
createReplyAction,
|
|
7
11
|
deleteAnnotationAction,
|
|
12
|
+
deleteReplyAction,
|
|
8
13
|
fetchAnnotationsAction,
|
|
9
14
|
removeAnnotationAction,
|
|
10
15
|
setActiveAnnotationIdAction,
|
|
11
16
|
setIsInitialized,
|
|
12
17
|
updateAnnotationAction,
|
|
18
|
+
updateReplyAction,
|
|
13
19
|
} from './actions';
|
|
14
20
|
import { setViewModeAction } from '../options/actions';
|
|
15
21
|
|
|
@@ -31,7 +37,9 @@ const annotationsAllIds = createReducer<AnnotationsState['allIds']>([], builder
|
|
|
31
37
|
.addCase(createAnnotationAction.fulfilled, (state, { payload: { id } }) => {
|
|
32
38
|
state.push(id);
|
|
33
39
|
})
|
|
34
|
-
.addCase(deleteAnnotationAction.fulfilled, (state, { payload: id }) =>
|
|
40
|
+
.addCase(deleteAnnotationAction.fulfilled, (state, { payload: id }) =>
|
|
41
|
+
state.filter(annotationId => annotationId !== id),
|
|
42
|
+
)
|
|
35
43
|
.addCase(removeAnnotationAction, (state, { payload: id }) => state.filter(annotationId => annotationId !== id))
|
|
36
44
|
.addCase(fetchAnnotationsAction.fulfilled, (state, { payload }) => {
|
|
37
45
|
payload.entries.forEach(({ id }) => state.indexOf(id) === -1 && state.push(id));
|
|
@@ -58,6 +66,46 @@ const annotationsById = createReducer<AnnotationsState['byId']>({}, builder =>
|
|
|
58
66
|
.addCase(updateAnnotationAction.fulfilled, (state, { payload }) => {
|
|
59
67
|
state[payload.id] = isDrawing(payload) ? formatDrawing(payload) : payload;
|
|
60
68
|
})
|
|
69
|
+
.addCase(updateReplyAction.fulfilled, (state, { payload: { annotationId, reply } }) => {
|
|
70
|
+
const annotation = state[annotationId];
|
|
71
|
+
if (annotation && annotation.replies) {
|
|
72
|
+
annotation.replies = annotation.replies.map(existing => (existing.id === reply.id ? reply : existing));
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
.addCase(deleteReplyAction.fulfilled, (state, { payload: { annotationId, replyId } }) => {
|
|
76
|
+
const annotation = state[annotationId];
|
|
77
|
+
if (annotation && annotation.replies) {
|
|
78
|
+
annotation.replies = annotation.replies.filter(existing => existing.id !== replyId);
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
.addCase(applySidebarAnnotationUpdateAction, (state, { payload }) => {
|
|
82
|
+
const existing = state[payload.id];
|
|
83
|
+
if (!existing) return;
|
|
84
|
+
Object.entries(payload).forEach(([key, value]) => {
|
|
85
|
+
if (value !== undefined) {
|
|
86
|
+
(existing as Record<string, unknown>)[key] = value;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
})
|
|
90
|
+
.addCase(applySidebarReplyCreateAction, (state, { payload: { annotationId, reply } }) => {
|
|
91
|
+
const annotation = state[annotationId];
|
|
92
|
+
if (!annotation) return;
|
|
93
|
+
const replies = annotation.replies ?? [];
|
|
94
|
+
if (replies.some(existing => existing.id === reply.id)) return;
|
|
95
|
+
annotation.replies = [...replies, reply];
|
|
96
|
+
})
|
|
97
|
+
.addCase(applySidebarReplyUpdateAction, (state, { payload: { annotationId, reply } }) => {
|
|
98
|
+
const annotation = state[annotationId];
|
|
99
|
+
if (!annotation || !annotation.replies) return;
|
|
100
|
+
annotation.replies = annotation.replies.map(existing =>
|
|
101
|
+
existing.id === reply.id ? { ...existing, ...reply } : existing,
|
|
102
|
+
);
|
|
103
|
+
})
|
|
104
|
+
.addCase(applySidebarReplyDeleteAction, (state, { payload: { annotationId, replyId } }) => {
|
|
105
|
+
const annotation = state[annotationId];
|
|
106
|
+
if (!annotation || !annotation.replies) return;
|
|
107
|
+
annotation.replies = annotation.replies.filter(existing => existing.id !== replyId);
|
|
108
|
+
})
|
|
61
109
|
.addCase(fetchAnnotationsAction.fulfilled, (state, { payload }) => {
|
|
62
110
|
payload.entries.forEach(annotation => {
|
|
63
111
|
state[annotation.id] = isDrawing(annotation) ? formatDrawing(annotation) : annotation;
|
|
@@ -11,13 +11,13 @@ export const getCreatorStaged = (state: State): CreatorItem | null => state.crea
|
|
|
11
11
|
export const getCreatorStagedForLocation = (state: State, location: number): CreatorItem | null => {
|
|
12
12
|
const staged = getCreatorStaged(state);
|
|
13
13
|
// The location is typically a page and this selector was originally meant to only return the staged annotation
|
|
14
|
-
// (those that have been drawn but not saved) that belong to the page specified by the location. For documents,
|
|
15
|
-
// the annotator creates new sets of managers(highlights, drawings, regions) for each page with the current
|
|
16
|
-
// page number being the location e.g. page 4 of 10. This selector prevents a staged annotation from staying on
|
|
17
|
-
// the screen when a user navigates to a different page. By default, the location value for managers is 1 which is
|
|
14
|
+
// (those that have been drawn but not saved) that belong to the page specified by the location. For documents,
|
|
15
|
+
// the annotator creates new sets of managers(highlights, drawings, regions) for each page with the current
|
|
16
|
+
// page number being the location e.g. page 4 of 10. This selector prevents a staged annotation from staying on
|
|
17
|
+
// the screen when a user navigates to a different page. By default, the location value for managers is 1 which is
|
|
18
18
|
// why this selector works for images. Video annotations will not work this way because they reference a timestamp, not a page,
|
|
19
|
-
// and the drawing canvas is the video player which has no pages. In this case, we just need to get the staged annotation and do the
|
|
20
|
-
// locaton check for MEDIA_LOCATION_INDEX. The MEDIA_LOCATION_INDEX is a special value, set in the manager, that is used to indicate that
|
|
19
|
+
// and the drawing canvas is the video player which has no pages. In this case, we just need to get the staged annotation and do the
|
|
20
|
+
// locaton check for MEDIA_LOCATION_INDEX. The MEDIA_LOCATION_INDEX is a special value, set in the manager, that is used to indicate that
|
|
21
21
|
// the this is actually a staged video annotation with a timestamp as its location.
|
|
22
22
|
if (location === MEDIA_LOCATION_INDEX) {
|
|
23
23
|
return staged;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import eventManager from '../../common/EventManager';
|
|
2
2
|
import { AsyncAction, Status } from './types';
|
|
3
3
|
import { AppState } from '../types';
|
|
4
|
-
import { Event } from '../../@types';
|
|
4
|
+
import { Annotation, Event, NewAnnotation } from '../../@types';
|
|
5
5
|
|
|
6
|
-
const emitCreateEvent = (action: AsyncAction, status: Status): void => {
|
|
6
|
+
const emitCreateEvent = (action: AsyncAction<NewAnnotation, Annotation>, status: Status): void => {
|
|
7
7
|
const { error, meta: { arg, requestId } = {}, payload } = action;
|
|
8
8
|
eventManager.emit(Event.ANNOTATION_CREATE, {
|
|
9
9
|
annotation: payload || arg,
|
|
@@ -15,8 +15,10 @@ const emitCreateEvent = (action: AsyncAction, status: Status): void => {
|
|
|
15
15
|
});
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
const createHandler =
|
|
19
|
-
|
|
18
|
+
const createHandler =
|
|
19
|
+
(status: Status) =>
|
|
20
|
+
(prevState: AppState, nextState: AppState, action: AsyncAction): void =>
|
|
21
|
+
emitCreateEvent(action as AsyncAction<NewAnnotation, Annotation>, status);
|
|
20
22
|
|
|
21
23
|
const handleCreateErrorEvents = createHandler(Status.ERROR);
|
|
22
24
|
const handleCreatePendingEvents = createHandler(Status.PENDING);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import eventManager from '../../common/EventManager';
|
|
2
|
+
import { AppState } from '../types';
|
|
3
|
+
import { AsyncAction, Status } from './types';
|
|
4
|
+
import { Event } from '../../@types';
|
|
5
|
+
|
|
6
|
+
const emitDeleteEvent = (action: AsyncAction<string, string>, status: Status): void => {
|
|
7
|
+
const { error, meta: { arg, requestId } = {}, payload } = action;
|
|
8
|
+
if (status === Status.SUCCESS && !payload) return;
|
|
9
|
+
const id = payload ?? arg;
|
|
10
|
+
eventManager.emit(Event.ANNOTATION_DELETE, {
|
|
11
|
+
annotation: id ? { id } : undefined,
|
|
12
|
+
error,
|
|
13
|
+
meta: { requestId, status },
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const deleteHandler =
|
|
18
|
+
(status: Status) =>
|
|
19
|
+
(_prev: AppState, _next: AppState, action: AsyncAction): void =>
|
|
20
|
+
emitDeleteEvent(action as AsyncAction<string, string>, status);
|
|
21
|
+
|
|
22
|
+
const handleDeleteErrorEvents = deleteHandler(Status.ERROR);
|
|
23
|
+
const handleDeletePendingEvents = deleteHandler(Status.PENDING);
|
|
24
|
+
const handleDeleteSuccessEvents = deleteHandler(Status.SUCCESS);
|
|
25
|
+
|
|
26
|
+
export { handleDeleteErrorEvents, handleDeletePendingEvents, handleDeleteSuccessEvents };
|
|
@@ -2,28 +2,58 @@ import noop from 'lodash/noop';
|
|
|
2
2
|
import { Action, Dispatch, Middleware, MiddlewareAPI } from '@reduxjs/toolkit';
|
|
3
3
|
import {
|
|
4
4
|
createAnnotationAction,
|
|
5
|
+
createReplyAction,
|
|
6
|
+
deleteAnnotationAction,
|
|
7
|
+
deleteReplyAction,
|
|
5
8
|
fetchAnnotationsAction,
|
|
6
9
|
setActiveAnnotationIdAction,
|
|
7
10
|
setIsInitialized,
|
|
11
|
+
updateAnnotationAction,
|
|
12
|
+
updateReplyAction,
|
|
8
13
|
} from '../annotations/actions';
|
|
9
14
|
import { navigateBoundingBoxHighlightAction } from '../boundingBoxHighlights/actions';
|
|
10
15
|
import { EventHandlerMap } from './types';
|
|
11
16
|
import { handleActiveAnnotationEvents } from './active';
|
|
12
17
|
import { handleAnnotationsInitialized } from './init';
|
|
18
|
+
import { handleNavigateBoundingBoxHighlight } from './boundingBoxHighlightNav';
|
|
13
19
|
import { handleCreateErrorEvents, handleCreatePendingEvents, handleCreateSuccessEvents } from './create';
|
|
20
|
+
import { handleDeleteErrorEvents, handleDeletePendingEvents, handleDeleteSuccessEvents } from './delete';
|
|
14
21
|
import { handleFetchErrorEvents } from './fetch';
|
|
15
|
-
import {
|
|
22
|
+
import { handleToggleAnnotationModeAction } from './mode';
|
|
23
|
+
import {
|
|
24
|
+
handleReplyCreateErrorEvents,
|
|
25
|
+
handleReplyCreatePendingEvents,
|
|
26
|
+
handleReplyCreateSuccessEvents,
|
|
27
|
+
} from './replyCreate';
|
|
28
|
+
import {
|
|
29
|
+
handleReplyDeleteErrorEvents,
|
|
30
|
+
handleReplyDeletePendingEvents,
|
|
31
|
+
handleReplyDeleteSuccessEvents,
|
|
32
|
+
} from './replyDelete';
|
|
33
|
+
import {
|
|
34
|
+
handleReplyUpdateErrorEvents,
|
|
35
|
+
handleReplyUpdatePendingEvents,
|
|
36
|
+
handleReplyUpdateSuccessEvents,
|
|
37
|
+
} from './replyUpdate';
|
|
16
38
|
import { handleResetCreatorAction, handleSetStagedAction } from './staged';
|
|
17
39
|
import { handleSetStatusAction } from './status';
|
|
18
|
-
import {
|
|
40
|
+
import { handleUpdateErrorEvents, handleUpdatePendingEvents, handleUpdateSuccessEvents } from './update';
|
|
19
41
|
import { resetCreatorAction, setStagedAction, setStatusAction } from '../creator';
|
|
20
42
|
import { toggleAnnotationModeAction } from '../common/actions';
|
|
21
43
|
|
|
22
|
-
|
|
23
|
-
const eventHandlers: EventHandlerMap = {
|
|
44
|
+
export const eventHandlers: EventHandlerMap = {
|
|
24
45
|
[createAnnotationAction.fulfilled.toString()]: handleCreateSuccessEvents,
|
|
25
46
|
[createAnnotationAction.pending.toString()]: handleCreatePendingEvents,
|
|
26
47
|
[createAnnotationAction.rejected.toString()]: handleCreateErrorEvents,
|
|
48
|
+
[createReplyAction.fulfilled.toString()]: handleReplyCreateSuccessEvents,
|
|
49
|
+
[createReplyAction.pending.toString()]: handleReplyCreatePendingEvents,
|
|
50
|
+
[createReplyAction.rejected.toString()]: handleReplyCreateErrorEvents,
|
|
51
|
+
[deleteAnnotationAction.fulfilled.toString()]: handleDeleteSuccessEvents,
|
|
52
|
+
[deleteAnnotationAction.pending.toString()]: handleDeletePendingEvents,
|
|
53
|
+
[deleteAnnotationAction.rejected.toString()]: handleDeleteErrorEvents,
|
|
54
|
+
[deleteReplyAction.fulfilled.toString()]: handleReplyDeleteSuccessEvents,
|
|
55
|
+
[deleteReplyAction.pending.toString()]: handleReplyDeletePendingEvents,
|
|
56
|
+
[deleteReplyAction.rejected.toString()]: handleReplyDeleteErrorEvents,
|
|
27
57
|
[fetchAnnotationsAction.rejected.toString()]: handleFetchErrorEvents,
|
|
28
58
|
[navigateBoundingBoxHighlightAction.toString()]: handleNavigateBoundingBoxHighlight,
|
|
29
59
|
[resetCreatorAction.toString()]: handleResetCreatorAction,
|
|
@@ -32,22 +62,30 @@ const eventHandlers: EventHandlerMap = {
|
|
|
32
62
|
[setStagedAction.toString()]: handleSetStagedAction,
|
|
33
63
|
[setStatusAction.toString()]: handleSetStatusAction,
|
|
34
64
|
[toggleAnnotationModeAction.toString()]: handleToggleAnnotationModeAction,
|
|
65
|
+
[updateAnnotationAction.fulfilled.toString()]: handleUpdateSuccessEvents,
|
|
66
|
+
[updateAnnotationAction.pending.toString()]: handleUpdatePendingEvents,
|
|
67
|
+
[updateAnnotationAction.rejected.toString()]: handleUpdateErrorEvents,
|
|
68
|
+
[updateReplyAction.fulfilled.toString()]: handleReplyUpdateSuccessEvents,
|
|
69
|
+
[updateReplyAction.pending.toString()]: handleReplyUpdatePendingEvents,
|
|
70
|
+
[updateReplyAction.rejected.toString()]: handleReplyUpdateErrorEvents,
|
|
35
71
|
};
|
|
36
72
|
|
|
37
73
|
function getEventingMiddleware(handlers: EventHandlerMap = eventHandlers): Middleware {
|
|
38
|
-
return (store: MiddlewareAPI) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
74
|
+
return (store: MiddlewareAPI) =>
|
|
75
|
+
(next: Dispatch) =>
|
|
76
|
+
(action: Action): Action => {
|
|
77
|
+
const { type } = action;
|
|
78
|
+
// Retrieve the prevState as well as the nextState after the action has modified the state in order
|
|
79
|
+
// to pass all that information to the event handler
|
|
80
|
+
const prevState = store.getState();
|
|
81
|
+
const result = next(action);
|
|
82
|
+
const nextState = store.getState();
|
|
45
83
|
|
|
46
|
-
|
|
47
|
-
|
|
84
|
+
const handler = handlers[type] || noop;
|
|
85
|
+
handler(prevState, nextState, action);
|
|
48
86
|
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
return result;
|
|
88
|
+
};
|
|
51
89
|
}
|
|
52
90
|
|
|
53
91
|
export default getEventingMiddleware;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import eventManager from '../../common/EventManager';
|
|
2
|
+
import { Reply, Event } from '../../@types';
|
|
3
|
+
import { AppState } from '../types';
|
|
4
|
+
import { AsyncAction, Status } from './types';
|
|
5
|
+
|
|
6
|
+
type ReplyCreateArg = { annotationId: string; message: string };
|
|
7
|
+
type ReplyCreatePayload = { annotationId: string; reply: Reply };
|
|
8
|
+
|
|
9
|
+
const emitReplyCreateEvent = (action: AsyncAction<ReplyCreateArg, ReplyCreatePayload>, status: Status): void => {
|
|
10
|
+
const { error, meta: { arg, requestId } = {}, payload } = action;
|
|
11
|
+
if (status === Status.SUCCESS && !payload?.reply) return;
|
|
12
|
+
const annotationId = payload?.annotationId ?? arg?.annotationId;
|
|
13
|
+
eventManager.emit(Event.ANNOTATION_REPLY_CREATE, {
|
|
14
|
+
annotation: annotationId ? { id: annotationId } : undefined,
|
|
15
|
+
annotationReply: payload?.reply,
|
|
16
|
+
error,
|
|
17
|
+
meta: { requestId, status },
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const replyCreateHandler =
|
|
22
|
+
(status: Status) =>
|
|
23
|
+
(_prev: AppState, _next: AppState, action: AsyncAction): void =>
|
|
24
|
+
emitReplyCreateEvent(action as AsyncAction<ReplyCreateArg, ReplyCreatePayload>, status);
|
|
25
|
+
|
|
26
|
+
const handleReplyCreateErrorEvents = replyCreateHandler(Status.ERROR);
|
|
27
|
+
const handleReplyCreatePendingEvents = replyCreateHandler(Status.PENDING);
|
|
28
|
+
const handleReplyCreateSuccessEvents = replyCreateHandler(Status.SUCCESS);
|
|
29
|
+
|
|
30
|
+
export { handleReplyCreateErrorEvents, handleReplyCreatePendingEvents, handleReplyCreateSuccessEvents };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import eventManager from '../../common/EventManager';
|
|
2
|
+
import { AppState } from '../types';
|
|
3
|
+
import { AsyncAction, Status } from './types';
|
|
4
|
+
import { Event } from '../../@types';
|
|
5
|
+
|
|
6
|
+
type ReplyDeleteArg = { annotationId: string; replyId: string };
|
|
7
|
+
type ReplyDeletePayload = { annotationId: string; replyId: string };
|
|
8
|
+
|
|
9
|
+
const emitReplyDeleteEvent = (action: AsyncAction<ReplyDeleteArg, ReplyDeletePayload>, status: Status): void => {
|
|
10
|
+
const { error, meta: { arg, requestId } = {}, payload } = action;
|
|
11
|
+
if (status === Status.SUCCESS && !payload) return;
|
|
12
|
+
const annotationId = payload?.annotationId ?? arg?.annotationId;
|
|
13
|
+
const replyId = payload?.replyId ?? arg?.replyId;
|
|
14
|
+
eventManager.emit(Event.ANNOTATION_REPLY_DELETE, {
|
|
15
|
+
annotation: annotationId ? { id: annotationId } : undefined,
|
|
16
|
+
annotationReply: replyId ? { id: replyId } : undefined,
|
|
17
|
+
error,
|
|
18
|
+
meta: { requestId, status },
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const replyDeleteHandler =
|
|
23
|
+
(status: Status) =>
|
|
24
|
+
(_prev: AppState, _next: AppState, action: AsyncAction): void =>
|
|
25
|
+
emitReplyDeleteEvent(action as AsyncAction<ReplyDeleteArg, ReplyDeletePayload>, status);
|
|
26
|
+
|
|
27
|
+
const handleReplyDeleteErrorEvents = replyDeleteHandler(Status.ERROR);
|
|
28
|
+
const handleReplyDeletePendingEvents = replyDeleteHandler(Status.PENDING);
|
|
29
|
+
const handleReplyDeleteSuccessEvents = replyDeleteHandler(Status.SUCCESS);
|
|
30
|
+
|
|
31
|
+
export { handleReplyDeleteErrorEvents, handleReplyDeletePendingEvents, handleReplyDeleteSuccessEvents };
|