Merge pull request #175 from vector-im/matthew/userlist
Reskin the userlist as per the design
This commit is contained in:
commit
616b4fe0f1
17 changed files with 333 additions and 204 deletions
|
@ -33,7 +33,6 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onClickDiv: function() {
|
||||
console.log("onClickDiv triggered");
|
||||
this.setState({
|
||||
phase: this.Phases.Edit,
|
||||
})
|
||||
|
|
35
skins/base/views/molecules/ContextualMenu.js
Normal file
35
skins/base/views/molecules/ContextualMenu.js
Normal file
|
@ -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 (
|
||||
<div className="mx_ContextualMenu">
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -27,84 +27,41 @@ 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 interactButton, kickButton, banButton, muteButton, giveModButton;
|
||||
if (this.props.member.userId === MatrixClientPeg.get().credentials.userId) {
|
||||
interactButton = <div className="mx_ContextualMenu_field" onClick={this.onLeaveClick}>Leave room</div>;
|
||||
}
|
||||
var kickButton, banButton, muteButton, giveModButton;
|
||||
else {
|
||||
interactButton = <div className="mx_ContextualMenu_field" onClick={this.onChatClick}>Start chat</div>;
|
||||
}
|
||||
|
||||
if (this.state.can.kick) {
|
||||
kickButton = <div className="mx_MemberInfo_button" onClick={this.onKick}>
|
||||
kickButton = <div className="mx_ContextualMenu_field" onClick={this.onKick}>
|
||||
Kick
|
||||
</div>;
|
||||
}
|
||||
if (this.state.can.ban) {
|
||||
banButton = <div className="mx_MemberInfo_button" onClick={this.onBan}>
|
||||
banButton = <div className="mx_ContextualMenu_field" onClick={this.onBan}>
|
||||
Ban
|
||||
</div>;
|
||||
}
|
||||
if (this.state.can.mute) {
|
||||
var muteLabel = this.state.muted ? "Unmute" : "Mute";
|
||||
muteButton = <div className="mx_MemberInfo_button" onClick={this.onMuteToggle}>
|
||||
muteButton = <div className="mx_ContextualMenu_field" onClick={this.onMuteToggle}>
|
||||
{muteLabel}
|
||||
</div>;
|
||||
}
|
||||
if (this.state.can.modifyLevel) {
|
||||
var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod";
|
||||
giveModButton = <div className="mx_MemberInfo_button" onClick={this.onModToggle}>
|
||||
giveModButton = <div className="mx_ContextualMenu_field" onClick={this.onModToggle}>
|
||||
{giveOpLabel}
|
||||
</div>
|
||||
}
|
||||
|
||||
var opLabel;
|
||||
if (this.state.isTargetMod) {
|
||||
var level = this.props.member.powerLevelNorm + "%";
|
||||
opLabel = <div className="mx_MemberInfo_field">Moderator ({level})</div>
|
||||
}
|
||||
return (
|
||||
<div className="mx_MemberInfo">
|
||||
<img className="mx_MemberInfo_chevron" src="img/chevron-right.png" width="9" height="16" />
|
||||
<div className="mx_MemberInfo_shim"></div>
|
||||
<div className="mx_MemberInfo_avatar">
|
||||
<MemberAvatar member={this.props.member} width={128} height={128} />
|
||||
</div>
|
||||
<div className="mx_MemberInfo_field">{this.props.member.userId}</div>
|
||||
{opLabel}
|
||||
<div className="mx_MemberInfo_field">Presence: {this.state.presence}</div>
|
||||
<div className="mx_MemberInfo_field">Last active: {activeAgo}</div>
|
||||
<div className="mx_MemberInfo_button" onClick={this.onChatClick}>Start chat</div>
|
||||
<div>
|
||||
{interactButton}
|
||||
{muteButton}
|
||||
{kickButton}
|
||||
{banButton}
|
||||
|
|
|
@ -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");
|
||||
|
@ -34,11 +35,6 @@ 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 });
|
||||
},
|
||||
|
@ -47,6 +43,58 @@ module.exports = React.createClass({
|
|||
this.setState({ 'hover': false });
|
||||
},
|
||||
|
||||
onClick: function(e) {
|
||||
var self = this;
|
||||
self.setState({ 'menu': true });
|
||||
ContextualMenu.createMenu(MemberInfo, {
|
||||
member: self.props.member,
|
||||
right: window.innerWidth - e.pageX,
|
||||
top: e.pageY,
|
||||
onFinished: function() {
|
||||
self.setState({ 'menu': 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) {
|
||||
if (!user) return "Unknown";
|
||||
var presence = user.presence;
|
||||
if (presence === "online") return "Online";
|
||||
if (presence === "unavailable") return "Idle"; // XXX: is this actually right?
|
||||
if (presence === "offline") return "Offline";
|
||||
return "Unknown";
|
||||
},
|
||||
|
||||
getPowerLabel: function() {
|
||||
var label = this.props.member.userId;
|
||||
if (this.state.isTargetMod) {
|
||||
label += " - Mod (" + this.props.member.powerLevelNorm + "%)";
|
||||
}
|
||||
return label;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var isMyUser = MatrixClientPeg.get().credentials.userId == this.props.member.userId;
|
||||
|
||||
|
@ -66,35 +114,47 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
mainClassName += presenceClass;
|
||||
if (this.state.hover || this.state.menu) {
|
||||
mainClassName += " mx_MemberTile_hover";
|
||||
}
|
||||
|
||||
var name = this.props.member.name;
|
||||
if (isMyUser) name += " (me)";
|
||||
var leave = isMyUser ? <span className="mx_MemberTile_leave" onClick={this.onLeaveClick}>X</span> : null;
|
||||
// if (isMyUser) name += " (me)"; // this does nothing other than introduce line wrapping and pain
|
||||
var leave = isMyUser ? <img className="mx_MemberTile_leave" src="img/delete.png" width="10" height="10" onClick={this.onLeaveClick}/> : 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) {
|
||||
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;
|
||||
if (active >= 0) {
|
||||
presence = <div className="mx_MemberTile_presence">{ this.getPrettyPresence(this.props.member.user) } for { this.getDuration(active) }</div>;
|
||||
}
|
||||
else {
|
||||
presence = <div className="mx_MemberTile_presence">{ this.getPrettyPresence(this.props.member.user) }</div>;
|
||||
}
|
||||
|
||||
nameEl =
|
||||
<div className="mx_MemberTile_nameWrapper">
|
||||
<MemberInfo member={this.props.member} />
|
||||
<span className={nameClass}>{name}</span>
|
||||
{leave}
|
||||
<div className="mx_MemberTile_details">
|
||||
{ leave }
|
||||
<div className="mx_MemberTile_userId">{ this.props.member.userId }</div>
|
||||
{ presence }
|
||||
</div>
|
||||
}
|
||||
else {
|
||||
nameEl =
|
||||
<div className={nameClass}>
|
||||
{name}
|
||||
{leave}
|
||||
{ name }
|
||||
</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={mainClassName} onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave }>
|
||||
<div className={mainClassName} title={ this.getPowerLabel() } onClick={ this.onClick } onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave }>
|
||||
<div className="mx_MemberTile_avatar">
|
||||
<MemberAvatar member={this.props.member} />
|
||||
{ power }
|
||||
|
|
|
@ -75,14 +75,9 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
inviteTile: function() {
|
||||
// if (this.state.inviting) {
|
||||
// return (
|
||||
// <div></div>
|
||||
// );
|
||||
// }
|
||||
|
||||
var classes = classNames({
|
||||
mx_MemberTile: true,
|
||||
mx_MemberTile_inviteTile: true,
|
||||
mx_MemberTile_inviteEditing: this.state.editing,
|
||||
});
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue