feat(dropbox): For mobile.
This involves redesign of the web recording dialog in order to look the same as the mobile one.
This commit is contained in:
parent
ae7a882188
commit
af37141e3d
|
@ -3,8 +3,23 @@
|
|||
}
|
||||
|
||||
.recording-dialog {
|
||||
flex: 0;
|
||||
flex-direction: column;
|
||||
|
||||
.recording-header {
|
||||
display: flex;
|
||||
flex: 0;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.recording-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.authorization-panel {
|
||||
border-bottom: 2px solid rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 10px;
|
||||
|
@ -32,7 +47,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.logged-in-pannel {
|
||||
.logged-in-panel {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -468,8 +468,7 @@
|
|||
"on": "Recording",
|
||||
"pending": "Preparing to record the meeting...",
|
||||
"rec": "REC",
|
||||
"authDropboxText": "Upload your recording to Dropbox.",
|
||||
"authDropboxCompletedText": "Your recording file will appear in your Dropbox shortly after the recording has finished.",
|
||||
"authDropboxText": "Upload to Dropbox",
|
||||
"serviceName": "Recording service",
|
||||
"signOut": "Sign Out",
|
||||
"signIn": "sign in",
|
||||
|
|
|
@ -1,40 +1,9 @@
|
|||
// @flow
|
||||
|
||||
import { Dropbox } from 'dropbox';
|
||||
|
||||
import {
|
||||
getJitsiMeetGlobalNS,
|
||||
getLocationContextRoot,
|
||||
parseStandardURIString
|
||||
} from '../base/util';
|
||||
import { parseURLParams } from '../base/config';
|
||||
import { getLocationContextRoot } from '../base/util';
|
||||
|
||||
import { UPDATE_DROPBOX_TOKEN } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Executes the oauth flow.
|
||||
*
|
||||
* @param {string} authUrl - The URL to oauth service.
|
||||
* @returns {Promise<string>} - The URL with the authorization details.
|
||||
*/
|
||||
function authorize(authUrl: string): Promise<string> {
|
||||
const windowName = `oauth${Date.now()}`;
|
||||
const gloabalNS = getJitsiMeetGlobalNS();
|
||||
|
||||
gloabalNS.oauthCallbacks = gloabalNS.oauthCallbacks || {};
|
||||
|
||||
return new Promise(resolve => {
|
||||
const popup = window.open(authUrl, windowName);
|
||||
|
||||
gloabalNS.oauthCallbacks[windowName] = () => {
|
||||
const returnURL = popup.location.href;
|
||||
|
||||
popup.close();
|
||||
delete gloabalNS.oauthCallbacks.windowName;
|
||||
resolve(returnURL);
|
||||
};
|
||||
});
|
||||
}
|
||||
import { _authorizeDropbox } from './functions';
|
||||
|
||||
/**
|
||||
* Action to authorize the Jitsi Recording app in dropbox.
|
||||
|
@ -45,18 +14,13 @@ export function authorizeDropbox() {
|
|||
return (dispatch: Function, getState: Function) => {
|
||||
const state = getState();
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { dropbox } = state['features/base/config'];
|
||||
const { dropbox = {} } = state['features/base/config'];
|
||||
const redirectURI = `${locationURL.origin
|
||||
+ getLocationContextRoot(locationURL)}static/oauth.html`;
|
||||
const dropboxAPI = new Dropbox({ clientId: dropbox.clientId });
|
||||
const url = dropboxAPI.getAuthenticationUrl(redirectURI);
|
||||
|
||||
authorize(url).then(returnUrl => {
|
||||
const params
|
||||
= parseURLParams(parseStandardURIString(returnUrl), true) || {};
|
||||
|
||||
dispatch(updateDropboxToken(params.access_token));
|
||||
});
|
||||
_authorizeDropbox(dropbox.clientId, redirectURI)
|
||||
.then(
|
||||
token => dispatch(updateDropboxToken(token)));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// @flow
|
||||
export * from './functions';
|
||||
|
||||
import { getDisplayName, getSpaceUsage } from './functions';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Information related to the user's dropbox account.
|
||||
*/
|
||||
type DropboxUserData = {
|
||||
|
||||
/**
|
||||
* The available space left in MB into the user's Dropbox account.
|
||||
*/
|
||||
spaceLeft: number,
|
||||
|
||||
/**
|
||||
* The display name of the user in Dropbox.
|
||||
*/
|
||||
userName: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches information about the user's dropbox account.
|
||||
*
|
||||
* @param {string} token - The dropbox access token.
|
||||
* @param {string} clientId - The Jitsi Recorder dropbox app ID.
|
||||
* @returns {Promise<DropboxUserData|undefined>}
|
||||
*/
|
||||
export function getDropboxData(
|
||||
token: string,
|
||||
clientId: string
|
||||
): Promise<?DropboxUserData> {
|
||||
return Promise.all(
|
||||
[ getDisplayName(token, clientId), getSpaceUsage(token, clientId) ]
|
||||
).then(([ userName, space ]) => {
|
||||
const { allocated, used } = space;
|
||||
|
||||
return {
|
||||
userName,
|
||||
spaceLeft: Math.floor((allocated - used) / 1048576)// 1MiB=1048576B
|
||||
};
|
||||
|
||||
}, error => {
|
||||
logger.error(error);
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { Dropbox } from 'dropbox';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Fetches information about the user's dropbox account.
|
||||
*
|
||||
* @param {string} token - The dropbox access token.
|
||||
* @param {string} clientId - The Jitsi Recorder dropbox app ID.
|
||||
* @returns {Promise<Object|undefined>}
|
||||
*/
|
||||
export function getDropboxData(
|
||||
token: string,
|
||||
clientId: string
|
||||
): Promise<?Object> {
|
||||
const dropboxAPI = new Dropbox({
|
||||
accessToken: token,
|
||||
clientId
|
||||
});
|
||||
|
||||
return Promise.all(
|
||||
[ dropboxAPI.usersGetCurrentAccount(), dropboxAPI.usersGetSpaceUsage() ]
|
||||
).then(([ account, space ]) => {
|
||||
const { allocation, used } = space;
|
||||
const { allocated } = allocation;
|
||||
|
||||
return {
|
||||
userName: account.name.display_name,
|
||||
spaceLeft: Math.floor((allocated - used) / 1048576)// 1MiB=1048576B
|
||||
};
|
||||
|
||||
}, error => {
|
||||
logger.error(error);
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// @flow
|
||||
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
const { Dropbox } = NativeModules;
|
||||
|
||||
/**
|
||||
* Returns the display name for the current dropbox account.
|
||||
*
|
||||
* @param {string} token - The dropbox access token.
|
||||
* @returns {Promise<string>} - The promise will be resolved with the display
|
||||
* name or rejected with an error.
|
||||
*/
|
||||
export function getDisplayName(token: string) {
|
||||
return Dropbox.getDisplayName(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the space usage for the current dropbox account.
|
||||
*
|
||||
* @param {string} token - The dropbox access token.
|
||||
* @returns {Promise<{ used: number, allocated: number}>} - The promise will be
|
||||
* resolved with the object with information about the space usage (the used
|
||||
* space and the allocated space) for the current dropbox account or rejected
|
||||
* with an error.
|
||||
*/
|
||||
export function getSpaceUsage(token: string) {
|
||||
return Dropbox.getSpaceUsage(token);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action to authorize the Jitsi Recording app in dropbox.
|
||||
*
|
||||
* @param {string} clientId - The Jitsi Recorder dropbox app ID.
|
||||
* @param {string} redirectURI - The return URL.
|
||||
* @returns {Promise<string>} - The promise will be resolved with the dropbox
|
||||
* access token or rejected with an error.
|
||||
*/
|
||||
export function _authorizeDropbox(): Promise<string> {
|
||||
return Dropbox.authorize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <tt>true</tt> if the dropbox features is enabled and <tt>false</tt>
|
||||
* otherwise.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isEnabled() {
|
||||
return Dropbox.ENABLED;
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
// @flow
|
||||
|
||||
import { Dropbox } from 'dropbox';
|
||||
|
||||
import {
|
||||
getJitsiMeetGlobalNS,
|
||||
parseStandardURIString
|
||||
} from '../base/util';
|
||||
import { parseURLParams } from '../base/config';
|
||||
|
||||
/**
|
||||
* Returns the display name for the current dropbox account.
|
||||
*
|
||||
* @param {string} token - The dropbox access token.
|
||||
* @param {string} clientId - The Jitsi Recorder dropbox app ID.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function getDisplayName(token: string, clientId: string) {
|
||||
const dropboxAPI = new Dropbox({
|
||||
accessToken: token,
|
||||
clientId
|
||||
});
|
||||
|
||||
return (
|
||||
dropboxAPI.usersGetCurrentAccount()
|
||||
.then(account => account.name.display_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the space usage for the current dropbox account.
|
||||
*
|
||||
* @param {string} token - The dropbox access token.
|
||||
* @param {string} clientId - The Jitsi Recorder dropbox app ID.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export function getSpaceUsage(token: string, clientId: string) {
|
||||
const dropboxAPI = new Dropbox({
|
||||
accessToken: token,
|
||||
clientId
|
||||
});
|
||||
|
||||
return dropboxAPI.usersGetSpaceUsage().then(space => {
|
||||
const { allocation, used } = space;
|
||||
const { allocated } = allocation;
|
||||
|
||||
return {
|
||||
used,
|
||||
allocated
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the oauth flow.
|
||||
*
|
||||
* @param {string} authUrl - The URL to oauth service.
|
||||
* @returns {Promise<string>} - The URL with the authorization details.
|
||||
*/
|
||||
function authorize(authUrl: string): Promise<string> {
|
||||
const windowName = `oauth${Date.now()}`;
|
||||
const gloabalNS = getJitsiMeetGlobalNS();
|
||||
|
||||
gloabalNS.oauthCallbacks = gloabalNS.oauthCallbacks || {};
|
||||
|
||||
return new Promise(resolve => {
|
||||
const popup = window.open(authUrl, windowName);
|
||||
|
||||
gloabalNS.oauthCallbacks[windowName] = () => {
|
||||
const returnURL = popup.location.href;
|
||||
|
||||
popup.close();
|
||||
delete gloabalNS.oauthCallbacks.windowName;
|
||||
resolve(returnURL);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to authorize the Jitsi Recording app in dropbox.
|
||||
*
|
||||
* @param {string} clientId - The Jitsi Recorder dropbox app ID.
|
||||
* @param {string} redirectURI - The return URL.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function _authorizeDropbox(
|
||||
clientId: string,
|
||||
redirectURI: string
|
||||
): Promise<string> {
|
||||
const dropboxAPI = new Dropbox({ clientId });
|
||||
const url = dropboxAPI.getAuthenticationUrl(redirectURI);
|
||||
|
||||
return authorize(url).then(returnUrl => {
|
||||
const params
|
||||
= parseURLParams(parseStandardURIString(returnUrl), true) || {};
|
||||
|
||||
return params.access_token;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <tt>true</tt> if the dropbox features is enabled and <tt>false</tt>
|
||||
* otherwise.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isEnabled(state: Object) {
|
||||
const { dropbox = {} } = state['features/base/config'];
|
||||
|
||||
return typeof dropbox.clientId === 'string';
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export * from './actions';
|
||||
export * from './functions';
|
||||
export * from './functions.any';
|
||||
|
||||
import './reducer';
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
getLocalParticipant,
|
||||
isLocalParticipantModerator
|
||||
} from '../../../base/participants';
|
||||
import { isEnabled as isDropboxEnabled } from '../../../dropbox';
|
||||
import {
|
||||
AbstractButton,
|
||||
type AbstractButtonProps
|
||||
|
@ -123,7 +124,6 @@ export function _mapStateToProps(state: Object, ownProps: Props): Object {
|
|||
// its own to be visible or not.
|
||||
const isModerator = isLocalParticipantModerator(state);
|
||||
const {
|
||||
dropbox = {},
|
||||
enableFeaturesBasedOnToken,
|
||||
fileRecordingsEnabled
|
||||
} = state['features/base/config'];
|
||||
|
@ -131,7 +131,7 @@ export function _mapStateToProps(state: Object, ownProps: Props): Object {
|
|||
|
||||
visible = isModerator
|
||||
&& fileRecordingsEnabled
|
||||
&& typeof dropbox.clientId === 'string';
|
||||
&& isDropboxEnabled(state);
|
||||
|
||||
if (enableFeaturesBasedOnToken) {
|
||||
visible = visible && String(features.recording) === 'true';
|
||||
|
|
|
@ -222,8 +222,10 @@ class StartRecordingDialog extends Component<Props, State> {
|
|||
* }}
|
||||
*/
|
||||
function mapStateToProps(state: Object) {
|
||||
const { dropbox = {} } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_clientId: state['features/base/config'].dropbox.clientId,
|
||||
_clientId: dropbox.clientId,
|
||||
_conference: state['features/base/conference'].conference,
|
||||
_token: state['features/dropbox'].token
|
||||
};
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
createRecordingDialogEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import {
|
||||
Container,
|
||||
LoadingIndicator,
|
||||
Switch,
|
||||
Text
|
||||
} from '../../../base/react';
|
||||
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox';
|
||||
|
||||
import styles from './styles';
|
||||
import { getRecordingDurationEstimation } from '../../functions';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* <tt>true</tt> if we have valid oauth token.
|
||||
*/
|
||||
isTokenValid: boolean,
|
||||
|
||||
/**
|
||||
* <tt>true</tt> if we are in process of validating the oauth token.
|
||||
*/
|
||||
isValidating: 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.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class StartRecordingDialogContent extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code StartRecordingDialogContent} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._signIn = this._signIn.bind(this);
|
||||
this._signOut = this._signOut.bind(this);
|
||||
this._onSwitchChange = this._onSwitchChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the component.
|
||||
*
|
||||
* @protected
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
render() {
|
||||
const { isTokenValid, isValidating, t } = this.props;
|
||||
|
||||
let content = null;
|
||||
|
||||
if (isValidating) {
|
||||
content = this._renderSpinner();
|
||||
} else if (isTokenValid) {
|
||||
content = this._renderSignOut();
|
||||
}
|
||||
|
||||
// else { // Sign in screen:
|
||||
// We don't need to render any additional information.
|
||||
// }
|
||||
|
||||
return (
|
||||
<Container
|
||||
className = 'recording-dialog'
|
||||
style = { styles.container }>
|
||||
<Container
|
||||
className = 'recording-header'
|
||||
style = { styles.header }>
|
||||
<Text
|
||||
className = 'recording-title'
|
||||
style = { styles.title }>
|
||||
{ t('recording.authDropboxText') }
|
||||
</Text>
|
||||
<Switch
|
||||
disabled = { isValidating }
|
||||
onValueChange = { this._onSwitchChange }
|
||||
style = { styles.switch }
|
||||
value = { isTokenValid } />
|
||||
</Container>
|
||||
<Container
|
||||
className = 'authorization-panel'>
|
||||
{ content }
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
_onSwitchChange: boolean => void;
|
||||
|
||||
/**
|
||||
* Handler for onValueChange events from the Switch component.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSwitchChange() {
|
||||
if (this.props.isTokenValid) {
|
||||
this._signOut();
|
||||
} else {
|
||||
this._signIn();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a spinner component.
|
||||
*
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderSpinner() {
|
||||
return (
|
||||
<LoadingIndicator
|
||||
isCompleting = { false }
|
||||
size = 'medium' />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the screen with the account information of a logged in user.
|
||||
*
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderSignOut() {
|
||||
const { spaceLeft, t, userName } = this.props;
|
||||
const duration = getRecordingDurationEstimation(spaceLeft);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Container
|
||||
className = 'logged-in-panel'
|
||||
style = { styles.loggedIn }>
|
||||
<Container>
|
||||
<Text>
|
||||
{ t('recording.loggedIn', { userName }) }
|
||||
</Text>
|
||||
</Container>
|
||||
<Container>
|
||||
<Text>
|
||||
{
|
||||
t('recording.availableSpace', {
|
||||
spaceLeft,
|
||||
duration
|
||||
})
|
||||
}
|
||||
</Text>
|
||||
</Container>
|
||||
</Container>
|
||||
<Container style = { styles.startRecordingText }>
|
||||
<Text>{ t('recording.startRecordingBody') }</Text>
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
_signIn: () => {};
|
||||
|
||||
/**
|
||||
* Sings in a user.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_signIn() {
|
||||
sendAnalytics(
|
||||
createRecordingDialogEvent('start', 'signIn.button')
|
||||
);
|
||||
this.props.dispatch(authorizeDropbox());
|
||||
}
|
||||
|
||||
_signOut: () => {};
|
||||
|
||||
/**
|
||||
* Sings out an user from dropbox.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_signOut() {
|
||||
sendAnalytics(
|
||||
createRecordingDialogEvent('start', 'signOut.button')
|
||||
);
|
||||
this.props.dispatch(updateDropboxToken());
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(StartRecordingDialogContent));
|
|
@ -1,38 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { DialogContent } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* React Component for getting confirmation to start a file recording session.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class StartRecordingDialogContent extends Component<Props> {
|
||||
/**
|
||||
* Renders the platform specific dialog content.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
{ t('recording.startRecordingBody') }
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(StartRecordingDialogContent);
|
|
@ -1,194 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import Spinner from '@atlaskit/spinner';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
createRecordingDialogEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* <tt>true</tt> if we have valid oauth token.
|
||||
*/
|
||||
isTokenValid: boolean,
|
||||
|
||||
/**
|
||||
* <tt>true</tt> if we are in process of validating the oauth token.
|
||||
*/
|
||||
isValidating: 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.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class StartRecordingDialogContent extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code StartRecordingDialogContent} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onSignInClick = this._onSignInClick.bind(this);
|
||||
this._onSignOutClick = this._onSignOutClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog content.
|
||||
*
|
||||
* @protected
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
render() {
|
||||
const { isTokenValid, isValidating, t } = this.props;
|
||||
|
||||
let content = null;
|
||||
|
||||
if (isValidating) {
|
||||
content = this._renderSpinner();
|
||||
} else if (isTokenValid) {
|
||||
content = this._renderSignOut();
|
||||
} else {
|
||||
content = this._renderSignIn();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = 'recording-dialog'>
|
||||
<div className = 'authorization-panel'>
|
||||
{ content }
|
||||
</div>
|
||||
<div>{ t('recording.startRecordingBody') }</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a spinner component.
|
||||
*
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderSpinner() {
|
||||
return (
|
||||
<Spinner
|
||||
isCompleting = { false }
|
||||
size = 'medium' />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the sign in screen.
|
||||
*
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderSignIn() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{ t('recording.authDropboxText') }</div>
|
||||
<div
|
||||
className = 'dropbox-sign-in'
|
||||
onClick = { this._onSignInClick }>
|
||||
<img
|
||||
className = 'dropbox-logo'
|
||||
src = 'images/dropboxLogo.svg' />
|
||||
<span>{ t('recording.signIn') }</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the screen with the account information of a logged in user.
|
||||
*
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderSignOut() {
|
||||
const { spaceLeft, t, userName } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{ t('recording.authDropboxCompletedText') }</div>
|
||||
<div className = 'logged-in-panel'>
|
||||
<div>
|
||||
{ t('recording.loggedIn', { userName }) } (
|
||||
<a onClick = { this._onSignOutClick }>
|
||||
{ t('recording.signOut') }
|
||||
</a>
|
||||
)
|
||||
</div>
|
||||
<div>
|
||||
{
|
||||
t('recording.availableSpace', {
|
||||
spaceLeft,
|
||||
|
||||
// assuming 1min -> 10MB recording:
|
||||
duration: Math.floor((spaceLeft || 0) / 10)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_onSignInClick: () => {};
|
||||
|
||||
/**
|
||||
* Handles click events for the dropbox sign in button.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSignInClick() {
|
||||
sendAnalytics(
|
||||
createRecordingDialogEvent('start', 'signIn.button')
|
||||
);
|
||||
this.props.dispatch(authorizeDropbox());
|
||||
}
|
||||
|
||||
_onSignOutClick: () => {};
|
||||
|
||||
/**
|
||||
* Sings out an user from dropbox.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSignOutClick() {
|
||||
sendAnalytics(
|
||||
createRecordingDialogEvent('start', 'signOut.button')
|
||||
);
|
||||
this.props.dispatch(updateDropboxToken());
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(StartRecordingDialogContent));
|
|
@ -0,0 +1,43 @@
|
|||
// @flow
|
||||
|
||||
import { BoxModel, createStyleSheet } from '../../../base/styles';
|
||||
|
||||
// XXX The "standard" {@code BoxModel.padding} has been deemed insufficient in
|
||||
// the special case(s) of the recording feature bellow.
|
||||
const _PADDING = BoxModel.padding * 1.5;
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Components} of the feature recording.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
container: {
|
||||
flex: 0,
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
||||
header: {
|
||||
alignItems: 'center',
|
||||
flex: 0,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
paddingBottom: _PADDING,
|
||||
paddingTop: _PADDING
|
||||
},
|
||||
|
||||
loggedIn: {
|
||||
paddingBottom: _PADDING
|
||||
},
|
||||
|
||||
startRecordingText: {
|
||||
paddingBottom: _PADDING
|
||||
},
|
||||
|
||||
switch: {
|
||||
paddingRight: BoxModel.padding
|
||||
},
|
||||
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
// XXX CSS is used on Web, JavaScript styles are use only for mobile. Export an
|
||||
// (empty) object so that styles[*] statements on Web don't trigger errors.
|
||||
export default {};
|
|
@ -19,6 +19,18 @@ export function getActiveSession(state: Object, mode: string) {
|
|||
|| sessionData.status === statusConstants.PENDING));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an estimated recording duration based on the size of the video file
|
||||
* in MB. The estimate is calculated under the assumption that 1 min of recorded
|
||||
* video needs 10MB of storage on avarage.
|
||||
*
|
||||
* @param {number} size - The size in MB of the recorded video.
|
||||
* @returns {number} - The estimated duration in minutes.
|
||||
*/
|
||||
export function getRecordingDurationEstimation(size: ?number) {
|
||||
return Math.floor((size || 0) / 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches in the passed in redux state for a recording session that matches
|
||||
* the passed in recording session ID.
|
||||
|
|
Loading…
Reference in New Issue