merge conflict

This commit is contained in:
Matthew Hodgson 2016-01-18 19:56:35 +00:00
commit 765174a600
15 changed files with 611 additions and 163 deletions

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
'use strict'; 'use strict';
var ContentRepo = require("matrix-js-sdk").ContentRepo;
var MatrixClientPeg = require('./MatrixClientPeg'); var MatrixClientPeg = require('./MatrixClientPeg');
module.exports = { module.exports = {
@ -37,6 +37,17 @@ module.exports = {
return url; return url;
}, },
avatarUrlForUser: function(user, width, height, resizeMethod) {
var url = ContentRepo.getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
width, height, resizeMethod
);
if (!url || url.length === 0) {
return null;
}
return url;
},
defaultAvatarUrlForString: function(s) { defaultAvatarUrlForString: function(s) {
var images = [ '76cfa6', '50e2c2', 'f4c371' ]; var images = [ '76cfa6', '50e2c2', 'f4c371' ];
var total = 0; var total = 0;

107
src/Entities.js Normal file
View file

@ -0,0 +1,107 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
var sdk = require('./index');
/*
* Converts various data models to Entity objects.
*
* Entity objects provide an interface for UI components to use to display
* members in a data-agnostic way. This means they don't need to care if the
* underlying data model is a RoomMember, User or 3PID data structure, it just
* cares about rendering.
*/
class Entity {
constructor(model) {
this.model = model;
}
getJsx() {
return null;
}
matches(queryString) {
return false;
}
}
class MemberEntity extends Entity {
getJsx() {
var MemberTile = sdk.getComponent("rooms.MemberTile");
return (
<MemberTile key={this.model.userId} member={this.model} />
);
}
matches(queryString) {
return this.model.name.toLowerCase().indexOf(queryString.toLowerCase()) === 0;
}
}
class UserEntity extends Entity {
constructor(model, showInviteButton, inviteFn) {
super(model);
this.showInviteButton = Boolean(showInviteButton);
this.inviteFn = inviteFn;
}
onClick() {
if (this.inviteFn) {
this.inviteFn(this.model.userId);
}
}
getJsx() {
var UserTile = sdk.getComponent("rooms.UserTile");
return (
<UserTile key={this.model.userId} user={this.model}
showInviteButton={this.showInviteButton} onClick={this.onClick.bind(this)} />
);
}
matches(queryString) {
var name = this.model.displayName || this.model.userId;
return name.toLowerCase().indexOf(queryString.toLowerCase()) === 0;
}
}
module.exports = {
/**
* @param {RoomMember[]} members
* @return {Entity[]}
*/
fromRoomMembers: function(members) {
return members.map(function(m) {
return new MemberEntity(m);
});
},
/**
* @param {User[]} users
* @param {boolean} showInviteButton
* @param {Function} inviteFn Called with the user ID.
* @return {Entity[]}
*/
fromUsers: function(users, showInviteButton, inviteFn) {
return users.map(function(u) {
return new UserEntity(u, showInviteButton, inviteFn);
})
}
};

View file

@ -23,6 +23,9 @@ limitations under the License.
module.exports.components = {}; module.exports.components = {};
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom'); module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
module.exports.components['structures.login.Login'] = require('./components/structures/login/Login');
module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration');
module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration');
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat'); module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
module.exports.components['structures.RoomView'] = require('./components/structures/RoomView'); module.exports.components['structures.RoomView'] = require('./components/structures/RoomView');
module.exports.components['structures.ScrollPanel'] = require('./components/structures/ScrollPanel'); module.exports.components['structures.ScrollPanel'] = require('./components/structures/ScrollPanel');
@ -41,6 +44,7 @@ module.exports.components['views.create_room.RoomAlias'] = require('./components
module.exports.components['views.dialogs.ErrorDialog'] = require('./components/views/dialogs/ErrorDialog'); module.exports.components['views.dialogs.ErrorDialog'] = require('./components/views/dialogs/ErrorDialog');
module.exports.components['views.dialogs.LogoutPrompt'] = require('./components/views/dialogs/LogoutPrompt'); module.exports.components['views.dialogs.LogoutPrompt'] = require('./components/views/dialogs/LogoutPrompt');
module.exports.components['views.dialogs.QuestionDialog'] = require('./components/views/dialogs/QuestionDialog'); module.exports.components['views.dialogs.QuestionDialog'] = require('./components/views/dialogs/QuestionDialog');
module.exports.components['views.dialogs.TextInputDialog'] = require('./components/views/dialogs/TextInputDialog');
module.exports.components['views.elements.EditableText'] = require('./components/views/elements/EditableText'); module.exports.components['views.elements.EditableText'] = require('./components/views/elements/EditableText');
module.exports.components['views.elements.PowerSelector'] = require('./components/views/elements/PowerSelector'); module.exports.components['views.elements.PowerSelector'] = require('./components/views/elements/PowerSelector');
module.exports.components['views.elements.ProgressBar'] = require('./components/views/elements/ProgressBar'); module.exports.components['views.elements.ProgressBar'] = require('./components/views/elements/ProgressBar');
@ -54,13 +58,14 @@ module.exports.components['views.login.LoginHeader'] = require('./components/vie
module.exports.components['views.login.PasswordLogin'] = require('./components/views/login/PasswordLogin'); module.exports.components['views.login.PasswordLogin'] = require('./components/views/login/PasswordLogin');
module.exports.components['views.login.RegistrationForm'] = require('./components/views/login/RegistrationForm'); module.exports.components['views.login.RegistrationForm'] = require('./components/views/login/RegistrationForm');
module.exports.components['views.login.ServerConfig'] = require('./components/views/login/ServerConfig'); module.exports.components['views.login.ServerConfig'] = require('./components/views/login/ServerConfig');
module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent');
module.exports.components['views.messages.MFileBody'] = require('./components/views/messages/MFileBody'); module.exports.components['views.messages.MFileBody'] = require('./components/views/messages/MFileBody');
module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody'); module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody');
module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody'); module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody');
module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent');
module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody'); module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody');
module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent'); module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent');
module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody'); module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody');
module.exports.components['views.rooms.EntityTile'] = require('./components/views/rooms/EntityTile');
module.exports.components['views.rooms.EventTile'] = require('./components/views/rooms/EventTile'); module.exports.components['views.rooms.EventTile'] = require('./components/views/rooms/EventTile');
module.exports.components['views.rooms.MemberInfo'] = require('./components/views/rooms/MemberInfo'); module.exports.components['views.rooms.MemberInfo'] = require('./components/views/rooms/MemberInfo');
module.exports.components['views.rooms.MemberList'] = require('./components/views/rooms/MemberList'); module.exports.components['views.rooms.MemberList'] = require('./components/views/rooms/MemberList');
@ -72,8 +77,10 @@ module.exports.components['views.rooms.RoomList'] = require('./components/views/
module.exports.components['views.rooms.RoomPreviewBar'] = require('./components/views/rooms/RoomPreviewBar'); module.exports.components['views.rooms.RoomPreviewBar'] = require('./components/views/rooms/RoomPreviewBar');
module.exports.components['views.rooms.RoomSettings'] = require('./components/views/rooms/RoomSettings'); module.exports.components['views.rooms.RoomSettings'] = require('./components/views/rooms/RoomSettings');
module.exports.components['views.rooms.RoomTile'] = require('./components/views/rooms/RoomTile'); module.exports.components['views.rooms.RoomTile'] = require('./components/views/rooms/RoomTile');
module.exports.components['views.rooms.SearchableEntityList'] = require('./components/views/rooms/SearchableEntityList');
module.exports.components['views.rooms.SearchResultTile'] = require('./components/views/rooms/SearchResultTile'); module.exports.components['views.rooms.SearchResultTile'] = require('./components/views/rooms/SearchResultTile');
module.exports.components['views.rooms.TabCompleteBar'] = require('./components/views/rooms/TabCompleteBar'); module.exports.components['views.rooms.TabCompleteBar'] = require('./components/views/rooms/TabCompleteBar');
module.exports.components['views.rooms.UserTile'] = require('./components/views/rooms/UserTile');
module.exports.components['views.settings.ChangeAvatar'] = require('./components/views/settings/ChangeAvatar'); module.exports.components['views.settings.ChangeAvatar'] = require('./components/views/settings/ChangeAvatar');
module.exports.components['views.settings.ChangeDisplayName'] = require('./components/views/settings/ChangeDisplayName'); module.exports.components['views.settings.ChangeDisplayName'] = require('./components/views/settings/ChangeDisplayName');
module.exports.components['views.settings.ChangePassword'] = require('./components/views/settings/ChangePassword'); module.exports.components['views.settings.ChangePassword'] = require('./components/views/settings/ChangePassword');

View file

@ -119,7 +119,7 @@ module.exports = React.createClass({
console.log("Attempting to peek into room %s", this.props.roomId); console.log("Attempting to peek into room %s", this.props.roomId);
MatrixClientPeg.get().peekInRoom(this.props.roomId).done(() => { MatrixClientPeg.get().peekInRoom(this.props.roomId).done(() => {
this.setState({ this.setState({
autoPeekDone: true; autoPeekDone: true
}); });
// we don't need to do anything - JS SDK will emit Room events // we don't need to do anything - JS SDK will emit Room events
@ -129,20 +129,6 @@ module.exports = React.createClass({
if (!peekedRoom) { if (!peekedRoom) {
return; return;
} }
var guestAccessEvent = peekedRoom.currentState.getStateEvents("m.room.guest_access", "");
if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") {
this.setState({
guestsCanJoin: true
});
}
var historyVisibility = peekedRoom.currentState.getStateEvents("m.room.history_visibility", "");
if (historyVisibility && historyVisibility.getContent().history_visibility === "world_readable") {
this.setState({
canPeek: true
});
}
}, function(err) { }, function(err) {
console.error("Failed to peek into room: %s", err); console.error("Failed to peek into room: %s", err);
}); });
@ -298,6 +284,20 @@ module.exports = React.createClass({
this.setState({ this.setState({
room: room room: room
}); });
var guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", "");
if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") {
this.setState({
guestsCanJoin: true
});
}
var historyVisibility = room.currentState.getStateEvents("m.room.history_visibility", "");
if (historyVisibility && historyVisibility.getContent().history_visibility === "world_readable") {
this.setState({
canPeek: true
});
}
} }
}, },
@ -960,7 +960,6 @@ module.exports = React.createClass({
this.state.room.roomId, "m.room.history_visibility", { this.state.room.roomId, "m.room.history_visibility", {
history_visibility: newVals.history_visibility, history_visibility: newVals.history_visibility,
}, "" }, ""
)
); );
} }
@ -985,6 +984,13 @@ module.exports = React.createClass({
deferreds.push(visibilityDeferred); deferreds.push(visibilityDeferred);
} }
// setRoomMutePushRule will do nothing if there is no change
deferreds.push(
MatrixClientPeg.get().setRoomMutePushRule(
"global", this.state.room.roomId, newVals.are_notifications_muted
)
);
if (newVals.power_levels) { if (newVals.power_levels) {
deferreds.push( deferreds.push(
MatrixClientPeg.get().sendStateEvent( MatrixClientPeg.get().sendStateEvent(
@ -1179,6 +1185,7 @@ module.exports = React.createClass({
topic: this.refs.header.getTopic(), topic: this.refs.header.getTopic(),
join_rule: this.refs.room_settings.getJoinRules(), join_rule: this.refs.room_settings.getJoinRules(),
history_visibility: this.refs.room_settings.getHistoryVisibility(), history_visibility: this.refs.room_settings.getHistoryVisibility(),
are_notifications_muted: this.refs.room_settings.areNotificationsMuted(),
power_levels: this.refs.room_settings.getPowerLevels(), power_levels: this.refs.room_settings.getPowerLevels(),
alias_operations: this.refs.room_settings.getAliasOperations(), alias_operations: this.refs.room_settings.getAliasOperations(),
tag_operations: this.refs.room_settings.getTagOperations(), tag_operations: this.refs.room_settings.getTagOperations(),
@ -1426,6 +1433,7 @@ module.exports = React.createClass({
if (!this.state.room) { if (!this.state.room) {
if (this.props.roomId) { if (this.props.roomId) {
if (this.props.autoPeek && !this.state.autoPeekDone) { if (this.props.autoPeek && !this.state.autoPeekDone) {
var Loader = sdk.getComponent("elements.Spinner");
return ( return (
<div className="mx_RoomView"> <div className="mx_RoomView">
<Loader /> <Loader />

View file

@ -152,10 +152,6 @@ module.exports = React.createClass({
this.logoutModal.closeDialog(); this.logoutModal.closeDialog();
}, },
onEnableNotificationsChange: function(event) {
UserSettingsStore.setEnableNotifications(event.target.checked);
},
render: function() { render: function() {
switch (this.state.phase) { switch (this.state.phase) {
case "UserSettings.LOADING": case "UserSettings.LOADING":
@ -173,6 +169,7 @@ module.exports = React.createClass({
var ChangeDisplayName = sdk.getComponent("views.settings.ChangeDisplayName"); var ChangeDisplayName = sdk.getComponent("views.settings.ChangeDisplayName");
var ChangePassword = sdk.getComponent("views.settings.ChangePassword"); var ChangePassword = sdk.getComponent("views.settings.ChangePassword");
var ChangeAvatar = sdk.getComponent('settings.ChangeAvatar'); var ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
var Notifications = sdk.getComponent("settings.Notifications");
var avatarUrl = ( var avatarUrl = (
this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
); );
@ -263,22 +260,7 @@ module.exports = React.createClass({
<h2>Notifications</h2> <h2>Notifications</h2>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<div className="mx_UserSettings_notifTable"> <Notifications/>
<div className="mx_UserSettings_notifTableRow">
<div className="mx_UserSettings_notifInputCell">
<input id="enableNotifications"
ref="enableNotifications"
type="checkbox"
checked={ UserSettingsStore.getEnableNotifications() }
onChange={ this.onEnableNotificationsChange } />
</div>
<div className="mx_UserSettings_notifLabelCell">
<label htmlFor="enableNotifications">
Enable desktop notifications
</label>
</div>
</div>
</div>
</div> </div>
<h2>Advanced</h2> <h2>Advanced</h2>

View file

@ -48,7 +48,7 @@ module.exports = React.createClass({
render: function() { render: function() {
return ( return (
<div className="mx_ErrorDialog"> <div className="mx_ErrorDialog">
<div className="mx_ErrorDialogTitle"> <div className="mx_Dialog_title">
{this.props.title} {this.props.title}
</div> </div>
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">

View file

@ -46,7 +46,7 @@ module.exports = React.createClass({
render: function() { render: function() {
return ( return (
<div className="mx_QuestionDialog"> <div className="mx_QuestionDialog">
<div className="mx_QuestionDialogTitle"> <div className="mx_Dialog_title">
{this.props.title} {this.props.title}
</div> </div>
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">

View file

@ -0,0 +1,94 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require("react");
module.exports = React.createClass({
displayName: 'TextInputDialog',
propTypes: {
title: React.PropTypes.string,
description: React.PropTypes.string,
value: React.PropTypes.string,
button: React.PropTypes.string,
focus: React.PropTypes.bool,
onFinished: React.PropTypes.func.isRequired
},
getDefaultProps: function() {
return {
title: "",
value: "",
description: "",
button: "OK",
focus: true
};
},
componentDidMount: function() {
if (this.props.focus) {
// Set the cursor at the end of the text input
this.refs.textinput.value = this.props.value;
}
},
onOk: function() {
this.props.onFinished(true, this.refs.textinput.value);
},
onCancel: function() {
this.props.onFinished(false);
},
onKeyDown: function(e) {
if (e.keyCode === 27) { // escape
e.stopPropagation();
e.preventDefault();
this.props.onFinished(false);
}
else if (e.keyCode === 13) { // enter
e.stopPropagation();
e.preventDefault();
this.props.onFinished(true, this.refs.textinput.value);
}
},
render: function() {
return (
<div className="mx_TextInputDialog">
<div className="mx_Dialog_title">
{this.props.title}
</div>
<div className="mx_Dialog_content">
<div className="mx_TextInputDialog_label">
<label htmlFor="textinput"> {this.props.description} </label>
</div>
<div>
<input id="textinput" ref="textinput" className="mx_TextInputDialog_input" defaultValue={this.props.value} autoFocus={this.props.focus} size="64" onKeyDown={this.onKeyDown}/>
</div>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onOk}>
{this.props.button}
</button>
<button onClick={this.onCancel}>
Cancel
</button>
</div>
</div>
);
}
});

View file

@ -22,7 +22,7 @@ module.exports = React.createClass({
render: function() { render: function() {
return ( return (
<div className="mx_ErrorDialog"> <div className="mx_ErrorDialog">
<div className="mx_ErrorDialogTitle"> <div className="mx_Dialog_title">
Custom Server Options Custom Server Options
</div> </div>
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">

View file

@ -0,0 +1,139 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
var PRESENCE_CLASS = {
"offline": "mx_EntityTile_offline",
"online": "mx_EntityTile_online",
"unavailable": "mx_EntityTile_unavailable"
};
module.exports = React.createClass({
displayName: 'EntityTile',
propTypes: {
name: React.PropTypes.string,
title: React.PropTypes.string,
avatarJsx: React.PropTypes.any, // <BaseAvatar />
presenceState: React.PropTypes.string,
presenceActiveAgo: React.PropTypes.number,
showInviteButton: React.PropTypes.bool,
shouldComponentUpdate: React.PropTypes.func,
onClick: React.PropTypes.func
},
getDefaultProps: function() {
return {
shouldComponentUpdate: function(nextProps, nextState) { return false; },
onClick: function() {},
presenceState: "offline",
presenceActiveAgo: -1,
showInviteButton: false,
};
},
getInitialState: function() {
return {
hover: false
};
},
shouldComponentUpdate: function(nextProps, nextState) {
if (this.state.hover !== nextState.hover) return true;
return this.props.shouldComponentUpdate(nextProps, nextState);
},
mouseEnter: function(e) {
this.setState({ 'hover': true });
},
mouseLeave: function(e) {
this.setState({ 'hover': false });
},
render: function() {
var presenceClass = PRESENCE_CLASS[this.props.presenceState];
var mainClassName = "mx_EntityTile ";
mainClassName += presenceClass;
if (this.state.hover) {
mainClassName += " mx_EntityTile_hover";
}
var nameEl;
if (this.state.hover) {
var PresenceLabel = sdk.getComponent("rooms.PresenceLabel");
nameEl = (
<div className="mx_EntityTile_details">
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12"/>
<div className="mx_EntityTile_name_hover">{ this.props.name }</div>
<PresenceLabel activeAgo={this.props.presenceActiveAgo}
presenceState={this.props.presenceState} />
</div>
);
}
else {
nameEl = (
<div className="mx_EntityTile_name">
{ this.props.name }
</div>
);
}
var inviteButton;
if (this.props.showInviteButton) {
inviteButton = (
<div className="mx_EntityTile_invite">
<img src="img/plus.svg" width="16" height="16" />
</div>
);
}
var power;
var powerLevel = this.props.powerLevel;
if (powerLevel >= 50 && powerLevel < 99) {
power = <img src="img/mod.svg" className="mx_EntityTile_power" width="16" height="17" alt="Mod"/>;
}
if (powerLevel >= 99) {
power = <img src="img/admin.svg" className="mx_EntityTile_power" width="16" height="17" alt="Admin"/>;
}
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
var av = this.props.avatarJsx || <BaseAvatar name={name} width={36} height={36} />;
return (
<div className={mainClassName} title={ this.props.title }
onClick={ this.props.onClick } onMouseEnter={ this.mouseEnter }
onMouseLeave={ this.mouseLeave }>
<div className="mx_EntityTile_avatar">
{av}
</div>
{ nameEl }
{ power }
{ inviteButton }
</div>
);
}
});

View file

@ -19,6 +19,7 @@ var Matrix = require("matrix-js-sdk");
var q = require('q'); var q = require('q');
var MatrixClientPeg = require("../../../MatrixClientPeg"); var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal"); var Modal = require("../../../Modal");
var Entities = require("../../../Entities");
var sdk = require('../../../index'); var sdk = require('../../../index');
var GeminiScrollbar = require('react-gemini-scrollbar'); var GeminiScrollbar = require('react-gemini-scrollbar');
@ -284,6 +285,8 @@ module.exports = React.createClass({
// we shouldn't add them if the 3pid invite state key (token) is in the // we shouldn't add them if the 3pid invite state key (token) is in the
// member invite (content.third_party_invite.signed.token) // member invite (content.third_party_invite.signed.token)
var room = MatrixClientPeg.get().getRoom(this.props.roomId); var room = MatrixClientPeg.get().getRoom(this.props.roomId);
var EntityTile = sdk.getComponent("rooms.EntityTile");
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
if (room) { if (room) {
room.currentState.getStateEvents("m.room.third_party_invite").forEach( room.currentState.getStateEvents("m.room.third_party_invite").forEach(
function(e) { function(e) {
@ -293,9 +296,12 @@ module.exports = React.createClass({
if (memberEvent) { if (memberEvent) {
return; return;
} }
var avatarJsx = (
<BaseAvatar name={e.getContent().display_name} width={36} height={36} />
);
memberList.push( memberList.push(
<MemberTile key={e.getStateKey()} ref={e.getStateKey()} <EntityTile key={e.getStateKey()} ref={e.getStateKey()}
customDisplayName={e.getContent().display_name} /> name={e.getContent().display_name} avatarJsx={avatarJsx} />
) )
}) })
} }
@ -304,11 +310,6 @@ module.exports = React.createClass({
return memberList; return memberList;
}, },
onPopulateInvite: function(e) {
this.onInvite(this.refs.invite.value);
e.preventDefault();
},
inviteTile: function() { inviteTile: function() {
if (this.state.inviting) { if (this.state.inviting) {
var Loader = sdk.getComponent("elements.Spinner"); var Loader = sdk.getComponent("elements.Spinner");
@ -316,10 +317,25 @@ module.exports = React.createClass({
<Loader /> <Loader />
); );
} else { } else {
// TODO: Cache this calculation
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
var allUsers = MatrixClientPeg.get().getUsers();
// only add Users if they are not joined
allUsers = allUsers.filter(function(u) {
return !room.hasMembershipState(u.userId, "join");
});
var SearchableEntityList = sdk.getComponent("rooms.SearchableEntityList");
return ( return (
<form onSubmit={this.onPopulateInvite}> <SearchableEntityList searchPlaceholderText={"Invite / Search"}
<input className="mx_MemberList_invite" ref="invite" id="mx_MemberList_invite" placeholder="Invite user (email)"/> onSubmit={this.onInvite}
</form> entities={
Entities.fromRoomMembers(
room.getJoinedMembers()
).concat(
Entities.fromUsers(allUsers, true, this.onInvite)
)
} />
); );
} }
}, },

View file

@ -27,9 +27,7 @@ module.exports = React.createClass({
displayName: 'MemberTile', displayName: 'MemberTile',
propTypes: { propTypes: {
member: React.PropTypes.any, // RoomMember member: React.PropTypes.any.isRequired, // RoomMember
onFinished: React.PropTypes.func,
customDisplayName: React.PropTypes.string // for 3pid invites
}, },
getInitialState: function() { getInitialState: function() {
@ -37,13 +35,11 @@ module.exports = React.createClass({
}, },
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate: function(nextProps, nextState) {
if (this.state.hover !== nextState.hover) return true;
if (!this.props.member) { return false; } // e.g. 3pid members
if ( if (
this.member_last_modified_time === undefined || this.member_last_modified_time === undefined ||
this.member_last_modified_time < nextProps.member.getLastModifiedTime() this.member_last_modified_time < nextProps.member.getLastModifiedTime()
) { ) {
return true return true;
} }
if ( if (
nextProps.member.user && nextProps.member.user &&
@ -55,17 +51,7 @@ module.exports = React.createClass({
return false; return false;
}, },
mouseEnter: function(e) {
this.setState({ 'hover': true });
},
mouseLeave: function(e) {
this.setState({ 'hover': false });
},
onClick: function(e) { onClick: function(e) {
if (!this.props.member) { return; } // e.g. 3pid members
dis.dispatch({ dis.dispatch({
action: 'view_user', action: 'view_user',
member: this.props.member, member: this.props.member,
@ -73,53 +59,27 @@ module.exports = React.createClass({
}, },
_getDisplayName: function() { _getDisplayName: function() {
if (this.props.customDisplayName) {
return this.props.customDisplayName;
}
return this.props.member.name; return this.props.member.name;
}, },
getPowerLabel: function() { getPowerLabel: function() {
if (!this.props.member) { return this.props.member.userId + " (power " + this.props.member.powerLevel + ")";
return this._getDisplayName();
}
var label = this.props.member.userId + " (power " + this.props.member.powerLevel + ")";
return label;
}, },
render: function() { render: function() {
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
var EntityTile = sdk.getComponent('rooms.EntityTile');
var member = this.props.member; var member = this.props.member;
var isMyUser = false;
var name = this._getDisplayName(); var name = this._getDisplayName();
var active = -1; var active = -1;
var presenceClass = "mx_MemberTile_offline"; var presenceState = member.user ? member.user.presence : null;
if (member) { var av = (
if (member.user) { <MemberAvatar member={member} width={36} height={36} />
this.user_last_modified_time = member.user.getLastModifiedTime();
// FIXME: make presence data update whenever User.presence changes...
active = (
(Date.now() - (member.user.lastPresenceTs - member.user.lastActiveAgo)) || -1
); );
if (member.user.presence === "online") {
presenceClass = "mx_MemberTile_online";
}
else if (member.user.presence === "unavailable") {
presenceClass = "mx_MemberTile_unavailable";
}
}
this.member_last_modified_time = member.getLastModifiedTime();
isMyUser = MatrixClientPeg.get().credentials.userId == member.userId;
// if (this.props.member && this.props.member.powerLevelNorm > 0) {
// var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png";
// power = <img src={ img } className="mx_MemberTile_power" width="44" height="44" alt=""/>;
// }
var power; var power;
if (this.props.member) {
var powerLevel = this.props.member.powerLevel; var powerLevel = this.props.member.powerLevel;
if (powerLevel >= 50 && powerLevel < 99) { if (powerLevel >= 50 && powerLevel < 99) {
power = <img src="img/mod.svg" className="mx_MemberTile_power" width="16" height="17" alt="Mod"/>; power = <img src="img/mod.svg" className="mx_MemberTile_power" width="16" height="17" alt="Mod"/>;
@ -127,61 +87,22 @@ module.exports = React.createClass({
if (powerLevel >= 99) { if (powerLevel >= 99) {
power = <img src="img/admin.svg" className="mx_MemberTile_power" width="16" height="17" alt="Admin"/>; power = <img src="img/admin.svg" className="mx_MemberTile_power" width="16" height="17" alt="Admin"/>;
} }
}
}
var mainClassName = "mx_MemberTile "; if (member.user) {
mainClassName += presenceClass; this.user_last_modified_time = member.user.getLastModifiedTime();
if (this.state.hover) {
mainClassName += " mx_MemberTile_hover";
}
var nameEl; // FIXME: make presence data update whenever User.presence changes...
if (this.state.hover && this.props.member) { active = (
var presenceState = (member && member.user) ? member.user.presence : null; (Date.now() - (member.user.lastPresenceTs - member.user.lastActiveAgo)) || -1
var PresenceLabel = sdk.getComponent("rooms.PresenceLabel");
nameEl = (
<div className="mx_MemberTile_details">
<img className="mx_MemberTile_chevron" src="img/member_chevron.png" width="8" height="12"/>
<div className="mx_MemberTile_userId">{ name }</div>
<PresenceLabel activeAgo={active}
presenceState={presenceState} />
</div>
);
}
else {
nameEl = (
<div className="mx_MemberTile_name">
{ name }
</div>
);
}
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
var av;
if (member) {
av = (
<MemberAvatar member={this.props.member} width={36} height={36} />
);
}
else {
av = (
<BaseAvatar name={name} width={36} height={36} />
); );
} }
this.member_last_modified_time = member.getLastModifiedTime();
return ( return (
<div className={mainClassName} title={ this.getPowerLabel() } <EntityTile {...this.props} presenceActiveAgo={active} presenceState={presenceState}
onClick={ this.onClick } onMouseEnter={ this.mouseEnter } avatarJsx={av} title={this.getPowerLabel()} onClick={this.onClick}
onMouseLeave={ this.mouseLeave }> shouldComponentUpdate={this.shouldComponentUpdate.bind(this)}
<div className="mx_MemberTile_avatar"> name={name} powerLevel={this.props.member.powerLevel} />
{ av }
{ power }
</div>
{ nameEl }
</div>
); );
} }
}); });

View file

@ -128,6 +128,10 @@ module.exports = React.createClass({
return this.refs.share_history.checked ? "shared" : "invited"; return this.refs.share_history.checked ? "shared" : "invited";
}, },
areNotificationsMuted: function() {
return this.refs.are_notifications_muted.checked;
},
getPowerLevels: function() { getPowerLevels: function() {
if (!this.state.power_levels_changed) return undefined; if (!this.state.power_levels_changed) return undefined;
@ -387,10 +391,19 @@ module.exports = React.createClass({
guest_access = guest_access.getContent().guest_access; guest_access = guest_access.getContent().guest_access;
} }
var are_notifications_muted;
var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId);
if (roomPushRule) {
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
are_notifications_muted = true;
}
}
var events_levels = (power_levels ? power_levels.events : {}) || {}; var events_levels = (power_levels ? power_levels.events : {}) || {};
var user_id = MatrixClientPeg.get().credentials.userId; var user_id = MatrixClientPeg.get().credentials.userId;
if (power_levels) { if (power_levels) {
power_levels = power_levels.getContent(); power_levels = power_levels.getContent();
@ -675,6 +688,11 @@ module.exports = React.createClass({
{ aliases_section } { aliases_section }
<h3>Notifications</h3>
<div className="mx_RoomSettings_settings">
<label><input type="checkbox" ref="are_notifications_muted" defaultChecked={are_notifications_muted}/> Mute notifications for this room</label>
</div>
<h3>Permissions</h3> <h3>Permissions</h3>
<div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings"> <div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings">
<div className="mx_RoomSettings_powerLevel"> <div className="mx_RoomSettings_powerLevel">

View file

@ -0,0 +1,89 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
var GeminiScrollbar = require('react-gemini-scrollbar');
// A list capable of displaying entities which conform to the SearchableEntity
// interface which is an object containing getJsx(): Jsx and matches(query: string): boolean
var SearchableEntityList = React.createClass({
displayName: 'SearchableEntityList',
propTypes: {
searchPlaceholderText: React.PropTypes.string,
emptyQueryShowsAll: React.PropTypes.bool,
onSubmit: React.PropTypes.func, // fn(inputText)
entities: React.PropTypes.array
},
getDefaultProps: function() {
return {
searchPlaceholderText: "Search",
entities: [],
emptyQueryShowsAll: false,
onSubmit: function() {}
};
},
getInitialState: function() {
return {
query: "",
results: this.getSearchResults("")
};
},
onQueryChanged: function(ev) {
var q = ev.target.value;
this.setState({
query: q,
results: this.getSearchResults(q)
});
},
onQuerySubmit: function(ev) {
ev.preventDefault();
this.props.onSubmit(this.state.query);
},
getSearchResults: function(query) {
if (!query || query.length === 0) {
return this.props.emptyQueryShowsAll ? this.props.entities : []
}
return this.props.entities.filter(function(e) {
return e.matches(query);
});
},
render: function() {
return (
<div>
<form onSubmit={this.onQuerySubmit}>
<input className="mx_SearchableEntityList_query" type="text"
onChange={this.onQueryChanged} value={this.state.query}
placeholder={this.props.searchPlaceholderText} />
</form>
<div className="mx_SearchableEntityList_list">
{this.state.results.map((entity) => {
return entity.getJsx();
})}
</div>
</div>
);
}
});
module.exports = SearchableEntityList;

View file

@ -0,0 +1,56 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var Avatar = require("../../../Avatar");
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
var dis = require('../../../dispatcher');
var Modal = require("../../../Modal");
module.exports = React.createClass({
displayName: 'UserTile',
propTypes: {
user: React.PropTypes.any.isRequired // User
},
render: function() {
var EntityTile = sdk.getComponent("rooms.EntityTile");
var user = this.props.user;
var name = user.displayName || user.userId;
var active = -1;
// FIXME: make presence data update whenever User.presence changes...
active = (
(Date.now() - (user.lastPresenceTs - user.lastActiveAgo)) || -1
);
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
var avatarJsx = (
<BaseAvatar width={36} height={36} name={name} idName={user.userId}
url={ Avatar.avatarUrlForUser(user, 36, 36, "crop") } />
);
return (
<EntityTile {...this.props} presenceState={user.presence} presenceActiveAgo={active}
name={name} title={user.userId} avatarJsx={avatarJsx} />
);
}
});