diff --git a/src/component-index.js b/src/component-index.js index 5fcf8e1ce0..6514fabf61 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -23,15 +23,15 @@ limitations under the License. module.exports.components = {}; module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom'); -module.exports.components['structures.login.ForgotPassword'] = require('./components/structures/login/ForgotPassword'); -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'); module.exports.components['structures.UploadBar'] = require('./components/structures/UploadBar'); module.exports.components['structures.UserSettings'] = require('./components/structures/UserSettings'); +module.exports.components['structures.login.ForgotPassword'] = require('./components/structures/login/ForgotPassword'); +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['views.avatars.BaseAvatar'] = require('./components/views/avatars/BaseAvatar'); module.exports.components['views.avatars.MemberAvatar'] = require('./components/views/avatars/MemberAvatar'); module.exports.components['views.avatars.RoomAvatar'] = require('./components/views/avatars/RoomAvatar'); @@ -42,6 +42,7 @@ module.exports.components['views.dialogs.ErrorDialog'] = require('./components/v 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.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'); module.exports.components['views.elements.TintableSvg'] = require('./components/views/elements/TintableSvg'); module.exports.components['views.elements.UserSelector'] = require('./components/views/elements/UserSelector'); @@ -53,10 +54,10 @@ 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'); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index e5af2a86b5..23be8d6587 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -636,6 +636,8 @@ module.exports = React.createClass({ onUserClick: function(event, userId) { event.preventDefault(); + + /* var MemberInfo = sdk.getComponent('rooms.MemberInfo'); var member = new Matrix.RoomMember(null, userId); ContextualMenu.createMenu(MemberInfo, { @@ -643,6 +645,14 @@ module.exports = React.createClass({ right: window.innerWidth - event.pageX, top: event.pageY }); + */ + + var member = new Matrix.RoomMember(null, userId); + if (!member) { return; } + dis.dispatch({ + action: 'view_user', + member: member, + }); }, onLogoutClick: function(event) { diff --git a/src/components/views/elements/PowerSelector.js b/src/components/views/elements/PowerSelector.js new file mode 100644 index 0000000000..c47c9f3809 --- /dev/null +++ b/src/components/views/elements/PowerSelector.js @@ -0,0 +1,108 @@ +/* +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 roles = { + 0: 'User', + 50: 'Moderator', + 100: 'Admin', +}; + +var reverseRoles = {}; +Object.keys(roles).forEach(function(key) { + reverseRoles[roles[key]] = key; +}); + +module.exports = React.createClass({ + displayName: 'PowerSelector', + + propTypes: { + value: React.PropTypes.number.isRequired, + disabled: React.PropTypes.bool, + onChange: React.PropTypes.func, + }, + + getInitialState: function() { + return { + custom: (roles[this.props.value] === undefined), + }; + }, + + onSelectChange: function(event) { + this.state.custom = (event.target.value === "Custom"); + this.props.onChange(this.getValue()); + }, + + onCustomBlur: function(event) { + this.props.onChange(this.getValue()); + }, + + onCustomKeyDown: function(event) { + if (event.key == "Enter") { + this.props.onChange(this.getValue()); + } + }, + + getValue: function() { + var value; + if (this.refs.select) { + value = reverseRoles[ this.refs.select.value ]; + if (this.refs.custom) { + if (value === undefined) value = parseInt( this.refs.custom.value ); + } + } + return value; + }, + + render: function() { + var customPicker; + if (this.state.custom) { + var input; + if (this.props.disabled) { + input = { this.props.value } + } + else { + input = + } + customPicker = of { input }; + } + + var selectValue = roles[this.props.value] || "Custom"; + var select; + if (this.props.disabled) { + select = { selectValue }; + } + else { + select = + + } + + return ( + + { select } + { customPicker } + + ); + } +}); diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index b5f0b88b40..a8a601c2d6 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -58,15 +58,16 @@ module.exports = React.createClass({ var roomId = this.props.member.roomId; var target = this.props.member.userId; MatrixClientPeg.get().kick(roomId, target).done(function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Kick success"); - }, function(err) { - Modal.createDialog(ErrorDialog, { - title: "Kick error", - description: err.message - }); - }); + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Kick success"); + }, function(err) { + Modal.createDialog(ErrorDialog, { + title: "Kick error", + description: err.message + }); + } + ); this.props.onFinished(); }, @@ -74,16 +75,18 @@ module.exports = React.createClass({ var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var roomId = this.props.member.roomId; var target = this.props.member.userId; - MatrixClientPeg.get().ban(roomId, target).done(function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Ban success"); - }, function(err) { - Modal.createDialog(ErrorDialog, { - title: "Ban error", - description: err.message - }); - }); + MatrixClientPeg.get().ban(roomId, target).done( + function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Ban success"); + }, function(err) { + Modal.createDialog(ErrorDialog, { + title: "Ban error", + description: err.message + }); + } + ); this.props.onFinished(); }, @@ -118,16 +121,17 @@ module.exports = React.createClass({ } MatrixClientPeg.get().setPowerLevel(roomId, target, level, powerLevelEvent).done( - function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Mute toggle success"); - }, function(err) { - Modal.createDialog(ErrorDialog, { - title: "Mute error", - description: err.message - }); - }); + function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Mute toggle success"); + }, function(err) { + Modal.createDialog(ErrorDialog, { + title: "Mute error", + description: err.message + }); + } + ); this.props.onFinished(); }, @@ -154,22 +158,55 @@ module.exports = React.createClass({ } var defaultLevel = powerLevelEvent.getContent().users_default; var modLevel = me.powerLevel - 1; + if (modLevel > 50 && defaultLevel < 50) modLevel = 50; // try to stick with the vector level defaults // toggle the level var newLevel = this.state.isTargetMod ? defaultLevel : modLevel; MatrixClientPeg.get().setPowerLevel(roomId, target, newLevel, powerLevelEvent).done( - function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Mod toggle success"); - }, function(err) { - Modal.createDialog(ErrorDialog, { - title: "Mod error", - description: err.message - }); - }); + function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Mod toggle success"); + }, function(err) { + Modal.createDialog(ErrorDialog, { + title: "Mod error", + description: err.message + }); + } + ); this.props.onFinished(); }, + onPowerChange: function(powerLevel) { + var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + var roomId = this.props.member.roomId; + var target = this.props.member.userId; + var room = MatrixClientPeg.get().getRoom(roomId); + if (!room) { + this.props.onFinished(); + return; + } + var powerLevelEvent = room.currentState.getStateEvents( + "m.room.power_levels", "" + ); + if (!powerLevelEvent) { + this.props.onFinished(); + return; + } + MatrixClientPeg.get().setPowerLevel(roomId, target, powerLevel, powerLevelEvent).done( + function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Power change success"); + }, function(err) { + Modal.createDialog(ErrorDialog, { + title: "Failure to change power level", + description: err.message + }); + } + ); + this.props.onFinished(); + }, + onChatClick: function() { // check if there are any existing rooms with just us and them (1:1) // If so, just view that room. If not, create a private room with them. @@ -209,20 +246,22 @@ module.exports = React.createClass({ MatrixClientPeg.get().createRoom({ invite: [this.props.member.userId], preset: "private_chat" - }).done(function(res) { - self.setState({ creatingRoom: false }); - dis.dispatch({ - action: 'view_room', - room_id: res.room_id - }); - self.props.onFinished(); - }, function(err) { - self.setState({ creatingRoom: false }); - console.error( - "Failed to create room: %s", JSON.stringify(err) - ); - self.props.onFinished(); - }); + }).done( + function(res) { + self.setState({ creatingRoom: false }); + dis.dispatch({ + action: 'view_room', + room_id: res.room_id + }); + self.props.onFinished(); + }, function(err) { + self.setState({ creatingRoom: false }); + console.error( + "Failed to create room: %s", JSON.stringify(err) + ); + self.props.onFinished(); + } + ); } }, @@ -291,9 +330,15 @@ module.exports = React.createClass({ (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) || powerLevels.state_default ); + var levelToSend = ( + (powerLevels.events ? powerLevels.events["m.room.message"] : null) || + powerLevels.events_default + ); + can.kick = me.powerLevel >= powerLevels.kick; can.ban = me.powerLevel >= powerLevels.ban; can.mute = me.powerLevel >= editPowerLevel; + can.toggleMod = me.powerLevel > them.powerLevel && them.powerLevel >= levelToSend; can.modifyLevel = me.powerLevel > them.powerLevel; return can; }, @@ -317,12 +362,11 @@ module.exports = React.createClass({ }, render: function() { - var interactButton, kickButton, banButton, muteButton, giveModButton, spinner; - if (this.props.member.userId === MatrixClientPeg.get().credentials.userId) { - interactButton =
Leave room
; - } - else { - interactButton =
Start chat
; + var startChat, kickButton, banButton, muteButton, giveModButton, spinner; + if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) { + // FIXME: we're referring to a vector component from react-sdk + var BottomLeftMenuTile = sdk.getComponent('rooms.BottomLeftMenuTile'); + startChat = } if (this.state.creatingRoom) { @@ -346,35 +390,56 @@ module.exports = React.createClass({ {muteLabel} ; } - if (this.state.can.modifyLevel) { - var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod"; + if (this.state.can.toggleMod) { + var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator"; giveModButton =
{giveOpLabel}
} + // TODO: we should have an invite button if this MemberInfo is showing a user who isn't actually in the current room yet + // e.g. clicking on a linkified userid in a room + + var adminTools; + if (kickButton || banButton || muteButton || giveModButton) { + adminTools = +
+

