Merge branch 'travis/ftue/user-lists/2-suggestions' into travis/ftue/user-lists/3-filtering

This commit is contained in:
Travis Ralston 2020-01-03 20:34:00 -07:00
commit 250222726d
2 changed files with 81 additions and 13 deletions

View file

@ -23,6 +23,7 @@ import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import DMRoomMap from "../../../utils/DMRoomMap"; import DMRoomMap from "../../../utils/DMRoomMap";
import {RoomMember} from "matrix-js-sdk/lib/matrix"; import {RoomMember} from "matrix-js-sdk/lib/matrix";
import * as humanize from "humanize"; import * as humanize from "humanize";
import SdkConfig from "../../../SdkConfig";
// TODO: [TravisR] Make this generic for all kinds of invites // TODO: [TravisR] Make this generic for all kinds of invites
@ -36,10 +37,6 @@ class DMRoomTile extends React.PureComponent {
onToggle: PropTypes.func.isRequired, onToggle: PropTypes.func.isRequired,
}; };
constructor() {
super();
}
_onClick = (e) => { _onClick = (e) => {
// Stop the browser from highlighting text // Stop the browser from highlighting text
e.preventDefault(); e.preventDefault();
@ -84,6 +81,8 @@ export default class DMInviteDialog extends React.PureComponent {
filterText: "", filterText: "",
recents: this._buildRecents(), recents: this._buildRecents(),
numRecentsShown: INITIAL_ROOMS_SHOWN, numRecentsShown: INITIAL_ROOMS_SHOWN,
suggestions: this._buildSuggestions(),
numSuggestionsShown: INITIAL_ROOMS_SHOWN,
}; };
} }
@ -109,6 +108,59 @@ export default class DMInviteDialog extends React.PureComponent {
return recents; return recents;
} }
_buildSuggestions(): {userId: string, user: RoomMember} {
const maxConsideredMembers = 200;
const client = MatrixClientPeg.get();
const excludedUserIds = [client.getUserId(), SdkConfig.get()['welcomeUserId']];
const joinedRooms = client.getRooms()
.filter(r => r.getMyMembership() === 'join')
.filter(r => r.getJoinedMemberCount() <= maxConsideredMembers);
const memberRooms = joinedRooms.reduce((members, room) => {
const joinedMembers = room.getJoinedMembers().filter(u => !excludedUserIds.includes(u.userId));
for (const member of joinedMembers) {
if (!members[member.userId]) {
members[member.userId] = {
member: member,
// Track the room size of the 'picked' member so we can use the profile of
// the smallest room (likely a DM).
pickedMemberRoomSize: room.getJoinedMemberCount(),
rooms: [],
};
}
members[member.userId].rooms.push(room);
if (room.getJoinedMemberCount() < members[member.userId].pickedMemberRoomSize) {
members[member.userId].member = member;
members[member.userId].pickedMemberRoomSize = room.getJoinedMemberCount();
}
}
return members;
}, {/* userId => {member, rooms[]} */});
const memberScores = Object.values(memberRooms).reduce((scores, entry) => {
const numMembersTotal = entry.rooms.reduce((c, r) => c + r.getJoinedMemberCount(), 0);
const maxRange = maxConsideredMembers * entry.rooms.length;
scores[entry.member.userId] = {
member: entry.member,
numRooms: entry.rooms.length,
score: Math.max(0, Math.pow(1 - (numMembersTotal / maxRange), 5)),
};
return scores;
}, {/* userId => {member, numRooms, score} */});
const members = Object.values(memberScores);
members.sort((a, b) => {
if (a.score === b.score) {
if (a.numRooms === b.numRooms) {
return a.member.userId.localeCompare(b.member.userId);
}
return b.numRooms - a.numRooms;
}
return b.score - a.score;
});
return members.map(m => ({userId: m.userId, user: m.member}));
}
_startDm = () => { _startDm = () => {
this.props.onFinished(this.state.targets); this.props.onFinished(this.state.targets);
}; };
@ -125,6 +177,10 @@ export default class DMInviteDialog extends React.PureComponent {
this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN}); this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN});
}; };
_showMoreSuggestions = () => {
this.setState({numSuggestionsShown: this.state.numSuggestionsShown + INCREMENT_ROOMS_SHOWN});
};
_toggleMember = (userId) => { _toggleMember = (userId) => {
const targets = this.state.targets.map(t => t); // cheap clone for mutation const targets = this.state.targets.map(t => t); // cheap clone for mutation
const idx = targets.indexOf(userId); const idx = targets.indexOf(userId);
@ -133,29 +189,39 @@ export default class DMInviteDialog extends React.PureComponent {
this.setState({targets}); this.setState({targets});
}; };
_renderRecents() { _renderSection(kind: "recents"|"suggestions") {
if (!this.state.recents || this.state.recents.length === 0) return null; const sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions;
let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this);
const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
const sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");
if (!sourceMembers || sourceMembers.length === 0) return null;
// If we're going to hide one member behind 'show more', just use up the space of the button
// with the member's tile instead.
if (showNum === sourceMembers.length - 1) showNum++;
// .slice() will return an incomplete array but won't error on us if we go too far // .slice() will return an incomplete array but won't error on us if we go too far
const toRender = this.state.recents.slice(0, this.state.numRecentsShown); const toRender = sourceMembers.slice(0, showNum);
const hasMore = toRender.length < this.state.recents.length; const hasMore = toRender.length < sourceMembers.length;
const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
let showMore = null; let showMore = null;
if (hasMore) { if (hasMore) {
showMore = ( showMore = (
<AccessibleButton onClick={this._showMoreRecents} kind="link"> <AccessibleButton onClick={showMoreFn} kind="link">
{_t("Show more")} {_t("Show more")}
</AccessibleButton> </AccessibleButton>
); );
} }
const tiles = toRender.map(r => ( const tiles = toRender.map(r => (
<DMRoomTile member={r.user} lastActiveTs={r.lastActive} key={r.userId} onToggle={this._toggleMember} /> <DMRoomTile member={r.user} lastActiveTs={lastActive(r)} key={r.userId} onToggle={this._toggleMember} />
)); ));
return ( return (
<div className='mx_DMInviteDialog_section'> <div className='mx_DMInviteDialog_section'>
<h3>{_t("Recent Conversations")}</h3> <h3>{sectionName}</h3>
{tiles} {tiles}
{showMore} {showMore}
</div> </div>
@ -209,7 +275,8 @@ export default class DMInviteDialog extends React.PureComponent {
{_t("Go")} {_t("Go")}
</AccessibleButton> </AccessibleButton>
</div> </div>
{this._renderRecents()} {this._renderSection('recents')}
{this._renderSection('suggestions')}
</div> </div>
</BaseDialog> </BaseDialog>
); );

View file

@ -1431,8 +1431,9 @@
"View Servers in Room": "View Servers in Room", "View Servers in Room": "View Servers in Room",
"Toolbox": "Toolbox", "Toolbox": "Toolbox",
"Developer Tools": "Developer Tools", "Developer Tools": "Developer Tools",
"Show more": "Show more",
"Recent Conversations": "Recent Conversations", "Recent Conversations": "Recent Conversations",
"Suggestions": "Suggestions",
"Show more": "Show more",
"Direct Messages": "Direct Messages", "Direct Messages": "Direct Messages",
"If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.", "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.",
"Go": "Go", "Go": "Go",