浏览代码

Updates recording dialog. (#3953)

* Updates recording dialog.

* Update config.js doc.

* Adds comment and make a check more intuitive.

* Changes of using enum for recording types.
j8
Дамян Минков 6 年前
父节点
当前提交
12d0aef686

+ 5
- 0
config.js 查看文件

@@ -180,6 +180,11 @@ var config = {
180 180
     //     redirectURI:
181 181
     //          'https://jitsi-meet.example.com/subfolder/static/oauth.html'
182 182
     // },
183
+    // When integrations like dropbox are enabled only that will be shown,
184
+    // by enabling fileRecordingsServiceEnabled, we show both the integrations
185
+    // and the generic recording service (its configuration and storage type
186
+    // depends on jibri configuration)
187
+    // fileRecordingsServiceEnabled: false
183 188
 
184 189
     // Whether to enable live streaming or not.
185 190
     // liveStreamingEnabled: false,

+ 20
- 25
css/_recording.scss 查看文件

@@ -11,42 +11,37 @@
11 11
         flex: 0;
12 12
         flex-direction: row;
13 13
         justify-content: space-between;
14
-        align-items: center;
14
+        margin-top: 32px;
15 15
 
16 16
         .recording-title {
17
+            display: inline-flex;
18
+            align-items: center;
17 19
             font-size: 16px;
18
-            font-weight: bold;
20
+            margin-left: 16px;
19 21
         }
20 22
     }
21 23
 
24
+    .recording-icon-container {
25
+        display: inline-flex;
26
+        align-items: center;
27
+    }
28
+
29
+    .recording-icon {
30
+        width: 32px;
31
+        height: 32px;
32
+        object-fit: contain;
33
+    }
34
+
35
+    .recording-switch {
36
+        margin-left: auto;
37
+    }
38
+
22 39
     .authorization-panel {
23 40
         display: flex;
24 41
         flex-direction: column;
25
-        margin-bottom: 10px;
42
+        margin: 0 40px 10px 40px;
26 43
         padding-bottom: 10px;
27 44
 
28
-        .dropbox-sign-in {
29
-            align-items: center;
30
-            border: 1px solid #4285f4;
31
-            background-color: white;
32
-            border-radius: 2px;
33
-            cursor: pointer;
34
-            display: inline-flex;
35
-            padding: 10px;
36
-            font-size: 18px;
37
-            font-weight: 600;
38
-            margin: 10px 0px;
39
-            color: #4285f4;
40
-
41
-            .dropbox-logo {
42
-                background-color: white;
43
-                border-radius: 2px;
44
-                display: inline-block;
45
-                padding-right: 5px;
46
-                height: 18px;
47
-            }
48
-        }
49
-
50 45
         .logged-in-panel {
51 46
             padding: 10px;
52 47
         }

二进制
images/dropboxLogo_square.png 查看文件


二进制
images/jitsiLogo_square.png 查看文件


+ 3
- 2
lang/main.json 查看文件

@@ -518,9 +518,10 @@
518 518
         "on": "Recording",
519 519
         "pending": "Preparing to record the meeting...",
520 520
         "rec": "REC",
521
+        "serviceDescription": "Your recording will be saved by the recording service",
521 522
         "serviceName": "Recording service",
522
-        "signIn": "sign in",
523
-        "signOut": "Sign Out",
523
+        "signIn": "Sign in",
524
+        "signOut": "Sign out",
524 525
         "startRecordingBody": "Are you sure you would like to start recording?",
525 526
         "unavailable": "Oops! The __serviceName__ is currently unavailable. We're working on resolving the issue. Please try again later.",
526 527
         "unavailableTitle": "Recording unavailable"

+ 23
- 1
react/features/analytics/AnalyticsEvents.js 查看文件

@@ -338,18 +338,40 @@ export function createProfilePanelButtonEvent(buttonName, attributes = {}) {
338 338
  * @param {string} dialogName - The name of the dialog (e.g. 'start' or 'stop').
339 339
  * @param {string} buttonName - The name of the button (e.g. 'confirm' or
340 340
  * 'cancel').
341
+ * @param {Object} attributes - Attributes to attach to the event.
341 342
  * @returns {Object} The event in a format suitable for sending via
342 343
  * sendAnalytics.
343 344
  */
344
-export function createRecordingDialogEvent(dialogName, buttonName) {
345
+export function createRecordingDialogEvent(
346
+        dialogName, buttonName, attributes = {}) {
345 347
     return {
346 348
         action: 'clicked',
347 349
         actionSubject: buttonName,
350
+        attributes,
348 351
         source: `${dialogName}.recording.dialog`,
349 352
         type: TYPE_UI
350 353
     };
351 354
 }
352 355
 
356
+/**
357
+ * Creates an event which indicates that a specific button on one of the
358
+ * liveStreaming-related dialogs was clicked.
359
+ *
360
+ * @param {string} dialogName - The name of the dialog (e.g. 'start' or 'stop').
361
+ * @param {string} buttonName - The name of the button (e.g. 'confirm' or
362
+ * 'cancel').
363
+ * @returns {Object} The event in a format suitable for sending via
364
+ * sendAnalytics.
365
+ */
366
+export function createLiveStreamingDialogEvent(dialogName, buttonName) {
367
+    return {
368
+        action: 'clicked',
369
+        actionSubject: buttonName,
370
+        source: `${dialogName}.liveStreaming.dialog`,
371
+        type: TYPE_UI
372
+    };
373
+}
374
+
353 375
 /**
354 376
  * Creates an event which indicates that an action related to recording has
355 377
  * occured.

+ 44
- 0
react/features/base/react/components/native/Button.js 查看文件

@@ -0,0 +1,44 @@
1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+import { Text, TouchableOpacity } from 'react-native';
5
+
6
+type Props = {
7
+
8
+    /**
9
+     * React Elements to display within the component.
10
+     */
11
+    children: React$Node | Object,
12
+
13
+    /**
14
+     * Handler called when the user presses the button.
15
+     */
16
+    onValueChange: Function,
17
+
18
+    /**
19
+     * The component's external style
20
+     */
21
+    style: Object
22
+};
23
+
24
+/**
25
+ * Renders a button.
26
+ */
27
+export default class ButtonImpl extends Component<Props> {
28
+    /**
29
+     * Implements React's {@link Component#render()}, renders the button.
30
+     *
31
+     * @inheritdoc
32
+     * @returns {ReactElement}
33
+     */
34
+    render() {
35
+        return (
36
+            <TouchableOpacity
37
+                onPress = { this.props.onValueChange } >
38
+                <Text style = { this.props.style }>
39
+                    { this.props.children }
40
+                </Text>
41
+            </TouchableOpacity>
42
+        );
43
+    }
44
+}

+ 41
- 0
react/features/base/react/components/native/Image.js 查看文件

@@ -0,0 +1,41 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { Image } from 'react-native';
5
+
6
+/**
7
+ * The type of the React {@code Component} props of {@link Image}.
8
+ */
9
+type Props = {
10
+
11
+    /**
12
+     * The URL to be rendered as image.
13
+     */
14
+    src: string,
15
+
16
+    /**
17
+     * The component's external style
18
+     */
19
+    style: Object
20
+};
21
+
22
+/**
23
+ * A component rendering aN IMAGE.
24
+ *
25
+ * @extends Component
26
+ */
27
+export default class ImageImpl extends Component<Props> {
28
+    /**
29
+     * Implements React's {@link Component#render()}.
30
+     *
31
+     * @inheritdoc
32
+     * @returns {ReactElement}
33
+     */
34
+    render() {
35
+        return (
36
+            <Image
37
+                source = { this.props.src }
38
+                style = { this.props.style } />
39
+        );
40
+    }
41
+}

+ 2
- 0
react/features/base/react/components/native/index.js 查看文件

@@ -2,10 +2,12 @@
2 2
 
3 3
 export { default as AvatarListItem } from './AvatarListItem';
4 4
 export { default as BackButton } from './BackButton';
5
+export { default as Button } from './Button';
5 6
 export { default as Container } from './Container';
6 7
 export { default as ForwardButton } from './ForwardButton';
7 8
 export { default as Header } from './Header';
8 9
 export { default as HeaderLabel } from './HeaderLabel';
10
+export { default as Image } from './Image';
9 11
 export { default as Link } from './Link';
10 12
 export { default as LoadingIndicator } from './LoadingIndicator';
11 13
 export { default as Modal } from './Modal';

+ 41
- 0
react/features/base/react/components/web/Button.js 查看文件

@@ -0,0 +1,41 @@
1
+/* @flow */
2
+
3
+import Button from '@atlaskit/button';
4
+import React, { Component } from 'react';
5
+
6
+type Props = {
7
+
8
+    /**
9
+     * React Elements to display within the component.
10
+     */
11
+    children: React$Node | Object,
12
+
13
+    /**
14
+     * Handler called when the user presses the button.
15
+     */
16
+    onValueChange: Function
17
+};
18
+
19
+/**
20
+ * Renders a button.
21
+ */
22
+export default class ButtonImpl extends Component<Props> {
23
+    /**
24
+     * Implements React's {@link Component#render()}.
25
+     *
26
+     * @inheritdoc
27
+     * @returns {ReactElement}
28
+     */
29
+    render() {
30
+        const { onValueChange } = this.props;
31
+
32
+        return (
33
+            <Button
34
+                appearance = 'primary'
35
+                onClick = { onValueChange }
36
+                type = 'button'>
37
+                { this.props.children }
38
+            </Button>
39
+        );
40
+    }
41
+}

+ 19
- 0
react/features/base/react/components/web/Image.js 查看文件

@@ -0,0 +1,19 @@
1
+import React, { Component } from 'react';
2
+
3
+/**
4
+ * Implements a React/Web {@link Component} for displaying image
5
+ * in order to facilitate cross-platform source code.
6
+ *
7
+ * @extends Component
8
+ */
9
+export default class Image extends Component {
10
+    /**
11
+     * Implements React's {@link Component#render()}.
12
+     *
13
+     * @inheritdoc
14
+     * @returns {ReactElement}
15
+     */
16
+    render() {
17
+        return React.createElement('img', this.props);
18
+    }
19
+}

+ 2
- 0
react/features/base/react/components/web/index.js 查看文件

@@ -1,4 +1,6 @@
1
+export { default as Button } from './Button';
1 2
 export { default as Container } from './Container';
3
+export { default as Image } from './Image';
2 4
 export { default as LoadingIndicator } from './LoadingIndicator';
3 5
 export { default as MeetingsList } from './MeetingsList';
4 6
 export { default as MultiSelectAutocomplete } from './MultiSelectAutocomplete';

+ 5
- 2
react/features/dropbox/functions.native.js 查看文件

@@ -44,8 +44,11 @@ export function getSpaceUsage(token: string) {
44 44
  * Returns <tt>true</tt> if the dropbox features is enabled and <tt>false</tt>
45 45
  * otherwise.
46 46
  *
47
+ * @param {Object} state - The redux state.
47 48
  * @returns {boolean}
48 49
  */
49
-export function isEnabled() {
50
-    return Dropbox.ENABLED;
50
+export function isEnabled(state: Object) {
51
+    const { dropbox = {} } = state['features/base/config'];
52
+
53
+    return Dropbox.ENABLED && typeof dropbox.appKey === 'string';
51 54
 }

+ 3
- 3
react/features/recording/components/LiveStream/AbstractStartLiveStreamDialog.js 查看文件

@@ -3,7 +3,7 @@
3 3
 import { Component } from 'react';
4 4
 
5 5
 import {
6
-    createRecordingDialogEvent,
6
+    createLiveStreamingDialogEvent,
7 7
     sendAnalytics
8 8
 } from '../../../analytics';
9 9
 import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
@@ -149,7 +149,7 @@ export default class AbstractStartLiveStreamDialog<P: Props>
149 149
      * @returns {boolean} True is returned to close the modal.
150 150
      */
151 151
     _onCancel() {
152
-        sendAnalytics(createRecordingDialogEvent('start', 'cancel.button'));
152
+        sendAnalytics(createLiveStreamingDialogEvent('start', 'cancel.button'));
153 153
 
154 154
         return true;
155 155
     }
@@ -211,7 +211,7 @@ export default class AbstractStartLiveStreamDialog<P: Props>
211 211
         }
212 212
 
213 213
         sendAnalytics(
214
-            createRecordingDialogEvent('start', 'confirm.button'));
214
+            createLiveStreamingDialogEvent('start', 'confirm.button'));
215 215
 
216 216
         this.props._conference.startRecording({
217 217
             broadcastId: selectedBroadcastID,

+ 2
- 2
react/features/recording/components/LiveStream/AbstractStopLiveStreamDialog.js 查看文件

@@ -3,7 +3,7 @@
3 3
 import { Component } from 'react';
4 4
 
5 5
 import {
6
-    createRecordingDialogEvent,
6
+    createLiveStreamingDialogEvent,
7 7
     sendAnalytics
8 8
 } from '../../../analytics';
9 9
 import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
@@ -61,7 +61,7 @@ export default class AbstractStopLiveStreamDialog extends Component<Props> {
61 61
      * @returns {boolean} True to close the modal.
62 62
      */
63 63
     _onSubmit() {
64
-        sendAnalytics(createRecordingDialogEvent('stop', 'confirm.button'));
64
+        sendAnalytics(createLiveStreamingDialogEvent('stop', 'confirm.button'));
65 65
 
66 66
         const { _session } = this.props;
67 67
 

+ 22
- 6
react/features/recording/components/Recording/AbstractStartRecordingDialog.js 查看文件

@@ -11,6 +11,7 @@ import {
11 11
     getDropboxData,
12 12
     isEnabled as isDropboxEnabled
13 13
 } from '../../../dropbox';
14
+import { RECORDING_TYPES } from '../../constants';
14 15
 
15 16
 type Props = {
16 17
 
@@ -24,6 +25,12 @@ type Props = {
24 25
      */
25 26
     _appKey: string,
26 27
 
28
+    /**
29
+     * Whether to show file recordings service, even if integrations
30
+     * are enabled.
31
+     */
32
+    _fileRecordingsServiceEnabled: boolean,
33
+
27 34
     /**
28 35
      * If true the dropbox integration is enabled, otherwise - disabled.
29 36
      */
@@ -165,23 +172,28 @@ class AbstractStartRecordingDialog extends Component<Props, State> {
165 172
      * @returns {boolean} - True (to note that the modal should be closed).
166 173
      */
167 174
     _onSubmit() {
168
-        sendAnalytics(
169
-            createRecordingDialogEvent('start', 'confirm.button')
170
-        );
171 175
         const { _conference, _isDropboxEnabled, _token } = this.props;
172 176
         let appData;
177
+        const attributes = {};
173 178
 
174
-        if (_isDropboxEnabled) {
179
+        if (_isDropboxEnabled && _token) {
175 180
             appData = JSON.stringify({
176 181
                 'file_recording_metadata': {
177 182
                     'upload_credentials': {
178
-                        'service_name': 'dropbox',
183
+                        'service_name': RECORDING_TYPES.DROPBOX,
179 184
                         'token': _token
180 185
                     }
181 186
                 }
182 187
             });
188
+            attributes.type = RECORDING_TYPES.DROPBOX;
189
+        } else {
190
+            attributes.type = RECORDING_TYPES.JITSI_REC_SERVICE;
183 191
         }
184 192
 
193
+        sendAnalytics(
194
+            createRecordingDialogEvent('start', 'confirm.button', attributes)
195
+        );
196
+
185 197
         _conference.startRecording({
186 198
             mode: JitsiRecordingConstants.mode.FILE,
187 199
             appData
@@ -212,11 +224,15 @@ class AbstractStartRecordingDialog extends Component<Props, State> {
212 224
  * }}
213 225
  */
214 226
 export function mapStateToProps(state: Object) {
215
-    const { dropbox = {} } = state['features/base/config'];
227
+    const {
228
+        fileRecordingsServiceEnabled = false,
229
+        dropbox = {}
230
+    } = state['features/base/config'];
216 231
 
217 232
     return {
218 233
         _appKey: dropbox.appKey,
219 234
         _conference: state['features/base/conference'].conference,
235
+        _fileRecordingsServiceEnabled: fileRecordingsServiceEnabled,
220 236
         _isDropboxEnabled: isDropboxEnabled(state),
221 237
         _token: state['features/dropbox'].token
222 238
     };

+ 180
- 40
react/features/recording/components/Recording/StartRecordingDialogContent.js 查看文件

@@ -8,20 +8,27 @@ import {
8 8
     sendAnalytics
9 9
 } from '../../../analytics';
10 10
 import {
11
-    DialogContent,
12 11
     _abstractMapStateToProps
13 12
 } from '../../../base/dialog';
14 13
 import { translate } from '../../../base/i18n';
15 14
 import {
15
+    Button,
16 16
     Container,
17
+    Image,
17 18
     LoadingIndicator,
18 19
     Switch,
19 20
     Text
20 21
 } from '../../../base/react';
21
-import { StyleType } from '../../../base/styles';
22
+import { ColorPalette, StyleType } from '../../../base/styles';
22 23
 import { authorizeDropbox, updateDropboxToken } from '../../../dropbox';
23 24
 
24
-import styles from './styles';
25
+import {
26
+    default as styles,
27
+    DROPBOX_LOGO,
28
+    JITSI_LOGO
29
+} from './styles';
30
+
31
+import { RECORDING_TYPES } from '../../constants';
25 32
 import { getRecordingDurationEstimation } from '../../functions';
26 33
 
27 34
 type Props = {
@@ -36,6 +43,12 @@ type Props = {
36 43
      */
37 44
     dispatch: Function,
38 45
 
46
+    /**
47
+     * Whether to show file recordings service, even if integrations
48
+     * are enabled.
49
+     */
50
+    fileRecordingsServiceEnabled: boolean,
51
+
39 52
     /**
40 53
      * If true the content related to the integrations will be shown.
41 54
      */
@@ -67,12 +80,24 @@ type Props = {
67 80
     userName: ?string,
68 81
 };
69 82
 
83
+/**
84
+ * State of the component.
85
+ */
86
+type State = {
87
+
88
+    /**
89
+     * The currently selected recording service of type: RECORDING_TYPES.
90
+     */
91
+    selectedRecordingService: string
92
+};
93
+
94
+
70 95
 /**
71 96
  * React Component for getting confirmation to start a file recording session.
72 97
  *
73 98
  * @extends Component
74 99
  */
75
-class StartRecordingDialogContent extends Component<Props> {
100
+class StartRecordingDialogContent extends Component<Props, State> {
76 101
     /**
77 102
      * Initializes a new {@code StartRecordingDialogContent} instance.
78 103
      *
@@ -82,9 +107,18 @@ class StartRecordingDialogContent extends Component<Props> {
82 107
         super(props);
83 108
 
84 109
         // Bind event handler so it is only bound once for every instance.
85
-        this._signIn = this._signIn.bind(this);
86
-        this._signOut = this._signOut.bind(this);
87
-        this._onSwitchChange = this._onSwitchChange.bind(this);
110
+        this._onSignIn = this._onSignIn.bind(this);
111
+        this._onSignOut = this._onSignOut.bind(this);
112
+        this._onDropboxSwitchChange
113
+            = this._onDropboxSwitchChange.bind(this);
114
+        this._onRecordingServiceSwitchChange
115
+            = this._onRecordingServiceSwitchChange.bind(this);
116
+
117
+        // the initial state is jitsi rec service is always selected
118
+        // if only one type of recording is enabled this state will be ignored
119
+        this.state = {
120
+            selectedRecordingService: RECORDING_TYPES.JITSI_REC_SERVICE
121
+        };
88 122
     }
89 123
 
90 124
     /**
@@ -94,11 +128,14 @@ class StartRecordingDialogContent extends Component<Props> {
94 128
      * @returns {React$Component}
95 129
      */
96 130
     render() {
97
-        if (this.props.integrationsEnabled === true) { // explicit true needed
98
-            return this._renderIntegrationsContent();
99
-        }
100
-
101
-        return this._renderNoIntegrationsContent();
131
+        return (
132
+            <Container
133
+                className = 'recording-dialog'
134
+                style = { styles.container }>
135
+                { this._renderNoIntegrationsContent() }
136
+                { this._renderIntegrationsContent() }
137
+            </Container>
138
+        );
102 139
     }
103 140
 
104 141
     /**
@@ -107,10 +144,51 @@ class StartRecordingDialogContent extends Component<Props> {
107 144
      * @returns {React$Component}
108 145
      */
109 146
     _renderNoIntegrationsContent() {
147
+
148
+        // show the non integrations part only if fileRecordingsServiceEnabled
149
+        // is enabled or when there are no integrations enabled
150
+        if (!(this.props.fileRecordingsServiceEnabled
151
+            || !this.props.integrationsEnabled)) {
152
+            return null;
153
+        }
154
+
155
+        const { _dialogStyles, isValidating, t } = this.props;
156
+
157
+        const switchContent
158
+            = this.props.integrationsEnabled
159
+                ? (
160
+                    <Switch
161
+                        className = 'recording-switch'
162
+                        disabled = { isValidating }
163
+                        onValueChange
164
+                            = { this._onRecordingServiceSwitchChange }
165
+                        style = { styles.switch }
166
+                        trackColor = {{ false: ColorPalette.lightGrey }}
167
+                        value = {
168
+                            this.state.selectedRecordingService
169
+                                === RECORDING_TYPES.JITSI_REC_SERVICE } />
170
+                ) : null;
171
+
110 172
         return (
111
-            <DialogContent style = { this.props._dialogStyles.text }>
112
-                { this.props.t('recording.startRecordingBody') }
113
-            </DialogContent>
173
+            <Container
174
+                className = 'recording-header'
175
+                style = { styles.header }>
176
+                <Container className = 'recording-icon-container'>
177
+                    <Image
178
+                        className = 'recording-icon'
179
+                        src = { JITSI_LOGO }
180
+                        style = { styles.recordingIcon } />
181
+                </Container>
182
+                <Text
183
+                    className = 'recording-title'
184
+                    style = {{
185
+                        ..._dialogStyles.text,
186
+                        ...styles.title
187
+                    }}>
188
+                    { t('recording.serviceDescription') }
189
+                </Text>
190
+                { switchContent }
191
+            </Container>
114 192
         );
115 193
     }
116 194
 
@@ -121,27 +199,67 @@ class StartRecordingDialogContent extends Component<Props> {
121 199
      * @returns {React$Component}
122 200
      */
123 201
     _renderIntegrationsContent() {
202
+        if (!this.props.integrationsEnabled) {
203
+            return null;
204
+        }
205
+
124 206
         const { _dialogStyles, isTokenValid, isValidating, t } = this.props;
125 207
 
126 208
         let content = null;
209
+        let switchContent = null;
127 210
 
128 211
         if (isValidating) {
129 212
             content = this._renderSpinner();
213
+            switchContent = <Container className = 'recording-switch' />;
130 214
         } else if (isTokenValid) {
131 215
             content = this._renderSignOut();
216
+            switchContent = (
217
+                <Container className = 'recording-switch'>
218
+                    <Button
219
+                        onValueChange = { this._onSignOut }
220
+                        style = { styles.signButton }>
221
+                        { t('recording.signOut') }
222
+                    </Button>
223
+                </Container>
224
+            );
225
+
226
+        } else {
227
+            switchContent = (
228
+                <Container className = 'recording-switch'>
229
+                    <Button
230
+                        onValueChange = { this._onSignIn }
231
+                        style = { styles.signButton }>
232
+                        { t('recording.signIn') }
233
+                    </Button>
234
+                </Container>
235
+            );
132 236
         }
133 237
 
134
-        // else { // Sign in screen:
135
-        // We don't need to render any additional information.
136
-        // }
238
+        if (this.props.fileRecordingsServiceEnabled) {
239
+            switchContent = (
240
+                <Switch
241
+                    className = 'recording-switch'
242
+                    disabled = { isValidating }
243
+                    onValueChange = { this._onDropboxSwitchChange }
244
+                    style = { styles.switch }
245
+                    trackColor = {{ false: ColorPalette.lightGrey }}
246
+                    value = { this.state.selectedRecordingService
247
+                        === RECORDING_TYPES.DROPBOX } />
248
+            );
249
+        }
137 250
 
138 251
         return (
139
-            <Container
140
-                className = 'recording-dialog'
141
-                style = { styles.container }>
252
+            <Container>
142 253
                 <Container
143 254
                     className = 'recording-header'
144 255
                     style = { styles.header }>
256
+                    <Container
257
+                        className = 'recording-icon-container'>
258
+                        <Image
259
+                            className = 'recording-icon'
260
+                            src = { DROPBOX_LOGO }
261
+                            style = { styles.recordingIcon } />
262
+                    </Container>
145 263
                     <Text
146 264
                         className = 'recording-title'
147 265
                         style = {{
@@ -150,11 +268,7 @@ class StartRecordingDialogContent extends Component<Props> {
150 268
                         }}>
151 269
                         { t('recording.authDropboxText') }
152 270
                     </Text>
153
-                    <Switch
154
-                        disabled = { isValidating }
155
-                        onValueChange = { this._onSwitchChange }
156
-                        style = { styles.switch }
157
-                        value = { isTokenValid } />
271
+                    { switchContent }
158 272
                 </Container>
159 273
                 <Container
160 274
                     className = 'authorization-panel'>
@@ -164,18 +278,49 @@ class StartRecordingDialogContent extends Component<Props> {
164 278
         );
165 279
     }
166 280
 
167
-    _onSwitchChange: boolean => void;
281
+    _onDropboxSwitchChange: boolean => void;
282
+    _onRecordingServiceSwitchChange: boolean => void;
168 283
 
169 284
     /**
170 285
      * Handler for onValueChange events from the Switch component.
171 286
      *
172 287
      * @returns {void}
173 288
      */
174
-    _onSwitchChange() {
289
+    _onRecordingServiceSwitchChange() {
290
+
291
+        // act like group, cannot toggle off
292
+        if (this.state.selectedRecordingService
293
+                === RECORDING_TYPES.JITSI_REC_SERVICE) {
294
+            return;
295
+        }
296
+
297
+        this.setState({
298
+            selectedRecordingService: RECORDING_TYPES.JITSI_REC_SERVICE
299
+        });
300
+
175 301
         if (this.props.isTokenValid) {
176
-            this._signOut();
177
-        } else {
178
-            this._signIn();
302
+            this._onSignOut();
303
+        }
304
+    }
305
+
306
+    /**
307
+     * Handler for onValueChange events from the Switch component.
308
+     *
309
+     * @returns {void}
310
+     */
311
+    _onDropboxSwitchChange() {
312
+        // act like group, cannot toggle off
313
+        if (this.state.selectedRecordingService
314
+                === RECORDING_TYPES.DROPBOX) {
315
+            return;
316
+        }
317
+
318
+        this.setState({
319
+            selectedRecordingService: RECORDING_TYPES.DROPBOX
320
+        });
321
+
322
+        if (!this.props.isTokenValid) {
323
+            this._onSignIn();
179 324
         }
180 325
     }
181 326
 
@@ -222,37 +367,32 @@ class StartRecordingDialogContent extends Component<Props> {
222 367
                         </Text>
223 368
                     </Container>
224 369
                 </Container>
225
-                <Container style = { styles.startRecordingText }>
226
-                    <Text style = { styles.text }>
227
-                        { t('recording.startRecordingBody') }
228
-                    </Text>
229
-                </Container>
230 370
             </Container>
231 371
         );
232 372
     }
233 373
 
234
-    _signIn: () => {};
374
+    _onSignIn: () => {};
235 375
 
236 376
     /**
237 377
      * Sings in a user.
238 378
      *
239 379
      * @returns {void}
240 380
      */
241
-    _signIn() {
381
+    _onSignIn() {
242 382
         sendAnalytics(
243 383
             createRecordingDialogEvent('start', 'signIn.button')
244 384
         );
245 385
         this.props.dispatch(authorizeDropbox());
246 386
     }
247 387
 
248
-    _signOut: () => {};
388
+    _onSignOut: () => {};
249 389
 
250 390
     /**
251 391
      * Sings out an user from dropbox.
252 392
      *
253 393
      * @returns {void}
254 394
      */
255
-    _signOut() {
395
+    _onSignOut() {
256 396
         sendAnalytics(
257 397
             createRecordingDialogEvent('start', 'signOut.button')
258 398
         );

+ 12
- 2
react/features/recording/components/Recording/native/StartRecordingDialog.js 查看文件

@@ -25,13 +25,23 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
25 25
      */
26 26
     render() {
27 27
         const { isTokenValid, isValidating, spaceLeft, userName } = this.state;
28
-        const { _isDropboxEnabled } = this.props;
28
+        const { _fileRecordingsServiceEnabled, _isDropboxEnabled } = this.props;
29
+
30
+        // disable ok button id recording service is shown only, when
31
+        // validating dropbox token, if that is not enabled we either always
32
+        // show the ok button or if just dropbox is enabled ok is available
33
+        // when there is token
34
+        const isOkDisabled
35
+            = _fileRecordingsServiceEnabled ? isValidating
36
+                : _isDropboxEnabled ? !isTokenValid : false;
29 37
 
30 38
         return (
31 39
             <CustomSubmitDialog
32
-                okDisabled = { _isDropboxEnabled && !isTokenValid }
40
+                okDisabled = { isOkDisabled }
33 41
                 onSubmit = { this._onSubmit } >
34 42
                 <StartRecordingDialogContent
43
+                    fileRecordingsServiceEnabled
44
+                        = { _fileRecordingsServiceEnabled }
35 45
                     integrationsEnabled = { _isDropboxEnabled }
36 46
                     isTokenValid = { isTokenValid }
37 47
                     isValidating = { isValidating }

+ 22
- 5
react/features/recording/components/Recording/styles.native.js 查看文件

@@ -6,6 +6,12 @@ import { BoxModel, createStyleSheet, ColorPalette } from '../../../base/styles';
6 6
 // the special case(s) of the recording feature bellow.
7 7
 const _PADDING = BoxModel.padding * 1.5;
8 8
 
9
+export const DROPBOX_LOGO
10
+    = require('../../../../../images/dropboxLogo_square.png');
11
+
12
+export const JITSI_LOGO
13
+    = require('../../../../../images/jitsiLogo_square.png');
14
+
9 15
 /**
10 16
  * The styles of the React {@code Components} of the feature recording.
11 17
  */
@@ -28,18 +34,29 @@ export default createStyleSheet({
28 34
         paddingBottom: _PADDING
29 35
     },
30 36
 
31
-    startRecordingText: {
32
-        paddingBottom: _PADDING
37
+    recordingIcon: {
38
+        width: 24,
39
+        height: 24
33 40
     },
34 41
 
35
-    switch: {
42
+    signButton: {
43
+        backgroundColor: ColorPalette.blue,
36 44
         color: ColorPalette.white,
37
-        paddingRight: BoxModel.padding
45
+        fontSize: 16,
46
+        borderRadius: 5,
47
+        padding: BoxModel.padding * 0.5
48
+    },
49
+
50
+    switch: {
51
+        color: ColorPalette.white
38 52
     },
39 53
 
40 54
     title: {
55
+        flex: 1,
41 56
         fontSize: 16,
42
-        fontWeight: 'bold'
57
+        fontWeight: 'bold',
58
+        textAlign: 'left',
59
+        paddingLeft: BoxModel.padding
43 60
     },
44 61
 
45 62
     text: {

+ 4
- 0
react/features/recording/components/Recording/styles.web.js 查看文件

@@ -1,3 +1,7 @@
1 1
 // XXX CSS is used on Web, JavaScript styles are use only for mobile. Export an
2 2
 // (empty) object so that styles[*] statements on Web don't trigger errors.
3 3
 export default {};
4
+
5
+export const DROPBOX_LOGO = 'images/dropboxLogo_square.png';
6
+
7
+export const JITSI_LOGO = 'images/jitsiLogo_square.png';

+ 14
- 4
react/features/recording/components/Recording/web/StartRecordingDialog.js 查看文件

@@ -25,16 +25,26 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
25 25
      */
26 26
     render() {
27 27
         const { isTokenValid, isValidating, spaceLeft, userName } = this.state;
28
-        const { _isDropboxEnabled } = this.props;
28
+        const { _fileRecordingsServiceEnabled, _isDropboxEnabled } = this.props;
29
+
30
+        // disable ok button id recording service is shown only, when
31
+        // validating dropbox token, if that is not enabled we either always
32
+        // show the ok button or if just dropbox is enabled ok is available
33
+        // when there is token
34
+        const isOkDisabled
35
+            = _fileRecordingsServiceEnabled ? isValidating
36
+                : _isDropboxEnabled ? !isTokenValid : false;
29 37
 
30 38
         return (
31 39
             <Dialog
32
-                okDisabled = { !isTokenValid && _isDropboxEnabled }
33
-                okKey = 'dialog.confirm'
40
+                okDisabled = { isOkDisabled }
41
+                okKey = 'dialog.startRecording'
34 42
                 onSubmit = { this._onSubmit }
35
-                titleKey = 'dialog.recording'
43
+                titleKey = 'dialog.startRecording'
36 44
                 width = 'small'>
37 45
                 <StartRecordingDialogContent
46
+                    fileRecordingsServiceEnabled
47
+                        = { _fileRecordingsServiceEnabled }
38 48
                     integrationsEnabled = { _isDropboxEnabled }
39 49
                     isTokenValid = { isTokenValid }
40 50
                     isValidating = { isValidating }

+ 4
- 5
react/features/recording/constants.js 查看文件

@@ -19,14 +19,13 @@ export const RECORDING_OFF_SOUND_ID = 'RECORDING_OFF_SOUND';
19 19
 export const RECORDING_ON_SOUND_ID = 'RECORDING_ON_SOUND';
20 20
 
21 21
 /**
22
- * Expected supported recording types. JIBRI is known to support live streaming
23
- * whereas JIRECON is for recording.
22
+ * Expected supported recording types.
24 23
  *
25
- * @type {Object}
24
+ * @enum {string}
26 25
  */
27 26
 export const RECORDING_TYPES = {
28
-    JIBRI: 'jibri',
29
-    JIRECON: 'jirecon'
27
+    JITSI_REC_SERVICE: 'recording-service',
28
+    DROPBOX: 'dropbox'
30 29
 };
31 30
 
32 31
 /**

正在加载...
取消
保存