box-annotations 5.1.0 → 5.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "box-annotations",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "Box Annotations",
5
5
  "author": "Box (https://www.box.com/)",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-use-before-define */
2
2
  import { Permissions } from './api';
3
+ import { TARGET_TYPE } from '../constants';
3
4
 
4
5
  // New Data Model Types
5
6
  export interface Annotation {
@@ -37,10 +38,17 @@ export type Dimensions = {
37
38
  };
38
39
 
39
40
  export interface Page {
40
- type: 'page';
41
+ type: TARGET_TYPE.PAGE;
41
42
  value: number;
42
43
  }
43
44
 
45
+ export interface Frame {
46
+ type: TARGET_TYPE.FRAME;
47
+ value: number;
48
+ }
49
+
50
+ export type Location = Page | Frame;
51
+
44
52
  export interface Path {
45
53
  clientId?: string;
46
54
  points: Array<Position>;
@@ -93,7 +101,7 @@ export interface Stroke {
93
101
  export type Target = TargetDrawing | TargetHighlight | TargetPoint | TargetRegion;
94
102
 
95
103
  export interface TargetDrawing {
96
- location: Page;
104
+ location: Location;
97
105
  path_groups: Array<PathGroup>;
98
106
  type: 'drawing';
99
107
  }
@@ -106,12 +114,12 @@ export interface TargetHighlight {
106
114
  }
107
115
 
108
116
  export interface TargetPoint extends Position {
109
- location: Page;
117
+ location: Location;
110
118
  type: 'point';
111
119
  }
112
120
 
113
121
  export interface TargetRegion {
114
- location: Page;
122
+ location: Location;
115
123
  shape: Rect;
116
124
  type: 'region';
117
125
  }
@@ -4,6 +4,7 @@ import BaseAnnotator from './common/BaseAnnotator';
4
4
  import ImageAnnotator from './image/ImageAnnotator';
5
5
  import DocumentAnnotator from './document/DocumentAnnotator';
6
6
  import { IntlOptions, Permissions, PERMISSIONS, Type } from './@types';
7
+ import MediaAnnotator from './media/MediaAnnotator';
7
8
 
8
9
  type Annotator = {
9
10
  CONSTRUCTOR: typeof BaseAnnotator;
@@ -60,6 +61,12 @@ const ANNOTATORS: Annotator[] = [
60
61
  TYPES: [Type.region],
61
62
  VIEWERS: ['Image'],
62
63
  },
64
+ {
65
+ CONSTRUCTOR: MediaAnnotator,
66
+ NAME: 'Media',
67
+ TYPES: [Type.region],
68
+ VIEWERS: ['Dash','MP4'],
69
+ },
63
70
  ];
64
71
 
65
72
  class BoxAnnotations {
@@ -2,13 +2,15 @@ import * as ReactDOM from 'react-dom/client';
2
2
  import { IntlShape } from 'react-intl';
3
3
  import { Store } from 'redux';
4
4
  import { applyResinTags } from '../utils/resin';
5
+ import { TARGET_TYPE } from '../constants';
5
6
 
6
7
  export type ResinTags = Record<string, unknown>;
7
8
 
8
9
  export type Options = {
9
10
  location?: number;
10
11
  referenceEl: HTMLElement;
11
- resinTags?: ResinTags;
12
+ resinTags?: Record<string, unknown>;
13
+ targetType?: TARGET_TYPE;
12
14
  };
13
15
 
14
16
  export type Props = {
@@ -30,13 +32,19 @@ export default class BaseManager implements Manager {
30
32
 
31
33
  root: ReactDOM.Root | null = null;
32
34
 
33
- constructor({ location = 1, referenceEl, resinTags = {} }: Options) {
35
+ targetType: TARGET_TYPE;
36
+
37
+ referenceEl: HTMLElement;
38
+
39
+ constructor({ location = 1, referenceEl, resinTags = {}, targetType = TARGET_TYPE.PAGE }: Options) {
34
40
  this.location = location;
35
41
  this.reactEl = this.insert(referenceEl, {
36
42
  ...resinTags,
37
43
  feature: 'annotations',
38
44
  });
39
45
 
46
+ this.referenceEl = referenceEl;
47
+ this.targetType = targetType;
40
48
  this.root = ReactDOM.createRoot(this.reactEl);
41
49
 
42
50
  this.decorate();
@@ -0,0 +1,23 @@
1
+ export const ANNOTATOR_EVENT = {
2
+ fetch: 'annotationsfetched',
3
+ error: 'annotationerror',
4
+ scale: 'scaleannotations',
5
+ setVisibility: 'annotationsetvisibility',
6
+ };
7
+
8
+ export const MOUSE_PRIMARY = 1;
9
+
10
+ /**
11
+ * This is the default location index for video annotations.
12
+ * Documents have locations that represent a page but that doesn't apply to
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
15
+ * video annotation.
16
+ */
17
+ export const MEDIA_LOCATION_INDEX = -1;
18
+
19
+
20
+ export enum TARGET_TYPE {
21
+ FRAME = 'frame',
22
+ PAGE = 'page',
23
+ }
@@ -10,6 +10,8 @@ import PopupDrawingToolbar, { PopupBaseRef } from '../components/Popups/PopupDra
10
10
  import { AnnotationDrawing, PathGroup } from '../@types';
11
11
  import { CreatorItemDrawing, CreatorStatus } from '../store';
12
12
  import './DrawingAnnotations.scss';
13
+ import { TARGET_TYPE } from '../constants';
14
+ import useVideoTiming from '../utils/useVideoTiming';
13
15
 
14
16
  export type Props = {
15
17
  activeAnnotationId: string | null;
@@ -29,6 +31,8 @@ export type Props = {
29
31
  setupDrawing: (location: number) => void;
30
32
  stashedPathGroups: Array<PathGroup>;
31
33
  undoDrawingPathGroup: () => void;
34
+ targetType: TARGET_TYPE;
35
+ referenceEl?: HTMLElement;
32
36
  };
33
37
 
34
38
  const DrawingAnnotations = (props: Props): JSX.Element => {
@@ -50,6 +54,8 @@ const DrawingAnnotations = (props: Props): JSX.Element => {
50
54
  setupDrawing,
51
55
  stashedPathGroups,
52
56
  undoDrawingPathGroup,
57
+ targetType,
58
+ referenceEl,
53
59
  } = props;
54
60
  const [isDrawing, setIsDrawing] = React.useState<boolean>(false);
55
61
  const [stagedRootEl, setStagedRootEl] = React.useState<DrawingSVGRef | null>(null);
@@ -60,6 +66,13 @@ const DrawingAnnotations = (props: Props): JSX.Element => {
60
66
  const stagedGroupRef = React.useRef<SVGGElement>(null);
61
67
  const { current: drawingSVGGroup } = stagedGroupRef;
62
68
 
69
+ const { isVideoSeeking, getCurrentVideoLocation } = useVideoTiming({
70
+ targetType,
71
+ referenceEl,
72
+ activeAnnotationId,
73
+ annotations,
74
+ });
75
+
63
76
  const handleAnnotationActive = (annotationId: string | null): void => {
64
77
  setActiveAnnotationId(annotationId);
65
78
  };
@@ -72,8 +85,16 @@ const DrawingAnnotations = (props: Props): JSX.Element => {
72
85
  const handleRedo = (): void => {
73
86
  redoDrawingPathGroup();
74
87
  };
88
+
89
+ const getCurrentLocation = (): number => {
90
+ if (targetType === TARGET_TYPE.FRAME) {
91
+ return getCurrentVideoLocation();
92
+ }
93
+ return location;
94
+ };
75
95
  const handleReply = (): void => {
76
- setStaged({ location, pathGroups: drawnPathGroups });
96
+ const annotationLocation = getCurrentLocation();
97
+ setStaged({ location: annotationLocation, pathGroups: drawnPathGroups });
77
98
  setStatus(CreatorStatus.staged);
78
99
  };
79
100
  const handleStart = (): void => {
@@ -95,11 +116,27 @@ const DrawingAnnotations = (props: Props): JSX.Element => {
95
116
  }
96
117
  }, [drawnPathGroups]);
97
118
 
119
+
120
+ let annotationsToShow: AnnotationDrawing[] = [];
121
+ // For video annotations, we only show the active annotation and we wait for the video
122
+ // to seek to the annotation location before showing it. This prevents annoations from being
123
+ // shown on the incorrect frame while the video is seeking.
124
+ if (targetType === TARGET_TYPE.FRAME ) {
125
+ if (!isVideoSeeking && activeAnnotationId) {
126
+ annotationsToShow = annotations.filter(annotation => annotation.id === activeAnnotationId);
127
+ }
128
+ } else {
129
+ annotationsToShow = annotations;
130
+ }
131
+
132
+
133
+
134
+
98
135
  return (
99
136
  <>
100
137
  <DrawingList
101
138
  activeId={activeAnnotationId}
102
- annotations={annotations}
139
+ annotations={annotationsToShow}
103
140
  className="ba-DrawingAnnotations-list"
104
141
  onSelect={handleAnnotationActive}
105
142
  />
@@ -14,6 +14,6 @@ export default class DrawingListManager extends BaseManager {
14
14
  this.root = ReactDOM.createRoot(this.reactEl);
15
15
  }
16
16
 
17
- this.root.render(<DrawingAnnotationsContainer location={this.location} {...props} />);
17
+ this.root.render(<DrawingAnnotationsContainer referenceEl={this.referenceEl} targetType={this.targetType} location={this.location} {...props} />);
18
18
  }
19
19
  }
@@ -2,16 +2,18 @@ import { AppThunkDispatch, AppState, getCreatorStatus, CreatorStatus } from '../
2
2
  import { createAnnotationAction } from '../store/annotations';
3
3
  import { getFileVersionId } from '../store/options';
4
4
  import { PathGroup } from '../@types';
5
+ import { TARGET_TYPE } from '../constants';
5
6
  import { resetDrawingAction, setDrawingLocationAction } from '../store/drawing';
6
7
 
7
8
  export type CreateArg = {
8
9
  location: number;
9
10
  message: string;
10
11
  pathGroups: Array<PathGroup>;
12
+ targetType: TARGET_TYPE;
11
13
  };
12
14
 
13
15
  export const createDrawingAction = (arg: CreateArg) => (dispatch: AppThunkDispatch, getState: () => AppState) => {
14
- const { location, message, pathGroups } = arg;
16
+ const { location, message, pathGroups, targetType } = arg;
15
17
  const state = getState();
16
18
  const newAnnotation = {
17
19
  description: {
@@ -23,7 +25,7 @@ export const createDrawingAction = (arg: CreateArg) => (dispatch: AppThunkDispat
23
25
  },
24
26
  target: {
25
27
  location: {
26
- type: 'page' as const,
28
+ type: targetType,
27
29
  value: location,
28
30
  },
29
31
  path_groups: pathGroups,
@@ -2,6 +2,7 @@ import { AppThunkDispatch, AppState } from '../store';
2
2
  import { createAnnotationAction } from '../store/annotations';
3
3
  import { getFileVersionId } from '../store/options';
4
4
  import { Rect } from '../@types';
5
+ import { TARGET_TYPE } from '../constants';
5
6
 
6
7
  export type CreateArg = {
7
8
  location: number;
@@ -23,7 +24,7 @@ export const createHighlightAction = (arg: CreateArg) => (dispatch: AppThunkDisp
23
24
  },
24
25
  target: {
25
26
  location: {
26
- type: 'page' as const,
27
+ type: TARGET_TYPE.PAGE as const,
27
28
  value: location,
28
29
  },
29
30
  shapes,
@@ -0,0 +1,20 @@
1
+ .bp-media {
2
+ .ba-Layer {
3
+ position: absolute;
4
+ white-space: initial; // Overrides white-space: no-wrap inherited .bp-image
5
+ text-align: initial; // Overrides text-align: center inherited .bp-image
6
+ pointer-events: none;
7
+ }
8
+
9
+ // Applied to img element, which is managed by Preview SDK
10
+ .ba-is-drawing {
11
+ user-select: none; // Prevent visible selection highlight
12
+ }
13
+
14
+ // Raise the rendering order to override the stacking context created by the
15
+ // transform applied to each of the managers' react mount element to account for
16
+ // rotation
17
+ .ba-Layer--popup {
18
+ z-index: 4;
19
+ }
20
+ }
@@ -0,0 +1,140 @@
1
+ import { Unsubscribe } from 'redux';
2
+ import BaseAnnotator, { Options } from '../common/BaseAnnotator';
3
+ import PopupManager from '../popup/PopupManager';
4
+ import { DrawingManager } from '../drawing';
5
+ import { RegionCreationManager, RegionManager } from '../region';
6
+ import { CreatorStatus, getCreatorStatus } from '../store/creator';
7
+ import { getAnnotation, getFileId, getIsCurrentFileVersion, getRotation } from '../store';
8
+ import { Manager } from '../common/BaseManager';
9
+ import './MediaAnnotator.scss';
10
+ import { MEDIA_LOCATION_INDEX, TARGET_TYPE } from '../constants';
11
+
12
+ export const CSS_IS_DRAWING_CLASS = 'ba-is-drawing';
13
+ export const VIDEO_PARENT_SELECTOR = '.bp-media';
14
+
15
+ export default class MediaAnnotator extends BaseAnnotator {
16
+ annotatedEl?: HTMLElement;
17
+
18
+ managers: Set<Manager> = new Set();
19
+
20
+ storeHandler?: Unsubscribe;
21
+
22
+ constructor(options: Options) {
23
+ super(options);
24
+
25
+ // Subscribe to the store so that the annoator can capture changes to the creator status
26
+ this.storeHandler = this.store.subscribe(this.handleStore);
27
+ }
28
+
29
+ destroy(): void {
30
+
31
+ // If the store handler is present, unsubscribe from the store.
32
+ if (this.storeHandler) {
33
+ this.storeHandler();
34
+ }
35
+
36
+ if (this.managers) {
37
+ this.managers.forEach(manager => manager.destroy());
38
+ }
39
+
40
+ super.destroy();
41
+ }
42
+
43
+ getAnnotatedElement(): HTMLElement | null | undefined {
44
+ return this.containerEl?.querySelector(VIDEO_PARENT_SELECTOR);
45
+ }
46
+
47
+
48
+ addManagers(referenceEl: HTMLVideoElement, resinTags: { fileid: string|null; iscurrent: boolean }): void {
49
+ this.managers.add(new PopupManager({ location: MEDIA_LOCATION_INDEX, referenceEl, resinTags, targetType: TARGET_TYPE.FRAME }));
50
+ this.managers.add(new DrawingManager({ location: MEDIA_LOCATION_INDEX, referenceEl, resinTags, targetType: TARGET_TYPE.FRAME }));
51
+ this.managers.add(new RegionManager({ location: MEDIA_LOCATION_INDEX, referenceEl, resinTags, targetType: TARGET_TYPE.FRAME }));
52
+ this.managers.add(new RegionCreationManager({ location: MEDIA_LOCATION_INDEX, referenceEl, resinTags, targetType: TARGET_TYPE.FRAME }));
53
+ }
54
+
55
+ getManagers(parentEl: HTMLElement, referenceEl: HTMLVideoElement): Set<Manager> {
56
+ const fileId = getFileId(this.store.getState());
57
+ const isCurrentFileVersion = getIsCurrentFileVersion(this.store.getState());
58
+ const resinTags = { fileid: fileId, iscurrent: isCurrentFileVersion };
59
+ this.managers.forEach(manager => {
60
+ if (!manager.exists(parentEl)) {
61
+ manager.destroy();
62
+ this.managers.delete(manager);
63
+ }
64
+ });
65
+
66
+ if (this.managers.size === 0) {
67
+ this.addManagers(referenceEl, resinTags);
68
+ }
69
+
70
+ return this.managers;
71
+ }
72
+
73
+
74
+
75
+ // returns the actual video element
76
+ getReference(): HTMLVideoElement | null | undefined {
77
+ return this.annotatedEl?.querySelector('video');
78
+ }
79
+
80
+ handleStore = (): void => {
81
+ const referenceEl = this.getReference();
82
+
83
+ if (!referenceEl) {
84
+ return;
85
+ }
86
+
87
+ if (getCreatorStatus(this.store.getState()) === CreatorStatus.started) {
88
+ referenceEl.classList.add(CSS_IS_DRAWING_CLASS);
89
+ } else {
90
+ referenceEl.classList.remove(CSS_IS_DRAWING_CLASS);
91
+ }
92
+ };
93
+
94
+ render(): void {
95
+ const referenceEl = this.getReference();
96
+ const rotation = getRotation(this.store.getState()) || 0;
97
+
98
+ if (!this.annotatedEl || !referenceEl) {
99
+ return;
100
+ }
101
+
102
+ this.getManagers(this.annotatedEl, referenceEl).forEach(manager => {
103
+ manager.style({
104
+ height: `${referenceEl.offsetHeight}px`,
105
+ left: `${referenceEl.offsetLeft}px`,
106
+ top: `${referenceEl.offsetTop}px`,
107
+ transform: `rotate(${rotation}deg)`,
108
+ width: `${referenceEl.offsetWidth}px`,
109
+ });
110
+ manager.render({
111
+ intl: this.intl,
112
+ store: this.store,
113
+ });
114
+ });
115
+
116
+ this.postRender();
117
+ }
118
+
119
+ // This function gets called by the client. We are using the name scrollToAnnotation to match
120
+ // the other annotators as the client calls this function when it wants to show an annoation on the
121
+ // preview. The only difference is that we are using the video element's currentTime to scroll to the
122
+ // annotation instead of the scrollLeft and scrollTop of the parent element.
123
+ scrollToAnnotation(annotationId: string | null): void {
124
+ if (!annotationId) {
125
+ return;
126
+ }
127
+
128
+ const annotation = getAnnotation(this.store.getState(), annotationId);
129
+ const annotationLocation = annotation?.target.location.value ?? 0
130
+ const video = this.getReference();
131
+ if (!annotation || !video || !this.annotatedEl) {
132
+ return;
133
+ }
134
+
135
+ video.pause();
136
+ video.currentTime = annotationLocation / 1000;
137
+
138
+ }
139
+ }
140
+
@@ -14,6 +14,7 @@ import {
14
14
  } from '../store';
15
15
  import { PopupReference } from '../components/Popups/Popper';
16
16
  import './PopupLayer.scss';
17
+ import { TARGET_TYPE } from '../constants';
17
18
 
18
19
  export type Props = {
19
20
  createDrawing?: (arg: DrawingCreateArg) => void;
@@ -28,6 +29,7 @@ export type Props = {
28
29
  setMessage: (message: string) => void;
29
30
  staged?: CreatorItem | null;
30
31
  status: CreatorStatus;
32
+ targetType: TARGET_TYPE;
31
33
  };
32
34
 
33
35
  const modeStagedMap: { [M in Mode]?: (staged: CreatorItem | null) => boolean } = {
@@ -49,6 +51,7 @@ const PopupLayer = (props: Props): JSX.Element | null => {
49
51
  setMessage,
50
52
  staged,
51
53
  status,
54
+ targetType,
52
55
  } = props;
53
56
 
54
57
  const [reference, setReference] = React.useState<PopupReference | null>(null);
@@ -70,11 +73,11 @@ const PopupLayer = (props: Props): JSX.Element | null => {
70
73
  }
71
74
 
72
75
  if (isCreatorStagedHighlight(staged)) {
73
- createHighlight({ ...staged, message });
76
+ createHighlight({ ...staged, message, targetType });
74
77
  } else if (isCreatorStagedRegion(staged)) {
75
- createRegion({ ...staged, message });
78
+ createRegion({ ...staged, message, targetType });
76
79
  } else if (isCreatorStagedDrawing(staged)) {
77
- createDrawing({ ...staged, message });
80
+ createDrawing({ ...staged, message, targetType });
78
81
  }
79
82
  };
80
83
 
@@ -82,6 +85,7 @@ const PopupLayer = (props: Props): JSX.Element | null => {
82
85
  setReference(referenceId ? document.querySelector(`[data-ba-reference-id="${referenceId}"]`) : null);
83
86
  }, [referenceId]);
84
87
 
88
+
85
89
  return (
86
90
  <>
87
91
  {canCreate && canReply && reference && staged && (
@@ -13,7 +13,7 @@ export default class PopupManager extends BaseManager {
13
13
  if (!this.root) {
14
14
  this.root = ReactDOM.createRoot(this.reactEl);
15
15
  }
16
-
17
- this.root.render(<PopupContainer location={this.location} {...props} />);
16
+
17
+ this.root.render(<PopupContainer location={this.location} {...props} targetType={this.targetType} />);
18
18
  }
19
19
  }
@@ -2,24 +2,45 @@ import * as React from 'react';
2
2
  import RegionList from './RegionList';
3
3
  import { AnnotationRegion } from '../@types';
4
4
  import './RegionAnnotations.scss';
5
+ import useVideoTiming from '../utils/useVideoTiming';
6
+ import { TARGET_TYPE } from '../constants';
5
7
 
6
- type Props = {
8
+ export type Props = {
7
9
  activeAnnotationId: string | null;
8
10
  annotations: AnnotationRegion[];
9
11
  setActiveAnnotationId: (annotationId: string | null) => void;
12
+ referenceEl?: HTMLElement;
13
+ targetType: TARGET_TYPE;
10
14
  };
11
15
 
12
16
  const RegionAnnotations = (props: Props): JSX.Element => {
13
- const { activeAnnotationId, annotations, setActiveAnnotationId } = props;
17
+ const { activeAnnotationId, annotations, setActiveAnnotationId, referenceEl, targetType } = props;
18
+ const { isVideoSeeking } = useVideoTiming({
19
+ targetType,
20
+ referenceEl,
21
+ activeAnnotationId: activeAnnotationId || null,
22
+ annotations: annotations || [],
23
+ });
14
24
 
15
25
  const handleAnnotationActive = (annotationId: string | null): void => {
16
26
  setActiveAnnotationId(annotationId);
17
27
  };
18
28
 
29
+ let annotationsToShow: AnnotationRegion[] = []
30
+
31
+ if (targetType === TARGET_TYPE.FRAME) {
32
+ if (!isVideoSeeking && activeAnnotationId) {
33
+ annotationsToShow = annotations.filter(annotation => annotation.id === activeAnnotationId);
34
+ }
35
+ } else {
36
+ annotationsToShow = annotations;
37
+ }
38
+
39
+
19
40
  return (
20
41
  <RegionList
21
42
  activeId={activeAnnotationId}
22
- annotations={annotations}
43
+ annotations={annotationsToShow}
23
44
  className="ba-RegionAnnotations-list"
24
45
  onSelect={handleAnnotationActive}
25
46
  />
@@ -4,6 +4,8 @@ import RegionRect, { RegionRectRef } from './RegionRect';
4
4
  import { Rect } from '../@types';
5
5
  import { CreatorItemRegion, CreatorStatus } from '../store/creator';
6
6
  import './RegionCreation.scss';
7
+ import { TARGET_TYPE } from '../constants';
8
+ import { getVideoCurrentTimeInMilliseconds } from '../utils/useVideoTiming';
7
9
 
8
10
  type Props = {
9
11
  isCreating: boolean;
@@ -14,6 +16,8 @@ type Props = {
14
16
  setStaged: (staged: CreatorItemRegion | null) => void;
15
17
  setStatus: (status: CreatorStatus) => void;
16
18
  staged?: CreatorItemRegion | null;
19
+ targetType: TARGET_TYPE;
20
+ referenceEl?: HTMLElement;
17
21
  };
18
22
 
19
23
  type State = {
@@ -45,8 +49,10 @@ export default class RegionCreation extends React.PureComponent<Props, State> {
45
49
  };
46
50
 
47
51
  handleStop = (shape: Rect): void => {
48
- const { location, setStaged, setStatus } = this.props;
49
- setStaged({ location, shape });
52
+ const { location, setStaged, setStatus, targetType, referenceEl } = this.props;
53
+ const annotationLocation = targetType !== TARGET_TYPE.FRAME ? location : getVideoCurrentTimeInMilliseconds(referenceEl as HTMLVideoElement);
54
+
55
+ setStaged({ location: annotationLocation, shape });
50
56
  setStatus(CreatorStatus.staged);
51
57
  };
52
58
 
@@ -14,6 +14,6 @@ export default class RegionManager extends BaseManager {
14
14
  this.root = ReactDOM.createRoot(this.reactEl);
15
15
  }
16
16
 
17
- this.root.render(<RegionCreationContainer location={this.location} {...props} />);
17
+ this.root.render(<RegionCreationContainer targetType={this.targetType} referenceEl={this.referenceEl} location={this.location} {...props} />);
18
18
  }
19
19
  }
@@ -14,6 +14,6 @@ export default class RegionListManager extends BaseManager {
14
14
  this.root = ReactDOM.createRoot(this.reactEl);
15
15
  }
16
16
 
17
- this.root.render(<RegionAnnotationsContainer location={this.location} {...props} />);
17
+ this.root.render(<RegionAnnotationsContainer location={this.location} referenceEl={this.referenceEl} targetType={this.targetType} {...props} />);
18
18
  }
19
19
  }
@@ -2,15 +2,17 @@ import { AppThunkDispatch, AppState } from '../store';
2
2
  import { createAnnotationAction } from '../store/annotations';
3
3
  import { getFileVersionId } from '../store/options';
4
4
  import { Rect } from '../@types';
5
+ import { TARGET_TYPE } from '../constants';
5
6
 
6
7
  export type CreateArg = {
7
8
  location: number;
8
9
  message: string;
9
10
  shape: Rect;
11
+ targetType: TARGET_TYPE;
10
12
  };
11
13
 
12
14
  export const createRegionAction = (arg: CreateArg) => (dispatch: AppThunkDispatch, getState: () => AppState) => {
13
- const { location, message, shape } = arg;
15
+ const { location, message, shape, targetType } = arg;
14
16
  const state = getState();
15
17
  const newAnnotation = {
16
18
  description: {
@@ -22,7 +24,7 @@ export const createRegionAction = (arg: CreateArg) => (dispatch: AppThunkDispatc
22
24
  },
23
25
  target: {
24
26
  location: {
25
- type: 'page' as const,
27
+ type: targetType,
26
28
  value: location,
27
29
  },
28
30
  shape,
@@ -1,12 +1,20 @@
1
1
  import getProp from 'lodash/get';
2
2
  import { Annotation } from '../../@types';
3
3
  import { AppState } from '../types';
4
+ import { MEDIA_LOCATION_INDEX } from '../../constants';
4
5
 
5
6
  type State = Pick<AppState, 'annotations'>;
6
7
 
7
8
  export const getActiveAnnotationId = ({ annotations }: State): string | null => annotations.activeId;
8
9
  export const getAnnotation = ({ annotations }: State, id: string): Annotation | undefined => annotations.byId[id];
9
10
  export const getAnnotations = ({ annotations }: State): Annotation[] => [...Object.values(annotations.byId)];
10
- export const getAnnotationsForLocation = (state: State, location: number): Annotation[] =>
11
- getAnnotations(state).filter(annotation => getProp(annotation, 'target.location.value') === location);
11
+ export const getAnnotationsForLocation = (state: State, location: number): Annotation[] => {
12
+ const annotations = getAnnotations(state);
13
+ // For video annotations the location will be -1 by default as there is no page number for videos. We will return all of the annotations
14
+ // and the component will filter them by the current play time of the video.
15
+ if (location === MEDIA_LOCATION_INDEX) {
16
+ return annotations;
17
+ }
18
+ return annotations.filter(annotation => getProp(annotation, 'target.location.value') === location);
19
+ };
12
20
  export const getIsInitialized = ({ annotations }: State): boolean => annotations.isInitialized;