From a2e7c4aa770f34ba32bdeb634c2dae495de627b5 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 14 Aug 2015 19:15:41 +0100 Subject: [PATCH 1/8] WIP for fixing the popovers --- skins/base/css/molecules/MemberTile.css | 16 ++++++- skins/base/img/delete.png | Bin 0 -> 1006 bytes skins/base/views/molecules/MemberTile.js | 54 ++++++++++++++++++++--- 3 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 skins/base/img/delete.png diff --git a/skins/base/css/molecules/MemberTile.css b/skins/base/css/molecules/MemberTile.css index f296668f31..bd622018bc 100644 --- a/skins/base/css/molecules/MemberTile.css +++ b/skins/base/css/molecules/MemberTile.css @@ -68,6 +68,17 @@ limitations under the License. text-overflow: ellipsis; } +.mx_MemberTile_hover { + background-color: #f0f0f0; + font-size: 12px; + color: #747474; +} + +.mx_MemberTile_userId { + font-weight: bold; +} + +/* .mx_MemberTile_nameWrapper { display: table-cell; vertical-align: middle; @@ -77,19 +88,20 @@ limitations under the License. .mx_MemberTile_nameSpan { } +*/ .mx_MemberTile_unavailable .mx_MemberTile_avatar, .mx_MemberTile_unavailable .mx_MemberTile_name, .mx_MemberTile_unavailable .mx_MemberTile_nameSpan { - opacity: 0.75; + opacity: 0.66; } .mx_MemberTile_offline .mx_MemberTile_avatar, .mx_MemberTile_offline .mx_MemberTile_name, .mx_MemberTile_offline .mx_MemberTile_nameSpan { - opacity: 0.5; + opacity: 0.25; } .mx_MemberTile_zalgo { diff --git a/skins/base/img/delete.png b/skins/base/img/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..8ff20a116d4340453072e38604cd8de5c894f30a GIT binary patch literal 1006 zcmaJ=PfXKL7_T^tkfCTehBQ%-B#LAacehOvJx_uxPvBk?SnPYzS2He;il0< zO;8dO!@;vhuaX$!#e*JDkH$npNIY}#rUzfgHas|c==`S*;K>u@zbSlrLyl2gsNs3EfSYCjsho}K5R_bV5mumSudjT7QxtW4+^N=Z z%_u7tav3v@VItQjXo{Mej(pQvf*91{qT{Lb!Rxm)aBP*nkuf;K*I>h$*$CkL#%$Hv zSh8fBp1umEB83pR5St)!m%UJlRJz5h5Ic6WG-!q3C6zubs%Fdq4FwQnm~6`8vJ#MF zhR;Y+TDk%Rj?b{1$ckJ_5S28iqy^CaXp$|k7nF)#Z0903m2P0{D=gb=Hkqc#pkR^Z zWm#r9ffa-liAaU(9yX(t7mjrpbQoHJ<6{STAZ9e{Xcen8sr0Y~*YDDLVS7wuU~FXi zEYEOpOPxT&_?24CMy7Gq}(R@I}ePQijpFE74B|TS-zSf_gN+grX%(dUo zj!D}e6PJWLJN*ytuT1ofzDxYrt0Z4-4wQy(@sFO?K9}F$ySzv3pS7DJKRmGQ?@XNe twp;#k@$v3%d2+;zw)&sYBd7Zk=cpIoM}G;mt)KA=EEQ(;m-*Xw{{X0DIb#3- literal 0 HcmV?d00001 diff --git a/skins/base/views/molecules/MemberTile.js b/skins/base/views/molecules/MemberTile.js index bd1840511c..244a9b6fb7 100644 --- a/skins/base/views/molecules/MemberTile.js +++ b/skins/base/views/molecules/MemberTile.js @@ -47,6 +47,33 @@ module.exports = React.createClass({ this.setState({ 'hover': false }); }, + 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 "; + }, + + getPrettyPresence: function(user) { + var presence = user.presence; + return presence.charAt(0).toUpperCase() + presence.slice(1); + }, + render: function() { var isMyUser = MatrixClientPeg.get().credentials.userId == this.props.member.userId; @@ -66,30 +93,43 @@ module.exports = React.createClass({ } } mainClassName += presenceClass; + if (this.state.hover) { + mainClassName += " mx_MemberTile_hover"; + } var name = this.props.member.name; if (isMyUser) name += " (me)"; - var leave = isMyUser ? X : null; + var leave = isMyUser ? : null; - var nameClass = this.state.hover ? "mx_MemberTile_nameSpan" : "mx_MemberTile_name"; + var nameClass = "mx_MemberTile_name"; if (zalgo.test(name)) { nameClass += " mx_MemberTile_zalgo"; } var nameEl; if (this.state.hover) { + var presence; + // FIXME: make presence data update whenever User.presence changes... + var active = this.props.member.user.lastActiveAgo || -1; + if (active >= 0) { + presence =
{ this.getPrettyPresence(this.props.member.user) } for { this.getDuration(active) }
; + } + else { + presence =
{ this.getPrettyPresence(this.props.member.user) }
; + } + nameEl = -
+
- {name} - {leave} +
{ this.props.member.userId }
+ { presence } + { leave }
} else { nameEl =
- {name} - {leave} + { name }
} From 80c3b2c8a3358c512ca84ab4021c2b6ec487a9ce Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 14 Aug 2015 21:14:05 +0100 Subject: [PATCH 2/8] match the design --- skins/base/css/molecules/MemberTile.css | 31 ++++++++++++++++++------ skins/base/css/organisms/MemberList.css | 3 +-- skins/base/views/atoms/EditableText.js | 1 - skins/base/views/molecules/MemberTile.js | 14 +++++++---- skins/base/views/organisms/MemberList.js | 7 +----- 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/skins/base/css/molecules/MemberTile.css b/skins/base/css/molecules/MemberTile.css index bd622018bc..a4310d206b 100644 --- a/skins/base/css/molecules/MemberTile.css +++ b/skins/base/css/molecules/MemberTile.css @@ -15,13 +15,14 @@ limitations under the License. */ .mx_MemberTile { - cursor: pointer; display: table-row; height: 49px; + position: relative; } .mx_MemberTile_avatar { display: table-cell; + padding-left: 14px; padding-right: 12px; padding-top: 3px; padding-bottom: 3px; @@ -31,6 +32,10 @@ limitations under the License. position: relative; } +.mx_MemberTile_inviteTile { + cursor: pointer; +} + .mx_MemberTile_inviteEditing { display: initial ! important; } @@ -50,14 +55,14 @@ limitations under the License. font-size: 14px; padding: 9px; margin-top: 6px; + margin-left: 14px; } .mx_MemberTile_power { - z-index: 10; position: absolute; width: 48px; height: 48px; - left: -4px; + left: 10px; top: -1px; } @@ -68,6 +73,12 @@ limitations under the License. text-overflow: ellipsis; } +.mx_MemberTile_details { + display: table-cell; + padding-right: 14px; + vertical-align: middle; +} + .mx_MemberTile_hover { background-color: #f0f0f0; font-size: 12px; @@ -76,6 +87,16 @@ limitations under the License. .mx_MemberTile_userId { font-weight: bold; + overflow: hidden; + text-overflow: ellipsis; +} + +.mx_MemberTile_leave { + cursor: pointer; + margin-top: 8px; + margin-right: -4px; + margin-left: 6px; + float: right; } /* @@ -107,7 +128,3 @@ limitations under the License. .mx_MemberTile_zalgo { font-family: Helvetica, Arial, Sans-Serif; } - -.mx_MemberTile_leave { - float: right; -} diff --git a/skins/base/css/organisms/MemberList.css b/skins/base/css/organisms/MemberList.css index 4d2e7215dc..aab0def49a 100644 --- a/skins/base/css/organisms/MemberList.css +++ b/skins/base/css/organisms/MemberList.css @@ -42,7 +42,6 @@ limitations under the License. border: 1px solid #a9dbf4; overflow-y: auto; border-radius: 8px; - padding: 20px 14px 14px 24px; background-color: #fff; order: 1; @@ -57,5 +56,5 @@ limitations under the License. } .mx_MemberList h2 { - margin-top: 0px; + margin: 14px; } diff --git a/skins/base/views/atoms/EditableText.js b/skins/base/views/atoms/EditableText.js index 38aa5c8d87..d4aa285790 100644 --- a/skins/base/views/atoms/EditableText.js +++ b/skins/base/views/atoms/EditableText.js @@ -33,7 +33,6 @@ module.exports = React.createClass({ }, onClickDiv: function() { - console.log("onClickDiv triggered"); this.setState({ phase: this.Phases.Edit, }) diff --git a/skins/base/views/molecules/MemberTile.js b/skins/base/views/molecules/MemberTile.js index 244a9b6fb7..6e3cb85982 100644 --- a/skins/base/views/molecules/MemberTile.js +++ b/skins/base/views/molecules/MemberTile.js @@ -70,8 +70,12 @@ module.exports = React.createClass({ }, getPrettyPresence: function(user) { + if (!user) return "Unknown"; var presence = user.presence; - return presence.charAt(0).toUpperCase() + presence.slice(1); + if (presence === "online") return "Online"; + if (presence === "unavailable") return "Idle"; // XXX: is this actually right? + if (presence === "offline") return "Offline"; + return "Unknown"; }, render: function() { @@ -110,7 +114,7 @@ module.exports = React.createClass({ if (this.state.hover) { var presence; // FIXME: make presence data update whenever User.presence changes... - var active = this.props.member.user.lastActiveAgo || -1; + var active = this.props.member.user ? (this.props.member.user.lastActiveAgo || -1) : -1; if (active >= 0) { presence =
{ this.getPrettyPresence(this.props.member.user) } for { this.getDuration(active) }
; } @@ -118,12 +122,12 @@ module.exports = React.createClass({ presence =
{ this.getPrettyPresence(this.props.member.user) }
; } + // nameEl =
- -
{ this.props.member.userId }
- { presence } { leave } +
{ this.props.member.userId }
+ { presence }
} else { diff --git a/skins/base/views/organisms/MemberList.js b/skins/base/views/organisms/MemberList.js index 3d30d125df..a9a3c782a9 100644 --- a/skins/base/views/organisms/MemberList.js +++ b/skins/base/views/organisms/MemberList.js @@ -75,14 +75,9 @@ module.exports = React.createClass({ }, inviteTile: function() { - // if (this.state.inviting) { - // return ( - //
- // ); - // } - var classes = classNames({ mx_MemberTile: true, + mx_MemberTile_inviteTile: true, mx_MemberTile_inviteEditing: this.state.editing, }); From e3798e1b8572693f1471fe9f544b8470a85613b7 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 15 Aug 2015 03:06:21 +0100 Subject: [PATCH 3/8] WIP fixing up the member list - just needs CSS and testing --- skins/base/img/edit.png | Bin 0 -> 498 bytes skins/base/views/molecules/MemberTile.js | 61 +++++- skins/base/views/organisms/RoomView.js | 2 +- src/controllers/molecules/MemberTile.js | 245 ++++++++++++++++++++++- src/controllers/organisms/MemberList.js | 4 +- 5 files changed, 296 insertions(+), 16 deletions(-) create mode 100644 skins/base/img/edit.png diff --git a/skins/base/img/edit.png b/skins/base/img/edit.png new file mode 100644 index 0000000000000000000000000000000000000000..2686885f792ed69bdb88518bfe6850301719e7f0 GIT binary patch literal 498 zcmVPx$tVu*cR5%f(l)+BIKoEwfO92Z+Lr{4D6%!NV(UTtZDbRyF8YUWIV)P00Y@!!0 zCIz`G2_n)KOUu^%*OY950?|3N&CYy(XJ%(tqbXK1t>d$G^MmkYTF;*(&li78Hv5fv3;`jlz`kdE0p6&6#bX@=BbZ4cZ^78wt z-aEW*THVIyV8}TS;tW2-Ahu?H9YlnX2^?>>AIDFuBNhx;h%r1+&R!vvD`rF|W|-{- z^w9DMWg->~SRgtYi=J#O>rymMr{{4xZ+uhR4M-Dh+h0o(7_;M-puw;^XsiZrjHx24 zOB(Iwbr}f;EHoIl7`qdrLE|E4n+AnD2}ZI)L&uz`4pk5$RZ-4|C9N}SnrPL|AK~CFgUys : null; var nameClass = "mx_MemberTile_name"; @@ -110,6 +118,41 @@ 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; @@ -122,11 +165,10 @@ module.exports = React.createClass({ presence =
{ this.getPrettyPresence(this.props.member.user) }
; } - // nameEl =
{ leave } -
{ this.props.member.userId }
+
{ this.props.member.userId }
{ presence }
} @@ -138,7 +180,8 @@ module.exports = React.createClass({ } return ( -
+
+ { menu }
{ power } diff --git a/skins/base/views/organisms/RoomView.js b/skins/base/views/organisms/RoomView.js index b6bfedf190..eff25d965a 100644 --- a/skins/base/views/organisms/RoomView.js +++ b/skins/base/views/organisms/RoomView.js @@ -72,7 +72,7 @@ module.exports = React.createClass({ if (!this.state.numUnreadMessages) { return ""; } - return this.state.numUnreadMessages + " new messages"; + return this.state.numUnreadMessages + " new message" + (this.state.numUnreadMessages > 1 ? "s" : ""); }, scrollToBottom: function() { diff --git a/src/controllers/molecules/MemberTile.js b/src/controllers/molecules/MemberTile.js index ae4682847a..19c08731c8 100644 --- a/src/controllers/molecules/MemberTile.js +++ b/src/controllers/molecules/MemberTile.js @@ -25,13 +25,169 @@ var Loader = require("react-loader"); var MatrixClientPeg = require("../../MatrixClientPeg"); module.exports = { - onClick: function() { - dis.dispatch({ - action: 'view_user', - user_id: this.props.member.userId + // onClick: function() { + // dis.dispatch({ + // action: 'view_user', + // user_id: this.props.member.userId + // }); + // }, + + 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, { @@ -56,5 +212,84 @@ module.exports = { } } }); - } + }, + + 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; + }, }; diff --git a/src/controllers/organisms/MemberList.js b/src/controllers/organisms/MemberList.js index 912b142a13..3eef007ed4 100644 --- a/src/controllers/organisms/MemberList.js +++ b/src/controllers/organisms/MemberList.js @@ -61,7 +61,9 @@ module.exports = { function updateUserState(event, user) { var tile = self.refs[user.userId]; if (tile) { - tile.forceUpdate(); + // update the whole list to get the order right, not just this cell... + self.forceUpdate(); + // tile.forceUpdate(); } } MatrixClientPeg.get().on("User.presence", updateUserState); From ce2632bbe66e63e6cecc8885fe3f815b055acc17 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 15 Sep 2015 15:18:39 +0100 Subject: [PATCH 4/8] thinko --- skins/base/views/molecules/MemberTile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skins/base/views/molecules/MemberTile.js b/skins/base/views/molecules/MemberTile.js index 7c66616b01..a60fbb06d8 100644 --- a/skins/base/views/molecules/MemberTile.js +++ b/skins/base/views/molecules/MemberTile.js @@ -180,7 +180,7 @@ module.exports = React.createClass({ } return ( -
+
{ menu }
From 048260bb1b2bbc96d281d47d3eb0a743a139c19b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 21 Sep 2015 19:22:29 +0200 Subject: [PATCH 5/8] 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; - }, }; From 260e22186b1e33eea5a08dd2df388d619e7d9c3a Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 21 Sep 2015 19:23:04 +0200 Subject: [PATCH 6/8] WIP at turning MemberInfo into a ContextualMenu --- skins/base/views/molecules/ContextualMenu.js | 35 ++++++++++ src/ContextualMenu.js | 72 ++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 skins/base/views/molecules/ContextualMenu.js create mode 100644 src/ContextualMenu.js diff --git a/skins/base/views/molecules/ContextualMenu.js b/skins/base/views/molecules/ContextualMenu.js new file mode 100644 index 0000000000..58c542ee6b --- /dev/null +++ b/skins/base/views/molecules/ContextualMenu.js @@ -0,0 +1,35 @@ +/* +Copyright 2015 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 classNames = require('classnames'); + +var dis = require("../../../../src/dispatcher"); + +var MatrixClientPeg = require("../../../../src/MatrixClientPeg"); + +module.exports = React.createClass({ + displayName: 'ContextualMenu', + + render: function() { + return ( +
+
+ ); + } +}); diff --git a/src/ContextualMenu.js b/src/ContextualMenu.js new file mode 100644 index 0000000000..cabab0c375 --- /dev/null +++ b/src/ContextualMenu.js @@ -0,0 +1,72 @@ +/* +Copyright 2015 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 q = require('q'); + +// Shamelessly ripped off Modal.js. There's probably a better way +// of doing reusable widgets like dialog boxes & menus where we go and +// pass in a custom control as the actual body. + +module.exports = { + ContextualMenuContainerId: "mx_ContextualMenu_Container", + + getOrCreateContainer: function() { + var container = document.getElementById(this.ContextualMenuContainerId); + + if (!container) { + container = document.createElement("div"); + container.id = this.ContextualMenuContainerId; + document.body.appendChild(container); + } + + return container; + }, + + createMenu: function (Element, props) { + var self = this; + + var closeMenu = function() { + React.unmountComponentAtNode(self.getOrCreateContainer()); + + if (props && props.onFinished) props.onFinished.apply(null, arguments); + }; + + var position = { + top: props.top - 20, + right: props.right + 8, + }; + + // FIXME: If a menu uses getDefaultProps it clobbers the onFinished + // property set here so you can't close the menu from a button click! + var menu = ( +
+
+ + +
+
+
+ ); + + React.render(menu, this.getOrCreateContainer()); + + return {close: closeMenu}; + }, +}; From 61c94d63e7d93db8c029b53ee4a9c5f0bcf76313 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 22 Sep 2015 01:16:45 +0200 Subject: [PATCH 7/8] make the new userlist UI actually work --- skins/base/css/common.css | 7 ++-- skins/base/views/molecules/MemberInfo.js | 11 +++++-- skins/base/views/molecules/MemberTile.js | 11 +++++-- src/controllers/molecules/MemberInfo.js | 42 ++++++++++++++++++++++++ src/controllers/molecules/MemberTile.js | 29 ++++++++++++++++ 5 files changed, 92 insertions(+), 8 deletions(-) diff --git a/skins/base/css/common.css b/skins/base/css/common.css index 9527b253d4..d6c5e091e7 100644 --- a/skins/base/css/common.css +++ b/skins/base/css/common.css @@ -54,12 +54,12 @@ html { } .mx_ContextualMenu { - text-align: center; border: 1px solid #a9dbf4; border-radius: 8px; background-color: #fff; + color: #747474; position: fixed; - z-index: 1000; + z-index: 2001; padding: 6px; } @@ -71,7 +71,8 @@ html { } .mx_ContextualMenu_field { - padding: 6px; + padding: 3px 6px 3px 6px; + cursor: pointer; } diff --git a/skins/base/views/molecules/MemberInfo.js b/skins/base/views/molecules/MemberInfo.js index 512ed9f423..b57a5b6fc2 100644 --- a/skins/base/views/molecules/MemberInfo.js +++ b/skins/base/views/molecules/MemberInfo.js @@ -28,7 +28,14 @@ module.exports = React.createClass({ mixins: [MemberInfoController], render: function() { - var kickButton, banButton, muteButton, giveModButton; + var interactButton, kickButton, banButton, muteButton, giveModButton; + if (this.props.member.userId === MatrixClientPeg.get().credentials.userId) { + interactButton =
Leave room
; + } + else { + interactButton =
Start chat
; + } + if (this.state.can.kick) { kickButton =
Kick @@ -54,7 +61,7 @@ module.exports = React.createClass({ return (
-
Start chat
+ {interactButton} {muteButton} {kickButton} {banButton} diff --git a/skins/base/views/molecules/MemberTile.js b/skins/base/views/molecules/MemberTile.js index 2cfceb4dc8..1414b562dc 100644 --- a/skins/base/views/molecules/MemberTile.js +++ b/skins/base/views/molecules/MemberTile.js @@ -44,10 +44,15 @@ module.exports = React.createClass({ }, onClick: function(e) { + var self = this; + self.setState({ 'menu': true }); ContextualMenu.createMenu(MemberInfo, { - member: this.props.member, + member: self.props.member, right: window.innerWidth - e.pageX, top: e.pageY, + onFinished: function() { + self.setState({ 'menu': false }); + } }); }, @@ -109,7 +114,7 @@ module.exports = React.createClass({ } } mainClassName += presenceClass; - if (this.state.hover) { + if (this.state.hover || this.state.menu) { mainClassName += " mx_MemberTile_hover"; } @@ -123,7 +128,7 @@ module.exports = React.createClass({ } var nameEl; - if (this.state.hover) { + if (this.state.hover || this.state.menu) { var presence; // FIXME: make presence data update whenever User.presence changes... var active = this.props.member.user ? (this.props.member.user.lastActiveAgo || -1) : -1; diff --git a/src/controllers/molecules/MemberInfo.js b/src/controllers/molecules/MemberInfo.js index 5c63d970c2..2e7c3f7108 100644 --- a/src/controllers/molecules/MemberInfo.js +++ b/src/controllers/molecules/MemberInfo.js @@ -32,6 +32,8 @@ var dis = require("../../dispatcher"); var Modal = require("../../Modal"); var ComponentBroker = require('../../ComponentBroker'); var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog"); +var QuestionDialog = ComponentBroker.get("organisms/QuestionDialog"); +var Loader = require("react-loader"); module.exports = { componentDidMount: function() { @@ -59,6 +61,7 @@ module.exports = { description: err.message }); }); + this.props.onFinished(); }, onBan: function() { @@ -75,6 +78,7 @@ module.exports = { description: err.message }); }); + this.props.onFinished(); }, onMuteToggle: function() { @@ -83,12 +87,14 @@ module.exports = { var self = this; 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; } var isMuted = this.state.muted; @@ -116,6 +122,7 @@ module.exports = { description: err.message }); }); + this.props.onFinished(); }, onModToggle: function() { @@ -123,16 +130,19 @@ module.exports = { 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; } var me = room.getMember(MatrixClientPeg.get().credentials.userId); if (!me) { + this.props.onFinished(); return; } var defaultLevel = powerLevelEvent.getContent().users_default; @@ -150,6 +160,7 @@ module.exports = { description: err.message }); }); + this.props.onFinished(); }, onChatClick: function() { @@ -199,6 +210,37 @@ module.exports = { ); }); } + this.props.onFinished(); + }, + + // FIXME: this is horribly duplicated with MemberTile's onLeaveClick. + // Not sure what the right solution to this is. + onLeaveClick: function() { + console.log("leaving room"); + 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() + }); + }); + } + } + }); + this.props.onFinished(); }, getInitialState: function() { diff --git a/src/controllers/molecules/MemberTile.js b/src/controllers/molecules/MemberTile.js index 38bf43d62c..43db7d1dba 100644 --- a/src/controllers/molecules/MemberTile.js +++ b/src/controllers/molecules/MemberTile.js @@ -35,6 +35,35 @@ module.exports = { getInitialState: function() { return { hover: false, + menu: false, } }, + + onLeaveClick: function(ev) { + ev.stopPropagation(); + ev.preventDefault(); + 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() + }); + }); + } + } + }); + } }; From ef3603cd1a7b6778e0ff9bb45bfa56bd26060e94 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 22 Sep 2015 01:25:58 +0200 Subject: [PATCH 8/8] oops, rogue debugging stmt --- src/controllers/molecules/MemberInfo.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/molecules/MemberInfo.js b/src/controllers/molecules/MemberInfo.js index 2e7c3f7108..21cbe7a54f 100644 --- a/src/controllers/molecules/MemberInfo.js +++ b/src/controllers/molecules/MemberInfo.js @@ -216,7 +216,6 @@ module.exports = { // FIXME: this is horribly duplicated with MemberTile's onLeaveClick. // Not sure what the right solution to this is. onLeaveClick: function() { - console.log("leaving room"); var roomId = this.props.member.roomId; Modal.createDialog(QuestionDialog, { title: "Leave room",