box-annotations 5.2.1-beta.6 → 5.2.1-beta.8
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 +3 -4
- package/dist/annotations.js +1 -1
- package/dist/annotations.js.LICENSE.txt +2 -42
- package/i18n/en-US.js +2 -0
- package/i18n/en-US.properties +4 -0
- package/i18n/json/src/components/Popups/messages.json +1 -1
- package/package.json +28 -15
- package/src/@types/model.ts +5 -0
- package/src/adapters/threadedAnnotationsAdapters.ts +155 -0
- package/src/api/types.ts +28 -1
- package/src/common/BaseAnnotator.scss +2 -1
- package/src/common/BaseAnnotator.ts +16 -0
- package/src/common/DeselectListener.tsx +3 -1
- package/src/components/Popups/PopupReply.tsx +8 -28
- package/src/components/Popups/PopupV2.scss +28 -0
- package/src/components/Popups/PopupV2.tsx +320 -0
- package/src/components/Popups/messages.ts +10 -0
- package/src/document/DocumentAnnotator.ts +29 -5
- package/src/drawing/DrawingTarget.tsx +1 -0
- package/src/highlight/HighlightTarget.tsx +1 -0
- package/src/image/ImageAnnotator.ts +1 -1
- package/src/media/MediaAnnotator.ts +1 -1
- package/src/popup/PopupContainer.tsx +11 -1
- package/src/popup/PopupLayer.tsx +66 -22
- package/src/popup/PopupManager.tsx +21 -3
- package/src/region/RegionAnnotation.tsx +1 -0
- package/src/store/annotations/actions.ts +78 -3
- package/src/store/annotations/reducer.ts +17 -0
- package/src/store/options/reducer.ts +3 -1
- package/src/store/options/selectors.ts +2 -0
- package/src/store/options/types.ts +2 -0
- package/src/components/Popups/PopupReplyV2.tsx +0 -10
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createAction, createAsyncThunk } from '@reduxjs/toolkit';
|
|
2
|
-
import { Annotation, NewAnnotation } from '../../@types';
|
|
2
|
+
import { Annotation, NewAnnotation, Reply } from '../../@types';
|
|
3
3
|
import { APICollection } from '../../api';
|
|
4
4
|
import { AppThunkAPI } from '../types';
|
|
5
|
-
import {
|
|
5
|
+
import { getAnnotation } from './selectors';
|
|
6
|
+
import { getFileId, getFileVersionId, getPermissions, isFeatureEnabled } from '../options';
|
|
6
7
|
|
|
7
8
|
export const createAnnotationAction = createAsyncThunk<Annotation, NewAnnotation, AppThunkAPI>(
|
|
8
9
|
'CREATE_ANNOTATION',
|
|
@@ -39,9 +40,83 @@ export const fetchAnnotationsAction = createAsyncThunk<APICollection<Annotation>
|
|
|
39
40
|
client.destroy();
|
|
40
41
|
});
|
|
41
42
|
|
|
43
|
+
const shouldFetchReplies = isFeatureEnabled({ options: getState().options }, 'isThreadedAnnotation');
|
|
44
|
+
|
|
42
45
|
// Wrap the client request in a promise to allow it to be returned and cancelled
|
|
43
46
|
return new Promise<APICollection<Annotation>>((resolve, reject) => {
|
|
44
|
-
client.getAnnotations(fileId, fileVersionId, permissions, resolve, reject, 1000, false);
|
|
47
|
+
client.getAnnotations(fileId, fileVersionId, permissions, resolve, reject, 1000, false, shouldFetchReplies);
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export const createReplyAction = createAsyncThunk<
|
|
53
|
+
{ annotationId: string; reply: Reply },
|
|
54
|
+
{ annotationId: string; message: string },
|
|
55
|
+
AppThunkAPI
|
|
56
|
+
>(
|
|
57
|
+
'CREATE_REPLY',
|
|
58
|
+
async ({ annotationId, message }, { extra, getState, signal }) => {
|
|
59
|
+
const client = extra.api.getAnnotationsAPI();
|
|
60
|
+
const state = getState();
|
|
61
|
+
const fileId = getFileId(state);
|
|
62
|
+
const filePermissions = getPermissions(state);
|
|
63
|
+
const annotation = getAnnotation(state, annotationId);
|
|
64
|
+
const permissions = { ...filePermissions, ...annotation?.permissions };
|
|
65
|
+
|
|
66
|
+
signal.addEventListener('abort', () => {
|
|
67
|
+
client.destroy();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const reply = await new Promise<Reply>((resolve, reject) => {
|
|
71
|
+
client.createAnnotationReply(fileId, annotationId, permissions, message, resolve, reject);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return { annotationId, reply };
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
export const deleteAnnotationAction = createAsyncThunk<string, string, AppThunkAPI>(
|
|
79
|
+
'DELETE_ANNOTATION',
|
|
80
|
+
async (annotationId, { extra, getState, signal }) => {
|
|
81
|
+
const client = extra.api.getAnnotationsAPI();
|
|
82
|
+
const state = getState();
|
|
83
|
+
const fileId = getFileId(state);
|
|
84
|
+
const filePermissions = getPermissions(state);
|
|
85
|
+
const annotation = getAnnotation(state, annotationId);
|
|
86
|
+
const permissions = { ...filePermissions, ...annotation?.permissions };
|
|
87
|
+
|
|
88
|
+
signal.addEventListener('abort', () => {
|
|
89
|
+
client.destroy();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await new Promise<void>((resolve, reject) => {
|
|
93
|
+
client.deleteAnnotation(fileId, annotationId, permissions, resolve, reject);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return annotationId;
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
export const updateAnnotationAction = createAsyncThunk<
|
|
101
|
+
Annotation,
|
|
102
|
+
{ annotationId: string; payload: { message?: string; status?: string } },
|
|
103
|
+
AppThunkAPI
|
|
104
|
+
>(
|
|
105
|
+
'UPDATE_ANNOTATION',
|
|
106
|
+
async ({ annotationId, payload }, { extra, getState, signal }) => {
|
|
107
|
+
const client = extra.api.getAnnotationsAPI();
|
|
108
|
+
const state = getState();
|
|
109
|
+
const fileId = getFileId(state);
|
|
110
|
+
const filePermissions = getPermissions(state);
|
|
111
|
+
const existingAnnotation = getAnnotation(state, annotationId);
|
|
112
|
+
const permissions = { ...filePermissions, ...existingAnnotation?.permissions };
|
|
113
|
+
|
|
114
|
+
signal.addEventListener('abort', () => {
|
|
115
|
+
client.destroy();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return new Promise<Annotation>((resolve, reject) => {
|
|
119
|
+
client.updateAnnotation(fileId, annotationId, permissions, payload, resolve, reject);
|
|
45
120
|
});
|
|
46
121
|
},
|
|
47
122
|
);
|
|
@@ -3,10 +3,13 @@ import { formatDrawing, isDrawing } from '../../drawing/drawingUtil';
|
|
|
3
3
|
import { AnnotationsState } from './types';
|
|
4
4
|
import {
|
|
5
5
|
createAnnotationAction,
|
|
6
|
+
createReplyAction,
|
|
7
|
+
deleteAnnotationAction,
|
|
6
8
|
fetchAnnotationsAction,
|
|
7
9
|
removeAnnotationAction,
|
|
8
10
|
setActiveAnnotationIdAction,
|
|
9
11
|
setIsInitialized,
|
|
12
|
+
updateAnnotationAction,
|
|
10
13
|
} from './actions';
|
|
11
14
|
import { setViewModeAction } from '../options/actions';
|
|
12
15
|
|
|
@@ -14,6 +17,7 @@ const activeAnnotationId = createReducer<AnnotationsState['activeId']>(null, bui
|
|
|
14
17
|
builder
|
|
15
18
|
/* Preview will set the active ID with a an event which will trigger the url change */
|
|
16
19
|
.addCase(createAnnotationAction.fulfilled, () => null)
|
|
20
|
+
.addCase(deleteAnnotationAction.fulfilled, (state, { payload: id }) => (state === id ? null : state))
|
|
17
21
|
.addCase(removeAnnotationAction, (state, { payload: id }) => (state === id ? null : state))
|
|
18
22
|
.addCase(setActiveAnnotationIdAction, (state, { payload: annotationId }) => annotationId)
|
|
19
23
|
.addCase(setViewModeAction, (state, { payload }) => {
|
|
@@ -27,6 +31,7 @@ const annotationsAllIds = createReducer<AnnotationsState['allIds']>([], builder
|
|
|
27
31
|
.addCase(createAnnotationAction.fulfilled, (state, { payload: { id } }) => {
|
|
28
32
|
state.push(id);
|
|
29
33
|
})
|
|
34
|
+
.addCase(deleteAnnotationAction.fulfilled, (state, { payload: id }) => state.filter(annotationId => annotationId !== id))
|
|
30
35
|
.addCase(removeAnnotationAction, (state, { payload: id }) => state.filter(annotationId => annotationId !== id))
|
|
31
36
|
.addCase(fetchAnnotationsAction.fulfilled, (state, { payload }) => {
|
|
32
37
|
payload.entries.forEach(({ id }) => state.indexOf(id) === -1 && state.push(id));
|
|
@@ -38,9 +43,21 @@ const annotationsById = createReducer<AnnotationsState['byId']>({}, builder =>
|
|
|
38
43
|
.addCase(createAnnotationAction.fulfilled, (state, { payload }) => {
|
|
39
44
|
state[payload.id] = isDrawing(payload) ? formatDrawing(payload) : payload;
|
|
40
45
|
})
|
|
46
|
+
.addCase(createReplyAction.fulfilled, (state, { payload: { annotationId, reply } }) => {
|
|
47
|
+
const annotation = state[annotationId];
|
|
48
|
+
if (annotation) {
|
|
49
|
+
annotation.replies = [...(annotation.replies ?? []), reply];
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.addCase(deleteAnnotationAction.fulfilled, (state, { payload: id }) => {
|
|
53
|
+
delete state[id];
|
|
54
|
+
})
|
|
41
55
|
.addCase(removeAnnotationAction, (state, { payload: id }) => {
|
|
42
56
|
delete state[id];
|
|
43
57
|
})
|
|
58
|
+
.addCase(updateAnnotationAction.fulfilled, (state, { payload }) => {
|
|
59
|
+
state[payload.id] = isDrawing(payload) ? formatDrawing(payload) : payload;
|
|
60
|
+
})
|
|
44
61
|
.addCase(fetchAnnotationsAction.fulfilled, (state, { payload }) => {
|
|
45
62
|
payload.entries.forEach(annotation => {
|
|
46
63
|
state[annotation.id] = isDrawing(annotation) ? formatDrawing(annotation) : annotation;
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
setViewModeAction,
|
|
10
10
|
} from './actions';
|
|
11
11
|
|
|
12
|
-
export const initialState = {
|
|
12
|
+
export const initialState: OptionsState = {
|
|
13
|
+
apiHost: '',
|
|
13
14
|
features: {},
|
|
14
15
|
fileId: null,
|
|
15
16
|
fileVersionId: null,
|
|
@@ -17,6 +18,7 @@ export const initialState = {
|
|
|
17
18
|
permissions: {},
|
|
18
19
|
rotation: 0,
|
|
19
20
|
scale: 1,
|
|
21
|
+
token: '',
|
|
20
22
|
viewMode: 'annotations' as ViewMode,
|
|
21
23
|
};
|
|
22
24
|
|
|
@@ -6,6 +6,7 @@ import { ViewMode } from './types';
|
|
|
6
6
|
|
|
7
7
|
type State = Pick<AppState, 'options'>;
|
|
8
8
|
|
|
9
|
+
export const getApiHost = (state: State): string => state.options.apiHost;
|
|
9
10
|
export const getFeatures = (state: State): Features => state.options.features;
|
|
10
11
|
export const getViewMode = (state: State): ViewMode => state.options.viewMode;
|
|
11
12
|
export const getFileId = (state: State): string | null => state.options.fileId;
|
|
@@ -14,5 +15,6 @@ export const getIsCurrentFileVersion = (state: State): boolean => state.options.
|
|
|
14
15
|
export const getPermissions = (state: State): Permissions => state.options.permissions;
|
|
15
16
|
export const getRotation = (state: State): number => state.options.rotation;
|
|
16
17
|
export const getScale = (state: State): number => state.options.scale;
|
|
18
|
+
export const getToken = (state: State): string => state.options.token;
|
|
17
19
|
export const isFeatureEnabled = (state: State, featurename: string): boolean =>
|
|
18
20
|
getProp(getFeatures(state), featurename, false);
|
|
@@ -5,6 +5,7 @@ import { Permissions } from '../../@types';
|
|
|
5
5
|
export type ViewMode = 'annotations' | 'boundingBoxes';
|
|
6
6
|
|
|
7
7
|
export type OptionsState = {
|
|
8
|
+
apiHost: string;
|
|
8
9
|
features: Features;
|
|
9
10
|
fileId: string | null;
|
|
10
11
|
fileVersionId: string | null;
|
|
@@ -12,5 +13,6 @@ export type OptionsState = {
|
|
|
12
13
|
permissions: Permissions;
|
|
13
14
|
rotation: number;
|
|
14
15
|
scale: number;
|
|
16
|
+
token: string;
|
|
15
17
|
viewMode: ViewMode;
|
|
16
18
|
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Placeholder for the threaded annotation reply popup (V2).
|
|
5
|
-
* Replaces PopupReply when the isThreadedAnnotation feature flag is enabled.
|
|
6
|
-
* Currently renders an empty container — props will be wired up when V2 is implemented.
|
|
7
|
-
*/
|
|
8
|
-
export default function PopupReplyV2(): JSX.Element {
|
|
9
|
-
return <div data-testid="popup-reply-v2" />;
|
|
10
|
-
}
|