Merge branch 'develop' into rav/update_status_bar

This commit is contained in:
Matthew Hodgson 2016-02-17 18:38:47 +00:00
commit b087157855
21 changed files with 229 additions and 182 deletions

View file

@ -175,7 +175,7 @@ module.exports = React.createClass({
guest: true
});
}, function(err) {
console.error(err.data);
console.error("Failed to register as guest: " + err + " " + err.data);
self._setAutoRegisterAsGuest(false);
});
},
@ -316,9 +316,6 @@ module.exports = React.createClass({
});
break;
case 'view_room':
// by default we autoPeek rooms, unless we were called explicitly with
// autoPeek=false by something like RoomDirectory who has already peeked
this.setState({ autoPeek : payload.auto_peek === false ? false : true });
this._viewRoom(payload.room_id, payload.show_settings, payload.event_id);
break;
case 'view_prev_room':
@ -880,7 +877,6 @@ module.exports = React.createClass({
eventId={this.state.initialEventId}
highlightedEventId={this.state.highlightedEventId}
eventPixelOffset={this.state.initialEventPixelOffset}
autoPeek={this.state.autoPeek}
key={this.state.currentRoom}
ConferenceHandler={this.props.ConferenceHandler} />
);
@ -974,7 +970,9 @@ module.exports = React.createClass({
onRegisterClick={this.onRegisterClick}
homeserverUrl={this.props.config.default_hs_url}
identityServerUrl={this.props.config.default_is_url}
onForgotPasswordClick={this.onForgotPasswordClick} />
onForgotPasswordClick={this.onForgotPasswordClick}
onLoginAsGuestClick={this.props.enableGuest && this.props.config && this.props.config.default_hs_url ? this._registerAsGuest: undefined}
/>
);
}
}

View file

@ -64,7 +64,10 @@ module.exports = React.createClass({
},
componentWillUnmount: function() {
MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange);
// we may have entirely lost our client as we're logging out before clicking login on the guest bar...
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange);
}
},
onSyncStateChange: function(state, prevState) {

View file

@ -60,12 +60,11 @@ module.exports = React.createClass({
displayName: 'RoomView',
propTypes: {
ConferenceHandler: React.PropTypes.any,
roomId: React.PropTypes.string,
autoPeek: React.PropTypes.bool, // Now unused, left here temporarily to avoid merge conflicts with @richvdh's branch.
roomId: React.PropTypes.string.isRequired,
// id of an event to jump to. If not given, will use the read-up-to-marker.
// id of an event to jump to. If not given, will go to the end of the
// live timeline.
eventId: React.PropTypes.string,
// where to position the event given by eventId, in pixels from the
@ -76,14 +75,6 @@ module.exports = React.createClass({
// ID of an event to highlight. If undefined, no event will be highlighted.
// Typically this will either be the same as 'eventId', or undefined.
highlightedEventId: React.PropTypes.string,
autoPeek: React.PropTypes.bool, // should we try to peek the room on mount, or has whoever invoked us already initiated a peek?
},
getDefaultProps: function() {
return {
autoPeek: true,
}
},
/* properties in RoomView objects include:
@ -155,11 +146,6 @@ module.exports = React.createClass({
// We can /peek though. If it fails then we present the join UI. If it
// succeeds then great, show the preview (but we still may be able to /join!).
if (!this.state.room) {
if (!this.props.autoPeek) {
console.log("No room loaded, and autopeek disabled");
return;
}
console.log("Attempting to peek into room %s", this.props.roomId);
roomProm = MatrixClientPeg.get().peekInRoom(this.props.roomId).then((room) => {
@ -193,11 +179,6 @@ module.exports = React.createClass({
_initTimeline: function(props) {
var initialEvent = props.eventId;
if (!initialEvent) {
// go to the 'read-up-to' mark if no explicit event given
initialEvent = this.state.readMarkerEventId;
}
var pixelOffset = props.eventPixelOffset;
return this._loadTimeline(initialEvent, pixelOffset);
},
@ -486,20 +467,6 @@ module.exports = React.createClass({
readMarkerEventId: readMarkerEventId,
readMarkerGhostEventId: readMarkerGhostEventId,
});
// if the scrollpanel is following the timeline, attempt to scroll
// it to bring the read message up to the middle of the panel. This
// will have no immediate effect (since we are already at the
// bottom), but will ensure that if there is no further user
// activity, but room activity continues, the read message will
// scroll up to the middle of the window, but no further.
//
// we do this here as well as in sendReadReceipt to deal with
// people using two clients at once.
if (this.refs.messagePanel && this.state.atEndOfLiveTimeline) {
this.refs.messagePanel.scrollToToken(readMarkerEventId);
}
}
},
@ -585,14 +552,6 @@ module.exports = React.createClass({
window.addEventListener('resize', this.onResize);
this.onResize();
if (this.refs.roomView) {
var roomView = ReactDOM.findDOMNode(this.refs.roomView);
roomView.addEventListener('drop', this.onDrop);
roomView.addEventListener('dragover', this.onDragOver);
roomView.addEventListener('dragleave', this.onDragLeaveOrEnd);
roomView.addEventListener('dragend', this.onDragLeaveOrEnd);
}
this._updateTabCompleteList();
// XXX: EVIL HACK to autofocus inviting on empty rooms.
@ -630,6 +589,16 @@ module.exports = React.createClass({
// separate component to avoid this ridiculous dance.
if (!this.refs.messagePanel) return;
if (this.refs.roomView) {
var roomView = ReactDOM.findDOMNode(this.refs.roomView);
if (!roomView.ondrop) {
roomView.addEventListener('drop', this.onDrop);
roomView.addEventListener('dragover', this.onDragOver);
roomView.addEventListener('dragleave', this.onDragLeaveOrEnd);
roomView.addEventListener('dragend', this.onDragLeaveOrEnd);
}
}
if (!this.refs.messagePanel.initialised) {
this._initialiseMessagePanel();
}
@ -1159,19 +1128,6 @@ module.exports = React.createClass({
// it failed, so allow retries next time the user is active
this.last_rr_sent_event_id = undefined;
});
// if the scrollpanel is following the timeline, attempt to scroll
// it to bring the read message up to the middle of the panel. This
// will have no immediate effect (since we are already at the
// bottom), but will ensure that if there is no further user
// activity, but room activity continues, the read message will
// scroll up to the middle of the window, but no further.
//
// we do this here as well as in onRoomReceipt to cater for guest users
// (which do not send out read receipts).
if (this.state.atEndOfLiveTimeline) {
this.refs.messagePanel.scrollToToken(lastReadEvent.getId());
}
}
},
@ -1275,11 +1231,19 @@ module.exports = React.createClass({
self.setState({
rejecting: false
});
}, function(err) {
console.error("Failed to reject invite: %s", err);
}, function(error) {
console.error("Failed to reject invite: %s", error);
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to reject invite",
description: msg
});
self.setState({
rejecting: false,
rejectError: err
rejectError: error
});
});
},
@ -1473,14 +1437,14 @@ module.exports = React.createClass({
);
}
else {
var joinErrorText = this.state.joinError ? "Failed to join room!" : "";
return (
<div className="mx_RoomView">
<RoomHeader ref="header" room={this.state.room} simpleHeader="Join room"/>
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
canJoin={ true } canPreview={ false }/>
<div className="error">{joinErrorText}</div>
canJoin={ true } canPreview={ false }
spinner={this.state.joining}
/>
</div>
<div className="mx_RoomView_messagePanel"></div>
</div>
@ -1506,10 +1470,6 @@ module.exports = React.createClass({
} else {
var inviteEvent = myMember.events.member;
var inviterName = inviteEvent.sender ? inviteEvent.sender.name : inviteEvent.getSender();
// XXX: Leaving this intentionally basic for now because invites are about to change totally
// FIXME: This comment is now outdated - what do we need to fix? ^
var joinErrorText = this.state.joinError ? "Failed to join room!" : "";
var rejectErrorText = this.state.rejectError ? "Failed to reject invite!" : "";
// We deliberately don't try to peek into invites, even if we have permission to peek
// as they could be a spam vector.
@ -1522,9 +1482,9 @@ module.exports = React.createClass({
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
onRejectClick={ this.onRejectButtonClicked }
inviterName={ inviterName }
canJoin={ true } canPreview={ false }/>
<div className="error">{joinErrorText}</div>
<div className="error">{rejectErrorText}</div>
canJoin={ true } canPreview={ false }
spinner={this.state.joining}
/>
</div>
<div className="mx_RoomView_messagePanel"></div>
</div>
@ -1588,13 +1548,17 @@ module.exports = React.createClass({
else if (this.state.guestsCanJoin && MatrixClientPeg.get().isGuest() &&
(!myMember || myMember.membership !== "join")) {
aux = (
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} canJoin={true} />
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} canJoin={true}
spinner={this.state.joining}
/>
);
}
else if (this.state.canPeek &&
(!myMember || myMember.membership !== "join")) {
aux = (
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} canJoin={true} />
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} canJoin={true}
spinner={this.state.joining}
/>
);
}
@ -1714,15 +1678,22 @@ module.exports = React.createClass({
</div>
);
} else {
// it's important that stickyBottom = false on this, otherwise if somebody hits the
// bottom of the loaded events when viewing historical messages, we get stuck in a
// loop of paginating our way through the entire history of the room.
// give the messagepanel a stickybottom if we're at the end of the
// live timeline, so that the arrival of new events triggers a
// scroll.
//
// Make sure that stickyBottom is *false* if we can paginate
// forwards, otherwise if somebody hits the bottom of the loaded
// events when viewing historical messages, we get stuck in a loop
// of paginating our way through the entire history of the room.
var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
messagePanel = (
<ScrollPanel ref="messagePanel" className="mx_RoomView_messagePanel"
onScroll={ this.onMessageListScroll }
onFillRequest={ this.onMessageListFillRequest }
style={ hideMessagePanel ? { display: 'none' } : {} }
stickyBottom={ false }>
stickyBottom={ stickyBottom }>
<li className={scrollheader_classes}></li>
{this.getEventTiles()}
</ScrollPanel>

View file

@ -315,6 +315,16 @@ module.exports = React.createClass({
onFinished={this.onPasswordChanged} />
);
}
var notification_area;
if (!MatrixClientPeg.get().isGuest()) {
notification_area = (<div>
<h2>Notifications</h2>
<div className="mx_UserSettings_section">
<Notifications/>
</div>
</div>);
}
return (
<div className="mx_UserSettings">
@ -364,11 +374,7 @@ module.exports = React.createClass({
{accountJsx}
</div>
<h2>Notifications</h2>
<div className="mx_UserSettings_section">
<Notifications/>
</div>
{notification_area}
<h2>Advanced</h2>

View file

@ -35,7 +35,8 @@ module.exports = React.createClass({displayName: 'Login',
// login shouldn't know or care how registration is done.
onRegisterClick: React.PropTypes.func.isRequired,
// login shouldn't care how password recovery is done.
onForgotPasswordClick: React.PropTypes.func
onForgotPasswordClick: React.PropTypes.func,
onLoginAsGuestClick: React.PropTypes.func,
},
getDefaultProps: function() {
@ -167,6 +168,13 @@ module.exports = React.createClass({displayName: 'Login',
var LoginFooter = sdk.getComponent("login.LoginFooter");
var loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
var loginAsGuestJsx;
if (this.props.onLoginAsGuestClick) {
loginAsGuestJsx =
<a className="mx_Login_create" onClick={this.props.onLoginAsGuestClick} href="#">
Login as guest
</a>
}
return (
<div className="mx_Login">
<div className="mx_Login_box">
@ -188,6 +196,7 @@ module.exports = React.createClass({displayName: 'Login',
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
Create a new account
</a>
{ loginAsGuestJsx }
<br/>
<LoginFooter />
</div>

View file

@ -115,6 +115,9 @@ module.exports = React.createClass({
onProcessingRegistration: function(promise) {
var self = this;
promise.done(function(response) {
self.setState({
busy: false
});
if (!response || !response.access_token) {
console.warn(
"FIXME: Register fulfilled without a final response, " +
@ -126,7 +129,7 @@ module.exports = React.createClass({
if (!response || !response.user_id || !response.access_token) {
console.error("Final response is missing keys.");
self.setState({
errorText: "There was a problem processing the response."
errorText: "Registration failed on server"
});
return;
}
@ -136,9 +139,6 @@ module.exports = React.createClass({
identityServerUrl: self.registerLogic.getIdentityServerUrl(),
accessToken: response.access_token
});
self.setState({
busy: false
});
}, function(err) {
if (err.message) {
self.setState({

View file

@ -31,14 +31,22 @@ module.exports = React.createClass({
}
},
onKeyDown: function(e) {
if (e.keyCode === 27) { // escape
e.stopPropagation();
e.preventDefault();
this.cancelPrompt();
}
},
render: function() {
return (
<div>
<div className="mx_Dialog_content">
Sign out?
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.logOut}>Sign Out</button>
<div className="mx_Dialog_buttons" onKeyDown={ this.onKeyDown }>
<button autoFocus onClick={this.logOut}>Sign Out</button>
<button onClick={this.cancelPrompt}>Cancel</button>
</div>
</div>

View file

@ -26,9 +26,20 @@ module.exports = React.createClass({
},
getInitialState: function() {
return {
value: this.props.currentDisplayName || "Guest "+MatrixClientPeg.get().getUserIdLocalpart(),
if (this.props.currentDisplayName) {
return { value: this.props.currentDisplayName };
}
if (MatrixClientPeg.get().isGuest()) {
return { value : "Guest " + MatrixClientPeg.get().getUserIdLocalpart() };
}
else {
return { value : MatrixClientPeg.get().getUserIdLocalpart() };
}
},
componentDidMount: function() {
this.refs.input_value.select();
},
getValue: function() {
@ -54,11 +65,12 @@ module.exports = React.createClass({
Set a Display Name
</div>
<div className="mx_Dialog_content">
Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?
Your display name is how you'll appear to others when you speak in rooms.<br/>
What would you like it to be?
</div>
<form onSubmit={this.onFormSubmit}>
<div className="mx_Dialog_content">
<input type="text" value={this.state.value}
<input type="text" ref="input_value" value={this.state.value}
autoFocus={true} onChange={this.onValueChange} size="30"
className="mx_SetDisplayNameDialog_input"
/>

View file

@ -51,7 +51,7 @@ module.exports = React.createClass({
if (this.props.truncateAt >= 0) {
var overflowCount = childCount - this.props.truncateAt;
if (overflowCount > 0) {
if (overflowCount > 1) {
overflowJsx = this.props.createOverflowElement(
overflowCount, childCount
);

View file

@ -42,9 +42,11 @@ module.exports = React.createClass({
// TODO: Keep this list bleeding-edge up-to-date. Practically speaking,
// it will do for now not being updated as random new users join different
// rooms as this list will be reloaded every room swap.
this._userList = MatrixClientPeg.get().getUsers().filter((u) => {
return !this._room.hasMembershipState(u.userId, "join");
});
if (this._room) {
this._userList = MatrixClientPeg.get().getUsers().filter((u) => {
return !this._room.hasMembershipState(u.userId, "join");
});
}
},
onInvite: function(ev) {
@ -87,7 +89,7 @@ module.exports = React.createClass({
}
return (
<SearchableEntityList searchPlaceholderText={"Invite / Search"}
<SearchableEntityList searchPlaceholderText={"Invite/search by name, email, id"}
onSubmit={this.props.onInvite}
onQueryChanged={this.onSearchQueryChanged}
entities={entities}

View file

@ -35,21 +35,23 @@ var invite_defer = q.defer();
module.exports = React.createClass({
displayName: 'MemberList',
getInitialState: function() {
if (!this.props.roomId) return { members: [] };
var cli = MatrixClientPeg.get();
var room = cli.getRoom(this.props.roomId);
if (!room) return { members: [] };
this.memberDict = this.getMemberDict();
var members = this.roomMembers(INITIAL_LOAD_NUM_MEMBERS);
return {
members: members,
var state = {
members: [],
// ideally we'd size this to the page height, but
// in practice I find that a little constraining
truncateAt: INITIAL_LOAD_NUM_MEMBERS,
};
if (!this.props.roomId) return state;
var cli = MatrixClientPeg.get();
var room = cli.getRoom(this.props.roomId);
if (!room) return state;
this.memberDict = this.getMemberDict();
state.members = this.roomMembers(INITIAL_LOAD_NUM_MEMBERS);
return state;
},
componentWillMount: function() {
@ -325,7 +327,7 @@ module.exports = React.createClass({
var memberList = self.state.members.filter(function(userId) {
var m = self.memberDict[userId];
if (query && m.name.toLowerCase().indexOf(query) !== 0) {
if (query && m.name.toLowerCase().indexOf(query) === -1) {
return false;
}
return m.membership == membership;

View file

@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
var sdk = require('../../../index');
module.exports = React.createClass({
displayName: 'RoomPreviewBar',
@ -27,6 +28,7 @@ module.exports = React.createClass({
inviterName: React.PropTypes.string,
canJoin: React.PropTypes.bool,
canPreview: React.PropTypes.bool,
spinner: React.PropTypes.bool,
},
getDefaultProps: function() {
@ -40,6 +42,13 @@ module.exports = React.createClass({
render: function() {
var joinBlock, previewBlock;
if (this.props.spinner) {
var Spinner = sdk.getComponent("elements.Spinner");
return (<div className="mx_RoomPreviewBar">
<Spinner />
</div>);
}
if (this.props.inviterName) {
joinBlock = (
<div>

View file

@ -37,10 +37,12 @@ module.exports = React.createClass({
});
var areNotifsMuted = false;
var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId);
if (roomPushRule) {
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
areNotifsMuted = true;
if (!MatrixClientPeg.get().isGuest()) {
var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId);
if (roomPushRule) {
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
areNotifsMuted = true;
}
}
}

View file

@ -140,34 +140,37 @@ var SearchableEntityList = React.createClass({
}
var list;
if (this.props.truncateAt) { // caller wants list truncated
var TruncatedList = sdk.getComponent("elements.TruncatedList");
list = (
<TruncatedList className="mx_SearchableEntityList_list"
truncateAt={this.state.truncateAt} // use state truncation as it may be expanded
createOverflowElement={this._createOverflowEntity}>
{this.state.results.map((entity) => {
return entity.getJsx();
})}
</TruncatedList>
);
}
else {
list = (
<div className="mx_SearchableEntityList_list">
{this.state.results.map((entity) => {
return entity.getJsx();
})}
</div>
);
if (this.state.results.length) {
if (this.props.truncateAt) { // caller wants list truncated
var TruncatedList = sdk.getComponent("elements.TruncatedList");
list = (
<TruncatedList className="mx_SearchableEntityList_list"
truncateAt={this.state.truncateAt} // use state truncation as it may be expanded
createOverflowElement={this._createOverflowEntity}>
{this.state.results.map((entity) => {
return entity.getJsx();
})}
</TruncatedList>
);
}
else {
list = (
<div className="mx_SearchableEntityList_list">
{this.state.results.map((entity) => {
return entity.getJsx();
})}
</div>
);
}
list = <GeminiScrollbar autoshow={true} className="mx_SearchableEntityList_listWrapper">
{ list }
</GeminiScrollbar>;
}
return (
<div className={ "mx_SearchableEntityList " + (this.state.query.length ? "mx_SearchableEntityList_expanded" : "") }>
{inputBox}
<GeminiScrollbar autoshow={true} className="mx_SearchableEntityList_listWrapper">
{ list }
</GeminiScrollbar>
{ inputBox }
{ list }
{ this.state.query.length ? <div className="mx_SearchableEntityList_hrWrapper"><hr/></div> : '' }
</div>
);

View file

@ -110,19 +110,17 @@ module.exports = React.createClass({
},
render: function() {
var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
var avatarImg;
// Having just set an avatar we just display that since it will take a little
// time to propagate through to the RoomAvatar.
if (this.props.room && !this.avatarSet) {
var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
avatarImg = <RoomAvatar room={this.props.room} width={ this.props.width } height={ this.props.height } resizeMethod='crop' />;
} else {
var style = {
width: this.props.width,
height: this.props.height,
objectFit: 'cover',
};
avatarImg = <img className="mx_BaseAvatar_image" src={this.state.avatarUrl} style={style} />;
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
// XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
avatarImg = <BaseAvatar width={this.props.width} height={this.props.height} resizeMethod='crop'
name='?' idName={ MatrixClientPeg.get().getUserIdLocalpart() } url={this.state.avatarUrl} />
}
var uploadSection;