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,12 +4,13 @@ import ReactDOM from 'react-dom';
|
|
|
4
4
|
import { useIntl } from 'react-intl';
|
|
5
5
|
import { useDispatch, useSelector } from 'react-redux';
|
|
6
6
|
|
|
7
|
-
import { BlueprintModernizationProvider } from '@box/blueprint-web';
|
|
7
|
+
import { BlueprintModernizationProvider, TooltipProvider } from '@box/blueprint-web';
|
|
8
8
|
import {
|
|
9
9
|
MentionContextProvider,
|
|
10
10
|
MessageEditorV2,
|
|
11
11
|
ThreadedAnnotationsV2,
|
|
12
12
|
serializeMentionMarkup,
|
|
13
|
+
serializeMessageToMarkdown,
|
|
13
14
|
} from '@box/threaded-annotations';
|
|
14
15
|
import type { DocumentNodeV2, TextMessageTypeV2 } from '@box/threaded-annotations';
|
|
15
16
|
import type { FetchedAvatarUrls, UserContactType } from '@box/user-selector';
|
|
@@ -17,16 +18,20 @@ import type { JSONContent } from '@tiptap/core';
|
|
|
17
18
|
import FocusTrap from 'box-ui-elements/es/components/focus-trap/FocusTrap';
|
|
18
19
|
|
|
19
20
|
import { annotationToMessages, collaboratorToUserContact } from '../../adapters/threadedAnnotationsAdapters';
|
|
21
|
+
import AnnotationCallbacksContext from '../../common/AnnotationCallbacksContext';
|
|
20
22
|
import {
|
|
21
23
|
createReplyAction,
|
|
22
24
|
deleteAnnotationAction,
|
|
25
|
+
deleteReplyAction,
|
|
23
26
|
setActiveAnnotationIdAction,
|
|
24
27
|
updateAnnotationAction,
|
|
28
|
+
updateReplyAction,
|
|
25
29
|
} from '../../store/annotations/actions';
|
|
26
30
|
import { getAnnotation } from '../../store/annotations/selectors';
|
|
27
|
-
import { getApiHost, getToken } from '../../store/options';
|
|
31
|
+
import { getApiHost, getFileId, getFileVersionId, getToken, isFeatureEnabled } from '../../store/options';
|
|
28
32
|
import { fetchCollaboratorsAction } from '../../store/users/actions';
|
|
29
33
|
|
|
34
|
+
import type { Token, TokenLiteral, TokenMap } from '../../@types';
|
|
30
35
|
import type { AppState, AppThunkDispatch } from '../../store/types';
|
|
31
36
|
|
|
32
37
|
import createPopper, { PopupReference } from './Popper';
|
|
@@ -62,12 +67,35 @@ const createDocumentNode = (content: JSONContent | null): DocumentNodeV2 => {
|
|
|
62
67
|
return { type: 'doc', content: [content] } as DocumentNodeV2;
|
|
63
68
|
};
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
const serializePopupMessage = (doc: DocumentNodeV2, isRichTextEnabled: boolean): string =>
|
|
71
|
+
isRichTextEnabled ? serializeMessageToMarkdown(doc) : serializeMentionMarkup(doc).text;
|
|
72
|
+
|
|
73
|
+
const literalToString = (literal: TokenLiteral): string | null => {
|
|
74
|
+
if (!literal) return null;
|
|
75
|
+
if (typeof literal === 'string') return literal;
|
|
76
|
+
return literal.read ?? literal.write ?? null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const resolveStringToken = async (token: Token, typedFileId: string): Promise<string | null> => {
|
|
80
|
+
const resolved = typeof token === 'function' ? await token(typedFileId) : token;
|
|
81
|
+
if (!resolved) return null;
|
|
82
|
+
if (typeof resolved === 'string') return resolved;
|
|
83
|
+
if (typedFileId in resolved) return literalToString((resolved as TokenMap)[typedFileId]);
|
|
84
|
+
return literalToString(resolved as TokenLiteral);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const fetchAvatarBlob = async (
|
|
88
|
+
apiHost: string,
|
|
89
|
+
token: Token,
|
|
90
|
+
fileId: string | null,
|
|
91
|
+
userId: string,
|
|
92
|
+
): Promise<string | null> => {
|
|
68
93
|
try {
|
|
94
|
+
if (!fileId) return null;
|
|
95
|
+
const stringToken = await resolveStringToken(token, `file_${fileId}`);
|
|
96
|
+
if (!stringToken) return null;
|
|
69
97
|
const response = await fetch(`${apiHost}/2.0/users/${userId}/avatar?pic_type=large`, {
|
|
70
|
-
headers: { Authorization: `Bearer ${
|
|
98
|
+
headers: { Authorization: `Bearer ${stringToken}` },
|
|
71
99
|
});
|
|
72
100
|
if (!response.ok) return null;
|
|
73
101
|
const blob = await response.blob();
|
|
@@ -80,12 +108,23 @@ const fetchAvatarBlob = async (apiHost: string, token: string, userId: string):
|
|
|
80
108
|
const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): JSX.Element | null => {
|
|
81
109
|
const intl = useIntl();
|
|
82
110
|
const dispatch = useDispatch<AppThunkDispatch>();
|
|
111
|
+
const { onCopyLink: consumerOnCopyLink } = React.useContext(AnnotationCallbacksContext);
|
|
83
112
|
const popupRef = React.useRef<HTMLDivElement>(null);
|
|
84
113
|
const popperRef = React.useRef<Instance>();
|
|
85
114
|
const optionsRef = React.useRef<Partial<Options>>(getPopupOptions());
|
|
86
115
|
|
|
87
116
|
const apiHost = useSelector(getApiHost);
|
|
117
|
+
const fileId = useSelector(getFileId);
|
|
118
|
+
const fileVersionId = useSelector(getFileVersionId);
|
|
119
|
+
const isRichTextEnabled = useSelector((state: AppState) => isFeatureEnabled(state, 'isRichTextEnabled'));
|
|
88
120
|
const token = useSelector(getToken);
|
|
121
|
+
const onCopyLink = React.useMemo(
|
|
122
|
+
() =>
|
|
123
|
+
consumerOnCopyLink && annotationId && fileVersionId
|
|
124
|
+
? () => consumerOnCopyLink({ annotationId, fileVersionId })
|
|
125
|
+
: undefined,
|
|
126
|
+
[consumerOnCopyLink, annotationId, fileVersionId],
|
|
127
|
+
);
|
|
89
128
|
const annotation = useSelector((state: AppState) =>
|
|
90
129
|
annotationId ? getAnnotation(state, annotationId) : undefined,
|
|
91
130
|
);
|
|
@@ -101,12 +140,9 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
101
140
|
if (cached) return cached;
|
|
102
141
|
const capturedApiHost = apiHost;
|
|
103
142
|
const capturedToken = token;
|
|
104
|
-
const url = await fetchAvatarBlob(capturedApiHost, capturedToken, userId);
|
|
143
|
+
const url = await fetchAvatarBlob(capturedApiHost, capturedToken, fileId, userId);
|
|
105
144
|
if (!url) return null;
|
|
106
|
-
if (
|
|
107
|
-
credentialsRef.current.apiHost !== capturedApiHost ||
|
|
108
|
-
credentialsRef.current.token !== capturedToken
|
|
109
|
-
) {
|
|
145
|
+
if (credentialsRef.current.apiHost !== capturedApiHost || credentialsRef.current.token !== capturedToken) {
|
|
110
146
|
URL.revokeObjectURL(url);
|
|
111
147
|
return null;
|
|
112
148
|
}
|
|
@@ -118,7 +154,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
118
154
|
avatarCacheRef.current.set(userId, url);
|
|
119
155
|
return url;
|
|
120
156
|
},
|
|
121
|
-
[apiHost, token],
|
|
157
|
+
[apiHost, fileId, token],
|
|
122
158
|
);
|
|
123
159
|
|
|
124
160
|
React.useEffect(() => {
|
|
@@ -153,7 +189,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
153
189
|
if (!annotation) return undefined;
|
|
154
190
|
|
|
155
191
|
const userIds = Array.from(
|
|
156
|
-
new Set(annotationToMessages(annotation).map(msg => String(msg.author.id))),
|
|
192
|
+
new Set(annotationToMessages(annotation, isRichTextEnabled).map(msg => String(msg.author.id))),
|
|
157
193
|
);
|
|
158
194
|
let cancelled = false;
|
|
159
195
|
|
|
@@ -171,26 +207,24 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
171
207
|
return () => {
|
|
172
208
|
cancelled = true;
|
|
173
209
|
};
|
|
174
|
-
}, [annotation, getOrFetchAvatarBlob]);
|
|
210
|
+
}, [annotation, getOrFetchAvatarBlob, isRichTextEnabled]);
|
|
175
211
|
|
|
176
212
|
const threadMessages: TextMessageTypeV2[] = React.useMemo(() => {
|
|
177
213
|
if (!annotation) return [];
|
|
178
|
-
return annotationToMessages(annotation).map(msg => ({
|
|
214
|
+
return annotationToMessages(annotation, isRichTextEnabled).map(msg => ({
|
|
179
215
|
...msg,
|
|
180
216
|
author: {
|
|
181
217
|
...msg.author,
|
|
182
218
|
avatarUrl: avatarBlobs[String(msg.author.id)],
|
|
183
219
|
},
|
|
184
220
|
}));
|
|
185
|
-
}, [annotation, avatarBlobs]);
|
|
221
|
+
}, [annotation, avatarBlobs, isRichTextEnabled]);
|
|
186
222
|
|
|
187
223
|
const isResolved = annotation?.status === 'resolved';
|
|
188
224
|
const resolvedBy = isResolved
|
|
189
|
-
? annotation?.resolution?.resolved_by?.name ?? annotation?.modified_by?.name
|
|
190
|
-
: undefined;
|
|
191
|
-
const resolvedAtSource = isResolved
|
|
192
|
-
? annotation?.resolution?.resolved_at ?? annotation?.modified_at
|
|
225
|
+
? (annotation?.resolution?.resolved_by?.name ?? annotation?.modified_by?.name)
|
|
193
226
|
: undefined;
|
|
227
|
+
const resolvedAtSource = isResolved ? (annotation?.resolution?.resolved_at ?? annotation?.modified_at) : undefined;
|
|
194
228
|
const resolvedAt = resolvedAtSource ? new Date(resolvedAtSource).getTime() : undefined;
|
|
195
229
|
|
|
196
230
|
const userSelectorProps = React.useMemo(
|
|
@@ -225,44 +259,57 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
225
259
|
const handlePost = React.useCallback(
|
|
226
260
|
async (content: JSONContent | null): Promise<void> => {
|
|
227
261
|
const doc = createDocumentNode(content);
|
|
228
|
-
const
|
|
262
|
+
const text = serializePopupMessage(doc, isRichTextEnabled);
|
|
229
263
|
if (annotationId) {
|
|
230
264
|
await dispatch(createReplyAction({ annotationId, message: text }));
|
|
231
265
|
} else {
|
|
232
266
|
onSubmit(text);
|
|
233
267
|
}
|
|
234
268
|
},
|
|
235
|
-
[annotationId, dispatch, onSubmit],
|
|
269
|
+
[annotationId, dispatch, isRichTextEnabled, onSubmit],
|
|
236
270
|
);
|
|
237
271
|
|
|
238
|
-
const
|
|
239
|
-
async (): Promise<void> => {
|
|
240
|
-
if (annotationId) {
|
|
241
|
-
await dispatch(deleteAnnotationAction(annotationId));
|
|
242
|
-
}
|
|
243
|
-
},
|
|
244
|
-
[annotationId, dispatch],
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
const handleResolve = React.useCallback(
|
|
248
|
-
async (): Promise<void> => {
|
|
272
|
+
const handleEdit = React.useCallback(
|
|
273
|
+
async (id: string, content: JSONContent | null): Promise<void> => {
|
|
249
274
|
if (!annotationId) return;
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
275
|
+
const doc = createDocumentNode(content);
|
|
276
|
+
const text = serializePopupMessage(doc, isRichTextEnabled);
|
|
277
|
+
if (id === annotationId) {
|
|
278
|
+
await dispatch(updateAnnotationAction({ annotationId, payload: { message: text } }));
|
|
279
|
+
return;
|
|
253
280
|
}
|
|
281
|
+
await dispatch(updateReplyAction({ annotationId, replyId: id, payload: { message: text } }));
|
|
254
282
|
},
|
|
255
|
-
[annotationId, dispatch],
|
|
283
|
+
[annotationId, dispatch, isRichTextEnabled],
|
|
256
284
|
);
|
|
257
285
|
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
286
|
+
const handleThreadDelete = React.useCallback(async (): Promise<void> => {
|
|
287
|
+
if (annotationId) {
|
|
288
|
+
await dispatch(deleteAnnotationAction(annotationId));
|
|
289
|
+
}
|
|
290
|
+
}, [annotationId, dispatch]);
|
|
291
|
+
|
|
292
|
+
const handleDelete = React.useCallback(
|
|
293
|
+
async (id: string): Promise<void> => {
|
|
294
|
+
if (!annotationId || id === annotationId) return;
|
|
295
|
+
await dispatch(deleteReplyAction({ annotationId, replyId: id }));
|
|
262
296
|
},
|
|
263
297
|
[annotationId, dispatch],
|
|
264
298
|
);
|
|
265
299
|
|
|
300
|
+
const handleResolve = React.useCallback(async (): Promise<void> => {
|
|
301
|
+
if (!annotationId) return;
|
|
302
|
+
const result = await dispatch(updateAnnotationAction({ annotationId, payload: { status: 'resolved' } }));
|
|
303
|
+
if (updateAnnotationAction.fulfilled.match(result)) {
|
|
304
|
+
dispatch(setActiveAnnotationIdAction(null));
|
|
305
|
+
}
|
|
306
|
+
}, [annotationId, dispatch]);
|
|
307
|
+
|
|
308
|
+
const handleUnresolve = React.useCallback(async (): Promise<void> => {
|
|
309
|
+
if (!annotationId) return;
|
|
310
|
+
await dispatch(updateAnnotationAction({ annotationId, payload: { status: 'open' } }));
|
|
311
|
+
}, [annotationId, dispatch]);
|
|
312
|
+
|
|
266
313
|
if (!popupPortalEl) return null;
|
|
267
314
|
|
|
268
315
|
return ReactDOM.createPortal(
|
|
@@ -270,45 +317,54 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
270
317
|
<div
|
|
271
318
|
ref={popupRef}
|
|
272
319
|
aria-label={intl.formatMessage(messages.ariaLabelComment)}
|
|
273
|
-
className="ba-
|
|
320
|
+
className="ba-PopupAnnotation"
|
|
274
321
|
data-resin-component={annotationId ? 'popupThreadV2' : 'popupReplyV2'}
|
|
275
|
-
|
|
322
|
+
data-resin-feature="annotations"
|
|
276
323
|
onMouseDown={handleEvent}
|
|
277
324
|
onMouseMove={handleEvent}
|
|
278
|
-
onMouseUp={handleEvent}
|
|
279
325
|
onTouchMove={handleEvent}
|
|
280
326
|
onTouchStart={handleEvent}
|
|
281
327
|
role="presentation"
|
|
282
328
|
>
|
|
283
329
|
<BlueprintModernizationProvider enableModernizedComponents>
|
|
284
|
-
<
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
330
|
+
<TooltipProvider>
|
|
331
|
+
<MentionContextProvider
|
|
332
|
+
value={{
|
|
333
|
+
collaborationPopoverProps: {
|
|
334
|
+
onSubmit: () => Promise.resolve(),
|
|
335
|
+
},
|
|
336
|
+
// Mentions are restricted to file collaborators; the invite popover is intentionally never shown.
|
|
337
|
+
fetchCollaboratorState: async () => true,
|
|
338
|
+
}}
|
|
339
|
+
>
|
|
340
|
+
{annotationId ? (
|
|
341
|
+
<ThreadedAnnotationsV2
|
|
342
|
+
isAnnotations
|
|
343
|
+
isResolved={isResolved}
|
|
344
|
+
isRichTextEnabled={isRichTextEnabled}
|
|
345
|
+
messages={threadMessages}
|
|
346
|
+
onAvatarClick={noop}
|
|
347
|
+
onCopyLink={onCopyLink}
|
|
348
|
+
onDelete={handleDelete}
|
|
349
|
+
onEdit={handleEdit}
|
|
350
|
+
onPost={handlePost}
|
|
351
|
+
onResolve={handleResolve}
|
|
352
|
+
onThreadDelete={handleThreadDelete}
|
|
353
|
+
onUnresolve={handleUnresolve}
|
|
354
|
+
resolvedAt={resolvedAt}
|
|
355
|
+
resolvedBy={resolvedBy}
|
|
356
|
+
userSelectorProps={userSelectorProps}
|
|
357
|
+
/>
|
|
358
|
+
) : (
|
|
359
|
+
<MessageEditorV2
|
|
360
|
+
isFirstAnnotation
|
|
361
|
+
isRichTextEnabled={isRichTextEnabled}
|
|
362
|
+
onPost={handlePost}
|
|
363
|
+
userSelectorProps={userSelectorProps}
|
|
364
|
+
/>
|
|
365
|
+
)}
|
|
366
|
+
</MentionContextProvider>
|
|
367
|
+
</TooltipProvider>
|
|
312
368
|
</BlueprintModernizationProvider>
|
|
313
369
|
<div data-threaded-annotations-portal />
|
|
314
370
|
</div>
|
|
@@ -70,14 +70,7 @@ const ReplyForm = (props: Props): JSX.Element => {
|
|
|
70
70
|
}, [formRef]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
71
71
|
|
|
72
72
|
return (
|
|
73
|
-
<Form
|
|
74
|
-
ref={formRef}
|
|
75
|
-
className="ba-ReplyForm"
|
|
76
|
-
data-testid="ba-ReplyForm"
|
|
77
|
-
onPointerEnterCapture={undefined}
|
|
78
|
-
onPointerLeaveCapture={undefined}
|
|
79
|
-
placeholder={undefined}
|
|
80
|
-
>
|
|
73
|
+
<Form ref={formRef} className="ba-ReplyForm" data-testid="ba-ReplyForm">
|
|
81
74
|
<div className="ba-Popup-main">
|
|
82
75
|
<ReplyField
|
|
83
76
|
className="ba-Popup-field"
|
package/src/constants.ts
CHANGED
|
@@ -9,14 +9,13 @@ export const MOUSE_PRIMARY = 1;
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* This is the default location index for video annotations.
|
|
12
|
-
* Documents have locations that represent a page but that doesn't apply to
|
|
12
|
+
* Documents have locations that represent a page but that doesn't apply to
|
|
13
13
|
* videos as they have no pages. The annnotations managers take locations as
|
|
14
|
-
* parameters so we need to provide a default value that indicates that this is a
|
|
14
|
+
* parameters so we need to provide a default value that indicates that this is a
|
|
15
15
|
* video annotation.
|
|
16
16
|
*/
|
|
17
17
|
export const MEDIA_LOCATION_INDEX = -1;
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
export enum TARGET_TYPE {
|
|
21
20
|
FRAME = 'frame',
|
|
22
21
|
PAGE = 'page',
|
|
@@ -97,13 +97,29 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
97
97
|
// Lazily instantiate managers as pages are added or re-rendered
|
|
98
98
|
if (managers.size === 0) {
|
|
99
99
|
if (viewMode === 'boundingBoxes') {
|
|
100
|
-
managers.add(
|
|
100
|
+
managers.add(
|
|
101
|
+
new BoundingBoxHighlightManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }),
|
|
102
|
+
);
|
|
101
103
|
return managers;
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
// Annotations mode
|
|
105
|
-
managers.add(
|
|
106
|
-
|
|
107
|
+
managers.add(
|
|
108
|
+
new PopupManager({
|
|
109
|
+
location: pageNumber,
|
|
110
|
+
popupPortalEl: this.popupPortalEl,
|
|
111
|
+
referenceEl: pageReferenceEl,
|
|
112
|
+
resinTags,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
managers.add(
|
|
116
|
+
new DrawingManager({
|
|
117
|
+
location: pageNumber,
|
|
118
|
+
popupPortalEl: this.popupPortalEl,
|
|
119
|
+
referenceEl: pageReferenceEl,
|
|
120
|
+
resinTags,
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
107
123
|
|
|
108
124
|
const textLayer = pageEl.querySelector('.textLayer') as HTMLElement;
|
|
109
125
|
|
|
@@ -118,7 +134,14 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
118
134
|
);
|
|
119
135
|
}
|
|
120
136
|
|
|
121
|
-
managers.add(
|
|
137
|
+
managers.add(
|
|
138
|
+
new HighlightManager({
|
|
139
|
+
location: pageNumber,
|
|
140
|
+
popupPortalEl: this.popupPortalEl,
|
|
141
|
+
referenceEl: pageReferenceEl,
|
|
142
|
+
resinTags,
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
122
145
|
managers.add(new RegionManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
|
|
123
146
|
|
|
124
147
|
const canvasLayerEl = pageEl.querySelector<HTMLElement>('.canvasWrapper');
|
|
@@ -220,10 +243,11 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
220
243
|
width: `${origWidth}px`,
|
|
221
244
|
});
|
|
222
245
|
} else {
|
|
223
|
-
manager.style({height: '', left: '', top: '', transform: '', width: ''});
|
|
246
|
+
manager.style({ height: '', left: '', top: '', transform: '', width: '' });
|
|
224
247
|
}
|
|
225
248
|
|
|
226
249
|
manager.render({
|
|
250
|
+
callbacks: { onCopyLink: this.onCopyLink },
|
|
227
251
|
intl: this.intl,
|
|
228
252
|
store: this.store,
|
|
229
253
|
});
|
package/src/document/docUtil.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
type Selection = {
|
|
2
|
+
containerEl: HTMLElement;
|
|
2
3
|
containerRect: DOMRect;
|
|
3
4
|
hasError?: boolean;
|
|
4
5
|
location: number;
|
|
@@ -61,5 +62,11 @@ export function getSelection(): Selection | null {
|
|
|
61
62
|
location = getPageNumber(selection.focusNode as Element) ?? endPage;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
return {
|
|
65
|
+
return {
|
|
66
|
+
containerEl: containerEl as HTMLElement,
|
|
67
|
+
containerRect: containerEl.getBoundingClientRect(),
|
|
68
|
+
hasError,
|
|
69
|
+
location,
|
|
70
|
+
range,
|
|
71
|
+
};
|
|
65
72
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
2
3
|
import classNames from 'classnames';
|
|
3
4
|
import DecoratedDrawingPath from './DecoratedDrawingPath';
|
|
4
5
|
import DrawingList from './DrawingList';
|
|
@@ -22,8 +23,10 @@ export type Props = {
|
|
|
22
23
|
drawnPathGroups: Array<PathGroup>;
|
|
23
24
|
isCreating: boolean;
|
|
24
25
|
location: number;
|
|
26
|
+
popupPortalEl?: HTMLElement | null;
|
|
25
27
|
redoDrawingPathGroup: () => void;
|
|
26
28
|
resetDrawing: () => void;
|
|
29
|
+
rotation: number;
|
|
27
30
|
setActiveAnnotationId: (annotationId: string | null) => void;
|
|
28
31
|
setReferenceId: (uuid: string) => void;
|
|
29
32
|
setStaged: (staged: CreatorItemDrawing | null) => void;
|
|
@@ -45,8 +48,10 @@ const DrawingAnnotations = (props: Props): JSX.Element => {
|
|
|
45
48
|
drawnPathGroups,
|
|
46
49
|
isCreating,
|
|
47
50
|
location,
|
|
51
|
+
popupPortalEl,
|
|
48
52
|
redoDrawingPathGroup,
|
|
49
53
|
resetDrawing,
|
|
54
|
+
rotation,
|
|
50
55
|
setActiveAnnotationId,
|
|
51
56
|
setReferenceId,
|
|
52
57
|
setStaged,
|
|
@@ -86,7 +91,7 @@ const DrawingAnnotations = (props: Props): JSX.Element => {
|
|
|
86
91
|
redoDrawingPathGroup();
|
|
87
92
|
};
|
|
88
93
|
|
|
89
|
-
const getCurrentLocation = (): number => {
|
|
94
|
+
const getCurrentLocation = (): number => {
|
|
90
95
|
if (targetType === TARGET_TYPE.FRAME) {
|
|
91
96
|
return getCurrentVideoLocation();
|
|
92
97
|
}
|
|
@@ -116,12 +121,11 @@ const DrawingAnnotations = (props: Props): JSX.Element => {
|
|
|
116
121
|
}
|
|
117
122
|
}, [drawnPathGroups]);
|
|
118
123
|
|
|
119
|
-
|
|
120
124
|
let annotationsToShow: AnnotationDrawing[] = [];
|
|
121
|
-
// For video annotations, we only show the active annotation and we wait for the video
|
|
125
|
+
// For video annotations, we only show the active annotation and we wait for the video
|
|
122
126
|
// to seek to the annotation location before showing it. This prevents annoations from being
|
|
123
127
|
// shown on the incorrect frame while the video is seeking.
|
|
124
|
-
if (targetType === TARGET_TYPE.FRAME
|
|
128
|
+
if (targetType === TARGET_TYPE.FRAME) {
|
|
125
129
|
if (!isVideoSeeking && activeAnnotationId) {
|
|
126
130
|
annotationsToShow = annotations.filter(annotation => annotation.id === activeAnnotationId);
|
|
127
131
|
}
|
|
@@ -129,9 +133,6 @@ const DrawingAnnotations = (props: Props): JSX.Element => {
|
|
|
129
133
|
annotationsToShow = annotations;
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
136
|
return (
|
|
136
137
|
<>
|
|
137
138
|
<DrawingList
|
|
@@ -170,23 +171,31 @@ const DrawingAnnotations = (props: Props): JSX.Element => {
|
|
|
170
171
|
color={color}
|
|
171
172
|
onStart={handleStart}
|
|
172
173
|
onStop={handleStop}
|
|
174
|
+
rotation={rotation}
|
|
173
175
|
/>
|
|
174
176
|
)}
|
|
175
177
|
|
|
176
|
-
{isCreating &&
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
178
|
+
{isCreating &&
|
|
179
|
+
hasPathGroups &&
|
|
180
|
+
drawingSVGGroup &&
|
|
181
|
+
canShowPopupToolbar &&
|
|
182
|
+
(() => {
|
|
183
|
+
const toolbar = (
|
|
184
|
+
<PopupDrawingToolbar
|
|
185
|
+
ref={popupDrawingToolbarRef}
|
|
186
|
+
canComment={hasDrawnPathGroups}
|
|
187
|
+
canRedo={hasStashedPathGroups}
|
|
188
|
+
canUndo={hasDrawnPathGroups}
|
|
189
|
+
className={classNames('ba-DrawingAnnotations-toolbar', { 'ba-is-drawing': isDrawing })}
|
|
190
|
+
onDelete={handleDelete}
|
|
191
|
+
onRedo={handleRedo}
|
|
192
|
+
onReply={handleReply}
|
|
193
|
+
onUndo={handleUndo}
|
|
194
|
+
reference={drawingSVGGroup}
|
|
195
|
+
/>
|
|
196
|
+
);
|
|
197
|
+
return popupPortalEl ? ReactDOM.createPortal(toolbar, popupPortalEl) : toolbar;
|
|
198
|
+
})()}
|
|
190
199
|
</>
|
|
191
200
|
);
|
|
192
201
|
};
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
getAnnotationsForLocation,
|
|
19
19
|
getColor,
|
|
20
20
|
getCreatorStatus,
|
|
21
|
+
getRotation,
|
|
21
22
|
Mode,
|
|
22
23
|
setActiveAnnotationIdAction,
|
|
23
24
|
setReferenceIdAction,
|
|
@@ -34,6 +35,7 @@ export type Props = {
|
|
|
34
35
|
canShowPopupToolbar: boolean;
|
|
35
36
|
drawnPathGroups: Array<PathGroup>;
|
|
36
37
|
isCreating: boolean;
|
|
38
|
+
rotation: number;
|
|
37
39
|
stashedPathGroups: Array<PathGroup>;
|
|
38
40
|
};
|
|
39
41
|
|
|
@@ -47,6 +49,7 @@ export const mapStateToProps = (state: AppState, { location }: { location: numbe
|
|
|
47
49
|
canShowPopupToolbar: creatorStatus === CreatorStatus.started,
|
|
48
50
|
drawnPathGroups: getDrawingDrawnPathGroupsForLocation(state, location),
|
|
49
51
|
isCreating: getAnnotationMode(state) === Mode.DRAWING && creatorStatus !== CreatorStatus.pending,
|
|
52
|
+
rotation: getRotation(state),
|
|
50
53
|
stashedPathGroups: getStashedDrawnPathGroupsForLocation(state, location),
|
|
51
54
|
};
|
|
52
55
|
};
|
|
@@ -7,6 +7,7 @@ import DrawingSVG, { DrawingSVGRef } from './DrawingSVG';
|
|
|
7
7
|
import PointerCapture, { PointerCaptureRef, Status as DrawingStatus } from '../components/PointerCapture';
|
|
8
8
|
import { getDrawingCursor } from './DrawingCursor';
|
|
9
9
|
import { getPathCommands } from './drawingUtil';
|
|
10
|
+
import { getElementLocalPosition } from '../utils/rotate';
|
|
10
11
|
import { PathGroup, Position } from '../@types';
|
|
11
12
|
import './DrawingCreator.scss';
|
|
12
13
|
|
|
@@ -15,6 +16,7 @@ export type Props = {
|
|
|
15
16
|
color?: string;
|
|
16
17
|
onStart: () => void;
|
|
17
18
|
onStop: (pathGroup: PathGroup) => void;
|
|
19
|
+
rotation?: number;
|
|
18
20
|
size?: number;
|
|
19
21
|
};
|
|
20
22
|
|
|
@@ -26,6 +28,7 @@ export default function DrawingCreator({
|
|
|
26
28
|
color = defaultStrokeColor,
|
|
27
29
|
onStart,
|
|
28
30
|
onStop,
|
|
31
|
+
rotation = 0,
|
|
29
32
|
size = defaultStrokeSize,
|
|
30
33
|
}: Props): JSX.Element {
|
|
31
34
|
const [drawingStatus, setDrawingStatus] = React.useState<DrawingStatus>(DrawingStatus.init);
|
|
@@ -45,7 +48,9 @@ export default function DrawingCreator({
|
|
|
45
48
|
return [];
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
// Get the element's dimensions (before any rotation is applied)
|
|
52
|
+
const width = creatorEl.offsetWidth;
|
|
53
|
+
const height = creatorEl.offsetHeight;
|
|
49
54
|
const { size: minSize } = stroke;
|
|
50
55
|
const MAX_X = width - minSize;
|
|
51
56
|
const MAX_Y = height - minSize;
|
|
@@ -56,21 +61,9 @@ export default function DrawingCreator({
|
|
|
56
61
|
}));
|
|
57
62
|
}, [stroke]);
|
|
58
63
|
|
|
59
|
-
const getPosition = (x: number, y: number): [number, number] => {
|
|
60
|
-
const { current: creatorEl } = creatorElRef;
|
|
61
|
-
|
|
62
|
-
if (!creatorEl) {
|
|
63
|
-
return [x, y];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Calculate the new position based on the mouse position less the page offset
|
|
67
|
-
const { left, top } = creatorEl.getBoundingClientRect();
|
|
68
|
-
return [x - left, y - top];
|
|
69
|
-
};
|
|
70
|
-
|
|
71
64
|
// Drawing Lifecycle Callbacks
|
|
72
65
|
const startDraw = (x: number, y: number): void => {
|
|
73
|
-
const [x1, y1] =
|
|
66
|
+
const [x1, y1] = getElementLocalPosition(x, y, creatorElRef.current, rotation);
|
|
74
67
|
|
|
75
68
|
setDrawingStatus(DrawingStatus.dragging);
|
|
76
69
|
|
|
@@ -100,7 +93,7 @@ export default function DrawingCreator({
|
|
|
100
93
|
|
|
101
94
|
const updateDraw = React.useCallback(
|
|
102
95
|
(x: number, y: number): void => {
|
|
103
|
-
const [x2, y2] =
|
|
96
|
+
const [x2, y2] = getElementLocalPosition(x, y, creatorElRef.current, rotation);
|
|
104
97
|
const { current: points } = capturedPointsRef;
|
|
105
98
|
|
|
106
99
|
points.push({ x: x2, y: y2 });
|
|
@@ -111,7 +104,7 @@ export default function DrawingCreator({
|
|
|
111
104
|
onStart();
|
|
112
105
|
}
|
|
113
106
|
},
|
|
114
|
-
[drawingStatus, onStart, setDrawingStatus],
|
|
107
|
+
[drawingStatus, onStart, rotation, setDrawingStatus],
|
|
115
108
|
);
|
|
116
109
|
|
|
117
110
|
// Event Handlers
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const getDrawingCursor = (color: string): string => {
|
|
2
|
-
const encodedSvg =
|
|
2
|
+
const encodedSvg =
|
|
3
|
+
encodeURIComponent(`<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='32' height='32' viewBox='0 0 32 32'>
|
|
3
4
|
<defs>
|
|
4
5
|
<path id='ba-DrawingCursor-b' d='M23.0868949,12.6610542 L21.7362465,14.0116939 C21.5985448,14.1493947 21.3758783,14.1493947 21.2381766,14.0116939 L17.9860729,10.7596114 C17.8483713,10.6219106 17.8483713,10.3992455 17.9860729,10.2615447 L19.3367214,8.91090502 C19.8845983,8.36303166 20.7752645,8.36303166 21.3260713,8.91090502 L23.0868949,10.6717173 C23.6377017,11.2195906 23.6377017,12.1102511 23.0868949,12.6610542 Z M16.825863,11.4217471 L9.13214756,19.1154126 L8.51102507,22.6751246 C8.4260602,23.1556124 8.8450249,23.5716446 9.32551588,23.4896101 L12.8852509,22.8655618 L20.5789664,15.1718963 C20.7166681,15.0341955 20.7166681,14.8115304 20.5789664,14.6738296 L17.3268627,11.4217471 C17.1862312,11.2840463 16.9635647,11.2840463 16.825863,11.4217471 Z M12.1352162,18.4562067 C11.974076,18.2950675 11.974076,18.0372447 12.1352162,17.8761055 L16.6471438,13.3642072 C16.8082841,13.203068 17.0661085,13.203068 17.2272488,13.3642072 C17.388389,13.5253464 17.388389,13.7831692 17.2272488,13.9443084 L12.7153212,18.4562067 C12.5541809,18.6173459 12.2963565,18.6173459 12.1352162,18.4562067 L12.1352162,18.4562067 Z M11.0775501,20.9201719 L12.4838652,20.9201719 L12.4838652,21.9836908 L10.5941293,22.3147587 L9.6829543,21.4035896 L10.0140243,19.513866 L11.0775501,19.513866 L11.0775501,20.9201719 Z'/>
|
|
5
6
|
<filter id='ba-DrawingCursor-a' width='196.7%' height='196.7%' x='-48.3%' y='-41.7%' filterUnits='objectBoundingBox'>
|