From 85ea45a64a4c8f04029666631b89f333b9279a25 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 27 Sep 2016 19:39:20 +0100 Subject: [PATCH] Room dir: New filtering & 3rd party networks Changes filtering on 3rd party networks to divide into portal / non portal rooms and not show portal rooms by default. Adds a special '_matrix' network for all rooms that aren't portal rooms. Also adds ability to query 3rd party directory servers. --- src/components/structures/RoomDirectory.js | 53 ++++++----- .../views/directory/NetworkDropdown.js | 94 ++++++++++++++++--- .../views/directory/NetworkDropdown.css | 4 + src/skins/vector/img/network-matrix.svg | 14 +++ 4 files changed, 129 insertions(+), 36 deletions(-) create mode 100644 src/skins/vector/img/network-matrix.svg diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 90afff5c27..8a2b52b949 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -53,6 +53,7 @@ module.exports = React.createClass({ publicRooms: [], loading: true, filterByNetwork: null, + roomServer: null, } }, @@ -76,10 +77,6 @@ module.exports = React.createClass({ // }); }, - componentDidMount: function() { - this.refreshRoomList(); - }, - componentWillUnmount: function() { // dis.dispatch({ // action: 'ui_opacity', @@ -102,6 +99,9 @@ module.exports = React.createClass({ const my_filter_string = this.filterString; const opts = {limit: 20}; + if (this.state.roomServer != MatrixClientPeg.getHomeServerName()) { + opts.server = this.state.roomServer; + } if (this.nextBatch) opts.since = this.nextBatch; if (this.filterString) opts.filter = { generic_search_term: my_filter_string } ; return MatrixClientPeg.get().publicRooms(opts).then((data) => { @@ -194,18 +194,11 @@ module.exports = React.createClass({ } }, - onNetworkChange: function(network) { + onOptionChange: function(server, network) { this.setState({ + roomServer: server, filterByNetwork: network, - }, () => { - // we just filtered out a bunch of rooms, so check to see if - // we need to fill up the scrollpanel again - // NB. Because we filter the results, the HS can keep giving - // us more rooms and we'll keep requesting more if none match - // the filter, which is pretty terrible. We need a way - // to filter by network on the server. - if (this.scrollPanel) this.scrollPanel.checkFillState(); - }); + }, this.refreshRoomList); }, onFillRequest: function(backwards) { @@ -295,7 +288,7 @@ module.exports = React.createClass({ var rooms = this.state.publicRooms.filter((a) => { if (this.state.filterByNetwork) { - if (!this._isRoomInNetwork(a, this.state.filterByNetwork)) return false; + if (!this._isRoomInNetwork(a, this.state.roomServer, this.state.filterByNetwork)) return false; } return true; @@ -365,14 +358,30 @@ module.exports = React.createClass({ * Terrible temporary function that guess what network a public room * entry is in, until synapse is able to tell us */ - _isRoomInNetwork(room, network) { - if (room.aliases && this.networkPatterns[network]) { - for (const alias of room.aliases) { - if (this.networkPatterns[network].test(alias)) return true; + _isRoomInNetwork(room, server, network) { + // We carve rooms into two categories here. 'portal' rooms are + // rooms created by a user joining a bridge 'portal' alias to + // participate in that room or a foreign network. A room is a + // portal room if it has exactly one alias and that alias matches + // a pattern defined in the config. It's network is the key + // of the pattern that it matches. + // All other rooms are considered 'native matrix' rooms, and + // go into the special '_matrix' network. + + let roomNetwork = '_matrix'; + if (room.aliases && room.aliases.length == 1) { + if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) { + for (const n of this.props.config.serverConfig[server].networks) { + const pat = this.networkPatterns[n]; + if (pat && pat) { + if (this.networkPatterns[n].test(room.aliases[0])) { + roomNetwork = n; + } + } + } } } - - return false; + return roomNetwork == network; }, render: function() { @@ -411,7 +420,7 @@ module.exports = React.createClass({ className="mx_RoomDirectory_searchbox" onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinClick} /> - + {content} diff --git a/src/components/views/directory/NetworkDropdown.js b/src/components/views/directory/NetworkDropdown.js index e1de4ffea1..e98922f2c2 100644 --- a/src/components/views/directory/NetworkDropdown.js +++ b/src/components/views/directory/NetworkDropdown.js @@ -15,6 +15,7 @@ limitations under the License. */ import React from 'react'; +import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; export default class NetworkDropdown extends React.Component { constructor() { @@ -26,12 +27,17 @@ export default class NetworkDropdown extends React.Component { this.onInputClick = this.onInputClick.bind(this); this.onRootClick = this.onRootClick.bind(this); this.onDocumentClick = this.onDocumentClick.bind(this); - this.onNetworkClick = this.onNetworkClick.bind(this); + this.onMenuOptionClick = this.onMenuOptionClick.bind(this); + this.onInputKeyUp = this.onInputKeyUp.bind(this); this.collectRoot = this.collectRoot.bind(this); + this.collectInputTextBox = this.collectInputTextBox.bind(this); + + this.inputTextBox = null; this.state = { expanded: false, - selectedNetwork: null, + selectedServer: MatrixClientPeg.getHomeServerName(), + selectedNetwork: '_matrix', }; } @@ -39,6 +45,9 @@ export default class NetworkDropdown extends React.Component { // Listen for all clicks on the document so we can close the // menu when the user clicks somewhere else document.addEventListener('click', this.onDocumentClick, false); + + // fire this now so the defaults can be set up + this.props.onOptionChange(this.state.selectedServer, this.state.selectedNetwork); } componentWillUnmount() { @@ -72,12 +81,24 @@ export default class NetworkDropdown extends React.Component { ev.preventDefault(); } - onNetworkClick(network, ev) { + onMenuOptionClick(server, network, ev) { this.setState({ expanded: false, + selectedServer: server, selectedNetwork: network, }); - this.props.onNetworkChange(network); + this.props.onOptionChange(server, network); + } + + onInputKeyUp(e) { + if (e.key == 'Enter') { + this.setState({ + expanded: false, + selectedServer: e.target.value, + selectedNetwork: null, + }); + this.props.onOptionChange(e.target.value, null); + } } collectRoot(e) { @@ -90,41 +111,86 @@ export default class NetworkDropdown extends React.Component { this.dropdownRootElement = e; } - _optionForNetwork(network, wire_onclick) { + collectInputTextBox(e) { + this.inputTextBox = e; + } + + _getMenuOptions() { + const options = []; + + let servers = []; + if (this.props.config.servers) { + servers = servers.concat(this.props.config.servers); + } + + if (servers.indexOf(MatrixClientPeg.getHomeServerName()) == -1) { + servers.unshift(MatrixClientPeg.getHomeServerName()); + } + + for (const server of servers) { + options.push(this._makeMenuOption(server, null)); + if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) { + for (const network of this.props.config.serverConfig[server].networks) { + options.push(this._makeMenuOption(server, network)); + } + } + } + + return options; + } + + _makeMenuOption(server, network, wire_onclick) { if (wire_onclick === undefined) wire_onclick = true; let icon; let name; let span_class; if (network === null) { - name = 'All networks'; + name = server; span_class = 'mx_NetworkDropdown_menu_all'; + } else if (network == '_matrix') { + name = 'Matrix'; + icon = ; + span_class = 'mx_NetworkDropdown_menu_network'; } else { name = this.props.config.networkNames[network]; icon = ; span_class = 'mx_NetworkDropdown_menu_network'; } - const click_handler = wire_onclick ? this.onNetworkClick.bind(this, network) : null; + const click_handler = wire_onclick ? this.onMenuOptionClick.bind(this, server, network) : null; - return
+ let key = server; + if (network !== null) { + key += '_' + network; + } + + return
{icon} {name}
; } + componentDidUpdate() { + if (this.state.expanded && this.inputTextBox) { + this.inputTextBox.focus(); + } + } + render() { - const current_value = this._optionForNetwork(this.state.selectedNetwork, false); + let current_value = this._makeMenuOption( + this.state.selectedServer, this.state.selectedNetwork, false + ); let menu; if (this.state.expanded) { - const menu_options = [this._optionForNetwork(null)]; - for (const network of this.props.config.networks) { - menu_options.push(this._optionForNetwork(network)); - } + const menu_options = this._getMenuOptions(); menu =
{menu_options}
; + current_value = } return
@@ -138,7 +204,7 @@ export default class NetworkDropdown extends React.Component { } NetworkDropdown.propTypes = { - onNetworkChange: React.PropTypes.func.isRequired, + onOptionChange: React.PropTypes.func.isRequired, config: React.PropTypes.object, }; diff --git a/src/skins/vector/css/vector-web/views/directory/NetworkDropdown.css b/src/skins/vector/css/vector-web/views/directory/NetworkDropdown.css index cc15280261..b500ffa212 100644 --- a/src/skins/vector/css/vector-web/views/directory/NetworkDropdown.css +++ b/src/skins/vector/css/vector-web/views/directory/NetworkDropdown.css @@ -52,6 +52,10 @@ limitations under the License. vertical-align: middle; } +input.mx_NetworkDropdown_networkoption, input.mx_NetworkDropdown_networkoption:focus { + border: 0; +} + .mx_NetworkDropdown_menu { position: absolute; left: -1px; diff --git a/src/skins/vector/img/network-matrix.svg b/src/skins/vector/img/network-matrix.svg new file mode 100644 index 0000000000..bb8278ae39 --- /dev/null +++ b/src/skins/vector/img/network-matrix.svg @@ -0,0 +1,14 @@ + + + + + + +