box-annotations 5.1.0 → 5.2.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/package.json +1 -1
- package/src/@types/model.ts +12 -4
- package/src/BoxAnnotations.ts +7 -0
- package/src/common/BaseManager.ts +10 -2
- package/src/components/Popups/PopupDrawingToolbar.scss +31 -0
- package/src/components/Popups/PopupDrawingToolbar.tsx +18 -4
- package/src/constants.ts +23 -0
- package/src/drawing/DrawingAnnotations.tsx +39 -2
- package/src/drawing/DrawingManager.tsx +1 -1
- package/src/drawing/actions.ts +4 -2
- package/src/highlight/actions.ts +2 -1
- package/src/icons/ArrowBack.tsx +23 -0
- package/src/icons/ArrowForward.tsx +23 -0
- package/src/icons/Trash.tsx +25 -0
- package/src/media/MediaAnnotator.scss +20 -0
- package/src/media/MediaAnnotator.ts +141 -0
- package/src/popup/PopupLayer.tsx +7 -3
- package/src/popup/PopupManager.tsx +2 -2
- package/src/region/RegionAnnotations.tsx +24 -3
- package/src/region/RegionCreation.tsx +8 -2
- package/src/region/RegionCreationManager.tsx +1 -1
- package/src/region/RegionManager.tsx +1 -1
- package/src/region/actions.ts +4 -2
- package/src/store/annotations/selectors.ts +10 -2
- package/src/store/creator/selectors.ts +13 -0
- package/src/test-utils/videoAnnotations.tsx +178 -0
- package/src/utils/useVideoTiming.ts +112 -0
- package/src/constants.js +0 -8
package/package.json
CHANGED
package/src/@types/model.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
117
|
+
location: Location;
|
|
110
118
|
type: 'point';
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
export interface TargetRegion {
|
|
114
|
-
location:
|
|
122
|
+
location: Location;
|
|
115
123
|
shape: Rect;
|
|
116
124
|
type: 'region';
|
|
117
125
|
}
|
package/src/BoxAnnotations.ts
CHANGED
|
@@ -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?:
|
|
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
|
-
|
|
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();
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// Legacy styles (default)
|
|
30
31
|
.ba-PopupDrawingToolbar {
|
|
31
32
|
background-color: $bdl-gray;
|
|
32
33
|
border-radius: $bdl-border-radius-size;
|
|
@@ -44,6 +45,19 @@
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
// Modernized styles (when feature flag is enabled)
|
|
49
|
+
.ba-PopupDrawingToolbar--modernized {
|
|
50
|
+
background-color: transparent;
|
|
51
|
+
border-radius: 46px;
|
|
52
|
+
|
|
53
|
+
.ba-Popup-content {
|
|
54
|
+
gap: 4px;
|
|
55
|
+
padding: 8px;
|
|
56
|
+
background-color: rgba(0, 0, 0, .8);
|
|
57
|
+
border-radius: 46px;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
.ba-PopupDrawingToolbar-group {
|
|
48
62
|
position: relative;
|
|
49
63
|
display: flex;
|
|
@@ -53,6 +67,15 @@
|
|
|
53
67
|
& + & {
|
|
54
68
|
border-left: 1px solid #000;
|
|
55
69
|
}
|
|
70
|
+
|
|
71
|
+
.ba-PopupDrawingToolbar--modernized & {
|
|
72
|
+
gap: 4px;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Modernized border-left for adjacent groups
|
|
77
|
+
.ba-PopupDrawingToolbar--modernized .ba-PopupDrawingToolbar-group + .ba-PopupDrawingToolbar-group {
|
|
78
|
+
border-left: 1px solid rgb(255, 255, 255, 8%);
|
|
56
79
|
}
|
|
57
80
|
|
|
58
81
|
.ba-PopupDrawingToolbar-delete,
|
|
@@ -65,6 +88,7 @@
|
|
|
65
88
|
}
|
|
66
89
|
}
|
|
67
90
|
|
|
91
|
+
// Legacy comment button styles
|
|
68
92
|
.ba-PopupDrawingToolbar-comment {
|
|
69
93
|
@include ba-PopupDrawingToolbarButton($width: max-content);
|
|
70
94
|
@include common-typography;
|
|
@@ -74,3 +98,10 @@
|
|
|
74
98
|
color: $white;
|
|
75
99
|
font-weight: bold;
|
|
76
100
|
}
|
|
101
|
+
|
|
102
|
+
// Modernized comment button styles
|
|
103
|
+
.ba-PopupDrawingToolbar--modernized .ba-PopupDrawingToolbar-comment {
|
|
104
|
+
padding-right: 8px;
|
|
105
|
+
padding-left: 12px;
|
|
106
|
+
font-size: 15px;
|
|
107
|
+
}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { useSelector } from 'react-redux';
|
|
2
3
|
import classNames from 'classnames';
|
|
3
4
|
import { FormattedMessage, useIntl } from 'react-intl';
|
|
5
|
+
// Legacy icons (box-ui-elements)
|
|
4
6
|
import IconRedo from 'box-ui-elements/es/icon/line/Redo16';
|
|
5
7
|
import IconTrash from 'box-ui-elements/es/icon/line/Trash16';
|
|
6
8
|
import IconUndo from 'box-ui-elements/es/icon/line/Undo16';
|
|
9
|
+
// Modernized icons
|
|
10
|
+
import ArrowBack from '../../icons/ArrowBack';
|
|
11
|
+
import ArrowForward from '../../icons/ArrowForward';
|
|
12
|
+
import Trash from '../../icons/Trash';
|
|
7
13
|
import messages from './messages';
|
|
8
14
|
import PopupBase from './PopupBase';
|
|
9
15
|
import { Options, PopupReference, Rect } from './Popper';
|
|
10
16
|
import './PopupDrawingToolbar.scss';
|
|
17
|
+
import { isFeatureEnabled } from '../../store/options/selectors';
|
|
18
|
+
import { AppState } from '../../store/types';
|
|
11
19
|
|
|
12
20
|
export type PopupBaseRef = PopupBase;
|
|
13
21
|
|
|
@@ -48,14 +56,20 @@ const options: Partial<Options> = {
|
|
|
48
56
|
placement: 'top',
|
|
49
57
|
};
|
|
50
58
|
|
|
59
|
+
const ICON_SIZE = 20;
|
|
60
|
+
|
|
51
61
|
const PopupDrawingToolbar = (props: Props, ref: React.Ref<PopupBaseRef>): JSX.Element => {
|
|
52
62
|
const { canComment, canRedo, canUndo, className, onDelete, onRedo, onReply, onUndo, reference } = props;
|
|
63
|
+
const isModernizationEnabled = useSelector((state: AppState) => isFeatureEnabled(state, 'modernization'));
|
|
64
|
+
|
|
53
65
|
const intl = useIntl();
|
|
54
66
|
|
|
55
67
|
return (
|
|
56
68
|
<PopupBase
|
|
57
69
|
ref={ref}
|
|
58
|
-
className={classNames(className, 'ba-PopupDrawingToolbar'
|
|
70
|
+
className={classNames(className, 'ba-PopupDrawingToolbar', {
|
|
71
|
+
'ba-PopupDrawingToolbar--modernized': isModernizationEnabled,
|
|
72
|
+
})}
|
|
59
73
|
data-resin-component="popupDrawingToolbar"
|
|
60
74
|
options={options}
|
|
61
75
|
reference={reference}
|
|
@@ -69,7 +83,7 @@ const PopupDrawingToolbar = (props: Props, ref: React.Ref<PopupBaseRef>): JSX.El
|
|
|
69
83
|
title={intl.formatMessage(messages.drawingButtonUndo)}
|
|
70
84
|
type="button"
|
|
71
85
|
>
|
|
72
|
-
<IconUndo />
|
|
86
|
+
{isModernizationEnabled ? <ArrowBack height={ICON_SIZE} width={ICON_SIZE} /> : <IconUndo />}
|
|
73
87
|
</button>
|
|
74
88
|
<button
|
|
75
89
|
className="ba-PopupDrawingToolbar-redo"
|
|
@@ -79,7 +93,7 @@ const PopupDrawingToolbar = (props: Props, ref: React.Ref<PopupBaseRef>): JSX.El
|
|
|
79
93
|
title={intl.formatMessage(messages.drawingButtonRedo)}
|
|
80
94
|
type="button"
|
|
81
95
|
>
|
|
82
|
-
<IconRedo />
|
|
96
|
+
{isModernizationEnabled ? <ArrowForward height={ICON_SIZE} width={ICON_SIZE} /> : <IconRedo />}
|
|
83
97
|
</button>
|
|
84
98
|
<button
|
|
85
99
|
className="ba-PopupDrawingToolbar-delete"
|
|
@@ -88,7 +102,7 @@ const PopupDrawingToolbar = (props: Props, ref: React.Ref<PopupBaseRef>): JSX.El
|
|
|
88
102
|
title={intl.formatMessage(messages.drawingButtonDelete)}
|
|
89
103
|
type="button"
|
|
90
104
|
>
|
|
91
|
-
<IconTrash />
|
|
105
|
+
{isModernizationEnabled ? <Trash height={ICON_SIZE} width={ICON_SIZE} /> : <IconTrash />}
|
|
92
106
|
</button>
|
|
93
107
|
</div>
|
|
94
108
|
<div className="ba-PopupDrawingToolbar-group">
|
package/src/constants.ts
ADDED
|
@@ -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
|
-
|
|
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={
|
|
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
|
}
|
package/src/drawing/actions.ts
CHANGED
|
@@ -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:
|
|
28
|
+
type: targetType,
|
|
27
29
|
value: location,
|
|
28
30
|
},
|
|
29
31
|
path_groups: pathGroups,
|
package/src/highlight/actions.ts
CHANGED
|
@@ -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:
|
|
27
|
+
type: TARGET_TYPE.PAGE as const,
|
|
27
28
|
value: location,
|
|
28
29
|
},
|
|
29
30
|
shapes,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
function ArrowBack(props: React.SVGProps<SVGSVGElement>): JSX.Element {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
data-testid="ArrowBack"
|
|
7
|
+
fill="none"
|
|
8
|
+
focusable="false"
|
|
9
|
+
height="1em"
|
|
10
|
+
role="img"
|
|
11
|
+
viewBox="0 0 20 20"
|
|
12
|
+
width="1em"
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
d="M6.91086 1.91058C7.2363 1.58515 7.76381 1.58515 8.08925 1.91058C8.41462 2.23602 8.41462 2.76355 8.08925 3.08896L5.34511 5.8331H12.5C14.1062 5.83312 15.3897 6.4396 16.2574 7.47291C17.1089 8.4871 17.5 9.83869 17.5 11.2498C17.5 12.6609 17.109 14.0125 16.2574 15.0266C15.3897 16.0599 14.1062 16.6664 12.5 16.6664H10C9.53987 16.6664 9.16679 16.2933 9.16671 15.8331C9.16671 15.3729 9.53979 14.9998 10 14.9998H12.5C13.6552 14.9998 14.4553 14.5803 14.9805 13.9549C15.5219 13.3102 15.8334 12.3699 15.8334 11.2498C15.8334 10.1296 15.5219 9.18935 14.9805 8.54469C14.4553 7.91928 13.6552 7.49979 12.5 7.49977H5.34511L8.08925 10.2439C8.41462 10.5694 8.41471 11.0969 8.08925 11.4223C7.76384 11.7477 7.23631 11.7477 6.91086 11.4223L2.7442 7.25563C2.41876 6.9302 2.41876 6.40268 2.7442 6.07725L6.91086 1.91058Z"
|
|
17
|
+
fill="#fff"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default ArrowBack;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
function ArrowForward(props: React.SVGProps<SVGSVGElement>): JSX.Element {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
data-testid="ArrowForward"
|
|
7
|
+
fill="none"
|
|
8
|
+
focusable="false"
|
|
9
|
+
height="1em"
|
|
10
|
+
role="img"
|
|
11
|
+
viewBox="0 0 20 20"
|
|
12
|
+
width="1em"
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
d="M11.9108 1.91058C12.2363 1.58515 12.7638 1.58515 13.0892 1.91058L17.2558 6.07725C17.5813 6.40268 17.5813 6.9302 17.2558 7.25563L13.0892 11.4223C12.7638 11.7477 12.2363 11.7477 11.9108 11.4223C11.5854 11.0969 11.5854 10.5694 11.9108 10.2439L14.6549 7.49977H7.5C6.34482 7.49977 5.54479 7.91927 5.01953 8.54469C4.4782 9.18935 4.16667 10.1296 4.16667 11.2498C4.16668 12.3699 4.47819 13.3102 5.01953 13.9549C5.54479 14.5803 6.34484 14.9998 7.5 14.9998H10C10.4603 14.9998 10.8333 15.3729 10.8333 15.8331C10.8333 16.2933 10.4603 16.6664 10 16.6664H7.5C5.89388 16.6664 4.6104 16.0599 3.74268 15.0266C2.89112 14.0125 2.50001 12.6609 2.5 11.2498C2.5 9.83869 2.89113 8.4871 3.74268 7.47291C4.6104 6.43958 5.89385 5.8331 7.5 5.8331H14.6549L11.9108 3.08896C11.5854 2.76354 11.5854 2.23602 11.9108 1.91058Z"
|
|
17
|
+
fill="#fff"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default ArrowForward;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
function Trash(props: React.SVGProps<SVGSVGElement>): JSX.Element {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
data-testid="Trash"
|
|
7
|
+
fill="none"
|
|
8
|
+
focusable="false"
|
|
9
|
+
height="1em"
|
|
10
|
+
role="img"
|
|
11
|
+
viewBox="0 0 20 20"
|
|
12
|
+
width="1em"
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
clipRule="evenodd"
|
|
17
|
+
d="M7.55758 4.25017C7.6296 3.87481 7.75758 3.73256 7.87369 3.65128C7.99454 3.56669 8.19891 3.47991 8.56146 3.41949C8.92362 3.35914 9.39079 3.33317 9.99996 3.33317C10.6092 3.33317 11.0764 3.35914 11.4385 3.41949C11.801 3.47991 12.0054 3.56669 12.1263 3.65128C12.2424 3.73256 12.3704 3.87481 12.4424 4.25017C11.7196 4.1984 10.9065 4.1665 9.99996 4.1665C9.09346 4.1665 8.2803 4.1984 7.55758 4.25017ZM5.85575 4.42505C5.92957 3.5488 6.20244 2.78673 6.91792 2.2859C7.31791 2.0059 7.79063 1.8583 8.28746 1.7755C8.78471 1.69262 9.35921 1.6665 9.99996 1.6665C10.6408 1.6665 11.2153 1.69262 11.7125 1.7755C12.2093 1.8583 12.682 2.0059 13.082 2.2859C13.7975 2.78673 14.0704 3.5488 14.1442 4.42505C14.5419 4.47947 14.8961 4.53848 15.208 4.59789C15.7812 4.70707 16.2109 4.81752 16.5036 4.90318C16.6499 4.946 16.762 4.98262 16.8405 5.00976C16.8797 5.02333 16.9105 5.03452 16.9331 5.04294L16.9608 5.05343L16.97 5.05701L16.9735 5.05837L16.9749 5.05895C16.9752 5.05906 16.9761 5.05944 16.6666 5.83317L16.9761 5.05944C17.4035 5.23037 17.6113 5.71535 17.4404 6.14266C17.2975 6.49966 16.9356 6.70348 16.5711 6.66122L16.0734 15.1939C15.9706 16.9565 14.5113 18.3332 12.7457 18.3332H7.25422C5.48867 18.3332 4.02937 16.9565 3.92655 15.1939L3.42881 6.66122C3.06437 6.70348 2.70237 6.49966 2.55958 6.14266C2.38865 5.71535 2.5965 5.23037 3.02382 5.05944L3.33331 5.83317C3.02382 5.05944 3.02351 5.05955 3.02382 5.05944L3.02506 5.05895L3.02649 5.05837L3.02994 5.05701L3.03914 5.05343L3.06682 5.04294C3.0894 5.03452 3.12027 5.02333 3.15952 5.00976C3.23802 4.98262 3.35004 4.946 3.49637 4.90318C3.78904 4.81752 4.21877 4.70707 4.79197 4.59789C5.10387 4.53848 5.45812 4.47947 5.85575 4.42505ZM5.59039 15.0969L5.07378 6.24087L5.10382 6.23512C6.1447 6.03685 7.75911 5.83317 9.99996 5.83317C12.2409 5.83317 13.8553 6.03685 14.8961 6.23512L14.9261 6.24087L14.4095 15.0969C14.3581 15.9782 13.6285 16.6665 12.7457 16.6665H7.25422C6.37145 16.6665 5.6418 15.9782 5.59039 15.0969ZM11.6666 7.49984C12.1269 7.49984 12.5 7.87294 12.5 8.33317V14.1665C12.5 14.6268 12.1269 14.9998 11.6666 14.9998C11.2064 14.9998 10.8333 14.6268 10.8333 14.1665V8.33317C10.8333 7.87294 11.2064 7.49984 11.6666 7.49984ZM9.16662 8.33317C9.16662 7.87294 8.79354 7.49984 8.33329 7.49984C7.87307 7.49984 7.49997 7.87294 7.49997 8.33317V14.1665C7.49997 14.6268 7.87307 14.9998 8.33329 14.9998C8.79354 14.9998 9.16662 14.6268 9.16662 14.1665V8.33317Z"
|
|
18
|
+
fill="#fff"
|
|
19
|
+
fillRule="evenodd"
|
|
20
|
+
/>
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default Trash;
|
|
@@ -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,141 @@
|
|
|
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 | undefined, defaultLocation: number = -1 ): void {
|
|
124
|
+
|
|
125
|
+
if (!annotationId) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const annotation = getAnnotation(this.store.getState(), annotationId);
|
|
129
|
+
const annotationLocation = annotation?.target?.location?.value ?? defaultLocation;
|
|
130
|
+
const video = this.getReference();
|
|
131
|
+
|
|
132
|
+
if ( !video || !this.annotatedEl || annotationLocation === -1 ) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
video.pause();
|
|
137
|
+
video.currentTime = annotationLocation / 1000;
|
|
138
|
+
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|