diff --git a/src/Avatar.js b/src/Avatar.js index e97ed6b673..0ef6c8d07b 100644 --- a/src/Avatar.js +++ b/src/Avatar.js @@ -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; diff --git a/src/Entities.js b/src/Entities.js new file mode 100644 index 0000000000..47103bfb65 --- /dev/null +++ b/src/Entities.js @@ -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 ( + + ); + } + + 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 ( + + ); + } + + 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); + }) + } +}; diff --git a/src/component-index.js b/src/component-index.js index 6514fabf61..2446b26b8d 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -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'); diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index b4986e7a33..f8511bce6b 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -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 (
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index fa932b6757..44b7b9a973 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -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({

Notifications

-
-
-
- -
-
- -
-
-
+

Advanced

diff --git a/src/components/views/dialogs/ErrorDialog.js b/src/components/views/dialogs/ErrorDialog.js index ed9364df60..d06cf2de84 100644 --- a/src/components/views/dialogs/ErrorDialog.js +++ b/src/components/views/dialogs/ErrorDialog.js @@ -48,7 +48,7 @@ module.exports = React.createClass({ render: function() { return (
-
+
{this.props.title}
diff --git a/src/components/views/dialogs/QuestionDialog.js b/src/components/views/dialogs/QuestionDialog.js index f415201e45..4eeecd64b3 100644 --- a/src/components/views/dialogs/QuestionDialog.js +++ b/src/components/views/dialogs/QuestionDialog.js @@ -46,7 +46,7 @@ module.exports = React.createClass({ render: function() { return (
-
+
{this.props.title}
diff --git a/src/components/views/dialogs/TextInputDialog.js b/src/components/views/dialogs/TextInputDialog.js new file mode 100644 index 0000000000..3cda852449 --- /dev/null +++ b/src/components/views/dialogs/TextInputDialog.js @@ -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 ( +
+
+ {this.props.title} +
+
+
+ +
+
+ +
+
+
+ + + +
+
+ ); + } +}); diff --git a/src/components/views/login/CustomServerDialog.js b/src/components/views/login/CustomServerDialog.js index 8a67dfd7e6..dc6a49abd6 100644 --- a/src/components/views/login/CustomServerDialog.js +++ b/src/components/views/login/CustomServerDialog.js @@ -22,7 +22,7 @@ module.exports = React.createClass({ render: function() { return (
-
+
Custom Server Options
diff --git a/src/components/views/rooms/EntityTile.js b/src/components/views/rooms/EntityTile.js new file mode 100644 index 0000000000..a0135fb0c3 --- /dev/null +++ b/src/components/views/rooms/EntityTile.js @@ -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, // + 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 = ( +
+ +
{ this.props.name }
+ +
+ ); + } + else { + nameEl = ( +
+ { this.props.name } +
+ ); + } + + var inviteButton; + if (this.props.showInviteButton) { + inviteButton = ( +
+ +
+ ); + } + + var power; + var powerLevel = this.props.powerLevel; + if (powerLevel >= 50 && powerLevel < 99) { + power = Mod; + } + if (powerLevel >= 99) { + power = Admin; + } + + + var MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); + var BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); + + var av = this.props.avatarJsx || ; + + return ( +
+
+ {av} +
+ { nameEl } + { power } + { inviteButton } +
+ ); + } +}); diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 3e3221992e..ff76e64c56 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -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 = ( + + ); memberList.push( - + ) }) } @@ -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({ ); } 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 ( -
- -
+ ); } }, diff --git a/src/components/views/rooms/MemberTile.js b/src/components/views/rooms/MemberTile.js index 0f40180274..9136e848f1 100644 --- a/src/components/views/rooms/MemberTile.js +++ b/src/components/views/rooms/MemberTile.js @@ -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 = ; - // } - - var power; - if (this.props.member) { - var powerLevel = this.props.member.powerLevel; - if (powerLevel >= 50 && powerLevel < 99) { - power = Mod; - } - if (powerLevel >= 99) { - power = 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 = ( -
- -
{ name }
- -
- ); - } - else { - nameEl = ( -
- { name } -
- ); - } - var MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); var BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); + var EntityTile = sdk.getComponent('rooms.EntityTile'); - var av; - if (member) { - av = ( - - ); + var member = this.props.member; + var name = this._getDisplayName(); + var active = -1; + var presenceState = member.user ? member.user.presence : null; + + var av = ( + + ); + var power; + var powerLevel = this.props.member.powerLevel; + if (powerLevel >= 50 && powerLevel < 99) { + power = Mod; } - else { - av = ( - - ); + if (powerLevel >= 99) { + power = 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 ( -
-
- { av } - { power } -
- { nameEl } -
+ ); } }); diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index f41e30091b..07751ee4da 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -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 } +

Notifications

+
+ +
+

Permissions

diff --git a/src/components/views/rooms/SearchableEntityList.js b/src/components/views/rooms/SearchableEntityList.js new file mode 100644 index 0000000000..52f3307bcd --- /dev/null +++ b/src/components/views/rooms/SearchableEntityList.js @@ -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 ( +
+
+ +
+
+ {this.state.results.map((entity) => { + return entity.getJsx(); + })} +
+
+ ); + } +}); + + module.exports = SearchableEntityList; diff --git a/src/components/views/rooms/UserTile.js b/src/components/views/rooms/UserTile.js new file mode 100644 index 0000000000..6597796764 --- /dev/null +++ b/src/components/views/rooms/UserTile.js @@ -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 = ( + + ); + + return ( + + ); + } +});