box-ui-elements 25.1.0-beta.2 → 25.1.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/explorer.js +1 -1
  2. package/dist/openwith.js +1 -1
  3. package/dist/picker.js +1 -1
  4. package/dist/preview.js +1 -1
  5. package/dist/sharing.js +1 -1
  6. package/dist/sidebar.js +1 -1
  7. package/dist/uploader.js +1 -1
  8. package/es/constants.js +1 -1
  9. package/es/elements/content-sidebar/activity-feed/annotations/AnnotationActivity.js +6 -1
  10. package/es/elements/content-sidebar/activity-feed/annotations/AnnotationActivity.js.flow +9 -1
  11. package/es/elements/content-sidebar/activity-feed/annotations/AnnotationActivity.js.map +1 -1
  12. package/es/elements/content-sidebar/activity-feed/common/activity-message/ActivityMessage.js +4 -2
  13. package/es/elements/content-sidebar/activity-feed/common/activity-message/ActivityMessage.js.map +1 -1
  14. package/es/elements/content-sidebar/activity-feed/utils/formatTaggedMessage.js +17 -8
  15. package/es/elements/content-sidebar/activity-feed/utils/formatTaggedMessage.js.flow +29 -11
  16. package/es/elements/content-sidebar/activity-feed/utils/formatTaggedMessage.js.map +1 -1
  17. package/es/features/security-cloud-game/DragCloud.js +3 -0
  18. package/es/features/security-cloud-game/DragCloud.js.flow +17 -2
  19. package/es/features/security-cloud-game/DragCloud.js.map +1 -1
  20. package/es/src/elements/content-sidebar/activity-feed/common/activity-message/ActivityMessage.d.ts +2 -0
  21. package/package.json +3 -3
  22. package/src/elements/content-sidebar/activity-feed/annotations/AnnotationActivity.js +9 -1
  23. package/src/elements/content-sidebar/activity-feed/annotations/__tests__/AnnotationActivity.test.js +149 -0
  24. package/src/elements/content-sidebar/activity-feed/common/activity-message/ActivityMessage.tsx +13 -2
  25. package/src/elements/content-sidebar/activity-feed/common/activity-message/__tests__/ActivityMessage.test.js +30 -0
  26. package/src/elements/content-sidebar/activity-feed/utils/formatTaggedMessage.js +29 -11
  27. package/src/features/security-cloud-game/DragCloud.js +17 -2
@@ -367,4 +367,153 @@ describe('elements/content-sidebar/ActivityFeed/annotations/AnnotationActivity',
367
367
  expect(event.stopPropagation).not.toHaveBeenCalled();
368
368
  });
369
369
  });
