Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paginate Room Directory #2241

Merged
merged 9 commits into from
Sep 17, 2016
164 changes: 115 additions & 49 deletions src/components/structures/RoomDirectory.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var linkify = require('linkifyjs');
var linkifyString = require('linkifyjs/string');
var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
var sanitizeHtml = require('sanitize-html');
var q = require('q');

linkifyMatrix(linkify);

Expand All @@ -50,7 +51,6 @@ module.exports = React.createClass({
getInitialState: function() {
return {
publicRooms: [],
roomAlias: '',
loading: true,
filterByNetwork: null,
}
Expand All @@ -64,6 +64,10 @@ module.exports = React.createClass({
this.networkPatterns[network] = new RegExp(this.props.config.networkPatterns[network]);
}
}
this.nextBatch = null;
this.filterString = null;
this.filterTimeout = null;
this.scrollPanel = null;

// dis.dispatch({
// action: 'ui_opacity',
Expand All @@ -73,7 +77,7 @@ module.exports = React.createClass({
},

componentDidMount: function() {
this.getPublicRooms();
this.refreshRoomList();
},

componentWillUnmount: function() {
Expand All @@ -84,24 +88,48 @@ module.exports = React.createClass({
// });
},

getPublicRooms: function() {
var self = this;
MatrixClientPeg.get().publicRooms(function (err, data) {
if (err) {
self.setState({ loading: false });
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to get public room list",
description: err.message
});
refreshRoomList: function() {
this.nextBatch = null;
this.setState({
publicRooms: [],
loading: true,
});
this.getMoreRooms().done();
},

getMoreRooms: function() {
const my_filter_string = this.filterString;
const opts = {limit: 20};
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) => {
if (my_filter_string != this.filterString) {
// if the filter has changed since this request was sent,
// throw away the result (don't even clear the busy flag
// since we must still have a request in flight)
return;
}
else {
self.setState({
publicRooms: data.chunk,
loading: false,
});

this.nextBatch = data.next_batch;
this.setState((s) => {
s.publicRooms.push(...data.chunk);
s.loading = false;
return s;
});
return Boolean(data.next_batch);
}, (err) => {
if (my_filter_string != this.filterString) {
// as above: we don't care about errors for old
// requests either
return;
}
this.setState({ loading: false });
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to get public room list",
description: err.message
});
});
},

Expand Down Expand Up @@ -142,10 +170,10 @@ module.exports = React.createClass({
return MatrixClientPeg.get().deleteAlias(alias);
}).done(() => {
modal.close();
this.getPublicRooms();
this.refreshRoomList();
}, function(err) {
modal.close();
this.getPublicRooms();
this.refreshRoomList();
Modal.createDialog(ErrorDialog, {
title: "Failed to "+step,
description: err.toString()
Expand All @@ -167,9 +195,47 @@ module.exports = React.createClass({
onNetworkChange: function(network) {
this.setState({
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();
});
},

onFillRequest: function(backwards) {
if (backwards || !this.nextBatch) return q(false);

return this.getMoreRooms();
},

onFilterChange: function(ev) {
const alias = ev.target.value;

this.filterString = alias || null;

// don't send the request for a little bit,
// no point hammering the server with a
// request for every keystroke, let the
// user finish typing.
if (this.filterTimeout) {
clearTimeout(this.filterTimeout);
}
this.filterTimeout = setTimeout(() => {
this.filterTimeout = null;
this.refreshRoomList();
}, 300);
},

onFilterKeyUp: function(ev) {
if (ev.key == "Enter") {
this.showRoomAlias(ev.target.value);
}
},

showRoomAlias: function(alias) {
this.showRoom(null, alias);
},
Expand Down Expand Up @@ -214,23 +280,17 @@ module.exports = React.createClass({
dis.dispatch(payload);
},

getRows: function(filter) {
getRows: function() {
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');

if (!this.state.publicRooms) return [];

var rooms = this.state.publicRooms.filter((a) => {
// FIXME: if incrementally typing, keep narrowing down the search set
// 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) ||
(a.aliases && a.aliases[0].toLowerCase().search(filter.toLowerCase()) >= 0)) &&
a.num_joined_members > 0);
}).sort(function(a,b) {
return a.num_joined_members - b.num_joined_members;
return true;
});
var rows = [];
var self = this;
Expand Down Expand Up @@ -259,7 +319,7 @@ module.exports = React.createClass({
var topic = rooms[i].topic || '';
topic = linkifyString(sanitizeHtml(topic));

rows.unshift(
rows.push(
<tr key={ rooms[i].room_id }
onClick={self.onRoomClicked.bind(self, rooms[i])}
// cancel onMouseDown otherwise shift-clicking highlights text
Expand Down Expand Up @@ -289,12 +349,8 @@ module.exports = React.createClass({
return rows;
},

onKeyUp: function(ev) {
this.forceUpdate();
this.setState({ roomAlias : this.refs.roomAlias.value })
if (ev.key == "Enter") {
this.showRoomAlias(this.refs.roomAlias.value);
}
collectScrollPanel: function(element) {
this.scrollPanel = element;
},

/**
Expand All @@ -312,13 +368,27 @@ module.exports = React.createClass({
},

render: function() {
let content;
if (this.state.loading) {
var Loader = sdk.getComponent("elements.Spinner");
return (
<div className="mx_RoomDirectory">
<Loader />
</div>
);
const Loader = sdk.getComponent("elements.Spinner");
content = <div className="mx_RoomDirectory">
<Loader />
</div>;
} else {
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
content = <ScrollPanel ref={this.collectScrollPanel}
className="mx_RoomDirectory_tableWrapper"
onFillRequest={ this.onFillRequest }
stickyBottom={false}
startAtBottom={false}
onResize={function(){}}
>
<table ref="directory_table" className="mx_RoomDirectory_table">
<tbody>
{ this.getRows() }
</tbody>
</table>
</ScrollPanel>;
}

const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
Expand All @@ -328,16 +398,12 @@ module.exports = React.createClass({
<SimpleRoomHeader title="Directory" />
<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 type="text" placeholder="Find a room by keyword or room ID (#foo:matrix.org)"
className="mx_RoomDirectory_input" size="64" onChange={this.onFilterChange} onKeyUp={this.onFilterKeyUp}
/>
<NetworkDropdown config={this.props.config} onNetworkChange={this.onNetworkChange} />
</div>
<GeminiScrollbar className="mx_RoomDirectory_tableWrapper">
<table ref="directory_table" className="mx_RoomDirectory_table">
<tbody>
{ this.getRows(this.state.roomAlias) }
</tbody>
</table>
</GeminiScrollbar>
{content}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ limitations under the License.
-webkit-flex-direction: column;
}

.mx_RoomDirectory_list .mx_RoomView_messageListWrapper {
justify-content: flex-start;
-webkit-justify-content: flex-start;
}

.mx_RoomDirectory_listheader {
display: table;
width: 100%;
Expand Down
2 changes: 1 addition & 1 deletion test/app-tests/joining.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('joining a room', function () {
httpBackend.when('GET', '/pushrules').respond(200, {});
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
httpBackend.when('GET', '/sync').respond(200, {});
httpBackend.when('GET', '/publicRooms').respond(200, {chunk: []});
httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []});
httpBackend.when('GET', '/directory/room/'+encodeURIComponent(ROOM_ALIAS)).respond(200, { room_id: ROOM_ID });

// start with a logged-in client
Expand Down