Merge pull request #2219 from vector-im/dbkr/directory_network_selector
Directory network selector
This commit is contained in:
commit
135c22c99d
6 changed files with 325 additions and 7 deletions
|
@ -38,6 +38,7 @@ module.exports.components['views.context_menus.MessageContextMenu'] = require('.
|
||||||
module.exports.components['views.context_menus.NotificationStateContextMenu'] = require('./components/views/context_menus/NotificationStateContextMenu');
|
module.exports.components['views.context_menus.NotificationStateContextMenu'] = require('./components/views/context_menus/NotificationStateContextMenu');
|
||||||
module.exports.components['views.context_menus.RoomTagContextMenu'] = require('./components/views/context_menus/RoomTagContextMenu');
|
module.exports.components['views.context_menus.RoomTagContextMenu'] = require('./components/views/context_menus/RoomTagContextMenu');
|
||||||
module.exports.components['views.dialogs.ChangelogDialog'] = require('./components/views/dialogs/ChangelogDialog');
|
module.exports.components['views.dialogs.ChangelogDialog'] = require('./components/views/dialogs/ChangelogDialog');
|
||||||
|
module.exports.components['views.directory.NetworkDropdown'] = require('./components/views/directory/NetworkDropdown');
|
||||||
module.exports.components['views.elements.ImageView'] = require('./components/views/elements/ImageView');
|
module.exports.components['views.elements.ImageView'] = require('./components/views/elements/ImageView');
|
||||||
module.exports.components['views.elements.Spinner'] = require('./components/views/elements/Spinner');
|
module.exports.components['views.elements.Spinner'] = require('./components/views/elements/Spinner');
|
||||||
module.exports.components['views.globals.GuestWarningBar'] = require('./components/views/globals/GuestWarningBar');
|
module.exports.components['views.globals.GuestWarningBar'] = require('./components/views/globals/GuestWarningBar');
|
||||||
|
|
|
@ -35,15 +35,36 @@ linkifyMatrix(linkify);
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomDirectory',
|
displayName: 'RoomDirectory',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
config: React.PropTypes.object,
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
config: {
|
||||||
|
networks: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
publicRooms: [],
|
publicRooms: [],
|
||||||
roomAlias: '',
|
roomAlias: '',
|
||||||
loading: true,
|
loading: true,
|
||||||
|
filterByNetwork: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
// precompile Regexps
|
||||||
|
this.networkPatterns = {};
|
||||||
|
if (this.props.config.networkPatterns) {
|
||||||
|
for (const network of Object.keys(this.props.config.networkPatterns)) {
|
||||||
|
this.networkPatterns[network] = new RegExp(this.props.config.networkPatterns[network]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dis.dispatch({
|
// dis.dispatch({
|
||||||
// action: 'ui_opacity',
|
// action: 'ui_opacity',
|
||||||
// sideOpacity: 0.3,
|
// sideOpacity: 0.3,
|
||||||
|
@ -143,6 +164,12 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onNetworkChange: function(network) {
|
||||||
|
this.setState({
|
||||||
|
filterByNetwork: network,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
showRoomAlias: function(alias) {
|
showRoomAlias: function(alias) {
|
||||||
this.showRoom(null, alias);
|
this.showRoom(null, alias);
|
||||||
},
|
},
|
||||||
|
@ -192,9 +219,13 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
if (!this.state.publicRooms) return [];
|
if (!this.state.publicRooms) return [];
|
||||||
|
|
||||||
var rooms = this.state.publicRooms.filter(function(a) {
|
var rooms = this.state.publicRooms.filter((a) => {
|
||||||
// FIXME: if incrementally typing, keep narrowing down the search set
|
// FIXME: if incrementally typing, keep narrowing down the search set
|
||||||
// incrementally rather than starting over each time.
|
// incrementally rather than starting over each time.
|
||||||
|
if (this.state.filterByNetwork) {
|
||||||
|
if (!this._isRoomInNetwork(a, this.state.filterByNetwork)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
return (((a.name && a.name.toLowerCase().search(filter.toLowerCase()) >= 0) ||
|
return (((a.name && a.name.toLowerCase().search(filter.toLowerCase()) >= 0) ||
|
||||||
(a.aliases && a.aliases[0].toLowerCase().search(filter.toLowerCase()) >= 0)) &&
|
(a.aliases && a.aliases[0].toLowerCase().search(filter.toLowerCase()) >= 0)) &&
|
||||||
a.num_joined_members > 0);
|
a.num_joined_members > 0);
|
||||||
|
@ -266,6 +297,20 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
var Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
@ -276,12 +321,16 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||||
|
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomDirectory">
|
<div className="mx_RoomDirectory">
|
||||||
<SimpleRoomHeader title="Directory" />
|
<SimpleRoomHeader title="Directory" />
|
||||||
<div className="mx_RoomDirectory_list">
|
<div className="mx_RoomDirectory_list">
|
||||||
|
<div className="mx_RoomDirectory_listheader">
|
||||||
<input ref="roomAlias" placeholder="Join a room (e.g. #foo:domain.com)" className="mx_RoomDirectory_input" size="64" onKeyUp={ this.onKeyUp }/>
|
<input ref="roomAlias" placeholder="Join a room (e.g. #foo:domain.com)" className="mx_RoomDirectory_input" size="64" onKeyUp={ this.onKeyUp }/>
|
||||||
|
<NetworkDropdown config={this.props.config} onNetworkChange={this.onNetworkChange} />
|
||||||
|
</div>
|
||||||
<GeminiScrollbar className="mx_RoomDirectory_tableWrapper">
|
<GeminiScrollbar className="mx_RoomDirectory_tableWrapper">
|
||||||
<table ref="directory_table" className="mx_RoomDirectory_table">
|
<table ref="directory_table" className="mx_RoomDirectory_table">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
150
src/components/views/directory/NetworkDropdown.js
Normal file
150
src/components/views/directory/NetworkDropdown.js
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default class NetworkDropdown extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.dropdownRootElement = null;
|
||||||
|
this.ignoreEvent = null;
|
||||||
|
|
||||||
|
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.collectRoot = this.collectRoot.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
expanded: false,
|
||||||
|
selectedNetwork: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('click', this.onDocumentClick, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDocumentClick(ev) {
|
||||||
|
// Close the dropdown if the user clicks anywhere that isn't
|
||||||
|
// within our root element
|
||||||
|
if (ev !== this.ignoreEvent) {
|
||||||
|
this.setState({
|
||||||
|
expanded: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onRootClick(ev) {
|
||||||
|
// This captures any clicks that happen within our elements,
|
||||||
|
// such that we can then ignore them when they're seen by the
|
||||||
|
// click listener on the document handler, ie. not close the
|
||||||
|
// dropdown immediately after opening it.
|
||||||
|
// NB. We can't just stopPropagation() because then the event
|
||||||
|
// doesn't reach the React onClick().
|
||||||
|
this.ignoreEvent = ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
onInputClick(ev) {
|
||||||
|
this.setState({
|
||||||
|
expanded: !this.state.expanded,
|
||||||
|
});
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
onNetworkClick(network, ev) {
|
||||||
|
this.setState({
|
||||||
|
expanded: false,
|
||||||
|
selectedNetwork: network,
|
||||||
|
});
|
||||||
|
this.props.onNetworkChange(network);
|
||||||
|
}
|
||||||
|
|
||||||
|
collectRoot(e) {
|
||||||
|
if (this.dropdownRootElement) {
|
||||||
|
this.dropdownRootElement.removeEventListener('click', this.onRootClick, false);
|
||||||
|
}
|
||||||
|
if (e) {
|
||||||
|
e.addEventListener('click', this.onRootClick, false);
|
||||||
|
}
|
||||||
|
this.dropdownRootElement = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
_optionForNetwork(network, wire_onclick) {
|
||||||
|
if (wire_onclick === undefined) wire_onclick = true;
|
||||||
|
let icon;
|
||||||
|
let name;
|
||||||
|
let span_class;
|
||||||
|
|
||||||
|
if (network === null) {
|
||||||
|
name = 'All networks';
|
||||||
|
span_class = 'mx_NetworkDropdown_menu_all';
|
||||||
|
} else {
|
||||||
|
name = this.props.config.networkNames[network];
|
||||||
|
icon = <img src={this.props.config.networkIcons[network]} />;
|
||||||
|
span_class = 'mx_NetworkDropdown_menu_network';
|
||||||
|
}
|
||||||
|
|
||||||
|
const click_handler = wire_onclick ? this.onNetworkClick.bind(this, network) : null;
|
||||||
|
|
||||||
|
return <div key={network} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
|
||||||
|
{icon}
|
||||||
|
<span className={span_class}>{name}</span>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const current_value = this._optionForNetwork(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));
|
||||||
|
}
|
||||||
|
menu = <div className="mx_NetworkDropdown_menu">
|
||||||
|
{menu_options}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="mx_NetworkDropdown" ref={this.collectRoot}>
|
||||||
|
<div className="mx_NetworkDropdown_input" onClick={this.onInputClick}>
|
||||||
|
{current_value}
|
||||||
|
<span className="mx_NetworkDropdown_arrow"></span>
|
||||||
|
{menu}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkDropdown.propTypes = {
|
||||||
|
onNetworkChange: React.PropTypes.func.isRequired,
|
||||||
|
config: React.PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
NetworkDropdown.defaultProps = {
|
||||||
|
config: {
|
||||||
|
networks: [],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -46,15 +46,26 @@ limitations under the License.
|
||||||
-webkit-flex-direction: column;
|
-webkit-flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomDirectory_listheader {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-spacing: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_input {
|
.mx_RoomDirectory_input {
|
||||||
margin: auto;
|
display: table-cell;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid #c7c7c7;
|
border: 1px solid #c7c7c7;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
padding: 9px;
|
padding: 9px;
|
||||||
margin-top: 12px;
|
}
|
||||||
margin-bottom: 12px;
|
|
||||||
|
.mx_RoomDirectory_listheader .mx_NetworkDropdown {
|
||||||
|
display: table-cell;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_tableWrapper {
|
.mx_RoomDirectory_tableWrapper {
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_NetworkDropdown {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_input {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #c7c7c7;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_arrow {
|
||||||
|
border-color: #4a4a4a transparent transparent;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 5px 5px 0;
|
||||||
|
display: block;
|
||||||
|
height: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 14px;
|
||||||
|
width: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_networkoption {
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_networkoption img {
|
||||||
|
margin: 5px;
|
||||||
|
width: 25px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_menu {
|
||||||
|
position: absolute;
|
||||||
|
left: -1px;
|
||||||
|
right: -1px;
|
||||||
|
top: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #76cfa6;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_menu .mx_NetworkDropdown_networkoption:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_menu_network {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
|
@ -4,5 +4,35 @@
|
||||||
"brand": "Vector",
|
"brand": "Vector",
|
||||||
"integrations_ui_url": "http://localhost:8081/",
|
"integrations_ui_url": "http://localhost:8081/",
|
||||||
"integrations_rest_url": "http://localhost:5050",
|
"integrations_rest_url": "http://localhost:5050",
|
||||||
"enableLabs": true
|
"enableLabs": true,
|
||||||
|
"roomDirectory": {
|
||||||
|
"networks": [
|
||||||
|
"matrix:example_com",
|
||||||
|
"matrix:matrix_org",
|
||||||
|
"gitter",
|
||||||
|
"irc:freenode",
|
||||||
|
"irc:mozilla"
|
||||||
|
],
|
||||||
|
"networkPatterns": {
|
||||||
|
"matrix:example_com": "#.*:example.com",
|
||||||
|
"matrix:matrix_org": "#.*:matrix.org",
|
||||||
|
"gitter": "#gitter_.*:matrix.org",
|
||||||
|
"irc:freenode": "#freenode_.*:matrix.org",
|
||||||
|
"irc:mozilla": "#mozilla_.*:matrix.org"
|
||||||
|
},
|
||||||
|
"networkNames": {
|
||||||
|
"matrix:example_com": "example.com",
|
||||||
|
"matrix:matrix_org": "matrix.org",
|
||||||
|
"irc:freenode": "Freenode",
|
||||||
|
"irc:mozilla": "Mozilla",
|
||||||
|
"gitter": "Gitter"
|
||||||
|
},
|
||||||
|
"networkIcons": {
|
||||||
|
"matrix:example_com": "//matrix.org/favicon.ico",
|
||||||
|
"matrix:matrix_org": "//matrix.org/favicon.ico",
|
||||||
|
"irc:freenode": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||||
|
"irc:mozilla": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||||
|
"gitter": "//gitter.im/favicon.ico"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue