@playkit-js/transcript 3.6.0-canary.0-a940bfb → 3.6.0-canary.0-baa8e71
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/CHANGELOG.md +61 -2
- package/dist/playkit-transcript.js +1 -1
- package/dist/playkit-transcript.js.map +1 -1
- package/package.json +9 -8
- package/src/components/attach-placeholder/attach-placeholder.tsx +2 -2
- package/src/components/caption/caption.scss +1 -1
- package/src/components/caption/caption.tsx +81 -19
- package/src/components/caption-list/captionList.tsx +31 -27
- package/src/components/plugin-button/plugin-button.tsx +1 -0
- package/src/components/popover-menu/popover-menu-item.tsx +108 -0
- package/src/components/popover-menu/popover-menu.scss +18 -7
- package/src/components/popover-menu/popover-menu.tsx +58 -68
- package/src/components/search/search.tsx +29 -0
- package/src/components/small-screen-slate/small-screen-slate.scss +1 -0
- package/src/components/transcript/transcript.scss +23 -2
- package/src/components/transcript/transcript.tsx +163 -42
- package/src/components/transcript-menu/transcript-menu.tsx +60 -12
- package/src/events/events.ts +4 -1
- package/src/transcript-plugin.tsx +85 -35
- package/src/utils/index.ts +0 -1
- package/src/utils/utils.ts +4 -0
- package/translations/ca_es.i18n.json +31 -0
- package/translations/en.i18n.json +11 -5
- package/src/utils/debounce.ts +0 -36
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
import {A11yWrapper} from '@playkit-js/common/dist/hoc/a11y-wrapper';
|
|
2
2
|
import {h, Component, VNode} from 'preact';
|
|
3
|
+
import {PopoverMenuItem, PopoverMenuItemData} from './popover-menu-item';
|
|
4
|
+
import {ui} from '@playkit-js/kaltura-player-js';
|
|
5
|
+
const {Tooltip} = ui.components;
|
|
6
|
+
const {withText, Text} = ui.preacti18n;
|
|
3
7
|
|
|
4
|
-
const {
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
const {withEventManager} = KalturaPlayer.ui.Event;
|
|
8
|
-
const {TAB} = KalturaPlayer.ui.utils.KeyMap;
|
|
8
|
+
const {withEventManager} = ui.Event;
|
|
9
|
+
const {TAB} = ui.utils.KeyMap;
|
|
9
10
|
|
|
10
11
|
import * as styles from './popover-menu.scss';
|
|
11
12
|
|
|
12
|
-
interface PopoverMenuItemData {
|
|
13
|
-
testId: string;
|
|
14
|
-
label: string;
|
|
15
|
-
onClick: () => void;
|
|
16
|
-
isDisabled?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
13
|
interface PopoverMenuProps {
|
|
20
14
|
moreOptionsLabel?: string;
|
|
21
15
|
eventManager?: any;
|
|
22
16
|
children?: VNode;
|
|
23
17
|
items: Array<PopoverMenuItemData>;
|
|
18
|
+
kitchenSinkDetached: boolean;
|
|
19
|
+
popOverMenuHeight: number;
|
|
20
|
+
shouldUseCalculatedHeight: boolean;
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
interface PopoverMenuState {
|
|
@@ -41,23 +38,25 @@ class PopoverMenu extends Component<PopoverMenuProps, PopoverMenuState> {
|
|
|
41
38
|
|
|
42
39
|
constructor(props: PopoverMenuProps) {
|
|
43
40
|
super(props);
|
|
44
|
-
this.state = {
|
|
41
|
+
this.state = {
|
|
42
|
+
isOpen: false
|
|
43
|
+
};
|
|
45
44
|
|
|
46
|
-
this.props.eventManager?.listen(document, 'click', this.
|
|
47
|
-
this.props.eventManager?.listen(document, 'keydown', this.
|
|
45
|
+
this.props.eventManager?.listen(document, 'click', this._handleMouseEvent);
|
|
46
|
+
this.props.eventManager?.listen(document, 'keydown', this._handleKeydownEvent);
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
componentWillUnmount() {
|
|
51
50
|
this._itemsRefMap = new Map();
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
private
|
|
53
|
+
private _handleMouseEvent = (event: MouseEvent) => {
|
|
55
54
|
if (!this._controlElementRef?.contains(event.target as Node | null)) {
|
|
56
|
-
this.
|
|
55
|
+
this._closePopover();
|
|
57
56
|
}
|
|
58
57
|
};
|
|
59
58
|
|
|
60
|
-
private
|
|
59
|
+
private _handleKeydownEvent = (event: KeyboardEvent) => {
|
|
61
60
|
const eventTarget = event.target as Node | null;
|
|
62
61
|
if (
|
|
63
62
|
this.state.isOpen &&
|
|
@@ -66,7 +65,7 @@ class PopoverMenu extends Component<PopoverMenuProps, PopoverMenuState> {
|
|
|
66
65
|
!this._popoverElementRef?.contains(eventTarget) &&
|
|
67
66
|
eventTarget !== this._controlElementRef
|
|
68
67
|
) {
|
|
69
|
-
this.
|
|
68
|
+
this._closePopover();
|
|
70
69
|
}
|
|
71
70
|
};
|
|
72
71
|
|
|
@@ -78,26 +77,26 @@ class PopoverMenu extends Component<PopoverMenuProps, PopoverMenuState> {
|
|
|
78
77
|
this._getItemRef(currentIndex + 1)?.focus();
|
|
79
78
|
};
|
|
80
79
|
|
|
81
|
-
private
|
|
80
|
+
private _closePopover() {
|
|
82
81
|
this.setState({isOpen: false});
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
private
|
|
84
|
+
private _togglePopover = () => {
|
|
86
85
|
const isOpen = !this.state.isOpen;
|
|
87
86
|
|
|
88
87
|
this.setState({isOpen}, () => {
|
|
89
|
-
if (isOpen){
|
|
88
|
+
if (isOpen) {
|
|
90
89
|
this._controlElementRef?.focus();
|
|
91
90
|
this.props.eventManager?.listen(this._controlElementRef, 'keydown', (event: KeyboardEvent) => {
|
|
92
|
-
if (event.keyCode === TAB){
|
|
91
|
+
if (event.keyCode === TAB) {
|
|
93
92
|
const firstNonDisabledItem = this.props.items.findIndex((item: PopoverMenuItemData) => !item.isDisabled);
|
|
94
93
|
if (firstNonDisabledItem !== -1) {
|
|
95
|
-
this._getItemRef(firstNonDisabledItem -1)?.focus();
|
|
94
|
+
this._getItemRef(firstNonDisabledItem - 1)?.focus();
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
|
-
})
|
|
97
|
+
});
|
|
99
98
|
}
|
|
100
|
-
})
|
|
99
|
+
});
|
|
101
100
|
};
|
|
102
101
|
|
|
103
102
|
private _getItemRef = (index: number) => {
|
|
@@ -109,23 +108,30 @@ class PopoverMenu extends Component<PopoverMenuProps, PopoverMenuState> {
|
|
|
109
108
|
};
|
|
110
109
|
|
|
111
110
|
render() {
|
|
112
|
-
const {children, items} = this.props;
|
|
111
|
+
const {children, items, kitchenSinkDetached, popOverMenuHeight, shouldUseCalculatedHeight} = this.props;
|
|
112
|
+
|
|
113
|
+
let popOverHeight = 0;
|
|
114
|
+
if (shouldUseCalculatedHeight) {
|
|
115
|
+
const neededHeight = 48 * items.length;
|
|
116
|
+
const padding = 14;
|
|
117
|
+
popOverHeight = popOverMenuHeight - neededHeight <= 0 ? popOverMenuHeight - padding : neededHeight - padding;
|
|
118
|
+
}
|
|
113
119
|
|
|
114
120
|
const popoverMenuContent = (
|
|
115
121
|
<div className={styles.popoverContainer}>
|
|
116
|
-
<A11yWrapper
|
|
117
|
-
e
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
<A11yWrapper
|
|
123
|
+
onClick={e => {
|
|
124
|
+
e.stopPropagation();
|
|
125
|
+
this._togglePopover();
|
|
126
|
+
}}>
|
|
120
127
|
<div
|
|
121
|
-
aria-label={this.props.moreOptionsLabel!}
|
|
122
128
|
tabIndex={0}
|
|
123
129
|
data-testid="popover-anchor-container"
|
|
124
130
|
className={`${styles.popoverAnchorContainer} ${this.state.isOpen ? styles.active : ''}`}
|
|
125
131
|
aria-expanded={this.state.isOpen}
|
|
126
132
|
aria-controls="popoverContent"
|
|
127
133
|
ref={node => {
|
|
128
|
-
if (node){
|
|
134
|
+
if (node) {
|
|
129
135
|
this._controlElementRef = node;
|
|
130
136
|
}
|
|
131
137
|
}}>
|
|
@@ -135,6 +141,7 @@ class PopoverMenu extends Component<PopoverMenuProps, PopoverMenuState> {
|
|
|
135
141
|
|
|
136
142
|
<div
|
|
137
143
|
className={styles.popoverComponent}
|
|
144
|
+
style={shouldUseCalculatedHeight ? {height: `${popOverHeight}px`, overflowY: 'auto'} : {}}
|
|
138
145
|
role="menu"
|
|
139
146
|
aria-expanded={this.state.isOpen}
|
|
140
147
|
id="popoverContent"
|
|
@@ -142,41 +149,22 @@ class PopoverMenu extends Component<PopoverMenuProps, PopoverMenuState> {
|
|
|
142
149
|
this._popoverElementRef = node;
|
|
143
150
|
}}>
|
|
144
151
|
{this.state.isOpen
|
|
145
|
-
? items.map((
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this._handleDownKeyPressed(index);
|
|
158
|
-
}
|
|
159
|
-
}}
|
|
160
|
-
onUpKeyPressed={() => {
|
|
161
|
-
if (!isDisabled) {
|
|
162
|
-
this._handleUpKeyPressed(index);
|
|
163
|
-
}
|
|
164
|
-
}}>
|
|
165
|
-
{
|
|
166
|
-
<div
|
|
167
|
-
tabIndex={isDisabled ? -1 : 0}
|
|
168
|
-
role="menuitem"
|
|
169
|
-
className={`${styles.popoverMenuItem} ${isDisabled ? styles.popoverMenuItemDisabled : ''}`}
|
|
170
|
-
data-testid={testId}
|
|
171
|
-
ref={node => {
|
|
172
|
-
this._setItemRef(index, node);
|
|
173
|
-
}}>
|
|
174
|
-
{label}
|
|
175
|
-
</div>
|
|
152
|
+
? items.map((item, index) => (
|
|
153
|
+
<PopoverMenuItem
|
|
154
|
+
key={index}
|
|
155
|
+
item={item}
|
|
156
|
+
index={index}
|
|
157
|
+
onKeyDown={this._handleDownKeyPressed}
|
|
158
|
+
onKeyUp={this._handleUpKeyPressed}
|
|
159
|
+
setRef={this._setItemRef}
|
|
160
|
+
onClick={() => {
|
|
161
|
+
this._closePopover();
|
|
162
|
+
if (!item.items) {
|
|
163
|
+
item.onClick?.();
|
|
176
164
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
))
|
|
180
168
|
: null}
|
|
181
169
|
</div>
|
|
182
170
|
</div>
|
|
@@ -186,7 +174,9 @@ class PopoverMenu extends Component<PopoverMenuProps, PopoverMenuState> {
|
|
|
186
174
|
popoverMenuContent
|
|
187
175
|
) : (
|
|
188
176
|
<div>
|
|
189
|
-
<Tooltip label={this.props.moreOptionsLabel!}
|
|
177
|
+
<Tooltip label={this.props.moreOptionsLabel!} {...(kitchenSinkDetached ? {type: 'bottom-left', strictPosition: true} : {})}>
|
|
178
|
+
{popoverMenuContent}
|
|
179
|
+
</Tooltip>
|
|
190
180
|
</div>
|
|
191
181
|
);
|
|
192
182
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import {h, Component} from 'preact';
|
|
2
2
|
import {InputField} from '@playkit-js/common/dist/components/input-field';
|
|
3
|
+
import {core, ui} from '@playkit-js/kaltura-player-js';
|
|
3
4
|
|
|
5
|
+
const {Utils} = core;
|
|
6
|
+
const {withEventManager} = KalturaPlayer.ui.Event;
|
|
7
|
+
const {TAB} = KalturaPlayer.ui.utils.KeyMap;
|
|
4
8
|
const {withText, Text} = KalturaPlayer.ui.preacti18n;
|
|
9
|
+
const {withPlayer} = ui.Components;
|
|
10
|
+
|
|
5
11
|
const translates = ({activeSearchIndex, totalSearchResults}: SearchProps) => ({
|
|
6
12
|
searchLabel: <Text id="transcript.search">Search in Transcript</Text>,
|
|
7
13
|
clearSearchLabel: <Text id="transcript.clear_search">Clear search</Text>,
|
|
@@ -34,11 +40,34 @@ export interface SearchProps {
|
|
|
34
40
|
nextMatchLabel?: string;
|
|
35
41
|
prevMatchLabel?: string;
|
|
36
42
|
searchResultsLabel?: string;
|
|
43
|
+
eventManager?: any
|
|
44
|
+
focusPluginButton: (event: KeyboardEvent) => void;
|
|
45
|
+
player?: any;
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
@withPlayer
|
|
49
|
+
@withEventManager
|
|
39
50
|
class SearchComponent extends Component<SearchProps> {
|
|
40
51
|
private _inputField: InputField | null = null;
|
|
41
52
|
|
|
53
|
+
constructor(props: SearchProps) {
|
|
54
|
+
super(props);
|
|
55
|
+
if (this.props.player._firstPlay){
|
|
56
|
+
this.props.eventManager?.listen(this.props.player, this.props.player.Event.FIRST_PLAY, () => {
|
|
57
|
+
this.props.eventManager?.listen(document, 'keydown', this.handleKeydownEvent);
|
|
58
|
+
})
|
|
59
|
+
} else {
|
|
60
|
+
this.props.eventManager?.listen(document, 'keydown', this.handleKeydownEvent);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private handleKeydownEvent = (event: KeyboardEvent) => {
|
|
65
|
+
if (event.keyCode === TAB && event.shiftKey && document.activeElement === this._inputField?.base?.childNodes[0]){
|
|
66
|
+
//@ts-ignore
|
|
67
|
+
this.props.focusPluginButton(event);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
42
71
|
shouldComponentUpdate(nextProps: Readonly<SearchProps>) {
|
|
43
72
|
const {value, activeSearchIndex, totalSearchResults, kitchenSinkActive} = this.props;
|
|
44
73
|
if (
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
@import '../../variables.scss';
|
|
2
2
|
|
|
3
|
+
$button-height: 32px;
|
|
4
|
+
|
|
3
5
|
.hidden {
|
|
4
6
|
visibility: hidden;
|
|
5
7
|
}
|
|
@@ -25,7 +27,7 @@
|
|
|
25
27
|
align-items: center;
|
|
26
28
|
justify-content: center;
|
|
27
29
|
width: 120px;
|
|
28
|
-
height:
|
|
30
|
+
height: $button-height;
|
|
29
31
|
border-radius: $roundness-3;
|
|
30
32
|
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.3);
|
|
31
33
|
border: solid 1px $primary-color;
|
|
@@ -44,6 +46,10 @@
|
|
|
44
46
|
font-family: sans-serif;
|
|
45
47
|
font-style: normal;
|
|
46
48
|
}
|
|
49
|
+
|
|
50
|
+
*:focus-visible:not(input) {
|
|
51
|
+
outline: 1px solid $tab-focus-color;
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
.global-container {
|
|
@@ -56,16 +62,31 @@
|
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
.header {
|
|
65
|
+
$header-margin-bottom: 8px;
|
|
66
|
+
|
|
67
|
+
position: relative;
|
|
59
68
|
display: flex;
|
|
60
69
|
align-items: center;
|
|
61
70
|
justify-content: space-between;
|
|
62
71
|
width: 100%;
|
|
63
|
-
margin-bottom:
|
|
72
|
+
margin-bottom: $header-margin-bottom;
|
|
64
73
|
padding-left: 16px;
|
|
65
74
|
font-size: 16px;
|
|
66
75
|
padding-right: 16px;
|
|
67
76
|
gap: 8px;
|
|
68
77
|
z-index: 2;
|
|
78
|
+
.to-search-button {
|
|
79
|
+
position: absolute;
|
|
80
|
+
right: 16px;
|
|
81
|
+
bottom: calc((#{$button-height + $header-margin-bottom}) * -1); // button height + margin
|
|
82
|
+
opacity: 0;
|
|
83
|
+
&:focus {
|
|
84
|
+
opacity: 1;
|
|
85
|
+
}
|
|
86
|
+
&[class^="Button__button"]:focus:not([class^="Button__disabled"]) {
|
|
87
|
+
background-color: $tone-6-color
|
|
88
|
+
}
|
|
89
|
+
}
|
|
69
90
|
}
|
|
70
91
|
|
|
71
92
|
.body {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {h, Component} from 'preact';
|
|
2
|
-
import {ui} from '@playkit-js/kaltura-player-js';
|
|
3
|
-
import {debounce} from '
|
|
2
|
+
import {ui, core} from '@playkit-js/kaltura-player-js';
|
|
3
|
+
import {debounce} from '@playkit-js/common/dist/utils-common';
|
|
4
4
|
import * as styles from './transcript.scss';
|
|
5
5
|
import {Spinner} from '../spinner';
|
|
6
6
|
import {Search} from '../search';
|
|
@@ -11,24 +11,27 @@ import {AutoscrollButton} from '../autoscroll-button';
|
|
|
11
11
|
import {TranscriptMenu} from '../transcript-menu';
|
|
12
12
|
import {SmallScreenSlate} from '../small-screen-slate';
|
|
13
13
|
import {Button, ButtonType, ButtonSize} from '@playkit-js/common/dist/components/button';
|
|
14
|
+
import {ScreenReaderProvider} from '@playkit-js/common/dist/hoc/sr-wrapper';
|
|
14
15
|
import {OnClickEvent, OnClick} from '@playkit-js/common/dist/hoc/a11y-wrapper';
|
|
15
16
|
import {TranscriptEvents} from '../../events/events';
|
|
16
17
|
|
|
17
|
-
const {ENTER, SPACE, TAB} = ui.utils.KeyMap;
|
|
18
|
+
const {ENTER, SPACE, TAB, ESC} = ui.utils.KeyMap;
|
|
18
19
|
const {withText, Text} = ui.preacti18n;
|
|
19
20
|
|
|
20
|
-
const {SidePanelModes} = ui;
|
|
21
|
+
const {SidePanelModes, SidePanelPositions} = ui;
|
|
21
22
|
const {PLAYER_BREAK_POINTS} = ui.Components;
|
|
22
23
|
const {connect} = ui.redux;
|
|
23
24
|
|
|
24
25
|
const SEARCH_EVENT_DISPATCH_TIMEOUT = 2000;
|
|
26
|
+
const WIDGET_RESIZE_DEBOUNCE_TIMEOUT = 250;
|
|
25
27
|
|
|
26
28
|
const translates = {
|
|
27
29
|
skipTranscript: <Text id="transcript.skip_transcript">Skip transcript</Text>,
|
|
28
30
|
errorTitle: <Text id="transcript.whoops">Whoops!</Text>,
|
|
29
31
|
errorDescripton: <Text id="transcript.load_failed">Failed to load transcript</Text>,
|
|
30
32
|
attachTranscript: <Text id="transcript.attach_transcript">Bring Transcript back</Text>,
|
|
31
|
-
detachTranscript: <Text id="transcript.detach_transcript">Popout
|
|
33
|
+
detachTranscript: <Text id="transcript.detach_transcript">Popout transcript</Text>,
|
|
34
|
+
toSearchResult: <Text id="transcript.to_search_result">Go to result</Text>
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
export interface TranscriptProps {
|
|
@@ -53,6 +56,7 @@ export interface TranscriptProps {
|
|
|
53
56
|
errorDescripton?: string;
|
|
54
57
|
attachTranscript?: string;
|
|
55
58
|
detachTranscript?: string;
|
|
59
|
+
toSearchResult?: string;
|
|
56
60
|
downloadDisabled: boolean;
|
|
57
61
|
onDownload: () => void;
|
|
58
62
|
printDisabled: boolean;
|
|
@@ -65,6 +69,13 @@ export interface TranscriptProps {
|
|
|
65
69
|
onAttach: () => void;
|
|
66
70
|
kitchenSinkDetached: boolean;
|
|
67
71
|
isMobile?: boolean;
|
|
72
|
+
playerWidth?: number;
|
|
73
|
+
onJumpToSearchMatch: () => void;
|
|
74
|
+
onScrollToSearchMatch: () => void;
|
|
75
|
+
focusPluginButton: (event: KeyboardEvent) => void;
|
|
76
|
+
textTracks: Array<core.TextTrack>;
|
|
77
|
+
changeLanguage: (textTrack: core.TextTrack) => void;
|
|
78
|
+
sidePanelPosition: string;
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
interface TranscriptState {
|
|
@@ -85,12 +96,13 @@ const initialSearch = {
|
|
|
85
96
|
searchLength: 0
|
|
86
97
|
};
|
|
87
98
|
|
|
99
|
+
const SMALL_WIDGET_WIDTH = 240;
|
|
88
100
|
const SEARCHBAR_HEIGHT = 38; // height of search bar with margins
|
|
89
|
-
const smallScreen = PLAYER_BREAK_POINTS?.SMALL || 480;
|
|
90
101
|
|
|
91
102
|
const mapStateToProps = (state: any, ownProps: Pick<TranscriptProps, 'expandMode'>) => ({
|
|
92
|
-
smallScreen: ownProps.expandMode === SidePanelModes.ALONGSIDE && state.shell.playerClientRect?.width
|
|
93
|
-
isMobile: state.shell.isMobile
|
|
103
|
+
smallScreen: ownProps.expandMode === SidePanelModes.ALONGSIDE && state.shell.playerClientRect?.width <= PLAYER_BREAK_POINTS.TINY,
|
|
104
|
+
isMobile: state.shell.isMobile,
|
|
105
|
+
playerWidth: state.shell.playerClientRect?.width
|
|
94
106
|
});
|
|
95
107
|
|
|
96
108
|
// @ts-ignore
|
|
@@ -104,20 +116,36 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
|
|
|
104
116
|
private _preventScrollEvent: boolean = false;
|
|
105
117
|
private _scrollToSearchMatchEnabled: boolean = false;
|
|
106
118
|
private _widgetRootRef: HTMLElement | null = null;
|
|
119
|
+
private _transcriptMenuRef: HTMLElement | null = null;
|
|
107
120
|
|
|
108
121
|
private _widgetHeight: number = 0;
|
|
109
122
|
private _topAutoscrollEdge: number = 0;
|
|
110
123
|
private _bottomAutoscrollEdge: number = 0;
|
|
111
124
|
private _thirdOfWidgetHeight: number = 0;
|
|
112
125
|
|
|
126
|
+
private _resizeObserver: ResizeObserver | null = null;
|
|
127
|
+
|
|
113
128
|
state: TranscriptState = {
|
|
114
129
|
isAutoScrollEnabled: true,
|
|
115
130
|
widgetWidth: 0,
|
|
116
131
|
...initialSearch
|
|
117
132
|
};
|
|
118
133
|
|
|
134
|
+
componentDidMount(): void {
|
|
135
|
+
if (window.ResizeObserver) {
|
|
136
|
+
// observe transcript root element size changes
|
|
137
|
+
this._resizeObserver = new ResizeObserver(() => {
|
|
138
|
+
this._debounced.setWidgetSize();
|
|
139
|
+
});
|
|
140
|
+
this._resizeObserver.observe(this._widgetRootRef!);
|
|
141
|
+
} else {
|
|
142
|
+
// use player size to define transcript root element size
|
|
143
|
+
this._debounced.setWidgetSize();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
119
147
|
componentDidUpdate(previousProps: Readonly<TranscriptProps>, previousState: Readonly<TranscriptState>): void {
|
|
120
|
-
const {captions, activeCaptionLanguage} = this.props;
|
|
148
|
+
const {captions, activeCaptionLanguage, playerWidth} = this.props;
|
|
121
149
|
const {search} = this.state;
|
|
122
150
|
if (previousProps.captions !== captions) {
|
|
123
151
|
// clear search value only if active caption language was switched, otherwise keep previous value
|
|
@@ -129,9 +157,25 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
|
|
|
129
157
|
this._debounced.findSearchMatches();
|
|
130
158
|
}
|
|
131
159
|
|
|
132
|
-
this.
|
|
160
|
+
if (!this._resizeObserver && previousProps.playerWidth !== playerWidth) {
|
|
161
|
+
// re-calculate wiget size if player size changed
|
|
162
|
+
this._debounced.setWidgetSize();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
componentWillUnmount(): void {
|
|
167
|
+
if (this._resizeObserver) {
|
|
168
|
+
this._resizeObserver?.disconnect();
|
|
169
|
+
this._resizeObserver = null;
|
|
170
|
+
}
|
|
133
171
|
}
|
|
134
172
|
|
|
173
|
+
private _handleClose = (event: KeyboardEvent) => {
|
|
174
|
+
if (event.keyCode === ESC) {
|
|
175
|
+
this.props.onClose(event, true);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
135
179
|
private _enableAutoScroll = (event: OnClickEvent, byKeyboard?: boolean) => {
|
|
136
180
|
event.preventDefault();
|
|
137
181
|
if (this.state.isAutoScrollEnabled) {
|
|
@@ -172,7 +216,10 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
|
|
|
172
216
|
onClick={kitchenSinkDetached ? onAttach : onDetach}
|
|
173
217
|
icon={kitchenSinkDetached ? 'attach' : 'detach'}
|
|
174
218
|
ariaLabel={kitchenSinkDetached ? attachTranscript : detachTranscript}
|
|
175
|
-
tooltip={{
|
|
219
|
+
tooltip={{
|
|
220
|
+
label: kitchenSinkDetached ? attachTranscript! : detachTranscript!,
|
|
221
|
+
...(kitchenSinkDetached ? {type: 'bottom-left', strictPosition: kitchenSinkDetached} : {})
|
|
222
|
+
}}
|
|
176
223
|
/>
|
|
177
224
|
</div>
|
|
178
225
|
);
|
|
@@ -180,6 +227,7 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
|
|
|
180
227
|
|
|
181
228
|
private _onSearch = (search: string) => {
|
|
182
229
|
this.setState({search});
|
|
230
|
+
this.props.onScrollToSearchMatch()
|
|
183
231
|
};
|
|
184
232
|
|
|
185
233
|
private _findSearchMatches = () => {
|
|
@@ -237,11 +285,64 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
|
|
|
237
285
|
return styles.smallWidth;
|
|
238
286
|
};
|
|
239
287
|
|
|
288
|
+
private _renderJumpToSearchButton = () => {
|
|
289
|
+
const {toSearchResult, onJumpToSearchMatch} = this.props;
|
|
290
|
+
const {search, activeSearchIndex, totalSearchResults} = this.state;
|
|
291
|
+
if (!search || totalSearchResults === 0 || activeSearchIndex === 0) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
return (
|
|
295
|
+
<Button
|
|
296
|
+
type={ButtonType.secondary}
|
|
297
|
+
className={styles.toSearchButton}
|
|
298
|
+
onClick={onJumpToSearchMatch}
|
|
299
|
+
ariaLabel={toSearchResult}
|
|
300
|
+
testId="transcript_jumpToSearchMatch">
|
|
301
|
+
{toSearchResult}
|
|
302
|
+
</Button>
|
|
303
|
+
);
|
|
304
|
+
};
|
|
305
|
+
|
|
240
306
|
private _renderHeader = () => {
|
|
241
|
-
const {
|
|
307
|
+
const {
|
|
308
|
+
sidePanelPosition,
|
|
309
|
+
toggledWithEnter,
|
|
310
|
+
kitchenSinkActive,
|
|
311
|
+
kitchenSinkDetached,
|
|
312
|
+
downloadDisabled,
|
|
313
|
+
onDownload,
|
|
314
|
+
printDisabled,
|
|
315
|
+
onPrint,
|
|
316
|
+
isLoading,
|
|
317
|
+
attachTranscript,
|
|
318
|
+
detachTranscript,
|
|
319
|
+
onAttach,
|
|
320
|
+
onDetach,
|
|
321
|
+
textTracks,
|
|
322
|
+
changeLanguage
|
|
323
|
+
} = this.props;
|
|
242
324
|
const {search, activeSearchIndex, totalSearchResults} = this.state;
|
|
325
|
+
const widgetHeight = this._widgetRootRef?.getBoundingClientRect().height;
|
|
326
|
+
const transcriptHeight = this._transcriptMenuRef?.getBoundingClientRect().height;
|
|
327
|
+
const popOverHeight = (widgetHeight ?? 0) - (transcriptHeight ?? 0) - 16;
|
|
328
|
+
const shouldUseCalculatedHeight = sidePanelPosition === SidePanelPositions.TOP || sidePanelPosition === SidePanelPositions.BOTTOM;
|
|
329
|
+
|
|
330
|
+
let detachMenuItem: null | any = null;
|
|
331
|
+
if (this.state.widgetWidth <= SMALL_WIDGET_WIDTH && !kitchenSinkDetached) {
|
|
332
|
+
detachMenuItem = {
|
|
333
|
+
label: kitchenSinkDetached ? attachTranscript : detachTranscript,
|
|
334
|
+
onClick: kitchenSinkDetached ? onAttach : onDetach,
|
|
335
|
+
testId: 'transcript-detach-attach-button',
|
|
336
|
+
disabled: isLoading
|
|
337
|
+
};
|
|
338
|
+
}
|
|
243
339
|
return (
|
|
244
|
-
<div
|
|
340
|
+
<div
|
|
341
|
+
className={[styles.header, this._getHeaderStyles()].join(' ')}
|
|
342
|
+
data-testid="transcript_header"
|
|
343
|
+
ref={node => {
|
|
344
|
+
this._transcriptMenuRef = node;
|
|
345
|
+
}}>
|
|
245
346
|
<Search
|
|
246
347
|
onChange={this._onSearch}
|
|
247
348
|
onSearchIndexChange={this._debounced.onActiveSearchIndexChange}
|
|
@@ -250,9 +351,25 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
|
|
|
250
351
|
totalSearchResults={totalSearchResults}
|
|
251
352
|
toggledWithEnter={toggledWithEnter}
|
|
252
353
|
kitchenSinkActive={kitchenSinkActive}
|
|
354
|
+
focusPluginButton={this.props.focusPluginButton}
|
|
355
|
+
/>
|
|
356
|
+
{this._renderJumpToSearchButton()}
|
|
357
|
+
<TranscriptMenu
|
|
358
|
+
{...{
|
|
359
|
+
shouldUseCalculatedHeight,
|
|
360
|
+
popOverHeight,
|
|
361
|
+
downloadDisabled,
|
|
362
|
+
onDownload,
|
|
363
|
+
printDisabled,
|
|
364
|
+
onPrint,
|
|
365
|
+
isLoading,
|
|
366
|
+
detachMenuItem,
|
|
367
|
+
kitchenSinkDetached,
|
|
368
|
+
textTracks,
|
|
369
|
+
changeLanguage
|
|
370
|
+
}}
|
|
253
371
|
/>
|
|
254
|
-
|
|
255
|
-
{this._renderDetachButton()}
|
|
372
|
+
{!detachMenuItem && this._renderDetachButton()}
|
|
256
373
|
{!kitchenSinkDetached && (
|
|
257
374
|
<div data-testid="transcriptCloseButton">
|
|
258
375
|
<Button
|
|
@@ -348,7 +465,6 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
|
|
|
348
465
|
activeSearchIndex={activeSearchIndex}
|
|
349
466
|
captionProps={captionProps}
|
|
350
467
|
onScroll={this._onScroll}
|
|
351
|
-
widgetWidth={this.state.widgetWidth}
|
|
352
468
|
showItemsIcons={true}
|
|
353
469
|
searchActive={false}
|
|
354
470
|
/>
|
|
@@ -415,39 +531,44 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
|
|
|
415
531
|
private _debounced = {
|
|
416
532
|
findSearchMatches: debounce(this._findSearchMatches, this.props.searchDebounceTimeout),
|
|
417
533
|
onActiveSearchIndexChange: debounce(this._setActiveSearchIndex, this.props.searchNextPrevDebounceTimeout),
|
|
418
|
-
searchEventDispatcher: debounce(this._dispatchSearchEvent, SEARCH_EVENT_DISPATCH_TIMEOUT)
|
|
534
|
+
searchEventDispatcher: debounce(this._dispatchSearchEvent, SEARCH_EVENT_DISPATCH_TIMEOUT),
|
|
535
|
+
setWidgetSize: debounce(this._setWidgetSize, WIDGET_RESIZE_DEBOUNCE_TIMEOUT)
|
|
419
536
|
};
|
|
420
537
|
|
|
421
538
|
render(props: TranscriptProps) {
|
|
422
539
|
const {isLoading, kitchenSinkActive, kitchenSinkDetached, hasError, smallScreen, toggledWithEnter} = props;
|
|
423
540
|
const renderTranscriptButtons = !(isLoading || hasError);
|
|
541
|
+
|
|
424
542
|
return (
|
|
425
|
-
<
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
this.
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
543
|
+
<ScreenReaderProvider>
|
|
544
|
+
<div
|
|
545
|
+
className={`${styles.root} ${kitchenSinkActive || kitchenSinkDetached ? '' : styles.hidden}`}
|
|
546
|
+
ref={node => {
|
|
547
|
+
this._widgetRootRef = node;
|
|
548
|
+
}}
|
|
549
|
+
onKeyUp={this._handleClose}
|
|
550
|
+
data-testid="transcript_root">
|
|
551
|
+
{smallScreen && !kitchenSinkDetached ? (
|
|
552
|
+
<SmallScreenSlate onClose={this.props.onClose} toggledWithEnter={toggledWithEnter} />
|
|
553
|
+
) : (
|
|
554
|
+
<div className={styles.globalContainer}>
|
|
555
|
+
{this._renderHeader()}
|
|
556
|
+
|
|
557
|
+
{renderTranscriptButtons && this._renderSkipTranscriptButton()}
|
|
558
|
+
<div
|
|
559
|
+
className={styles.body}
|
|
560
|
+
onScroll={this._onScroll}
|
|
561
|
+
ref={node => {
|
|
562
|
+
this._transcriptListRef = node;
|
|
563
|
+
}}
|
|
564
|
+
data-testid="transcript_list">
|
|
565
|
+
{isLoading ? this._renderLoading() : this._renderTranscript()}
|
|
566
|
+
</div>
|
|
567
|
+
{renderTranscriptButtons && this._renderScrollToButton()}
|
|
446
568
|
</div>
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
</div>
|
|
569
|
+
)}
|
|
570
|
+
</div>
|
|
571
|
+
</ScreenReaderProvider>
|
|
451
572
|
);
|
|
452
573
|
}
|
|
453
574
|
}
|