Merge pull request #1412 from matrix-org/dbkr/truncatedlist_experiment
Allow TruncatedList to get children via a callback
This commit is contained in:
commit
a868fa4be9
2 changed files with 93 additions and 44 deletions
|
@ -27,6 +27,15 @@ module.exports = React.createClass({
|
||||||
truncateAt: PropTypes.number,
|
truncateAt: PropTypes.number,
|
||||||
// The className to apply to the wrapping div
|
// The className to apply to the wrapping div
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
// A function that returns the children to be rendered into the element.
|
||||||
|
// function getChildren(start: number, end: number): Array<React.Node>
|
||||||
|
// The start element is included, the end is not (as in `slice`).
|
||||||
|
// If omitted, the React child elements will be used. This parameter can be used
|
||||||
|
// to avoid creating unnecessary React elements.
|
||||||
|
getChildren: PropTypes.func,
|
||||||
|
// A function that should return the total number of child element available.
|
||||||
|
// Required if getChildren is supplied.
|
||||||
|
getChildCount: PropTypes.func,
|
||||||
// A function which will be invoked when an overflow element is required.
|
// A function which will be invoked when an overflow element is required.
|
||||||
// This will be inserted after the children.
|
// This will be inserted after the children.
|
||||||
createOverflowElement: PropTypes.func,
|
createOverflowElement: PropTypes.func,
|
||||||
|
@ -43,33 +52,49 @@ module.exports = React.createClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
_getChildren: function(start, end) {
|
||||||
let childsJsx = this.props.children;
|
if (this.props.getChildren && this.props.getChildCount) {
|
||||||
let overflowJsx;
|
return this.props.getChildren(start, end);
|
||||||
const childArray = React.Children.toArray(this.props.children).filter((c) => {
|
} else {
|
||||||
|
// XXX: I'm not sure why anything would pass null into this, it seems
|
||||||
|
// like a bizzare case to handle, but I'm preserving the behaviour.
|
||||||
|
// (see commit 38d5c7d5c5d5a34dc16ef5d46278315f5c57f542)
|
||||||
|
return React.Children.toArray(this.props.children).filter((c) => {
|
||||||
return c != null;
|
return c != null;
|
||||||
});
|
}).slice(start, end);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
const childCount = childArray.length;
|
_getChildCount: function() {
|
||||||
|
if (this.props.getChildren && this.props.getChildCount) {
|
||||||
|
return this.props.getChildCount();
|
||||||
|
} else {
|
||||||
|
return React.Children.toArray(this.props.children).filter((c) => {
|
||||||
|
return c != null;
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
let overflowNode = null;
|
||||||
|
|
||||||
|
const totalChildren = this._getChildCount();
|
||||||
|
let upperBound = totalChildren;
|
||||||
if (this.props.truncateAt >= 0) {
|
if (this.props.truncateAt >= 0) {
|
||||||
const overflowCount = childCount - this.props.truncateAt;
|
const overflowCount = totalChildren - this.props.truncateAt;
|
||||||
|
|
||||||
if (overflowCount > 1) {
|
if (overflowCount > 1) {
|
||||||
overflowJsx = this.props.createOverflowElement(
|
overflowNode = this.props.createOverflowElement(
|
||||||
overflowCount, childCount,
|
overflowCount, totalChildren,
|
||||||
);
|
);
|
||||||
|
upperBound = this.props.truncateAt;
|
||||||
// cut out the overflow elements
|
|
||||||
childArray.splice(childCount - overflowCount, overflowCount);
|
|
||||||
childsJsx = childArray; // use what is left
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const childNodes = this._getChildren(0, upperBound);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.props.className}>
|
<div className={this.props.className}>
|
||||||
{childsJsx}
|
{childNodes}
|
||||||
{overflowJsx}
|
{overflowNode}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,10 +15,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
var React = require('react');
|
|
||||||
|
import React from 'react';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
var classNames = require('classnames');
|
import classNames from 'classnames';
|
||||||
var Matrix = require("matrix-js-sdk");
|
import Matrix from 'matrix-js-sdk';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
|
@ -27,13 +29,13 @@ var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
var rate_limited_func = require('../../../ratelimitedfunc');
|
var rate_limited_func = require('../../../ratelimitedfunc');
|
||||||
var CallHandler = require("../../../CallHandler");
|
var CallHandler = require("../../../CallHandler");
|
||||||
|
|
||||||
var INITIAL_LOAD_NUM_MEMBERS = 30;
|
const INITIAL_LOAD_NUM_MEMBERS = 30;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MemberList',
|
displayName: 'MemberList',
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
var state = {
|
const state = {
|
||||||
members: [],
|
members: [],
|
||||||
// ideally we'd size this to the page height, but
|
// ideally we'd size this to the page height, but
|
||||||
// in practice I find that a little constraining
|
// in practice I find that a little constraining
|
||||||
|
@ -48,6 +50,8 @@ module.exports = React.createClass({
|
||||||
this.memberDict = this.getMemberDict();
|
this.memberDict = this.getMemberDict();
|
||||||
|
|
||||||
state.members = this.roomMembers();
|
state.members = this.roomMembers();
|
||||||
|
state.filteredJoinedMembers = this._filterMembers(state.members, 'join');
|
||||||
|
state.filteredInvitedMembers = this._filterMembers(state.members, 'invite');
|
||||||
return state;
|
return state;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -146,10 +150,12 @@ module.exports = React.createClass({
|
||||||
// console.log("Updating memberlist");
|
// console.log("Updating memberlist");
|
||||||
this.memberDict = this.getMemberDict();
|
this.memberDict = this.getMemberDict();
|
||||||
|
|
||||||
var self = this;
|
const newState = {
|
||||||
this.setState({
|
members: this.roomMembers(),
|
||||||
members: self.roomMembers()
|
};
|
||||||
});
|
newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join');
|
||||||
|
newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite');
|
||||||
|
this.setState(newState);
|
||||||
}, 500),
|
}, 500),
|
||||||
|
|
||||||
getMemberDict: function() {
|
getMemberDict: function() {
|
||||||
|
@ -279,17 +285,17 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onSearchQueryChanged: function(ev) {
|
onSearchQueryChanged: function(ev) {
|
||||||
this.setState({ searchQuery: ev.target.value });
|
const q = ev.target.value;
|
||||||
|
this.setState({
|
||||||
|
searchQuery: q,
|
||||||
|
filteredJoinedMembers: this._filterMembers(this.state.members, 'join', q),
|
||||||
|
filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', q),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
makeMemberTiles: function(membership, query) {
|
_filterMembers: function(members, membership, query) {
|
||||||
var MemberTile = sdk.getComponent("rooms.MemberTile");
|
return members.filter((userId) => {
|
||||||
query = (query || "").toLowerCase();
|
const m = this.memberDict[userId];
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var memberList = self.state.members.filter(function(userId) {
|
|
||||||
var m = self.memberDict[userId];
|
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
const matchesName = m.name.toLowerCase().indexOf(query) !== -1;
|
const matchesName = m.name.toLowerCase().indexOf(query) !== -1;
|
||||||
|
@ -301,14 +307,23 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.membership == membership;
|
return m.membership == membership;
|
||||||
}).map(function(userId) {
|
});
|
||||||
var m = self.memberDict[userId];
|
},
|
||||||
|
|
||||||
|
_makeMemberTiles: function(members, membership) {
|
||||||
|
const MemberTile = sdk.getComponent("rooms.MemberTile");
|
||||||
|
|
||||||
|
const memberList = members.map((userId) => {
|
||||||
|
const m = this.memberDict[userId];
|
||||||
return (
|
return (
|
||||||
<MemberTile key={userId} member={m} ref={userId} />
|
<MemberTile key={userId} member={m} ref={userId} />
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// XXX: surely this is not the right home for this logic.
|
// XXX: surely this is not the right home for this logic.
|
||||||
|
// Double XXX: Now it's really, really not the right home for this logic:
|
||||||
|
// we shouldn't even be passing in the 'membership' param to this function.
|
||||||
|
// Ew, ew, and ew.
|
||||||
if (membership === "invite") {
|
if (membership === "invite") {
|
||||||
// include 3pid invites (m.room.third_party_invite) state events.
|
// include 3pid invites (m.room.third_party_invite) state events.
|
||||||
// The HS may have already converted these into m.room.member invites so
|
// The HS may have already converted these into m.room.member invites so
|
||||||
|
@ -341,9 +356,17 @@ module.exports = React.createClass({
|
||||||
return memberList;
|
return memberList;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_getChildrenJoined: function(start, end) {
|
||||||
|
return this._makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end));
|
||||||
|
},
|
||||||
|
|
||||||
|
_getChildCountJoined: function() {
|
||||||
|
return this.state.filteredJoinedMembers.length;
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var invitedSection = null;
|
let invitedSection = null;
|
||||||
var invitedMemberTiles = this.makeMemberTiles('invite', this.state.searchQuery);
|
const invitedMemberTiles = this._makeMemberTiles(this.state.filteredInvitedMembers, 'invite');
|
||||||
if (invitedMemberTiles.length > 0) {
|
if (invitedMemberTiles.length > 0) {
|
||||||
invitedSection = (
|
invitedSection = (
|
||||||
<div className="mx_MemberList_invited">
|
<div className="mx_MemberList_invited">
|
||||||
|
@ -355,7 +378,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var inputBox = (
|
const inputBox = (
|
||||||
<form autoComplete="off">
|
<form autoComplete="off">
|
||||||
<input className="mx_MemberList_query" id="mx_MemberList_query" type="text"
|
<input className="mx_MemberList_query" id="mx_MemberList_query" type="text"
|
||||||
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
|
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
|
||||||
|
@ -363,15 +386,16 @@ module.exports = React.createClass({
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|
||||||
var TruncatedList = sdk.getComponent("elements.TruncatedList");
|
const TruncatedList = sdk.getComponent("elements.TruncatedList");
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberList">
|
<div className="mx_MemberList">
|
||||||
{ inputBox }
|
{ inputBox }
|
||||||
<GeminiScrollbar autoshow={true} className="mx_MemberList_joined mx_MemberList_outerWrapper">
|
<GeminiScrollbar autoshow={true} className="mx_MemberList_joined mx_MemberList_outerWrapper">
|
||||||
<TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt}
|
<TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt}
|
||||||
createOverflowElement={this._createOverflowTile}>
|
createOverflowElement={this._createOverflowTile}
|
||||||
{this.makeMemberTiles('join', this.state.searchQuery)}
|
getChildren={this._getChildrenJoined}
|
||||||
</TruncatedList>
|
getChildCount={this._getChildCountJoined}
|
||||||
|
/>
|
||||||
{invitedSection}
|
{invitedSection}
|
||||||
</GeminiScrollbar>
|
</GeminiScrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue