merge in develop

This commit is contained in:
Matthew Hodgson 2015-12-13 14:47:53 +00:00
commit 5ce917ba84
6 changed files with 150 additions and 80 deletions

View file

@ -29,6 +29,7 @@ var Login = require("./login/Login");
var Registration = require("./login/Registration"); var Registration = require("./login/Registration");
var PostRegistration = require("./login/PostRegistration"); var PostRegistration = require("./login/PostRegistration");
var Modal = require("../../Modal");
var sdk = require('../../index'); var sdk = require('../../index');
var MatrixTools = require('../../MatrixTools'); var MatrixTools = require('../../MatrixTools');
var linkifyMatrix = require("../../linkify-matrix"); var linkifyMatrix = require("../../linkify-matrix");
@ -203,6 +204,36 @@ module.exports = React.createClass({
self.setState({errorText: 'Login failed.'}); self.setState({errorText: 'Login failed.'});
}); });
break;
case 'leave_room':
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
var roomId = payload.room_id;
Modal.createDialog(QuestionDialog, {
title: "Leave room",
description: "Are you sure you want to leave the room?",
onFinished: function(should_leave) {
if (should_leave) {
var d = MatrixClientPeg.get().leave(roomId);
// FIXME: controller shouldn't be loading a view :(
var Loader = sdk.getComponent("elements.Spinner");
var modal = Modal.createDialog(Loader);
d.then(function() {
modal.close();
dis.dispatch({action: 'view_next_room'});
}, function(err) {
modal.close();
Modal.createDialog(ErrorDialog, {
title: "Failed to leave room",
description: err.toString()
});
});
}
}
});
break; break;
case 'view_room': case 'view_room':
this._viewRoom(payload.room_id); this._viewRoom(payload.room_id);

View file

@ -58,7 +58,7 @@ module.exports = React.createClass({
searching: false, searching: false,
searchResults: null, searchResults: null,
syncState: MatrixClientPeg.get().getSyncState(), syncState: MatrixClientPeg.get().getSyncState(),
hasUnsentMessages: this._hasUnsentMessages(room) hasUnsentMessages: this._hasUnsentMessages(room),
} }
}, },
@ -90,6 +90,8 @@ module.exports = React.createClass({
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange); MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange);
} }
window.removeEventListener('resize', this.onResize);
}, },
onAction: function(payload) { onAction: function(payload) {
@ -272,6 +274,9 @@ module.exports = React.createClass({
} }
this._updateConfCallNotification(); this._updateConfCallNotification();
window.addEventListener('resize', this.onResize);
this.onResize();
}, },
componentDidUpdate: function() { componentDidUpdate: function() {
@ -426,6 +431,10 @@ module.exports = React.createClass({
} }
var self = this; var self = this;
self.setState({
searchInProgress: true
});
MatrixClientPeg.get().search({ MatrixClientPeg.get().search({
body: { body: {
search_categories: { search_categories: {
@ -450,6 +459,12 @@ module.exports = React.createClass({
} }
} }
}).then(function(data) { }).then(function(data) {
if (!self.state.searching || term !== self.refs.search_bar.refs.search_term.value) {
console.error("Discarding stale search results");
return;
}
// for debugging: // for debugging:
// data.search_categories.room_events.highlights = ["hello", "everybody"]; // data.search_categories.room_events.highlights = ["hello", "everybody"];
@ -470,8 +485,10 @@ module.exports = React.createClass({
self.setState({ self.setState({
highlights: highlights, highlights: highlights,
searchTerm: term,
searchResults: data, searchResults: data,
searchScope: scope, searchScope: scope,
searchCount: data.search_categories.room_events.count,
}); });
}, function(error) { }, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -479,6 +496,10 @@ module.exports = React.createClass({
title: "Search failed", title: "Search failed",
description: error.toString() description: error.toString()
}); });
}).finally(function() {
self.setState({
searchInProgress: false
});
}); });
}, },
@ -492,10 +513,14 @@ module.exports = React.createClass({
var EventTile = sdk.getComponent('rooms.EventTile'); var EventTile = sdk.getComponent('rooms.EventTile');
var self = this; var self = this;
if (this.state.searchResults && if (this.state.searchResults)
this.state.searchResults.search_categories.room_events.results &&
this.state.searchResults.search_categories.room_events.groups)
{ {
if (!this.state.searchResults.search_categories.room_events.results ||
!this.state.searchResults.search_categories.room_events.groups)
{
return ret;
}
// XXX: this dance is foul, due to the results API not directly returning sorted results // XXX: this dance is foul, due to the results API not directly returning sorted results
var results = this.state.searchResults.search_categories.room_events.results; var results = this.state.searchResults.search_categories.room_events.results;
var roomIdGroups = this.state.searchResults.search_categories.room_events.groups.room_id; var roomIdGroups = this.state.searchResults.search_categories.room_events.groups.room_id;
@ -763,6 +788,14 @@ module.exports = React.createClass({
this.setState(this.getInitialState()); this.setState(this.getInitialState());
}, },
onLeaveClick: function() {
dis.dispatch({
action: 'leave_room',
room_id: this.props.roomId,
});
this.props.onFinished();
},
onRejectButtonClicked: function(ev) { onRejectButtonClicked: function(ev) {
var self = this; var self = this;
this.setState({ this.setState({
@ -912,6 +945,27 @@ module.exports = React.createClass({
} }
}, },
onResize: function(e) {
// It seems flexbox doesn't give us a way to constrain the auxPanel height to have
// a minimum of the height of the video element, whilst also capping it from pushing out the page
// so we have to do it via JS instead. In this implementation we cap the height by putting
// a maxHeight on the underlying remote video tag.
var auxPanelMaxHeight;
if (this.refs.callView) {
// XXX: don't understand why we have to call findDOMNode here in react 0.14 - it should already be a DOM node.
var video = ReactDOM.findDOMNode(this.refs.callView.refs.video.refs.remote);
// header + footer + status + give us at least 100px of scrollback at all times.
auxPanelMaxHeight = window.innerHeight - (83 + 72 + 36 + 100);
// XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway
// but it's better than the video going missing entirely
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
video.style.maxHeight = auxPanelMaxHeight + "px";
}
},
render: function() { render: function() {
var RoomHeader = sdk.getComponent('rooms.RoomHeader'); var RoomHeader = sdk.getComponent('rooms.RoomHeader');
var MessageComposer = sdk.getComponent('rooms.MessageComposer'); var MessageComposer = sdk.getComponent('rooms.MessageComposer');
@ -1051,7 +1105,7 @@ module.exports = React.createClass({
aux = <Loader/>; aux = <Loader/>;
} }
else if (this.state.searching) { else if (this.state.searching) {
aux = <SearchBar ref="search_bar" onCancelClick={this.onCancelClick} onSearch={this.onSearch}/>; aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress } onCancelClick={this.onCancelClick} onSearch={this.onSearch}/>;
} }
var conferenceCallNotification = null; var conferenceCallNotification = null;
@ -1071,30 +1125,37 @@ module.exports = React.createClass({
if (this.state.draggingFile) { if (this.state.draggingFile) {
fileDropTarget = <div className="mx_RoomView_fileDropTarget"> fileDropTarget = <div className="mx_RoomView_fileDropTarget">
<div className="mx_RoomView_fileDropTargetLabel"> <div className="mx_RoomView_fileDropTargetLabel">
<img src="img/upload.svg" width="43" height="57" alt="Drop File Here"/><br/> <img src="img/upload-big.svg" width="45" height="59" alt="Drop File Here"/><br/>
Drop File Here Drop File Here
</div> </div>
</div>; </div>;
} }
var messageComposer; var messageComposer, searchInfo;
if (!this.state.searchResults) { if (!this.state.searchResults) {
messageComposer = messageComposer =
<MessageComposer room={this.state.room} roomView={this} uploadFile={this.uploadFile} /> <MessageComposer room={this.state.room} roomView={this} uploadFile={this.uploadFile} />
} }
else {
searchInfo = {
searchTerm : this.state.searchTerm,
searchScope : this.state.searchScope,
searchCount : this.state.searchCount,
}
}
return ( return (
<div className="mx_RoomView"> <div className="mx_RoomView">
<RoomHeader ref="header" room={this.state.room} editing={this.state.editingRoomSettings} onSearchClick={this.onSearchClick} <RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo} editing={this.state.editingRoomSettings} onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick} onSaveClick={this.onSaveClick} onCancelClick={this.onCancelClick} /> onSettingsClick={this.onSettingsClick} onSaveClick={this.onSaveClick} onCancelClick={this.onCancelClick} onLeaveClick={this.onLeaveClick} />
{ fileDropTarget }
<div className="mx_RoomView_auxPanel"> <div className="mx_RoomView_auxPanel">
<CallView room={this.state.room} ConferenceHandler={this.props.ConferenceHandler}/> <CallView ref="callView" room={this.state.room} ConferenceHandler={this.props.ConferenceHandler}/>
{ conferenceCallNotification } { conferenceCallNotification }
{ aux } { aux }
</div> </div>
<GeminiScrollbar autoshow={true} ref="messagePanel" className="mx_RoomView_messagePanel" onScroll={ this.onMessageListScroll }> <GeminiScrollbar autoshow={true} ref="messagePanel" className="mx_RoomView_messagePanel" onScroll={ this.onMessageListScroll }>
<div className="mx_RoomView_messageListWrapper"> <div className="mx_RoomView_messageListWrapper">
{ fileDropTarget }
<ol className="mx_RoomView_MessageList" aria-live="polite"> <ol className="mx_RoomView_MessageList" aria-live="polite">
<li className={scrollheader_classes}> <li className={scrollheader_classes}>
</li> </li>

View file

@ -226,36 +226,10 @@ module.exports = React.createClass({
} }
}, },
// FIXME: this is horribly duplicated with MemberTile's onLeaveClick.
// Not sure what the right solution to this is.
onLeaveClick: function() { onLeaveClick: function() {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); dis.dispatch({
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); action: 'leave_room',
room_id: this.props.member.roomId,
var roomId = this.props.member.roomId;
Modal.createDialog(QuestionDialog, {
title: "Leave room",
description: "Are you sure you want to leave the room?",
onFinished: function(should_leave) {
if (should_leave) {
var d = MatrixClientPeg.get().leave(roomId);
// FIXME: controller shouldn't be loading a view :(
var Loader = sdk.getComponent("elements.Spinner");
var modal = Modal.createDialog(Loader);
d.then(function() {
modal.close();
dis.dispatch({action: 'view_next_room'});
}, function(err) {
modal.close();
Modal.createDialog(ErrorDialog, {
title: "Failed to leave room",
description: err.toString()
});
});
}
}
}); });
this.props.onFinished(); this.props.onFinished();
}, },

View file

@ -31,33 +31,11 @@ module.exports = React.createClass({
}, },
onLeaveClick: function() { onLeaveClick: function() {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); dis.dispatch({
action: 'leave_room',
var roomId = this.props.member.roomId; room_id: this.props.member.roomId,
Modal.createDialog(QuestionDialog, {
title: "Leave room",
description: "Are you sure you want to leave the room?",
onFinished: function(should_leave) {
if (should_leave) {
var d = MatrixClientPeg.get().leave(roomId);
// FIXME: controller shouldn't be loading a view :(
var Loader = sdk.getComponent("elements.Spinner");
var modal = Modal.createDialog(Loader);
d.then(function() {
modal.close();
dis.dispatch({action: 'view_next_room'});
}, function(err) {
modal.close();
Modal.createDialog(ErrorDialog, {
title: "Failed to leave room",
description: err.toString()
});
});
}
}
}); });
this.props.onFinished();
}, },
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate: function(nextProps, nextState) {

View file

@ -35,6 +35,8 @@ module.exports = React.createClass({
editing: React.PropTypes.bool, editing: React.PropTypes.bool,
onSettingsClick: React.PropTypes.func, onSettingsClick: React.PropTypes.func,
onSaveClick: React.PropTypes.func, onSaveClick: React.PropTypes.func,
onSearchClick: React.PropTypes.func,
onLeaveClick: React.PropTypes.func,
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -187,6 +189,7 @@ module.exports = React.createClass({
} }
var name = null; var name = null;
var searchStatus = null;
var topic_el = null; var topic_el = null;
var cancel_button = null; var cancel_button = null;
var save_button = null; var save_button = null;
@ -203,9 +206,16 @@ module.exports = React.createClass({
save_button = <div className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save Changes</div> save_button = <div className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save Changes</div>
} else { } else {
// <EditableText label={this.props.room.name} initialValue={actual_name} placeHolder="Name" onValueChanged={this.onNameChange} /> // <EditableText label={this.props.room.name} initialValue={actual_name} placeHolder="Name" onValueChanged={this.onNameChange} />
var searchStatus;
if (this.props.searchInfo && this.props.searchInfo.searchTerm) {
searchStatus = <div className="mx_RoomHeader_searchStatus">&nbsp;({ this.props.searchInfo.searchCount } results)</div>;
}
name = name =
<div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}> <div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}>
<div className="mx_RoomHeader_nametext">{ this.props.room.name }</div> <div className="mx_RoomHeader_nametext" title={ this.props.room.name }>{ this.props.room.name }</div>
{ searchStatus }
<div className="mx_RoomHeader_settingsButton"> <div className="mx_RoomHeader_settingsButton">
<img src="img/settings.svg" width="12" height="12"/> <img src="img/settings.svg" width="12" height="12"/>
</div> </div>
@ -233,9 +243,21 @@ module.exports = React.createClass({
<div className="mx_RoomHeader_button mx_RoomHeader_video" onClick={activeCall && activeCall.type === "video" ? this.onMuteVideoClick : this.onVideoClick}> <div className="mx_RoomHeader_button mx_RoomHeader_video" onClick={activeCall && activeCall.type === "video" ? this.onMuteVideoClick : this.onVideoClick}>
<img src="img/video.png" title="Video call" alt="Video call" width="32" height="32" style={{ 'marginTop': '-8px' }}/> <img src="img/video.png" title="Video call" alt="Video call" width="32" height="32" style={{ 'marginTop': '-8px' }}/>
</div>; </div>;
var img = "img/voip.png";
if (activeCall.isMicrophoneMuted()) {
img = "img/voip-mute.png";
}
voice_button = voice_button =
<div className="mx_RoomHeader_button mx_RoomHeader_voice" onClick={activeCall ? this.onMuteAudioClick : this.onVoiceClick}> <div className="mx_RoomHeader_button mx_RoomHeader_voice" onClick={activeCall ? this.onMuteAudioClick : this.onVoiceClick}>
<img src="img/voip.png" title="VoIP call" alt="VoIP call" width="32" height="32" style={{ 'marginTop': '-8px' }}/> <img src={img} title="VoIP call" alt="VoIP call" width="32" height="32" style={{ 'marginTop': '-8px' }}/>
</div>;
}
var exit_button;
if (this.props.onLeaveClick) {
exit_button =
<div className="mx_RoomHeader_button mx_RoomHeader_leaveButton">
<img src="img/leave.svg" title="Leave room" alt="Leave room" width="26" height="20" onClick={this.props.onLeaveClick}/>
</div>; </div>;
} }
@ -257,6 +279,7 @@ module.exports = React.createClass({
{ video_button } { video_button }
{ voice_button } { voice_button }
{ zoom_button } { zoom_button }
{ exit_button }
<div className="mx_RoomHeader_button"> <div className="mx_RoomHeader_button">
<img src="img/search.svg" title="Search" alt="Search" width="21" height="19" onClick={this.props.onSearchClick}/> <img src="img/search.svg" title="Search" alt="Search" width="21" height="19" onClick={this.props.onSearchClick}/>
</div> </div>

View file

@ -103,8 +103,11 @@ module.exports = React.createClass({
hl = 1; hl = 1;
} }
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev); var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
if (actions && actions.tweaks && actions.tweaks.highlight) { if ((actions && actions.tweaks && actions.tweaks.highlight) ||
(me && me.membership == "invite"))
{
hl = 2; hl = 2;
} }
} }
@ -153,17 +156,17 @@ module.exports = React.createClass({
var self = this; var self = this;
var s = { lists: {} }; var s = { lists: {} };
s.lists["m.invite"] = []; s.lists["im.vector.fake.invite"] = [];
s.lists["m.favourite"] = []; s.lists["m.favourite"] = [];
s.lists["m.recent"] = []; s.lists["im.vector.fake.recent"] = [];
s.lists["m.lowpriority"] = []; s.lists["m.lowpriority"] = [];
s.lists["m.archived"] = []; s.lists["im.vector.fake.archived"] = [];
MatrixClientPeg.get().getRooms().forEach(function(room) { MatrixClientPeg.get().getRooms().forEach(function(room) {
var me = room.getMember(MatrixClientPeg.get().credentials.userId); var me = room.getMember(MatrixClientPeg.get().credentials.userId);
if (me && me.membership == "invite") { if (me && me.membership == "invite") {
s.lists["m.invite"].push(room); s.lists["im.vector.fake.invite"].push(room);
} }
else { else {
var shouldShowRoom = ( var shouldShowRoom = (
@ -196,13 +199,13 @@ module.exports = React.createClass({
} }
} }
else { else {
s.lists["m.recent"].push(room); s.lists["im.vector.fake.recent"].push(room);
} }
} }
} }
}); });
//console.log("calculated new roomLists; m.recent = " + s.lists["m.recent"]); //console.log("calculated new roomLists; im.vector.fake.recent = " + s.lists["im.vector.fake.recent"]);
// we actually apply the sorting to this when receiving the prop in RoomSubLists. // we actually apply the sorting to this when receiving the prop in RoomSubLists.
@ -235,7 +238,7 @@ module.exports = React.createClass({
<div className="mx_RoomList"> <div className="mx_RoomList">
{ expandButton } { expandButton }
<RoomSubList list={ self.state.lists['m.invite'] } <RoomSubList list={ self.state.lists['im.vector.fake.invite'] }
label="Invites" label="Invites"
editable={ false } editable={ false }
order="recent" order="recent"
@ -253,7 +256,7 @@ module.exports = React.createClass({
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
<RoomSubList list={ self.state.lists['m.recent'] } <RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
label="Conversations" label="Conversations"
editable={ true } editable={ true }
verb="restore" verb="restore"
@ -263,7 +266,7 @@ module.exports = React.createClass({
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
{ Object.keys(self.state.lists).map(function(tagName) { { Object.keys(self.state.lists).map(function(tagName) {
if (!tagName.match(/^m\.(invite|favourite|recent|lowpriority|archived)$/)) { if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|archived))$/)) {
return <RoomSubList list={ self.state.lists[tagName] } return <RoomSubList list={ self.state.lists[tagName] }
key={ tagName } key={ tagName }
label={ tagName } label={ tagName }
@ -284,12 +287,12 @@ module.exports = React.createClass({
verb="demote" verb="demote"
editable={ true } editable={ true }
order="recent" order="recent"
bottommost={ self.state.lists['m.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 }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
<RoomSubList list={ self.state.lists['m.archived'] } <RoomSubList list={ self.state.lists['im.vector.fake.archived'] }
label="Historical" label="Historical"
editable={ false } editable={ false }
order="recent" order="recent"