[RN] Add ExpandedLabel
This commit is contained in:
parent
d604cdfe27
commit
e5cc732b72
|
@ -462,6 +462,9 @@
|
|||
"busyTitle": "All recorders are currently busy",
|
||||
"buttonTooltip": "Start / Stop recording",
|
||||
"error": "Recording failed. Please try again.",
|
||||
"expandedOff": "Recording has stopped",
|
||||
"expandedOn": "The meeting is currently being recorded.",
|
||||
"expandedPending": "Recording is being started...",
|
||||
"failedToStart": "Recording failed to start",
|
||||
"live": "LIVE",
|
||||
"off": "Recording stopped",
|
||||
|
@ -483,6 +486,7 @@
|
|||
"pending" : "Preparing to transcribe the meeting...",
|
||||
"off" : "Transcribing stopped",
|
||||
"error": "Transcribing failed. Please try again.",
|
||||
"expandedLabel": "Transcribing is currently on",
|
||||
"failedToStart": "Transcribing failed to start",
|
||||
"tr": "TR",
|
||||
"labelToolTip": "The meeting is being transcribed",
|
||||
|
@ -502,6 +506,9 @@
|
|||
"error": "Live Streaming failed. Please try again.",
|
||||
"errorAPI": "An error occurred while accessing your YouTube broadcasts. Please try logging in again.",
|
||||
"errorLiveStreamNotEnabled": "Live Streaming is not enabled on __email__. Please enable live streaming or log into an account with live streaming enabled.",
|
||||
"expandedOff": "The live streaming has stopped",
|
||||
"expandedOn": "The meeting is currently being streamed to YouTube.",
|
||||
"expandedPending": "The live streaming is being started...",
|
||||
"failedToStart": "Live Streaming failed to start",
|
||||
"off": "Live Streaming stopped",
|
||||
"on": "Live Streaming",
|
||||
|
@ -546,6 +553,7 @@
|
|||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "AUD",
|
||||
"audioOnlyExpanded": "You are in audio only mode. This mode saves bandwidth but you won't see videos of others.",
|
||||
"callQuality": "Call Quality",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Viewing high definition video",
|
||||
|
|
|
@ -441,7 +441,7 @@
|
|||
},
|
||||
"@atlaskit/inline-dialog": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "http://registry.npmjs.org/@atlaskit/inline-dialog/-/inline-dialog-5.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/inline-dialog/-/inline-dialog-5.3.0.tgz",
|
||||
"integrity": "sha512-4bEeC5rZwtb4YO9BxW1UCJYCp/dyCVXqcygRW1BDnYVbveAI8wdym6qEi4BRvIwXCT4qgNhsVsqcxSrn0X6CKQ==",
|
||||
"requires": {
|
||||
"@atlaskit/layer": "^2.8.0",
|
||||
|
@ -451,7 +451,7 @@
|
|||
"dependencies": {
|
||||
"@atlaskit/layer": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "http://registry.npmjs.org/@atlaskit/layer/-/layer-2.9.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/layer/-/layer-2.9.1.tgz",
|
||||
"integrity": "sha512-nyIVGeS2OhuGR5gIMTYUfRmCG8z/9KMgUzTpbpsB70sH6+d4KSFhfkz+KhKNIa8gvKI6zBc+3UBYSlUW1t1qmQ==",
|
||||
"requires": {
|
||||
"styled-components": "1.4.6 - 3"
|
||||
|
|
|
@ -12,6 +12,7 @@ export type Props = {
|
|||
/**
|
||||
* Abstract class for the {@code CircularLabel} component.
|
||||
*/
|
||||
export default class AbstractCircularLabel<P: Props> extends Component<P> {
|
||||
export default class AbstractCircularLabel<P: Props, S: *>
|
||||
extends Component<P, S> {
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Animated, Text } from 'react-native';
|
||||
|
||||
import { combineStyles, type StyleType } from '../../styles';
|
||||
|
||||
|
@ -9,36 +9,142 @@ import AbstractCircularLabel, {
|
|||
} from './AbstractCircularLabel';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Const for status string 'in progress'.
|
||||
*/
|
||||
const STATUS_IN_PROGRESS = 'in_progress';
|
||||
|
||||
/**
|
||||
* Const for status string 'off'.
|
||||
*/
|
||||
const STATUS_OFF = 'off';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* Status of the label. This prop adds some additional styles based on its
|
||||
* value. E.g. if status = off, it will render the label symbolising that
|
||||
* the thing it displays (e.g. recording) is off.
|
||||
*/
|
||||
status: ('in_progress' | 'off' | 'on'),
|
||||
|
||||
/**
|
||||
* Style of the label.
|
||||
*/
|
||||
style?: ?StyleType
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* An animation object handling the opacity changes of the in progress
|
||||
* label.
|
||||
*/
|
||||
pulseAnimation: Object
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a circular indicator to be used for status icons, such as recording
|
||||
* on, audio-only conference, video quality and similar.
|
||||
*/
|
||||
export default class CircularLabel extends AbstractCircularLabel<Props> {
|
||||
export default class CircularLabel extends AbstractCircularLabel<Props, State> {
|
||||
/**
|
||||
* A reference to the started animation of this label.
|
||||
*/
|
||||
animationReference: Object;
|
||||
|
||||
/**
|
||||
* Instantiates a new instance of {@code CircularLabel}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pulseAnimation: new Animated.Value(0)
|
||||
};
|
||||
|
||||
this._maybeToggleAnimation({}, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code Component#componentWillReceiveProps}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentWillReceiveProps(newProps: Props) {
|
||||
this._maybeToggleAnimation(this.props, newProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React {@link Component}'s render.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { label, style } = this.props;
|
||||
const { status, label, style } = this.props;
|
||||
|
||||
let extraStyle = null;
|
||||
|
||||
switch (status) {
|
||||
case STATUS_IN_PROGRESS:
|
||||
extraStyle = {
|
||||
opacity: this.state.pulseAnimation
|
||||
};
|
||||
break;
|
||||
case STATUS_OFF:
|
||||
extraStyle = styles.labelOff;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style = {
|
||||
combineStyles(styles.indicatorContainer, style)
|
||||
}>
|
||||
<Animated.View
|
||||
style = { [
|
||||
combineStyles(styles.indicatorContainer, style),
|
||||
extraStyle
|
||||
] }>
|
||||
<Text style = { styles.indicatorText }>
|
||||
{ label }
|
||||
</Text>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the animation has to be started or stopped and acts
|
||||
* accordingly.
|
||||
*
|
||||
* @param {Props} oldProps - The previous values of the Props.
|
||||
* @param {Props} newProps - The new values of the Props.
|
||||
* @returns {void}
|
||||
*/
|
||||
_maybeToggleAnimation(oldProps, newProps) {
|
||||
const { status: oldStatus } = oldProps;
|
||||
const { status: newStatus } = newProps;
|
||||
const { pulseAnimation } = this.state;
|
||||
|
||||
if (newStatus === STATUS_IN_PROGRESS
|
||||
&& oldStatus !== STATUS_IN_PROGRESS) {
|
||||
// Animation must be started
|
||||
this.animationReference = Animated.loop(Animated.sequence([
|
||||
Animated.timing(pulseAnimation, {
|
||||
delay: 500,
|
||||
toValue: 1,
|
||||
useNativeDriver: true
|
||||
}),
|
||||
Animated.timing(pulseAnimation, {
|
||||
toValue: 0.3,
|
||||
useNativeDriver: true
|
||||
})
|
||||
]));
|
||||
|
||||
this.animationReference.start();
|
||||
} else if (this.animationReference
|
||||
&& newStatus !== STATUS_IN_PROGRESS
|
||||
&& oldStatus === STATUS_IN_PROGRESS) {
|
||||
// Animation must be stopped
|
||||
this.animationReference.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ type Props = AbstractProps & {
|
|||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class CircularLabel extends AbstractCircularLabel<Props> {
|
||||
export default class CircularLabel extends AbstractCircularLabel<Props, {}> {
|
||||
/**
|
||||
* Default values for {@code CircularLabel} component's properties.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Animated, Text, View } from 'react-native';
|
||||
|
||||
import styles, { DEFAULT_COLOR, LABEL_MARGIN, LABEL_SIZE } from './styles';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The position of the parent element (from right to left) to display the
|
||||
* arrow.
|
||||
*/
|
||||
parentPosition: number
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The opacity animation Object.
|
||||
*/
|
||||
opacityAnimation: Object,
|
||||
|
||||
/**
|
||||
* A boolean to descide to show or not show the arrow. This is required as
|
||||
* we can't easily animate this transformed Component so we render it once
|
||||
* the animation is done.
|
||||
*/
|
||||
showArrow: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Offset to the arrow to be rendered in the right position.
|
||||
*/
|
||||
const ARROW_OFFSET = 0;
|
||||
|
||||
/**
|
||||
* A react {@code Component} that implements an expanded label as tooltip-like
|
||||
* component to explain the meaning of the {@code Label}.
|
||||
*/
|
||||
export default class ExpandedLabel<P: Props> extends Component<P, State> {
|
||||
/**
|
||||
* Instantiates a new {@code ExpandedLabel} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
opacityAnimation: new Animated.Value(0),
|
||||
showArrow: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React {@code Component}'s componentDidMount.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
Animated.decay(this.state.opacityAnimation, {
|
||||
toValue: 1,
|
||||
velocity: 1,
|
||||
useNativeDriver: true
|
||||
}).start(({ finished }) => {
|
||||
finished && this.setState({
|
||||
showArrow: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React {@code Component}'s render.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const arrowPosition
|
||||
= this.props.parentPosition - LABEL_MARGIN - (LABEL_SIZE / 2);
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style = { [
|
||||
styles.expandedLabelWrapper,
|
||||
{
|
||||
opacity: this.state.opacityAnimation
|
||||
}
|
||||
] } >
|
||||
<View
|
||||
style = { [
|
||||
styles.expandedLabelArrow,
|
||||
{
|
||||
backgroundColor: this._getColor() || DEFAULT_COLOR,
|
||||
marginRight: arrowPosition + ARROW_OFFSET
|
||||
}
|
||||
] } />
|
||||
<View
|
||||
style = { [
|
||||
styles.expandedLabelContainer,
|
||||
{
|
||||
backgroundColor: this._getColor() || DEFAULT_COLOR
|
||||
}
|
||||
] }>
|
||||
<Text style = { styles.expandedLabelText }>
|
||||
{ this._getLabel() }
|
||||
</Text>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label that needs to be rendered in the box. To be implemented
|
||||
* by its overriding classes.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getLabel: () => string
|
||||
|
||||
_getColor: () => string
|
||||
|
||||
/**
|
||||
* Defines the color of the expanded label. This function returns a default
|
||||
* value if implementing classes don't override it, but the goal is to have
|
||||
* expanded labels matching to circular labels in color.
|
||||
* If implementing classes return a falsy value, it also uses the default
|
||||
* color.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getColor() {
|
||||
return DEFAULT_COLOR;
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
export { default as CircularLabel } from './CircularLabel';
|
||||
export { default as ExpandedLabel } from './ExpandedLabel';
|
||||
|
|
|
@ -1,30 +1,75 @@
|
|||
// @flow
|
||||
|
||||
import { ColorPalette, createStyleSheet } from '../../styles';
|
||||
import { BoxModel, ColorPalette, createStyleSheet } from '../../styles';
|
||||
|
||||
/**
|
||||
* The default color of the {@code Label} and {@code ExpandedLabel}.
|
||||
*/
|
||||
export const DEFAULT_COLOR = '#808080';
|
||||
|
||||
/**
|
||||
* Margin of the {@Label} - to be reused when rendering the
|
||||
* {@code ExpandedLabel}.
|
||||
*/
|
||||
export const LABEL_MARGIN = 5;
|
||||
|
||||
/**
|
||||
* Size of the {@Label} - to be reused when rendering the
|
||||
* {@code ExpandedLabel}.
|
||||
*/
|
||||
export const LABEL_SIZE = 36;
|
||||
|
||||
/**
|
||||
* The styles of the native base/label feature.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
|
||||
expandedLabelArrow: {
|
||||
backgroundColor: ColorPalette.blue,
|
||||
height: 15,
|
||||
transform: [ { rotate: '45deg' }, { translateX: 10 } ],
|
||||
width: 15
|
||||
},
|
||||
|
||||
expandedLabelContainer: {
|
||||
backgroundColor: ColorPalette.blue,
|
||||
borderColor: ColorPalette.blue,
|
||||
borderRadius: 6,
|
||||
marginHorizontal: BoxModel.margin,
|
||||
padding: BoxModel.padding
|
||||
},
|
||||
|
||||
expandedLabelText: {
|
||||
color: ColorPalette.white
|
||||
},
|
||||
|
||||
expandedLabelWrapper: {
|
||||
alignItems: 'flex-end',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
||||
/**
|
||||
* The outermost view.
|
||||
*/
|
||||
indicatorContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#808080',
|
||||
borderRadius: 18,
|
||||
backgroundColor: DEFAULT_COLOR,
|
||||
borderRadius: LABEL_SIZE / 2,
|
||||
borderWidth: 0,
|
||||
flex: 0,
|
||||
height: 36,
|
||||
height: LABEL_SIZE,
|
||||
justifyContent: 'center',
|
||||
margin: 5,
|
||||
margin: LABEL_MARGIN,
|
||||
opacity: 0.6,
|
||||
width: 36
|
||||
width: LABEL_SIZE
|
||||
},
|
||||
|
||||
indicatorText: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 12
|
||||
},
|
||||
|
||||
labelOff: {
|
||||
opacity: 0.3
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
||||
import {
|
||||
RecordingExpandedLabel
|
||||
} from '../../recording';
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../base/responsive-ui';
|
||||
import { TranscribingExpandedLabel } from '../../transcribing';
|
||||
import { VideoQualityExpandedLabel } from '../../video-quality';
|
||||
|
||||
import AbstractLabels, {
|
||||
_abstractMapStateToProps,
|
||||
|
@ -21,6 +26,11 @@ import styles from './styles';
|
|||
*/
|
||||
type Props = AbstractLabelsProps & {
|
||||
|
||||
/**
|
||||
* Function to translate i18n labels.
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced (to accommodate
|
||||
* smaller display areas).
|
||||
|
@ -30,10 +40,108 @@ type Props = AbstractLabelsProps & {
|
|||
_reducedUI: boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* Layout object of the outermost container. For stucture please see:
|
||||
* https://facebook.github.io/react-native/docs/view#onlayout
|
||||
*/
|
||||
containerLayout: ?Object,
|
||||
|
||||
/**
|
||||
* Layout objects of the individual labels. This data type contains the same
|
||||
* structure as the layout is defined here:
|
||||
* https://facebook.github.io/react-native/docs/view#onlayout
|
||||
* but keyed with the ID of the label its layout it contains. E.g.
|
||||
*
|
||||
* {
|
||||
* transcribing: {
|
||||
* { layout: { x, y, width, height } }
|
||||
* },
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
labelLayouts: Object,
|
||||
|
||||
/**
|
||||
* Position of the label to render the {@code ExpandedLabel} to.
|
||||
*/
|
||||
parentPosition: ?number,
|
||||
|
||||
/**
|
||||
* String to show which {@code ExpandedLabel} to be shown. (Equals to the
|
||||
* label IDs below.)
|
||||
*/
|
||||
visibleExpandedLabel: ?string
|
||||
}
|
||||
|
||||
const LABEL_ID_QUALITY = 'quality';
|
||||
const LABEL_ID_RECORDING = 'recording';
|
||||
const LABEL_ID_STREAMING = 'streaming';
|
||||
const LABEL_ID_TRANSCRIBING = 'transcribing';
|
||||
|
||||
/**
|
||||
* The {@code ExpandedLabel} components to be rendered for the individual
|
||||
* {@code Label}s.
|
||||
*/
|
||||
const EXPANDED_LABELS = {
|
||||
quality: VideoQualityExpandedLabel,
|
||||
recording: {
|
||||
component: RecordingExpandedLabel,
|
||||
props: {
|
||||
mode: JitsiRecordingConstants.mode.FILE
|
||||
}
|
||||
},
|
||||
streaming: {
|
||||
component: RecordingExpandedLabel,
|
||||
props: {
|
||||
mode: JitsiRecordingConstants.mode.STREAM
|
||||
}
|
||||
},
|
||||
transcribing: TranscribingExpandedLabel
|
||||
};
|
||||
|
||||
/**
|
||||
* Timeout to hide the {@ExpandedLabel}.
|
||||
*/
|
||||
const EXPANDED_LABEL_TIMEOUT = 5000;
|
||||
|
||||
/**
|
||||
* A container that renders the conference indicators, if any.
|
||||
*/
|
||||
class Labels extends AbstractLabels<Props, *> {
|
||||
class Labels extends AbstractLabels<Props, State> {
|
||||
/**
|
||||
* Timeout for the expanded labels to disappear.
|
||||
*/
|
||||
expandedLabelTimeout: TimeoutID;
|
||||
|
||||
/**
|
||||
* Instantiates a new instance of {@code Labels}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
containerLayout: undefined,
|
||||
labelLayouts: {},
|
||||
parentPosition: undefined,
|
||||
visibleExpandedLabel: undefined
|
||||
};
|
||||
|
||||
this._onTopViewLayout = this._onTopViewLayout.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React {@code Component}'s componentWillUnmount.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.expandedLabelTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React {@code Component}'s render.
|
||||
*
|
||||
|
@ -46,34 +154,181 @@ class Labels extends AbstractLabels<Props, *> {
|
|||
return (
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { [
|
||||
styles.indicatorContainer,
|
||||
wide && _filmstripVisible && styles.indicatorContainerWide
|
||||
] }>
|
||||
{
|
||||
this._renderRecordingLabel(
|
||||
JitsiRecordingConstants.mode.FILE)
|
||||
}
|
||||
{
|
||||
this._renderRecordingLabel(
|
||||
JitsiRecordingConstants.mode.STREAM)
|
||||
}
|
||||
{
|
||||
this._renderTranscribingLabel()
|
||||
}
|
||||
{/*
|
||||
* Emil, Lyubomir, Nichole, and Zoli said that the Labels
|
||||
* should not be rendered in Picture-in-Picture. Saul argued
|
||||
* that the recording Labels should be rendered. As a temporary
|
||||
* compromise, don't render the VideoQualityLabel at least
|
||||
* because it's not that important.
|
||||
*/
|
||||
_reducedUI || this._renderVideoQualityLabel()
|
||||
}
|
||||
style = { styles.labelWrapper }>
|
||||
<View
|
||||
onLayout = { this._onTopViewLayout }
|
||||
pointerEvents = 'box-none'
|
||||
style = { [
|
||||
styles.indicatorContainer,
|
||||
wide && _filmstripVisible
|
||||
&& styles.indicatorContainerWide
|
||||
] }>
|
||||
<TouchableOpacity
|
||||
onLayout = { this._createOnLayout(LABEL_ID_RECORDING) }
|
||||
onPress = { this._createOnPress(LABEL_ID_RECORDING) } >
|
||||
{
|
||||
this._renderRecordingLabel(
|
||||
JitsiRecordingConstants.mode.FILE)
|
||||
}
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onLayout = { this._createOnLayout(LABEL_ID_STREAMING) }
|
||||
onPress = { this._createOnPress(LABEL_ID_STREAMING) } >
|
||||
{
|
||||
this._renderRecordingLabel(
|
||||
JitsiRecordingConstants.mode.STREAM)
|
||||
}
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onLayout = {
|
||||
this._createOnLayout(LABEL_ID_TRANSCRIBING)
|
||||
}
|
||||
onPress = {
|
||||
this._createOnPress(LABEL_ID_TRANSCRIBING)
|
||||
} >
|
||||
{
|
||||
this._renderTranscribingLabel()
|
||||
}
|
||||
</TouchableOpacity>
|
||||
{/*
|
||||
* Emil, Lyubomir, Nichole, and Zoli said that the Labels
|
||||
* should not be rendered in Picture-in-Picture. Saul
|
||||
* argued that the recording Labels should be rendered. As
|
||||
* a temporary compromise, don't render the
|
||||
* VideoQualityLabel at least because it's not that
|
||||
* important.
|
||||
*/
|
||||
_reducedUI || (
|
||||
<TouchableOpacity
|
||||
onLayout = {
|
||||
this._createOnLayout(LABEL_ID_QUALITY) }
|
||||
onPress = {
|
||||
this._createOnPress(LABEL_ID_QUALITY) } >
|
||||
{ this._renderVideoQualityLabel() }
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
</View>
|
||||
<View
|
||||
style = { [
|
||||
styles.indicatorContainer,
|
||||
wide && _filmstripVisible
|
||||
&& styles.indicatorContainerWide
|
||||
] }>
|
||||
{
|
||||
this._renderExpandedLabel()
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function to be invoked when the onLayout of the touchables are
|
||||
* triggered.
|
||||
*
|
||||
* @param {string} label - The identifier of the label that's onLayout is
|
||||
* triggered.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_createOnLayout(label) {
|
||||
return ({ nativeEvent: { layout } }) => {
|
||||
const { labelLayouts } = this.state;
|
||||
const updatedLayout = {};
|
||||
|
||||
updatedLayout[label] = layout;
|
||||
|
||||
this.setState({
|
||||
labelLayouts: {
|
||||
...labelLayouts,
|
||||
...updatedLayout
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function to be invoked when the onPress of the touchables are
|
||||
* triggered.
|
||||
*
|
||||
* @param {string} label - The identifier of the label that's onLayout is
|
||||
* triggered.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_createOnPress(label) {
|
||||
return () => {
|
||||
const {
|
||||
containerLayout,
|
||||
labelLayouts
|
||||
} = this.state;
|
||||
let { visibleExpandedLabel } = this.state;
|
||||
|
||||
if (containerLayout) {
|
||||
const labelLayout = labelLayouts[label];
|
||||
|
||||
// This calculation has to be changed if the labels are not
|
||||
// positioned right anymore.
|
||||
const right = containerLayout.width - labelLayout.x;
|
||||
|
||||
visibleExpandedLabel
|
||||
= visibleExpandedLabel === label ? undefined : label;
|
||||
|
||||
clearTimeout(this.expandedLabelTimeout);
|
||||
this.setState({
|
||||
parentPosition: right,
|
||||
visibleExpandedLabel
|
||||
});
|
||||
|
||||
if (visibleExpandedLabel) {
|
||||
this.expandedLabelTimeout = setTimeout(() => {
|
||||
this.setState({
|
||||
visibleExpandedLabel: undefined
|
||||
});
|
||||
}, EXPANDED_LABEL_TIMEOUT);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_onTopViewLayout: Object => void
|
||||
|
||||
/**
|
||||
* Invoked when the View containing the {@code Label}s is laid out.
|
||||
*
|
||||
* @param {Object} layout - The native layout object.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTopViewLayout({ nativeEvent: { layout } }) {
|
||||
this.setState({
|
||||
containerLayout: layout
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendes the expanded (explaining) label for the label that was touched.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderExpandedLabel() {
|
||||
const { parentPosition, visibleExpandedLabel } = this.state;
|
||||
|
||||
if (visibleExpandedLabel) {
|
||||
const expandedLabel = EXPANDED_LABELS[visibleExpandedLabel];
|
||||
|
||||
if (expandedLabel) {
|
||||
const component = expandedLabel.component || expandedLabel;
|
||||
const expandedLabelProps = expandedLabel.props || {};
|
||||
|
||||
return React.createElement(component, {
|
||||
...expandedLabelProps,
|
||||
parentPosition
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_renderRecordingLabel: string => React$Element<*>;
|
||||
|
||||
_renderTranscribingLabel: () => React$Element<*>
|
||||
|
|
|
@ -9,13 +9,26 @@ import { FILMSTRIP_SIZE } from '../../filmstrip';
|
|||
export const AVATAR_SIZE = 200;
|
||||
|
||||
export default createStyleSheet({
|
||||
|
||||
/**
|
||||
* View that contains the indicators.
|
||||
*/
|
||||
indicatorContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
margin: BoxModel.margin,
|
||||
justifyContent: 'flex-end',
|
||||
margin: BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicator container for wide aspect ratio.
|
||||
*/
|
||||
indicatorContainerWide: {
|
||||
marginRight: FILMSTRIP_SIZE + BoxModel.margin
|
||||
},
|
||||
|
||||
labelWrapper: {
|
||||
flexDirection: 'column',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
|
||||
|
@ -25,13 +38,6 @@ export default createStyleSheet({
|
|||
top: BoxModel.margin * 3
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicator container for wide aspect ratio.
|
||||
*/
|
||||
indicatorContainerWide: {
|
||||
right: FILMSTRIP_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
* Large video container style.
|
||||
*/
|
||||
|
|
|
@ -4,6 +4,8 @@ import { Component } from 'react';
|
|||
|
||||
import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
||||
|
||||
import { getSessionStatusToShow } from '../functions';
|
||||
|
||||
/**
|
||||
* NOTE: Web currently renders multiple indicators if multiple recording
|
||||
* sessions are running. This is however may not be a good UX as it's not
|
||||
|
@ -12,13 +14,12 @@ import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
|||
* running. These boolean are shared across the two components to make it
|
||||
* easier to align web's behaviour to mobile's later if necessary.
|
||||
*/
|
||||
export type Props = {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* True if there is an active recording with the provided mode therefore the
|
||||
* component must be rendered.
|
||||
* The status of the highermost priority session.
|
||||
*/
|
||||
_visible: boolean,
|
||||
_status: ?string,
|
||||
|
||||
/**
|
||||
* The recording mode this indicator should display.
|
||||
|
@ -34,8 +35,8 @@ export type Props = {
|
|||
/**
|
||||
* Abstract class for the {@code RecordingLabel} component.
|
||||
*/
|
||||
export default class AbstractRecordingLabel<P: Props>
|
||||
extends Component<P> {
|
||||
export default class AbstractRecordingLabel
|
||||
extends Component<Props> {
|
||||
|
||||
/**
|
||||
* Implements React {@code Component}'s render.
|
||||
|
@ -43,7 +44,7 @@ export default class AbstractRecordingLabel<P: Props>
|
|||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
return this.props._visible ? this._renderLabel() : null;
|
||||
return this.props._status ? this._renderLabel() : null;
|
||||
}
|
||||
|
||||
_getLabelKey: () => ?string
|
||||
|
@ -84,20 +85,13 @@ export default class AbstractRecordingLabel<P: Props>
|
|||
* @param {Props} ownProps - The component's own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _visible: boolean
|
||||
* _status: ?string
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
const { mode } = ownProps;
|
||||
const _recordingSessions = state['features/recording'].sessionDatas;
|
||||
const _visible
|
||||
= Array.isArray(_recordingSessions)
|
||||
&& _recordingSessions.some(
|
||||
session => session.status === JitsiRecordingConstants.status.ON
|
||||
&& session.mode === mode
|
||||
);
|
||||
|
||||
return {
|
||||
_visible
|
||||
_status: getSessionStatusToShow(state, mode)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
||||
import {
|
||||
ExpandedLabel,
|
||||
type Props as AbstractProps
|
||||
} from '../../base/label';
|
||||
|
||||
import { getSessionStatusToShow } from '../functions';
|
||||
|
||||
import { LIVE_LABEL_COLOR, REC_LABEL_COLOR } from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The status of the highermost priority session.
|
||||
*/
|
||||
_status: ?string,
|
||||
|
||||
/**
|
||||
* The recording mode this indicator should display.
|
||||
*/
|
||||
mode: string,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* A react {@code Component} that implements an expanded label as tooltip-like
|
||||
* component to explain the meaning of the {@code RecordingLabel}.
|
||||
*/
|
||||
class RecordingExpandedLabel extends ExpandedLabel<Props> {
|
||||
/**
|
||||
* Returns the color this expanded label should be rendered with.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getColor() {
|
||||
switch (this.props.mode) {
|
||||
case JitsiRecordingConstants.mode.STREAM:
|
||||
return LIVE_LABEL_COLOR;
|
||||
case JitsiRecordingConstants.mode.FILE:
|
||||
return REC_LABEL_COLOR;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label specific text of this {@code ExpandedLabel}.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getLabel() {
|
||||
const { _status, mode, t } = this.props;
|
||||
let postfix = 'recording', prefix = 'expandedOn'; // Default values.
|
||||
|
||||
switch (mode) {
|
||||
case JitsiRecordingConstants.mode.STREAM:
|
||||
prefix = 'liveStreaming';
|
||||
break;
|
||||
case JitsiRecordingConstants.mode.FILE:
|
||||
prefix = 'recording';
|
||||
break;
|
||||
}
|
||||
|
||||
switch (_status) {
|
||||
case JitsiRecordingConstants.status.OFF:
|
||||
postfix = 'expandedOff';
|
||||
break;
|
||||
case JitsiRecordingConstants.status.PENDING:
|
||||
postfix = 'expandedPending';
|
||||
break;
|
||||
case JitsiRecordingConstants.status.ON:
|
||||
postfix = 'expandedOn';
|
||||
break;
|
||||
}
|
||||
|
||||
return t(`${prefix}.${postfix}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated
|
||||
* {@code RecordingExpandedLabel}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The component's own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _status: ?string
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
const { mode } = ownProps;
|
||||
|
||||
return {
|
||||
_status: getSessionStatusToShow(state, mode)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(RecordingExpandedLabel));
|
|
@ -8,7 +8,6 @@ import { CircularLabel } from '../../base/label';
|
|||
import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
||||
|
||||
import AbstractRecordingLabel, {
|
||||
type Props,
|
||||
_mapStateToProps
|
||||
} from './AbstractRecordingLabel';
|
||||
import styles from './styles';
|
||||
|
@ -19,7 +18,7 @@ import styles from './styles';
|
|||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
class RecordingLabel extends AbstractRecordingLabel<Props> {
|
||||
class RecordingLabel extends AbstractRecordingLabel {
|
||||
|
||||
/**
|
||||
* Renders the platform specific label component.
|
||||
|
@ -41,9 +40,21 @@ class RecordingLabel extends AbstractRecordingLabel<Props> {
|
|||
return null;
|
||||
}
|
||||
|
||||
let status = 'on';
|
||||
|
||||
switch (this.props._status) {
|
||||
case JitsiRecordingConstants.status.PENDING:
|
||||
status = 'in_progress';
|
||||
break;
|
||||
case JitsiRecordingConstants.status.OFF:
|
||||
status = 'off';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<CircularLabel
|
||||
label = { this.props.t(this._getLabelKey()) }
|
||||
status = { status }
|
||||
style = { indicatorStyle } />
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import { CircularLabel } from '../../base/label';
|
|||
import { translate } from '../../base/i18n';
|
||||
|
||||
import AbstractRecordingLabel, {
|
||||
type Props,
|
||||
_mapStateToProps
|
||||
} from './AbstractRecordingLabel';
|
||||
|
||||
|
@ -17,7 +16,7 @@ import AbstractRecordingLabel, {
|
|||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
class RecordingLabel extends AbstractRecordingLabel<Props> {
|
||||
class RecordingLabel extends AbstractRecordingLabel {
|
||||
/**
|
||||
* Renders the platform specific label component.
|
||||
*
|
||||
|
|
|
@ -9,3 +9,4 @@ export {
|
|||
StopRecordingDialog
|
||||
} from './Recording';
|
||||
export { default as RecordingLabel } from './RecordingLabel';
|
||||
export { default as RecordingExpandedLabel } from './RecordingExpandedLabel';
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
import { ColorPalette, createStyleSheet } from '../../base/styles';
|
||||
|
||||
export const LIVE_LABEL_COLOR = ColorPalette.blue;
|
||||
export const REC_LABEL_COLOR = ColorPalette.red;
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Components} of the feature recording.
|
||||
*/
|
||||
|
@ -11,13 +14,13 @@ export default createStyleSheet({
|
|||
* Style for the recording indicator.
|
||||
*/
|
||||
indicatorLive: {
|
||||
backgroundColor: ColorPalette.blue
|
||||
backgroundColor: LIVE_LABEL_COLOR
|
||||
},
|
||||
|
||||
/**
|
||||
* Style for the recording indicator.
|
||||
*/
|
||||
indicatorRecording: {
|
||||
backgroundColor: ColorPalette.red
|
||||
backgroundColor: REC_LABEL_COLOR
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
|
||||
/**
|
||||
* The identifier of the sound to be played when a recording or live streaming
|
||||
* session is stopped.
|
||||
|
@ -26,3 +28,15 @@ export const RECORDING_TYPES = {
|
|||
JIBRI: 'jibri',
|
||||
JIRECON: 'jirecon'
|
||||
};
|
||||
|
||||
/**
|
||||
* An array defining the priorities of the recording (or live streaming)
|
||||
* statuses, where the index of the array is the priority itself.
|
||||
*
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
export const RECORDING_STATUS_PRIORITIES = [
|
||||
JitsiRecordingConstants.status.OFF,
|
||||
JitsiRecordingConstants.status.PENDING,
|
||||
JitsiRecordingConstants.status.ON
|
||||
];
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
|
||||
import { RECORDING_STATUS_PRIORITIES } from './constants';
|
||||
|
||||
/**
|
||||
* Searches in the passed in redux state for an active recording session of the
|
||||
* passed in mode.
|
||||
|
@ -43,3 +45,30 @@ export function getSessionById(state: Object, id: string) {
|
|||
return state['features/recording'].sessionDatas.find(
|
||||
sessionData => sessionData.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recording session status that is to be shown in a label. E.g. if
|
||||
* there is a session with the status OFF and one with PENDING, then the PENDING
|
||||
* one will be shown, because that is likely more important for the user to see.
|
||||
*
|
||||
* @param {Object} state - The redux state to search in.
|
||||
* @param {string} mode - The recording mode to get status for.
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
export function getSessionStatusToShow(state: Object, mode: string): ?string {
|
||||
const recordingSessions = state['features/recording'].sessionDatas;
|
||||
let status;
|
||||
|
||||
if (Array.isArray(recordingSessions)) {
|
||||
for (const session of recordingSessions) {
|
||||
if (session.mode === mode
|
||||
&& (!status
|
||||
|| (RECORDING_STATUS_PRIORITIES.indexOf(session.status)
|
||||
> RECORDING_STATUS_PRIORITIES.indexOf(status)))) {
|
||||
status = session.status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// @flow
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { ExpandedLabel, type Props as AbstractProps } from '../../base/label';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* A react {@code Component} that implements an expanded label as tooltip-like
|
||||
* component to explain the meaning of the {@code TranscribingLabel}.
|
||||
*/
|
||||
class TranscribingExpandedLabel extends ExpandedLabel<Props> {
|
||||
/**
|
||||
* Returns the label specific text of this {@code ExpandedLabel}.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getLabel() {
|
||||
return this.props.t('transcribing.expandedLabel');
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(TranscribingExpandedLabel);
|
|
@ -1 +1,4 @@
|
|||
export { default as TranscribingLabel } from './TranscribingLabel';
|
||||
export {
|
||||
default as TranscribingExpandedLabel
|
||||
} from './TranscribingExpandedLabel';
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// @flow
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { ExpandedLabel, type Props as AbstractProps } from '../../base/label';
|
||||
|
||||
import { AUD_LABEL_COLOR } from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* A react {@code Component} that implements an expanded label as tooltip-like
|
||||
* component to explain the meaning of the {@code VideoQualityLabel}.
|
||||
*/
|
||||
class VideoQualityExpandedLabel extends ExpandedLabel<Props> {
|
||||
/**
|
||||
* Returns the color this expanded label should be rendered with.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getColor() {
|
||||
return AUD_LABEL_COLOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label specific text of this {@code ExpandedLabel}.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getLabel() {
|
||||
return this.props.t('videoStatus.audioOnlyExpanded');
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(VideoQualityExpandedLabel);
|
|
@ -3,3 +3,6 @@ export {
|
|||
} from './OverflowMenuVideoQualityItem';
|
||||
export { default as VideoQualityDialog } from './VideoQualityDialog';
|
||||
export { default as VideoQualityLabel } from './VideoQualityLabel';
|
||||
export {
|
||||
default as VideoQualityExpandedLabel
|
||||
} from './VideoQualityExpandedLabel';
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import { ColorPalette, createStyleSheet } from '../../base/styles';
|
||||
|
||||
export const AUD_LABEL_COLOR = ColorPalette.green;
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Components} of the feature video-quality.
|
||||
*/
|
||||
|
@ -11,6 +13,6 @@ export default createStyleSheet({
|
|||
* Style for the audio-only indicator.
|
||||
*/
|
||||
indicatorAudioOnly: {
|
||||
backgroundColor: ColorPalette.green
|
||||
backgroundColor: AUD_LABEL_COLOR
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue