Parcourir la source

feat(subject): UI

master
Hristo Terezov il y a 6 ans
Parent
révision
cb8e9eed5e

+ 3
- 13
conference.js Voir le fichier

@@ -38,6 +38,7 @@ import {
38 38
     conferenceFailed,
39 39
     conferenceJoined,
40 40
     conferenceLeft,
41
+    conferenceSubjectChanged,
41 42
     conferenceWillJoin,
42 43
     conferenceWillLeave,
43 44
     dataChannelOpened,
@@ -45,8 +46,7 @@ import {
45 46
     onStartMutedPolicyChanged,
46 47
     p2pStatusChanged,
47 48
     sendLocalParticipant,
48
-    setDesktopSharingEnabled,
49
-    setSubject
49
+    setDesktopSharingEnabled
50 50
 } from './react/features/base/conference';
51 51
 import {
52 52
     getAvailableDevices,
@@ -1834,7 +1834,7 @@ export default {
1834 1834
             APP.UI.showToolbar(6000);
1835 1835
         });
1836 1836
         room.on(JitsiConferenceEvents.SUBJECT_CHANGED,
1837
-            subject => APP.API.notifySubjectChanged(subject));
1837
+            subject => APP.store.dispatch(conferenceSubjectChanged(subject)));
1838 1838
 
1839 1839
         room.on(
1840 1840
             JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
@@ -2767,16 +2767,6 @@ export default {
2767 2767
         APP.API.notifyAudioMutedStatusChanged(muted);
2768 2768
     },
2769 2769
 
2770
-    /**
2771
-     * Changes the subject of the conference.
2772
-     * Note: available only for moderator.
2773
-     *
2774
-     * @param subject {string} the new subject for the conference.
2775
-     */
2776
-    setSubject(subject) {
2777
-        APP.store.dispatch(setSubject(subject));
2778
-    },
2779
-
2780 2770
     /**
2781 2771
      * Dispatches the passed in feedback for submission. The submitted score
2782 2772
      * should be a number inclusively between 1 through 5, or -1 for no score.

+ 21
- 0
css/_subject.scss Voir le fichier

@@ -0,0 +1,21 @@
1
+.subject {
2
+    top: -120px;
3
+    transition: top .3s ease-in;
4
+    height: 95px;
5
+    width: 100%;
6
+    position: absolute;
7
+    padding: 25px 140px 0 140px;
8
+    text-align: center;
9
+    font-size: 17px;
10
+    color: #fff;
11
+    z-index: $toolbarBackgroundZ;
12
+    overflow: hidden;
13
+    text-overflow: ellipsis;
14
+    box-sizing: border-box;
15
+    white-space: nowrap;
16
+    background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
17
+
18
+    &.visible {
19
+        top: 0px;
20
+    }
21
+}

+ 1
- 0
css/main.scss Voir le fichier

@@ -49,6 +49,7 @@ $flagsImagePath: "/images/";
49 49
 @import 'modals/local-recording/local-recording';
50 50
 @import 'videolayout_default';
51 51
 @import 'notice';
52
+@import 'subject';
52 53
 @import 'popup_menu';
53 54
 @import 'recording';
54 55
 @import 'login_menu';

+ 2
- 1
modules/API/API.js Voir le fichier

@@ -5,6 +5,7 @@ import {
5 5
     createApiEvent,
6 6
     sendAnalytics
7 7
 } from '../../react/features/analytics';
8
+import { setSubject } from '../../react/features/base/conference';
8 9
 import { parseJWTFromURLParams } from '../../react/features/base/jwt';
9 10
 import { invite } from '../../react/features/invite';
10 11
 import { getJitsiMeetTransport } from '../transport';
@@ -65,7 +66,7 @@ function initCommands() {
65 66
         },
66 67
         'subject': subject => {
67 68
             sendAnalytics(createApiEvent('subject.changed'));
68
-            APP.conference.setSubject(subject);
69
+            APP.store.dispatch(setSubject(subject));
69 70
         },
70 71
         'submit-feedback': feedback => {
71 72
             sendAnalytics(createApiEvent('submit.feedback'));

+ 1
- 1
modules/API/external/external_api.js Voir le fichier

@@ -548,7 +548,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
548 548
      * {@code displayName} - Sets the display name of the local participant to
549 549
      * the value passed in the arguments array.
550 550
      * {@code subject} - Sets the subject of the conference, the value passed
551
-     * in the arguments array. Note: available only for moderator.
551
+     * in the arguments array. Note: Available only for moderator.
552 552
      *
553 553
      * {@code toggleAudio} - Mutes / unmutes audio with no arguments.
554 554
      * {@code toggleVideo} - Mutes / unmutes video with no arguments.

+ 20
- 10
react/features/base/conference/actionTypes.js Voir le fichier

@@ -42,6 +42,16 @@ export const CONFERENCE_JOINED = Symbol('CONFERENCE_JOINED');
42 42
  */
43 43
 export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
44 44
 
45
+/**
46
+ * The type of (redux) action, which indicates conference subject changes.
47
+ *
48
+ * {
49
+ *     type: CONFERENCE_SUBJECT_CHANGED
50
+ *     subject: string
51
+ * }
52
+ */
53
+export const CONFERENCE_SUBJECT_CHANGED = Symbol('CONFERENCE_SUBJECT_CHANGED');
54
+
45 55
 /**
46 56
  * The type of (redux) action which signals that a specific conference will be
47 57
  * joined.
@@ -119,16 +129,6 @@ export const P2P_STATUS_CHANGED = Symbol('P2P_STATUS_CHANGED');
119 129
  */
120 130
 export const SET_AUDIO_ONLY = Symbol('SET_AUDIO_ONLY');
121 131
 
122
-/**
123
- * The type of (redux) action, which indicates to set conference subject.
124
- *
125
- * {
126
- *     type: SET_CONFERENCE_SUBJECT
127
- *     subject: string
128
- * }
129
- */
130
-export const SET_CONFERENCE_SUBJECT = Symbol('SET_CONFERENCE_SUBJECT');
131
-
132 132
 /**
133 133
  * The type of (redux) action which sets the desktop sharing enabled flag for
134 134
  * the current conference.
@@ -199,6 +199,16 @@ export const SET_PASSWORD = Symbol('SET_PASSWORD');
199 199
  */
200 200
 export const SET_PASSWORD_FAILED = Symbol('SET_PASSWORD_FAILED');
201 201
 
202
+/**
203
+ * The type of (redux) action which signals for pending subject changes.
204
+ *
205
+ * {
206
+ *     type: SET_PENDING_SUBJECT_CHANGE,
207
+ *     subject: string
208
+ * }
209
+ */
210
+export const SET_PENDING_SUBJECT_CHANGE = Symbol('SET_PENDING_SUBJECT_CHANGE');
211
+
202 212
 /**
203 213
  * The type of (redux) action which sets the preferred maximum video height that
204 214
  * should be received from remote participants.

+ 34
- 5
react/features/base/conference/actions.js Voir le fichier

@@ -26,6 +26,7 @@ import {
26 26
     CONFERENCE_FAILED,
27 27
     CONFERENCE_JOINED,
28 28
     CONFERENCE_LEFT,
29
+    CONFERENCE_SUBJECT_CHANGED,
29 30
     CONFERENCE_WILL_JOIN,
30 31
     CONFERENCE_WILL_LEAVE,
31 32
     DATA_CHANNEL_OPENED,
@@ -33,7 +34,6 @@ import {
33 34
     LOCK_STATE_CHANGED,
34 35
     P2P_STATUS_CHANGED,
35 36
     SET_AUDIO_ONLY,
36
-    SET_CONFERENCE_SUBJECT,
37 37
     SET_DESKTOP_SHARING_ENABLED,
38 38
     SET_FOLLOW_ME,
39 39
     SET_LASTN,
@@ -42,6 +42,7 @@ import {
42 42
     SET_PASSWORD_FAILED,
43 43
     SET_PREFERRED_RECEIVER_VIDEO_QUALITY,
44 44
     SET_ROOM,
45
+    SET_PENDING_SUBJECT_CHANGE,
45 46
     SET_START_MUTED_POLICY
46 47
 } from './actionTypes';
47 48
 import {
@@ -272,6 +273,22 @@ export function conferenceLeft(conference: Object) {
272 273
     };
273 274
 }
274 275
 
276
+/**
277
+ * Signals that the conference subject has been changed.
278
+ *
279
+ * @param {string} subject - The new subject.
280
+ * @returns {{
281
+ *     type: CONFERENCE_SUBJECT_CHANGED,
282
+ *     subject: string
283
+ * }}
284
+ */
285
+export function conferenceSubjectChanged(subject: string) {
286
+    return {
287
+        type: CONFERENCE_SUBJECT_CHANGED,
288
+        subject
289
+    };
290
+}
291
+
275 292
 /**
276 293
  * Adds any existing local tracks to a specific conference before the conference
277 294
  * is joined. Then signals the intention of the application to have the local
@@ -736,9 +753,21 @@ export function toggleAudioOnly() {
736 753
  * @param {string} subject - The new subject.
737 754
  * @returns {void}
738 755
  */
739
-export function setSubject(subject: String) {
740
-    return {
741
-        type: SET_CONFERENCE_SUBJECT,
742
-        subject
756
+export function setSubject(subject: string = '') {
757
+    return (dispatch: Dispatch<*>, getState: Function) => {
758
+        const { conference } = getState()['features/base/conference'];
759
+
760
+        if (conference) {
761
+            dispatch({
762
+                type: SET_PENDING_SUBJECT_CHANGE,
763
+                subject: undefined
764
+            });
765
+            conference.setSubject(subject);
766
+        } else {
767
+            dispatch({
768
+                type: SET_PENDING_SUBJECT_CHANGE,
769
+                subject
770
+            });
771
+        }
743 772
     };
744 773
 }

+ 38
- 29
react/features/base/conference/middleware.js Voir le fichier

@@ -27,15 +27,16 @@ import {
27 27
     conferenceLeft,
28 28
     conferenceWillLeave,
29 29
     createConference,
30
-    setLastN
30
+    setLastN,
31
+    setSubject
31 32
 } from './actions';
32 33
 import {
33 34
     CONFERENCE_FAILED,
34 35
     CONFERENCE_JOINED,
36
+    CONFERENCE_SUBJECT_CHANGED,
35 37
     CONFERENCE_WILL_LEAVE,
36 38
     DATA_CHANNEL_OPENED,
37 39
     SET_AUDIO_ONLY,
38
-    SET_CONFERENCE_SUBJECT,
39 40
     SET_LASTN,
40 41
     SET_ROOM
41 42
 } from './actionTypes';
@@ -75,6 +76,9 @@ MiddlewareRegistry.register(store => next => action => {
75 76
     case CONNECTION_FAILED:
76 77
         return _connectionFailed(store, next, action);
77 78
 
79
+    case CONFERENCE_SUBJECT_CHANGED:
80
+        return _conferenceSubjectChanged(store, next, action);
81
+
78 82
     case CONFERENCE_WILL_LEAVE:
79 83
         _conferenceWillLeave();
80 84
         break;
@@ -91,9 +95,6 @@ MiddlewareRegistry.register(store => next => action => {
91 95
     case SET_AUDIO_ONLY:
92 96
         return _setAudioOnly(store, next, action);
93 97
 
94
-    case SET_CONFERENCE_SUBJECT:
95
-        return _setSubject(store, next, action);
96
-
97 98
     case SET_LASTN:
98 99
         return _setLastN(store, next, action);
99 100
 
@@ -192,7 +193,15 @@ function _conferenceFailed(store, next, action) {
192 193
 function _conferenceJoined({ dispatch, getState }, next, action) {
193 194
     const result = next(action);
194 195
 
195
-    const { audioOnly, conference } = getState()['features/base/conference'];
196
+    const {
197
+        audioOnly,
198
+        conference,
199
+        pendingSubjectChange
200
+    } = getState()['features/base/conference'];
201
+
202
+    if (pendingSubjectChange) {
203
+        dispatch(setSubject(pendingSubjectChange));
204
+    }
196 205
 
197 206
     // FIXME On Web the audio only mode for "start audio only" is toggled before
198 207
     // conference is added to the redux store ("on conference joined" action)
@@ -305,6 +314,29 @@ function _connectionFailed({ dispatch, getState }, next, action) {
305 314
     return result;
306 315
 }
307 316
 
317
+/**
318
+ * Notifies the feature base/conference that the action
319
+ * {@code CONFERENCE_SUBJECT_CHANGED} is being dispatched within a specific
320
+ *  redux store.
321
+ *
322
+ * @param {Store} store - The redux store in which the specified {@code action}
323
+ * is being dispatched.
324
+ * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
325
+ * specified {@code action} to the specified {@code store}.
326
+ * @param {Action} action - The redux action {@code CONFERENCE_SUBJECT_CHANGED}
327
+ * which is being dispatched in the specified {@code store}.
328
+ * @private
329
+ * @returns {Object} The value returned by {@code next(action)}.
330
+ */
331
+function _conferenceSubjectChanged({ getState }, next, action) {
332
+    const result = next(action);
333
+    const { subject } = getState()['features/base/conference'];
334
+
335
+    typeof APP === 'object' && APP.API.notifySubjectChanged(subject);
336
+
337
+    return result;
338
+}
339
+
308 340
 /**
309 341
  * Notifies the feature base/conference that the action
310 342
  * {@code CONFERENCE_WILL_LEAVE} is being dispatched within a specific redux
@@ -683,26 +715,3 @@ function _updateLocalParticipantInConference({ getState }, next, action) {
683 715
 
684 716
     return result;
685 717
 }
686
-
687
-/**
688
- * Changing conference subject.
689
- *
690
- * @param {Store} store - The redux store in which the specified {@code action}
691
- * is being dispatched.
692
- * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
693
- * specified {@code action} to the specified {@code store}.
694
- * @param {Action} action - The redux action which is being dispatched in the
695
- * specified {@code store}.
696
- * @private
697
- * @returns {Object} The value returned by {@code next(action)}.
698
- */
699
-function _setSubject({ getState }, next, action) {
700
-    const { conference } = getState()['features/base/conference'];
701
-    const { subject } = action;
702
-
703
-    if (subject) {
704
-        conference.setSubject(subject);
705
-    }
706
-
707
-    return next(action);
708
-}

+ 8
- 0
react/features/base/conference/reducer.js Voir le fichier

@@ -10,6 +10,7 @@ import {
10 10
     CONFERENCE_FAILED,
11 11
     CONFERENCE_JOINED,
12 12
     CONFERENCE_LEFT,
13
+    CONFERENCE_SUBJECT_CHANGED,
13 14
     CONFERENCE_WILL_JOIN,
14 15
     CONFERENCE_WILL_LEAVE,
15 16
     LOCK_STATE_CHANGED,
@@ -19,6 +20,7 @@ import {
19 20
     SET_FOLLOW_ME,
20 21
     SET_MAX_RECEIVER_VIDEO_QUALITY,
21 22
     SET_PASSWORD,
23
+    SET_PENDING_SUBJECT_CHANGE,
22 24
     SET_PREFERRED_RECEIVER_VIDEO_QUALITY,
23 25
     SET_ROOM,
24 26
     SET_SIP_GATEWAY_ENABLED,
@@ -55,6 +57,9 @@ ReducerRegistry.register(
55 57
         case CONFERENCE_JOINED:
56 58
             return _conferenceJoined(state, action);
57 59
 
60
+        case CONFERENCE_SUBJECT_CHANGED:
61
+            return set(state, 'subject', action.subject);
62
+
58 63
         case CONFERENCE_LEFT:
59 64
         case CONFERENCE_WILL_LEAVE:
60 65
             return _conferenceLeftOrWillLeave(state, action);
@@ -92,6 +97,9 @@ ReducerRegistry.register(
92 97
         case SET_PASSWORD:
93 98
             return _setPassword(state, action);
94 99
 
100
+        case SET_PENDING_SUBJECT_CHANGE:
101
+            return set(state, 'pendingSubjectChange', action.subject);
102
+
95 103
         case SET_PREFERRED_RECEIVER_VIDEO_QUALITY:
96 104
             return set(
97 105
                 state,

+ 2
- 0
react/features/conference/components/web/Conference.js Voir le fichier

@@ -31,6 +31,7 @@ import { maybeShowSuboptimalExperienceNotification } from '../../functions';
31 31
 
32 32
 import Labels from './Labels';
33 33
 import { default as Notice } from './Notice';
34
+import { default as Subject } from './Subject';
34 35
 
35 36
 declare var APP: Object;
36 37
 declare var config: Object;
@@ -217,6 +218,7 @@ class Conference extends Component<Props> {
217 218
                 id = 'videoconference_page'
218 219
                 onMouseMove = { this._onShowToolbar }>
219 220
                 <Notice />
221
+                <Subject />
220 222
                 <div id = 'videospace'>
221 223
                     <LargeVideo />
222 224
                     { hideVideoQualityLabel

+ 67
- 0
react/features/conference/components/web/Subject.js Voir le fichier

@@ -0,0 +1,67 @@
1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { isToolboxVisible } from '../../../toolbox';
7
+
8
+/**
9
+ * The type of the React {@code Component} props of {@link Subject}.
10
+ */
11
+type Props = {
12
+
13
+    /**
14
+     * The subject of the conference.
15
+     */
16
+    _subject: string,
17
+
18
+    /**
19
+     * Indicates whether the component should be visible or not.
20
+     */
21
+    _visible: boolean
22
+};
23
+
24
+/**
25
+ * Subject react component.
26
+ *
27
+ * @class Subject
28
+ */
29
+class Subject extends Component<Props> {
30
+
31
+    /**
32
+     * Implements React's {@link Component#render()}.
33
+     *
34
+     * @inheritdoc
35
+     * @returns {ReactElement}
36
+     */
37
+    render() {
38
+        const { _subject, _visible } = this.props;
39
+
40
+        return (
41
+            <div className = { `subject ${_visible ? 'visible' : ''}` }>
42
+                { _subject }
43
+            </div>
44
+        );
45
+    }
46
+}
47
+
48
+/**
49
+ * Maps (parts of) the Redux state to the associated
50
+ * {@code Subject}'s props.
51
+ *
52
+ * @param {Object} state - The Redux state.
53
+ * @private
54
+ * @returns {{
55
+ *     _subject: string,
56
+ *     _visible: boolean
57
+ * }}
58
+ */
59
+function _mapStateToProps(state) {
60
+    const { subject } = state['features/base/conference'];
61
+
62
+    return {
63
+        _subject: subject,
64
+        _visible: isToolboxVisible(state)
65
+    };
66
+}
67
+export default connect(_mapStateToProps)(Subject);

+ 3
- 5
react/features/toolbox/components/web/Toolbox.js Voir le fichier

@@ -55,6 +55,7 @@ import {
55 55
     setToolbarHovered
56 56
 } from '../../actions';
57 57
 import AudioMuteButton from '../AudioMuteButton';
58
+import { isToolboxVisible } from '../../functions';
58 59
 import HangupButton from '../HangupButton';
59 60
 import OverflowMenuButton from './OverflowMenuButton';
60 61
 import OverflowMenuProfileItem from './OverflowMenuProfileItem';
@@ -1281,11 +1282,8 @@ function _mapStateToProps(state) {
1281 1282
     } = state['features/base/config'];
1282 1283
     const sharedVideoStatus = state['features/shared-video'].status;
1283 1284
     const {
1284
-        alwaysVisible,
1285 1285
         fullScreen,
1286
-        overflowMenuVisible,
1287
-        timeoutID,
1288
-        visible
1286
+        overflowMenuVisible
1289 1287
     } = state['features/toolbox'];
1290 1288
     const localParticipant = getLocalParticipant(state);
1291 1289
     const localRecordingStates = state['features/local-recording'];
@@ -1333,7 +1331,7 @@ function _mapStateToProps(state) {
1333 1331
         _sharingVideo: sharedVideoStatus === 'playing'
1334 1332
             || sharedVideoStatus === 'start'
1335 1333
             || sharedVideoStatus === 'pause',
1336
-        _visible: Boolean(timeoutID || visible || alwaysVisible),
1334
+        _visible: isToolboxVisible(state),
1337 1335
 
1338 1336
         // XXX: We are not currently using state here, but in the future, when
1339 1337
         // interfaceConfig is part of redux we will.

+ 0
- 17
react/features/toolbox/functions.any.js Voir le fichier

@@ -1,17 +0,0 @@
1
-// @flow
2
-
3
-import { toState } from '../base/redux';
4
-
5
-/**
6
- * Returns true if the toolbox is visible.
7
- *
8
- * @param {Object | Function} stateful - A function or object that can be
9
- * resolved to Redux state by the function {@code toState}.
10
- * @returns {boolean}
11
- */
12
-export function isToolboxVisible(stateful: Object | Function) {
13
-    const { alwaysVisible, enabled, visible }
14
-        = toState(stateful)['features/toolbox'];
15
-
16
-    return enabled && (alwaysVisible || visible);
17
-}

+ 15
- 1
react/features/toolbox/functions.native.js Voir le fichier

@@ -1,3 +1,17 @@
1 1
 // @flow
2 2
 
3
-export * from './functions.any';
3
+import { toState } from '../base/redux';
4
+
5
+/**
6
+ * Returns true if the toolbox is visible.
7
+ *
8
+ * @param {Object | Function} stateful - A function or object that can be
9
+ * resolved to Redux state by the function {@code toState}.
10
+ * @returns {boolean}
11
+ */
12
+export function isToolboxVisible(stateful: Object | Function) {
13
+    const { alwaysVisible, enabled, visible }
14
+        = toState(stateful)['features/toolbox'];
15
+
16
+    return enabled && (alwaysVisible || visible);
17
+}

+ 18
- 2
react/features/toolbox/functions.web.js Voir le fichier

@@ -1,7 +1,5 @@
1 1
 // @flow
2 2
 
3
-export * from './functions.any';
4
-
5 3
 declare var interfaceConfig: Object;
6 4
 
7 5
 /**
@@ -26,3 +24,21 @@ export function getToolboxHeight() {
26 24
 export function isButtonEnabled(name: string) {
27 25
     return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1;
28 26
 }
27
+
28
+
29
+/**
30
+ * Indicates if the toolbox is visible or not.
31
+ *
32
+ * @param {string} state - The state from the Redux store.
33
+ * @returns {boolean} - True to indicate that the toolbox is visible, false -
34
+ * otherwise.
35
+ */
36
+export function isToolboxVisible(state: Object) {
37
+    const {
38
+        alwaysVisible,
39
+        timeoutID,
40
+        visible
41
+    } = state['features/toolbox'];
42
+
43
+    return Boolean(timeoutID || visible || alwaysVisible);
44
+}

Chargement…
Annuler
Enregistrer