From 2121ddc295508aee1c5d5733752c47b52b03e362 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Mon, 20 Jul 2015 01:51:58 +0100
Subject: [PATCH] add a MemberInfo list overlay as a mini user-profile page

---
 skins/base/css/molecules/MemberInfo.css     |  61 ++++++++++++++++++++
 skins/base/css/molecules/RoomTile.css       |  14 ++---
 skins/base/css/organisms/MemberList.css     |   2 +-
 skins/base/img/chevron-right.png            | Bin 0 -> 181 bytes
 skins/base/views/molecules/DirectoryMenu.js |   1 +
 skins/base/views/molecules/MemberInfo.js    |  50 ++++++++++++++++
 skins/base/views/molecules/MemberTile.js    |  30 +++++++++-
 src/ComponentBroker.js                      |   3 +-
 8 files changed, 146 insertions(+), 15 deletions(-)
 create mode 100644 skins/base/css/molecules/MemberInfo.css
 create mode 100644 skins/base/img/chevron-right.png
 create mode 100644 skins/base/views/molecules/MemberInfo.js

diff --git a/skins/base/css/molecules/MemberInfo.css b/skins/base/css/molecules/MemberInfo.css
new file mode 100644
index 00000000..32442305
--- /dev/null
+++ b/skins/base/css/molecules/MemberInfo.css
@@ -0,0 +1,61 @@
+/*
+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.
+*/
+
+.mx_MemberInfo {
+    text-align: center;
+    border: 1px solid #a9dbf4;
+    border-radius: 8px;
+    background-color: #fff;
+    position: absolute;
+    width: 200px;
+    margin-left: -295px;
+    margin-top: -12px;
+    z-index: 1000;
+    padding: 6px;
+}
+
+.mx_MemberInfo_chevron {
+    padding: 12px;
+    position: absolute;
+    right: -21px;
+    top: 0px;
+}
+
+.mx_MemberInfo_avatar {
+    padding: 6px;
+}
+
+.mx_MemberInfo_avatarImg {
+    border-radius: 128px;
+}
+
+.mx_MemberInfo_field {
+    padding: 6px;
+    font-weight: bold;
+}
+
+.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/css/molecules/RoomTile.css b/skins/base/css/molecules/RoomTile.css
index 43f05440..d43945c3 100644
--- a/skins/base/css/molecules/RoomTile.css
+++ b/skins/base/css/molecules/RoomTile.css
@@ -82,16 +82,12 @@ limitations under the License.
     bottom: 3px;
 }
 
