Add user list to groups

This commit is contained in:
David Baker 2017-07-25 15:56:16 +01:00
parent b589fcc3b0
commit c2034d5335
22 changed files with 287 additions and 44 deletions

View file

@ -227,6 +227,7 @@ export default React.createClass({
const HomePage = sdk.getComponent('structures.HomePage');
const GroupView = sdk.getComponent('structures.GroupView');
const MyGroups = sdk.getComponent('structures.MyGroups');
const GroupMemberList = sdk.getComponent('groups.GroupMemberList');
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
@ -307,6 +308,7 @@ export default React.createClass({
page_element = <GroupView
groupId={this.props.currentGroupId}
/>;
right_panel = <RightPanel groupId={this.props.currentGroupId} opacity={this.props.rightOpacity} />;
break;
}

View file

@ -14,10 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var AvatarLogic = require("../../../Avatar");
import React from 'react';
import AvatarLogic from '../../../Avatar';
import sdk from '../../../index';
import AccessibleButton from '../elements/AccessibleButton';

View file

@ -0,0 +1,146 @@
/*
Copyright 2017 Vector Creations 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';
import { _t } from '../../../languageHandler';
import Promise from 'bluebird';
import sdk from '../../../index';
import GeminiScrollbar from 'react-gemini-scrollbar';
import PropTypes from 'prop-types';
import withMatrixClient from '../../../wrappers/withMatrixClient';
const INITIAL_LOAD_NUM_MEMBERS = 30;
export default withMatrixClient(React.createClass({
displayName: 'GroupMemberList',
propTypes: {
matrixClient: PropTypes.object.isRequired,
groupId: PropTypes.string.isRequired,
},
getInitialState: function() {
return {
fetching: false,
members: null,
}
},
componentWillMount: function() {
this._unmounted = false;
this._fetchMembers();
},
_fetchMembers: function() {
this.setState({fetching: true});
this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => {
this.setState({
members: result.chunk,
fetching: false,
});
}).catch((e) => {
this.setState({fetching: false});
console.error("Failed to get group member list: " + e);
});
},
_createOverflowTile: function(overflowCount, totalCount) {
// For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
} name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullMemberList} />
);
},
_showFullMemberList: function() {
this.setState({
truncateAt: -1
});
},
onSearchQueryChanged: function(ev) {
this.setState({ searchQuery: ev.target.value });
},
makeGroupMemberTiles: function(query) {
const GroupMemberTile = sdk.getComponent("groups.GroupMemberTile");
query = (query || "").toLowerCase();
const memberList = this.state.members.filter((m) => {
if (query) {
//const matchesName = m.name.toLowerCase().indexOf(query) !== -1;
const matchesId = m.user_id.toLowerCase().indexOf(query) !== -1;
if (/*!matchesName &&*/ !matchesId) {
return false;
}
}
return true;
}).map(function(m) {
return (
<GroupMemberTile key={m.user_id} member={m} />
);
});
memberList.sort((a, b) => {
// should put admins at the top: we don't yet have that info
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
return memberList;
},
render: function() {
if (this.state.fetching) {
const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />;
} else if (this.state.members === null) {
return null;
}
const inputBox = (
<form autoComplete="off">
<input className="mx_MemberList_query" id="mx_MemberList_query" type="text"
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder={ _t('Filter group members') } />
</form>
);
const TruncatedList = sdk.getComponent("elements.TruncatedList");
return (
<div className="mx_MemberList">
{ inputBox }
<GeminiScrollbar autoshow={true} className="mx_MemberList_joined mx_MemberList_outerWrapper">
<TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile}>
{this.makeGroupMemberTiles(this.state.searchQuery)}
</TruncatedList>
</GeminiScrollbar>
</div>
);
}
}));

View file

@ -0,0 +1,68 @@
/*
Copyright 2017 Vector Creations 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';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
import withMatrixClient from '../../../wrappers/withMatrixClient';
import Matrix from "matrix-js-sdk";
export default withMatrixClient(React.createClass({
displayName: 'GroupMemberTile',
propTypes: {
matrixClient: PropTypes.object,
member: PropTypes.shape({
user_id: PropTypes.string.isRequired,
}).isRequired,
},
getInitialState: function() {
return {};
},
onClick: function(e) {
const member = new Matrix.RoomMember(null, this.props.member.user_id);
dis.dispatch({
action: 'view_user',
member: member,
});
},
getPowerLabel: function() {
return _t("%(userName)s (power %(powerLevelNumber)s)", {userName: this.props.member.userId, powerLevelNumber: this.props.member.powerLevel});
},
render: function() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const EntityTile = sdk.getComponent('rooms.EntityTile');
const name = this.props.member.user_id;
const av = (
<BaseAvatar name={this.props.member.user_id} width={36} height={36} />
);
return (
<EntityTile presenceState="online"
avatarJsx={av} title={this.getPowerLabel()} onClick={this.onClick}
name={name} powerLevel={0} suppressOnHover={true}
/>
);
}
}));

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -200,11 +201,9 @@ module.exports = React.createClass({
_createOverflowTile: function(overflowCount, totalCount) {
// For now we'll pretend this is any entity. It should probably be a separate tile.
var EntityTile = sdk.getComponent("rooms.EntityTile");
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
var text = (overflowCount > 1)
? _t("and %(overflowCount)s others...", { overflowCount: overflowCount })
: _t("and one other...");
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />

View file

@ -117,9 +117,7 @@ var SearchableEntityList = React.createClass({
_createOverflowEntity: function(overflowCount, totalCount) {
var EntityTile = sdk.getComponent("rooms.EntityTile");
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
var text = (overflowCount > 1)
? _t("and %(overflowCount)s others...", { overflowCount: overflowCount })
: _t("and one other...");
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />

View file

@ -552,8 +552,10 @@
"Failed to forget room %(errCode)s": "Das Entfernen des Raums ist fehlgeschlagen %(errCode)s",
"Failed to join the room": "Fehler beim Betreten des Raumes",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "Eine Textnachricht wurde an +%(msisdn)s gesendet. Bitte gebe den Verifikationscode ein, den er beinhaltet",
"and %(overflowCount)s others...": "und %(overflowCount)s weitere...",
"and one other...": "und ein(e) weitere(r)...",
"and %(count)s others...": {
"other": "und %(count)s weitere...",
"one": "und ein(e) weitere(r)..."
},
"Are you sure?": "Bist du sicher?",
"Attachment": "Anhang",
"Ban": "Dauerhaft aus dem Raum ausschließen",

View file

@ -175,8 +175,10 @@
"an address": "μία διεύθηνση",
"%(items)s and %(remaining)s others": "%(items)s και %(remaining)s ακόμα",
"%(items)s and one other": "%(items)s και ένας ακόμα",
"and %(overflowCount)s others...": "και %(overflowCount)s άλλοι...",
"and one other...": "και ένας ακόμα...",
"and %(count)s others...": {
"other": "και %(count)s άλλοι...",
"one": "και ένας ακόμα..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s και %(lastPerson)s γράφουν",
"%(names)s and one other are typing": "%(names)s και ένας ακόμα γράφουν",
"%(names)s and %(count)s others are typing": "%(names)s και %(count)s άλλοι γράφουν",

View file

@ -157,8 +157,10 @@
"%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others",
"%(items)s and one other": "%(items)s and one other",
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
"and %(overflowCount)s others...": "and %(overflowCount)s others...",
"and one other...": "and one other...",
"and %(count)s others...": {
"other": "and %(count)s others...",
"one": "and one other..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
"%(names)s and one other are typing": "%(names)s and one other are typing",
"%(names)s and %(count)s others are typing": "%(names)s and %(count)s others are typing",

View file

@ -154,8 +154,10 @@
"%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others",
"%(items)s and one other": "%(items)s and one other",
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
"and %(overflowCount)s others...": "and %(overflowCount)s others...",
"and one other...": "and one other...",
"and %(count)s others...": {
"other": "and %(count)s others...",
"one": "and one other..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
"%(names)s and one other are typing": "%(names)s and one other are typing",
"%(names)s and %(count)s others are typing": "%(names)s and %(count)s others are typing",

View file

@ -140,8 +140,10 @@
"%(items)s and %(remaining)s others": "%(items)s y %(remaining)s otros",
"%(items)s and one other": "%(items)s y otro",
"%(items)s and %(lastItem)s": "%(items)s y %(lastItem)s",
"and %(overflowCount)s others...": "y %(overflowCount)s otros...",
"and one other...": "y otro...",
"and %(count)s others...": {
"other": "y %(count)s otros...",
"one": "y otro..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s y %(lastPerson)s están escribiendo",
"%(names)s and one other are typing": "%(names)s y otro están escribiendo",
"%(names)s and %(count)s others are typing": "%(names)s y %(count)s otros están escribiendo",

View file

@ -188,8 +188,10 @@
"%(items)s and %(remaining)s others": "%(items)s et %(remaining)s autres",
"%(items)s and one other": "%(items)s et un autre",
"%(items)s and %(lastItem)s": "%(items)s et %(lastItem)s",
"and %(overflowCount)s others...": "et %(overflowCount)s autres...",
"and one other...": "et un autre...",
"and %(count)s others...": {
"other": "et %(count)s autres...",
"one": "et un autre..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s et %(lastPerson)s sont en train de taper",
"%(names)s and one other are typing": "%(names)s et un autre sont en train de taper",
"%(names)s and %(count)s others are typing": "%(names)s et %(count)s d'autres sont en train de taper",

View file

@ -190,8 +190,10 @@
"%(items)s and %(remaining)s others": "%(items)s és még: %(remaining)s",
"%(items)s and one other": "%(items)s és még egy",
"%(items)s and %(lastItem)s": "%(items)s és %(lastItem)s",
"and %(overflowCount)s others...": "és még: %(overflowCount)s ...",
"and one other...": "és még egy...",
"and %(count)s others...": {
"other": "és még: %(count)s ...",
"one": "és még egy..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s és %(lastPerson)s írnak",
"%(names)s and one other are typing": "%(names)s és még valaki ír",
"%(names)s and %(count)s others are typing": "%(names)s és %(count)s ember ír",

View file

@ -225,8 +225,10 @@
"%(items)s and %(remaining)s others": "%(items)s과 %(remaining)s",
"%(items)s and one other": "%(items)s과 다른 하나",
"%(items)s and %(lastItem)s": "%(items)s과 %(lastItem)s",
"and %(overflowCount)s others...": "그리고 %(overflowCount)s...",
"and one other...": "그리고 다른 하나...",
"and %(count)s others...": {
"other": "그리고 %(count)s...",
"one": "그리고 다른 하나..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s님과 %(lastPerson)s님이 입력중",
"%(names)s and one other are typing": "%(names)s님과 다른 분이 입력중",
"%(names)s and %(count)s others are typing": "%(names)s님과 %(count)s 분들이 입력중",

View file

@ -141,8 +141,10 @@
"%(items)s and %(remaining)s others": "%(items)s en %(remaining)s andere",
"%(items)s and one other": "%(items)s en één andere",
"%(items)s and %(lastItem)s": "%(items)s en %(lastItem)s",
"and %(overflowCount)s others...": "en %(overflowCount)s andere...",
"and one other...": "en één andere...",
"and %(count)s others...": {
"other": "en %(count)s andere...",
"one": "en één andere..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s en %(lastPerson)s zijn aan het typen",
"%(names)s and one other are typing": "%(names)s en één andere zijn aan het typen",
"%(names)s and %(count)s others are typing": "%(names)s en %(count)s andere zijn aan het typen",

View file

@ -556,8 +556,10 @@
"%(items)s and %(remaining)s others": "%(items)s e %(remaining)s outros",
"%(items)s and one other": "%(items)s e um outro",
"%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s",
"and %(overflowCount)s others...": "e %(overflowCount)s outros...",
"and one other...": "e um outro...",
"and %(count)s others...": {
"other": "e %(count)s outros...",
"one": "e um outro..."
},
"Are you sure?": "Você tem certeza?",
"Attachment": "Anexo",
"Autoplay GIFs and videos": "Reproduzir automaticamente GIFs e videos",

View file

@ -557,8 +557,10 @@
"%(items)s and %(remaining)s others": "%(items)s e %(remaining)s outros",
"%(items)s and one other": "%(items)s e um outro",
"%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s",
"and %(overflowCount)s others...": "e %(overflowCount)s outros...",
"and one other...": "e um outro...",
"and %(count)s others...": {
"other": "e %(count)s outros...",
"one": "e um outro..."
},
"Are you sure?": "Você tem certeza?",
"Attachment": "Anexo",
"Autoplay GIFs and videos": "Reproduzir automaticamente GIFs e videos",

View file

@ -448,7 +448,10 @@
"sx": "Суту",
"zh-hk": "Китайский (Гонконг)",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "На +%(msisdn)s было отправлено текстовое сообщение. Пожалуйста, введите проверочный код из него",
"and %(overflowCount)s others...": "и %(overflowCount)s других...",
"and %(count)s others...": {
"other": "и %(count)s других...",
"one": "и ещё один..."
},
"Are you sure?": "Вы уверены?",
"Autoplay GIFs and videos": "Проигрывать GIF и видео автоматически",
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Невозможно соединиться с домашним сервером - проверьте своё соединение и убедитесь, что <a>SSL-сертификат вашего домашнего сервера</a> включён в доверяемые.",
@ -479,7 +482,6 @@
"%(items)s and %(remaining)s others": "%(items)s и другие %(remaining)s",
"%(items)s and one other": "%(items)s и ещё один",
"%(items)s and %(lastItem)s": "%(items)s и %(lastItem)s",
"and one other...": "и ещё один...",
"An error has occurred.": "Произошла ошибка.",
"Attachment": "Вложение",
"Ban": "Запретить",

View file

@ -150,8 +150,10 @@
"%(items)s and %(remaining)s others": "%(items)s och %(remaining)s andra",
"%(items)s and one other": "%(items)s och en annan",
"%(items)s and %(lastItem)s": "%(items)s och %(lastItem)s",
"and %(overflowCount)s others...": "och %(overflowCount)s andra...",
"and one other...": "och en annan...",
"and %(count)s others...": {
"other": "och %(count)s andra...",
"one": "och en annan..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s och %(lastPerson)s skriver",
"%(names)s and one other are typing": "%(names)s och en annan skriver",
"%(names)s and %(count)s others are typing": "%(names)s och %(count)s andra skriver",

View file

@ -98,8 +98,10 @@
"%(items)s and %(remaining)s others": "%(items)s และอีก %(remaining)s ผู้ใช้",
"%(items)s and one other": "%(items)s และอีกหนึ่งผู้ใช้",
"%(items)s and %(lastItem)s": "%(items)s และ %(lastItem)s",
"and %(overflowCount)s others...": "และอีก %(overflowCount)s ผู้ใช้...",
"and one other...": "และอีกหนึ่งผู้ใช้...",
"and %(count)s others...": {
"other": "และอีก %(count)s ผู้ใช้...",
"one": "และอีกหนึ่งผู้ใช้..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s และ %(lastPerson)s กำลังพิมพ์",
"%(names)s and one other are typing": "%(names)s และอีกหนึ่งคนกำลังพิมพ์",
"%(names)s and %(count)s others are typing": "%(names)s และอีก %(count)s คนกำลังพิมพ์",

View file

@ -156,8 +156,10 @@
"%(items)s and %(remaining)s others": "%(items)s ve %(remaining)s diğerleri",
"%(items)s and one other": "%(items)s ve bir başkası",
"%(items)s and %(lastItem)s": "%(items)s ve %(lastItem)s",
"and %(overflowCount)s others...": "ve %(overflowCount)s diğerleri...",
"and one other...": "ve bir diğeri...",
"and %(count)s others...": {
"other": "ve %(count)s diğerleri...",
"one": "ve bir diğeri..."
},
"%(names)s and %(lastPerson)s are typing": "%(names)s ve %(lastPerson)s yazıyorlar",
"%(names)s and one other are typing": "%(names)s ve birisi yazıyor",
"%(names)s and %(count)s others are typing": "%(names)s ve %(count)s diğeri yazıyor",

View file

@ -239,8 +239,10 @@
"%(items)s and %(remaining)s others": "%(items)s 和其它 %(remaining)s 个",
"%(items)s and one other": "%(items)s 和其它一个",
"%(items)s and %(lastItem)s": "%(items)s 和 %(lastItem)s",
"and %(overflowCount)s others...": "和其它 %(overflowCount)s 个...",
"and one other...": "和其它一个...",
"and %(count)s others...": {
"other": "和其它 %(count)s 个...",
"one": "和其它一个..."
},
"%(names)s and one other are typing": "%(names)s 和另一个人正在打字",
"anyone": "任何人",
"Anyone": "任何人",