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';
var ContentRepo = require("matrix-js-sdk").ContentRepo;
var MatrixClientPeg = require('./MatrixClientPeg');
module.exports = {
@ -37,6 +37,17 @@ module.exports = {
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) {
var images = [ '76cfa6', '50e2c2', 'f4c371' ];
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['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.RoomView'] = require('./components/structures/RoomView');
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.LogoutPrompt'] = require('./components/views/dialogs/LogoutPrompt');
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.PowerSelector'] = require('./components/views/elements/PowerSelector');
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.RegistrationForm'] = require('./components/views/login/RegistrationForm');
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.MImageBody'] = require('./components/views/messages/MImageBody');
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.TextualEvent'] = require('./components/views/messages/TextualEvent');
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.MemberInfo'] = require('./components/views/rooms/MemberInfo');
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.RoomSettings'] = require('./components/views/rooms/RoomSettings');
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.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.ChangeDisplayName'] = require('./components/views/settings/ChangeDisplayName');
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);
MatrixClientPeg.get().peekInRoom(this.props.roomId).done(() => {
this.setState({
autoPeekDone: true;
autoPeekDone: true
});
// we don't need to do anything - JS SDK will emit Room events
@ -129,20 +129,6 @@ module.exports = React.createClass({
if (!peekedRoom) {
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) {
console.error("Failed to peek into room: %s", err);
});
@ -298,6 +284,20 @@ module.exports = React.createClass({
this.setState({
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,8 +960,7 @@ module.exports = React.createClass({
this.state.room.roomId, "m.room.history_visibility", {
history_visibility: newVals.history_visibility,
}, ""
)
);
);
}
if (old_guest_read != newVals.guest_read ||
@ -985,6 +984,13 @@ module.exports = React.createClass({
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) {
deferreds.push(
MatrixClientPeg.get().sendStateEvent(
@ -1179,6 +1185,7 @@ module.exports = React.createClass({
topic: this.refs.header.getTopic(),
join_rule: this.refs.room_settings.getJoinRules(),
history_visibility: this.refs.room_settings.getHistoryVisibility(),
are_notifications_muted: this.refs.room_settings.areNotificationsMuted(),
power_levels: this.refs.room_settings.getPowerLevels(),
alias_operations: this.refs.room_settings.getAliasOperations(),
tag_operations: this.refs.room_settings.getTagOperations(),
@ -1426,6 +1433,7 @@ module.exports = React.createClass({
if (!this.state.room) {
if (this.props.roomId) {
if (this.props.autoPeek && !this.state.autoPeekDone) {
var Loader = sdk.getComponent("elements.Spinner");
return (
<div className="mx_RoomView">
<Loader />

View file

@ -152,10 +152,6 @@ module.exports = React.createClass({
this.logoutModal.closeDialog();
},
onEnableNotificationsChange: function(event) {
UserSettingsStore.setEnableNotifications(event.target.checked);
},
render: function() {
switch (this.state.phase) {
case "UserSettings.LOADING":
@ -173,6 +169,7 @@ module.exports = React.createClass({
var ChangeDisplayName = sdk.getComponent("views.settings.ChangeDisplayName");
var ChangePassword = sdk.getComponent("views.settings.ChangePassword");
var ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
var Notifications = sdk.getComponent("settings.Notifications");
var avatarUrl = (
this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
);
@ -263,22 +260,7 @@ module.exports = React.createClass({
<h2>Notifications</h2>
<div className="mx_UserSettings_section">
<div className="mx_UserSettings_notifTable">
<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>
<Notifications/>
</div>
<h2>Advanced</h2>

View file

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

View file

@ -46,7 +46,7 @@ module.exports = React.createClass({
render: function() {
return (
<div className="mx_QuestionDialog">
<div className="mx_QuestionDialogTitle">
<div className="mx_Dialog_title">
{this.props.title}
</div>
<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() {
return (
<div className="mx_ErrorDialog">
<div className="mx_ErrorDialogTitle">
<div className="mx_Dialog_title">
Custom Server Options
</div>
<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 MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
var Entities = require("../../../Entities");
var sdk = require('../../../index');
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
// member invite (content.third_party_invite.signed.token)
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
var EntityTile = sdk.getComponent("rooms.EntityTile");
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
if (room) {
room.currentState.getStateEvents("m.room.third_party_invite").forEach(
function(e) {
@ -293,9 +296,12 @@ module.exports = React.createClass({
if (memberEvent) {
return;
}
var avatarJsx = (
<BaseAvatar name={e.getContent().display_name} width={36} height={36} />
);
memberList.push(
<MemberTile key={e.getStateKey()} ref={e.getStateKey()}
customDisplayName={e.getContent().display_name} />
<EntityTile key={e.getStateKey()} ref={e.getStateKey()}
name={e.getContent().display_name} avatarJsx={avatarJsx} />
)
})
}
@ -304,11 +310,6 @@ module.exports = React.createClass({
return memberList;
},
onPopulateInvite: function(e) {
this.onInvite(this.refs.invite.value);
e.preventDefault();
},
inviteTile: function() {
if (this.state.inviting) {
var Loader = sdk.getComponent("elements.Spinner");
@ -316,10 +317,25 @@ module.exports = React.createClass({
<Loader />
);
} 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 (
<form onSubmit={this.onPopulateInvite}>
<input className="mx_MemberList_invite" ref="invite" id="mx_MemberList_invite" placeholder="Invite user (email)"/>
</form>
<SearchableEntityList searchPlaceholderText={"Invite / Search"}
onSubmit={this.onInvite}
entities={
Entities.fromRoomMembers(
room.getJoinedMembers()
).concat(
Entities.fromUsers(allUsers, true, this.onInvite)
)
} />
);
}
},

View file

@ -27,9 +27,7 @@ module.exports = React.createClass({
displayName: 'MemberTile',
propTypes: {
member: React.PropTypes.any, // RoomMember
onFinished: React.PropTypes.func,
customDisplayName: React.PropTypes.string // for 3pid invites
member: React.PropTypes.any.isRequired, // RoomMember
},
getInitialState: function() {
@ -37,13 +35,11 @@ module.exports = React.createClass({
},
shouldComponentUpdate: function(nextProps, nextState) {
if (this.state.hover !== nextState.hover) return true;
if (!this.props.member) { return false; } // e.g. 3pid members
if (
this.member_last_modified_time === undefined ||
this.member_last_modified_time < nextProps.member.getLastModifiedTime()
) {
return true
return true;
}
if (
nextProps.member.user &&
@ -55,17 +51,7 @@ module.exports = React.createClass({
return false;
},
mouseEnter: function(e) {
this.setState({ 'hover': true });
},
mouseLeave: function(e) {
this.setState({ 'hover': false });
},
onClick: function(e) {
if (!this.props.member) { return; } // e.g. 3pid members
dis.dispatch({
action: 'view_user',
member: this.props.member,
@ -73,115 +59,50 @@ module.exports = React.createClass({
},
_getDisplayName: function() {
if (this.props.customDisplayName) {
return this.props.customDisplayName;
}
return this.props.member.name;
},
getPowerLabel: function() {
if (!this.props.member) {
return this._getDisplayName();
}
var label = this.props.member.userId + " (power " + this.props.member.powerLevel + ")";
return label;
return this.props.member.userId + " (power " + this.props.member.powerLevel + ")";
},
render: function() {
var member = this.props.member;
var isMyUser = false;
var name = this._getDisplayName();
var active = -1;
var presenceClass = "mx_MemberTile_offline";
if (member) {
if (member.user) {
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;
if (this.props.member) {
var powerLevel = this.props.member.powerLevel;
if (powerLevel >= 50 && powerLevel < 99) {
power = <img src="img/mod.svg" className="mx_MemberTile_power" width="16" height="17" alt="Mod"/>;
}
if (powerLevel >= 99) {
power = <img src="img/admin.svg" className="mx_MemberTile_power" width="16" height="17" alt="Admin"/>;
}
}
}
var mainClassName = "mx_MemberTile ";
mainClassName += presenceClass;
if (this.state.hover) {
mainClassName += " mx_MemberTile_hover";
}
var nameEl;
if (this.state.hover && this.props.member) {
var presenceState = (member && member.user) ? member.user.presence : null;
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 EntityTile = sdk.getComponent('rooms.EntityTile');
var av;
if (member) {
av = (
<MemberAvatar member={this.props.member} width={36} height={36} />
);
var member = this.props.member;
var name = this._getDisplayName();
var active = -1;
var presenceState = member.user ? member.user.presence : null;
var av = (
<MemberAvatar member={member} width={36} height={36} />
);
var power;
var powerLevel = this.props.member.powerLevel;
if (powerLevel >= 50 && powerLevel < 99) {
power = <img src="img/mod.svg" className="mx_MemberTile_power" width="16" height="17" alt="Mod"/>;
}
else {
av = (
<BaseAvatar name={name} width={36} height={36} />
);
if (powerLevel >= 99) {
power = <img src="img/admin.svg" className="mx_MemberTile_power" width="16" height="17" alt="Admin"/>;
}
if (member.user) {
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
);
}
this.member_last_modified_time = member.getLastModifiedTime();
return (
<div className={mainClassName} title={ this.getPowerLabel() }
onClick={ this.onClick } onMouseEnter={ this.mouseEnter }
onMouseLeave={ this.mouseLeave }>
<div className="mx_MemberTile_avatar">
{ av }
{ power }
</div>
{ nameEl }
</div>
<EntityTile {...this.props} presenceActiveAgo={active} presenceState={presenceState}
avatarJsx={av} title={this.getPowerLabel()} onClick={this.onClick}
shouldComponentUpdate={this.shouldComponentUpdate.bind(this)}
name={name} powerLevel={this.props.member.powerLevel} />
);
}
});

View file

@ -128,6 +128,10 @@ module.exports = React.createClass({
return this.refs.share_history.checked ? "shared" : "invited";
},
areNotificationsMuted: function() {
return this.refs.are_notifications_muted.checked;
},
getPowerLevels: function() {
if (!this.state.power_levels_changed) return undefined;
@ -387,10 +391,19 @@ module.exports = React.createClass({
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 user_id = MatrixClientPeg.get().credentials.userId;
if (power_levels) {
power_levels = power_levels.getContent();
@ -675,6 +688,11 @@ module.exports = React.createClass({
{ 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>
<div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings">
<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} />
);
}
});