Merge branch 'feature-toolarea' into develop
This commit is contained in:
@@ -732,6 +732,78 @@ export default class RoomClient
|
||||
});
|
||||
}
|
||||
|
||||
pausePeerScreen(peerName)
|
||||
{
|
||||
logger.debug('pausePeerScreen() [peerName:"%s"]', peerName);
|
||||
|
||||
this._dispatch(
|
||||
stateActions.setPeerScreenInProgress(peerName, true));
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() =>
|
||||
{
|
||||
for (const peer of this._room.peers)
|
||||
{
|
||||
if (peer.name === peerName)
|
||||
{
|
||||
for (const consumer of peer.consumers)
|
||||
{
|
||||
if (consumer.appData.source !== 'screen')
|
||||
continue;
|
||||
|
||||
consumer.pause('pause-screen');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._dispatch(
|
||||
stateActions.setPeerScreenInProgress(peerName, false));
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('pausePeerScreen() failed: %o', error);
|
||||
|
||||
this._dispatch(
|
||||
stateActions.setPeerScreenInProgress(peerName, false));
|
||||
});
|
||||
}
|
||||
|
||||
resumePeerScreen(peerName)
|
||||
{
|
||||
logger.debug('resumePeerScreen() [peerName:"%s"]', peerName);
|
||||
|
||||
this._dispatch(
|
||||
stateActions.setPeerScreenInProgress(peerName, true));
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() =>
|
||||
{
|
||||
for (const peer of this._room.peers)
|
||||
{
|
||||
if (peer.name === peerName)
|
||||
{
|
||||
for (const consumer of peer.consumers)
|
||||
{
|
||||
if (consumer.appData.source !== 'screen' || !consumer.supported)
|
||||
continue;
|
||||
|
||||
consumer.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._dispatch(
|
||||
stateActions.setPeerScreenInProgress(peerName, false));
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('resumePeerScreen() failed: %o', error);
|
||||
|
||||
this._dispatch(
|
||||
stateActions.setPeerScreenInProgress(peerName, false));
|
||||
});
|
||||
}
|
||||
|
||||
enableAudioOnly()
|
||||
{
|
||||
logger.debug('enableAudioOnly()');
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as stateActions from '../../redux/stateActions';
|
||||
import * as requestActions from '../../redux/requestActions';
|
||||
import MessageList from './MessageList';
|
||||
|
||||
class Chat extends Component
|
||||
{
|
||||
render()
|
||||
{
|
||||
const {
|
||||
senderPlaceHolder,
|
||||
onSendMessage,
|
||||
disabledInput,
|
||||
autofocus,
|
||||
displayName
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div data-component='Chat'>
|
||||
<MessageList />
|
||||
<form
|
||||
data-component='Sender'
|
||||
onSubmit={(e) => { onSendMessage(e, displayName); }}
|
||||
>
|
||||
<input
|
||||
type='text'
|
||||
className='new-message'
|
||||
name='message'
|
||||
placeholder={senderPlaceHolder}
|
||||
disabled={disabledInput}
|
||||
autoFocus={autofocus}
|
||||
autoComplete='off'
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Chat.propTypes =
|
||||
{
|
||||
senderPlaceHolder : PropTypes.string,
|
||||
onSendMessage : PropTypes.func,
|
||||
disabledInput : PropTypes.bool,
|
||||
autofocus : PropTypes.bool,
|
||||
displayName : PropTypes.string
|
||||
};
|
||||
|
||||
Chat.defaultProps =
|
||||
{
|
||||
senderPlaceHolder : 'Type a message...',
|
||||
autofocus : true,
|
||||
displayName : null
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
disabledInput : state.chatbehavior.disabledInput,
|
||||
displayName : state.me.displayName
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
{
|
||||
return {
|
||||
onSendMessage : (event, displayName) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
const userInput = event.target.message.value;
|
||||
|
||||
if (userInput)
|
||||
{
|
||||
dispatch(stateActions.addUserMessage(userInput));
|
||||
dispatch(requestActions.sendChatMessage(userInput, displayName));
|
||||
}
|
||||
event.target.message.value = '';
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const ChatContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Chat);
|
||||
|
||||
export default ChatContainer;
|
||||
@@ -0,0 +1,166 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import * as requestActions from '../../redux/requestActions';
|
||||
|
||||
const ListPeer = (props) =>
|
||||
{
|
||||
const {
|
||||
peer,
|
||||
micConsumer,
|
||||
webcamConsumer,
|
||||
screenConsumer,
|
||||
onMuteMic,
|
||||
onUnmuteMic,
|
||||
onDisableWebcam,
|
||||
onEnableWebcam,
|
||||
onDisableScreen,
|
||||
onEnableScreen
|
||||
} = props;
|
||||
|
||||
const micEnabled = (
|
||||
Boolean(micConsumer) &&
|
||||
!micConsumer.locallyPaused &&
|
||||
!micConsumer.remotelyPaused
|
||||
);
|
||||
|
||||
const videoVisible = (
|
||||
Boolean(webcamConsumer) &&
|
||||
!webcamConsumer.locallyPaused &&
|
||||
!webcamConsumer.remotelyPaused
|
||||
);
|
||||
|
||||
const screenVisible = (
|
||||
Boolean(screenConsumer) &&
|
||||
!screenConsumer.locallyPaused &&
|
||||
!screenConsumer.remotelyPaused
|
||||
);
|
||||
|
||||
return (
|
||||
<div data-component='ListPeer'>
|
||||
<img className='avatar' />
|
||||
<div className='peer-info'>
|
||||
{peer.displayName}
|
||||
</div>
|
||||
<div className='controls'>
|
||||
{ screenConsumer ?
|
||||
<div
|
||||
className={classnames('button', 'screen', {
|
||||
on : screenVisible,
|
||||
off : !screenVisible,
|
||||
disabled : peer.peerScreenInProgress
|
||||
})}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
screenVisible ?
|
||||
onDisableScreen(peer.name) : onEnableScreen(peer.name);
|
||||
}}
|
||||
/>
|
||||
:null
|
||||
}
|
||||
<div
|
||||
className={classnames('button', 'mic', {
|
||||
on : micEnabled,
|
||||
off : !micEnabled,
|
||||
disabled : peer.peerAudioInProgress
|
||||
})}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
micEnabled ? onMuteMic(peer.name) : onUnmuteMic(peer.name);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={classnames('button', 'webcam', {
|
||||
on : videoVisible,
|
||||
off : !videoVisible,
|
||||
disabled : peer.peerVideoInProgress
|
||||
})}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
videoVisible ?
|
||||
onDisableWebcam(peer.name) : onEnableWebcam(peer.name);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ListPeer.propTypes =
|
||||
{
|
||||
advancedMode : PropTypes.bool,
|
||||
peer : appPropTypes.Peer.isRequired,
|
||||
micConsumer : appPropTypes.Consumer,
|
||||
webcamConsumer : appPropTypes.Consumer,
|
||||
screenConsumer : appPropTypes.Consumer,
|
||||
onMuteMic : PropTypes.func.isRequired,
|
||||
onUnmuteMic : PropTypes.func.isRequired,
|
||||
onEnableWebcam : PropTypes.func.isRequired,
|
||||
onDisableWebcam : PropTypes.func.isRequired,
|
||||
onEnableScreen : PropTypes.func.isRequired,
|
||||
onDisableScreen : PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, { name }) =>
|
||||
{
|
||||
const peer = state.peers[name];
|
||||
const consumersArray = peer.consumers
|
||||
.map((consumerId) => state.consumers[consumerId]);
|
||||
const micConsumer =
|
||||
consumersArray.find((consumer) => consumer.source === 'mic');
|
||||
const webcamConsumer =
|
||||
consumersArray.find((consumer) => consumer.source === 'webcam');
|
||||
const screenConsumer =
|
||||
consumersArray.find((consumer) => consumer.source === 'screen');
|
||||
|
||||
return {
|
||||
peer,
|
||||
micConsumer,
|
||||
webcamConsumer,
|
||||
screenConsumer
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
{
|
||||
return {
|
||||
onMuteMic : (peerName) =>
|
||||
{
|
||||
dispatch(requestActions.mutePeerAudio(peerName));
|
||||
},
|
||||
onUnmuteMic : (peerName) =>
|
||||
{
|
||||
dispatch(requestActions.unmutePeerAudio(peerName));
|
||||
},
|
||||
onEnableWebcam : (peerName) =>
|
||||
{
|
||||
|
||||
dispatch(requestActions.resumePeerVideo(peerName));
|
||||
},
|
||||
onDisableWebcam : (peerName) =>
|
||||
{
|
||||
dispatch(requestActions.pausePeerVideo(peerName));
|
||||
},
|
||||
onEnableScreen : (peerName) =>
|
||||
{
|
||||
dispatch(requestActions.resumePeerScreen(peerName));
|
||||
},
|
||||
onDisableScreen : (peerName) =>
|
||||
{
|
||||
dispatch(requestActions.pausePeerScreen(peerName));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const ListPeerContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ListPeer);
|
||||
|
||||
export default ListPeerContainer;
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import * as requestActions from '../../redux/requestActions';
|
||||
import * as stateActions from '../../redux/stateActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import ListPeer from './ListPeer';
|
||||
|
||||
class ParticipantList extends React.Component
|
||||
{
|
||||
constructor(props)
|
||||
{
|
||||
super(props);
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
const {
|
||||
advancedMode,
|
||||
peers
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div data-component='ParticipantList'>
|
||||
<ul className='list'>
|
||||
{
|
||||
peers.map((peer) =>
|
||||
{
|
||||
return (
|
||||
<li key={peer.name} className='list-item'>
|
||||
<ListPeer name={peer.name} advancedMode={advancedMode} />
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ParticipantList.propTypes =
|
||||
{
|
||||
advancedMode : PropTypes.bool,
|
||||
peers : PropTypes.arrayOf(appPropTypes.Peer).isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
const peersArray = Object.values(state.peers);
|
||||
|
||||
return {
|
||||
peers : peersArray
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
{
|
||||
return {
|
||||
handleChangeWebcam : (device) =>
|
||||
{
|
||||
dispatch(requestActions.changeWebcam(device.value));
|
||||
},
|
||||
handleChangeAudioDevice : (device) =>
|
||||
{
|
||||
dispatch(requestActions.changeAudioDevice(device.value));
|
||||
},
|
||||
onToggleAdvancedMode : () =>
|
||||
{
|
||||
dispatch(stateActions.toggleAdvancedMode());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const ParticipantListContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ParticipantList);
|
||||
|
||||
export default ParticipantListContainer;
|
||||
+138
-136
@@ -5,14 +5,13 @@ import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import ClipboardButton from 'react-clipboard.js';
|
||||
import * as appPropTypes from './appPropTypes';
|
||||
import * as stateActions from '../redux/stateActions';
|
||||
import * as requestActions from '../redux/requestActions';
|
||||
import { Appear } from './transitions';
|
||||
import Me from './Me';
|
||||
import Peers from './Peers';
|
||||
import Notifications from './Notifications';
|
||||
import ChatWidget from './ChatWidget';
|
||||
import Settings from './Settings';
|
||||
import ToolAreaButton from './ToolArea/ToolAreaButton';
|
||||
import ToolArea from './ToolArea/ToolArea';
|
||||
|
||||
class Room extends React.Component
|
||||
{
|
||||
@@ -21,10 +20,10 @@ class Room extends React.Component
|
||||
const {
|
||||
room,
|
||||
me,
|
||||
toolAreaOpen,
|
||||
amActiveSpeaker,
|
||||
screenProducer,
|
||||
onRoomLinkCopy,
|
||||
onToggleSettings,
|
||||
onLogin,
|
||||
onShareScreen,
|
||||
onUnShareScreen,
|
||||
@@ -60,137 +59,143 @@ class Room extends React.Component
|
||||
return (
|
||||
<Appear duration={300}>
|
||||
<div data-component='Room'>
|
||||
<Notifications />
|
||||
<ChatWidget />
|
||||
|
||||
<div className='state' data-tip='Server status'>
|
||||
<div className={classnames('icon', room.state)} />
|
||||
<p className={classnames('text', room.state)}>{room.state}</p>
|
||||
</div>
|
||||
|
||||
<div className='room-link-wrapper'>
|
||||
<div className='room-link'>
|
||||
<ClipboardButton
|
||||
component='a'
|
||||
className='link'
|
||||
button-href={room.url}
|
||||
button-target='_blank'
|
||||
data-tip='Click to copy room link'
|
||||
data-clipboard-text={room.url}
|
||||
onSuccess={onRoomLinkCopy}
|
||||
onClick={(event) =>
|
||||
{
|
||||
// If this is a 'Open in new window/tab' don't prevent
|
||||
// click default action.
|
||||
if (
|
||||
event.ctrlKey || event.shiftKey || event.metaKey ||
|
||||
// Middle click (IE > 9 and everyone else).
|
||||
(event.button && event.button === 1)
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
invitation link
|
||||
</ClipboardButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Peers
|
||||
advancedMode={room.advancedMode}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={classnames('me-container', {
|
||||
'active-speaker' : amActiveSpeaker
|
||||
})}
|
||||
className='room-wrapper'
|
||||
style={{
|
||||
width : toolAreaOpen ? '80%' : '100%'
|
||||
}}
|
||||
>
|
||||
<Me
|
||||
<Notifications />
|
||||
<ToolAreaButton />
|
||||
|
||||
<div className='state' data-tip='Server status'>
|
||||
<div className={classnames('icon', room.state)} />
|
||||
<p className={classnames('text', room.state)}>{room.state}</p>
|
||||
</div>
|
||||
|
||||
<div className='room-link-wrapper'>
|
||||
<div className='room-link'>
|
||||
<ClipboardButton
|
||||
component='a'
|
||||
className='link'
|
||||
button-href={room.url}
|
||||
button-target='_blank'
|
||||
data-tip='Click to copy room link'
|
||||
data-clipboard-text={room.url}
|
||||
onSuccess={onRoomLinkCopy}
|
||||
onClick={(event) =>
|
||||
{
|
||||
// If this is a 'Open in new window/tab' don't prevent
|
||||
// click default action.
|
||||
if (
|
||||
event.ctrlKey || event.shiftKey || event.metaKey ||
|
||||
// Middle click (IE > 9 and everyone else).
|
||||
(event.button && event.button === 1)
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
invitation link
|
||||
</ClipboardButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Peers
|
||||
advancedMode={room.advancedMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='sidebar'>
|
||||
<div
|
||||
className={classnames('button', 'screen', screenState)}
|
||||
data-tip={screenTip}
|
||||
data-type='dark'
|
||||
onClick={() =>
|
||||
{
|
||||
switch (screenState)
|
||||
className={classnames('me-container', {
|
||||
'active-speaker' : amActiveSpeaker
|
||||
})}
|
||||
>
|
||||
<Me
|
||||
advancedMode={room.advancedMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='sidebar'>
|
||||
<div
|
||||
className={classnames('button', 'screen', screenState)}
|
||||
data-tip={screenTip}
|
||||
data-type='dark'
|
||||
onClick={() =>
|
||||
{
|
||||
case 'on':
|
||||
switch (screenState)
|
||||
{
|
||||
onUnShareScreen();
|
||||
break;
|
||||
case 'on':
|
||||
{
|
||||
onUnShareScreen();
|
||||
break;
|
||||
}
|
||||
case 'off':
|
||||
{
|
||||
onShareScreen();
|
||||
break;
|
||||
}
|
||||
case 'need-extension':
|
||||
{
|
||||
onNeedExtension();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
case 'off':
|
||||
{
|
||||
onShareScreen();
|
||||
break;
|
||||
}
|
||||
case 'need-extension':
|
||||
{
|
||||
onNeedExtension();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={classnames('button', 'settings', {
|
||||
on : room.showSettings,
|
||||
off : !room.showSettings
|
||||
})}
|
||||
data-tip='Open settings'
|
||||
data-type='dark'
|
||||
onClick={() => onToggleSettings()}
|
||||
/>
|
||||
<div
|
||||
className={classnames('button', 'login', 'off', {
|
||||
disabled : me.loginInProgress
|
||||
})}
|
||||
data-tip='Login'
|
||||
data-type='dark'
|
||||
onClick={() => onLogin()}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={classnames('button', 'login', 'off', {
|
||||
disabled : me.loginInProgress
|
||||
})}
|
||||
data-tip='Login'
|
||||
data-type='dark'
|
||||
onClick={() => onLogin()}
|
||||
/>
|
||||
<div
|
||||
className={classnames('button', 'raise-hand', {
|
||||
on : me.raiseHand,
|
||||
disabled : me.raiseHandInProgress
|
||||
})}
|
||||
data-tip='Raise hand'
|
||||
data-type='dark'
|
||||
onClick={() => onToggleHand(!me.raiseHand)}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={classnames('button', 'raise-hand', {
|
||||
on : me.raiseHand,
|
||||
disabled : me.raiseHandInProgress
|
||||
})}
|
||||
data-tip='Raise hand'
|
||||
data-type='dark'
|
||||
onClick={() => onToggleHand(!me.raiseHand)}
|
||||
/>
|
||||
<div
|
||||
className={classnames('button', 'leave-meeting')}
|
||||
data-tip='Leave meeting'
|
||||
data-type='dark'
|
||||
onClick={() => onLeaveMeeting()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={classnames('button', 'leave-meeting')}
|
||||
data-tip='Leave meeting'
|
||||
data-type='dark'
|
||||
onClick={() => onLeaveMeeting()}
|
||||
<ReactTooltip
|
||||
effect='solid'
|
||||
delayShow={100}
|
||||
delayHide={100}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Settings
|
||||
onToggleSettings={onToggleSettings}
|
||||
/>
|
||||
|
||||
<ReactTooltip
|
||||
effect='solid'
|
||||
delayShow={100}
|
||||
delayHide={100}
|
||||
/>
|
||||
<div
|
||||
className='toolarea-wrapper'
|
||||
style={{
|
||||
width : toolAreaOpen ? '20%' : '0%'
|
||||
}}
|
||||
>
|
||||
{toolAreaOpen ?
|
||||
<ToolArea
|
||||
advancedMode={room.advancedMode}
|
||||
/>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Appear>
|
||||
);
|
||||
@@ -199,18 +204,18 @@ class Room extends React.Component
|
||||
|
||||
Room.propTypes =
|
||||
{
|
||||
room : appPropTypes.Room.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
amActiveSpeaker : PropTypes.bool.isRequired,
|
||||
screenProducer : appPropTypes.Producer,
|
||||
onRoomLinkCopy : PropTypes.func.isRequired,
|
||||
onShareScreen : PropTypes.func.isRequired,
|
||||
onUnShareScreen : PropTypes.func.isRequired,
|
||||
onNeedExtension : PropTypes.func.isRequired,
|
||||
onToggleSettings : PropTypes.func.isRequired,
|
||||
onToggleHand : PropTypes.func.isRequired,
|
||||
onLeaveMeeting : PropTypes.func.isRequired,
|
||||
onLogin : PropTypes.func.isRequired
|
||||
room : appPropTypes.Room.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
amActiveSpeaker : PropTypes.bool.isRequired,
|
||||
toolAreaOpen : PropTypes.bool.isRequired,
|
||||
screenProducer : appPropTypes.Producer,
|
||||
onRoomLinkCopy : PropTypes.func.isRequired,
|
||||
onShareScreen : PropTypes.func.isRequired,
|
||||
onUnShareScreen : PropTypes.func.isRequired,
|
||||
onNeedExtension : PropTypes.func.isRequired,
|
||||
onToggleHand : PropTypes.func.isRequired,
|
||||
onLeaveMeeting : PropTypes.func.isRequired,
|
||||
onLogin : PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
@@ -222,6 +227,7 @@ const mapStateToProps = (state) =>
|
||||
return {
|
||||
room : state.room,
|
||||
me : state.me,
|
||||
toolAreaOpen : state.toolarea.toolAreaOpen,
|
||||
amActiveSpeaker : state.me.name === state.room.activeSpeakerName,
|
||||
screenProducer : screenProducer
|
||||
};
|
||||
@@ -237,10 +243,6 @@ const mapDispatchToProps = (dispatch) =>
|
||||
text : 'Room link copied to the clipboard'
|
||||
}));
|
||||
},
|
||||
onToggleSettings : () =>
|
||||
{
|
||||
dispatch(stateActions.toggleSettings());
|
||||
},
|
||||
onToggleHand : (enable) =>
|
||||
{
|
||||
if (enable)
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as appPropTypes from './appPropTypes';
|
||||
import * as requestActions from '../redux/requestActions';
|
||||
import * as stateActions from '../redux/stateActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Appear } from './transitions';
|
||||
import Dropdown from 'react-dropdown';
|
||||
|
||||
class Settings extends React.Component
|
||||
@@ -21,13 +20,9 @@ class Settings extends React.Component
|
||||
me,
|
||||
handleChangeWebcam,
|
||||
handleChangeAudioDevice,
|
||||
onToggleSettings,
|
||||
onToggleAdvancedMode
|
||||
} = this.props;
|
||||
|
||||
if (!room.showSettings)
|
||||
return null;
|
||||
|
||||
let webcams;
|
||||
let webcamText;
|
||||
|
||||
@@ -55,43 +50,28 @@ class Settings extends React.Component
|
||||
audioDevices = [];
|
||||
|
||||
return (
|
||||
<Appear duration={500}>
|
||||
<div data-component='Settings'>
|
||||
<div className='dialog'>
|
||||
<div className='header'>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
<div className='settings'>
|
||||
<Dropdown
|
||||
disabled={!me.canChangeWebcam}
|
||||
options={webcams}
|
||||
onChange={handleChangeWebcam}
|
||||
placeholder={webcamText}
|
||||
/>
|
||||
<Dropdown
|
||||
disabled={!me.canChangeAudioDevice}
|
||||
options={audioDevices}
|
||||
onChange={handleChangeAudioDevice}
|
||||
placeholder={audioDevicesText}
|
||||
/>
|
||||
<input
|
||||
type='checkbox'
|
||||
defaultChecked={room.advancedMode}
|
||||
onChange={onToggleAdvancedMode}
|
||||
/>
|
||||
<span>Advanced mode</span>
|
||||
</div>
|
||||
<div className='footer'>
|
||||
<span
|
||||
className='button'
|
||||
onClick={() => onToggleSettings()}
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div data-component='Settings'>
|
||||
<div className='settings'>
|
||||
<Dropdown
|
||||
disabled={!me.canChangeWebcam}
|
||||
options={webcams}
|
||||
onChange={handleChangeWebcam}
|
||||
placeholder={webcamText}
|
||||
/>
|
||||
<Dropdown
|
||||
disabled={!me.canChangeAudioDevice}
|
||||
options={audioDevices}
|
||||
onChange={handleChangeAudioDevice}
|
||||
placeholder={audioDevicesText}
|
||||
/>
|
||||
<input
|
||||
type='checkbox'
|
||||
defaultChecked={room.advancedMode}
|
||||
onChange={onToggleAdvancedMode}
|
||||
/>
|
||||
<span>Advanced mode</span>
|
||||
</div>
|
||||
</Appear>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -100,7 +80,6 @@ Settings.propTypes =
|
||||
{
|
||||
me : appPropTypes.Me.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
onToggleSettings : PropTypes.func.isRequired,
|
||||
handleChangeWebcam : PropTypes.func.isRequired,
|
||||
handleChangeAudioDevice : PropTypes.func.isRequired,
|
||||
onToggleAdvancedMode : PropTypes.func.isRequired
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as stateActions from '../../redux/stateActions';
|
||||
import ParticipantList from '../ParticipantList/ParticipantList';
|
||||
import Chat from '../Chat/Chat';
|
||||
import Settings from '../Settings';
|
||||
|
||||
class ToolArea extends React.Component
|
||||
{
|
||||
constructor(props)
|
||||
{
|
||||
super(props);
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
const {
|
||||
toolarea,
|
||||
setToolTab
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div data-component='ToolArea'>
|
||||
<div className='tabs'>
|
||||
<input
|
||||
type='radio'
|
||||
name='tabs'
|
||||
id='tab-chat'
|
||||
onChange={() =>
|
||||
{
|
||||
setToolTab('chat');
|
||||
}}
|
||||
checked={toolarea.currentToolTab === 'chat'}
|
||||
/>
|
||||
<label htmlFor='tab-chat'>Chat</label>
|
||||
|
||||
<div className='tab'>
|
||||
<Chat />
|
||||
</div>
|
||||
|
||||
<input
|
||||
type='radio'
|
||||
name='tabs'
|
||||
id='tab-users'
|
||||
onChange={() =>
|
||||
{
|
||||
setToolTab('users');
|
||||
}}
|
||||
checked={toolarea.currentToolTab === 'users'}
|
||||
/>
|
||||
<label htmlFor='tab-users'>Users</label>
|
||||
|
||||
<div className='tab'>
|
||||
<ParticipantList />
|
||||
</div>
|
||||
|
||||
<input
|
||||
type='radio'
|
||||
name='tabs'
|
||||
id='tab-settings'
|
||||
onChange={() =>
|
||||
{
|
||||
setToolTab('settings');
|
||||
}}
|
||||
checked={toolarea.currentToolTab === 'settings'}
|
||||
/>
|
||||
<label htmlFor='tab-settings'>Settings</label>
|
||||
|
||||
<div className='tab'>
|
||||
<Settings />
|
||||
</div>
|
||||
|
||||
<input
|
||||
type='radio'
|
||||
name='tabs'
|
||||
id='tab-layout'
|
||||
onChange={() =>
|
||||
{
|
||||
setToolTab('layout');
|
||||
}}
|
||||
checked={toolarea.currentToolTab === 'layout'}
|
||||
/>
|
||||
<label htmlFor='tab-layout'>Layout</label>
|
||||
|
||||
<div className='tab'>
|
||||
<h1>Tab Three Content</h1>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ToolArea.propTypes =
|
||||
{
|
||||
advancedMode : PropTypes.bool,
|
||||
toolarea : PropTypes.object.isRequired,
|
||||
setToolTab : PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
toolarea : state.toolarea
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
{
|
||||
return {
|
||||
setToolTab : (toolTab) =>
|
||||
{
|
||||
dispatch(stateActions.setToolTab(toolTab));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const ToolAreaContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ToolArea);
|
||||
|
||||
export default ToolAreaContainer;
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import * as stateActions from '../../redux/stateActions';
|
||||
|
||||
class ToolAreaButton extends React.Component
|
||||
{
|
||||
render()
|
||||
{
|
||||
const {
|
||||
toolAreaOpen,
|
||||
toggleToolArea
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div data-component='ToolAreaButton'>
|
||||
<div
|
||||
className={classnames('button', 'toolarea-button', {
|
||||
on : toolAreaOpen
|
||||
})}
|
||||
data-tip='Toggle tool area'
|
||||
data-type='dark'
|
||||
data-for='globaltip'
|
||||
onClick={() => toggleToolArea()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ToolAreaButton.propTypes =
|
||||
{
|
||||
toolAreaOpen : PropTypes.bool.isRequired,
|
||||
toggleToolArea : PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
toolAreaOpen : state.toolarea.toolAreaOpen
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
{
|
||||
return {
|
||||
toggleToolArea : () =>
|
||||
{
|
||||
dispatch(stateActions.toggleToolArea());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const ToolAreaButtonContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ToolAreaButton);
|
||||
|
||||
export default ToolAreaButtonContainer;
|
||||
@@ -7,6 +7,7 @@ import consumers from './consumers';
|
||||
import notifications from './notifications';
|
||||
import chatmessages from './chatmessages';
|
||||
import chatbehavior from './chatbehavior';
|
||||
import toolarea from './toolarea';
|
||||
|
||||
const reducers = combineReducers(
|
||||
{
|
||||
@@ -17,7 +18,8 @@ const reducers = combineReducers(
|
||||
consumers,
|
||||
notifications,
|
||||
chatmessages,
|
||||
chatbehavior
|
||||
chatbehavior,
|
||||
toolarea
|
||||
});
|
||||
|
||||
export default reducers;
|
||||
|
||||
@@ -60,6 +60,19 @@ const peers = (state = initialState, action) =>
|
||||
return { ...state, [newPeer.name]: newPeer };
|
||||
}
|
||||
|
||||
case 'SET_PEER_SCREEN_IN_PROGRESS':
|
||||
{
|
||||
const { peerName, flag } = action.payload;
|
||||
const peer = state[peerName];
|
||||
|
||||
if (!peer)
|
||||
throw new Error('no Peer found');
|
||||
|
||||
const newPeer = { ...peer, peerScreenInProgress: flag };
|
||||
|
||||
return { ...state, [newPeer.name]: newPeer };
|
||||
}
|
||||
|
||||
case 'SET_PEER_RAISE_HAND_STATE':
|
||||
{
|
||||
const { peerName, raiseHandState } = action.payload;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const initialState =
|
||||
{
|
||||
toolAreaOpen : false,
|
||||
currentToolTab : 'chat' // chat, settings, layout, users
|
||||
};
|
||||
|
||||
const toolarea = (state = initialState, action) =>
|
||||
{
|
||||
switch (action.type)
|
||||
{
|
||||
case 'TOGGLE_TOOL_AREA':
|
||||
{
|
||||
const toolAreaOpen = !state.toolAreaOpen;
|
||||
|
||||
return { ...state, toolAreaOpen };
|
||||
}
|
||||
|
||||
case 'SET_TOOL_TAB':
|
||||
{
|
||||
const { toolTab } = action.payload;
|
||||
|
||||
return { ...state, currentToolTab: toolTab };
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default toolarea;
|
||||
@@ -119,6 +119,22 @@ export const resumePeerVideo = (peerName) =>
|
||||
};
|
||||
};
|
||||
|
||||
export const pausePeerScreen = (peerName) =>
|
||||
{
|
||||
return {
|
||||
type : 'PAUSE_PEER_SCREEN',
|
||||
payload : { peerName }
|
||||
};
|
||||
};
|
||||
|
||||
export const resumePeerScreen = (peerName) =>
|
||||
{
|
||||
return {
|
||||
type : 'RESUME_PEER_SCREEN',
|
||||
payload : { peerName }
|
||||
};
|
||||
};
|
||||
|
||||
export const userLogin = () =>
|
||||
{
|
||||
return {
|
||||
|
||||
@@ -149,6 +149,24 @@ export default ({ dispatch, getState }) => (next) =>
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PAUSE_PEER_SCREEN':
|
||||
{
|
||||
const { peerName } = action.payload;
|
||||
|
||||
client.pausePeerScreen(peerName);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'RESUME_PEER_SCREEN':
|
||||
{
|
||||
const { peerName } = action.payload;
|
||||
|
||||
client.resumePeerScreen(peerName);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'RAISE_HAND':
|
||||
{
|
||||
client.sendRaiseHandState(true);
|
||||
|
||||
@@ -125,6 +125,14 @@ export const setPeerAudioInProgress = (peerName, flag) =>
|
||||
};
|
||||
};
|
||||
|
||||
export const setPeerScreenInProgress = (peerName, flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PEER_SCREEN_IN_PROGRESS',
|
||||
payload : { peerName, flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const setMyRaiseHandState = (flag) =>
|
||||
{
|
||||
return {
|
||||
@@ -148,6 +156,21 @@ export const toggleSettings = () =>
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleToolArea = () =>
|
||||
{
|
||||
return {
|
||||
type : 'TOGGLE_TOOL_AREA'
|
||||
};
|
||||
};
|
||||
|
||||
export const setToolTab = (toolTab) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_TOOL_TAB',
|
||||
payload : { toolTab }
|
||||
};
|
||||
};
|
||||
|
||||
export const setMyRaiseHandStateInProgress = (flag) =>
|
||||
{
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user