mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-11 12:22:32 +00:00
Rearrange recording feature files
This commit is contained in:
committed by
Zoltan Bettenbuk
parent
2b1cb75e40
commit
71edea8aac
@@ -0,0 +1,142 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { Container, Text } from '../../../../base/react';
|
||||
|
||||
import AbstractLiveStreamButton, {
|
||||
_mapStateToProps as _abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractLiveStreamButton';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* True if the button should be disabled, false otherwise.
|
||||
*
|
||||
* NOTE: On web, if the feature is not disabled on purpose, then we still
|
||||
* show the button but disabled and with a tooltip rendered on it,
|
||||
* explaining why it's not available.
|
||||
*/
|
||||
_disabled: boolean,
|
||||
|
||||
/**
|
||||
* Tooltip for the button when it's disabled in a certain way.
|
||||
*/
|
||||
_liveStreamDisabledTooltipKey: ?string
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a button for starting and stopping live streaming.
|
||||
*/
|
||||
class LiveStreamButton extends AbstractLiveStreamButton<Props> {
|
||||
iconName = 'icon-public';
|
||||
toggledIconName = 'icon-public';
|
||||
|
||||
/**
|
||||
* Constructor of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.tooltip = props._liveStreamDisabledTooltipKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code Component}'s componentWillReceiveProps.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentWillReceiveProps(newProps: Props) {
|
||||
this.tooltip = newProps._liveStreamDisabledTooltipKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which returns
|
||||
* a React Element to display (a beta tag) at the end of the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_getElementAfter() {
|
||||
return (
|
||||
<Container
|
||||
className = { 'beta-tag' }>
|
||||
<Text>
|
||||
{ this.props.t('recording.beta') }
|
||||
</Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which must return a
|
||||
* boolean value indicating if this button is disabled or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return this.props._disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for the
|
||||
* {@code LiveStreamButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The own props of the Component.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _conference: Object,
|
||||
* _isLiveStreamRunning: boolean,
|
||||
* _disabled: boolean,
|
||||
* visible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
const abstractProps = _abstractMapStateToProps(state, ownProps);
|
||||
let { visible } = ownProps;
|
||||
|
||||
const _disabledByFeatures = abstractProps.disabledByFeatures;
|
||||
let _disabled = false;
|
||||
let _liveStreamDisabledTooltipKey;
|
||||
|
||||
if (!abstractProps.visible
|
||||
&& _disabledByFeatures !== undefined && !_disabledByFeatures) {
|
||||
_disabled = true;
|
||||
|
||||
// button and tooltip
|
||||
if (state['features/base/jwt'].isGuest) {
|
||||
_liveStreamDisabledTooltipKey
|
||||
= 'dialog.liveStreamingDisabledForGuestTooltip';
|
||||
} else {
|
||||
_liveStreamDisabledTooltipKey
|
||||
= 'dialog.liveStreamingDisabledTooltip';
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof visible === 'undefined') {
|
||||
visible = interfaceConfig.TOOLBAR_BUTTONS.includes('livestreaming')
|
||||
&& (abstractProps.visible
|
||||
|| Boolean(_liveStreamDisabledTooltipKey));
|
||||
}
|
||||
|
||||
return {
|
||||
...abstractProps,
|
||||
_disabled,
|
||||
_liveStreamDisabledTooltipKey,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(LiveStreamButton));
|
||||
@@ -0,0 +1,369 @@
|
||||
// @flow
|
||||
|
||||
import Spinner from '@atlaskit/spinner';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
|
||||
import {
|
||||
GOOGLE_API_STATES,
|
||||
GoogleSignInButton,
|
||||
loadGoogleAPI,
|
||||
requestAvailableYouTubeBroadcasts,
|
||||
requestLiveStreamsForYouTubeBroadcast,
|
||||
showAccountSelection,
|
||||
signIn,
|
||||
updateProfile
|
||||
} from '../../../../google-api';
|
||||
|
||||
import AbstractStartLiveStreamDialog, {
|
||||
_mapStateToProps as _abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractStartLiveStreamDialog';
|
||||
|
||||
import StreamKeyPicker from './StreamKeyPicker';
|
||||
import StreamKeyForm from './StreamKeyForm';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The ID for the Google client application used for making stream key
|
||||
* related requests.
|
||||
*/
|
||||
_googleApiApplicationClientID: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A React Component for requesting a YouTube stream key to use for live
|
||||
* streaming of the current conference.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class StartLiveStreamDialog
|
||||
extends AbstractStartLiveStreamDialog<Props> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code StartLiveStreamDialog} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new {@code StartLiveStreamDialog} instance with.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onGetYouTubeBroadcasts = this._onGetYouTubeBroadcasts.bind(this);
|
||||
this._onInitializeGoogleApi = this._onInitializeGoogleApi.bind(this);
|
||||
this._onGoogleSignIn = this._onGoogleSignIn.bind(this);
|
||||
this._onRequestGoogleSignIn = this._onRequestGoogleSignIn.bind(this);
|
||||
this._onYouTubeBroadcastIDSelected
|
||||
= this._onYouTubeBroadcastIDSelected.bind(this);
|
||||
|
||||
this._renderDialogContent = this._renderDialogContent.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@link Component#componentDidMount()}. Invoked immediately
|
||||
* after this component is mounted.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
|
||||
if (this.props._googleApiApplicationClientID) {
|
||||
this._onInitializeGoogleApi();
|
||||
}
|
||||
}
|
||||
|
||||
_onInitializeGoogleApi: () => Promise<*>;
|
||||
|
||||
/**
|
||||
* Loads the Google web client application used for fetching stream keys.
|
||||
* If the user is already logged in, then a request for available YouTube
|
||||
* broadcasts is also made.
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onInitializeGoogleApi() {
|
||||
this.props.dispatch(
|
||||
loadGoogleAPI(this.props._googleApiApplicationClientID))
|
||||
.catch(response => this._parseErrorFromResponse(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically selects the input field's value after starting to edit the
|
||||
* display name.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidUpdate(previousProps) {
|
||||
if (previousProps._googleAPIState === GOOGLE_API_STATES.LOADED
|
||||
&& this.props._googleAPIState === GOOGLE_API_STATES.SIGNED_IN) {
|
||||
this._onGetYouTubeBroadcasts();
|
||||
}
|
||||
}
|
||||
|
||||
_onGetYouTubeBroadcasts: () => Promise<*>;
|
||||
|
||||
/**
|
||||
* Asks the user to sign in, if not already signed in, and then requests a
|
||||
* list of the user's YouTube broadcasts.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onGetYouTubeBroadcasts() {
|
||||
this.props.dispatch(updateProfile())
|
||||
.catch(response => this._parseErrorFromResponse(response));
|
||||
|
||||
this.props.dispatch(requestAvailableYouTubeBroadcasts())
|
||||
.then(broadcasts => {
|
||||
this._setStateIfMounted({
|
||||
broadcasts
|
||||
});
|
||||
|
||||
if (broadcasts.length === 1) {
|
||||
const broadcast = broadcasts[0];
|
||||
|
||||
this._onYouTubeBroadcastIDSelected(broadcast.boundStreamID);
|
||||
}
|
||||
})
|
||||
.catch(response => this._parseErrorFromResponse(response));
|
||||
}
|
||||
|
||||
_onGoogleSignIn: () => Object;
|
||||
|
||||
/**
|
||||
* Forces the Google web client application to prompt for a sign in, such as
|
||||
* when changing account, and will then fetch available YouTube broadcasts.
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onGoogleSignIn() {
|
||||
this.props.dispatch(signIn())
|
||||
.catch(response => this._parseErrorFromResponse(response));
|
||||
}
|
||||
|
||||
_onRequestGoogleSignIn: () => Object;
|
||||
|
||||
/**
|
||||
* Forces the Google web client application to prompt for a sign in, such as
|
||||
* when changing account, and will then fetch available YouTube broadcasts.
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onRequestGoogleSignIn() {
|
||||
// when there is an error we show the google sign-in button.
|
||||
// once we click it we want to clear the error from the state
|
||||
this.props.dispatch(showAccountSelection())
|
||||
.then(() =>
|
||||
this._setStateIfMounted({
|
||||
broadcasts: undefined,
|
||||
errorType: undefined
|
||||
}))
|
||||
.then(() => this._onGetYouTubeBroadcasts());
|
||||
}
|
||||
|
||||
_onStreamKeyChange: string => void;
|
||||
|
||||
_onYouTubeBroadcastIDSelected: (string) => Object;
|
||||
|
||||
/**
|
||||
* Fetches the stream key for a YouTube broadcast and updates the internal
|
||||
* state to display the associated stream key as being entered.
|
||||
*
|
||||
* @param {string} boundStreamID - The bound stream ID associated with the
|
||||
* broadcast from which to get the stream key.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onYouTubeBroadcastIDSelected(boundStreamID) {
|
||||
this.props.dispatch(
|
||||
requestLiveStreamsForYouTubeBroadcast(boundStreamID))
|
||||
.then(({ streamKey, selectedBoundStreamID }) =>
|
||||
this._setStateIfMounted({
|
||||
streamKey,
|
||||
selectedBoundStreamID
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Only show an error if an external request was made with the Google api.
|
||||
* Do not error if the login in canceled.
|
||||
* And searches in a Google API error response for the error type.
|
||||
*
|
||||
* @param {Object} response - The Google API response that may contain an
|
||||
* error.
|
||||
* @private
|
||||
* @returns {string|null}
|
||||
*/
|
||||
_parseErrorFromResponse(response) {
|
||||
|
||||
if (!response || !response.result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = response.result;
|
||||
const error = result.error;
|
||||
const errors = error && error.errors;
|
||||
const firstError = errors && errors[0];
|
||||
|
||||
this._setStateIfMounted({
|
||||
errorType: (firstError && firstError.reason) || null
|
||||
});
|
||||
}
|
||||
|
||||
_renderDialogContent: () => React$Component<*>
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog content.
|
||||
*
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderDialogContent() {
|
||||
const { _googleApiApplicationClientID } = this.props;
|
||||
|
||||
return (
|
||||
<div className = 'live-stream-dialog'>
|
||||
{ _googleApiApplicationClientID
|
||||
? this._renderYouTubePanel() : null }
|
||||
<StreamKeyForm
|
||||
onChange = { this._onStreamKeyChange }
|
||||
value = { this.state.streamKey || this.props._streamKey } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a React Element for authenticating with the Google web client.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderYouTubePanel() {
|
||||
const {
|
||||
t,
|
||||
_googleProfileEmail
|
||||
} = this.props;
|
||||
const {
|
||||
broadcasts,
|
||||
selectedBoundStreamID
|
||||
} = this.state;
|
||||
|
||||
let googleContent, helpText;
|
||||
|
||||
switch (this.props._googleAPIState) {
|
||||
case GOOGLE_API_STATES.LOADED:
|
||||
googleContent
|
||||
= <GoogleSignInButton onClick = { this._onGoogleSignIn } />;
|
||||
helpText = t('liveStreaming.signInCTA');
|
||||
|
||||
break;
|
||||
|
||||
case GOOGLE_API_STATES.SIGNED_IN:
|
||||
googleContent = (
|
||||
<StreamKeyPicker
|
||||
broadcasts = { broadcasts }
|
||||
onBroadcastSelected = { this._onYouTubeBroadcastIDSelected }
|
||||
selectedBoundStreamID = { selectedBoundStreamID } />
|
||||
);
|
||||
|
||||
/**
|
||||
* FIXME: Ideally this help text would be one translation string
|
||||
* that also accepts the anchor. This can be done using the Trans
|
||||
* component of react-i18next but I couldn't get it working...
|
||||
*/
|
||||
helpText = (
|
||||
<div>
|
||||
{ `${t('liveStreaming.chooseCTA',
|
||||
{ email: _googleProfileEmail })} ` }
|
||||
<a onClick = { this._onRequestGoogleSignIn }>
|
||||
{ t('liveStreaming.changeSignIn') }
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
case GOOGLE_API_STATES.NEEDS_LOADING:
|
||||
default:
|
||||
googleContent = (
|
||||
<Spinner
|
||||
isCompleting = { false }
|
||||
size = 'medium' />
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.state.errorType !== undefined) {
|
||||
googleContent = (
|
||||
<GoogleSignInButton
|
||||
onClick = { this._onRequestGoogleSignIn } />
|
||||
);
|
||||
helpText = this._getGoogleErrorMessageToDisplay();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = 'google-panel'>
|
||||
<div className = 'live-stream-cta'>
|
||||
{ helpText }
|
||||
</div>
|
||||
<div className = 'google-api'>
|
||||
{ googleContent }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_setStateIfMounted: Object => void
|
||||
|
||||
/**
|
||||
* Returns the error message to display for the current error state.
|
||||
*
|
||||
* @private
|
||||
* @returns {string} The error message to display.
|
||||
*/
|
||||
_getGoogleErrorMessageToDisplay() {
|
||||
let text;
|
||||
|
||||
switch (this.state.errorType) {
|
||||
case 'liveStreamingNotEnabled':
|
||||
text = this.props.t(
|
||||
'liveStreaming.errorLiveStreamNotEnabled',
|
||||
{ email: this.props._googleProfileEmail });
|
||||
break;
|
||||
default:
|
||||
text = this.props.t('liveStreaming.errorAPI');
|
||||
break;
|
||||
}
|
||||
|
||||
return <div className = 'google-error'>{ text }</div>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the component's props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {{
|
||||
* _googleApiApplicationClientID: string
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_googleApiApplicationClientID:
|
||||
state['features/base/config'].googleApiApplicationClientID
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(StartLiveStreamDialog));
|
||||
@@ -0,0 +1,29 @@
|
||||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
|
||||
import AbstractStopLiveStreamDialog, {
|
||||
_mapStateToProps
|
||||
} from '../AbstractStopLiveStreamDialog';
|
||||
|
||||
/**
|
||||
* A React Component for confirming the participant wishes to stop the currently
|
||||
* active live stream of the conference.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class StopLiveStreamDialog extends AbstractStopLiveStreamDialog {
|
||||
|
||||
/**
|
||||
* Renders the platform specific {@code Dialog} content.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderDialogContent() {
|
||||
return this.props.t('dialog.stopStreamingWarning');
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(StopLiveStreamDialog));
|
||||
@@ -0,0 +1,85 @@
|
||||
// @flow
|
||||
|
||||
import { FieldTextStateless } from '@atlaskit/field-text';
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
|
||||
import AbstractStreamKeyForm, {
|
||||
type Props
|
||||
} from '../AbstractStreamKeyForm';
|
||||
|
||||
/**
|
||||
* A React Component for entering a key for starting a YouTube live stream.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class StreamKeyForm extends AbstractStreamKeyForm {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code StreamKeyForm} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new {@code StreamKeyForm} instance with.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onOpenHelp = this._onOpenHelp.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { value, t } = this.props;
|
||||
|
||||
return (
|
||||
<div className = 'stream-key-form'>
|
||||
<FieldTextStateless
|
||||
autoFocus = { true }
|
||||
compact = { true }
|
||||
isSpellCheckEnabled = { false }
|
||||
label = { t('dialog.streamKey') }
|
||||
name = 'streamId'
|
||||
okDisabled = { !value }
|
||||
onChange = { this._onInputChange }
|
||||
placeholder = { t('liveStreaming.enterStreamKey') }
|
||||
shouldFitContainer = { true }
|
||||
type = 'text'
|
||||
value = { this.state.value } />
|
||||
{ this.helpURL
|
||||
? <div className = 'form-footer'>
|
||||
<a
|
||||
className = 'helper-link'
|
||||
onClick = { this._onOpenHelp }>
|
||||
{ t('liveStreaming.streamIdHelp') }
|
||||
</a>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_onInputChange: Object => void
|
||||
|
||||
_onOpenHelp: () => void
|
||||
|
||||
/**
|
||||
* Opens a new tab with information on how to manually locate a YouTube
|
||||
* broadcast stream key.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenHelp() {
|
||||
window.open(this.helpURL, 'noopener');
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(StreamKeyForm);
|
||||
@@ -0,0 +1,182 @@
|
||||
/* @flow */
|
||||
|
||||
import {
|
||||
DropdownItem,
|
||||
DropdownItemGroup,
|
||||
DropdownMenuStateless
|
||||
} from '@atlaskit/dropdown-menu';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link StreamKeyPicker}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Broadcasts available for selection. Each broadcast item should be an
|
||||
* object with a title for display in the dropdown and a boundStreamID to
|
||||
* return in the {@link onBroadcastSelected} callback.
|
||||
*/
|
||||
broadcasts: Array<Object>,
|
||||
|
||||
/**
|
||||
* Callback invoked when an item in the dropdown is selected. The selected
|
||||
* broadcast's boundStreamID will be passed back.
|
||||
*/
|
||||
onBroadcastSelected: Function,
|
||||
|
||||
/**
|
||||
* The boundStreamID of the broadcast that should display as selected in the
|
||||
* dropdown.
|
||||
*/
|
||||
selectedBoundStreamID: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link StreamKeyPicker}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* Whether or not to display the dropdown menu to pick a YouTube broadcast.
|
||||
*/
|
||||
isDropdownOpen: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* A dropdown to select a YouTube broadcast.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class StreamKeyPicker extends PureComponent<Props, State> {
|
||||
/**
|
||||
* Default values for {@code StreamKeyForm} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
broadcasts: []
|
||||
};
|
||||
|
||||
/**
|
||||
* The initial state of a {@code StreamKeyForm} instance.
|
||||
*/
|
||||
state = {
|
||||
isDropdownOpen: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code StreamKeyPicker} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new {@code StreamKeyPicker} instance with.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onDropdownOpenChange = this._onDropdownOpenChange.bind(this);
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { broadcasts, selectedBoundStreamID, t } = this.props;
|
||||
|
||||
const dropdownItems
|
||||
= broadcasts.map(broadcast => (
|
||||
<DropdownItem
|
||||
key = { broadcast.boundStreamID }
|
||||
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = { () => this._onSelect(broadcast.boundStreamID) }>
|
||||
{ broadcast.title }
|
||||
</DropdownItem>));
|
||||
const selected
|
||||
= this.props.broadcasts.find(
|
||||
broadcast => broadcast.boundStreamID === selectedBoundStreamID);
|
||||
const triggerText
|
||||
= (selected && selected.title) || t('liveStreaming.choose');
|
||||
|
||||
return (
|
||||
<div className = 'broadcast-dropdown'>
|
||||
<DropdownMenuStateless
|
||||
isOpen = { this.state.isDropdownOpen }
|
||||
onItemActivated = { this._onSelect }
|
||||
onOpenChange = { this._onDropdownOpenChange }
|
||||
shouldFitContainer = { true }
|
||||
trigger = { triggerText }
|
||||
triggerButtonProps = {{
|
||||
className: 'broadcast-dropdown-trigger',
|
||||
shouldFitContainer: true
|
||||
}}
|
||||
triggerType = 'button'>
|
||||
<DropdownItemGroup>
|
||||
{ dropdownItems }
|
||||
</DropdownItemGroup>
|
||||
</DropdownMenuStateless>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the passed in broadcasts into an array of objects that can
|
||||
* be parsed by {@code DropdownMenuStateless}.
|
||||
*
|
||||
* @param {Array<Object>} broadcasts - The YouTube broadcasts to display.
|
||||
* @private
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
_formatBroadcasts(broadcasts) {
|
||||
return broadcasts.map(broadcast => {
|
||||
return {
|
||||
content: broadcast.title,
|
||||
value: broadcast
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
_onDropdownOpenChange: (Object) => void;
|
||||
|
||||
/**
|
||||
* Sets the dropdown to be displayed or not based on the passed in event.
|
||||
*
|
||||
* @param {Object} dropdownEvent - The event passed from
|
||||
* {@code DropdownMenuStateless} indicating if the dropdown should be open
|
||||
* or closed.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDropdownOpenChange(dropdownEvent) {
|
||||
this.setState({
|
||||
isDropdownOpen: dropdownEvent.isOpen
|
||||
});
|
||||
}
|
||||
|
||||
_onSelect: (string) => void;
|
||||
|
||||
/**
|
||||
* Callback invoked when an item has been clicked in the dropdown menu.
|
||||
*
|
||||
* @param {Object} boundStreamID - The bound stream ID for the selected
|
||||
* broadcast.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSelect(boundStreamID) {
|
||||
this.props.onBroadcastSelected(boundStreamID);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(StreamKeyPicker);
|
||||
@@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
export { default as LiveStreamButton } from './LiveStreamButton';
|
||||
export { default as StartLiveStreamDialog } from './StartLiveStreamDialog';
|
||||
export { default as StopLiveStreamDialog } from './StopLiveStreamDialog';
|
||||
Reference in New Issue
Block a user