From 52cdf609540f1883abfec9bb3998f330fa2e5bf0 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 26 May 2018 20:27:48 -0600 Subject: [PATCH 01/27] Add options to pin unread/mentioned rooms to the top of the room list Fixes https://github.com/vector-im/riot-web/issues/6604 Fixes https://github.com/vector-im/riot-web/issues/732 Fixes https://github.com/vector-im/riot-web/issues/1104 Signed-off-by: Travis Ralston --- src/components/structures/RoomSubList.js | 21 ++++++++++++++++++++- src/components/structures/UserSettings.js | 2 ++ src/i18n/strings/en_EN.json | 2 ++ src/settings/Settings.js | 10 ++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index fb82ee067b..b3c9d5f1b6 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -17,6 +17,8 @@ limitations under the License. 'use strict'; +import SettingsStore from "../../settings/SettingsStore"; + var React = require('react'); var ReactDOM = require('react-dom'); var classNames = require('classnames'); @@ -98,8 +100,10 @@ var RoomSubList = React.createClass({ componentWillReceiveProps: function(newProps) { // order the room list appropriately before we re-render //if (debug) console.log("received new props, list = " + newProps.list); + const filteredRooms = this.applySearchFilter(newProps.list, newProps.searchFilter); + const sortedRooms = newProps.order === "recent" ? this.applyPinnedTileRules(filteredRooms) : filteredRooms; this.setState({ - sortedList: this.applySearchFilter(newProps.list, newProps.searchFilter), + sortedList: sortedRooms, }); }, @@ -110,6 +114,21 @@ var RoomSubList = React.createClass({ }); }, + applyPinnedTileRules: function(list) { + const pinUnread = SettingsStore.getValue("pinUnreadRooms"); + const pinMentioned = SettingsStore.getValue("pinMentionedRooms"); + if (!pinUnread && !pinMentioned) { + return list; // Nothing to sort + } + + const mentioned = !pinMentioned ? [] : list.filter(room => room.getUnreadNotificationCount("highlight") > 0); + const unread = !pinUnread ? [] : list.filter(room => Unread.doesRoomHaveUnreadMessages(room)); + + return mentioned + .concat(unread.filter(room => !mentioned.find(other => other === room))) + .concat(list.filter(room => !unread.find(other => other === room))); + }, + // The header is collapsable if it is hidden or not stuck // The dataset elements are added in the RoomList _initAndPositionStickyHeaders method isCollapsableOnClick: function() { diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index c8ce79905d..3695e0cf9f 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -81,6 +81,8 @@ const SIMPLE_SETTINGS = [ { id: "VideoView.flipVideoHorizontally" }, { id: "TagPanel.disableTagPanel" }, { id: "enableWidgetScreenshots" }, + { id: "pinMentionedRooms" }, + { id: "pinUnreadRooms" }, ]; // These settings must be defined in SettingsStore diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bf9e395bee..1b378c34d3 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -218,6 +218,8 @@ "Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)", "Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room", "Room Colour": "Room Colour", + "Pin unread rooms to the top of the room list": "Pin unread rooms to the top of the room list", + "Pin rooms I'm mentioned in to the top of the room list": "Pin rooms I'm mentioned in to the top of the room list", "Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index b1bc4161fd..1665a59dfd 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -269,6 +269,16 @@ export const SETTINGS = { default: true, controller: new AudioNotificationsEnabledController(), }, + "pinMentionedRooms": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td("Pin rooms I'm mentioned in to the top of the room list"), + default: false, + }, + "pinUnreadRooms": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td("Pin unread rooms to the top of the room list"), + default: false, + }, "enableWidgetScreenshots": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Enable widget screenshots on supported widgets'), From 3d8f0adf56d0bd0607d6ad553c47cf915a87cf95 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 12 Oct 2018 14:35:54 -0600 Subject: [PATCH 02/27] Move pinned rooms check to the RoomListStore --- src/components/structures/RoomSubList.js | 20 +------------------ src/stores/RoomListStore.js | 25 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 6aaa875e48..d798070659 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -19,7 +19,6 @@ limitations under the License. import React from 'react'; import classNames from 'classnames'; import sdk from '../../index'; -import SettingsStore from "../../settings/SettingsStore"; import { Droppable } from 'react-beautiful-dnd'; import { _t } from '../../languageHandler'; import dis from '../../dispatcher'; @@ -100,10 +99,8 @@ const RoomSubList = React.createClass({ componentWillReceiveProps: function(newProps) { // order the room list appropriately before we re-render //if (debug) console.log("received new props, list = " + newProps.list); - const filteredRooms = this.applySearchFilter(newProps.list, newProps.searchFilter); - const sortedRooms = newProps.order === "recent" ? this.applyPinnedTileRules(filteredRooms) : filteredRooms; this.setState({ - sortedList: sortedRooms, + sortedList: this.applySearchFilter(newProps.list, newProps.searchFilter), }); }, @@ -116,21 +113,6 @@ const RoomSubList = React.createClass({ (filter[0] === '#' && room.getAliases().some((alias) => alias.toLowerCase().startsWith(lcFilter)))); }, - applyPinnedTileRules: function(list) { - const pinUnread = SettingsStore.getValue("pinUnreadRooms"); - const pinMentioned = SettingsStore.getValue("pinMentionedRooms"); - if (!pinUnread && !pinMentioned) { - return list; // Nothing to sort - } - - const mentioned = !pinMentioned ? [] : list.filter(room => room.getUnreadNotificationCount("highlight") > 0); - const unread = !pinUnread ? [] : list.filter(room => Unread.doesRoomHaveUnreadMessages(room)); - - return mentioned - .concat(unread.filter(room => !mentioned.find(other => other === room))) - .concat(list.filter(room => !unread.find(other => other === room))); - }, - // The header is collapsable if it is hidden or not stuck // The dataset elements are added in the RoomList _initAndPositionStickyHeaders method isCollapsableOnClick: function() { diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 67c0c13be7..4c3e10e77f 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -17,6 +17,7 @@ import {Store} from 'flux/utils'; import dis from '../dispatcher'; import DMRoomMap from '../utils/DMRoomMap'; import Unread from '../Unread'; +import SettingsStore from "../settings/SettingsStore"; /** * A class for storing application state for categorising rooms in @@ -263,6 +264,30 @@ class RoomListStore extends Store { } _recentsComparator(roomA, roomB) { + const pinUnread = SettingsStore.getValue("pinUnreadRooms"); + const pinMentioned = SettingsStore.getValue("pinMentionedRooms"); + + // We try and set the ordering to be Mentioned > Unread > Recent + // assuming the user has the right settings, of course + + if (pinMentioned) { + const mentionsA = roomA.getUnreadNotificationCount("highlight") > 0; + const mentionsB = roomB.getUnreadNotificationCount("highlight") > 0; + if (mentionsA && !mentionsB) return -1; + if (!mentionsA && mentionsB) return 1; + if (mentionsA && mentionsB) return 0; + // If neither have mentions, fall through to remaining checks + } + + if (pinUnread) { + const unreadA = Unread.doesRoomHaveUnreadMessages(roomA); + const unreadB = Unread.doesRoomHaveUnreadMessages(roomB); + if (unreadA && !unreadB) return -1; + if (!unreadA && unreadB) return 1; + if (unreadA && unreadB) return 0; + // If neither have unread messages, fall through to remaining checks + } + // XXX: We could use a cache here and update it when we see new // events that trigger a reorder return this._tsOfNewestEvent(roomB) - this._tsOfNewestEvent(roomA); From 873133458a16a8b51e950cfd0857879e19eba591 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 31 Oct 2018 13:06:57 -0600 Subject: [PATCH 03/27] Remove the request-only stuff we don't need anymore This was introduced in https://github.com/matrix-org/matrix-react-sdk/pull/2250 but can be pulled out due to https://github.com/matrix-org/matrix-js-sdk/pull/770. See https://github.com/vector-im/riot-web/issues/7634 for more information about the future. --- karma.conf.js | 13 ------------- package.json | 1 - 2 files changed, 14 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index 41ddbdf249..4d699599cb 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -199,25 +199,12 @@ module.exports = function (config) { 'matrix-react-sdk': path.resolve('test/skinned-sdk.js'), 'sinon': 'sinon/pkg/sinon.js', - - // To make webpack happy - // Related: https://github.com/request/request/issues/1529 - // (there's no mock available for fs, so we fake a mock by using - // an in-memory version of fs) - "fs": "memfs", }, modules: [ path.resolve('./test'), "node_modules" ], }, - node: { - // Because webpack is made of fail - // https://github.com/request/request/issues/1529 - // Note: 'mock' is the new 'empty' - net: 'mock', - tls: 'mock' - }, devtool: 'inline-source-map', externals: { // Don't try to bundle electron: leave it as a commonjs dependency diff --git a/package.json b/package.json index b72080cd36..8a51c0877d 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "lodash": "^4.13.1", "lolex": "2.3.2", "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", - "memfs": "^2.10.1", "optimist": "^0.6.1", "pako": "^1.0.5", "prop-types": "^15.5.8", From 5558b7a3b2b8d8beec94cd901712e3f778cfa6e6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 1 Nov 2018 14:43:15 -0600 Subject: [PATCH 04/27] Avoid hitting the SettingsStore thousands of times when generating room lists Should fix https://github.com/vector-im/riot-web/issues/7646 to some degree --- src/stores/RoomListStore.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 8909dbb489..527fc0fe2e 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -217,11 +217,16 @@ class RoomListStore extends Store { } }); + // Note: we check the settings up here instead of in the forEach or + // in the _recentsComparator to avoid hitting the SettingsStore a few + // thousand times. + const pinUnread = SettingsStore.getValue("pinUnreadRooms"); + const pinMentioned = SettingsStore.getValue("pinMentionedRooms"); Object.keys(lists).forEach((listKey) => { let comparator; switch (RoomListStore._listOrders[listKey]) { case "recent": - comparator = this._recentsComparator; + comparator = (roomA, roomB) => this._recentsComparator(roomA, roomB, pinUnread, pinMentioned); break; case "manual": default: @@ -262,10 +267,7 @@ class RoomListStore extends Store { } } - _recentsComparator(roomA, roomB) { - const pinUnread = SettingsStore.getValue("pinUnreadRooms"); - const pinMentioned = SettingsStore.getValue("pinMentionedRooms"); - + _recentsComparator(roomA, roomB, pinUnread, pinMentioned) { // We try and set the ordering to be Mentioned > Unread > Recent // assuming the user has the right settings, of course From 272acfa2f55b89f87658da91a503a2023ef83958 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 1 Nov 2018 14:46:39 -0600 Subject: [PATCH 05/27] Appease the linter --- src/stores/RoomListStore.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 527fc0fe2e..65148ec8c4 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -226,7 +226,9 @@ class RoomListStore extends Store { let comparator; switch (RoomListStore._listOrders[listKey]) { case "recent": - comparator = (roomA, roomB) => this._recentsComparator(roomA, roomB, pinUnread, pinMentioned); + comparator = (roomA, roomB) => { + return this._recentsComparator(roomA, roomB, pinUnread, pinMentioned); + }; break; case "manual": default: From 0c7aadb92b0d3b7042ff8658c33df5b0b697cefe Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 1 Nov 2018 16:28:13 -0600 Subject: [PATCH 06/27] Improve room list sort performance by caching common variables This won't help much if the user is in a ton of highly active rooms, but for the most part this will help those in thousands of rooms, many of which are likely to be quiet. Fixes https://github.com/vector-im/riot-web/issues/7646 Fixes https://github.com/vector-im/riot-web/issues/7645 (due to timestamp ordering) --- src/stores/RoomListStore.js | 135 ++++++++++++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 15 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 65148ec8c4..e77debd2f2 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -54,6 +54,7 @@ class RoomListStore extends Store { "im.vector.fake.archived": [], }, ready: false, + roomCache: {}, // roomId => { cacheType => value } }; } @@ -85,6 +86,8 @@ class RoomListStore extends Store { !payload.isLiveUnfilteredRoomTimelineEvent || !this._eventTriggersRecentReorder(payload.event) ) break; + + this._clearCachedRoomState(payload.event.getRoomId()); this._generateRoomLists(); } break; @@ -112,6 +115,8 @@ class RoomListStore extends Store { if (liveTimeline !== eventTimeline || !this._eventTriggersRecentReorder(payload.event) ) break; + + this._clearCachedRoomState(payload.event.getRoomId()); this._generateRoomLists(); } break; @@ -222,12 +227,20 @@ class RoomListStore extends Store { // thousand times. const pinUnread = SettingsStore.getValue("pinUnreadRooms"); const pinMentioned = SettingsStore.getValue("pinMentionedRooms"); + this._timings = {}; Object.keys(lists).forEach((listKey) => { let comparator; switch (RoomListStore._listOrders[listKey]) { case "recent": comparator = (roomA, roomB) => { - return this._recentsComparator(roomA, roomB, pinUnread, pinMentioned); + this._timings["overall_" + roomA.roomId + "_" + roomB.roomId] = { + type: "overall", + start: performance.now(), + end: 0, + }; + const ret = this._recentsComparator(roomA, roomB, pinUnread, pinMentioned); + this._timings["overall_" + roomA.roomId + "_" + roomB.roomId].end = performance.now(); + return ret; }; break; case "manual": @@ -238,12 +251,76 @@ class RoomListStore extends Store { lists[listKey].sort(comparator); }); + // Combine the samples for performance metrics + const samplesByType = {}; + for (const sampleName of Object.keys(this._timings)) { + const sample = this._timings[sampleName]; + if (!samplesByType[sample.type]) samplesByType[sample.type] = { + min: 999999999, + max: 0, + count: 0, + total: 0, + }; + + const record = samplesByType[sample.type]; + const duration = sample.end - sample.start; + if (duration < record.min) record.min = duration; + if (duration > record.max) record.max = duration; + record.count++; + record.total += duration; + } + + for (const category of Object.keys(samplesByType)) { + const {min, max, count, total} = samplesByType[category]; + const average = total / count; + + console.log(`RoomListSortPerf : type=${category} min=${min} max=${max} total=${total} samples=${count} average=${average}`); + } + this._setState({ lists, ready: true, // Ready to receive updates via Room.tags events }); } + _updateCachedRoomState(roomId, type, value) { + const roomCache = this._state.roomCache; + if (!roomCache[roomId]) roomCache[roomId] = {}; + + if (value) roomCache[roomId][type] = value; + else delete roomCache[roomId][type]; + + this._setState({roomCache}); + } + + _clearCachedRoomState(roomId) { + const roomCache = this._state.roomCache; + delete roomCache[roomId]; + this._setState({roomCache}); + } + + _getRoomState(room, type) { + const roomId = room.roomId; + const roomCache = this._state.roomCache; + if (roomCache[roomId] && typeof roomCache[roomId][type] !== 'undefined') { + return roomCache[roomId][type]; + } + + if (type === "timestamp") { + const ts = this._tsOfNewestEvent(room); + this._updateCachedRoomState(roomId, "timestamp", ts); + return ts; + } else if (type === "unread") { + const unread = room.getUnreadNotificationCount() > 0; + this._updateCachedRoomState(roomId, "unread", unread); + return unread; + } else if (type === "notifications") { + const notifs = room.getUnreadNotificationCount("highlight") > 0; + this._updateCachedRoomState(roomId, "notifications", notifs); + return notifs; + } else throw new Error("Unrecognized room cache type: " + type); + } + _eventTriggersRecentReorder(ev) { return ev.getTs() && ( Unread.eventTriggersUnreadCount(ev) || @@ -270,30 +347,58 @@ class RoomListStore extends Store { } _recentsComparator(roomA, roomB, pinUnread, pinMentioned) { + //console.log("Comparing " + roomA.roomId + " with " + roomB.roomId +" || pinUnread=" + pinUnread +" pinMentioned="+pinMentioned); // We try and set the ordering to be Mentioned > Unread > Recent - // assuming the user has the right settings, of course + // assuming the user has the right settings, of course. + + this._timings["timestamp_" + roomA.roomId + "_" + roomB.roomId] = { + type: "timestamp", + start: performance.now(), + end: 0, + }; + const timestampA = this._getRoomState(roomA, "timestamp"); + const timestampB = this._getRoomState(roomB, "timestamp"); + const timestampDiff = timestampB - timestampA; + this._timings["timestamp_" + roomA.roomId + "_" + roomB.roomId].end = performance.now(); if (pinMentioned) { - const mentionsA = roomA.getUnreadNotificationCount("highlight") > 0; - const mentionsB = roomB.getUnreadNotificationCount("highlight") > 0; - if (mentionsA && !mentionsB) return -1; - if (!mentionsA && mentionsB) return 1; - if (mentionsA && mentionsB) return 0; - // If neither have mentions, fall through to remaining checks + this._timings["mentioned_" + roomA.roomId + "_" + roomB.roomId] = { + type: "mentioned", + start: performance.now(), + end: 0, + }; + const mentionsA = this._getRoomState(roomA, "notifications"); + const mentionsB = this._getRoomState(roomB, "notifications"); + this._timings["mentioned_" + roomA.roomId + "_" + roomB.roomId].end = performance.now(); + if (mentionsA && !mentionsB) return -1; + if (!mentionsA && mentionsB) return 1; + + // If they both have notifications, sort by timestamp. + // If neither have notifications (the fourth check not shown + // here), then try and sort by unread messages and finally by + // timestamp. + if (mentionsA && mentionsB) return timestampDiff; } if (pinUnread) { - const unreadA = Unread.doesRoomHaveUnreadMessages(roomA); - const unreadB = Unread.doesRoomHaveUnreadMessages(roomB); + this._timings["unread_" + roomA.roomId + "_" + roomB.roomId] = { + type: "unread", + start: performance.now(), + end: 0, + }; + const unreadA = this._getRoomState(roomA, "unread"); + const unreadB = this._getRoomState(roomB, "notifications"); + this._timings["unread_" + roomA.roomId + "_" + roomB.roomId].end = performance.now(); if (unreadA && !unreadB) return -1; if (!unreadA && unreadB) return 1; - if (unreadA && unreadB) return 0; - // If neither have unread messages, fall through to remaining checks + + // If they both have unread messages, sort by timestamp + // If nether have unread message (the fourth check not shown + // here), then just sort by timestamp anyways. + if (unreadA && unreadB) return timestampDiff; } - // XXX: We could use a cache here and update it when we see new - // events that trigger a reorder - return this._tsOfNewestEvent(roomB) - this._tsOfNewestEvent(roomA); + return timestampDiff; } _lexicographicalComparator(roomA, roomB) { From 122868e32f3c27b1565c1044b3c325b96c5a0b54 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 1 Nov 2018 16:30:48 -0600 Subject: [PATCH 07/27] Removing timing/performance tracking on room list store This was used to verify the fix was actually making improvements and can be safely taken out. --- src/stores/RoomListStore.js | 55 +------------------------------------ 1 file changed, 1 insertion(+), 54 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index e77debd2f2..8dc557ace0 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -227,20 +227,12 @@ class RoomListStore extends Store { // thousand times. const pinUnread = SettingsStore.getValue("pinUnreadRooms"); const pinMentioned = SettingsStore.getValue("pinMentionedRooms"); - this._timings = {}; Object.keys(lists).forEach((listKey) => { let comparator; switch (RoomListStore._listOrders[listKey]) { case "recent": comparator = (roomA, roomB) => { - this._timings["overall_" + roomA.roomId + "_" + roomB.roomId] = { - type: "overall", - start: performance.now(), - end: 0, - }; - const ret = this._recentsComparator(roomA, roomB, pinUnread, pinMentioned); - this._timings["overall_" + roomA.roomId + "_" + roomB.roomId].end = performance.now(); - return ret; + return this._recentsComparator(roomA, roomB, pinUnread, pinMentioned); }; break; case "manual": @@ -251,32 +243,6 @@ class RoomListStore extends Store { lists[listKey].sort(comparator); }); - // Combine the samples for performance metrics - const samplesByType = {}; - for (const sampleName of Object.keys(this._timings)) { - const sample = this._timings[sampleName]; - if (!samplesByType[sample.type]) samplesByType[sample.type] = { - min: 999999999, - max: 0, - count: 0, - total: 0, - }; - - const record = samplesByType[sample.type]; - const duration = sample.end - sample.start; - if (duration < record.min) record.min = duration; - if (duration > record.max) record.max = duration; - record.count++; - record.total += duration; - } - - for (const category of Object.keys(samplesByType)) { - const {min, max, count, total} = samplesByType[category]; - const average = total / count; - - console.log(`RoomListSortPerf : type=${category} min=${min} max=${max} total=${total} samples=${count} average=${average}`); - } - this._setState({ lists, ready: true, // Ready to receive updates via Room.tags events @@ -347,29 +313,16 @@ class RoomListStore extends Store { } _recentsComparator(roomA, roomB, pinUnread, pinMentioned) { - //console.log("Comparing " + roomA.roomId + " with " + roomB.roomId +" || pinUnread=" + pinUnread +" pinMentioned="+pinMentioned); // We try and set the ordering to be Mentioned > Unread > Recent // assuming the user has the right settings, of course. - this._timings["timestamp_" + roomA.roomId + "_" + roomB.roomId] = { - type: "timestamp", - start: performance.now(), - end: 0, - }; const timestampA = this._getRoomState(roomA, "timestamp"); const timestampB = this._getRoomState(roomB, "timestamp"); const timestampDiff = timestampB - timestampA; - this._timings["timestamp_" + roomA.roomId + "_" + roomB.roomId].end = performance.now(); if (pinMentioned) { - this._timings["mentioned_" + roomA.roomId + "_" + roomB.roomId] = { - type: "mentioned", - start: performance.now(), - end: 0, - }; const mentionsA = this._getRoomState(roomA, "notifications"); const mentionsB = this._getRoomState(roomB, "notifications"); - this._timings["mentioned_" + roomA.roomId + "_" + roomB.roomId].end = performance.now(); if (mentionsA && !mentionsB) return -1; if (!mentionsA && mentionsB) return 1; @@ -381,14 +334,8 @@ class RoomListStore extends Store { } if (pinUnread) { - this._timings["unread_" + roomA.roomId + "_" + roomB.roomId] = { - type: "unread", - start: performance.now(), - end: 0, - }; const unreadA = this._getRoomState(roomA, "unread"); const unreadB = this._getRoomState(roomB, "notifications"); - this._timings["unread_" + roomA.roomId + "_" + roomB.roomId].end = performance.now(); if (unreadA && !unreadB) return -1; if (!unreadA && unreadB) return 1; From a713cc5c52309f74d1dfccef28556de037eb3db1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 1 Nov 2018 17:07:05 -0600 Subject: [PATCH 08/27] Compare the right types of events --- src/stores/RoomListStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 8dc557ace0..f7596774b6 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -335,7 +335,7 @@ class RoomListStore extends Store { if (pinUnread) { const unreadA = this._getRoomState(roomA, "unread"); - const unreadB = this._getRoomState(roomB, "notifications"); + const unreadB = this._getRoomState(roomB, "unread"); if (unreadA && !unreadB) return -1; if (!unreadA && unreadB) return 1; From 3960ae2fcd956702c5adc4f0929438acdea53388 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 1 Nov 2018 17:17:01 -0600 Subject: [PATCH 09/27] Add more commentary around how the roomCache works --- src/stores/RoomListStore.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index f7596774b6..2b70d53b59 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -54,7 +54,24 @@ class RoomListStore extends Store { "im.vector.fake.archived": [], }, ready: false, - roomCache: {}, // roomId => { cacheType => value } + + // The room cache stores a mapping of roomId to cache record. + // Each cache record is a key/value pair for various bits of + // data used to sort the room list. Currently this stores the + // following bits of informations: + // "timestamp": number, The timestamp of the last relevant + // event in the room. + // "notifications": boolean, Whether or not the user has been + // highlighted on any unread events. + // "unread": boolean, Whether or not the user has any + // unread events. + // + // All of the cached values are lazily loaded on read in the + // recents comparator. When an event is received for a particular + // room, all the cached values are invalidated - forcing the + // next read to set new values. The entries do not expire on + // their own. + roomCache: {}, }; } From f9d5c11d8d373ce6d521ce436b5e6ccdaa4604aa Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 4 Nov 2018 19:47:24 -0700 Subject: [PATCH 10/27] Regenerate the room list when m.fully_read is issued Not doing so results in the RoomListStore tracking stale data when the user reads messages on another device. The visual effect of this is rooms being incorrectly pinned in places they shouldn't be, such as the top of the list. This also fixes another visual bug where rooms don't move down once their timelines are read. This second issue is mot prominent when multiple rooms have been pinned to the top, and the middle one is read ahead of the others - it'll stick around until some other condition decides to wipe the room's cached state. Fixes https://github.com/vector-im/riot-web/issues/7653 --- src/actions/MatrixActionCreators.js | 30 +++++++++++++++++++++++++++++ src/stores/RoomListStore.js | 7 +++++++ 2 files changed, 37 insertions(+) diff --git a/src/actions/MatrixActionCreators.js b/src/actions/MatrixActionCreators.js index 31bcac3e52..2f2c23d06f 100644 --- a/src/actions/MatrixActionCreators.js +++ b/src/actions/MatrixActionCreators.js @@ -62,6 +62,35 @@ function createAccountDataAction(matrixClient, accountDataEvent) { }; } +/** + * @typedef RoomAccountDataAction + * @type {Object} + * @property {string} action 'MatrixActions.Room.accountData'. + * @property {MatrixEvent} event the MatrixEvent that triggered the dispatch. + * @property {string} event_type the type of the MatrixEvent, e.g. "m.direct". + * @property {Object} event_content the content of the MatrixEvent. + * @property {Room} room the room where the account data was changed. + */ + +/** + * Create a MatrixActions.Room.accountData action that represents a MatrixClient `Room.accountData` + * matrix event. + * + * @param {MatrixClient} matrixClient the matrix client. + * @param {MatrixEvent} accountDataEvent the account data event. + * @param {Room} room the room where account data was changed + * @returns {RoomAccountDataAction} an action of type MatrixActions.accountData. + */ +function createRoomAccountDataAction(matrixClient, accountDataEvent, room) { + return { + action: 'MatrixActions.Room.accountData', + event: accountDataEvent, + event_type: accountDataEvent.getType(), + event_content: accountDataEvent.getContent(), + room: room, + }; +} + /** * @typedef RoomAction * @type {Object} @@ -201,6 +230,7 @@ export default { start(matrixClient) { this._addMatrixClientListener(matrixClient, 'sync', createSyncAction); this._addMatrixClientListener(matrixClient, 'accountData', createAccountDataAction); + this._addMatrixClientListener(matrixClient, 'Room.accountData', createRoomAccountDataAction); this._addMatrixClientListener(matrixClient, 'Room', createRoomAction); this._addMatrixClientListener(matrixClient, 'Room.tags', createRoomTagsAction); this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction); diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 2b70d53b59..0f8e5d7b4d 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -142,6 +142,13 @@ class RoomListStore extends Store { this._generateRoomLists(); } break; + case 'MatrixActions.Room.accountData': { + if (payload.event_type === 'm.fully_read') { + this._clearCachedRoomState(payload.room.roomId); + this._generateRoomLists(); + } + } + break; case 'MatrixActions.Room.myMembership': { this._generateRoomLists(); } From ec2528e8b5f1c015e1c35e2f87c9c9eb9f748344 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 4 Nov 2018 23:00:47 -0700 Subject: [PATCH 11/27] Update MatrixActionCreators.js --- src/actions/MatrixActionCreators.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/MatrixActionCreators.js b/src/actions/MatrixActionCreators.js index 2f2c23d06f..c1d42ffd0d 100644 --- a/src/actions/MatrixActionCreators.js +++ b/src/actions/MatrixActionCreators.js @@ -79,7 +79,7 @@ function createAccountDataAction(matrixClient, accountDataEvent) { * @param {MatrixClient} matrixClient the matrix client. * @param {MatrixEvent} accountDataEvent the account data event. * @param {Room} room the room where account data was changed - * @returns {RoomAccountDataAction} an action of type MatrixActions.accountData. + * @returns {RoomAccountDataAction} an action of type MatrixActions.Room.accountData. */ function createRoomAccountDataAction(matrixClient, accountDataEvent, room) { return { From 1d77a676835c18161262de8b55ad8e7603d46343 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 10:09:05 +0100 Subject: [PATCH 12/27] fix room list chevron --- res/css/structures/_RoomSubList.scss | 9 ++- res/img/topleft-chevron.svg | 99 +++++++++++++++++++++++----- 2 files changed, 88 insertions(+), 20 deletions(-) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index e062a912a5..2b89e055df 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -109,12 +109,11 @@ limitations under the License. pointer-events: none; position: absolute; top: 11px; - width: 9px; - height: 4px; + width: 10px; + height: 6px; background-image: url('../../img/topleft-chevron.svg'); - background-size: cover; - // the transition doesn't work as the chevron gets remounted - transition: rotateZ 0.2s ease-in; + background-repeat: no-repeat; + transition: transform 0.2s ease-in; } .mx_RoomSubList_chevronDown { diff --git a/res/img/topleft-chevron.svg b/res/img/topleft-chevron.svg index 9fbf5fe9ca..fa89852874 100644 --- a/res/img/topleft-chevron.svg +++ b/res/img/topleft-chevron.svg @@ -1,17 +1,86 @@ - - - - dropdown - Created with Sketch. - - - - - - - - - + + + + + + image/svg+xml + + dropdown + + + + + + dropdown + Created with Sketch. + + + + + + + + - \ No newline at end of file + + From 010f077092f8fd59a05198a5d8b2e268c3230585 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 10:35:38 +0100 Subject: [PATCH 13/27] align badges with design --- res/css/structures/_RoomSubList.scss | 21 ++++--------------- res/css/views/rooms/_RoomTile.scss | 30 ++-------------------------- 2 files changed, 6 insertions(+), 45 deletions(-) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index 2b89e055df..9eebbfe63f 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -28,6 +28,7 @@ limitations under the License. .mx_RoomSubList_labelContainer { display: flex; flex-direction: row; + align-items: center; flex: 0 0 auto; margin: 8px 19px 0 0; } @@ -56,14 +57,12 @@ limitations under the License. } .mx_RoomSubList_badge { - height: 18px; - border-radius: 9px; + flex: 0 1 content; + border-radius: 8px; color: $accent-fg-color; font-weight: 600; font-size: 12px; - vertical-align: middle; - line-height: 18px; - padding: 0 4px; + padding: 0 5px; background-color: $accent-color; } @@ -92,18 +91,6 @@ limitations under the License. background-color: $warning-color; } -/* 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 $warning-color; - border-right: 7px solid transparent; -} - .mx_RoomSubList_chevron { left: 0px; pointer-events: none; diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index ca23d79137..4bedc4cf4f 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -21,7 +21,7 @@ limitations under the License. cursor: pointer; height: 40px; margin: 0; - padding: 2px 12px; + padding: 2px 10px; position: relative; background-color: $secondary-accent-color; } @@ -88,43 +88,17 @@ limitations under the License. padding: 0px 4px 0px 4px; z-index: 3; } - - /* Hide the bottom of speech bubble */ - .mx_RoomTile_highlight .mx_RoomTile_badge:after { - display: none; - } -} - -/* This is the bottom of the speech bubble */ -.mx_RoomTile_highlight .mx_RoomTile_badge:after { - content: ""; - position: absolute; - display: block; - width: 0; - height: 0; - margin-left: 5px; - border-top: 5px solid $warning-color; - border-right: 7px solid transparent; } .mx_RoomTile_badge { flex: 0 1 content; - min-width: 15px; border-radius: 8px; color: $accent-fg-color; font-weight: 600; font-size: 12px; - text-align: center; - padding-top: 1px; - padding-left: 4px; - padding-right: 4px; + padding: 0 5px; } -.mx_RoomTile .mx_RoomTile_badge.mx_RoomTile_badgeButton, -.mx_RoomTile.mx_RoomTile_menuDisplayed .mx_RoomTile_badge { - letter-spacing: 0.1em; - opacity: 1; -} .mx_RoomTile.mx_RoomTile_noBadges .mx_RoomTile_badge.mx_RoomTile_badgeButton, .mx_RoomTile.mx_RoomTile_menuDisplayed.mx_RoomTile_noBadges .mx_RoomTile_badge { From 6993dc034d0baa9caec6f45fb8240d40e3d18803 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 10:35:50 +0100 Subject: [PATCH 14/27] always show badges in room sub list header --- src/components/structures/RoomSubList.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 3ea819e088..32c53cd52a 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -266,19 +266,17 @@ const RoomSubList = React.createClass({ let badge; - if (this.state.hidden) { - const badgeClasses = classNames({ - 'mx_RoomSubList_badge': true, - 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, - }); - if (subListNotifCount > 0) { - badge =
- { FormattingUtils.formatCount(subListNotifCount) } -
; - } else if (this.props.isInvite) { - // no notifications but highlight anyway because this is an invite badge - badge =
!
; - } + const badgeClasses = classNames({ + 'mx_RoomSubList_badge': true, + 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, + }); + if (subListNotifCount > 0) { + badge =
+ { FormattingUtils.formatCount(subListNotifCount) } +
; + } else if (this.props.isInvite) { + // no notifications but highlight anyway because this is an invite badge + badge =
!
; } // When collapsed, allow a long hover on the header to show user From 01626d6b02da826fc66e92e95262a499f7e9130b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 14:21:58 +0100 Subject: [PATCH 15/27] never show horizontal scrollbar in subroom list --- res/css/structures/_RoomSubList.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index 9eebbfe63f..c0454c2914 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -119,6 +119,7 @@ limitations under the License. /* let rooms list grab all available space */ flex: 0 1 auto; padding: 0 15px !important; + overflow-x: hidden; } /* for browsers that don't support overlay scrollbars, From c0e3e3925ffe360aac253b86fc899b9f77ca1c7b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 14:22:41 +0100 Subject: [PATCH 16/27] more badge cleanup --- res/css/structures/_RoomSubList.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index c0454c2914..382e04b98e 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -140,11 +140,6 @@ body.mx_scrollbar_nooverlay .mx_RoomSubList_scroll.mx_AutoHideScrollbar_overflow width: 28px; /* collapsed LHS Panel width */ } - /* Hide the bottom of speech bubble */ - .mx_RoomSubList_badgeHighlight:after { - display: none; - } - .mx_RoomSubList_line { display: none; } From cb8393d449c5a2d94486b2ad210dc502c084b10b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 14:22:55 +0100 Subject: [PATCH 17/27] fix room sublist padding when collapsed --- res/css/structures/_RoomSubList.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index 382e04b98e..93d02ed100 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -131,8 +131,13 @@ body.mx_scrollbar_nooverlay .mx_RoomSubList_scroll.mx_AutoHideScrollbar_overflow } .collapsed { + + .mx_RoomSubList_scroll { + padding: 0px !important; + } .mx_RoomSubList_label { height: 17px; + padding-right: 0; width: 28px; /* collapsed LHS Panel width */ } From c0becc7664ad75ccb94cbe0a589d49448c7dfe11 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 14:23:28 +0100 Subject: [PATCH 18/27] fix roomtile badge only put it in the dom if it has anything to show as it has a border now in collapsed mode --- res/css/views/rooms/_RoomTile.scss | 14 +++++++------- src/components/views/rooms/RoomTile.js | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 4bedc4cf4f..d7726d818d 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -71,6 +71,8 @@ limitations under the License. .mx_RoomTile { margin: 2px; padding: 2px 0 2px 12px; + display: block; // not flex + position: relative; } .mx_RoomTile_name { @@ -80,23 +82,21 @@ limitations under the License. .mx_RoomTile_badge { display: block; position: absolute; - height: 15px; - right: 8px; - top: 2px; - min-width: 12px; + right: 6px; + top: 0px; border-radius: 16px; - padding: 0px 4px 0px 4px; z-index: 3; + border: 0.18em solid $secondary-accent-color; } } .mx_RoomTile_badge { flex: 0 1 content; - border-radius: 8px; + border-radius: 0.8em; + padding: 0 0.4em; color: $accent-fg-color; font-weight: 600; font-size: 12px; - padding: 0 5px; } diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 1afff783a1..56b34c4385 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -283,11 +283,11 @@ module.exports = React.createClass({ } else if (badges) { const limitedCount = FormattingUtils.formatCount(notificationCount); badgeContent = notificationCount ? limitedCount : '!'; - } else { - badgeContent = '\u200B'; } - const badge =
{ badgeContent }
; + const badge = badgeContent ? + (
{ badgeContent }
) : + undefined; const EmojiText = sdk.getComponent('elements.EmojiText'); let label; From 05935db8fc4ef97d5596baaf7b3e9316a1375351 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 14:25:07 +0100 Subject: [PATCH 19/27] don't show sublist header badge in collapsed mode just not enough space --- src/components/structures/RoomSubList.js | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 32c53cd52a..ccaeda9659 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -264,19 +264,20 @@ const RoomSubList = React.createClass({ const subListNotifCount = subListNotifications[0]; const subListNotifHighlight = subListNotifications[1]; - let badge; - const badgeClasses = classNames({ - 'mx_RoomSubList_badge': true, - 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, - }); - if (subListNotifCount > 0) { - badge =
- { FormattingUtils.formatCount(subListNotifCount) } -
; - } else if (this.props.isInvite) { - // no notifications but highlight anyway because this is an invite badge - badge =
!
; + if (!this.props.collapsed) { + const badgeClasses = classNames({ + 'mx_RoomSubList_badge': true, + 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, + }); + if (subListNotifCount > 0) { + badge =
+ { FormattingUtils.formatCount(subListNotifCount) } +
; + } else if (this.props.isInvite) { + // no notifications but highlight anyway because this is an invite badge + badge =
!
; + } } // When collapsed, allow a long hover on the header to show user From 665d42475826245984d6b79385eba01f1d39e0a6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 14:25:28 +0100 Subject: [PATCH 20/27] use svg icon in add room button --- res/css/structures/_RoomSubList.scss | 16 ++--- res/img/icons-room-add.svg | 92 ++++++++++++++++++------ src/components/structures/RoomSubList.js | 4 +- 3 files changed, 79 insertions(+), 33 deletions(-) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index 93d02ed100..2b83e4ee03 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -77,14 +77,14 @@ limitations under the License. .mx_RoomSubList_addRoom { background-color: $roomheader-addroom-color; color: $roomsublist-background; - border-radius: 9px; - text-align: center; - vertical-align: middle; - line-height: 18px; - font-weight: bold; - font-size: 18px; - width: 18px; - height: 18px; + background-image: url('../../img/icons-room-add.svg'); + background-repeat: no-repeat; + background-position: center; + border-radius: 10px; // 16/2 + 2 padding + height: 16px; + flex: 0 0 16px; + background-clip: content-box; + padding: 2px; } .mx_RoomSubList_badgeHighlight { diff --git a/res/img/icons-room-add.svg b/res/img/icons-room-add.svg index fc0ab750b6..6dd2e21295 100644 --- a/res/img/icons-room-add.svg +++ b/res/img/icons-room-add.svg @@ -1,23 +1,71 @@ - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index ccaeda9659..f5a9ede9c9 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -305,9 +305,7 @@ const RoomSubList = React.createClass({ let addRoomButton; if (this.props.onAddRoom) { addRoomButton = ( - - + - + ); } From ef26189addaffc1791d3ef335684a54872f4d597 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 17:42:25 +0100 Subject: [PATCH 21/27] add dedicated menu button to room tile with new design --- res/css/views/rooms/_RoomTile.scss | 34 ++++++++++++++++++-------- src/components/views/rooms/RoomTile.js | 21 ++++++++-------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index d7726d818d..2cda8360b1 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -26,6 +26,26 @@ limitations under the License. background-color: $secondary-accent-color; } +.mx_RoomTile_menuButton { + display: none; + flex: 0 0 16px; + height: 16px; + background-image: url('../../img/icon_context.svg'); + background-repeat: no-repeat; + background-position: right center; +} + +// toggle menuButton and badge on hover/menu displayed +.mx_RoomTile:hover, .mx_RoomTile_menuDisplayed { + .mx_RoomTile_menuButton { + display: block; + } + + .mx_RoomTile_badge { + display: none; + } +} + .mx_RoomTile_tooltip { display: inline-block; position: relative; @@ -88,6 +108,10 @@ limitations under the License. z-index: 3; border: 0.18em solid $secondary-accent-color; } + + .mx_RoomTile_menuButton { + display: none; //no design for this for now + } } .mx_RoomTile_badge { @@ -99,12 +123,6 @@ limitations under the License. font-size: 12px; } - -.mx_RoomTile.mx_RoomTile_noBadges .mx_RoomTile_badge.mx_RoomTile_badgeButton, -.mx_RoomTile.mx_RoomTile_menuDisplayed.mx_RoomTile_noBadges .mx_RoomTile_badge { - background-color: $neutral-badge-color; -} - .mx_RoomTile_unreadNotify .mx_RoomTile_badge { background-color: $accent-color; } @@ -144,10 +162,6 @@ limitations under the License. background-color: $roomtile-focused-bg-color; } -.mx_RoomTile .mx_RoomTile_name.mx_RoomTile_badgeShown { - width: 140px; -} - .mx_RoomTile_arrow { position: absolute; right: 0px; diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 56b34c4385..faa08c7001 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -220,7 +220,7 @@ module.exports = React.createClass({ this.setState( { badgeHover: false } ); }, - onBadgeClicked: function(e) { + onOpenMenu: function(e) { // Prevent the RoomTile onClick event firing as well e.stopPropagation(); // Only allow non-guests to access the context menu @@ -276,19 +276,14 @@ module.exports = React.createClass({ if (name == undefined || name == null) name = ''; name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon - let badgeContent; - if (this.state.badgeHover || this.state.menuDisplayed) { - badgeContent = "\u00B7\u00B7\u00B7"; - } else if (badges) { + let badge; + if (badges) { const limitedCount = FormattingUtils.formatCount(notificationCount); - badgeContent = notificationCount ? limitedCount : '!'; + const badgeContent = notificationCount ? limitedCount : '!'; + badge =
{ badgeContent }
; } - const badge = badgeContent ? - (
{ badgeContent }
) : - undefined; - const EmojiText = sdk.getComponent('elements.EmojiText'); let label; let tooltip; @@ -317,6 +312,11 @@ module.exports = React.createClass({ // incomingCallBox = ; //} + let contextMenuButton; + if (!MatrixClientPeg.get().isGuest()) { + contextMenuButton = ; + } + const RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); let dmIndicator; @@ -338,6 +338,7 @@ module.exports = React.createClass({ { label } + { contextMenuButton } { badge } { /* { incomingCallBox } */ } { tooltip } From fd0b33edafa8179df89ca9f071868908bcaeaddc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 17:42:59 +0100 Subject: [PATCH 22/27] margin adjusment of room tile according to design --- res/css/views/rooms/_RoomTile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 2cda8360b1..c22566ee34 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -21,7 +21,7 @@ limitations under the License. cursor: pointer; height: 40px; margin: 0; - padding: 2px 10px; + padding: 1px 10px; position: relative; background-color: $secondary-accent-color; } From 3c70c2b82f4a9a29226845e0869a701e53e17352 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Nov 2018 17:43:16 +0100 Subject: [PATCH 23/27] not needed --- res/css/views/rooms/_RoomTile.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index c22566ee34..f13e0ea2fd 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -91,7 +91,6 @@ limitations under the License. .mx_RoomTile { margin: 2px; padding: 2px 0 2px 12px; - display: block; // not flex position: relative; } @@ -100,7 +99,6 @@ limitations under the License. } .mx_RoomTile_badge { - display: block; position: absolute; right: 6px; top: 0px; From c9dc273cb0a1bb513e214d5c24776ecff766c8b7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Nov 2018 10:20:04 +0100 Subject: [PATCH 24/27] better native scrollbar width compensation for FF instead of having to offset the padding of children of the autohiding scrollbar container, which gets fiddly quickly, add a new child to the scrollbar container that gets a negative margin of the scrollbar width when needed (on hover and overflowing when overlay is not supported). This needs an extra DOM element, but as it doesn't do anything weird layout-wise (like set position), it shouldn't affect styling at all. It also makes the auto hide scrollbar workarounds completely transparent to the rest of the code. --- res/css/structures/_AutoHideScrollbar.scss | 22 +++++++++---------- res/css/structures/_RoomSubList.scss | 13 ++--------- res/css/views/rooms/_RoomTile.scss | 2 +- .../structures/AutoHideScrollbar.js | 4 +++- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/res/css/structures/_AutoHideScrollbar.scss b/res/css/structures/_AutoHideScrollbar.scss index 0b8558a61e..70dbbe8d07 100644 --- a/res/css/structures/_AutoHideScrollbar.scss +++ b/res/css/structures/_AutoHideScrollbar.scss @@ -18,6 +18,7 @@ limitations under the License. 1. for browsers that support native overlay auto-hiding scrollbars */ .mx_AutoHideScrollbar { + overflow-x: hidden; overflow-y: auto; -ms-overflow-style: -ms-autohiding-scrollbar; } @@ -34,23 +35,20 @@ body.mx_scrollbar_overlay_noautohide .mx_AutoHideScrollbar:hover { } /* 3. as a last fallback, compensate for the scrollbar taking up space in the layout -by playing with the paddings. the default below will add a right padding -of the scrollbar width and clear that on hover. -this won't work well on classes that also need to set their padding, -so this needs to be overriden and adjust the padding with calc like so: -``` -body.mx_scrollbar_nooverlay .componentClass.mx_AutoHideScrollbar_overflow:hover { - padding-right: calc(15px - var(--scrollbar-width)) !important; -} -``` +by having giving the child element (.mx_AutoHideScrollbar_offset) a +negative right margin of the width of the scrollbar when the container +is overflowing. This is what Firefox ends up using. Overflow is detected +in javascript, and adds the mx_AutoHideScrollbar_overflow class to the container. +This only works in Firefox, which should be fine as this fallback is only needed there. */ body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar { - box-sizing: border-box; overflow-y: hidden; - padding-right: var(--scrollbar-width); } body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar:hover { overflow-y: auto; - padding-right: 0; +} + +body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow > .mx_AutoHideScrollbar_offset { + margin-right: calc(-1 * var(--scrollbar-width)); } diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index 2b83e4ee03..1a92a792d6 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -118,22 +118,13 @@ limitations under the License. .mx_RoomSubList_scroll { /* let rooms list grab all available space */ flex: 0 1 auto; - padding: 0 15px !important; - overflow-x: hidden; -} -/* -for browsers that don't support overlay scrollbars, -subtract scrollbar width from right padding on hover when overflowing -so the content doesn't jump when showing the scrollbars -*/ -body.mx_scrollbar_nooverlay .mx_RoomSubList_scroll.mx_AutoHideScrollbar_overflow:hover { - padding-right: calc(15px - var(--scrollbar-width)) !important; + padding: 0 8px; } .collapsed { .mx_RoomSubList_scroll { - padding: 0px !important; + padding: 0; } .mx_RoomSubList_label { height: 17px; diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index f13e0ea2fd..1ea62b8ca8 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -36,7 +36,7 @@ limitations under the License. } // toggle menuButton and badge on hover/menu displayed -.mx_RoomTile:hover, .mx_RoomTile_menuDisplayed { +.mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:hover, .mx_RoomTile_menuDisplayed { .mx_RoomTile_menuButton { display: block; } diff --git a/src/components/structures/AutoHideScrollbar.js b/src/components/structures/AutoHideScrollbar.js index 507af2c5f0..a462b2bf14 100644 --- a/src/components/structures/AutoHideScrollbar.js +++ b/src/components/structures/AutoHideScrollbar.js @@ -112,7 +112,9 @@ export default class AutoHideScrollbar extends React.Component { ref={this._collectContainerRef} className={["mx_AutoHideScrollbar", this.props.className].join(" ")} > - { this.props.children } +
+ { this.props.children } +
); } } From 3ce52d104c35f38881d5b44d36fcaf6f9d978db1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Nov 2018 14:02:43 +0100 Subject: [PATCH 25/27] align collapsed roomtile with design --- res/css/views/rooms/_RoomTile.scss | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 1ea62b8ca8..3bb6339a3c 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -21,7 +21,7 @@ limitations under the License. cursor: pointer; height: 40px; margin: 0; - padding: 1px 10px; + padding: 0 8px 0 10px; position: relative; background-color: $secondary-accent-color; } @@ -32,7 +32,7 @@ limitations under the License. height: 16px; background-image: url('../../img/icon_context.svg'); background-repeat: no-repeat; - background-position: right center; + background-position: center; } // toggle menuButton and badge on hover/menu displayed @@ -83,15 +83,12 @@ limitations under the License. text-overflow: ellipsis; } -.mx_RoomTile_invite { -/* color: rgba(69, 69, 69, 0.5); */ -} - .collapsed { .mx_RoomTile { - margin: 2px; - padding: 2px 0 2px 12px; + margin: 0 2px; + padding: 0 2px; position: relative; + justify-content: center; } .mx_RoomTile_name { From 3bd0bcde4b054a25ad83a51e50d1920dd8ad0c29 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Nov 2018 14:03:01 +0100 Subject: [PATCH 26/27] align room sub list header with design also make css more maintainable with less hardcoded dimensions --- res/css/structures/_RoomSubList.scss | 73 ++++++++---------------- src/components/structures/RoomSubList.js | 2 +- 2 files changed, 26 insertions(+), 49 deletions(-) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index 1a92a792d6..2223d8eee8 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -22,7 +22,7 @@ limitations under the License. } .mx_RoomSubList_nonEmpty { - margin-bottom: 8px; + margin-bottom: 4px; } .mx_RoomSubList_labelContainer { @@ -30,34 +30,30 @@ limitations under the License. flex-direction: row; align-items: center; flex: 0 0 auto; - margin: 8px 19px 0 0; + margin: 0 16px; + height: 36px; } .mx_RoomSubList_label { flex: 1; - position: relative; + cursor: pointer; + background-color: $secondary-accent-color; + display: flex; + align-items: center; + padding: 0 6px; +} + +.mx_RoomSubList_label > span { + flex: 1 1 auto; text-transform: uppercase; color: $roomsublist-label-fg-color; font-weight: 700; font-size: 12px; - margin-left: 16px; - padding-left: 16px; /* gutter */ - padding-right: 16px; /* gutter */ - padding-top: 6px; - padding-bottom: 6px; - cursor: pointer; - background-color: $secondary-accent-color; -} - -.mx_RoomSubList_label.mx_RoomSubList_fixed { - position: fixed; - top: 0; - z-index: 5; - /* pointer-events: none; */ + margin-left: 8px; } .mx_RoomSubList_badge { - flex: 0 1 content; + flex: 0 0 auto; border-radius: 8px; color: $accent-fg-color; font-weight: 600; @@ -66,12 +62,8 @@ limitations under the License. background-color: $accent-color; } -.mx_RoomSubList_label .mx_RoomSubList_badge:hover { - filter: brightness($focus-brightness); -} - .mx_RoomSubList_addRoom, .mx_RoomSubList_badge { - margin: 5px; + margin-left: 7px; } .mx_RoomSubList_addRoom { @@ -84,7 +76,6 @@ limitations under the License. height: 16px; flex: 0 0 16px; background-clip: content-box; - padding: 2px; } .mx_RoomSubList_badgeHighlight { @@ -92,15 +83,14 @@ limitations under the License. } .mx_RoomSubList_chevron { - left: 0px; pointer-events: none; - position: absolute; - top: 11px; - width: 10px; - height: 6px; background-image: url('../../img/topleft-chevron.svg'); background-repeat: no-repeat; transition: transform 0.2s ease-in; + width: 10px; + height: 10px; + background-position: center; + margin-left: 2px; } .mx_RoomSubList_chevronDown { @@ -126,31 +116,18 @@ limitations under the License. .mx_RoomSubList_scroll { padding: 0; } - .mx_RoomSubList_label { - height: 17px; - padding-right: 0; - width: 28px; /* collapsed LHS Panel width */ - } .mx_RoomSubList_labelContainer { - width: 28px; /* collapsed LHS Panel width */ + margin-right: 14px; + margin-left: 2px; } - .mx_RoomSubList_line { - display: none; + .mx_RoomSubList_addRoom { + margin-left: 3px; + margin-right: 28px; } - .mx_RoomSubList_moreBadge { - position: static; - margin-left: 16px; - margin-top: 2px; - } - - .mx_RoomSubList_ellipsis { - height: 20px; - } - - .mx_RoomSubList_more { + .mx_RoomSubList_label > span { display: none; } } diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index f5a9ede9c9..ee4aa3f11e 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -325,7 +325,7 @@ const RoomSubList = React.createClass({
{ chevron } - { this.props.collapsed ? '' : this.props.label } + {this.props.label} { incomingCall } { badge } From ba00c293511489ab39cecd7bb673034a5d31eba7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Nov 2018 14:03:29 +0100 Subject: [PATCH 27/27] ellipsis class not used anymore --- src/components/structures/LeftPanel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index 247b12dc88..ddd8467458 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -151,8 +151,7 @@ const LeftPanel = React.createClass({ } } while (element && !( classes.contains("mx_RoomTile") || - classes.contains("mx_SearchBox_search") || - classes.contains("mx_RoomSubList_ellipsis"))); + classes.contains("mx_SearchBox_search"))); if (element) { element.focus();