feat(callee-info): Redesign.
This commit is contained in:
parent
485ff81443
commit
769e782c6f
|
@ -1662,6 +1662,7 @@ export default {
|
||||||
const displayName = user.getDisplayName();
|
const displayName = user.getDisplayName();
|
||||||
|
|
||||||
APP.store.dispatch(participantJoined({
|
APP.store.dispatch(participantJoined({
|
||||||
|
botType: user.getBotType(),
|
||||||
conference: room,
|
conference: room,
|
||||||
id,
|
id,
|
||||||
name: displayName,
|
name: displayName,
|
||||||
|
@ -1862,6 +1863,17 @@ export default {
|
||||||
APP.UI.changeDisplayName(id, formattedDisplayName);
|
APP.UI.changeDisplayName(id, formattedDisplayName);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
room.on(
|
||||||
|
JitsiConferenceEvents.BOT_TYPE_CHANGED,
|
||||||
|
(id, botType) => {
|
||||||
|
|
||||||
|
APP.store.dispatch(participantUpdated({
|
||||||
|
conference: room,
|
||||||
|
id,
|
||||||
|
botType
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
room.on(
|
room.on(
|
||||||
JitsiConferenceEvents.LOCK_STATE_CHANGED,
|
JitsiConferenceEvents.LOCK_STATE_CHANGED,
|
||||||
|
|
|
@ -6,12 +6,10 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: $ringingZ;
|
z-index: $ringingZ;
|
||||||
background: linear-gradient(transparent, #000);
|
@include transparentBg(#283447, 0.95);
|
||||||
opacity: 0.8;
|
|
||||||
|
|
||||||
&.solidBG {
|
&.solidBG {
|
||||||
background: $defaultBackground;
|
background: $defaultBackground;
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
|
@ -22,20 +20,26 @@
|
||||||
top: 50%;
|
top: 50%;
|
||||||
margin-left: -200px;
|
margin-left: -200px;
|
||||||
margin-top: -125px;
|
margin-top: -125px;
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__avatar {
|
&__avatar {
|
||||||
width: 100px;
|
width: 128px;
|
||||||
height: 100px;
|
height: 128px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
border: 2px solid #1B2638;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__caller-info {
|
&__status{
|
||||||
.mention {
|
margin-top: 15px;
|
||||||
color: #333;
|
font-size: 14px;
|
||||||
}
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 32px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -632,12 +632,12 @@
|
||||||
},
|
},
|
||||||
"presenceStatus": {
|
"presenceStatus": {
|
||||||
"invited": "Invited",
|
"invited": "Invited",
|
||||||
"ringing": "Ringing",
|
"ringing": "Ringing...",
|
||||||
"calling": "Calling",
|
"calling": "Calling...",
|
||||||
"initializingCall": "Initializing Call",
|
"initializingCall": "Initializing Call...",
|
||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
"connecting": "Connecting",
|
"connecting": "Connecting...",
|
||||||
"connecting2": "Connecting*",
|
"connecting2": "Connecting*...",
|
||||||
"disconnected": "Disconnected",
|
"disconnected": "Disconnected",
|
||||||
"busy": "Busy",
|
"busy": "Busy",
|
||||||
"rejected": "Rejected",
|
"rejected": "Rejected",
|
||||||
|
|
|
@ -113,8 +113,10 @@ function initCommands() {
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'invite':
|
case 'invite':
|
||||||
|
// The store should be already available because API.init is called
|
||||||
|
// on appWillMount action.
|
||||||
APP.store.dispatch(
|
APP.store.dispatch(
|
||||||
invite(request.invitees))
|
invite(request.invitees, true))
|
||||||
.then(failedInvitees => {
|
.then(failedInvitees => {
|
||||||
let error;
|
let error;
|
||||||
let result;
|
let result;
|
||||||
|
|
|
@ -238,7 +238,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
this._invitees = invitees;
|
this.invite(invitees);
|
||||||
this._isLargeVideoVisible = true;
|
this._isLargeVideoVisible = true;
|
||||||
this._numberOfParticipants = 0;
|
this._numberOfParticipants = 0;
|
||||||
this._participants = {};
|
this._participants = {};
|
||||||
|
@ -369,9 +369,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'video-conference-joined':
|
case 'video-conference-joined':
|
||||||
if (this._invitees) {
|
|
||||||
this.invite(this._invitees);
|
|
||||||
}
|
|
||||||
this._myUserID = userID;
|
this._myUserID = userID;
|
||||||
this._participants[userID] = {
|
this._participants[userID] = {
|
||||||
avatarURL: data.avatarURL
|
avatarURL: data.avatarURL
|
||||||
|
|
|
@ -492,7 +492,10 @@ UI.updateUserRole = user => {
|
||||||
* @param {string} status - The new status.
|
* @param {string} status - The new status.
|
||||||
*/
|
*/
|
||||||
UI.updateUserStatus = (user, status) => {
|
UI.updateUserStatus = (user, status) => {
|
||||||
if (!status) {
|
const reduxState = APP.store.getState() || {};
|
||||||
|
const { calleeInfoVisible } = reduxState['features/invite'] || {};
|
||||||
|
|
||||||
|
if (!status || calleeInfoVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -428,7 +428,12 @@ export default class LargeVideoManager {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store = { APP.store }>
|
<Provider store = { APP.store }>
|
||||||
<I18nextProvider i18n = { i18next }>
|
<I18nextProvider i18n = { i18next }>
|
||||||
<PresenceLabel participantID = { id } />
|
<PresenceLabel
|
||||||
|
noContentStyles = { {
|
||||||
|
className: 'presence-label no-presence'
|
||||||
|
} }
|
||||||
|
participantID = { id }
|
||||||
|
styles = { { className: 'presence-label' } } />
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
presenceLabelContainer.get(0));
|
presenceLabelContainer.get(0));
|
||||||
|
|
|
@ -573,7 +573,12 @@ RemoteVideo.prototype.addPresenceLabel = function() {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store = { APP.store }>
|
<Provider store = { APP.store }>
|
||||||
<I18nextProvider i18n = { i18next }>
|
<I18nextProvider i18n = { i18next }>
|
||||||
<PresenceLabel participantID = { this.id } />
|
<PresenceLabel
|
||||||
|
noContentStyles = { {
|
||||||
|
className: 'presence-label no-presence'
|
||||||
|
} }
|
||||||
|
participantID = { this.id }
|
||||||
|
styles = { { className: 'presence-label' } } />
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
presenceLabelContainer);
|
presenceLabelContainer);
|
||||||
|
|
|
@ -144,6 +144,7 @@ function _addConferenceListeners(conference, dispatch) {
|
||||||
conference.on(
|
conference.on(
|
||||||
JitsiConferenceEvents.USER_JOINED,
|
JitsiConferenceEvents.USER_JOINED,
|
||||||
(id, user) => !user.isHidden() && dispatch(participantJoined({
|
(id, user) => !user.isHidden() && dispatch(participantJoined({
|
||||||
|
botType: user.getBotType(),
|
||||||
conference,
|
conference,
|
||||||
id,
|
id,
|
||||||
name: user.getDisplayName(),
|
name: user.getDisplayName(),
|
||||||
|
@ -161,6 +162,14 @@ function _addConferenceListeners(conference, dispatch) {
|
||||||
JitsiConferenceEvents.USER_STATUS_CHANGED,
|
JitsiConferenceEvents.USER_STATUS_CHANGED,
|
||||||
(...args) => dispatch(participantPresenceChanged(...args)));
|
(...args) => dispatch(participantPresenceChanged(...args)));
|
||||||
|
|
||||||
|
conference.on(
|
||||||
|
JitsiConferenceEvents.BOT_TYPE_CHANGED,
|
||||||
|
(id, botType) => dispatch(participantUpdated({
|
||||||
|
conference,
|
||||||
|
id,
|
||||||
|
botType
|
||||||
|
})));
|
||||||
|
|
||||||
conference.addCommandListener(
|
conference.addCommandListener(
|
||||||
AVATAR_ID_COMMAND,
|
AVATAR_ID_COMMAND,
|
||||||
(data, id) => dispatch(participantUpdated({
|
(data, id) => dispatch(participantUpdated({
|
||||||
|
|
|
@ -1,13 +1,3 @@
|
||||||
/**
|
|
||||||
* The type of redux action which sets the visibility of {@code CalleeInfo}.
|
|
||||||
*
|
|
||||||
* {
|
|
||||||
* type: SET_CALLEE_INFO_VISIBLE,
|
|
||||||
* calleeInfoVisible: boolean
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export const SET_CALLEE_INFO_VISIBLE = Symbol('SET_CALLEE_INFO_VISIBLE');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of redux action which stores a specific JSON Web Token (JWT) into
|
* The type of redux action which stores a specific JSON Web Token (JWT) into
|
||||||
* the redux store.
|
* the redux store.
|
||||||
|
|
|
@ -1,28 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { SET_CALLEE_INFO_VISIBLE, SET_JWT } from './actionTypes';
|
import { SET_JWT } from './actionTypes';
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the visibility of {@code CalleeInfo}.
|
|
||||||
*
|
|
||||||
* @param {boolean|undefined} [calleeInfoVisible] - If {@code CalleeInfo} is
|
|
||||||
* to be displayed/visible, then {@code true}; otherwise, {@code false} or
|
|
||||||
* {@code undefined}.
|
|
||||||
* @returns {{
|
|
||||||
* type: SET_CALLEE_INFO_VISIBLE,
|
|
||||||
* calleeInfoVisible: (boolean|undefined)
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
export function setCalleeInfoVisible(calleeInfoVisible: ?boolean) {
|
|
||||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
|
||||||
getState()['features/base/jwt']
|
|
||||||
.calleeInfoVisible === calleeInfoVisible
|
|
||||||
|| dispatch({
|
|
||||||
type: SET_CALLEE_INFO_VISIBLE,
|
|
||||||
calleeInfoVisible
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a specific JSON Web Token (JWT) into the redux store.
|
* Stores a specific JSON Web Token (JWT) into the redux store.
|
||||||
|
|
|
@ -1,347 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { Audio } from '../../media';
|
|
||||||
import { Avatar } from '../../participants';
|
|
||||||
import { Container, Text } from '../../react';
|
|
||||||
import UIEvents from '../../../../../service/UI/UIEvents';
|
|
||||||
|
|
||||||
import styles from './styles';
|
|
||||||
|
|
||||||
declare var $: Object;
|
|
||||||
declare var APP: Object;
|
|
||||||
declare var interfaceConfig: Object;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the React {@code Component} props of {@link CalleeInfo}.
|
|
||||||
*/
|
|
||||||
type Props = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The callee's information such as avatar and display name.
|
|
||||||
*/
|
|
||||||
_callee: Object
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the React {@code Component} state of {@link CalleeInfo}.
|
|
||||||
*/
|
|
||||||
type State = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The CSS class (name), if any, to add to this {@code CalleeInfo}.
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
className: ?string,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The indicator which determines whether this {@code CalleeInfo}
|
|
||||||
* should play/render audio to indicate the ringing phase of the
|
|
||||||
* call establishment between the local participant and the
|
|
||||||
* associated remote callee.
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
renderAudio: boolean,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The indicator which determines whether this {@code CalleeInfo}
|
|
||||||
* is depicting the ringing phase of the call establishment between
|
|
||||||
* the local participant and the associated remote callee or the
|
|
||||||
* phase afterwards when the callee has not answered the call for a
|
|
||||||
* period of time and, consequently, is considered unavailable.
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
ringing: boolean
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements a React {@link Component} which depicts the establishment of a
|
|
||||||
* call with a specific remote callee.
|
|
||||||
*
|
|
||||||
* @extends Component
|
|
||||||
*/
|
|
||||||
class CalleeInfo extends Component<Props, State> {
|
|
||||||
/**
|
|
||||||
* The (reference to the) {@link Audio} which plays/renders the audio
|
|
||||||
* depicting the ringing phase of the call establishment represented by this
|
|
||||||
* {@code CalleeInfo}.
|
|
||||||
*/
|
|
||||||
_audio: ?Audio;
|
|
||||||
|
|
||||||
_onLargeVideoAvatarVisible: Function;
|
|
||||||
|
|
||||||
_playAudioInterval: ?IntervalID;
|
|
||||||
|
|
||||||
_ringingTimeout: ?TimeoutID;
|
|
||||||
|
|
||||||
_setAudio: Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a new {@code CalleeInfo} instance.
|
|
||||||
*
|
|
||||||
* @param {Object} props - The read-only React {@link Component} props with
|
|
||||||
* which the new instance is to be initialized.
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
className: undefined,
|
|
||||||
renderAudio:
|
|
||||||
typeof interfaceConfig !== 'object'
|
|
||||||
|| !interfaceConfig.DISABLE_RINGING,
|
|
||||||
ringing: true
|
|
||||||
};
|
|
||||||
|
|
||||||
this._onLargeVideoAvatarVisible
|
|
||||||
= this._onLargeVideoAvatarVisible.bind(this);
|
|
||||||
this._setAudio = this._setAudio.bind(this);
|
|
||||||
|
|
||||||
if (typeof APP === 'object') {
|
|
||||||
APP.UI.addListener(
|
|
||||||
UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
|
|
||||||
this._onLargeVideoAvatarVisible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up timeouts such as the timeout to end the ringing phase of the call
|
|
||||||
* establishment depicted by this {@code CalleeInfo}.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
// Set up the timeout to end the ringing phase of the call establishment
|
|
||||||
// depicted by this CalleeInfo.
|
|
||||||
if (this.state.ringing && !this._ringingTimeout) {
|
|
||||||
this._ringingTimeout
|
|
||||||
= setTimeout(
|
|
||||||
() => {
|
|
||||||
this._pauseAudio();
|
|
||||||
|
|
||||||
this._ringingTimeout = undefined;
|
|
||||||
this.setState({
|
|
||||||
ringing: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
30000);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._playAudio();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up before this {@code Calleverlay} is unmounted and destroyed.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
|
||||||
this._pauseAudio();
|
|
||||||
|
|
||||||
if (this._ringingTimeout) {
|
|
||||||
clearTimeout(this._ringingTimeout);
|
|
||||||
this._ringingTimeout = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof APP === 'object') {
|
|
||||||
APP.UI.removeListener(
|
|
||||||
UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
|
|
||||||
this._onLargeVideoAvatarVisible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements React's {@link Component#render()}.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const { className, ringing } = this.state;
|
|
||||||
const { avatarUrl, avatar, name } = this.props._callee;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container
|
|
||||||
{ ...this._style('ringing', className) }
|
|
||||||
id = 'ringOverlay'>
|
|
||||||
<Container
|
|
||||||
{ ...this._style('ringing__content') }>
|
|
||||||
<Text { ...this._style('ringing__text') }>
|
|
||||||
{ ringing ? 'Calling...' : '' }
|
|
||||||
</Text>
|
|
||||||
<Avatar
|
|
||||||
{ ...this._style('ringing__avatar') }
|
|
||||||
uri = { avatarUrl || avatar } />
|
|
||||||
<Container
|
|
||||||
{ ...this._style('ringing__caller-info') }>
|
|
||||||
<Text
|
|
||||||
{ ...this._style('ringing__text') }>
|
|
||||||
{ name }
|
|
||||||
{ ringing ? '' : ' isn\'t available' }
|
|
||||||
</Text>
|
|
||||||
</Container>
|
|
||||||
</Container>
|
|
||||||
{ this._renderAudio() }
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies this {@code CalleeInfo} that the visibility of the
|
|
||||||
* participant's avatar in the large video has changed.
|
|
||||||
*
|
|
||||||
* @param {boolean} largeVideoAvatarVisible - If the avatar in the large
|
|
||||||
* video (i.e. of the participant on the stage) is visible, then
|
|
||||||
* {@code true}; otherwise, {@code false}.
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onLargeVideoAvatarVisible(largeVideoAvatarVisible: boolean) {
|
|
||||||
this.setState({
|
|
||||||
className: largeVideoAvatarVisible ? 'solidBG' : undefined
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the playback of the audio which represents the ringing phase of the
|
|
||||||
* call establishment depicted by this {@code CalleeInfo}.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_pauseAudio() {
|
|
||||||
const audio = this._audio;
|
|
||||||
|
|
||||||
if (audio) {
|
|
||||||
audio.pause();
|
|
||||||
}
|
|
||||||
if (this._playAudioInterval) {
|
|
||||||
clearInterval(this._playAudioInterval);
|
|
||||||
this._playAudioInterval = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the playback of the audio which represents the ringing phase of
|
|
||||||
* the call establishment depicted by this {@code CalleeInfo}.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_playAudio() {
|
|
||||||
if (this._audio) {
|
|
||||||
this._audio.play();
|
|
||||||
if (!this._playAudioInterval) {
|
|
||||||
this._playAudioInterval
|
|
||||||
= setInterval(() => this._playAudio(), 5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders an audio element to represent the ringing phase of the call
|
|
||||||
* establishment represented by this {@code CalleeInfo}.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
_renderAudio() {
|
|
||||||
if (this.state.renderAudio && this.state.ringing) {
|
|
||||||
return (
|
|
||||||
<Audio
|
|
||||||
setRef = { this._setAudio }
|
|
||||||
src = './sounds/ring.ogg' />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the (reference to the) {@link Audio} which renders the ringing phase
|
|
||||||
* of the call establishment represented by this {@code CalleeInfo}.
|
|
||||||
*
|
|
||||||
* @param {Audio} audio - The (reference to the) {@code Audio} which
|
|
||||||
* plays/renders the audio depicting the ringing phase of the call
|
|
||||||
* establishment represented by this {@code CalleeInfo}.
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_setAudio(audio) {
|
|
||||||
this._audio = audio;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to convert specified CSS class names into React
|
|
||||||
* {@link Component} props {@code style} or {@code className}.
|
|
||||||
*
|
|
||||||
* @param {Array<string>} classNames - The CSS class names to convert
|
|
||||||
* into React {@code Component} props {@code style} or {@code className}.
|
|
||||||
* @returns {{
|
|
||||||
* className: string,
|
|
||||||
* style: Object
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
_style(...classNames: Array<?string>) {
|
|
||||||
let className = '';
|
|
||||||
let style;
|
|
||||||
|
|
||||||
for (const aClassName of classNames) {
|
|
||||||
if (aClassName) {
|
|
||||||
// Attemp to convert aClassName into style.
|
|
||||||
if (styles && aClassName in styles) {
|
|
||||||
// React Native will accept an Array as the value of the
|
|
||||||
// style prop. However, I do not know about React.
|
|
||||||
style = {
|
|
||||||
...style,
|
|
||||||
...styles[aClassName]
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Otherwise, leave it as className.
|
|
||||||
className += aClassName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose which of the className and/or style props has a value and,
|
|
||||||
// consequently, must be returned.
|
|
||||||
const props = {};
|
|
||||||
|
|
||||||
if (className) {
|
|
||||||
props.className = className;
|
|
||||||
}
|
|
||||||
if (style) {
|
|
||||||
props.style = style;
|
|
||||||
}
|
|
||||||
|
|
||||||
return props;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps (parts of) the redux state to {@code CalleeInfo}'s props.
|
|
||||||
*
|
|
||||||
* @param {Object} state - The redux state.
|
|
||||||
* @private
|
|
||||||
* @returns {{
|
|
||||||
* _callee: Object
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
function _mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* The callee's information such as avatar and display name.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
_callee: state['features/base/jwt'].callee
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(_mapStateToProps)(CalleeInfo);
|
|
|
@ -1,6 +1,5 @@
|
||||||
export * from './actions';
|
export * from './actions';
|
||||||
export * from './actionTypes';
|
export * from './actionTypes';
|
||||||
export * from './components';
|
|
||||||
export * from './functions';
|
export * from './functions';
|
||||||
|
|
||||||
import './middleware';
|
import './middleware';
|
||||||
|
|
|
@ -2,24 +2,15 @@
|
||||||
|
|
||||||
import jwtDecode from 'jwt-decode';
|
import jwtDecode from 'jwt-decode';
|
||||||
|
|
||||||
import {
|
|
||||||
CONFERENCE_FAILED,
|
|
||||||
CONFERENCE_LEFT,
|
|
||||||
CONFERENCE_WILL_LEAVE,
|
|
||||||
SET_ROOM
|
|
||||||
} from '../conference';
|
|
||||||
import { SET_CONFIG } from '../config';
|
import { SET_CONFIG } from '../config';
|
||||||
import { SET_LOCATION_URL } from '../connection';
|
import { SET_LOCATION_URL } from '../connection';
|
||||||
import { LIB_INIT_ERROR } from '../lib-jitsi-meet';
|
|
||||||
import {
|
import {
|
||||||
getLocalParticipant,
|
getLocalParticipant,
|
||||||
getParticipantCount,
|
|
||||||
PARTICIPANT_JOINED,
|
|
||||||
participantUpdated
|
participantUpdated
|
||||||
} from '../participants';
|
} from '../participants';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
|
||||||
import { setCalleeInfoVisible, setJWT } from './actions';
|
import { setJWT } from './actions';
|
||||||
import { SET_JWT } from './actionTypes';
|
import { SET_JWT } from './actionTypes';
|
||||||
import { parseJWTFromURLParams } from './functions';
|
import { parseJWTFromURLParams } from './functions';
|
||||||
|
|
||||||
|
@ -34,14 +25,6 @@ declare var APP: Object;
|
||||||
*/
|
*/
|
||||||
MiddlewareRegistry.register(store => next => action => {
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONFERENCE_FAILED:
|
|
||||||
case CONFERENCE_LEFT:
|
|
||||||
case CONFERENCE_WILL_LEAVE:
|
|
||||||
case LIB_INIT_ERROR:
|
|
||||||
case PARTICIPANT_JOINED:
|
|
||||||
case SET_ROOM:
|
|
||||||
return _maybeSetCalleeInfoVisible(store, next, action);
|
|
||||||
|
|
||||||
case SET_CONFIG:
|
case SET_CONFIG:
|
||||||
case SET_LOCATION_URL:
|
case SET_LOCATION_URL:
|
||||||
// XXX The JSON Web Token (JWT) is not the only piece of state that we
|
// XXX The JSON Web Token (JWT) is not the only piece of state that we
|
||||||
|
@ -58,73 +41,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
return next(action);
|
return next(action);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the feature jwt that a specific {@code action} is being dispatched
|
|
||||||
* within a specific redux {@code store} which may have an effect on the
|
|
||||||
* visiblity of (the) {@code CalleeInfo}.
|
|
||||||
*
|
|
||||||
* @param {Store} store - The redux store in which the specified {@code action}
|
|
||||||
* is being dispatched.
|
|
||||||
* @param {Dispatch} next - The redux 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 new state that is the result of the reduction of the
|
|
||||||
* specified {@code action}.
|
|
||||||
*/
|
|
||||||
function _maybeSetCalleeInfoVisible({ dispatch, getState }, next, action) {
|
|
||||||
const result = next(action);
|
|
||||||
|
|
||||||
const state = getState();
|
|
||||||
const stateFeaturesBaseJWT = state['features/base/jwt'];
|
|
||||||
let calleeInfoVisible;
|
|
||||||
|
|
||||||
if (stateFeaturesBaseJWT.callee) {
|
|
||||||
const { conference, leaving, room } = state['features/base/conference'];
|
|
||||||
|
|
||||||
// XXX The CalleeInfo is to be displayed/visible as soon as possible
|
|
||||||
// including even before the conference is joined.
|
|
||||||
if (room && (!conference || conference !== leaving)) {
|
|
||||||
switch (action.type) {
|
|
||||||
case CONFERENCE_FAILED:
|
|
||||||
case CONFERENCE_LEFT:
|
|
||||||
case CONFERENCE_WILL_LEAVE:
|
|
||||||
case LIB_INIT_ERROR:
|
|
||||||
// Because the CalleeInfo is to be displayed/visible as soon as
|
|
||||||
// possible even before the connection is established and/or the
|
|
||||||
// conference is joined, it is very complicated to figure out
|
|
||||||
// based on the current state alone. In order to reduce the
|
|
||||||
// risks of displaying the CallOverly at inappropirate times, do
|
|
||||||
// not even attempt to figure out based on the current state.
|
|
||||||
// The (redux) actions listed above are also the equivalents of
|
|
||||||
// the execution ponints at which APP.UI.hideRingOverlay() used
|
|
||||||
// to be invoked.
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: {
|
|
||||||
// The CalleeInfo is to no longer be displayed/visible as soon
|
|
||||||
// as another participant joins.
|
|
||||||
calleeInfoVisible
|
|
||||||
= getParticipantCount(state) === 1
|
|
||||||
&& Boolean(getLocalParticipant(state));
|
|
||||||
|
|
||||||
// However, the CallDialog is not to be displayed/visible again
|
|
||||||
// after all remote participants leave.
|
|
||||||
if (calleeInfoVisible
|
|
||||||
&& stateFeaturesBaseJWT.calleeInfoVisible === false) {
|
|
||||||
calleeInfoVisible = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dispatch(setCalleeInfoVisible(calleeInfoVisible));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overwrites the properties {@code avatarURL}, {@code email}, and {@code name}
|
* Overwrites the properties {@code avatarURL}, {@code email}, and {@code name}
|
||||||
* of the local participant stored in the redux state base/participants.
|
* of the local participant stored in the redux state base/participants.
|
||||||
|
@ -248,7 +164,7 @@ function _setJWT(store, next, action) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _maybeSetCalleeInfoVisible(store, next, action);
|
return next(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,27 +1,18 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { equals, set, ReducerRegistry } from '../redux';
|
import { equals, ReducerRegistry } from '../redux';
|
||||||
|
|
||||||
import { SET_CALLEE_INFO_VISIBLE, SET_JWT } from './actionTypes';
|
import { SET_JWT } from './actionTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default/initial redux state of the feature jwt.
|
* The default/initial redux state of the feature jwt.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @type {{
|
* @type {{
|
||||||
* calleeInfoVisible: ?boolean
|
|
||||||
* isGuest: boolean
|
* isGuest: boolean
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
const DEFAULT_STATE = {
|
const DEFAULT_STATE = {
|
||||||
/**
|
|
||||||
* The indicator which determines whether (the) {@code CalleeInfo} is
|
|
||||||
* visible.
|
|
||||||
*
|
|
||||||
* @type {boolean|undefined}
|
|
||||||
*/
|
|
||||||
calleeInfoVisible: undefined,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The indicator which determines whether the local participant is a guest
|
* The indicator which determines whether the local participant is a guest
|
||||||
* in the conference.
|
* in the conference.
|
||||||
|
@ -44,9 +35,6 @@ ReducerRegistry.register(
|
||||||
'features/base/jwt',
|
'features/base/jwt',
|
||||||
(state = DEFAULT_STATE, action) => {
|
(state = DEFAULT_STATE, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SET_CALLEE_INFO_VISIBLE:
|
|
||||||
return set(state, 'calleeInfoVisible', action.calleeInfoVisible);
|
|
||||||
|
|
||||||
case SET_JWT: {
|
case SET_JWT: {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { type, ...payload } = action;
|
const { type, ...payload } = action;
|
||||||
|
|
|
@ -180,6 +180,7 @@ function _participant(state: Object = {}, action) {
|
||||||
function _participantJoined({ participant }) {
|
function _participantJoined({ participant }) {
|
||||||
const {
|
const {
|
||||||
avatarURL,
|
avatarURL,
|
||||||
|
botType,
|
||||||
connectionStatus,
|
connectionStatus,
|
||||||
dominantSpeaker,
|
dominantSpeaker,
|
||||||
email,
|
email,
|
||||||
|
@ -212,6 +213,7 @@ function _participantJoined({ participant }) {
|
||||||
return {
|
return {
|
||||||
avatarID,
|
avatarID,
|
||||||
avatarURL,
|
avatarURL,
|
||||||
|
botType,
|
||||||
conference,
|
conference,
|
||||||
connectionStatus,
|
connectionStatus,
|
||||||
dominantSpeaker: dominantSpeaker || false,
|
dominantSpeaker: dominantSpeaker || false,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { connect as reactReduxConnect } from 'react-redux';
|
||||||
import { appNavigate } from '../../app';
|
import { appNavigate } from '../../app';
|
||||||
import { connect, disconnect } from '../../base/connection';
|
import { connect, disconnect } from '../../base/connection';
|
||||||
import { DialogContainer } from '../../base/dialog';
|
import { DialogContainer } from '../../base/dialog';
|
||||||
import { CalleeInfoContainer } from '../../base/jwt';
|
import { CalleeInfoContainer } from '../../invite';
|
||||||
import { getParticipantCount } from '../../base/participants';
|
import { getParticipantCount } from '../../base/participants';
|
||||||
import { Container, LoadingIndicator, TintedView } from '../../base/react';
|
import { Container, LoadingIndicator, TintedView } from '../../base/react';
|
||||||
import { TestConnectionInfo } from '../../base/testing';
|
import { TestConnectionInfo } from '../../base/testing';
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { connect as reactReduxConnect } from 'react-redux';
|
||||||
import { connect, disconnect } from '../../base/connection';
|
import { connect, disconnect } from '../../base/connection';
|
||||||
import { DialogContainer } from '../../base/dialog';
|
import { DialogContainer } from '../../base/dialog';
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
import { CalleeInfoContainer } from '../../base/jwt';
|
|
||||||
import { Filmstrip } from '../../filmstrip';
|
import { Filmstrip } from '../../filmstrip';
|
||||||
|
import { CalleeInfoContainer } from '../../invite';
|
||||||
import { LargeVideo } from '../../large-video';
|
import { LargeVideo } from '../../large-video';
|
||||||
import { NotificationsContainer } from '../../notifications';
|
import { NotificationsContainer } from '../../notifications';
|
||||||
import { SidePanel } from '../../side-panel';
|
import { SidePanel } from '../../side-panel';
|
||||||
|
|
|
@ -32,6 +32,10 @@ export function isFilmstripVisible(stateful: Object | Function) {
|
||||||
* in the filmstrip, then {@code true}; otherwise, {@code false}.
|
* in the filmstrip, then {@code true}; otherwise, {@code false}.
|
||||||
*/
|
*/
|
||||||
export function shouldRemoteVideosBeVisible(state: Object) {
|
export function shouldRemoteVideosBeVisible(state: Object) {
|
||||||
|
if (state['features/invite'].calleeInfoVisible) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const participantCount = getParticipantCount(state);
|
const participantCount = getParticipantCount(state);
|
||||||
let pinnedParticipant;
|
let pinnedParticipant;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { setLastN } from '../base/conference';
|
import { setLastN } from '../base/conference';
|
||||||
import { SET_CALLEE_INFO_VISIBLE } from '../base/jwt';
|
|
||||||
import { pinParticipant } from '../base/participants';
|
import { pinParticipant } from '../base/participants';
|
||||||
import { MiddlewareRegistry } from '../base/redux';
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
|
|
||||||
|
|
||||||
import { SET_FILMSTRIP_ENABLED } from './actionTypes';
|
import { SET_FILMSTRIP_ENABLED } from './actionTypes';
|
||||||
|
|
||||||
|
@ -12,9 +10,6 @@ declare var APP: Object;
|
||||||
|
|
||||||
MiddlewareRegistry.register(store => next => action => {
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SET_CALLEE_INFO_VISIBLE:
|
|
||||||
return _setCalleeInfoVisible(store, next, action);
|
|
||||||
|
|
||||||
case SET_FILMSTRIP_ENABLED:
|
case SET_FILMSTRIP_ENABLED:
|
||||||
return _setFilmstripEnabled(store, next, action);
|
return _setFilmstripEnabled(store, next, action);
|
||||||
}
|
}
|
||||||
|
@ -22,42 +17,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
return next(action);
|
return next(action);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the feature filmstrip that the action
|
|
||||||
* {@link SET_CALLEE_INFO_VISIBLE} is being dispatched within a specific redux
|
|
||||||
* store.
|
|
||||||
*
|
|
||||||
* @param {Store} store - The redux store in which the specified action is being
|
|
||||||
* dispatched.
|
|
||||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
|
||||||
* specified action to the specified store.
|
|
||||||
* @param {Action} action - The redux action {@code SET_CALLEE_INFO_VISIBLE}
|
|
||||||
* which is being dispatched in the specified store.
|
|
||||||
* @private
|
|
||||||
* @returns {Object} The value returned by {@code next(action)}.
|
|
||||||
*/
|
|
||||||
function _setCalleeInfoVisible({ getState }, next, action) {
|
|
||||||
if (typeof APP !== 'undefined') {
|
|
||||||
const oldValue
|
|
||||||
= Boolean(getState()['features/base/jwt'].calleeInfoVisible);
|
|
||||||
const result = next(action);
|
|
||||||
const newValue
|
|
||||||
= Boolean(getState()['features/base/jwt'].calleeInfoVisible);
|
|
||||||
|
|
||||||
oldValue === newValue
|
|
||||||
|
|
||||||
// FIXME The following accesses the private state filmstrip of
|
|
||||||
// Filmstrip. It is written with the understanding that Filmstrip
|
|
||||||
// will be rewritten in React and, consequently, will not need the
|
|
||||||
// middleware implemented here, Filmstrip.init, and UI.start.
|
|
||||||
|| (Filmstrip.filmstrip && Filmstrip.toggleFilmstrip(!newValue));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the feature filmstrip that the action {@link SET_FILMSTRIP_ENABLED}
|
* Notifies the feature filmstrip that the action {@link SET_FILMSTRIP_ENABLED}
|
||||||
* is being dispatched within a specific redux store.
|
* is being dispatched within a specific redux store.
|
||||||
|
|
|
@ -1,14 +1,3 @@
|
||||||
/**
|
|
||||||
* The type of the (redux) action which signals that a click/tap has been
|
|
||||||
* performed on {@link InviteButton} and that the execution flow for
|
|
||||||
* adding/inviting people to the current conference/meeting is to begin.
|
|
||||||
*
|
|
||||||
* {
|
|
||||||
* type: BEGIN_ADD_PEOPLE
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export const BEGIN_ADD_PEOPLE = Symbol('BEGIN_ADD_PEOPLE');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of redux action to set the {@code EventEmitter} subscriptions
|
* The type of redux action to set the {@code EventEmitter} subscriptions
|
||||||
* utilized by the feature invite.
|
* utilized by the feature invite.
|
||||||
|
@ -22,6 +11,49 @@ export const BEGIN_ADD_PEOPLE = Symbol('BEGIN_ADD_PEOPLE');
|
||||||
*/
|
*/
|
||||||
export const _SET_EMITTER_SUBSCRIPTIONS = Symbol('_SET_EMITTER_SUBSCRIPTIONS');
|
export const _SET_EMITTER_SUBSCRIPTIONS = Symbol('_SET_EMITTER_SUBSCRIPTIONS');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of redux action which will add pending invite request to the redux
|
||||||
|
* store.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: ADD_PENDING_INVITE_REQUEST,
|
||||||
|
* request: Object
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const ADD_PENDING_INVITE_REQUEST = Symbol('ADD_PENDING_INVITE_REQUEST');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the (redux) action which signals that a click/tap has been
|
||||||
|
* performed on {@link InviteButton} and that the execution flow for
|
||||||
|
* adding/inviting people to the current conference/meeting is to begin.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: BEGIN_ADD_PEOPLE
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const BEGIN_ADD_PEOPLE = Symbol('BEGIN_ADD_PEOPLE');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of redux action which will remove pending invite requests from the
|
||||||
|
* redux store.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: REMOVE_PENDING_INVITE_REQUESTS
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const REMOVE_PENDING_INVITE_REQUESTS
|
||||||
|
= Symbol('REMOVE_PENDING_INVITE_REQUESTS');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of redux action which sets the visibility of {@code CalleeInfo}.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_CALLEE_INFO_VISIBLE,
|
||||||
|
* calleeInfoVisible: boolean
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_CALLEE_INFO_VISIBLE = Symbol('SET_CALLEE_INFO_VISIBLE');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the action which signals an error occurred while requesting dial-
|
* The type of the action which signals an error occurred while requesting dial-
|
||||||
* in numbers.
|
* in numbers.
|
||||||
|
|
|
@ -2,9 +2,13 @@
|
||||||
|
|
||||||
import { getInviteURL } from '../base/connection';
|
import { getInviteURL } from '../base/connection';
|
||||||
import { inviteVideoRooms } from '../videosipgw';
|
import { inviteVideoRooms } from '../videosipgw';
|
||||||
|
import { getParticipants } from '../base/participants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ADD_PENDING_INVITE_REQUEST,
|
||||||
BEGIN_ADD_PEOPLE,
|
BEGIN_ADD_PEOPLE,
|
||||||
|
REMOVE_PENDING_INVITE_REQUESTS,
|
||||||
|
SET_CALLEE_INFO_VISIBLE,
|
||||||
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
||||||
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
@ -31,23 +35,51 @@ export function beginAddPeople() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invites (i.e. sends invites to) an array of invitees (which may be a
|
* Invites (i.e. sends invites to) an array of invitees (which may be a
|
||||||
* combination of users, rooms, phone numbers, and video rooms).
|
* combination of users, rooms, phone numbers, and video rooms).
|
||||||
*
|
*
|
||||||
* @param {Array<Object>} invitees - The recepients to send invites to.
|
* @param {Array<Object>} invitees - The recepients to send invites to.
|
||||||
|
* @param {Array<Object>} showCalleeInfo - Indicates whether the
|
||||||
|
* {@code CalleeInfo} should be displayed or not.
|
||||||
* @returns {Promise<Array<Object>>} A {@code Promise} resolving with an array
|
* @returns {Promise<Array<Object>>} A {@code Promise} resolving with an array
|
||||||
* of invitees who were not invited (i.e. invites were not sent to them).
|
* of invitees who were not invited (i.e. invites were not sent to them).
|
||||||
*/
|
*/
|
||||||
export function invite(invitees: Array<Object>) {
|
export function invite(
|
||||||
|
invitees: Array<Object>,
|
||||||
|
showCalleeInfo: boolean = false) {
|
||||||
return (
|
return (
|
||||||
dispatch: Dispatch<*>,
|
dispatch: Dispatch<*>,
|
||||||
getState: Function): Promise<Array<Object>> => {
|
getState: Function): Promise<Array<Object>> => {
|
||||||
|
const state = getState();
|
||||||
|
const participants = getParticipants(state);
|
||||||
|
const { calleeInfoVisible } = state['features/invite'];
|
||||||
|
|
||||||
|
if (showCalleeInfo
|
||||||
|
&& !calleeInfoVisible
|
||||||
|
&& invitees.length === 1
|
||||||
|
&& invitees[0].type === 'user'
|
||||||
|
&& participants.length === 1) {
|
||||||
|
dispatch(setCalleeInfoVisible(true, invitees[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { conference } = state['features/base/conference'];
|
||||||
|
|
||||||
|
if (typeof conference === 'undefined') {
|
||||||
|
// Invite will fail before CONFERENCE_JOIN. The request will be
|
||||||
|
// cached in order to be executed on CONFERENCE_JOIN.
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dispatch(addPendingInviteRequest({
|
||||||
|
invitees,
|
||||||
|
callback: failedInvitees => resolve(failedInvitees)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let allInvitePromises = [];
|
let allInvitePromises = [];
|
||||||
let invitesLeftToSend = [ ...invitees ];
|
let invitesLeftToSend = [ ...invitees ];
|
||||||
|
|
||||||
const state = getState();
|
|
||||||
const { conference } = state['features/base/conference'];
|
|
||||||
const {
|
const {
|
||||||
callFlowsEnabled,
|
callFlowsEnabled,
|
||||||
inviteServiceUrl,
|
inviteServiceUrl,
|
||||||
|
@ -57,27 +89,25 @@ export function invite(invitees: Array<Object>) {
|
||||||
const { jwt } = state['features/base/jwt'];
|
const { jwt } = state['features/base/jwt'];
|
||||||
|
|
||||||
// First create all promises for dialing out.
|
// First create all promises for dialing out.
|
||||||
if (conference) {
|
const phoneNumbers
|
||||||
const phoneNumbers
|
= invitesLeftToSend.filter(({ type }) => type === 'phone');
|
||||||
= invitesLeftToSend.filter(({ type }) => type === 'phone');
|
|
||||||
|
|
||||||
// For each number, dial out. On success, remove the number from
|
// For each number, dial out. On success, remove the number from
|
||||||
// {@link invitesLeftToSend}.
|
// {@link invitesLeftToSend}.
|
||||||
const phoneInvitePromises = phoneNumbers.map(item => {
|
const phoneInvitePromises = phoneNumbers.map(item => {
|
||||||
const numberToInvite = item.number;
|
const numberToInvite = item.number;
|
||||||
|
|
||||||
return conference.dial(numberToInvite)
|
return conference.dial(numberToInvite)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
invitesLeftToSend
|
invitesLeftToSend
|
||||||
= invitesLeftToSend.filter(
|
= invitesLeftToSend.filter(
|
||||||
invitee => invitee !== item);
|
invitee => invitee !== item);
|
||||||
})
|
})
|
||||||
.catch(error =>
|
.catch(error =>
|
||||||
logger.error('Error inviting phone number:', error));
|
logger.error('Error inviting phone number:', error));
|
||||||
});
|
});
|
||||||
|
|
||||||
allInvitePromises = allInvitePromises.concat(phoneInvitePromises);
|
allInvitePromises = allInvitePromises.concat(phoneInvitePromises);
|
||||||
}
|
|
||||||
|
|
||||||
const usersAndRooms
|
const usersAndRooms
|
||||||
= invitesLeftToSend.filter(
|
= invitesLeftToSend.filter(
|
||||||
|
@ -98,7 +128,10 @@ export function invite(invitees: Array<Object>) {
|
||||||
= invitesLeftToSend.filter(
|
= invitesLeftToSend.filter(
|
||||||
({ type }) => type !== 'user' && type !== 'room');
|
({ type }) => type !== 'user' && type !== 'room');
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error inviting people:', error));
|
.catch(error => {
|
||||||
|
dispatch(setCalleeInfoVisible(false));
|
||||||
|
logger.error('Error inviting people:', error);
|
||||||
|
});
|
||||||
|
|
||||||
allInvitePromises.push(peopleInvitePromise);
|
allInvitePromises.push(peopleInvitePromise);
|
||||||
}
|
}
|
||||||
|
@ -163,3 +196,56 @@ export function updateDialInNumbers() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the visibility of {@code CalleeInfo}.
|
||||||
|
*
|
||||||
|
* @param {boolean|undefined} [calleeInfoVisible] - If {@code CalleeInfo} is
|
||||||
|
* to be displayed/visible, then {@code true}; otherwise, {@code false} or
|
||||||
|
* {@code undefined}.
|
||||||
|
* @param {Object|undefined} [initialCalleeInfo] - Callee information.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_CALLEE_INFO_VISIBLE,
|
||||||
|
* calleeInfoVisible: (boolean|undefined),
|
||||||
|
* initialCalleeInfo
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setCalleeInfoVisible(
|
||||||
|
calleeInfoVisible: boolean,
|
||||||
|
initialCalleeInfo: ?Object) {
|
||||||
|
return {
|
||||||
|
type: SET_CALLEE_INFO_VISIBLE,
|
||||||
|
calleeInfoVisible,
|
||||||
|
initialCalleeInfo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds pending invite request.
|
||||||
|
*
|
||||||
|
* @param {Object} request - The request.
|
||||||
|
* @returns {{
|
||||||
|
* type: ADD_PENDING_INVITE_REQUEST,
|
||||||
|
* request: Object
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function addPendingInviteRequest(
|
||||||
|
request: { invitees: Array<Object>, callback: Function }) {
|
||||||
|
return {
|
||||||
|
type: ADD_PENDING_INVITE_REQUEST,
|
||||||
|
request
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all pending invite requests.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: REMOVE_PENDING_INVITE_REQUEST
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function removePendingInviteRequests() {
|
||||||
|
return {
|
||||||
|
type: REMOVE_PENDING_INVITE_REQUESTS
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { MEDIA_TYPE } from '../../../base/media';
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
getAvatarURL,
|
||||||
|
getParticipants,
|
||||||
|
getParticipantDisplayName,
|
||||||
|
getParticipantPresenceStatus
|
||||||
|
} from '../../../base/participants';
|
||||||
|
import { Container, Text } from '../../../base/react';
|
||||||
|
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||||
|
import { CALLING, PresenceLabel } from '../../../presence-status';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the React {@code Component} props of {@link CalleeInfo}.
|
||||||
|
*/
|
||||||
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The callee's information such as avatar and display name.
|
||||||
|
*/
|
||||||
|
_callee: Object,
|
||||||
|
|
||||||
|
_isVideoMuted: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React {@link Component} which depicts the establishment of a
|
||||||
|
* call with a specific remote callee.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class CalleeInfo extends Component<Props> {
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
avatar,
|
||||||
|
name,
|
||||||
|
status = CALLING
|
||||||
|
} = this.props._callee;
|
||||||
|
const className = this.props._isVideoMuted ? 'solidBG' : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
{ ...this._style('ringing', className) }
|
||||||
|
id = 'ringOverlay'>
|
||||||
|
<Container
|
||||||
|
{ ...this._style('ringing__content') }>
|
||||||
|
<Avatar
|
||||||
|
{ ...this._style('ringing__avatar') }
|
||||||
|
uri = { avatar } />
|
||||||
|
<Container { ...this._style('ringing__status') }>
|
||||||
|
<PresenceLabel
|
||||||
|
defaultPresence = { status }
|
||||||
|
styles = { this._style('ringing__text') } />
|
||||||
|
</Container>
|
||||||
|
<Container { ...this._style('ringing__name') }>
|
||||||
|
<Text
|
||||||
|
{ ...this._style('ringing__text') }>
|
||||||
|
{ name }
|
||||||
|
</Text>
|
||||||
|
</Container>
|
||||||
|
</Container>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to convert specified CSS class names into React
|
||||||
|
* {@link Component} props {@code style} or {@code className}.
|
||||||
|
*
|
||||||
|
* @param {Array<string>} classNames - The CSS class names to convert
|
||||||
|
* into React {@code Component} props {@code style} or {@code className}.
|
||||||
|
* @returns {{
|
||||||
|
* className: string,
|
||||||
|
* style: Object
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
_style(...classNames: Array<?string>) {
|
||||||
|
let className = '';
|
||||||
|
let style;
|
||||||
|
|
||||||
|
for (const aClassName of classNames) {
|
||||||
|
if (aClassName) {
|
||||||
|
// Attemp to convert aClassName into style.
|
||||||
|
if (styles && aClassName in styles) {
|
||||||
|
// React Native will accept an Array as the value of the
|
||||||
|
// style prop. However, I do not know about React.
|
||||||
|
style = {
|
||||||
|
...style,
|
||||||
|
...styles[aClassName]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Otherwise, leave it as className.
|
||||||
|
className += `${aClassName} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose which of the className and/or style props has a value and,
|
||||||
|
// consequently, must be returned.
|
||||||
|
const props = {};
|
||||||
|
|
||||||
|
if (className) {
|
||||||
|
props.className = className.trim();
|
||||||
|
}
|
||||||
|
if (style) {
|
||||||
|
props.style = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the redux state to {@code CalleeInfo}'s props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _callee: Object
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
const _isVideoMuted
|
||||||
|
= isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
|
||||||
|
const poltergeist
|
||||||
|
= getParticipants(state).find(p => p.botType === 'poltergeist');
|
||||||
|
|
||||||
|
if (poltergeist) {
|
||||||
|
const { id } = poltergeist;
|
||||||
|
|
||||||
|
return {
|
||||||
|
_callee: {
|
||||||
|
avatar: getAvatarURL(poltergeist),
|
||||||
|
name: getParticipantDisplayName(state, id),
|
||||||
|
status: getParticipantPresenceStatus(state, id)
|
||||||
|
},
|
||||||
|
_isVideoMuted
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
_callee: state['features/invite'].initialCalleeInfo,
|
||||||
|
_isVideoMuted
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(CalleeInfo);
|
|
@ -57,7 +57,7 @@ function _mapStateToProps(state: Object): Object {
|
||||||
* @private
|
* @private
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
_calleeInfoVisible: state['features/base/jwt'].calleeInfoVisible
|
_calleeInfoVisible: state['features/invite'].calleeInfoVisible
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export { default as CalleeInfo } from './CalleeInfo';
|
|
||||||
export { default as CalleeInfoContainer } from './CalleeInfoContainer';
|
export { default as CalleeInfoContainer } from './CalleeInfoContainer';
|
|
@ -1,6 +1,6 @@
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import { ColorPalette, createStyleSheet } from '../../styles';
|
import { ColorPalette, createStyleSheet } from '../../../base/styles';
|
||||||
|
|
||||||
export default createStyleSheet({
|
export default createStyleSheet({
|
||||||
// XXX The names bellow were preserved for the purposes of compatibility
|
// XXX The names bellow were preserved for the purposes of compatibility
|
|
@ -2,3 +2,4 @@ export { default as AddPeopleDialog } from './AddPeopleDialog';
|
||||||
export { DialInSummary } from './dial-in-summary';
|
export { DialInSummary } from './dial-in-summary';
|
||||||
export { default as InfoDialogButton } from './InfoDialogButton';
|
export { default as InfoDialogButton } from './InfoDialogButton';
|
||||||
export { default as InviteButton } from './InviteButton';
|
export { default as InviteButton } from './InviteButton';
|
||||||
|
export * from './callee-info';
|
||||||
|
|
|
@ -2,11 +2,17 @@
|
||||||
|
|
||||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
|
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
|
||||||
import {
|
import {
|
||||||
|
CONFERENCE_JOINED
|
||||||
|
} from '../base/conference';
|
||||||
|
import {
|
||||||
|
getLocalParticipant,
|
||||||
getParticipantPresenceStatus,
|
getParticipantPresenceStatus,
|
||||||
|
getParticipants,
|
||||||
PARTICIPANT_JOINED,
|
PARTICIPANT_JOINED,
|
||||||
PARTICIPANT_JOINED_SOUND_ID,
|
PARTICIPANT_JOINED_SOUND_ID,
|
||||||
PARTICIPANT_LEFT,
|
PARTICIPANT_LEFT,
|
||||||
PARTICIPANT_UPDATED
|
PARTICIPANT_UPDATED,
|
||||||
|
pinParticipant
|
||||||
} from '../base/participants';
|
} from '../base/participants';
|
||||||
import { MiddlewareRegistry } from '../base/redux';
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
import {
|
import {
|
||||||
|
@ -24,7 +30,15 @@ import {
|
||||||
RINGING
|
RINGING
|
||||||
} from '../presence-status';
|
} from '../presence-status';
|
||||||
|
|
||||||
import { UPDATE_DIAL_IN_NUMBERS_FAILED } from './actionTypes';
|
import {
|
||||||
|
SET_CALLEE_INFO_VISIBLE,
|
||||||
|
UPDATE_DIAL_IN_NUMBERS_FAILED
|
||||||
|
} from './actionTypes';
|
||||||
|
import {
|
||||||
|
invite,
|
||||||
|
removePendingInviteRequests,
|
||||||
|
setCalleeInfoVisible
|
||||||
|
} from './actions';
|
||||||
import {
|
import {
|
||||||
OUTGOING_CALL_EXPIRED_SOUND_ID,
|
OUTGOING_CALL_EXPIRED_SOUND_ID,
|
||||||
OUTGOING_CALL_REJECTED_SOUND_ID,
|
OUTGOING_CALL_REJECTED_SOUND_ID,
|
||||||
|
@ -59,13 +73,22 @@ const statusToRingtone = {
|
||||||
*/
|
*/
|
||||||
MiddlewareRegistry.register(store => next => action => {
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
let oldParticipantPresence;
|
let oldParticipantPresence;
|
||||||
|
const { dispatch, getState } = store;
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
if (action.type === PARTICIPANT_UPDATED
|
if (action.type === PARTICIPANT_UPDATED
|
||||||
|| action.type === PARTICIPANT_LEFT) {
|
|| action.type === PARTICIPANT_LEFT) {
|
||||||
oldParticipantPresence
|
oldParticipantPresence
|
||||||
= getParticipantPresenceStatus(
|
= getParticipantPresenceStatus(state, action.participant.id);
|
||||||
store.getState(),
|
}
|
||||||
action.participant.id);
|
|
||||||
|
if (action.type === SET_CALLEE_INFO_VISIBLE) {
|
||||||
|
if (action.calleeInfoVisible) {
|
||||||
|
dispatch(pinParticipant(getLocalParticipant(state).id));
|
||||||
|
} else {
|
||||||
|
// unpin participant
|
||||||
|
dispatch(pinParticipant());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
|
@ -73,23 +96,27 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case APP_WILL_MOUNT:
|
case APP_WILL_MOUNT:
|
||||||
for (const [ soundId, sound ] of sounds.entries()) {
|
for (const [ soundId, sound ] of sounds.entries()) {
|
||||||
store.dispatch(registerSound(soundId, sound.file, sound.options));
|
dispatch(registerSound(soundId, sound.file, sound.options));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case APP_WILL_UNMOUNT:
|
case APP_WILL_UNMOUNT:
|
||||||
for (const soundId of sounds.keys()) {
|
for (const soundId of sounds.keys()) {
|
||||||
store.dispatch(unregisterSound(soundId));
|
dispatch(unregisterSound(soundId));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CONFERENCE_JOINED:
|
||||||
|
_onConferenceJoined(store);
|
||||||
|
break;
|
||||||
|
|
||||||
case PARTICIPANT_JOINED:
|
case PARTICIPANT_JOINED:
|
||||||
case PARTICIPANT_LEFT:
|
case PARTICIPANT_LEFT:
|
||||||
case PARTICIPANT_UPDATED: {
|
case PARTICIPANT_UPDATED: {
|
||||||
|
_maybeHideCalleeInfo(action, store);
|
||||||
|
|
||||||
const newParticipantPresence
|
const newParticipantPresence
|
||||||
= getParticipantPresenceStatus(
|
= getParticipantPresenceStatus(state, action.participant.id);
|
||||||
store.getState(),
|
|
||||||
action.participant.id);
|
|
||||||
|
|
||||||
if (oldParticipantPresence === newParticipantPresence) {
|
if (oldParticipantPresence === newParticipantPresence) {
|
||||||
break;
|
break;
|
||||||
|
@ -108,11 +135,11 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldSoundId) {
|
if (oldSoundId) {
|
||||||
store.dispatch(stopSound(oldSoundId));
|
dispatch(stopSound(oldSoundId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newSoundId) {
|
if (newSoundId) {
|
||||||
store.dispatch(playSound(newSoundId));
|
dispatch(playSound(newSoundId));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -126,3 +153,50 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the callee info layot if there are more than 1 real
|
||||||
|
* (not poltergeist, shared video, etc.) participants in the call.
|
||||||
|
*
|
||||||
|
* @param {Object} action - The redux action.
|
||||||
|
* @param {ReduxStore} store - The redux store.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _maybeHideCalleeInfo(action, store) {
|
||||||
|
const state = store.getState();
|
||||||
|
|
||||||
|
if (!state['features/invite'].calleeInfoVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const participants = getParticipants(state);
|
||||||
|
const numberOfPoltergeists
|
||||||
|
= participants.filter(p => p.botType === 'poltergeist').length;
|
||||||
|
const numberOfRealParticipants = participants.length - numberOfPoltergeists;
|
||||||
|
|
||||||
|
if ((numberOfPoltergeists > 1 || numberOfRealParticipants > 1)
|
||||||
|
|| (action.type === PARTICIPANT_LEFT && participants.length === 1)) {
|
||||||
|
store.dispatch(setCalleeInfoVisible(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the pending invitation requests if any.
|
||||||
|
*
|
||||||
|
* @param {ReduxStore} store - The redux store.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _onConferenceJoined(store) {
|
||||||
|
const { dispatch, getState } = store;
|
||||||
|
|
||||||
|
const pendingInviteRequests
|
||||||
|
= getState()['features/invite'].pendingInviteRequests || [];
|
||||||
|
|
||||||
|
pendingInviteRequests.forEach(({ invitees, callback }) => {
|
||||||
|
dispatch(invite(invitees))
|
||||||
|
.then(failedInvitees => {
|
||||||
|
callback(failedInvitees);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(removePendingInviteRequests());
|
||||||
|
}
|
||||||
|
|
|
@ -4,12 +4,24 @@ import { assign, ReducerRegistry } from '../base/redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
_SET_EMITTER_SUBSCRIPTIONS,
|
_SET_EMITTER_SUBSCRIPTIONS,
|
||||||
|
ADD_PENDING_INVITE_REQUEST,
|
||||||
|
REMOVE_PENDING_INVITE_REQUESTS,
|
||||||
|
SET_CALLEE_INFO_VISIBLE,
|
||||||
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
||||||
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
|
||||||
const DEFAULT_STATE = {
|
const DEFAULT_STATE = {
|
||||||
numbersEnabled: true
|
/**
|
||||||
|
* The indicator which determines whether (the) {@code CalleeInfo} is
|
||||||
|
* visible.
|
||||||
|
*
|
||||||
|
* @type {boolean|undefined}
|
||||||
|
*/
|
||||||
|
calleeInfoVisible: false,
|
||||||
|
|
||||||
|
numbersEnabled: true,
|
||||||
|
pendingInviteRequests: []
|
||||||
};
|
};
|
||||||
|
|
||||||
ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
|
ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
|
||||||
|
@ -17,6 +29,26 @@ ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
|
||||||
case _SET_EMITTER_SUBSCRIPTIONS:
|
case _SET_EMITTER_SUBSCRIPTIONS:
|
||||||
return (
|
return (
|
||||||
assign(state, 'emitterSubscriptions', action.emitterSubscriptions));
|
assign(state, 'emitterSubscriptions', action.emitterSubscriptions));
|
||||||
|
case ADD_PENDING_INVITE_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
pendingInviteRequests: [
|
||||||
|
...state.pendingInviteRequests,
|
||||||
|
action.request
|
||||||
|
]
|
||||||
|
};
|
||||||
|
case REMOVE_PENDING_INVITE_REQUESTS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
pendingInviteRequests: []
|
||||||
|
};
|
||||||
|
|
||||||
|
case SET_CALLEE_INFO_VISIBLE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
calleeInfoVisible: action.calleeInfoVisible,
|
||||||
|
initialCalleeInfo: action.initialCalleeInfo
|
||||||
|
};
|
||||||
|
|
||||||
case UPDATE_DIAL_IN_NUMBERS_FAILED:
|
case UPDATE_DIAL_IN_NUMBERS_FAILED:
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -139,8 +139,10 @@ export default class AbstractNotificationsContainer<P: Props>
|
||||||
export function _abstractMapStateToProps(state: Object) {
|
export function _abstractMapStateToProps(state: Object) {
|
||||||
const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
|
const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
|
||||||
const { enabled, notifications } = state['features/notifications'];
|
const { enabled, notifications } = state['features/notifications'];
|
||||||
|
const { calleeInfoVisible } = state['features/invite'];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_notifications: enabled && !isAnyOverlayVisible ? notifications : []
|
_notifications: enabled && !isAnyOverlayVisible && !calleeInfoVisible
|
||||||
|
? notifications : []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
import { getParticipantById } from '../../base/participants';
|
import { getParticipantById } from '../../base/participants';
|
||||||
|
import { Text } from '../../base/react';
|
||||||
|
|
||||||
import { STATUS_TO_I18N_KEY } from '../constants';
|
import { STATUS_TO_I18N_KEY } from '../constants';
|
||||||
|
|
||||||
|
@ -35,11 +36,27 @@ class PresenceLabel extends Component {
|
||||||
*/
|
*/
|
||||||
_presence: PropTypes.string,
|
_presence: PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default presence status that will be displayed if user's presence
|
||||||
|
* status is not available.
|
||||||
|
*/
|
||||||
|
defaultPresence: PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styles for the case where there isn't any content to be shown.
|
||||||
|
*/
|
||||||
|
noContentStyles: PropTypes.object,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the participant whose presence status shoul display.
|
* The ID of the participant whose presence status shoul display.
|
||||||
*/
|
*/
|
||||||
participantID: PropTypes.string,
|
participantID: PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styles for the presence label.
|
||||||
|
*/
|
||||||
|
styles: PropTypes.object,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked to obtain translated strings.
|
* Invoked to obtain translated strings.
|
||||||
*/
|
*/
|
||||||
|
@ -53,14 +70,13 @@ class PresenceLabel extends Component {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { _presence } = this.props;
|
const { _presence, styles, noContentStyles } = this.props;
|
||||||
|
const combinedStyles = _presence ? styles : noContentStyles;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Text { ...combinedStyles }>
|
||||||
className
|
|
||||||
= { `presence-label ${_presence ? '' : 'no-presence'}` }>
|
|
||||||
{ this._getPresenceText() }
|
{ this._getPresenceText() }
|
||||||
</div>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +118,8 @@ function _mapStateToProps(state, ownProps) {
|
||||||
const participant = getParticipantById(state, ownProps.participantID);
|
const participant = getParticipantById(state, ownProps.participantID);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_presence: participant && participant.presence
|
_presence:
|
||||||
|
(participant && participant.presence) || ownProps.defaultPresence
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,5 +122,6 @@ export const STATUS_TO_I18N_KEY = {
|
||||||
[CONNECTING]: 'presenceStatus.connecting',
|
[CONNECTING]: 'presenceStatus.connecting',
|
||||||
[CONNECTING2]: 'presenceStatus.connecting2',
|
[CONNECTING2]: 'presenceStatus.connecting2',
|
||||||
[CONNECTED_PHONE_NUMBER]: 'presenceStatus.connected',
|
[CONNECTED_PHONE_NUMBER]: 'presenceStatus.connected',
|
||||||
|
[CONNECTED_USER]: 'presenceStatus.connected',
|
||||||
[DISCONNECTED]: 'presenceStatus.disconnected'
|
[DISCONNECTED]: 'presenceStatus.disconnected'
|
||||||
};
|
};
|
||||||
|
|
|
@ -89,7 +89,7 @@ export function hideToolbox(force: boolean = false): Function {
|
||||||
|
|
||||||
if (!force
|
if (!force
|
||||||
&& (hovered
|
&& (hovered
|
||||||
|| state['features/base/jwt'].calleeInfoVisible
|
|| state['features/invite'].calleeInfoVisible
|
||||||
|| SideContainerToggler.isVisible())) {
|
|| SideContainerToggler.isVisible())) {
|
||||||
dispatch(
|
dispatch(
|
||||||
setToolboxTimeout(
|
setToolboxTimeout(
|
||||||
|
|
Loading…
Reference in New Issue