diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js
index 45e471314..63b340563 100644
--- a/src/components/structures/RoomSubList.js
+++ b/src/components/structures/RoomSubList.js
@@ -17,15 +17,20 @@ limitations under the License.
'use strict';
var React = require('react');
+var ReactDOM = require('react-dom');
+var classNames = require('classnames');
var DropTarget = require('react-dnd').DropTarget;
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
var Unread = require('matrix-react-sdk/lib/Unread');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
+var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
// turn this on for drop & drag console debugging galore
var debug = false;
+const TRUNCATE_AT = 10;
+
var roomListTarget = {
canDrop: function() {
return true;
@@ -80,7 +85,8 @@ var RoomSubList = React.createClass({
getInitialState: function() {
return {
hidden: this.props.startAsHidden || false,
- truncateAt: 20,
+ capTruncate: this.props.list.length > TRUNCATE_AT,
+ truncateAt: this.props.list.length > TRUNCATE_AT ? TRUNCATE_AT : -1,
sortedList: [],
};
},
@@ -109,17 +115,62 @@ var RoomSubList = React.createClass({
});
},
- onClick: function(ev) {
- var isHidden = !this.state.hidden;
- this.setState({ hidden : isHidden });
-
- if (isHidden) {
- // as good a way as any to reset the truncate state
- this.setState({ truncateAt : 20 });
- this.props.onShowMoreRooms();
+ // The header is collapsable if it is hidden or not stuck
+ // The dataset elements are added in the RoomList _initAndPositionStickyHeaders method
+ isCollapsableOnClick: function() {
+ var stuck = this.refs.header.dataset.stuck;
+ if (this.state.hidden || stuck === undefined || stuck === "none") {
+ return true;
+ } else {
+ return false;
}
+ },
- this.props.onHeaderClick(isHidden);
+ onClick: function(ev) {
+ if (this.isCollapsableOnClick()) {
+ // The header iscCollapsable, so the click is to be interpreted as collapse and truncation logic
+ var isHidden = false;
+ var isTruncatable = this.props.list.length > TRUNCATE_AT;
+
+ if (this.state.hidden && (this.state.capTruncate && isTruncatable)) {
+ isHidden = false;
+ this.setState({
+ hidden : isHidden,
+ capTruncate : true,
+ truncateAt : TRUNCATE_AT
+ });
+ } else if ((!this.state.hidden && this.state.capTruncate)
+ || (this.state.hidden && (this.state.capTruncate && !isTruncatable)))
+ {
+ isHidden = false;
+ this.setState({
+ hidden : isHidden,
+ capTruncate : false,
+ truncateAt : -1
+ });
+ } else if (!this.state.hidden && !this.state.capTruncate) {
+ isHidden = true;
+ this.setState({
+ hidden : isHidden,
+ capTruncate : true,
+ truncateAt : TRUNCATE_AT
+ });
+ } else {
+ // Catch any weird states the system gets into
+ isHidden = false;
+ this.setState({
+ hidden : isHidden,
+ capTruncate : true,
+ truncateAt : TRUNCATE_AT
+ });
+ }
+
+ this.props.onShowMoreRooms();
+ this.props.onHeaderClick(isHidden);
+ } else {
+ // The header is stuck, so the click is to be interpreted as a scroll to the header
+ this.props.onHeaderClick(this.state.hidden, this.refs.header.dataset.originalPosition);
+ }
},
tsOfNewestEvent: function(room) {
@@ -186,6 +237,44 @@ var RoomSubList = React.createClass({
this.setState({ sortedList: list.sort(comparator) });
},
+ _shouldShowNotifBadge: function(roomNotifState) {
+ const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD];
+ return showBadgeInStates.indexOf(roomNotifState) > -1;
+ },
+
+ _shouldShowMentionBadge: function(roomNotifState) {
+ return roomNotifState != RoomNotifs.MUTE;
+ },
+
+ roomNotificationCount: function() {
+ var self = this;
+
+ return this.props.list.reduce(function(result, room) {
+ var roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId);
+ var highlight = room.getUnreadNotificationCount('highlight') > 0 || self.props.label === 'Invites';
+ var notificationCount = room.getUnreadNotificationCount();
+
+ const notifBadges = notificationCount > 0 && self._shouldShowNotifBadge(roomNotifState);
+ const mentionBadges = highlight && self._shouldShowMentionBadge(roomNotifState);
+ const badges = notifBadges || mentionBadges;
+
+ if (badges) {
+ result[0] += notificationCount;
+ if (highlight) {
+ result[1] = true;
+ }
+ }
+ return result;
+ }, [0, false]);
+ },
+
+ _updateSubListCount: function() {
+ // Force an update by setting the state to the current state
+ // Doing it this way rather than using forceUpdate(), so that the shouldComponentUpdate()
+ // method is honoured
+ this.setState(this.state);
+ },
+
moveRoomTile: function(room, atIndex) {
if (debug) console.log("moveRoomTile: id " + room.roomId + ", atIndex " + atIndex);
//console.log("moveRoomTile before: " + JSON.stringify(this.state.rooms));
@@ -293,6 +382,7 @@ var RoomSubList = React.createClass({
unread={ Unread.doesRoomHaveUnreadMessages(room) }
highlight={ room.getUnreadNotificationCount('highlight') > 0 || self.props.label === 'Invites' }
isInvite={ self.props.label === 'Invites' }
+ refreshSubList={ self._updateSubListCount }
incomingCall={ self.props.incomingCall && (self.props.incomingCall.roomId === room.roomId) ? self.props.incomingCall : null } />
);
});
@@ -300,35 +390,47 @@ var RoomSubList = React.createClass({
_getHeaderJsx: function() {
var TintableSvg = sdk.getComponent("elements.TintableSvg");
- return (
-
- { this.props.collapsed ? '' : this.props.label }
-
-
- );
- },
- _createOverflowTile: function(overflowCount, totalCount) {
- var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
- // XXX: this is duplicated from RoomTile - factor it out
+ var subListNotifications = this.roomNotificationCount();
+ var subListNotifCount = subListNotifications[0];
+ var subListNotifHighlight = subListNotifications[1];
+
+ var roomCount = this.props.list.length > 0 ? this.props.list.length : '';
+ var isTruncatable = this.props.list.length > TRUNCATE_AT;
+ if (!this.state.hidden && this.state.capTruncate && isTruncatable) {
+ roomCount = TRUNCATE_AT + " of " + roomCount;
+ }
+
+ var chevronClasses = classNames({
+ 'mx_RoomSubList_chevron': true,
+ 'mx_RoomSubList_chevronUp': this.state.hidden,
+ 'mx_RoomSubList_chevronRight': !this.state.hidden && this.state.capTruncate,
+ 'mx_RoomSubList_chevronDown': !this.state.hidden && !this.state.capTruncate,
+ });
+
+ var badgeClasses = classNames({
+ 'mx_RoomSubList_badge': true,
+ 'mx_RoomSubList_badgeHighlight': subListNotifHighlight,
+ });
+
+ var badge;
+ if (subListNotifCount > 0) {
+ badge = {subListNotifCount > 99 ? "99+" : subListNotifCount}
;
+ }
+
return (
-
-
-
+
+
+ { this.props.collapsed ? '' : this.props.label }
+
{roomCount}
+
+ {badge}
-
and { overflowCount } others...
);
},
- _showFullMemberList: function() {
- this.setState({
- truncateAt: -1
- });
- this.props.onShowMoreRooms();
- },
+ _createOverflowTile: function() {}, // NOP
// Fix any undefined order elements of a room in a manual ordered list
// room.tag[tagname].order
diff --git a/src/skins/vector/css/matrix-react-sdk/structures/SearchBox.css b/src/skins/vector/css/matrix-react-sdk/structures/SearchBox.css
index d9e4e05fd..0b5362593 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/SearchBox.css
+++ b/src/skins/vector/css/matrix-react-sdk/structures/SearchBox.css
@@ -20,7 +20,6 @@ limitations under the License.
margin-right: 16px;
padding-top: 24px;
padding-bottom: 22px;
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
display: flex;
display: -webkit-flex;
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomList.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomList.css
index a22418534..4dcda646f 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomList.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomList.css
@@ -15,7 +15,6 @@ limitations under the License.
*/
.mx_RoomList {
- padding-top: 8px;
padding-bottom: 12px;
min-height: 400px;
}
@@ -33,3 +32,8 @@ limitations under the License.
overflow-x: hidden ! important;
overflow-y: scroll ! important;
}
+
+/* Make sure the scrollbar is above the sticky headers from RoomList */
+.mx_RoomList_scrollbar .gm-scrollbar.-vertical {
+ z-index: 5;
+}
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomTile.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomTile.css
index c266b027d..254565035 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomTile.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomTile.css
@@ -109,9 +109,8 @@ limitations under the License.
}
.collapsed .mx_RoomTile_badge {
- top: -2px;
+ top: 0px;
min-width: 12px;
- height: 16px;
border-radius: 16px;
padding: 0px 4px 0px 4px;
z-index: 200;
@@ -129,22 +128,22 @@ limitations under the License.
display: block;
width: 0;
height: 0;
- margin-left: 6px;
- border-top: 8px solid #ff0064;
- border-right: 10px solid transparent;
+ margin-left: 5px;
+ border-top: 5px solid #ff0064;
+ border-right: 7px solid transparent;
}
.mx_RoomTile_badge {
display: inline-block;
- min-width: 19px;
- height: 17px;
+ min-width: 15px;
+ height: 15px;
position: absolute;
right: 8px; /*gutter */
top: 9px;
- border-radius: 14px;
+ border-radius: 8px;
color: #fff;
font-weight: 600;
- font-size: 11px;
+ font-size: 10px;
text-align: center;
padding-top: 1px;
padding-left: 4px;
@@ -175,7 +174,7 @@ limitations under the License.
}
.mx_RoomTile_selected {
- background-color: rgba(118,207,166,0.2);
+ background-color: rgba(255, 255, 255, 0.8);
}
.mx_RoomTile .mx_RoomTile_name.mx_RoomTile_badgeShown {
@@ -187,5 +186,3 @@ limitations under the License.
right: 0px;
}
-.mx_RoomTile:hover {
-}
diff --git a/src/skins/vector/css/vector-web/structures/LeftPanel.css b/src/skins/vector/css/vector-web/structures/LeftPanel.css
index 316246cb3..fb023ffea 100644
--- a/src/skins/vector/css/vector-web/structures/LeftPanel.css
+++ b/src/skins/vector/css/vector-web/structures/LeftPanel.css
@@ -49,6 +49,7 @@ limitations under the License.
flex: 1 1 0;
overflow-y: auto;
+ z-index: 5;
}
.mx_LeftPanel.collapsed .mx_BottomLeftMenu {
@@ -62,7 +63,7 @@ limitations under the License.
-webkit-order: 3;
order: 3;
- border-top: 1px solid rgba(0, 0, 0, 0.1);
+ border-top: 1px solid rgba(118, 207, 166, 0.2);
margin-left: 16px; /* gutter */
margin-right: 16px; /* gutter */
-webkit-flex: 0 0 60px;
diff --git a/src/skins/vector/css/vector-web/structures/RoomSubList.css b/src/skins/vector/css/vector-web/structures/RoomSubList.css
index b54fea981..4d9b0668f 100644
--- a/src/skins/vector/css/vector-web/structures/RoomSubList.css
+++ b/src/skins/vector/css/vector-web/structures/RoomSubList.css
@@ -20,23 +20,128 @@ limitations under the License.
width: 100%;
}
+.mx_RoomSubList_labelContainer {
+ height: 31px; /* mx_RoomSubList_label height including border */
+ width: 235px; /* LHS Panel width */
+ position: relative;
+}
+
.mx_RoomSubList_label {
+ position: relative;
text-transform: uppercase;
color: #3d3b39;
font-weight: 600;
- font-size: 13px;
+ font-size: 12px;
+ width: 203px; /* padding + width = LHS Panel width */
+ height: 17px; /* padding + height = 29px, same as mx_RoomSubList_stickyContainer */
padding-left: 16px; /* gutter */
padding-right: 16px; /* gutter */
- margin-top: 8px;
- margin-bottom: 4px;
+ padding-top: 6px;
+ padding-bottom: 6px;
cursor: pointer;
+ background-color: rgba(118, 207, 166, 0.2);
+ border-top: solid 2px #eaf5f0;
+}
+
+.mx_RoomSubList_label.mx_RoomSubList_fixed {
+ position: fixed;
+ top: 0;
+ z-index: 4;
+ /* pointer-events: none; */
+}
+
+.collapsed .mx_RoomSubList_label {
+ height: 17px;
+ width: 28px; /* collapsed LHS Panel width */
+}
+
+.collapsed .mx_RoomSubList_labelContainer {
+ width: 28px; /* collapsed LHS Panel width */
+}
+
+.mx_RoomSubList_roomCount {
+ display: inline-block;
+ font-size: 12px;
+ font-weight: normal;
+ color: #76cfa6;
+ padding-left: 5px;
+ text-transform: none;
+}
+
+.collapsed .mx_RoomSubList_roomCount {
+ display: none;
+}
+
+.mx_RoomSubList_badge {
+ display: inline-block;
+ min-width: 15px;
+ height: 15px;
+ position: absolute;
+ right: 8px; /*gutter */
+ top: 7px;
+ border-radius: 8px;
+ color: #fff;
+ font-weight: 600;
+ font-size: 10px;
+ text-align: center;
+ padding-top: 1px;
+ padding-left: 4px;
+ padding-right: 4px;
+ background-color: #76cfa6;
+}
+
+.collapsed .mx_RoomSubList_badge {
+ display: none;
+}
+
+.mx_RoomSubList_badgeHighlight {
+ background-color: #ff0064;
+}
+
+/* This is the bottom of the speech bubble */
+.mx_RoomSubList_badgeHighlight:after {
+ content: "";
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ margin-left: 5px;
+ border-top: 5px solid #ff0064;
+ border-right: 7px solid transparent;
+}
+
+/* Hide the bottom of speech bubble */
+.collapsed .mx_RoomSubList_badgeHighlight:after {
+ display: none;
}
.mx_RoomSubList_chevron {
- padding-left: 4px;
pointer-events: none;
+ position: absolute;
+ right: 41px;
+ top: 11px;
}
-.collapsed .mx_RoomSubList_chevron {
- padding-left: 12px;
+.mx_RoomSubList_chevronDown {
+ width: 0;
+ height: 0;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 6px solid #76cfa6;
+}
+
+.mx_RoomSubList_chevronUp {
+ width: 0;
+ height: 0;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-bottom: 6px solid #76cfa6;
+}
+
+.mx_RoomSubList_chevronRight {
+ width: 0;
+ height: 0;
+ border-top: 5px solid transparent;
+ border-left: 6px solid #76cfa6;
+ border-bottom: 5px solid transparent;
}