diff --git a/react/features/base/ui/components/types.ts b/react/features/base/ui/components/types.ts index 2a69f0cf5..e2a10075b 100644 --- a/react/features/base/ui/components/types.ts +++ b/react/features/base/ui/components/types.ts @@ -7,7 +7,7 @@ export interface ButtonProps { /** * Label used for accessibility. */ - accessibilityLabel: string; + accessibilityLabel?: string; /** * Whether or not the button is disabled. diff --git a/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx b/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx new file mode 100644 index 000000000..e28449757 --- /dev/null +++ b/react/features/recording/components/Recording/AbstractStartRecordingDialogContent.tsx @@ -0,0 +1,360 @@ +/* eslint-disable lines-around-comment */ +import { Component } from 'react'; +// @ts-ignore +import { WithTranslation } from 'react-i18next'; + +import { + createRecordingDialogEvent, + sendAnalytics + // @ts-ignore +} from '../../../analytics'; +// @ts-ignore +import { ColorSchemeRegistry } from '../../../base/color-scheme'; +// @ts-ignore +import { + _abstractMapStateToProps + // @ts-ignore +} from '../../../base/dialog'; +// @ts-ignore +import { StyleType } from '../../../base/styles'; +// @ts-ignore +import { authorizeDropbox, updateDropboxToken } from '../../../dropbox'; +// @ts-ignore +import { isVpaasMeeting } from '../../../jaas/functions'; +// @ts-ignore +import { RECORDING_TYPES } from '../../constants'; +// @ts-ignore +import { supportsLocalRecording } from '../../functions'; + +/** + * The type of the React {@code Component} props of + * {@link AbstractStartRecordingDialogContent}. + */ +export interface Props extends WithTranslation { + + /** + * Style of the dialogs feature. + */ + _dialogStyles: StyleType, + + /** + * Whether to hide the storage warning or not. + */ + _hideStorageWarning: boolean, + + /** + * Whether local recording is available or not. + */ + _localRecordingAvailable: boolean, + + /** + * Whether local recording is enabled or not. + */ + _localRecordingEnabled: boolean, + + /** + * Whether we won't notify the other participants about the recording. + */ + _localRecordingNoNotification: boolean, + + /** + * Whether self local recording is enabled or not. + */ + _localRecordingSelfEnabled: boolean, + + /** + * The color-schemed stylesheet of this component. + */ + _styles: StyleType, + + /** + * The redux dispatch function. + */ + dispatch: Function, + + /** + * Whether to show file recordings service, even if integrations + * are enabled. + */ + fileRecordingsServiceEnabled: boolean, + + /** + * Whether to show the possibility to share file recording with other people (e.g. Meeting participants), based on + * the actual implementation on the backend. + */ + fileRecordingsServiceSharingEnabled: boolean, + + /** + * If true the content related to the integrations will be shown. + */ + integrationsEnabled: boolean, + + /** + * true if we have valid oauth token. + */ + isTokenValid: boolean, + + /** + * true if we are in process of validating the oauth token. + */ + isValidating: boolean, + + /** + * Whether or not the current meeting is a vpaas one. + */ + isVpaas: boolean, + + /** + * Whether or not we should only record the local streams. + */ + localRecordingOnlySelf: boolean, + + /** + * The function will be called when there are changes related to the + * switches. + */ + onChange: Function, + + /** + * Callback to change the local recording only self setting. + */ + onLocalRecordingSelfChange: Function, + + /** + * Callback to be invoked on sharing setting change. + */ + onSharingSettingChanged: Function, + + /** + * The currently selected recording service of type: RECORDING_TYPES. + */ + selectedRecordingService: string | null, + + /** + * Boolean to set file recording sharing on or off. + */ + sharingSetting: boolean, + + /** + * Number of MiB of available space in user's Dropbox account. + */ + spaceLeft: number | null, + + /** + * The display name of the user's Dropbox account. + */ + userName: string | null +} + +/** + * React Component for getting confirmation to start a file recording session. + * + * @augments Component + */ +class AbstractStartRecordingDialogContent

extends Component

{ + /** + * Initializes a new {@code AbstractStartRecordingDialogContent} instance. + * + * @inheritdoc + */ + constructor(props: P) { + super(props); + + // Bind event handler; it bounds once for every instance. + this._onSignIn = this._onSignIn.bind(this); + this._onSignOut = this._onSignOut.bind(this); + this._onDropboxSwitchChange = this._onDropboxSwitchChange.bind(this); + this._onRecordingServiceSwitchChange = this._onRecordingServiceSwitchChange.bind(this); + this._onLocalRecordingSwitchChange = this._onLocalRecordingSwitchChange.bind(this); + } + + /** + * Implements the Component's componentDidMount method. + * + * @inheritdoc + */ + componentDidMount() { + if (!this._shouldRenderNoIntegrationsContent() + && !this._shouldRenderIntegrationsContent() + && !this._shouldRenderFileSharingContent()) { + this._onLocalRecordingSwitchChange(); + } + } + + /** + * Implements {@code Component#componentDidUpdate}. + * + * @inheritdoc + */ + componentDidUpdate(prevProps: P) { + // Auto sign-out when the use chooses another recording service. + if (prevProps.selectedRecordingService === RECORDING_TYPES.DROPBOX + && this.props.selectedRecordingService !== RECORDING_TYPES.DROPBOX && this.props.isTokenValid) { + this._onSignOut(); + } + } + + /** + * Whether the file sharing content should be rendered or not. + * + * @returns {boolean} + */ + _shouldRenderFileSharingContent() { + const { + fileRecordingsServiceEnabled, + fileRecordingsServiceSharingEnabled, + isVpaas, + selectedRecordingService + } = this.props; + + if (!fileRecordingsServiceEnabled + || !fileRecordingsServiceSharingEnabled + || isVpaas + || selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE) { + return false; + } + + return true; + } + + /** + * Whether the no integrations content should be rendered or not. + * + * @returns {boolean} + */ + _shouldRenderNoIntegrationsContent() { + // show the non integrations part only if fileRecordingsServiceEnabled + // is enabled + if (!this.props.fileRecordingsServiceEnabled) { + return false; + } + + return true; + } + + /** + * Whether the integrations content should be rendered or not. + * + * @returns {boolean} + */ + _shouldRenderIntegrationsContent() { + if (!this.props.integrationsEnabled) { + return false; + } + + return true; + } + + /** + * Handler for onValueChange events from the Switch component. + * + * @returns {void} + */ + _onRecordingServiceSwitchChange() { + const { + onChange, + selectedRecordingService + } = this.props; + + // act like group, cannot toggle off + if (selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) { + return; + } + + onChange(RECORDING_TYPES.JITSI_REC_SERVICE); + } + + /** + * Handler for onValueChange events from the Switch component. + * + * @returns {void} + */ + _onDropboxSwitchChange() { + const { + isTokenValid, + onChange, + selectedRecordingService + } = this.props; + + // act like group, cannot toggle off + if (selectedRecordingService === RECORDING_TYPES.DROPBOX) { + return; + } + + onChange(RECORDING_TYPES.DROPBOX); + + if (!isTokenValid) { + this._onSignIn(); + } + } + + /** + * Handler for onValueChange events from the Switch component. + * + * @returns {void} + */ + _onLocalRecordingSwitchChange() { + const { + _localRecordingAvailable, + onChange, + selectedRecordingService + } = this.props; + + if (!_localRecordingAvailable) { + return; + } + + // act like group, cannot toggle off + if (selectedRecordingService + === RECORDING_TYPES.LOCAL) { + return; + } + + onChange(RECORDING_TYPES.LOCAL); + } + + /** + * Sings in a user. + * + * @returns {void} + */ + _onSignIn() { + sendAnalytics(createRecordingDialogEvent('start', 'signIn.button')); + this.props.dispatch(authorizeDropbox()); + } + + /** + * Sings out an user from dropbox. + * + * @returns {void} + */ + _onSignOut() { + sendAnalytics(createRecordingDialogEvent('start', 'signOut.button')); + this.props.dispatch(updateDropboxToken()); + } +} + +/** + * Maps part of the redux state to the props of this component. + * + * @param {Object} state - The Redux state. + * @returns {Props} + */ +export function mapStateToProps(state: any) { + const { localRecording, recordingService } = state['features/base/config']; + const _localRecordingAvailable + = !localRecording?.disable && supportsLocalRecording(); + + return { + ..._abstractMapStateToProps(state), + isVpaas: isVpaasMeeting(state), + _hideStorageWarning: recordingService?.hideStorageWarning, + _localRecordingAvailable, + _localRecordingEnabled: !localRecording?.disable, + _localRecordingSelfEnabled: !localRecording?.disableSelfRecording, + _localRecordingNoNotification: !localRecording?.notifyAllParticipants, + _styles: ColorSchemeRegistry.get(state, 'StartRecordingDialogContent') + }; +} + +export default AbstractStartRecordingDialogContent; diff --git a/react/features/recording/components/Recording/StartRecordingDialogContent.js b/react/features/recording/components/Recording/StartRecordingDialogContent.js deleted file mode 100644 index fc9fe6520..000000000 --- a/react/features/recording/components/Recording/StartRecordingDialogContent.js +++ /dev/null @@ -1,781 +0,0 @@ -import React, { Component } from 'react'; - -import { - createRecordingDialogEvent, - sendAnalytics -} from '../../../analytics'; -import { ColorSchemeRegistry } from '../../../base/color-scheme'; -import { - _abstractMapStateToProps -} from '../../../base/dialog'; -import { translate } from '../../../base/i18n'; -import { - Container, - Image, - LoadingIndicator, - Switch, - Text -} from '../../../base/react'; -import { connect } from '../../../base/redux'; -import { StyleType } from '../../../base/styles'; -import { Button } from '../../../base/ui'; -import { BUTTON_TYPES } from '../../../base/ui/constants'; -import { authorizeDropbox, updateDropboxToken } from '../../../dropbox'; -import { isVpaasMeeting } from '../../../jaas/functions'; -import { RECORDING_TYPES } from '../../constants'; -import { getRecordingDurationEstimation, supportsLocalRecording } from '../../functions'; - -import { - DROPBOX_LOGO, - ICON_CLOUD, - ICON_INFO, - ICON_USERS, - LOCAL_RECORDING, - TRACK_COLOR -} from './styles'; - -type Props = { - - /** - * Style of the dialogs feature. - */ - _dialogStyles: StyleType, - - /** - * Whether to hide the storage warning or not. - */ - _hideStorageWarning: boolean, - - /** - * Whether local recording is enabled or not. - */ - _localRecordingEnabled: boolean, - - /** - * Whether we won't notify the other participants about the recording. - */ - _localRecordingNoNotification: boolean, - - /** - * Whether self local recording is enabled or not. - */ - _localRecordingSelfEnabled: boolean, - - /** - * The color-schemed stylesheet of this component. - */ - _styles: StyleType, - - /** - * The redux dispatch function. - */ - dispatch: Function, - - /** - * Whether to show file recordings service, even if integrations - * are enabled. - */ - fileRecordingsServiceEnabled: boolean, - - /** - * Whether to show the possibility to share file recording with other people (e.g. Meeting participants), based on - * the actual implementation on the backend. - */ - fileRecordingsServiceSharingEnabled: boolean, - - /** - * If true the content related to the integrations will be shown. - */ - integrationsEnabled: boolean, - - /** - * true if we have valid oauth token. - */ - isTokenValid: boolean, - - /** - * true if we are in process of validating the oauth token. - */ - isValidating: boolean, - - /** - * Whether or not the current meeting is a vpaas one. - */ - isVpaas: boolean, - - /** - * Whether or not we should only record the local streams. - */ - localRecordingOnlySelf: boolean, - - /** - * The function will be called when there are changes related to the - * switches. - */ - onChange: Function, - - /** - * Callback to change the local recording only self setting. - */ - onLocalRecordingSelfChange: Function, - - /** - * Callback to be invoked on sharing setting change. - */ - onSharingSettingChanged: Function, - - /** - * The currently selected recording service of type: RECORDING_TYPES. - */ - selectedRecordingService: ?string, - - /** - * Boolean to set file recording sharing on or off. - */ - sharingSetting: boolean, - - /** - * Number of MiB of available space in user's Dropbox account. - */ - spaceLeft: ?number, - - /** - * The translate function. - */ - t: Function, - - /** - * The display name of the user's Dropbox account. - */ - userName: ?string -}; - -/** - * React Component for getting confirmation to start a file recording session. - * - * @augments Component - */ -class StartRecordingDialogContent extends Component { - _localRecordingAvailable: boolean; - - /** - * Initializes a new {@code StartRecordingDialogContent} instance. - * - * @inheritdoc - */ - constructor(props) { - super(props); - - this._localRecordingAvailable = props._localRecordingEnabled && supportsLocalRecording(); - - // Bind event handler so it is only bound once for every instance. - this._onSignIn = this._onSignIn.bind(this); - this._onSignOut = this._onSignOut.bind(this); - this._onDropboxSwitchChange = this._onDropboxSwitchChange.bind(this); - this._onRecordingServiceSwitchChange = this._onRecordingServiceSwitchChange.bind(this); - this._onLocalRecordingSwitchChange = this._onLocalRecordingSwitchChange.bind(this); - } - - /** - * Implements the Component's componentDidMount method. - * - * @inheritdoc - */ - componentDidMount() { - if (!this._shouldRenderNoIntegrationsContent() - && !this._shouldRenderIntegrationsContent() - && !this._shouldRenderFileSharingContent()) { - this._onLocalRecordingSwitchChange(); - } - } - - /** - * Implements {@code Component#componentDidUpdate}. - * - * @inheritdoc - */ - componentDidUpdate(prevProps) { - // Auto sign-out when the use chooses another recording service. - if (prevProps.selectedRecordingService === RECORDING_TYPES.DROPBOX - && this.props.selectedRecordingService !== RECORDING_TYPES.DROPBOX && this.props.isTokenValid) { - this._onSignOut(); - } - } - - /** - * Renders the component. - * - * @protected - * @returns {React$Component} - */ - render() { - const { _styles: styles } = this.props; - - return ( - - { this._renderNoIntegrationsContent() } - { this._renderFileSharingContent() } - { this._renderUploadToTheCloudInfo() } - { this._renderIntegrationsContent() } - { this._renderLocalRecordingContent() } - - ); - } - - /** - * Whether the file sharing content should be rendered or not. - * - * @returns {boolean} - */ - _shouldRenderFileSharingContent() { - const { - fileRecordingsServiceEnabled, - fileRecordingsServiceSharingEnabled, - isVpaas, - selectedRecordingService - } = this.props; - - if (!fileRecordingsServiceEnabled - || !fileRecordingsServiceSharingEnabled - || isVpaas - || selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE) { - return false; - } - - return true; - } - - /** - * Renders the file recording service sharing options, if enabled. - * - * @returns {React$Component} - */ - _renderFileSharingContent() { - if (!this._shouldRenderFileSharingContent()) { - return null; - } - - const { - _dialogStyles, - _styles: styles, - isValidating, - onSharingSettingChanged, - sharingSetting, - t - } = this.props; - - return ( - - - - - - { t('recording.fileSharingdescription') } - - - - ); - } - - /** - * Renders the info in case recording is uploaded to the cloud. - * - * @returns {React$Component} - */ - _renderUploadToTheCloudInfo() { - const { - _dialogStyles, - _hideStorageWarning, - _styles: styles, - isVpaas, - selectedRecordingService, - t - } = this.props; - - if (!(isVpaas && selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) || _hideStorageWarning) { - return null; - } - - return ( - - - - { t('recording.serviceDescriptionCloudInfo') } - - - ); - } - - /** - * Whether the no integrations content should be rendered or not. - * - * @returns {boolean} - */ - _shouldRenderNoIntegrationsContent() { - // show the non integrations part only if fileRecordingsServiceEnabled - // is enabled - if (!this.props.fileRecordingsServiceEnabled) { - return false; - } - - return true; - } - - /** - * Renders the content in case no integrations were enabled. - * - * @returns {React$Component} - */ - _renderNoIntegrationsContent() { - if (!this._shouldRenderNoIntegrationsContent()) { - return null; - } - - const { _dialogStyles, _styles: styles, isValidating, isVpaas, t } = this.props; - - const switchContent - = this.props.integrationsEnabled || this.props._localRecordingEnabled - ? ( - - ) : null; - - const label = isVpaas ? t('recording.serviceDescriptionCloud') : t('recording.serviceDescription'); - const jitsiContentRecordingIconContainer - = this.props.integrationsEnabled || this.props._localRecordingEnabled - ? 'jitsi-content-recording-icon-container-with-switch' - : 'jitsi-content-recording-icon-container-without-switch'; - const contentRecordingClass = isVpaas - ? 'cloud-content-recording-icon-container' - : jitsiContentRecordingIconContainer; - const jitsiRecordingHeaderClass = !isVpaas && 'jitsi-recording-header'; - - return ( - - - - - - { label } - - { switchContent } - - ); - } - - /** - * Whether the integrations content should be rendered or not. - * - * @returns {boolean} - */ - _shouldRenderIntegrationsContent() { - if (!this.props.integrationsEnabled) { - return false; - } - - return true; - } - - /** - * Renders the content in case integrations were enabled. - * - * @protected - * @returns {React$Component} - */ - _renderIntegrationsContent() { - if (!this._shouldRenderIntegrationsContent()) { - return null; - } - - const { _dialogStyles, _styles: styles, isTokenValid, isValidating, t } = this.props; - - let content = null; - let switchContent = null; - - if (isValidating) { - content = this._renderSpinner(); - switchContent = ; - } else if (isTokenValid) { - content = this._renderSignOut(); - switchContent = ( - -