370
+
371
+ describe('video annotations', () => {
372
+ const mockVideoAnnotation = {
373
+ ...mockAnnotation,
374
+ target: {
375
+ location: {
376
+ type: 'frame',
377
+ value: 60000, // 1 minute in milliseconds
378
+ },
379
+ },
380
+ };
381
+
382
+ test('should detect video annotation when target location type is frame', () => {
383
+ const wrapper = getWrapper({ item: mockVideoAnnotation });
384
+ const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))');
385
+
386
+ expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBe('0:01:00');
387
+ });
388
+
389
+ test('should not show version link for video annotations even when hasVersions is true', () => {
390
+ const wrapper = getWrapper({
391
+ item: mockVideoAnnotation,
392
+ hasVersions: true,
393
+ isCurrentVersion: true,
394
+ });
395
+
396
+ expect(wrapper.exists('AnnotationActivityLink')).toBe(false);
397
+ });
398
+
399
+ test('should pass correct timestamp format to ActivityMessage for video annotations', () => {
400
+ const videoAnnotationWithTimestamp = {
401
+ ...mockVideoAnnotation,
402
+ target: {
403
+ location: {
404
+ type: 'frame',
405
+ value: 3661000, // 1 hour, 1 minute, 1 second
406
+ },
407
+ },
408
+ };
409
+
410
+ const wrapper = getWrapper({ item: videoAnnotationWithTimestamp });
411
+ const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))');
412
+
413
+ expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBe('1:01:01');
414
+ });
415
+
416
+ test('should handle zero timestamp for video annotations', () => {
417
+ const videoAnnotationWithZeroTimestamp = {
418
+ ...mockVideoAnnotation,
419
+ target: {
420
+ location: {
421
+ type: 'frame',
422
+ value: 0,
423
+ },
424
+ },
425
+ };
426
+
427
+ const wrapper = getWrapper({ item: videoAnnotationWithZeroTimestamp });
428
+ const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))');
429
+
430
+ expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBe('0:00:00');
431
+ });
432
+
433
+ test('should handle undefined timestamp for video annotations', () => {
434
+ const videoAnnotationWithUndefinedTimestamp = {
435
+ ...mockVideoAnnotation,
436
+ target: {
437
+ location: {
438
+ type: 'frame',
439
+ value: undefined,
440
+ },
441
+ },
442
+ };
443
+
444
+ const wrapper = getWrapper({ item: videoAnnotationWithUndefinedTimestamp });
445
+ const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))');
446
+
447
+ expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBe('0:00:00');
448
+ });
449
+
450
+ test('should not pass timestamp to ActivityMessage for non-video annotations', () => {
451
+ const regularAnnotation = {
452
+ ...mockAnnotation,
453
+ target: {
454
+ location: {
455
+ type: 'page',
456
+ value: 1,
457
+ },
458
+ },
459
+ };
460
+
461
+ const wrapper = getWrapper({ item: regularAnnotation });
462
+ const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))');
463
+
464
+ expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBeFalsy();
465
+ });
466
+
467
+ test('should not pass timestamp to ActivityMessage when target location type is missing', () => {
468
+ const annotationWithoutType = {
469
+ ...mockAnnotation,
470
+ target: {
471
+ location: {
472
+ value: 60000,
473
+ },
474
+ },
475
+ };
476
+
477
+ const wrapper = getWrapper({ item: annotationWithoutType });
478
+ const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))');
479
+
480
+ expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBeFalsy();
481
+ });
482
+
483
+ test('should not pass timestamp to ActivityMessage when target is missing', () => {
484
+ const annotationWithoutTarget = {
485
+ ...mockAnnotation,
486
+ target: {
487
+ location: {
488
+ value: 1,
489
+ },
490
+ },
491
+ };
492
+
493
+ const wrapper = getWrapper({ item: annotationWithoutTarget });
494
+ const activityMessage = wrapper.find('ForwardRef(withFeatureConsumer(ActivityMessage))');
495
+
496
+ expect(activityMessage.prop('annotationsMillisecondTimestamp')).toBeFalsy();
497
+ });
498
+
499
+ test('should show version link for non-video annotations when hasVersions is true', () => {
500
+ const regularAnnotation = {
501
+ ...mockAnnotation,
502
+ target: {
503
+ location: {
504
+ type: 'page',
505
+ value: 1,
506
+ },
507
+ },
508
+ };
509
+
510
+ const wrapper = getWrapper({
511
+ item: regularAnnotation,
512
+ hasVersions: true,
513
+ isCurrentVersion: true,
514
+ });
515
+
516
+ expect(wrapper.exists('AnnotationActivityLink')).toBe(true);
517
+ });
518
+ });
370
519
  });
@@ -7,7 +7,7 @@ import LoadingIndicator, { LoadingIndicatorSize } from '../../../../../component
7
7
  import ShowOriginalButton from './ShowOriginalButton';
8
8
  import TranslateButton from './TranslateButton';
9
9
 
10
- import formatTaggedMessage from '../../utils/formatTaggedMessage';
10
+ import formatTaggedMessage, { renderTimestampWithText } from '../../utils/formatTaggedMessage';
11
11
  import { withFeatureConsumer, isFeatureEnabled } from '../../../../common/feature-checking';
12
12
 
13
13
  import messages from './messages';
@@ -22,11 +22,13 @@ export interface ActivityMessageProps extends WrappedComponentProps {
22
22
  getUserProfileUrl?: GetProfileUrlCallback;
23
23
  id: string;
24
24
  isEdited?: boolean;
25
+ onClick?: () => void;
25
26
  onTranslate?: ({ id, tagged_message }: { id: string; tagged_message: string }) => void;
26
27
  tagged_message: string;
27
28
  translatedTaggedMessage?: string;
28
29
  translationEnabled?: boolean;
29
30
  translationFailed?: boolean | null;
31
+ annotationsMillisecondTimestamp?: string | null;
30
32
  }
31
33
 
32
34
  type State = {
@@ -92,6 +94,8 @@ class ActivityMessage extends React.Component<ActivityMessageProps, State> {
92
94
  id,
93
95
  intl,
94
96
  isEdited,
97
+ onClick = noop,
98
+ annotationsMillisecondTimestamp,
95
99
  tagged_message,
96
100
  translatedTaggedMessage,
97
101
  translationEnabled,
@@ -110,7 +114,14 @@ class ActivityMessage extends React.Component<ActivityMessageProps, State> {
110
114
  ) : (
111
115
  <div className="bcs-ActivityMessage">
112
116
  <MessageWrapper>
113
- {formatTaggedMessage(commentToDisplay, id, false, getUserProfileUrl, intl)}
117
+ {annotationsMillisecondTimestamp
118
+ ? renderTimestampWithText(
119
+ annotationsMillisecondTimestamp,
120
+ onClick,
121
+ intl,
122
+ ` ${commentToDisplay}`,
123
+ )
124
+ : formatTaggedMessage(commentToDisplay, id, false, getUserProfileUrl, intl)}
114
125
  {isEdited && (
115
126
  <span className="bcs-ActivityMessage-edited">
116
127
  <FormattedMessage {...messages.activityMessageEdited} />
@@ -1,5 +1,6 @@
1
1
  import React, { act } from 'react';
2
2
  import { mount, shallow } from 'enzyme';
3
+ import { createIntl } from 'react-intl';
3
4
 
4
5
  import { ActivityMessage } from '../ActivityMessage';
5
6
 
@@ -184,4 +185,33 @@ describe('elements/content-sidebar/ActivityFeed/common/activity-message', () =>
184
185
 
185
186
  expect(wrapper.exists({ id: 'be.contentSidebar.activityFeed.common.editedMessage' })).toBe(expected);
186
187
  });
188
+
189
+ describe('video annotation', () => {
190
+ test('should render timestamp with text when annotationsMillisecondTimestamp is provided', () => {
191
+ const onClick = jest.fn();
192
+ const videoAnnotation = {
193
+ annotationsMillisecondTimestamp: '0:01:00',
194
+ tagged_message: 'test',
195
+ onClick,
196
+ };
197
+
198
+ const intl = createIntl({ locale: 'en' });
199
+ const wrapper = mount(<ActivityMessage id="123" {...videoAnnotation} intl={intl} />);
200
+ expect(wrapper.find('button[aria-label="Seek to video timestamp"]').length).toBe(1);
201
+ expect(wrapper.find('button[aria-label="Seek to video timestamp"]').text()).toBe('0:01:00');
202
+ wrapper.find('button[aria-label="Seek to video timestamp"]').simulate('click');
203
+ expect(onClick).toHaveBeenCalled();
204
+ });
205
+
206
+ test('should render original message when annotationsMillisecondTimestamp is not provided', () => {
207
+ const comment = {
208
+ annotationsMillisecondTimestamp: undefined,
209
+ tagged_message: 'test',
210
+ };
211
+ const intl = createIntl({ locale: 'en' });
212
+ const wrapper = mount(<ActivityMessage id="123" {...comment} intl={intl} />);
213
+ expect(wrapper.find('button[aria-label="Seek to video timestamp"]').length).toBe(0);
214
+ expect(wrapper.find('.bcs-ActivityMessage').text()).toBe('test');
215
+ });
216
+ });
187
217
  });
@@ -11,6 +11,34 @@ import UserLink from '../common/user-link';
11
11
  import messages from '../common/activity-message/messages';
12
12
  import { convertTimestampToSeconds, convertMillisecondsToHMMSS } from '../../../../utils/timestamp';
13
13
 
14
+ /**
15
+ * Renders the timestamp button and remaining text
16
+ * @param timestampInHHMMSS The formatted timestamp string (HH:MM:SS)
17
+ * @param timestampLabel The aria label for the timestamp button
18
+ * @param handleClick The click handler for the timestamp button
19
+ * @param textAfterTimestamp The text that comes after the timestamp
20
+ * @returns A React Fragment with timestamp button and text
21
+ */
22
+ export const renderTimestampWithText = (
23
+ timestampInHHMMSS: string,
24
+ handleClick: (e: SyntheticMouseEvent<HTMLButtonElement>) => void,
25
+ intl: IntlShape,
26
+ textAfterTimestamp: string,
27
+ ): React$Element<any> => (
28
+ <>
29
+ <div className="bcs-ActivityMessage-timestamp">
30
+ <button
31
+ aria-label={intl.formatMessage(messages.activityMessageTimestampLabel)}
32
+ type="button"
33
+ onClick={handleClick}
34
+ >
35
+ {timestampInHHMMSS}
36
+ </button>
37
+ </div>
38
+ {textAfterTimestamp}
39
+ </>
40
+ );
41
+
14
42
  /**
15
43
  * Formats text containing a timestamp by wrapping the timestamp in a Link component
16
44
  * @param text The text containing the timestamp
@@ -51,17 +79,7 @@ const formatTimestamp = (text: string, timestamp: string, intl: IntlShape): Reac
51
79
  }
52
80
  };
53
81
 
54
- const timestampLabel = intl.formatMessage(messages.activityMessageTimestampLabel);
55
- return (
56
- <>
57
- <div className="bcs-ActivityMessage-timestamp">
58
- <button aria-label={timestampLabel} type="button" onClick={handleClick}>
59
- {timestampInHHMMSS}
60
- </button>
61
- </div>
62
- {textAfterTimestamp}
63
- </>
64
- );
82
+ return renderTimestampWithText(timestampInHHMMSS, handleClick, intl, textAfterTimestamp);
65
83
  };
66
84
 
67
85
  // this regex matches one of the following regular expressions:
@@ -33,6 +33,7 @@ const DragCloud = ({
33
33
  updateLiveText,
34
34
  updatePosition,
35
35
  }) => {
36
+ const nodeRef = React.useRef({});
36
37
  const [isMoving, setIsMoving] = useState(false);
37
38
 
38
39
  const dragCloudClasses = classNames('bdl-DragCloud', {
@@ -137,8 +138,22 @@ const DragCloud = ({
137
138
  const onDrag = throttle((e, { x, y }) => updatePosition({ x, y }), 100, { leading: true, trailing: true });
138
139
 
139
140
  return (
140
- <Draggable bounds="parent" disabled={disabled} onDrag={onDrag} onStop={onDrop} position={position}>
141
- <div className={dragCloudClasses} onBlur={onBlur} onKeyDown={onKeyDown} role="button" tabIndex={0}>
141
+ <Draggable
142
+ nodeRef={nodeRef}
143
+ bounds="parent"
144
+ disabled={disabled}
145
+ onDrag={onDrag}
146
+ onStop={onDrop}
147
+ position={position}
148
+ >
149
+ <div
150
+ ref={nodeRef}
151
+ className={dragCloudClasses}
152
+ onBlur={onBlur}
153
+ onKeyDown={onKeyDown}
154
+ role="button"
155
+ tabIndex={0}
156
+ >
142
157
  <IconCloud
143
158
  filter={{ id: 'drop-shadow', definition: <DropShadowFilter /> }}
144
159
  height={cloudSize}