-.mx_RoomTile_unread {
-    font-weight: bold;
-}
-
-.mx_RoomTile_highlight {
-    font-weight: bold;
-}
-
-.mx_RoomTile_invited {
+.mx_RoomTile_unread,
+.mx_RoomTile_highlight,
+.mx_RoomTile_invited
+{
     font-weight: bold;
+    color: #000;
 }
 
 .mx_RoomTile_selected {
diff --git a/skins/base/css/organisms/MemberList.css b/skins/base/css/organisms/MemberList.css
index f65fec81..b56e3f11 100644
--- a/skins/base/css/organisms/MemberList.css
+++ b/skins/base/css/organisms/MemberList.css
@@ -39,7 +39,7 @@ limitations under the License.
 .mx_MemberList_wrapper {
     display: table;
     table-layout: fixed;
-    width: 100%; 
+    width: 100%;
 }
 
 .mx_MemberList h2 {
diff --git a/skins/base/img/chevron-right.png b/skins/base/img/chevron-right.png
new file mode 100644
index 0000000000000000000000000000000000000000..1fe5d347db3d0c807a90859f33760b8858b06ff1
GIT binary patch
literal 181
zcmeAS@N?(olHy`uVBq!ia0vp^oIotV!3HFcc!W9wDajJoh?3y^w370~qErUQl>DSr
z1<%~X^wgl##FWaylc_d9Md6+<jv*Ddl5YHZ@VZ_h;XwP$Clxa!x7q&rHlI<RL9+Pg
z+5Z1Od|0%VmI$olnC~Rwx<e#M<IpmJ)k-C;K0HSlHmy;9QPAY*U|_)M#;wSj;Ce(w
dw1I({;XwX|W=+pm`+!z3c)I$ztaD0e0sx;wJhA`)

literal 0
HcmV?d00001

diff --git a/skins/base/views/molecules/DirectoryMenu.js b/skins/base/views/molecules/DirectoryMenu.js
index 461ec1c6..8ffb4180 100644
--- a/skins/base/views/molecules/DirectoryMenu.js
+++ b/skins/base/views/molecules/DirectoryMenu.js
@@ -29,6 +29,7 @@ module.exports = React.createClass({
     displayName: 'DirectoryMenu',
     // mixins: [DirectoryMenuController],
 
+    // FIXME: should these onClicks be in the controller instead?
     onSettingsClick: function() {
         dis.dispatch({action: 'view_user_settings'});
     },
diff --git a/skins/base/views/molecules/MemberInfo.js b/skins/base/views/molecules/MemberInfo.js
new file mode 100644
index 00000000..19e0dc46
--- /dev/null
+++ b/skins/base/views/molecules/MemberInfo.js
@@ -0,0 +1,50 @@
+/*
+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 MatrixClientPeg = require("../../../../src/MatrixClientPeg");
+//var MemberInfoController = require("../../../../src/controllers/molecules/MemberInfo");
+
+module.exports = React.createClass({
+    displayName: 'MemberInfo',
+    //mixins: [MemberInfoController],
+
+    render: function() {
+        var power;
+        if (this.props.member) {
+            var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png";
+            power = <img src={ img } className="mx_MemberTile_power" width="48" height="48" alt=""/>;
+        }
+
+        return (
+            <div className="mx_MemberInfo">
+                <img className="mx_MemberInfo_chevron" src="img/chevron-right.png" width="9" height="16" />
+                <div className="mx_MemberInfo_avatar">
+                    <img className="mx_MemberInfo_avatarImg"
+                         src={ this.props.member ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.member, 128, 128, "crop") : null }
+                         width="128" height="128" alt=""/>
+                </div>
+                <div className="mx_MemberInfo_field">{this.props.member.userId}</div>
+                <div className="mx_MemberInfo_field">Presence: {this.props.member.presence}</div>
+                <div className="mx_MemberInfo_field">Last active: {this.props.member.last_active_ago}</div>
+                <div className="mx_MemberInfo_button">Start chat</div>
+            </div>
+        );
+    }
+});
diff --git a/skins/base/views/molecules/MemberTile.js b/skins/base/views/molecules/MemberTile.js
index 84609932..4e00f2af 100644
--- a/skins/base/views/molecules/MemberTile.js
+++ b/skins/base/views/molecules/MemberTile.js
@@ -19,11 +19,27 @@ limitations under the License.
 var React = require('react');
 
 var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
+var ComponentBroker = require('../../../../src/ComponentBroker');
 var MemberTileController = require("../../../../src/controllers/molecules/MemberTile");
+var MemberInfo = ComponentBroker.get('molecules/MemberInfo');
 
 module.exports = React.createClass({
     displayName: 'MemberTile',
     mixins: [MemberTileController],
+
+    // XXX: should these be in the controller?
+    getInitialState: function() {
+        return { 'hover': false };
+    },
+
+    mouseEnter: function(e) {
+        this.setState({ 'hover': true });
+    },
+
+    mouseLeave: function(e) {
+        this.setState({ 'hover': false });
+    },
+
     render: function() {
         var power;
         if (this.props.member) {
@@ -32,9 +48,17 @@ module.exports = React.createClass({
         }
 
         return (
-            <div className="mx_MemberTile">
-                <div className="mx_MemberTile_avatar"><img className="mx_MemberTile_avatarImg" src={ this.props.member ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.member, 40, 40, "crop") : null } width="40" height="40" alt=""/>{ power }</div>
-                <div className="mx_MemberTile_name">{this.props.member.name}</div>
+            <div className="mx_MemberTile" onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave } >
+                <div className="mx_MemberTile_avatar">
+                    <img className="mx_MemberTile_avatarImg"
+                         src={ this.props.member ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.member, 40, 40, "crop") : null }
+                         width="40" height="40" alt=""/>
+                         { power }
+                </div>
+                <div className="mx_MemberTile_name">
+                    { this.state.hover ? <MemberInfo member={this.props.member} /> : null }
+                    {this.props.member.name}
+                </div>
             </div>
         );
     }
diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js
index db1915ea..342a5393 100644
--- a/src/ComponentBroker.js
+++ b/src/ComponentBroker.js
@@ -107,6 +107,5 @@ require('../skins/base/views/molecules/voip/MCallInviteTile');
 require('../skins/base/views/molecules/voip/MCallAnswerTile');
 require('../skins/base/views/molecules/voip/MCallHangupTile');
 require('../skins/base/views/molecules/EventAsTextTile');
-
-
+require('../skins/base/views/molecules/MemberInfo');
 }