[RN] Add ExpandedLabel

This commit is contained in:
Bettenbuk Zoltan 2018-09-11 12:16:01 +02:00 committed by Saúl Ibarra Corretgé
parent d604cdfe27
commit e5cc732b72
27 changed files with 860 additions and 75 deletions

View File

@ -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",

4
package-lock.json generated
View File

@ -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"

View File

@ -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> {
}

View File

@ -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();
}
}
}

View File

@ -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.
*

View File

@ -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;
}
}

View File

@ -1 +1,2 @@
export { default as CircularLabel } from './CircularLabel';
export { default as ExpandedLabel } from './ExpandedLabel';

View File

@ -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
}
});

View File

@ -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.
*
@ -45,35 +153,182 @@ class Labels extends AbstractLabels<Props, *> {
return (
<View
pointerEvents = 'box-none'
style = { styles.labelWrapper }>
<View
onLayout = { this._onTopViewLayout }
pointerEvents = 'box-none'
style = { [
styles.indicatorContainer,
wide && _filmstripVisible && styles.indicatorContainerWide
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.
* 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()
_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<*>

View File

@ -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.
*/

View File

@ -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)
};
}

View File

@ -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));

View File

@ -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 } />
);
}

View File

@ -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.
*

View File

@ -9,3 +9,4 @@ export {
StopRecordingDialog
} from './Recording';
export { default as RecordingLabel } from './RecordingLabel';
export { default as RecordingExpandedLabel } from './RecordingExpandedLabel';

View File

@ -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
}
});

View File

@ -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
];

View File

@ -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;
}

View File

@ -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);

View File

@ -1 +1,4 @@
export { default as TranscribingLabel } from './TranscribingLabel';
export {
default as TranscribingExpandedLabel
} from './TranscribingExpandedLabel';

View File

@ -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);

View File

@ -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';

View File

@ -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
}
});