Admin tools

+ +
+ {muteButton} + {kickButton} + {banButton} + {giveModButton} +
+
+ } + var MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); + var PowerSelector = sdk.getComponent('elements.PowerSelector'); return (
+

{ this.props.member.name }

-
- { this.props.member.userId } -
-
- power: { this.props.member.powerLevelNorm }% -
-
- {interactButton} - {muteButton} - {kickButton} - {banButton} - {giveModButton} - {spinner} + +
+
+ { this.props.member.userId } +
+
+ Level: +
+ + { startChat } + + { adminTools } + + { spinner }
); } diff --git a/src/components/views/rooms/MemberTile.js b/src/components/views/rooms/MemberTile.js index 304d759ea7..84d8edb2b5 100644 --- a/src/components/views/rooms/MemberTile.js +++ b/src/components/views/rooms/MemberTile.js @@ -83,10 +83,7 @@ module.exports = React.createClass({ if (!this.props.member) { return this._getDisplayName(); } - var label = this.props.member.userId; - if (this.state.isTargetMod) { - label += " - Mod (" + this.props.member.powerLevelNorm + "%)"; - } + var label = this.props.member.userId + " (power " + this.props.member.powerLevel + ")"; return label; }, @@ -120,8 +117,18 @@ module.exports = React.createClass({ // 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; @@ -170,7 +177,8 @@ module.exports = React.createClass({ onClick={ this.onClick } onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave }>
- {av} + { av } + { power }
{ nameEl }