From a74bbb424c7c07494d71329e8d4c6ef9fc37a7b9 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 15 Apr 2017 11:37:09 +0100 Subject: [PATCH 01/15] cmd-k shortcut to the searchbox --- src/components/structures/SearchBox.js | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js index 729e7ef7..fb5beab1 100644 --- a/src/components/structures/SearchBox.js +++ b/src/components/structures/SearchBox.js @@ -21,6 +21,7 @@ var sdk = require('matrix-react-sdk') var dis = require('matrix-react-sdk/lib/dispatcher'); var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc'); var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); +var KeyCode = require('matrix-react-sdk/lib/KeyCode'); module.exports = React.createClass({ displayName: 'SearchBox', @@ -38,10 +39,12 @@ module.exports = React.createClass({ componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); + document.addEventListener('keydown', this._onKeyDown); }, componentWillUnmount: function() { dis.unregister(this.dispatcherRef); + document.removeEventListener('keydown', this._onKeyDown); }, onAction: function(payload) { @@ -90,6 +93,33 @@ module.exports = React.createClass({ this.onChange(); }, + _onKeyDown: function(ev) { + let handled = false; + const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; + let ctrlCmdOnly; + if (isMac) { + ctrlCmdOnly = ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey; + } else { + ctrlCmdOnly = ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey; + } + + switch (ev.keyCode) { + case KeyCode.KEY_K: + if (ctrlCmdOnly) { + if (this.refs.search) { + this.refs.search.focus(); + } + handled = true; + } + break; + } + + if (handled) { + ev.stopPropagation(); + ev.preventDefault(); + } + }, + render: function() { var TintableSvg = sdk.getComponent('elements.TintableSvg'); From e5e259e1f8e403383f5051e148c76229b59e0440 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 15 Apr 2017 12:02:16 +0100 Subject: [PATCH 02/15] put a ! on invite sublists --- src/components/structures/RoomSubList.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 4aa9ff00..de883932 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -248,10 +248,9 @@ var RoomSubList = React.createClass({ if (badges) { result[0] += notificationCount; - if (highlight) { - result[1] = true; - } } + + result[1] |= highlight; } return result; }, [0, false]); @@ -403,6 +402,9 @@ var RoomSubList = React.createClass({ if (subListNotifCount > 0) { badge =
{ FormattingUtils.formatCount(subListNotifCount) }
; } + else if (subListNotifHighlight) { + badge =
{ ! }
; + } // When collapsed, allow a long hover on the header to show user // the full tag name and room count From 27de972bfbba85a853c69c800519404826595f68 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 15 Apr 2017 12:02:50 +0100 Subject: [PATCH 03/15] oops --- src/components/structures/RoomSubList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index de883932..241a1efa 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -403,7 +403,7 @@ var RoomSubList = React.createClass({ badge =
{ FormattingUtils.formatCount(subListNotifCount) }
; } else if (subListNotifHighlight) { - badge =
{ ! }
; + badge =
!
; } // When collapsed, allow a long hover on the header to show user From 8351ec97380350b6c8ca8c63b920120b33468fa9 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 15 Apr 2017 13:23:11 +0100 Subject: [PATCH 04/15] thread RoomTile focus events through RoomSubList up to RoomList --- src/components/structures/RoomSubList.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 241a1efa..a0c9a5e1 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -78,6 +78,7 @@ var RoomSubList = React.createClass({ showSpinner: React.PropTypes.bool, // true to show a spinner if 0 elements when expanded collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed? onHeaderClick: React.PropTypes.func, + onRoomTileFocus: React.PropTypes.func, alwaysShowHeader: React.PropTypes.bool, incomingCall: React.PropTypes.object, onShowMoreRooms: React.PropTypes.func, @@ -373,6 +374,7 @@ var RoomSubList = React.createClass({ refreshSubList={ self._updateSubListCount } incomingCall={ null } onClick={ self.onRoomTileClick } + onFocus={ self.props.onRoomTileFocus } /> ); }); From c6ee221ae4aaac5d37be994cf5afcf97de2b854e Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 16 Apr 2017 15:58:00 +0100 Subject: [PATCH 05/15] typos --- src/components/structures/RoomSubList.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index a0c9a5e1..a2d05d8a 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -28,7 +28,7 @@ var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs'); var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils'); var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); -// turn this on for drop & drag console debugging galore +// turn this on for drag & drop console debugging galore var debug = false; const TRUNCATE_AT = 10; @@ -502,7 +502,7 @@ var RoomSubList = React.createClass({ // gets triggered and another list is passed in. Doing it one at a time means that // we always correctly calculate the highest order for the list - stops multiple // rooms getting the same order. This is only really relevant for the first time this - // is run with historical room tag data, after that there should only be undefined + // is run with historical room tag data, after that there should only be one undefined // in the list at a time anyway. for (let i = 0; i < list.length; i++) { if (list[i].tags[self.props.tagName] && list[i].tags[self.props.tagName].order === undefined) { From 5ff49f400000f05912b6b701f315885eb08768ca Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 17 Apr 2017 20:53:46 +0100 Subject: [PATCH 06/15] split out header from RoomSubList and let it update separately By moving the header into its own RoomSubListHeader, we can refresh it explicitly by poking it by the new constantTimeDispatcher without re-rendering the whole stack of room tiles *UNTESTED* --- src/component-index.js | 2 + src/components/structures/RoomSubList.js | 115 +++++++---------- .../structures/RoomSubListHeader.js | 116 ++++++++++++++++++ 3 files changed, 161 insertions(+), 72 deletions(-) create mode 100644 src/components/structures/RoomSubListHeader.js diff --git a/src/component-index.js b/src/component-index.js index 2b67aa15..4bf0b0f9 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -40,6 +40,8 @@ import structures$RoomDirectory from './components/structures/RoomDirectory'; structures$RoomDirectory && (module.exports.components['structures.RoomDirectory'] = structures$RoomDirectory); import structures$RoomSubList from './components/structures/RoomSubList'; structures$RoomSubList && (module.exports.components['structures.RoomSubList'] = structures$RoomSubList); +import structures$RoomSubListHeader from './components/structures/RoomSubListHeader'; +structures$RoomSubListHeader && (module.exports.components['structures.RoomSubListHeader'] = structures$RoomSubListHeader); import structures$SearchBox from './components/structures/SearchBox'; structures$SearchBox && (module.exports.components['structures.SearchBox'] = structures$SearchBox); import structures$ViewSource from './components/structures/ViewSource'; diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index a2d05d8a..1c02b416 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -27,6 +27,7 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs'); var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils'); var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); +var ConstantTimeDispatcher = require('matrix-react-sdk/lib/ConstantTimeDispatcher'); // turn this on for drag & drop console debugging galore var debug = false; @@ -101,13 +102,25 @@ var RoomSubList = React.createClass({ }, componentWillMount: function() { + constantTimeDispatcher.register("RoomSubList.sort", this.props.tagName, this.onSort); this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order); + this._fixUndefinedOrder(list); + }, + + componentWillUnmount: function() { + constantTimeDispatcher.unregister("RoomSubList.sort", this.props.tagName, this.onSort); }, componentWillReceiveProps: function(newProps) { // order the room list appropriately before we re-render //if (debug) console.log("received new props, list = " + newProps.list); this.sortList(this.applySearchFilter(newProps.list, newProps.searchFilter), newProps.order); + this._fixUndefinedOrder(list); + }, + + onSort: function() { + this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order); + // we deliberately don't waste time trying to fix undefined ordering here }, applySearchFilter: function(list, filter) { @@ -212,9 +225,6 @@ var RoomSubList = React.createClass({ if (order === "manual") comparator = this.manualComparator; if (order === "recent") comparator = this.recentsComparator; - // Fix undefined orders here, and make sure the backend gets updated as well - this._fixUndefinedOrder(list); - //if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list); this.setState({ sortedList: list.sort(comparator) }); }, @@ -380,73 +390,6 @@ var RoomSubList = React.createClass({ }); }, - _getHeaderJsx: function() { - var TintableSvg = sdk.getComponent("elements.TintableSvg"); - - var subListNotifications = this.roomNotificationCount(); - var subListNotifCount = subListNotifications[0]; - var subListNotifHighlight = subListNotifications[1]; - - var roomCount = this.props.list.length > 0 ? this.props.list.length : ''; - - var chevronClasses = classNames({ - 'mx_RoomSubList_chevron': true, - 'mx_RoomSubList_chevronRight': this.state.hidden, - 'mx_RoomSubList_chevronDown': !this.state.hidden, - }); - - var badgeClasses = classNames({ - 'mx_RoomSubList_badge': true, - 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, - }); - - var badge; - if (subListNotifCount > 0) { - badge =
{ FormattingUtils.formatCount(subListNotifCount) }
; - } - else if (subListNotifHighlight) { - badge =
!
; - } - - // When collapsed, allow a long hover on the header to show user - // the full tag name and room count - var title; - if (this.props.collapsed) { - title = this.props.label; - if (roomCount !== '') { - title += " [" + roomCount + "]"; - } - } - - var incomingCall; - if (this.props.incomingCall) { - var self = this; - // Check if the incoming call is for this section - var incomingCallRoom = this.props.list.filter(function(room) { - return self.props.incomingCall.roomId === room.roomId; - }); - - if (incomingCallRoom.length === 1) { - var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox"); - incomingCall = ; - } - } - - var tabindex = this.props.searchFilter === "" ? "0" : "-1"; - - return ( -
- - { this.props.collapsed ? '' : this.props.label } -
{ roomCount }
-
- { badge } - { incomingCall } -
-
- ); - }, - _createOverflowTile: function(overflowCount, totalCount) { var content =
; @@ -536,6 +479,16 @@ var RoomSubList = React.createClass({ target = ; } + var roomCount = this.props.list.length > 0 ? this.props.list.length : ''; + + var isIncomingCallRoom; + if (this.props.incomingCall) { + // Check if the incoming call is for this section + isIncomingCallRoom = this.props.list.find(room=>{ + return this.props.incomingCall.roomId === room.roomId; + }) ? true : false; + } + if (this.state.sortedList.length > 0 || this.props.editable) { var subList; var classes = "mx_RoomSubList"; @@ -554,7 +507,15 @@ var RoomSubList = React.createClass({ return connectDropTarget(
- { this._getHeaderJsx() } +
); @@ -563,7 +524,17 @@ var RoomSubList = React.createClass({ var Loader = sdk.getComponent("elements.Spinner"); return (
- { this.props.alwaysShowHeader ? this._getHeaderJsx() : undefined } + { this.props.alwaysShowHeader ? +
); diff --git a/src/components/structures/RoomSubListHeader.js b/src/components/structures/RoomSubListHeader.js new file mode 100644 index 00000000..f97f9f47 --- /dev/null +++ b/src/components/structures/RoomSubListHeader.js @@ -0,0 +1,116 @@ +/* +Copyright 2017 Vector Creations 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 ReactDOM = require('react-dom'); +var classNames = require('classnames'); +var sdk = require('matrix-react-sdk') +var dis = require('matrix-react-sdk/lib/dispatcher'); +var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); +var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs'); +var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); +var ConstantTimeDispatcher = require('matrix-react-sdk/lib/ConstantTimeDispatcher'); + +module.exports = React.createClass({ + displayName: 'RoomSubListHeader', + + propTypes: { + label: React.PropTypes.string.isRequired, + tagName: React.PropTypes.string, + roomCount: React.PropTypes.string, + collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed? + isIncomingCallRoom: React.PropTypes.bool, + hidden: React.PropTypes.bool, + onHeaderClick: React.PropTypes.func, + }, + + getDefaultProps: function() { + return { + onHeaderClick: function() {}, // NOP + }; + }, + + componentWillMount: function() { + constantTimeDispatcher.register("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh); + }, + + componentWillUnmount: function() { + constantTimeDispatcher.unregister("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh); + }, + + onRefresh: function() { + this.forceUpdate(); + }, + + render: function() { + var TintableSvg = sdk.getComponent("elements.TintableSvg"); + + var subListNotifications = this.roomNotificationCount(); + var subListNotifCount = subListNotifications[0]; + var subListNotifHighlight = subListNotifications[1]; + + var chevronClasses = classNames({ + 'mx_RoomSubList_chevron': true, + 'mx_RoomSubList_chevronRight': this.props.hidden, + 'mx_RoomSubList_chevronDown': !this.props.hidden, + }); + + var badgeClasses = classNames({ + 'mx_RoomSubList_badge': true, + 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, + }); + + var badge; + if (subListNotifCount > 0) { + badge =
{ FormattingUtils.formatCount(subListNotifCount) }
; + } + else if (subListNotifHighlight) { + badge =
!
; + } + + // When collapsed, allow a long hover on the header to show user + // the full tag name and room count + var title; + if (this.props.collapsed) { + title = this.props.label; + if (roomCount !== '') { + title += " [" + roomCount + "]"; + } + } + + if (this.props.isIncomingCallRoom) { + var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox"); + incomingCall = ; + } + + var tabindex = this.props.searchFilter === "" ? "0" : "-1"; + + return ( +
+ + { this.props.collapsed ? '' : this.props.label } +
{ roomCount }
+
+ { badge } + { incomingCall } +
+
+ ); + }, +}); + From f8aa2c3487061881c1f4ca0cbe02af11981d6dfd Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 18 Apr 2017 02:43:06 +0100 Subject: [PATCH 07/15] fix bugs in RoomSubListHeader splitout --- src/components/structures/RoomSubList.js | 15 +++++++++++---- src/components/structures/RoomSubListHeader.js | 16 +++++++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 1c02b416..7656149f 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -28,6 +28,7 @@ var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs'); var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils'); var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); var ConstantTimeDispatcher = require('matrix-react-sdk/lib/ConstantTimeDispatcher'); +var RoomSubListHeader = require('./RoomSubListHeader.js'); // turn this on for drag & drop console debugging galore var debug = false; @@ -104,7 +105,7 @@ var RoomSubList = React.createClass({ componentWillMount: function() { constantTimeDispatcher.register("RoomSubList.sort", this.props.tagName, this.onSort); this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order); - this._fixUndefinedOrder(list); + this._fixUndefinedOrder(this.props.list); }, componentWillUnmount: function() { @@ -115,7 +116,7 @@ var RoomSubList = React.createClass({ // order the room list appropriately before we re-render //if (debug) console.log("received new props, list = " + newProps.list); this.sortList(this.applySearchFilter(newProps.list, newProps.searchFilter), newProps.order); - this._fixUndefinedOrder(list); + this._fixUndefinedOrder(newProps.list); }, onSort: function() { @@ -133,7 +134,7 @@ var RoomSubList = React.createClass({ // 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; + var stuck = this.refs.header.refs.header.dataset.stuck; if (this.state.hidden || stuck === undefined || stuck === "none") { return true; } else { @@ -156,7 +157,7 @@ var RoomSubList = React.createClass({ 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); + this.props.onHeaderClick(this.state.hidden, this.refs.header.refs.header.dataset.originalPosition); } }, @@ -508,12 +509,15 @@ var RoomSubList = React.createClass({ return connectDropTarget(