feat(subject): UI
This commit is contained in:
parent
2715e81f1d
commit
cb8e9eed5e
|
@ -38,6 +38,7 @@ import {
|
|||
conferenceFailed,
|
||||
conferenceJoined,
|
||||
conferenceLeft,
|
||||
conferenceSubjectChanged,
|
||||
conferenceWillJoin,
|
||||
conferenceWillLeave,
|
||||
dataChannelOpened,
|
||||
|
@ -45,8 +46,7 @@ import {
|
|||
onStartMutedPolicyChanged,
|
||||
p2pStatusChanged,
|
||||
sendLocalParticipant,
|
||||
setDesktopSharingEnabled,
|
||||
setSubject
|
||||
setDesktopSharingEnabled
|
||||
} from './react/features/base/conference';
|
||||
import {
|
||||
getAvailableDevices,
|
||||
|
@ -1834,7 +1834,7 @@ export default {
|
|||
APP.UI.showToolbar(6000);
|
||||
});
|
||||
room.on(JitsiConferenceEvents.SUBJECT_CHANGED,
|
||||
subject => APP.API.notifySubjectChanged(subject));
|
||||
subject => APP.store.dispatch(conferenceSubjectChanged(subject)));
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
|
||||
|
@ -2767,16 +2767,6 @@ export default {
|
|||
APP.API.notifyAudioMutedStatusChanged(muted);
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the subject of the conference.
|
||||
* Note: available only for moderator.
|
||||
*
|
||||
* @param subject {string} the new subject for the conference.
|
||||
*/
|
||||
setSubject(subject) {
|
||||
APP.store.dispatch(setSubject(subject));
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches the passed in feedback for submission. The submitted score
|
||||
* should be a number inclusively between 1 through 5, or -1 for no score.
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
.subject {
|
||||
top: -120px;
|
||||
transition: top .3s ease-in;
|
||||
height: 95px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
padding: 25px 140px 0 140px;
|
||||
text-align: center;
|
||||
font-size: 17px;
|
||||
color: #fff;
|
||||
z-index: $toolbarBackgroundZ;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
|
||||
|
||||
&.visible {
|
||||
top: 0px;
|
||||
}
|
||||
}
|
|
@ -49,6 +49,7 @@ $flagsImagePath: "/images/";
|
|||
@import 'modals/local-recording/local-recording';
|
||||
@import 'videolayout_default';
|
||||
@import 'notice';
|
||||
@import 'subject';
|
||||
@import 'popup_menu';
|
||||
@import 'recording';
|
||||
@import 'login_menu';
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
createApiEvent,
|
||||
sendAnalytics
|
||||
} from '../../react/features/analytics';
|
||||
import { setSubject } from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import { invite } from '../../react/features/invite';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
|
@ -65,7 +66,7 @@ function initCommands() {
|
|||
},
|
||||
'subject': subject => {
|
||||
sendAnalytics(createApiEvent('subject.changed'));
|
||||
APP.conference.setSubject(subject);
|
||||
APP.store.dispatch(setSubject(subject));
|
||||
},
|
||||
'submit-feedback': feedback => {
|
||||
sendAnalytics(createApiEvent('submit.feedback'));
|
||||
|
|
|
@ -548,7 +548,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
|||
* {@code displayName} - Sets the display name of the local participant to
|
||||
* the value passed in the arguments array.
|
||||
* {@code subject} - Sets the subject of the conference, the value passed
|
||||
* in the arguments array. Note: available only for moderator.
|
||||
* in the arguments array. Note: Available only for moderator.
|
||||
*
|
||||
* {@code toggleAudio} - Mutes / unmutes audio with no arguments.
|
||||
* {@code toggleVideo} - Mutes / unmutes video with no arguments.
|
||||
|
|
|
@ -42,6 +42,16 @@ export const CONFERENCE_JOINED = Symbol('CONFERENCE_JOINED');
|
|||
*/
|
||||
export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
|
||||
|
||||
/**
|
||||
* The type of (redux) action, which indicates conference subject changes.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_SUBJECT_CHANGED
|
||||
* subject: string
|
||||
* }
|
||||
*/
|
||||
export const CONFERENCE_SUBJECT_CHANGED = Symbol('CONFERENCE_SUBJECT_CHANGED');
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that a specific conference will be
|
||||
* joined.
|
||||
|
@ -119,16 +129,6 @@ export const P2P_STATUS_CHANGED = Symbol('P2P_STATUS_CHANGED');
|
|||
*/
|
||||
export const SET_AUDIO_ONLY = Symbol('SET_AUDIO_ONLY');
|
||||
|
||||
/**
|
||||
* The type of (redux) action, which indicates to set conference subject.
|
||||
*
|
||||
* {
|
||||
* type: SET_CONFERENCE_SUBJECT
|
||||
* subject: string
|
||||
* }
|
||||
*/
|
||||
export const SET_CONFERENCE_SUBJECT = Symbol('SET_CONFERENCE_SUBJECT');
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the desktop sharing enabled flag for
|
||||
* the current conference.
|
||||
|
@ -199,6 +199,16 @@ export const SET_PASSWORD = Symbol('SET_PASSWORD');
|
|||
*/
|
||||
export const SET_PASSWORD_FAILED = Symbol('SET_PASSWORD_FAILED');
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals for pending subject changes.
|
||||
*
|
||||
* {
|
||||
* type: SET_PENDING_SUBJECT_CHANGE,
|
||||
* subject: string
|
||||
* }
|
||||
*/
|
||||
export const SET_PENDING_SUBJECT_CHANGE = Symbol('SET_PENDING_SUBJECT_CHANGE');
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the preferred maximum video height that
|
||||
* should be received from remote participants.
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_SUBJECT_CHANGED,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
DATA_CHANNEL_OPENED,
|
||||
|
@ -33,7 +34,6 @@ import {
|
|||
LOCK_STATE_CHANGED,
|
||||
P2P_STATUS_CHANGED,
|
||||
SET_AUDIO_ONLY,
|
||||
SET_CONFERENCE_SUBJECT,
|
||||
SET_DESKTOP_SHARING_ENABLED,
|
||||
SET_FOLLOW_ME,
|
||||
SET_LASTN,
|
||||
|
@ -42,6 +42,7 @@ import {
|
|||
SET_PASSWORD_FAILED,
|
||||
SET_PREFERRED_RECEIVER_VIDEO_QUALITY,
|
||||
SET_ROOM,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
SET_START_MUTED_POLICY
|
||||
} from './actionTypes';
|
||||
import {
|
||||
|
@ -272,6 +273,22 @@ export function conferenceLeft(conference: Object) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the conference subject has been changed.
|
||||
*
|
||||
* @param {string} subject - The new subject.
|
||||
* @returns {{
|
||||
* type: CONFERENCE_SUBJECT_CHANGED,
|
||||
* subject: string
|
||||
* }}
|
||||
*/
|
||||
export function conferenceSubjectChanged(subject: string) {
|
||||
return {
|
||||
type: CONFERENCE_SUBJECT_CHANGED,
|
||||
subject
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any existing local tracks to a specific conference before the conference
|
||||
* is joined. Then signals the intention of the application to have the local
|
||||
|
@ -736,9 +753,21 @@ export function toggleAudioOnly() {
|
|||
* @param {string} subject - The new subject.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setSubject(subject: String) {
|
||||
return {
|
||||
type: SET_CONFERENCE_SUBJECT,
|
||||
subject
|
||||
export function setSubject(subject: string = '') {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
|
||||
if (conference) {
|
||||
dispatch({
|
||||
type: SET_PENDING_SUBJECT_CHANGE,
|
||||
subject: undefined
|
||||
});
|
||||
conference.setSubject(subject);
|
||||
} else {
|
||||
dispatch({
|
||||
type: SET_PENDING_SUBJECT_CHANGE,
|
||||
subject
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -27,15 +27,16 @@ import {
|
|||
conferenceLeft,
|
||||
conferenceWillLeave,
|
||||
createConference,
|
||||
setLastN
|
||||
setLastN,
|
||||
setSubject
|
||||
} from './actions';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_SUBJECT_CHANGED,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
DATA_CHANNEL_OPENED,
|
||||
SET_AUDIO_ONLY,
|
||||
SET_CONFERENCE_SUBJECT,
|
||||
SET_LASTN,
|
||||
SET_ROOM
|
||||
} from './actionTypes';
|
||||
|
@ -75,6 +76,9 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
case CONNECTION_FAILED:
|
||||
return _connectionFailed(store, next, action);
|
||||
|
||||
case CONFERENCE_SUBJECT_CHANGED:
|
||||
return _conferenceSubjectChanged(store, next, action);
|
||||
|
||||
case CONFERENCE_WILL_LEAVE:
|
||||
_conferenceWillLeave();
|
||||
break;
|
||||
|
@ -91,9 +95,6 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
case SET_AUDIO_ONLY:
|
||||
return _setAudioOnly(store, next, action);
|
||||
|
||||
case SET_CONFERENCE_SUBJECT:
|
||||
return _setSubject(store, next, action);
|
||||
|
||||
case SET_LASTN:
|
||||
return _setLastN(store, next, action);
|
||||
|
||||
|
@ -192,7 +193,15 @@ function _conferenceFailed(store, next, action) {
|
|||
function _conferenceJoined({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const { audioOnly, conference } = getState()['features/base/conference'];
|
||||
const {
|
||||
audioOnly,
|
||||
conference,
|
||||
pendingSubjectChange
|
||||
} = getState()['features/base/conference'];
|
||||
|
||||
if (pendingSubjectChange) {
|
||||
dispatch(setSubject(pendingSubjectChange));
|
||||
}
|
||||
|
||||
// FIXME On Web the audio only mode for "start audio only" is toggled before
|
||||
// conference is added to the redux store ("on conference joined" action)
|
||||
|
@ -305,6 +314,29 @@ function _connectionFailed({ dispatch, getState }, next, action) {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature base/conference that the action
|
||||
* {@code CONFERENCE_SUBJECT_CHANGED} is being dispatched within a specific
|
||||
* redux store.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code CONFERENCE_SUBJECT_CHANGED}
|
||||
* which is being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _conferenceSubjectChanged({ getState }, next, action) {
|
||||
const result = next(action);
|
||||
const { subject } = getState()['features/base/conference'];
|
||||
|
||||
typeof APP === 'object' && APP.API.notifySubjectChanged(subject);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature base/conference that the action
|
||||
* {@code CONFERENCE_WILL_LEAVE} is being dispatched within a specific redux
|
||||
|
@ -683,26 +715,3 @@ function _updateLocalParticipantInConference({ getState }, next, action) {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changing conference subject.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action which is being dispatched in the
|
||||
* specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _setSubject({ getState }, next, action) {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
const { subject } = action;
|
||||
|
||||
if (subject) {
|
||||
conference.setSubject(subject);
|
||||
}
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_SUBJECT_CHANGED,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
LOCK_STATE_CHANGED,
|
||||
|
@ -19,6 +20,7 @@ import {
|
|||
SET_FOLLOW_ME,
|
||||
SET_MAX_RECEIVER_VIDEO_QUALITY,
|
||||
SET_PASSWORD,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
SET_PREFERRED_RECEIVER_VIDEO_QUALITY,
|
||||
SET_ROOM,
|
||||
SET_SIP_GATEWAY_ENABLED,
|
||||
|
@ -55,6 +57,9 @@ ReducerRegistry.register(
|
|||
case CONFERENCE_JOINED:
|
||||
return _conferenceJoined(state, action);
|
||||
|
||||
case CONFERENCE_SUBJECT_CHANGED:
|
||||
return set(state, 'subject', action.subject);
|
||||
|
||||
case CONFERENCE_LEFT:
|
||||
case CONFERENCE_WILL_LEAVE:
|
||||
return _conferenceLeftOrWillLeave(state, action);
|
||||
|
@ -92,6 +97,9 @@ ReducerRegistry.register(
|
|||
case SET_PASSWORD:
|
||||
return _setPassword(state, action);
|
||||
|
||||
case SET_PENDING_SUBJECT_CHANGE:
|
||||
return set(state, 'pendingSubjectChange', action.subject);
|
||||
|
||||
case SET_PREFERRED_RECEIVER_VIDEO_QUALITY:
|
||||
return set(
|
||||
state,
|
||||
|
|
|
@ -31,6 +31,7 @@ import { maybeShowSuboptimalExperienceNotification } from '../../functions';
|
|||
|
||||
import Labels from './Labels';
|
||||
import { default as Notice } from './Notice';
|
||||
import { default as Subject } from './Subject';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var config: Object;
|
||||
|
@ -217,6 +218,7 @@ class Conference extends Component<Props> {
|
|||
id = 'videoconference_page'
|
||||
onMouseMove = { this._onShowToolbar }>
|
||||
<Notice />
|
||||
<Subject />
|
||||
<div id = 'videospace'>
|
||||
<LargeVideo />
|
||||
{ hideVideoQualityLabel
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { isToolboxVisible } from '../../../toolbox';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Subject}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The subject of the conference.
|
||||
*/
|
||||
_subject: string,
|
||||
|
||||
/**
|
||||
* Indicates whether the component should be visible or not.
|
||||
*/
|
||||
_visible: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Subject react component.
|
||||
*
|
||||
* @class Subject
|
||||
*/
|
||||
class Subject extends Component<Props> {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _subject, _visible } = this.props;
|
||||
|
||||
return (
|
||||
<div className = { `subject ${_visible ? 'visible' : ''}` }>
|
||||
{ _subject }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated
|
||||
* {@code Subject}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _subject: string,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { subject } = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
_subject: subject,
|
||||
_visible: isToolboxVisible(state)
|
||||
};
|
||||
}
|
||||
export default connect(_mapStateToProps)(Subject);
|
|
@ -55,6 +55,7 @@ import {
|
|||
setToolbarHovered
|
||||
} from '../../actions';
|
||||
import AudioMuteButton from '../AudioMuteButton';
|
||||
import { isToolboxVisible } from '../../functions';
|
||||
import HangupButton from '../HangupButton';
|
||||
import OverflowMenuButton from './OverflowMenuButton';
|
||||
import OverflowMenuProfileItem from './OverflowMenuProfileItem';
|
||||
|
@ -1281,11 +1282,8 @@ function _mapStateToProps(state) {
|
|||
} = state['features/base/config'];
|
||||
const sharedVideoStatus = state['features/shared-video'].status;
|
||||
const {
|
||||
alwaysVisible,
|
||||
fullScreen,
|
||||
overflowMenuVisible,
|
||||
timeoutID,
|
||||
visible
|
||||
overflowMenuVisible
|
||||
} = state['features/toolbox'];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const localRecordingStates = state['features/local-recording'];
|
||||
|
@ -1333,7 +1331,7 @@ function _mapStateToProps(state) {
|
|||
_sharingVideo: sharedVideoStatus === 'playing'
|
||||
|| sharedVideoStatus === 'start'
|
||||
|| sharedVideoStatus === 'pause',
|
||||
_visible: Boolean(timeoutID || visible || alwaysVisible),
|
||||
_visible: isToolboxVisible(state),
|
||||
|
||||
// XXX: We are not currently using state here, but in the future, when
|
||||
// interfaceConfig is part of redux we will.
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
/**
|
||||
* Returns true if the toolbox is visible.
|
||||
*
|
||||
* @param {Object | Function} stateful - A function or object that can be
|
||||
* resolved to Redux state by the function {@code toState}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isToolboxVisible(stateful: Object | Function) {
|
||||
const { alwaysVisible, enabled, visible }
|
||||
= toState(stateful)['features/toolbox'];
|
||||
|
||||
return enabled && (alwaysVisible || visible);
|
||||
}
|
|
@ -1,3 +1,17 @@
|
|||
// @flow
|
||||
|
||||
export * from './functions.any';
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
/**
|
||||
* Returns true if the toolbox is visible.
|
||||
*
|
||||
* @param {Object | Function} stateful - A function or object that can be
|
||||
* resolved to Redux state by the function {@code toState}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isToolboxVisible(stateful: Object | Function) {
|
||||
const { alwaysVisible, enabled, visible }
|
||||
= toState(stateful)['features/toolbox'];
|
||||
|
||||
return enabled && (alwaysVisible || visible);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
// @flow
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
|
@ -26,3 +24,21 @@ export function getToolboxHeight() {
|
|||
export function isButtonEnabled(name: string) {
|
||||
return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indicates if the toolbox is visible or not.
|
||||
*
|
||||
* @param {string} state - The state from the Redux store.
|
||||
* @returns {boolean} - True to indicate that the toolbox is visible, false -
|
||||
* otherwise.
|
||||
*/
|
||||
export function isToolboxVisible(state: Object) {
|
||||
const {
|
||||
alwaysVisible,
|
||||
timeoutID,
|
||||
visible
|
||||
} = state['features/toolbox'];
|
||||
|
||||
return Boolean(timeoutID || visible || alwaysVisible);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue