Merge branch 'develop' into dbkr/scalar

This commit is contained in:
Matthew Hodgson 2016-06-19 22:40:48 +01:00
commit 94aec10512
23 changed files with 1578 additions and 460 deletions

View file

@ -38,11 +38,13 @@ var MatrixTools = require('../../MatrixTools');
var linkifyMatrix = require("../../linkify-matrix");
var KeyCode = require('../../KeyCode');
var createRoom = require("../../createRoom");
module.exports = React.createClass({
displayName: 'MatrixChat',
propTypes: {
config: React.PropTypes.object.isRequired,
config: React.PropTypes.object,
ConferenceHandler: React.PropTypes.any,
onNewScreen: React.PropTypes.func,
registrationUrl: React.PropTypes.string,
@ -63,6 +65,13 @@ module.exports = React.createClass({
getInitialState: function() {
var s = {
// If we are viewing a room by alias, this contains the alias
currentRoomAlias: null,
// The ID of the room we're viewing. This is either populated directly
// in the case where we view a room by ID or by RoomView when it resolves
// what ID an alias points at.
currentRoomId: null,
logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials),
collapse_lhs: false,
collapse_rhs: false,
@ -85,7 +94,8 @@ module.exports = React.createClass({
getDefaultProps: function() {
return {
startingQueryParams: {}
startingQueryParams: {},
config: {},
};
},
@ -98,10 +108,9 @@ module.exports = React.createClass({
else if (window.localStorage && window.localStorage.getItem("mx_hs_url")) {
return window.localStorage.getItem("mx_hs_url");
}
else if (this.props.config) {
return this.props.config.default_hs_url
else {
return this.props.config.default_hs_url || "https://matrix.org";
}
return "https://matrix.org";
},
getFallbackHsUrl: function() {
@ -117,10 +126,9 @@ module.exports = React.createClass({
else if (window.localStorage && window.localStorage.getItem("mx_is_url")) {
return window.localStorage.getItem("mx_is_url");
}
else if (this.props.config) {
return this.props.config.default_is_url
else {
return this.props.config.default_is_url || "https://vector.im"
}
return "https://matrix.org";
},
componentWillMount: function() {
@ -393,6 +401,10 @@ module.exports = React.createClass({
});
break;
case 'view_room':
// Takes either a room ID or room alias: if switching to a room the client is already
// known to be in (eg. user clicks on a room in the recents panel), supply the ID
// If the user is clicking on a room in the context of the alias being presented
// to them, supply the room alias. If both are supplied, the room ID will be ignored.
this._viewRoom(
payload.room_id, payload.room_alias, payload.show_settings, payload.event_id,
payload.third_party_invite, payload.oob_data
@ -406,7 +418,7 @@ module.exports = React.createClass({
);
var roomIndex = -1;
for (var i = 0; i < allRooms.length; ++i) {
if (allRooms[i].roomId == this.state.currentRoom) {
if (allRooms[i].roomId == this.state.currentRoomId) {
roomIndex = i;
break;
}
@ -424,42 +436,6 @@ module.exports = React.createClass({
this._viewRoom(allRooms[roomIndex].roomId);
}
break;
case 'view_room_alias':
if (!this.state.logged_in) {
this.starting_room_alias_payload = payload;
// Login is the default screen, so we'd do this anyway,
// but this will set the URL bar appropriately.
dis.dispatch({ action: 'start_login' });
return;
}
var foundRoom = MatrixTools.getRoomForAlias(
MatrixClientPeg.get().getRooms(), payload.room_alias
);
if (foundRoom) {
dis.dispatch({
action: 'view_room',
room_id: foundRoom.roomId,
room_alias: payload.room_alias,
event_id: payload.event_id,
third_party_invite: payload.third_party_invite,
oob_data: payload.oob_data,
});
return;
}
// resolve the alias and *then* view it
MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias).done(
function(result) {
dis.dispatch({
action: 'view_room',
room_id: result.room_id,
room_alias: payload.room_alias,
event_id: payload.event_id,
third_party_invite: payload.third_party_invite,
oob_data: payload.oob_data,
});
});
break;
case 'view_user_settings':
this._setPage(this.PageTypes.UserSettings);
this.notifyNewScreen('settings');
@ -468,49 +444,7 @@ module.exports = React.createClass({
//this._setPage(this.PageTypes.CreateRoom);
//this.notifyNewScreen('new');
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
var Loader = sdk.getComponent("elements.Spinner");
var modal = Modal.createDialog(Loader);
if (MatrixClientPeg.get().isGuest()) {
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't create new rooms. Please register to create room and start a chat."
});
return;
}
// XXX: FIXME: deduplicate this with MemberInfo's 'start chat' impl
MatrixClientPeg.get().createRoom({
preset: "private_chat",
// Allow guests by default since the room is private and they'd
// need an invite. This means clicking on a 3pid invite email can
// actually drop you right in to a chat.
initial_state: [
{
content: {
guest_access: 'can_join'
},
type: 'm.room.guest_access',
state_key: '',
visibility: 'private',
}
],
}).done(function(res) {
modal.close();
dis.dispatch({
action: 'view_room',
room_id: res.room_id,
// show_settings: true,
});
}, function(err) {
modal.close();
Modal.createDialog(ErrorDialog, {
title: "Failed to create room",
description: err.toString()
});
});
createRoom().done();
break;
case 'view_room_directory':
this._setPage(this.PageTypes.RoomDirectory);
@ -575,16 +509,19 @@ module.exports = React.createClass({
this.focusComposer = true;
var newState = {
currentRoom: roomId,
currentRoomAlias: roomAlias,
initialEventId: eventId,
highlightedEventId: eventId,
initialEventPixelOffset: undefined,
page_type: this.PageTypes.RoomView,
thirdPartyInvite: thirdPartyInvite,
roomOobData: oob_data,
currentRoomAlias: roomAlias,
};
if (!roomAlias) {
newState.currentRoomId = roomId;
}
// if we aren't given an explicit event id, look for one in the
// scrollStateMap.
if (!eventId) {
@ -604,7 +541,7 @@ module.exports = React.createClass({
var presentedId = roomAlias || roomId;
var room = MatrixClientPeg.get().getRoom(roomId);
if (room) {
var theAlias = MatrixTools.getCanonicalAliasForRoom(room);
var theAlias = MatrixTools.getDisplayAliasForRoom(room);
if (theAlias) presentedId = theAlias;
// No need to do this given RoomView triggers it itself...
@ -677,13 +614,13 @@ module.exports = React.createClass({
dis.dispatch(self.starting_room_alias_payload);
delete self.starting_room_alias_payload;
} else if (!self.state.page_type) {
if (!self.state.currentRoom) {
if (!self.state.currentRoomId) {
var firstRoom = null;
if (cli.getRooms() && cli.getRooms().length) {
firstRoom = RoomListSorter.mostRecentActivityFirst(
cli.getRooms()
)[0].roomId;
self.setState({ready: true, currentRoom: firstRoom, page_type: self.PageTypes.RoomView});
self.setState({ready: true, currentRoomId: firstRoom, page_type: self.PageTypes.RoomView});
} else {
self.setState({ready: true, page_type: self.PageTypes.RoomDirectory});
}
@ -693,10 +630,10 @@ module.exports = React.createClass({
// we notifyNewScreen now because now the room will actually be displayed,
// and (mostly) now we can get the correct alias.
var presentedId = self.state.currentRoom;
var room = MatrixClientPeg.get().getRoom(self.state.currentRoom);
var presentedId = self.state.currentRoomId;
var room = MatrixClientPeg.get().getRoom(self.state.currentRoomId);
if (room) {
var theAlias = MatrixTools.getCanonicalAliasForRoom(room);
var theAlias = MatrixTools.getDisplayAliasForRoom(room);
if (theAlias) presentedId = theAlias;
}
@ -861,22 +798,28 @@ module.exports = React.createClass({
inviterName: params.inviter_name,
};
var payload = {
action: 'view_room',
event_id: eventId,
third_party_invite: third_party_invite,
oob_data: oob_data,
};
if (roomString[0] == '#') {
dis.dispatch({
action: 'view_room_alias',
room_alias: roomString,
event_id: eventId,
third_party_invite: third_party_invite,
oob_data: oob_data,
});
payload.room_alias = roomString;
} else {
dis.dispatch({
action: 'view_room',
room_id: roomString,
event_id: eventId,
third_party_invite: third_party_invite,
oob_data: oob_data,
});
payload.room_id = roomString;
}
// we can't view a room unless we're logged in
// (a guest account is fine)
if (!this.state.logged_in) {
this.starting_room_alias_payload = payload;
// Login is the default screen, so we'd do this anyway,
// but this will set the URL bar appropriately.
dis.dispatch({ action: 'start_login' });
return;
} else {
dis.dispatch(payload);
}
}
else {
@ -892,7 +835,7 @@ module.exports = React.createClass({
onAliasClick: function(event, alias) {
event.preventDefault();
dis.dispatch({action: 'view_room_alias', room_alias: alias});
dis.dispatch({action: 'view_room', room_alias: alias});
},
onUserClick: function(event, userId) {
@ -1038,10 +981,10 @@ module.exports = React.createClass({
onUserSettingsClose: function() {
// XXX: use browser history instead to find the previous room?
// or maintain a this.state.pageHistory in _setPage()?
if (this.state.currentRoom) {
if (this.state.currentRoomId) {
dis.dispatch({
action: 'view_room',
room_id: this.state.currentRoom,
room_id: this.state.currentRoomId,
});
}
else {
@ -1051,6 +994,13 @@ module.exports = React.createClass({
}
},
onRoomIdResolved: function(room_id) {
// It's the RoomView's resposibility to look up room aliases, but we need the
// ID to pass into things like the Member List, so the Room View tells us when
// its done that resolution so we can display things that take a room ID.
this.setState({currentRoomId: room_id});
},
render: function() {
var LeftPanel = sdk.getComponent('structures.LeftPanel');
var RoomView = sdk.getComponent('structures.RoomView');
@ -1081,20 +1031,21 @@ module.exports = React.createClass({
page_element = (
<RoomView
ref="roomView"
roomAddress={this.state.currentRoom || this.state.currentRoomAlias}
roomAddress={this.state.currentRoomAlias || this.state.currentRoomId}
onRoomIdResolved={this.onRoomIdResolved}
eventId={this.state.initialEventId}
thirdPartyInvite={this.state.thirdPartyInvite}
oobData={this.state.roomOobData}
highlightedEventId={this.state.highlightedEventId}
eventPixelOffset={this.state.initialEventPixelOffset}
key={this.state.currentRoom}
key={this.state.currentRoomAlias || this.state.currentRoomId}
opacity={this.state.middleOpacity}
ConferenceHandler={this.props.ConferenceHandler} />
);
right_panel = <RightPanel roomId={this.state.currentRoom} collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity} />
right_panel = <RightPanel roomId={this.state.currentRoomId} collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity} />
break;
case this.PageTypes.UserSettings:
page_element = <UserSettings onClose={this.onUserSettingsClose} version={this.state.version} />
page_element = <UserSettings onClose={this.onUserSettingsClose} version={this.state.version} brand={this.props.config.brand} />
right_panel = <RightPanel collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity}/>
break;
case this.PageTypes.CreateRoom:
@ -1127,7 +1078,7 @@ module.exports = React.createClass({
<div className="mx_MatrixChat_wrapper">
{topBar}
<div className={bodyClasses}>
<LeftPanel selectedRoom={this.state.currentRoom} collapsed={this.state.collapse_lhs} opacity={this.state.sideOpacity}/>
<LeftPanel selectedRoom={this.state.currentRoomId} collapsed={this.state.collapse_lhs} opacity={this.state.sideOpacity}/>
<main className="mx_MatrixChat_middlePanel">
{page_element}
</main>

View file

@ -86,6 +86,10 @@ module.exports = React.createClass({
// to manage its animations
this._readReceiptMap = {};
// Remember the read marker ghost node so we can do the cleanup that
// Velocity requires
this._readMarkerGhostNode = null;
this._isMounted = true;
},
@ -422,9 +426,16 @@ module.exports = React.createClass({
},
_startAnimation: function(ghostNode) {
Velocity(ghostNode, {opacity: '0', width: '10%'},
{duration: 400, easing: 'easeInSine',
delay: 1000});
if (this._readMarkerGhostNode) {
Velocity.Utilities.removeData(this._readMarkerGhostNode);
}
this._readMarkerGhostNode = ghostNode;
if (ghostNode) {
Velocity(ghostNode, {opacity: '0', width: '10%'},
{duration: 400, easing: 'easeInSine',
delay: 1000});
}
},
_getReadMarkerGhostTile: function() {

View file

@ -39,6 +39,7 @@ var dis = require("../../dispatcher");
var Tinter = require("../../Tinter");
var rate_limited_func = require('../../ratelimitedfunc');
var ObjectUtils = require('../../ObjectUtils');
var MatrixTools = require('../../MatrixTools');
var DEBUG = false;
@ -54,16 +55,17 @@ module.exports = React.createClass({
propTypes: {
ConferenceHandler: React.PropTypes.any,
// the ID for this room (or, if we don't know it, an alias for it)
//
// XXX: if this is an alias, we will display a 'join' dialogue,
// regardless of whether we are already a member, or if the room is
// peekable. Currently there is a big mess, where at least four
// different components (RoomView, MatrixChat, RoomDirectory,
// SlashCommands) have logic for turning aliases into rooms, and each
// of them do it differently and have different edge cases.
// Either a room ID or room alias for the room to display.
// If the room is being displayed as a result of the user clicking
// on a room alias, the alias should be supplied. Otherwise, a room
// ID should be supplied.
roomAddress: React.PropTypes.string.isRequired,
// If a room alias is passed to roomAddress, a function can be
// provided here that will be called with the ID of the room
// once it has been resolved.
onRoomIdResolved: React.PropTypes.func,
// An object representing a third party invite to join this room
// Fields:
// * inviteSignUrl (string) The URL used to join this room from an email invite
@ -100,17 +102,17 @@ module.exports = React.createClass({
},
getInitialState: function() {
var room = MatrixClientPeg.get().getRoom(this.props.roomAddress);
return {
room: room,
roomLoading: !room,
room: null,
roomId: null,
roomLoading: true,
editingRoomSettings: false,
uploadingRoomSettings: false,
numUnreadMessages: 0,
draggingFile: false,
searching: false,
searchResults: null,
hasUnsentMessages: this._hasUnsentMessages(room),
hasUnsentMessages: false,
callState: null,
guestsCanJoin: false,
canPeek: false,
@ -142,6 +144,39 @@ module.exports = React.createClass({
}
});
if (this.props.roomAddress[0] == '#') {
// we always look up the alias from the directory server:
// we want the room that the given alias is pointing to
// right now. We may have joined that alias before but there's
// no guarantee the alias hasn't subsequently been remapped.
MatrixClientPeg.get().getRoomIdForAlias(this.props.roomAddress).done((result) => {
if (this.props.onRoomIdResolved) {
this.props.onRoomIdResolved(result.room_id);
}
var room = MatrixClientPeg.get().getRoom(result.room_id);
this.setState({
room: room,
roomId: result.room_id,
roomLoading: !room,
hasUnsentMessages: this._hasUnsentMessages(room),
}, this._updatePeeking);
}, (err) => {
this.setState({
roomLoading: false,
});
});
} else {
var room = MatrixClientPeg.get().getRoom(this.props.roomAddress);
this.setState({
roomId: this.props.roomAddress,
room: room,
roomLoading: !room,
hasUnsentMessages: this._hasUnsentMessages(room),
}, this._updatePeeking);
}
},
_updatePeeking: function() {
// if this is an unknown room then we're in one of three states:
// - This is a room we can peek into (search engine) (we can /peek)
// - This is a room we can publicly join or were invited to. (we can /join)
@ -149,10 +184,13 @@ module.exports = React.createClass({
// We can't try to /join because this may implicitly accept invites (!)
// 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) {
console.log("Attempting to peek into room %s", this.props.roomAddress);
// Note that peeking works by room ID and room ID only, as opposed to joining
// which must be by alias or invite wherever possible (peeking currently does
// not work over federation).
if (!this.state.room && this.state.roomId) {
console.log("Attempting to peek into room %s", this.state.roomId);
MatrixClientPeg.get().peekInRoom(this.props.roomAddress).then((room) => {
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
this.setState({
room: room,
roomLoading: false,
@ -171,7 +209,7 @@ module.exports = React.createClass({
throw err;
}
}).done();
} else {
} else if (this.state.room) {
MatrixClientPeg.get().stopPeeking();
this._onRoomLoaded(this.state.room);
}

View file

@ -20,18 +20,36 @@ var MatrixClientPeg = require("../../MatrixClientPeg");
var Modal = require('../../Modal');
var dis = require("../../dispatcher");
var q = require('q');
var version = require('../../../package.json').version;
var package_json = require('../../../package.json');
var UserSettingsStore = require('../../UserSettingsStore');
var GeminiScrollbar = require('react-gemini-scrollbar');
var Email = require('../../email');
var AddThreepid = require('../../AddThreepid');
const LABS_FEATURES = [
{
name: 'Rich Text Editor',
id: 'rich_text_editor'
},
{
name: 'End-to-End Encryption',
id: 'e2e_encryption'
}
];
// if this looks like a release, use the 'version' from package.json; else use
// the git sha.
const REACT_SDK_VERSION =
'dist' in package_json ? package_json.version : package_json.gitHead || "<local>";
module.exports = React.createClass({
displayName: 'UserSettings',
propTypes: {
version: React.PropTypes.string,
onClose: React.PropTypes.func
onClose: React.PropTypes.func,
// The brand string given when creating email pushers
brand: React.PropTypes.string,
},
getDefaultProps: function() {
@ -44,7 +62,6 @@ module.exports = React.createClass({
return {
avatarUrl: null,
threePids: [],
clientVersion: version,
phase: "UserSettings.LOADING", // LOADING, DISPLAY
email_add_pending: false,
};
@ -244,6 +261,27 @@ module.exports = React.createClass({
});
},
_renderDeviceInfo: function() {
if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) {
return null;
}
var client = MatrixClientPeg.get();
var deviceId = client.deviceId;
var olmKey = client.getDeviceEd25519Key() || "<not supported>";
return (
<div>
<h3>Cryptography</h3>
<div className="mx_UserSettings_section">
<ul>
<li>Device ID: {deviceId}</li>
<li>Device key: {olmKey}</li>
</ul>
</div>
</div>
);
},
render: function() {
var self = this;
var Loader = sdk.getComponent("elements.Spinner");
@ -333,11 +371,35 @@ module.exports = React.createClass({
<h3>Notifications</h3>
<div className="mx_UserSettings_section">
<Notifications threepids={this.state.threepids} />
<Notifications threepids={this.state.threepids} brand={this.props.brand} />
</div>
</div>);
}
this._renderLabs = function () {
let features = LABS_FEATURES.map(feature => (
<div key={feature.id}>
<input
type="checkbox"
id={feature.id}
name={feature.id}
defaultChecked={UserSettingsStore.isFeatureEnabled(feature.id)}
onChange={e => UserSettingsStore.setFeatureEnabled(feature.id, e.target.checked)} />
<label htmlFor={feature.id}>{feature.name}</label>
</div>
));
return (
<div>
<h3>Labs</h3>
<div className="mx_UserSettings_section">
<p>These are experimental features that may break in unexpected ways. Use with caution.</p>
{features}
</div>
</div>
)
};
return (
<div className="mx_UserSettings">
<SimpleRoomHeader title="Settings" onCancelClick={ this.props.onClose }/>
@ -390,6 +452,10 @@ module.exports = React.createClass({
{notification_area}
{this._renderDeviceInfo()}
{this._renderLabs()}
<h3>Advanced</h3>
<div className="mx_UserSettings_section">
@ -403,7 +469,7 @@ module.exports = React.createClass({
Identity Server is { MatrixClientPeg.get().getIdentityServerUrl() }
</div>
<div className="mx_UserSettings_advanced">
matrix-react-sdk version: {this.state.clientVersion}<br/>
matrix-react-sdk version: {REACT_SDK_VERSION}<br/>
vector-web version: {this.props.version}<br/>
</div>
</div>