Merge pull request #57 from matrix-org/matthew/inbound-calls

Position the inbound call box correctly
This commit is contained in:
Matthew Hodgson 2015-12-18 15:59:18 +00:00
commit 831aaec457
7 changed files with 103 additions and 78 deletions

View file

@ -138,9 +138,17 @@ function _setCallListeners(call) {
function _setCallState(call, roomId, status) { function _setCallState(call, roomId, status) {
console.log( console.log(
"Call state in %s changed to %s (%s)", roomId, status, (call ? call.state : "-") "Call state in %s changed to %s (%s)", roomId, status, (call ? call.call_state : "-")
); );
calls[roomId] = call; calls[roomId] = call;
if (status === "ringing") {
play("ringAudio")
}
else if (call && call.call_state === "ringing") {
pause("ringAudio")
}
if (call) { if (call) {
call.call_state = status; call.call_state = status;
} }

View file

@ -1280,8 +1280,9 @@ module.exports = React.createClass({
} }
var call = CallHandler.getCallForRoom(this.props.roomId); var call = CallHandler.getCallForRoom(this.props.roomId);
//var call = CallHandler.getAnyActiveCall();
var inCall = false; var inCall = false;
if (call && this.state.callState != 'ended') { if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) {
inCall = true; inCall = true;
var zoomButton, voiceMuteButton, videoMuteButton; var zoomButton, voiceMuteButton, videoMuteButton;

View file

@ -529,6 +529,7 @@ module.exports = React.createClass({
onHangupClick: function() { onHangupClick: function() {
var call = CallHandler.getCallForRoom(this.props.room.roomId); var call = CallHandler.getCallForRoom(this.props.room.roomId);
//var call = CallHandler.getAnyActiveCall();
if (!call) { if (!call) {
return; return;
} }
@ -563,6 +564,7 @@ module.exports = React.createClass({
var callButton, videoCallButton, hangupButton; var callButton, videoCallButton, hangupButton;
var call = CallHandler.getCallForRoom(this.props.room.roomId); var call = CallHandler.getCallForRoom(this.props.room.roomId);
//var call = CallHandler.getAnyActiveCall();
if (this.props.callState && this.props.callState !== 'ended') { if (this.props.callState && this.props.callState !== 'ended') {
hangupButton = hangupButton =
<div className="mx_MessageComposer_hangup" onClick={this.onHangupClick}> <div className="mx_MessageComposer_hangup" onClick={this.onHangupClick}>

View file

@ -19,6 +19,7 @@ var React = require("react");
var ReactDOM = require("react-dom"); var ReactDOM = require("react-dom");
var GeminiScrollbar = require('react-gemini-scrollbar'); var GeminiScrollbar = require('react-gemini-scrollbar');
var MatrixClientPeg = require("../../../MatrixClientPeg"); var MatrixClientPeg = require("../../../MatrixClientPeg");
var CallHandler = require('../../../CallHandler');
var RoomListSorter = require("../../../RoomListSorter"); var RoomListSorter = require("../../../RoomListSorter");
var UnreadStatus = require('../../../UnreadStatus'); var UnreadStatus = require('../../../UnreadStatus');
var dis = require("../../../dispatcher"); var dis = require("../../../dispatcher");
@ -39,6 +40,7 @@ module.exports = React.createClass({
return { return {
activityMap: null, activityMap: null,
lists: {}, lists: {},
incomingCall: null,
} }
}, },
@ -66,7 +68,21 @@ module.exports = React.createClass({
this.tooltip = payload.tooltip; this.tooltip = payload.tooltip;
this._repositionTooltip(); this._repositionTooltip();
if (this.tooltip) this.tooltip.style.display = 'block'; if (this.tooltip) this.tooltip.style.display = 'block';
break break;
case 'call_state':
var call = CallHandler.getCall(payload.room_id);
if (call && call.call_state === 'ringing') {
this.setState({
incomingCall: call
});
this._repositionIncomingCallBox(undefined, true);
}
else {
this.setState({
incomingCall: null
});
}
break;
} }
}, },
@ -212,10 +228,58 @@ module.exports = React.createClass({
return s; return s;
}, },
_getScrollNode: function() {
var panel = ReactDOM.findDOMNode(this);
if (!panel) return null;
if (panel.classList.contains('gm-prevented')) {
return panel;
} else {
return panel.children[2]; // XXX: Fragile!
}
},
_repositionTooltips: function(e) {
this._repositionTooltip(e);
this._repositionIncomingCallBox(e, false);
},
_repositionTooltip: function(e) { _repositionTooltip: function(e) {
if (this.tooltip && this.tooltip.parentElement) { if (this.tooltip && this.tooltip.parentElement) {
var scroll = ReactDOM.findDOMNode(this); var scroll = ReactDOM.findDOMNode(this);
this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.children[2].scrollTop) + "px"; this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - this._getScrollNode().scrollTop) + "px";
}
},
_repositionIncomingCallBox: function(e, firstTime) {
var incomingCallBox = document.getElementById("incomingCallBox");
if (incomingCallBox && incomingCallBox.parentElement) {
var scroll = this._getScrollNode();
var top = (scroll.offsetTop + incomingCallBox.parentElement.offsetTop - scroll.scrollTop);
if (firstTime) {
// scroll to make sure the callbox is on the screen...
if (top < 10) { // 10px of vertical margin at top of screen
scroll.scrollTop = incomingCallBox.parentElement.offsetTop - 10;
}
else if (top > scroll.clientHeight - incomingCallBox.offsetHeight + 50) {
scroll.scrollTop = incomingCallBox.parentElement.offsetTop - scroll.offsetHeight + incomingCallBox.offsetHeight - 50;
}
// recalculate top in case we clipped it.
top = (scroll.offsetTop + incomingCallBox.parentElement.offsetTop - scroll.scrollTop);
}
else {
// stop the box from scrolling off the screen
if (top < 10) {
top = 10;
}
else if (top > scroll.clientHeight - incomingCallBox.offsetHeight + 50) {
top = scroll.clientHeight - incomingCallBox.offsetHeight + 50;
}
}
incomingCallBox.style.top = top + "px";
incomingCallBox.style.left = scroll.offsetLeft + scroll.offsetWidth + "px";
} }
}, },
@ -234,7 +298,7 @@ module.exports = React.createClass({
var self = this; var self = this;
return ( return (
<GeminiScrollbar className="mx_RoomList_scrollbar" autoshow={true} onScroll={self._repositionTooltip}> <GeminiScrollbar className="mx_RoomList_scrollbar" autoshow={true} onScroll={ self._repositionTooltips }>
<div className="mx_RoomList"> <div className="mx_RoomList">
{ expandButton } { expandButton }
@ -244,6 +308,7 @@ module.exports = React.createClass({
order="recent" order="recent"
activityMap={ self.state.activityMap } activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
<RoomSubList list={ self.state.lists['m.favourite'] } <RoomSubList list={ self.state.lists['m.favourite'] }
@ -254,6 +319,7 @@ module.exports = React.createClass({
order="manual" order="manual"
activityMap={ self.state.activityMap } activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] } <RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
@ -263,6 +329,7 @@ module.exports = React.createClass({
order="recent" order="recent"
activityMap={ self.state.activityMap } activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
{ Object.keys(self.state.lists).map(function(tagName) { { Object.keys(self.state.lists).map(function(tagName) {
@ -276,6 +343,7 @@ module.exports = React.createClass({
order="manual" order="manual"
activityMap={ self.state.activityMap } activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
} }
@ -290,6 +358,7 @@ module.exports = React.createClass({
bottommost={ self.state.lists['im.vector.fake.archived'].length === 0 } bottommost={ self.state.lists['im.vector.fake.archived'].length === 0 }
activityMap={ self.state.activityMap } activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
<RoomSubList list={ self.state.lists['im.vector.fake.archived'] } <RoomSubList list={ self.state.lists['im.vector.fake.archived'] }
@ -299,6 +368,7 @@ module.exports = React.createClass({
bottommost={ true } bottommost={ true }
activityMap={ self.state.activityMap } activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
</div> </div>
</GeminiScrollbar> </GeminiScrollbar>

View file

@ -38,6 +38,7 @@ module.exports = React.createClass({
highlight: React.PropTypes.bool.isRequired, highlight: React.PropTypes.bool.isRequired,
isInvite: React.PropTypes.bool.isRequired, isInvite: React.PropTypes.bool.isRequired,
roomSubList: React.PropTypes.object.isRequired, roomSubList: React.PropTypes.object.isRequired,
incomingCall: React.PropTypes.object,
}, },
getInitialState: function() { getInitialState: function() {
@ -105,6 +106,12 @@ module.exports = React.createClass({
label = <RoomTooltip room={this.props.room}/>; label = <RoomTooltip room={this.props.room}/>;
} }
var incomingCallBox;
if (this.props.incomingCall) {
var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
incomingCallBox = <IncomingCallBox incomingCall={ this.props.incomingCall }/>;
}
var RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
// These props are injected by React DnD, // These props are injected by React DnD,
@ -120,6 +127,7 @@ module.exports = React.createClass({
{ badge } { badge }
</div> </div>
{ label } { label }
{ incomingCallBox }
</div> </div>
)); ));
} }

View file

@ -35,19 +35,13 @@ module.exports = React.createClass({
componentDidMount: function() { componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
this._trackedRoom = null;
if (this.props.room) { if (this.props.room) {
this._trackedRoom = this.props.room; this.showCall(this.props.room.roomId);
this.showCall(this._trackedRoom.roomId);
} }
else { else {
// XXX: why would we ever not have a this.props.room?
var call = CallHandler.getAnyActiveCall(); var call = CallHandler.getAnyActiveCall();
if (call) { if (call) {
console.log(
"Global CallView is now tracking active call in room %s",
call.roomId
);
this._trackedRoom = MatrixClientPeg.get().getRoom(call.roomId);
this.showCall(call.roomId); this.showCall(call.roomId);
} }
} }
@ -81,7 +75,7 @@ module.exports = React.createClass({
// and for the voice stream of screen captures // and for the voice stream of screen captures
call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement()); call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement());
} }
if (call && call.type === "video" && call.state !== 'ended') { if (call && call.type === "video" && call.call_state !== "ended" && call.call_state !== "ringing") {
// if this call is a conf call, don't display local video as the // if this call is a conf call, don't display local video as the
// conference will have us in it // conference will have us in it
this.getVideoView().getLocalVideoElement().style.display = ( this.getVideoView().getLocalVideoElement().style.display = (

View file

@ -21,87 +21,29 @@ var CallHandler = require("../../../CallHandler");
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'IncomingCallBox', displayName: 'IncomingCallBox',
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
getInitialState: function() {
return {
incomingCall: null
}
},
onAction: function(payload) {
if (payload.action !== 'call_state') {
return;
}
var call = CallHandler.getCall(payload.room_id);
if (!call || call.call_state !== 'ringing') {
this.setState({
incomingCall: null,
});
this.getRingAudio().pause();
return;
}
if (call.call_state === "ringing") {
this.getRingAudio().load();
this.getRingAudio().play();
}
else {
this.getRingAudio().pause();
}
this.setState({
incomingCall: call
});
},
onAnswerClick: function() { onAnswerClick: function() {
dis.dispatch({ dis.dispatch({
action: 'answer', action: 'answer',
room_id: this.state.incomingCall.roomId room_id: this.props.incomingCall.roomId
}); });
}, },
onRejectClick: function() { onRejectClick: function() {
dis.dispatch({ dis.dispatch({
action: 'hangup', action: 'hangup',
room_id: this.state.incomingCall.roomId room_id: this.props.incomingCall.roomId
}); });
}, },
getRingAudio: function() {
return this.refs.ringAudio;
},
render: function() { render: function() {
// NB: This block MUST have a "key" so React doesn't clobber the elements
// between in-call / not-in-call.
var audioBlock = (
<audio ref="ringAudio" key="voip_ring_audio" loop>
<source src="media/ring.ogg" type="audio/ogg" />
<source src="media/ring.mp3" type="audio/mpeg" />
</audio>
);
if (!this.state.incomingCall || !this.state.incomingCall.roomId) { var room = this.props.incomingCall ? MatrixClientPeg.get().getRoom(this.props.incomingCall.roomId) : null;
return ( var caller = room ? room.name : "unknown";
<div>
{audioBlock}
</div>
);
}
var caller = MatrixClientPeg.get().getRoom(this.state.incomingCall.roomId).name;
return ( return (
<div className="mx_IncomingCallBox"> <div className="mx_IncomingCallBox" id="incomingCallBox">
{audioBlock}
<img className="mx_IncomingCallBox_chevron" src="img/chevron-left.png" width="9" height="16" /> <img className="mx_IncomingCallBox_chevron" src="img/chevron-left.png" width="9" height="16" />
<div className="mx_IncomingCallBox_title"> <div className="mx_IncomingCallBox_title">
Incoming { this.state.incomingCall ? this.state.incomingCall.type : '' } call from { caller } Incoming { this.props.incomingCall ? this.props.incomingCall.type : '' } call from { caller }
</div> </div>
<div className="mx_IncomingCallBox_buttons"> <div className="mx_IncomingCallBox_buttons">
<div className="mx_IncomingCallBox_buttons_cell"> <div className="mx_IncomingCallBox_buttons_cell">