Merge pull request #2750 from vector-im/dbkr/roomdir_remove_client_filter

Remove the client side filtering from the room dir
pull/2754/head
David Baker 2016-12-16 16:44:05 +00:00 committed by GitHub
commit 09f79b94dd
6 changed files with 147 additions and 362 deletions

View File

@ -81,33 +81,9 @@ You can configure the app by copying `config.sample.json` to
and https://vector.im. In future identity servers will be decentralised. and https://vector.im. In future identity servers will be decentralised.
1. `integrations_ui_url`: URL to the web interface for the integrations server. 1. `integrations_ui_url`: URL to the web interface for the integrations server.
1. `integrations_rest_url`: URL to the REST interface for the integrations server. 1. `integrations_rest_url`: URL to the REST interface for the integrations server.
1. `roomDirectory`: config for the public room directory. This section encodes behaviour 1. `roomDirectory`: config for the public room directory. This section is optional.
on the room directory screen for filtering the list by server / network type and joining
third party networks. This config section will disappear once APIs are available to
get this information for home servers. This section is optional.
1. `roomDirectory.servers`: List of other Home Servers' directories to include in the drop 1. `roomDirectory.servers`: List of other Home Servers' directories to include in the drop
down list. Optional. down list. Optional.
1. `roomDirectory.serverConfig`: Config for each server in `roomDirectory.servers`. Optional.
1. `roomDirectory.serverConfig.<server_name>.networks`: List of networks (named
in `roomDirectory.networks`) to include for this server. Optional. If set, this will
override any networks sent by the Home Server (eg. if ASes are configured).
1. `roomDirectory.networks`: config for each network type. Optional.
1. `roomDirectory.<network_type>.name`: Human-readable name for the network. Required.
1. `roomDirectory.<network_type>.protocol`: Protocol as given by the server in
`/_matrix/client/unstable/thirdparty/protocols` response. Required to be able to join
this type of third party network.
1. `roomDirectory.<network_type>.domain`: Domain as given by the server in
`/_matrix/client/unstable/thirdparty/protocols` response, if present. Required to be
able to join this type of third party network, if present in `thirdparty/protocols`.
1. `roomDirectory.<network_type>.portalRoomPattern`: Regular expression matching aliases
for portal rooms to locations on this network. Required.
1. `roomDirectory.<network_type>.icon`: URL to an icon to be displayed for this network. Required.
1. `roomDirectory.<network_type>.example`: Textual example of a location on this network,
eg. '#channel' for an IRC network. Optional.
1. `roomDirectory.<network_type>.nativePattern`: Regular expression that matches a
valid location on this network. This is used as a hint to the user to indicate
when a valid location has been entered so it's not necessary for this to be
exactly correct. Optional.
1. `update_base_url` (electron app only): HTTPS URL to a web server to download 1. `update_base_url` (electron app only): HTTPS URL to a web server to download
updates from. This should be the path to the directory containing `macos` updates from. This should be the path to the directory containing `macos`
and `win32` (for update packages, not installer packages). and `win32` (for update packages, not installer packages).

View File

@ -8,64 +8,6 @@
"roomDirectory": { "roomDirectory": {
"servers": [ "servers": [
"matrix.org" "matrix.org"
],
"serverConfig": {
"matrix.org": {
"networks": [
"_matrix",
"gitter",
"irc:freenode",
"irc:mozilla",
"irc:snoonet",
"irc:oftc"
] ]
} }
},
"networks": {
"gitter": {
"protocol": "gitter",
"portalRoomPattern": "#gitter_.*:matrix.org",
"name": "Gitter",
"icon": "//gitter.im/favicon.ico",
"example": "org/community",
"nativePattern": "[^\\s]+/[^\\s]+$"
},
"irc:freenode": {
"protocol": "irc",
"domain": "chat.freenode.net",
"portalRoomPattern": "#freenode_.*:matrix.org",
"name": "Freenode",
"icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
"example": "#channel",
"nativePattern": "^#[^\\s]+$"
},
"irc:mozilla": {
"protocol": "irc",
"domain": "irc.mozilla.org",
"portalRoomPattern": "#mozilla_.*:matrix.org",
"name": "Mozilla",
"icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
"example": "#channel",
"nativePattern": "^#[^\\s]+$"
},
"irc:snoonet": {
"protocol": "irc",
"domain": "ipv6-irc.snoonet.org",
"portalRoomPattern": "#_snoonet_.*:matrix.org",
"name": "Snoonet",
"icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
"example": "#channel",
"nativePattern": "^#[^\\s]+$"
},
"irc:oftc": {
"protocol": "irc",
"domain": "irc.oftc.net",
"portalRoomPattern": "#_oftc_.*:matrix.org",
"name": "OFTC",
"icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
"example": "#channel",
"nativePattern": "^#[^\\s]+$"
}
}
}
} }

View File

@ -31,6 +31,8 @@ var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
var sanitizeHtml = require('sanitize-html'); var sanitizeHtml = require('sanitize-html');
var q = require('q'); var q = require('q');
import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils';
linkifyMatrix(linkify); linkifyMatrix(linkify);
module.exports = React.createClass({ module.exports = React.createClass({
@ -42,9 +44,7 @@ module.exports = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
config: { config: {},
networks: [],
},
} }
}, },
@ -52,37 +52,26 @@ module.exports = React.createClass({
return { return {
publicRooms: [], publicRooms: [],
loading: true, loading: true,
network: null, protocolsLoading: true,
instance_id: null, instanceId: null,
includeAll: false,
roomServer: null, roomServer: null,
filterString: null, filterString: null,
} }
}, },
componentWillMount: function() { componentWillMount: function() {
// precompile Regexps
this.portalRoomPatterns = {};
this.nativePatterns = {};
if (this.props.config.networks) {
for (const network of Object.keys(this.props.config.networks)) {
const network_info = this.props.config.networks[network];
if (network_info.portalRoomPattern) {
this.portalRoomPatterns[network] = new RegExp(network_info.portalRoomPattern);
}
if (network_info.nativePattern) {
this.nativePatterns[network] = new RegExp(network_info.nativePattern);
}
}
}
this.nextBatch = null; this.nextBatch = null;
this.filterTimeout = null; this.filterTimeout = null;
this.scrollPanel = null; this.scrollPanel = null;
this.protocols = null; this.protocols = null;
this.setState({protocolsLoading: true});
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => { MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
this.protocols = response; this.protocols = response;
this.setState({protocolsLoading: false});
}, (err) => { }, (err) => {
this.setState({protocolsLoading: false});
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
// Guests currently aren't allowed to use this API, so // Guests currently aren't allowed to use this API, so
// ignore this as otherwise this error is literally the // ignore this as otherwise this error is literally the
@ -132,9 +121,9 @@ module.exports = React.createClass({
if (my_server != MatrixClientPeg.getHomeServerName()) { if (my_server != MatrixClientPeg.getHomeServerName()) {
opts.server = my_server; opts.server = my_server;
} }
if (this.state.instance_id) { if (this.state.instanceId) {
opts.third_party_instance_id = this.state.instance_id; opts.third_party_instance_id = this.state.instanceId;
} else if (this.state.network !== '_matrix') { } else if (this.state.includeAll) {
opts.include_all_networks = true; opts.include_all_networks = true;
} }
if (this.nextBatch) opts.since = this.nextBatch; if (this.nextBatch) opts.since = this.nextBatch;
@ -237,7 +226,7 @@ module.exports = React.createClass({
} }
}, },
onOptionChange: function(server, network, instance_id) { onOptionChange: function(server, instanceId, includeAll) {
// clear next batch so we don't try to load more rooms // clear next batch so we don't try to load more rooms
this.nextBatch = null; this.nextBatch = null;
this.setState({ this.setState({
@ -246,8 +235,8 @@ module.exports = React.createClass({
// to clear the list anyway. // to clear the list anyway.
publicRooms: [], publicRooms: [],
roomServer: server, roomServer: server,
network: network, instanceId: instanceId,
instance_id: instance_id, includeAll: includeAll,
}, this.refreshRoomList); }, this.refreshRoomList);
// We also refresh the room list each time even though this // We also refresh the room list each time even though this
// filtering is client-side. It hopefully won't be client side // filtering is client-side. It hopefully won't be client side
@ -278,7 +267,7 @@ module.exports = React.createClass({
this.filterTimeout = setTimeout(() => { this.filterTimeout = setTimeout(() => {
this.filterTimeout = null; this.filterTimeout = null;
this.refreshRoomList(); this.refreshRoomList();
}, 300); }, 700);
}, },
onFilterClear: function() { onFilterClear: function() {
@ -293,14 +282,19 @@ module.exports = React.createClass({
}, },
onJoinClick: function(alias) { onJoinClick: function(alias) {
// If we're on the 'Matrix' network (or all networks), // If we don't have a particular instance id selected, just show that rooms alias
// just show that rooms alias if (!this.state.instanceId) {
if (this.state.network == null || this.state.network == '_matrix') { // If the user specified an alias without a domain, add on whichever server is selected
// in the dropdown
if (alias.indexOf(':') == -1) {
alias = alias + ':' + this.state.roomServer;
}
this.showRoomAlias(alias); this.showRoomAlias(alias);
} else { } else {
// This is a 3rd party protocol. Let's see if we // This is a 3rd party protocol. Let's see if we can join it
// can join it const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
const fields = this._getFieldsForThirdPartyLocation(alias, this.state.network); const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
const fields = protocolName ? this._getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance) : null;
if (!fields) { if (!fields) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
@ -309,8 +303,7 @@ module.exports = React.createClass({
}); });
return; return;
} }
const protocol = this._protocolForThirdPartyNetwork(this.state.network); MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => {
MatrixClientPeg.get().getThirdpartyLocation(protocol, fields).done((resp) => {
if (resp.length > 0 && resp[0].alias) { if (resp.length > 0 && resp[0].alias) {
this.showRoomAlias(resp[0].alias); this.showRoomAlias(resp[0].alias);
} else { } else {
@ -379,13 +372,7 @@ module.exports = React.createClass({
if (!this.state.publicRooms) return []; if (!this.state.publicRooms) return [];
var rooms = this.state.publicRooms.filter((a) => { var rooms = this.state.publicRooms;
if (this.state.network) {
if (!this._isRoomInNetwork(a, this.state.roomServer, this.state.network)) return false;
}
return true;
});
var rows = []; var rows = [];
var self = this; var self = this;
var guestRead, guestJoin, perms; var guestRead, guestJoin, perms;
@ -447,119 +434,46 @@ module.exports = React.createClass({
this.scrollPanel = element; this.scrollPanel = element;
}, },
/** _stringLooksLikeId: function(s, field_type) {
* Terrible temporary function that guess what network a public room
* entry is in, until synapse is able to tell us
*/
_isRoomInNetwork: function(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. Its 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.portalRoomPatterns[n];
if (pat && pat.test(room.aliases[0])) {
roomNetwork = n;
}
}
}
}
return roomNetwork == network;
},
_stringLooksLikeId: function(s, network) {
let pat = /^#[^\s]+:[^\s]/; let pat = /^#[^\s]+:[^\s]/;
if ( if (field_type && field_type.regexp) {
network && network != '_matrix' && pat = new RegExp(field_type.regexp);
this.nativePatterns[network]
) {
pat = this.nativePatterns[network];
} }
return pat.test(s); return pat.test(s);
}, },
_protocolForThirdPartyNetwork: function(network) { _getFieldsForThirdPartyLocation: function(userInput, protocol, instance) {
if ( // make an object with the fields specified by that protocol. We
this.props.config.networks &&
this.props.config.networks[network] &&
this.props.config.networks[network].protocol
) {
return this.props.config.networks[network].protocol;
}
},
_getFieldsForThirdPartyLocation: function(user_input, network) {
if (!this.props.config.networks || !this.props.config.networks[network]) return null;
const network_info = this.props.config.networks[network];
if (!network_info.protocol) return null;
if (!this.protocols) return null;
let matched_instance;
// Try to find which instance in the 'protocols' response
// matches this network. We look for a matching protocol
// and the existence of a 'domain' field and if present,
// its value.
if (
this.protocols[network_info.protocol] &&
this.protocols[network_info.protocol].instances &&
this.protocols[network_info.protocol].instances.length == 1
) {
const the_instance = this.protocols[network_info.protocol].instances[0];
// If there's only one instance in this protocol, use it
// as long as it has no domain (which we assume to mean it's
// there is only one possible instance).
if (
(
the_instance.fields.domain === undefined &&
network_info.domain === undefined
) ||
(
the_instance.fields.domain !== undefined &&
the_instance.fields.domain == network_info.domain
)
) {
matched_instance = the_instance;
}
} else if (network_info.domain) {
// otherwise, we look for one with a matching domain.
for (const this_instance of this.protocols[network_info.protocol].instances) {
if (this_instance.fields.domain == network_info.domain) {
matched_instance = this_instance;
}
}
}
if (matched_instance === undefined) return null;
// now make an object with the fields specified by that protocol. We
// require that the values of all but the last field come from the // require that the values of all but the last field come from the
// instance. The last is the user input. // instance. The last is the user input.
const required_fields = this.protocols[network_info.protocol].location_fields; const requiredFields = protocol.location_fields;
if (!requiredFields) return null;
const fields = {}; const fields = {};
for (let i = 0; i < required_fields.length - 1; ++i) { for (let i = 0; i < requiredFields.length - 1; ++i) {
const this_field = required_fields[i]; const thisField = requiredFields[i];
if (matched_instance.fields[this_field] === undefined) return null; if (instance.fields[thisField] === undefined) return null;
fields[this_field] = matched_instance.fields[this_field]; fields[thisField] = instance.fields[thisField];
} }
fields[required_fields[required_fields.length - 1]] = user_input; fields[requiredFields[requiredFields.length - 1]] = userInput;
return fields; return fields;
}, },
render: function() { render: function() {
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const Loader = sdk.getComponent("elements.Spinner");
if (this.state.protocolsLoading) {
return (
<div className="mx_RoomDirectory">
<SimpleRoomHeader title="Directory" />
<Loader />
</div>
);
}
let content; let content;
if (this.state.loading) { if (this.state.loading) {
const Loader = sdk.getComponent("elements.Spinner");
content = <div className="mx_RoomDirectory"> content = <div className="mx_RoomDirectory">
<Loader /> <Loader />
</div>; </div>;
@ -590,26 +504,35 @@ module.exports = React.createClass({
</ScrollPanel>; </ScrollPanel>;
} }
let placeholder = 'Search for a room'; const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
if (this.state.network === null || this.state.network === '_matrix') { let instance_expected_field_type;
placeholder = '#example:' + this.state.roomServer; if (
} else if ( protocolName &&
this.props.config.networks && this.protocols &&
this.props.config.networks[this.state.network] && this.protocols[protocolName] &&
this.props.config.networks[this.state.network].example && this.protocols[protocolName].location_fields.length > 0 &&
this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network) this.protocols[protocolName].field_types
) { ) {
placeholder = this.props.config.networks[this.state.network].example; const last_field = this.protocols[protocolName].location_fields.slice(-1)[0];
instance_expected_field_type = this.protocols[protocolName].field_types[last_field];
} }
let showJoinButton = this._stringLooksLikeId(this.state.filterString, this.state.network);
if (this.state.network && this.state.network != '_matrix') { let placeholder = 'Search for a room';
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network) === null) { if (!this.state.instanceId) {
placeholder = '#example:' + this.state.roomServer;
} else if (instance_expected_field_type) {
placeholder = instance_expected_field_type.placeholder;
}
let showJoinButton = this._stringLooksLikeId(this.state.filterString, instance_expected_field_type);
if (protocolName) {
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.protocols[protocolName], instance) === null) {
showJoinButton = false; showJoinButton = false;
} }
} }
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown'); const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox'); const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
return ( return (

View File

@ -16,6 +16,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import {instanceForInstanceId} from '../../../utils/DirectoryUtils';
const DEFAULT_ICON_URL = "img/network-matrix.svg"; const DEFAULT_ICON_URL = "img/network-matrix.svg";
@ -30,7 +31,6 @@ export default class NetworkDropdown extends React.Component {
this.onRootClick = this.onRootClick.bind(this); this.onRootClick = this.onRootClick.bind(this);
this.onDocumentClick = this.onDocumentClick.bind(this); this.onDocumentClick = this.onDocumentClick.bind(this);
this.onMenuOptionClick = this.onMenuOptionClick.bind(this); this.onMenuOptionClick = this.onMenuOptionClick.bind(this);
this.onMenuOptionClickProtocolInstance = this.onMenuOptionClickProtocolInstance.bind(this);
this.onInputKeyUp = this.onInputKeyUp.bind(this); this.onInputKeyUp = this.onInputKeyUp.bind(this);
this.collectRoot = this.collectRoot.bind(this); this.collectRoot = this.collectRoot.bind(this);
this.collectInputTextBox = this.collectInputTextBox.bind(this); this.collectInputTextBox = this.collectInputTextBox.bind(this);
@ -38,20 +38,11 @@ export default class NetworkDropdown extends React.Component {
this.inputTextBox = null; this.inputTextBox = null;
const server = MatrixClientPeg.getHomeServerName(); const server = MatrixClientPeg.getHomeServerName();
let defaultNetwork = null;
if (
this.props.config.serverConfig &&
this.props.config.serverConfig[server] &&
this.props.config.serverConfig[server].networks &&
this.props.config.serverConfig[server].networks.indexOf('_matrix') > -1
) {
defaultNetwork = '_matrix';
}
this.state = { this.state = {
expanded: false, expanded: false,
selectedServer: server, selectedServer: server,
selectedNetwork: defaultNetwork, selectedInstance: null,
includeAllNetworks: false,
}; };
} }
@ -61,7 +52,7 @@ export default class NetworkDropdown extends React.Component {
document.addEventListener('click', this.onDocumentClick, false); document.addEventListener('click', this.onDocumentClick, false);
// fire this now so the defaults can be set up // fire this now so the defaults can be set up
this.props.onOptionChange(this.state.selectedServer, this.state.selectedNetwork); this.props.onOptionChange(this.state.selectedServer, this.state.selectedInstance, this.state.includeAllNetworks);
} }
componentWillUnmount() { componentWillUnmount() {
@ -101,24 +92,14 @@ export default class NetworkDropdown extends React.Component {
ev.preventDefault(); ev.preventDefault();
} }
onMenuOptionClick(server, network) { onMenuOptionClick(server, instance, includeAll) {
this.setState({ this.setState({
expanded: false, expanded: false,
selectedServer: server, selectedServer: server,
selectedNetwork: network, selectedInstanceId: instance ? instance.instance_id : null,
selectedInstanceId: null, includeAll: includeAll,
}); });
this.props.onOptionChange(server, network); this.props.onOptionChange(server, instance ? instance.instance_id : null, includeAll);
}
onMenuOptionClickProtocolInstance(server, instance_id) {
this.setState({
expanded: false,
selectedServer: server,
selectedNetwork: null,
selectedInstanceId: instance_id,
});
this.props.onOptionChange(server, null, instance_id);
} }
onInputKeyUp(e) { onInputKeyUp(e) {
@ -158,33 +139,21 @@ export default class NetworkDropdown extends React.Component {
servers.unshift(MatrixClientPeg.getHomeServerName()); servers.unshift(MatrixClientPeg.getHomeServerName());
} }
// if the thirdparty/protocols entries have instance_ids,
// we can get the local server listings from here. If not,
// the server is too old.
let use_protocols = true;
for (const proto of Object.keys(this.props.protocols)) {
if (!this.props.protocols[proto].instances) continue;
for (const instance of this.props.protocols[proto].instances) {
if (!instance.instance_id) use_protocols = false;
}
}
// For our own HS, we can use the instance_ids given in the third party protocols // For our own HS, we can use the instance_ids given in the third party protocols
// response to get the server to filter the room list by network for us (if the // response to get the server to filter the room list by network for us.
// server is new enough), although for now we prefer the config if it exists. // We can't get thirdparty protocols for remote server yet though, so for those
// For remote HSes, we use the data from the config. // we can only show the default room list.
for (const server of servers) { for (const server of servers) {
options.push(this._makeMenuOption(server, null)); options.push(this._makeMenuOption(server, null, true));
if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) { if (server == MatrixClientPeg.getHomeServerName()) {
for (const network of this.props.config.serverConfig[server].networks) { options.push(this._makeMenuOption(server, null, false));
options.push(this._makeMenuOption(server, network)); if (this.props.protocols) {
}
} else if (server == MatrixClientPeg.getHomeServerName() && use_protocols) {
options.push(this._makeMenuOption(server, '_matrix'));
for (const proto of Object.keys(this.props.protocols)) { for (const proto of Object.keys(this.props.protocols)) {
if (!this.props.protocols[proto].instances) continue; if (!this.props.protocols[proto].instances) continue;
for (const instance of this.props.protocols[proto].instances) { for (const instance of this.props.protocols[proto].instances) {
options.push(this._makeMenuOptionFromProtocolInstance(server, this.props.protocols[proto], instance)); if (!instance.instance_id) continue;
options.push(this._makeMenuOption(server, instance, false));
}
} }
} }
} }
@ -193,82 +162,36 @@ export default class NetworkDropdown extends React.Component {
return options; return options;
} }
_makeMenuOptionFromProtocolInstance(server, protocol, instance, handleClicks) { _makeMenuOption(server, instance, includeAll, handleClicks) {
if (handleClicks === undefined) handleClicks = true; if (handleClicks === undefined) handleClicks = true;
const name = instance.desc;
const icon = <img src={instance.icon || DEFAULT_ICON_URL} />;
const key = instance.instance_id;
const click_handler = handleClicks ? this.onMenuOptionClickProtocolInstance.bind(this, server, instance.instance_id) : null;
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
{icon}
<span className="mx_NetworkDropdown_menu_network">{name}</span>
</div>;
}
_makeMenuOption(server, network, handleClicks) {
if (handleClicks === undefined) handleClicks = true;
let icon; let icon;
let name; let name;
let span_class; let span_class;
let key;
if (network === null) { if (!instance && includeAll) {
key = server;
name = server; name = server;
span_class = 'mx_NetworkDropdown_menu_all'; span_class = 'mx_NetworkDropdown_menu_all';
} else if (network == '_matrix') { } else if (!instance) {
key = server + '_all';
name = 'Matrix'; name = 'Matrix';
icon = <img src="img/network-matrix.svg" />; icon = <img src="img/network-matrix.svg" />;
span_class = 'mx_NetworkDropdown_menu_network'; span_class = 'mx_NetworkDropdown_menu_network';
} else { } else {
if (this.props.config.networks[network] === undefined) { key = server + '_inst_' + instance.instance_id;
throw new Error(network + ' network missing from config'); icon = <img src={instance.icon || DEFAULT_ICON_URL} />;
} name = instance.desc;
if (this.props.config.networks[network].name) {
name = this.props.config.networks[network].name;
} else {
name = network;
}
if (this.props.config.networks[network].icon) {
// omit height here so if people define a non-square logo in the config, it
// will keep the aspect when it scales
icon = <img src={this.props.config.networks[network].icon} />;
} else {
icon = <img src={iconPath} />;
}
span_class = 'mx_NetworkDropdown_menu_network'; span_class = 'mx_NetworkDropdown_menu_network';
} }
const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, network) : null; const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, instance, includeAll) : null;
let key = server;
if (network !== null) {
key += '_' + network;
}
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}> return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
{icon} {icon}
<span className={span_class}>{name}</span> <span className="mx_NetworkDropdown_menu_network">{name}</span>
</div>; </div>
}
_protocolNameForInstanceId(instance_id) {
for (const proto of Object.keys(this.props.protocols)) {
if (!this.props.protocols[proto].instances) continue;
for (const instance of this.props.protocols[proto].instances) {
if (instance.instance_id == instance_id) return proto;
}
}
}
instanceForInstanceId(instance_id) {
for (const proto of Object.keys(this.props.protocols)) {
if (!this.props.protocols[proto].instances) continue;
for (const instance of this.props.protocols[proto].instances) {
if (instance.instance_id == instance_id) return instance;
}
}
} }
render() { render() {
@ -285,18 +208,11 @@ export default class NetworkDropdown extends React.Component {
placeholder="matrix.org" // 'matrix.org' as an example of an HS name placeholder="matrix.org" // 'matrix.org' as an example of an HS name
/> />
} else { } else {
if (this.state.selectedInstanceId) { const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
const protocolName = this._protocolNameForInstanceId(this.state.selectedInstanceId);
const instance = this.instanceForInstanceId(this.state.selectedInstanceId);
current_value = this._makeMenuOptionFromProtocolInstance(
this.state.selectedServer, this.props.protocols[protocolName], instance, false
);
} else {
current_value = this._makeMenuOption( current_value = this._makeMenuOption(
this.state.selectedServer, this.state.selectedNetwork, false this.state.selectedServer, instance, this.state.includeAll, false
); );
} }
}
return <div className="mx_NetworkDropdown" ref={this.collectRoot}> return <div className="mx_NetworkDropdown" ref={this.collectRoot}>
<div className="mx_NetworkDropdown_input" onClick={this.onInputClick}> <div className="mx_NetworkDropdown_input" onClick={this.onInputClick}>
@ -310,14 +226,12 @@ export default class NetworkDropdown extends React.Component {
NetworkDropdown.propTypes = { NetworkDropdown.propTypes = {
onOptionChange: React.PropTypes.func.isRequired, onOptionChange: React.PropTypes.func.isRequired,
config: React.PropTypes.object,
protocols: React.PropTypes.object, protocols: React.PropTypes.object,
// The room directory config. May have a 'servers' key that is a list of server names to include in the dropdown
config: React.PropTypes.object,
}; };
NetworkDropdown.defaultProps = { NetworkDropdown.defaultProps = {
config: {
networks: [],
},
protocols: {}, protocols: {},
config: {},
}; };

View File

@ -0,0 +1,23 @@
// Find a protocol 'instance' with a given instance_id
// in the supplied protocols dict
export function instanceForInstanceId(protocols, instance_id) {
if (!instance_id) return null;
for (const proto of Object.keys(protocols)) {
if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue;
for (const instance of protocols[proto].instances) {
if (instance.instance_id == instance_id) return instance;
}
}
}
// given an instance_id, return the name of the protocol for
// that instance ID in the supplied protocols dict
export function protocolNameForInstanceId(protocols, instance_id) {
if (!instance_id) return null;
for (const proto of Object.keys(protocols)) {
if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue;
for (const instance of protocols[proto].instances) {
if (instance.instance_id == instance_id) return proto;
}
}
}

View File

@ -77,6 +77,7 @@ describe('joining a room', function () {
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' }); httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
httpBackend.when('GET', '/sync').respond(200, {}); httpBackend.when('GET', '/sync').respond(200, {});
httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []}); httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []});
httpBackend.when('GET', '/thirdparty/protocols').respond(200, {});
httpBackend.when('GET', '/directory/room/'+encodeURIComponent(ROOM_ALIAS)).respond(200, { room_id: ROOM_ID }); httpBackend.when('GET', '/directory/room/'+encodeURIComponent(ROOM_ALIAS)).respond(200, { room_id: ROOM_ID });
// start with a logged-in client // start with a logged-in client
@ -132,6 +133,12 @@ describe('joining a room', function () {
httpBackend.when('POST', '/join/'+encodeURIComponent(ROOM_ALIAS)) httpBackend.when('POST', '/join/'+encodeURIComponent(ROOM_ALIAS))
.respond(200, {room_id: ROOM_ID}); .respond(200, {room_id: ROOM_ID});
return httpBackend.flush(); return httpBackend.flush();
}).then(() => {
// wait for the join request to be made
return q.delay(1);
}).then(() => {
// flush it through
return httpBackend.flush();
}).then(() => { }).then(() => {
httpBackend.verifyNoOutstandingExpectation(); httpBackend.verifyNoOutstandingExpectation();