From 048260bb1b2bbc96d281d47d3eb0a743a139c19b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 21 Sep 2015 19:22:29 +0200 Subject: [PATCH] WIP at turning MemberInfo into a ContextualMenu --- skins/base/css/common.css | 36 +++- skins/base/css/molecules/MemberInfo.css | 57 ----- skins/base/views/molecules/MemberInfo.js | 62 +----- skins/base/views/molecules/MemberTile.js | 44 +--- src/Modal.js | 4 +- src/controllers/molecules/MemberInfo.js | 43 ---- src/controllers/molecules/MemberTile.js | 255 ----------------------- 7 files changed, 47 insertions(+), 454 deletions(-) diff --git a/skins/base/css/common.css b/skins/base/css/common.css index d90ce11ac4..9527b253d4 100644 --- a/skins/base/css/common.css +++ b/skins/base/css/common.css @@ -43,9 +43,39 @@ html { overflow: -moz-scrollbars-none; } -/* FIXME: why is all the dialog stuff in here rather than in per-component files? */ +.mx_ContextualMenu_background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 1.0; + z-index: 2000; +} -.mx_Dialog_Background { +.mx_ContextualMenu { + text-align: center; + border: 1px solid #a9dbf4; + border-radius: 8px; + background-color: #fff; + position: fixed; + z-index: 1000; + padding: 6px; +} + +.mx_ContextualMenu_chevron { + padding: 12px; + position: absolute; + right: -21px; + top: 0px; +} + +.mx_ContextualMenu_field { + padding: 6px; +} + + +.mx_Dialog_background { position: fixed; top: 0; left: 0; @@ -56,7 +86,7 @@ html { z-index: 2000; } -.mx_Dialog_Wrapper { +.mx_Dialog_wrapper { position: fixed; top: 0; left: 0; diff --git a/skins/base/css/molecules/MemberInfo.css b/skins/base/css/molecules/MemberInfo.css index 144212d766..52c48a795f 100644 --- a/skins/base/css/molecules/MemberInfo.css +++ b/skins/base/css/molecules/MemberInfo.css @@ -14,60 +14,3 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_MemberInfo { - text-align: center; - border: 1px solid #a9dbf4; - border-radius: 8px; - background-color: #fff; - position: absolute; - width: 200px; - margin-left: -295px; - margin-top: 0px; - z-index: 1000; - padding: 6px; -} - -.mx_MemberInfo_chevron { - padding: 12px; - position: absolute; - right: -21px; - top: 0px; -} - -/* - * a hacky shim to extend the hitmask of the overlay to overlap - * better with the main menu itself - */ -.mx_MemberInfo_shim { - position: absolute; - left: 212px; - width: 40px; - height: 100%; -} - -.mx_MemberInfo_avatar { - padding: 6px; -} - -.mx_MemberInfo_avatarImg { - border-radius: 128px; -} - -.mx_MemberInfo_field { - padding: 6px; - overflow: hidden; - text-overflow: ellipsis; -} - -.mx_MemberInfo_button { - vertical-align: middle; - max-width: 100px; - height: 36px; - background-color: #50e3c2; - line-height: 36px; - border-radius: 36px; - color: #fff; - margin: auto; - margin-top: 6px; - margin-bottom: 6px; -} diff --git a/skins/base/views/molecules/MemberInfo.js b/skins/base/views/molecules/MemberInfo.js index e79b99a264..512ed9f423 100644 --- a/skins/base/views/molecules/MemberInfo.js +++ b/skins/base/views/molecules/MemberInfo.js @@ -27,84 +27,34 @@ module.exports = React.createClass({ displayName: 'MemberInfo', mixins: [MemberInfoController], - componentDidMount: function() { - var self = this; - - var memberInfo = this.getDOMNode(); - var memberListScroll = document.getElementsByClassName("mx_MemberList_border")[0]; - if (memberListScroll) { - memberInfo.style.top = (memberInfo.parentElement.offsetTop - memberListScroll.scrollTop) + "px"; - } - }, - - getDuration: function(time) { - if (!time) return; - var t = parseInt(time / 1000); - var s = t % 60; - var m = parseInt(t / 60) % 60; - var h = parseInt(t / (60 * 60)) % 24; - var d = parseInt(t / (60 * 60 * 24)); - if (t < 60) { - if (t < 0) { - return "0s"; - } - return s + "s"; - } - if (t < 60 * 60) { - return m + "m"; - } - if (t < 24 * 60 * 60) { - return h + "h"; - } - return d + "d "; - }, - render: function() { - var activeAgo = "unknown"; - if (this.state.active >= 0) { - activeAgo = this.getDuration(this.state.active); - } var kickButton, banButton, muteButton, giveModButton; if (this.state.can.kick) { - kickButton =
+ kickButton =
Kick
; } if (this.state.can.ban) { - banButton =
+ banButton =
Ban
; } if (this.state.can.mute) { var muteLabel = this.state.muted ? "Unmute" : "Mute"; - muteButton =
+ muteButton =
{muteLabel}
; } if (this.state.can.modifyLevel) { var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod"; - giveModButton =
+ giveModButton =
{giveOpLabel}
} - var opLabel; - if (this.state.isTargetMod) { - var level = this.props.member.powerLevelNorm + "%"; - opLabel =
Moderator ({level})
- } return ( -
- -
-
- -
-
{this.props.member.userId}
- {opLabel} -
Presence: {this.state.presence}
-
Last active: {activeAgo}
-
Start chat
+
+
Start chat
{muteButton} {kickButton} {banButton} diff --git a/skins/base/views/molecules/MemberTile.js b/skins/base/views/molecules/MemberTile.js index a60fbb06d8..2cfceb4dc8 100644 --- a/skins/base/views/molecules/MemberTile.js +++ b/skins/base/views/molecules/MemberTile.js @@ -21,6 +21,7 @@ var React = require('react'); var MatrixClientPeg = require("../../../../src/MatrixClientPeg"); var ComponentBroker = require('../../../../src/ComponentBroker'); var Modal = require("../../../../src/Modal"); +var ContextualMenu = require("../../../../src/ContextualMenu"); var MemberTileController = require("../../../../src/controllers/molecules/MemberTile"); var MemberInfo = ComponentBroker.get('molecules/MemberInfo'); var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog"); @@ -43,8 +44,11 @@ module.exports = React.createClass({ }, onClick: function(e) { - this.setState({ 'menu': true }); - this.setState(this._calculateOpsPermissions()); + ContextualMenu.createMenu(MemberInfo, { + member: this.props.member, + right: window.innerWidth - e.pageX, + top: e.pageY, + }); }, getDuration: function(time) { @@ -118,41 +122,6 @@ module.exports = React.createClass({ nameClass += " mx_MemberTile_zalgo"; } - var menu; - if (this.state.menu) { - var kickButton, banButton, muteButton, giveModButton; - if (this.state.can.kick) { - kickButton =
- Kick -
; - } - if (this.state.can.ban) { - banButton =
- Ban -
; - } - if (this.state.can.mute) { - var muteLabel = this.state.muted ? "Unmute" : "Mute"; - muteButton =
- {muteLabel} -
; - } - if (this.state.can.modifyLevel) { - var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod"; - giveModButton =
- {giveOpLabel} -
- } - menu =
- -
Chat
- {muteButton} - {kickButton} - {banButton} - {giveModButton} -
; - } - var nameEl; if (this.state.hover) { var presence; @@ -181,7 +150,6 @@ module.exports = React.createClass({ return (
- { menu }
{ power } diff --git a/src/Modal.js b/src/Modal.js index d18b39e68b..ba7660bf9d 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -47,11 +47,11 @@ module.exports = { // FIXME: If a dialog uses getDefaultProps it clobbers the onFinished // property set here so you can't close the dialog from a button click! var dialog = ( -
+
-
+
); diff --git a/src/controllers/molecules/MemberInfo.js b/src/controllers/molecules/MemberInfo.js index 5404c3370d..5c63d970c2 100644 --- a/src/controllers/molecules/MemberInfo.js +++ b/src/controllers/molecules/MemberInfo.js @@ -16,8 +16,6 @@ limitations under the License. /* * State vars: - * 'presence' : string (online|offline|unavailable etc) - * 'active' : number (ms ago; can be -1) * 'can': { * kick: boolean, * ban: boolean, @@ -38,54 +36,15 @@ var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog"); module.exports = { componentDidMount: function() { var self = this; - // listen for presence changes - function updateUserState(event, user) { - if (!self.props.member) { return; } - - if (user.userId === self.props.member.userId) { - self.setState({ - presence: user.presence, - active: user.lastActiveAgo - }); - } - } - MatrixClientPeg.get().on("User.presence", updateUserState); - this.userPresenceFn = updateUserState; - - // listen for power level changes - function updatePowerLevel(event, member) { - if (!self.props.member) { return; } - - if (member.roomId !== self.props.member.roomId) { - return; - } - // only interested in changes to us or them - var myUserId = MatrixClientPeg.get().credentials.userId; - if ([myUserId, self.props.member.userId].indexOf(member.userId) === -1) { - return; - } - self.setState(self._calculateOpsPermissions()); - } - MatrixClientPeg.get().on("RoomMember.powerLevel", updatePowerLevel); - this.updatePowerLevelFn = updatePowerLevel; // work out the current state if (this.props.member) { var usr = MatrixClientPeg.get().getUser(this.props.member.userId) || {}; var memberState = this._calculateOpsPermissions(); - memberState.presence = usr.presence || "offline"; - memberState.active = usr.lastActiveAgo || -1; this.setState(memberState); } }, - componentWillUnmount: function() { - MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn); - MatrixClientPeg.get().removeListener( - "RoomMember.powerLevel", this.updatePowerLevelFn - ); - }, - onKick: function() { var roomId = this.props.member.roomId; var target = this.props.member.userId; @@ -244,8 +203,6 @@ module.exports = { getInitialState: function() { return { - presence: "offline", - active: -1, can: { kick: false, ban: false, diff --git a/src/controllers/molecules/MemberTile.js b/src/controllers/molecules/MemberTile.js index 19c08731c8..38bf43d62c 100644 --- a/src/controllers/molecules/MemberTile.js +++ b/src/controllers/molecules/MemberTile.js @@ -32,264 +32,9 @@ module.exports = { // }); // }, - onKick: function() { - var roomId = this.props.member.roomId; - var target = this.props.member.userId; - var self = this; - 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 - }); - }); - }, - - onBan: function() { - var roomId = this.props.member.roomId; - var target = this.props.member.userId; - var self = this; - 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 - }); - }); - }, - - onMuteToggle: function() { - var roomId = this.props.member.roomId; - var target = this.props.member.userId; - var self = this; - var room = MatrixClientPeg.get().getRoom(roomId); - if (!room) { - return; - } - var powerLevelEvent = room.currentState.getStateEvents( - "m.room.power_levels", "" - ); - if (!powerLevelEvent) { - return; - } - var isMuted = this.state.muted; - var powerLevels = powerLevelEvent.getContent(); - var levelToSend = ( - (powerLevels.events ? powerLevels.events["m.room.message"] : null) || - powerLevels.events_default - ); - var level; - if (isMuted) { // unmute - level = levelToSend; - } - else { // mute - level = levelToSend - 1; - } - - 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 - }); - }); - }, - - onModToggle: function() { - var roomId = this.props.member.roomId; - var target = this.props.member.userId; - var room = MatrixClientPeg.get().getRoom(roomId); - if (!room) { - return; - } - var powerLevelEvent = room.currentState.getStateEvents( - "m.room.power_levels", "" - ); - if (!powerLevelEvent) { - return; - } - var me = room.getMember(MatrixClientPeg.get().credentials.userId); - if (!me) { - return; - } - var defaultLevel = powerLevelEvent.getContent().users_default; - var modLevel = me.powerLevel - 1; - // 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 - }); - }); - }, - - 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. - var rooms = MatrixClientPeg.get().getRooms(); - var userIds = [ - this.props.member.userId, - MatrixClientPeg.get().credentials.userId - ]; - var existingRoomId = null; - for (var i = 0; i < rooms.length; i++) { - var members = rooms[i].getJoinedMembers(); - if (members.length === 2) { - var hasTargetUsers = true; - for (var j = 0; j < members.length; j++) { - if (userIds.indexOf(members[j].userId) === -1) { - hasTargetUsers = false; - break; - } - } - if (hasTargetUsers) { - existingRoomId = rooms[i].roomId; - break; - } - } - } - - if (existingRoomId) { - dis.dispatch({ - action: 'view_room', - room_id: existingRoomId - }); - } - else { - MatrixClientPeg.get().createRoom({ - invite: [this.props.member.userId], - preset: "private_chat" - }).done(function(res) { - dis.dispatch({ - action: 'view_room', - room_id: res.room_id - }); - }, function(err) { - console.error( - "Failed to create room: %s", JSON.stringify(err) - ); - }); - } - }, - - onLeaveClick: function() { - var roomId = this.props.member.roomId; - Modal.createDialog(QuestionDialog, { - title: "Leave room", - description: "Are you sure you want to leave the room?", - onFinished: function(should_leave) { - if (should_leave) { - var d = MatrixClientPeg.get().leave(roomId); - - var modal = Modal.createDialog(Loader); - - d.then(function() { - modal.close(); - dis.dispatch({action: 'view_next_room'}); - }, function(err) { - modal.close(); - Modal.createDialog(ErrorDialog, { - title: "Failed to leave room", - description: err.toString() - }); - }); - } - } - }); - }, - getInitialState: function() { return { hover: false, - menu: false, - - // presence: "offline", - // active: -1, - can: { - kick: false, - ban: false, - mute: false, - modifyLevel: false - }, - muted: false, - isTargetMod: false, } }, - - _calculateOpsPermissions: function() { - var defaultPerms = { - can: {}, - muted: false, - modifyLevel: false - }; - var room = MatrixClientPeg.get().getRoom(this.props.member.roomId); - if (!room) { - return defaultPerms; - } - var powerLevels = room.currentState.getStateEvents( - "m.room.power_levels", "" - ); - if (!powerLevels) { - return defaultPerms; - } - var me = room.getMember(MatrixClientPeg.get().credentials.userId); - var them = this.props.member; - return { - can: this._calculateCanPermissions( - me, them, powerLevels.getContent() - ), - muted: this._isMuted(them, powerLevels.getContent()), - isTargetMod: them.powerLevel > powerLevels.getContent().users_default - }; - }, - - _calculateCanPermissions: function(me, them, powerLevels) { - var can = { - kick: false, - ban: false, - mute: false, - modifyLevel: false - }; - var canAffectUser = them.powerLevel < me.powerLevel; - if (!canAffectUser) { - //console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel); - return can; - } - var editPowerLevel = ( - (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) || - powerLevels.state_default - ); - can.kick = me.powerLevel >= powerLevels.kick; - can.ban = me.powerLevel >= powerLevels.ban; - can.mute = me.powerLevel >= editPowerLevel; - can.modifyLevel = me.powerLevel > them.powerLevel; - return can; - }, - - _isMuted: function(member, powerLevelContent) { - if (!powerLevelContent || !member) { - return false; - } - var levelToSend = ( - (powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) || - powerLevelContent.events_default - ); - return member.powerLevel < levelToSend; - }, };