From c2034d53359abb67123177438304f49f06072f76 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 25 Jul 2017 15:56:16 +0100 Subject: [PATCH 01/17] Add user list to groups --- src/components/structures/LoggedInView.js | 2 + src/components/views/avatars/BaseAvatar.js | 6 +- .../views/groups/GroupMemberList.js | 146 ++++++++++++++++++ .../views/groups/GroupMemberTile.js | 68 ++++++++ src/components/views/rooms/MemberList.js | 9 +- .../views/rooms/SearchableEntityList.js | 4 +- src/i18n/strings/de_DE.json | 6 +- src/i18n/strings/el.json | 6 +- src/i18n/strings/en_EN.json | 6 +- src/i18n/strings/en_US.json | 6 +- src/i18n/strings/es.json | 6 +- src/i18n/strings/fr.json | 6 +- src/i18n/strings/hu.json | 6 +- src/i18n/strings/ko.json | 6 +- src/i18n/strings/nl.json | 6 +- src/i18n/strings/pt.json | 6 +- src/i18n/strings/pt_BR.json | 6 +- src/i18n/strings/ru.json | 6 +- src/i18n/strings/sv.json | 6 +- src/i18n/strings/th.json | 6 +- src/i18n/strings/tr.json | 6 +- src/i18n/strings/zh_Hans.json | 6 +- 22 files changed, 287 insertions(+), 44 deletions(-) create mode 100644 src/components/views/groups/GroupMemberList.js create mode 100644 src/components/views/groups/GroupMemberTile.js diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index a051af5d08..edc663547b 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -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 = ; + right_panel = ; break; } diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js index a4443430f4..e88fc869fa 100644 --- a/src/components/views/avatars/BaseAvatar.js +++ b/src/components/views/avatars/BaseAvatar.js @@ -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'; diff --git a/src/components/views/groups/GroupMemberList.js b/src/components/views/groups/GroupMemberList.js new file mode 100644 index 0000000000..48d49dc04b --- /dev/null +++ b/src/components/views/groups/GroupMemberList.js @@ -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 ( + + } 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 ( + + ); + }); + + 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 ; + } else if (this.state.members === null) { + return null; + } + + const inputBox = ( +
+ +
+ ); + + const TruncatedList = sdk.getComponent("elements.TruncatedList"); + return ( +
+ { inputBox } + + + {this.makeGroupMemberTiles(this.state.searchQuery)} + + +
+ ); + } +})); diff --git a/src/components/views/groups/GroupMemberTile.js b/src/components/views/groups/GroupMemberTile.js new file mode 100644 index 0000000000..1ef59fb1a6 --- /dev/null +++ b/src/components/views/groups/GroupMemberTile.js @@ -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 = ( + + ); + + return ( + + ); + } +})); diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 37a9bcd0cd..e5f4f08572 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -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 ( diff --git a/src/components/views/rooms/SearchableEntityList.js b/src/components/views/rooms/SearchableEntityList.js index 4412fc539f..45ad2c6b9e 100644 --- a/src/components/views/rooms/SearchableEntityList.js +++ b/src/components/views/rooms/SearchableEntityList.js @@ -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 ( diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 68cfec2cd9..e855abffc0 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -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", diff --git a/src/i18n/strings/el.json b/src/i18n/strings/el.json index d339508488..bf96278fbd 100644 --- a/src/i18n/strings/el.json +++ b/src/i18n/strings/el.json @@ -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 άλλοι γράφουν", diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4841f2f0de..da3d1545ae 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -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", diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index 6a2a326870..47bb96ed14 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -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", diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index f7506b946d..05b1abffd7 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -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", diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 9f6fdf3391..9ae015eaff 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -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", diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 8335976a06..2d1f07c34e 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -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", diff --git a/src/i18n/strings/ko.json b/src/i18n/strings/ko.json index 601f7d1ce0..52970db2ed 100644 --- a/src/i18n/strings/ko.json +++ b/src/i18n/strings/ko.json @@ -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 분들이 입력중", diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index 95c5e0ee78..ae2cf977ea 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -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", diff --git a/src/i18n/strings/pt.json b/src/i18n/strings/pt.json index 0405d000af..16ffc1f107 100644 --- a/src/i18n/strings/pt.json +++ b/src/i18n/strings/pt.json @@ -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", diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 4820104aa1..330dc8f143 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -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", diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 76a047d079..cad0617ae2 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -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 homeserver's SSL certificate is trusted.": "Невозможно соединиться с домашним сервером - проверьте своё соединение и убедитесь, что SSL-сертификат вашего домашнего сервера включён в доверяемые.", @@ -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": "Запретить", diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 21bda3b741..549e06c991 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -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", diff --git a/src/i18n/strings/th.json b/src/i18n/strings/th.json index 5dff4816ee..265c3e1b58 100644 --- a/src/i18n/strings/th.json +++ b/src/i18n/strings/th.json @@ -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 คนกำลังพิมพ์", diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 3d0d774926..effc426464 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -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", diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index 9fdc7a0b42..ad56ab45b8 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -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": "任何人", From 598411dd03892189a04fcf35937541abe80173d5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 25 Jul 2017 17:22:05 +0100 Subject: [PATCH 02/17] Add translation --- src/i18n/strings/en_EN.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 86cf8727bb..a12d277a92 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -961,5 +961,7 @@ "Edit Group": "Edit Group", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Failed to upload image": "Failed to upload image", - "Failed to update group": "Failed to update group" + "Failed to update group": "Failed to update group", + "Description": "Description", + "Filter group members": "Filter group members" } From fe19dbee02fef5bc69e8c0051b9e1075d3fe30e9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 3 Aug 2017 09:26:41 +0100 Subject: [PATCH 03/17] Remove unused component --- src/components/structures/LoggedInView.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index edc663547b..2dcb72deb9 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -227,7 +227,6 @@ 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'); From 234a88c0988f40b801e288332ff75c130da57d7d Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 4 Aug 2017 15:00:34 +0100 Subject: [PATCH 04/17] WIP group member list/tiles --- .../views/dialogs/ConfirmUserActionDialog.js | 27 ++- .../views/groups/GroupMemberInfo.js | 158 ++++++++++++++++++ .../views/groups/GroupMemberList.js | 11 +- .../views/groups/GroupMemberTile.js | 16 +- src/i18n/strings/en_EN.json | 3 +- 5 files changed, 198 insertions(+), 17 deletions(-) create mode 100644 src/components/views/groups/GroupMemberInfo.js diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.js b/src/components/views/dialogs/ConfirmUserActionDialog.js index b10df3ccef..c4e4f18ee0 100644 --- a/src/components/views/dialogs/ConfirmUserActionDialog.js +++ b/src/components/views/dialogs/ConfirmUserActionDialog.js @@ -18,6 +18,7 @@ import React from 'react'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import classnames from 'classnames'; +import { GroupMemberType } from '../../../groups'; /* * A dialog for confirming an operation on another user. @@ -30,7 +31,10 @@ import classnames from 'classnames'; export default React.createClass({ displayName: 'ConfirmUserActionDialog', propTypes: { - member: React.PropTypes.object.isRequired, // matrix-js-sdk member object + // matrix-js-sdk (room) member object. Supply either this or 'groupMember' + member: React.PropTypes.object, + // group member object. Supply either this or 'member' + groupMember: GroupMemberType, action: React.PropTypes.string.isRequired, // eg. 'Ban' // Whether to display a text field for a reason @@ -69,6 +73,7 @@ export default React.createClass({ render: function() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); + const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar"); const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action}); const confirmButtonClass = classnames({ @@ -91,6 +96,20 @@ export default React.createClass({ ); } + let avatar; + let name; + let userId; + if (this.props.member) { + avatar = ; + name = this.props.member.name; + userId = this.props.member.userId; + } else { + // we don't get this info from the API yet + avatar = + name = this.props.groupMember.userId; + userId = this.props.groupMember.userId; + } + return (
- + {avatar}
-
{this.props.member.name}
-
{this.props.member.userId}
+
{name}
+
{userId}
{reasonBox}
diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js new file mode 100644 index 0000000000..70aa7d4504 --- /dev/null +++ b/src/components/views/groups/GroupMemberInfo.js @@ -0,0 +1,158 @@ +/* +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 PropTypes from 'prop-types'; +import React from 'react'; +import classNames from 'classnames'; +import dis from '../../../dispatcher'; +import Modal from '../../../Modal'; +import sdk from '../../../index'; +import { _t } from '../../../languageHandler'; +import createRoom from '../../../createRoom'; +import DMRoomMap from '../../../utils/DMRoomMap'; +import Unread from '../../../Unread'; +import { GroupMemberType } from '../../../groups'; +import { findReadReceiptFromUserId } from '../../../utils/Receipt'; +import withMatrixClient from '../../../wrappers/withMatrixClient'; +import AccessibleButton from '../elements/AccessibleButton'; +import GeminiScrollbar from 'react-gemini-scrollbar'; + + +module.exports = withMatrixClient(React.createClass({ + displayName: 'GroupMemberInfo', + + propTypes: { + matrixClient: PropTypes.object.isRequired, + groupId: PropTypes.string, + member: GroupMemberType, + }, + + componentWillMount: function() { + 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); + }); + }, + + _onKick: function() { + const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); + Modal.createDialog(ConfirmUserActionDialog, { + groupMember: this.props.member, + action: _t('Remove from group'), + danger: true, + onFinished: (proceed) => { + }, + }); + }, + + onCancel: function(e) { + dis.dispatch({ + action: "view_user", + member: null + }); + }, + + onRoomTileClick(roomId) { + dis.dispatch({ + action: 'view_room', + room_id: roomId, + }); + }, + + render: function() { + if (this.state.fetching) { + const Loader = sdk.getComponent("elements.Spinner"); + return ; + } + if (!this.state.members) return null; + + let targetIsInGroup = false; + for (const m of this.state.members) { + if (m.user_id == this.props.member.userId) { + targetIsInGroup = true; + } + } + + let kickButton, adminButton; + + if (targetIsInGroup) { + kickButton = ( + + {_t('Remove from group')} + + ); + + // No make/revoke admin API yet + /*const opLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator"); + giveModButton = + {giveOpLabel} + ;*/ + } + + let adminTools; + if (kickButton || adminButton) { + adminTools = +
+

{_t("Admin tools")}

+ +
+ {kickButton} + {adminButton} +
+
; + } + + const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); + const avatar = ( + + ); + + const memberName = this.props.member.userId; + + const EmojiText = sdk.getComponent('elements.EmojiText'); + return ( +
+ + +
+ {avatar} +
+ + {memberName} + +
+
+ { this.props.member.userId } +
+
+ + { adminTools } +
+
+ ); + } +})); diff --git a/src/components/views/groups/GroupMemberList.js b/src/components/views/groups/GroupMemberList.js index 48d49dc04b..0f74ff5eed 100644 --- a/src/components/views/groups/GroupMemberList.js +++ b/src/components/views/groups/GroupMemberList.js @@ -17,6 +17,7 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import Promise from 'bluebird'; import sdk from '../../../index'; +import { groupMemberFromApiObject } from '../../../groups'; import GeminiScrollbar from 'react-gemini-scrollbar'; import PropTypes from 'prop-types'; import withMatrixClient from '../../../wrappers/withMatrixClient'; @@ -47,7 +48,9 @@ export default withMatrixClient(React.createClass({ this.setState({fetching: true}); this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => { this.setState({ - members: result.chunk, + members: result.chunk.map((apiMember) => { + return groupMemberFromApiObject(apiMember); + }), fetching: false, }); }).catch((e) => { @@ -86,7 +89,7 @@ export default withMatrixClient(React.createClass({ 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; + const matchesId = m.userId.toLowerCase().indexOf(query) !== -1; if (/*!matchesName &&*/ !matchesId) { return false; @@ -94,9 +97,9 @@ export default withMatrixClient(React.createClass({ } return true; - }).map(function(m) { + }).map((m) => { return ( - + ); }); diff --git a/src/components/views/groups/GroupMemberTile.js b/src/components/views/groups/GroupMemberTile.js index 1ef59fb1a6..d129fb9440 100644 --- a/src/components/views/groups/GroupMemberTile.js +++ b/src/components/views/groups/GroupMemberTile.js @@ -19,6 +19,7 @@ import PropTypes from 'prop-types'; import sdk from '../../../index'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; +import { GroupMemberType } from '../../../groups'; import withMatrixClient from '../../../wrappers/withMatrixClient'; import Matrix from "matrix-js-sdk"; @@ -27,9 +28,8 @@ export default withMatrixClient(React.createClass({ propTypes: { matrixClient: PropTypes.object, - member: PropTypes.shape({ - user_id: PropTypes.string.isRequired, - }).isRequired, + groupId: PropTypes.string.isRequired, + member: GroupMemberType.isRequired, }, getInitialState: function() { @@ -37,10 +37,10 @@ export default withMatrixClient(React.createClass({ }, onClick: function(e) { - const member = new Matrix.RoomMember(null, this.props.member.user_id); dis.dispatch({ - action: 'view_user', - member: member, + action: 'view_group_user', + member: this.props.member, + groupId: this.props.groupId, }); }, @@ -52,10 +52,10 @@ export default withMatrixClient(React.createClass({ const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const EntityTile = sdk.getComponent('rooms.EntityTile'); - const name = this.props.member.user_id; + const name = this.props.member.userId; const av = ( - + ); return ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a12d277a92..82e10bf3d1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -963,5 +963,6 @@ "Failed to upload image": "Failed to upload image", "Failed to update group": "Failed to update group", "Description": "Description", - "Filter group members": "Filter group members" + "Filter group members": "Filter group members", + "Remove from group": "Remove from group" } From 4aff43afdad9c6215cace8b0481215572e2227dd Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 7 Aug 2017 10:24:51 +0100 Subject: [PATCH 05/17] Forgot to commit this file --- src/groups.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/groups.js diff --git a/src/groups.js b/src/groups.js new file mode 100644 index 0000000000..9627fcc1cd --- /dev/null +++ b/src/groups.js @@ -0,0 +1,27 @@ +/* +Copyright 2017 New Vector 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 PropTypes from 'prop-types'; + +export const GroupMemberType = PropTypes.shape({ + userId: PropTypes.string.isRequired, +}); + +export function groupMemberFromApiObject(apiObject) { + return { + userId: apiObject.user_id, + } +} From 4daf9064d9ffa44545b5ac14f572e9a7227f1380 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 9 Aug 2017 18:15:31 +0100 Subject: [PATCH 06/17] Convert to API objects & remove redundant power label --- src/components/views/groups/GroupMemberInfo.js | 7 +++++-- src/components/views/groups/GroupMemberTile.js | 6 +----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js index 70aa7d4504..d37afb70a9 100644 --- a/src/components/views/groups/GroupMemberInfo.js +++ b/src/components/views/groups/GroupMemberInfo.js @@ -26,6 +26,7 @@ import DMRoomMap from '../../../utils/DMRoomMap'; import Unread from '../../../Unread'; import { GroupMemberType } from '../../../groups'; import { findReadReceiptFromUserId } from '../../../utils/Receipt'; +import { groupMemberFromApiObject } from '../../../groups'; import withMatrixClient from '../../../wrappers/withMatrixClient'; import AccessibleButton from '../elements/AccessibleButton'; import GeminiScrollbar from 'react-gemini-scrollbar'; @@ -48,7 +49,9 @@ module.exports = withMatrixClient(React.createClass({ this.setState({fetching: true}); this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => { this.setState({ - members: result.chunk, + members: result.chunk.map((apiMember) => { + return groupMemberFromApiObject(apiMember); + }), fetching: false, }); }).catch((e) => { @@ -91,7 +94,7 @@ module.exports = withMatrixClient(React.createClass({ let targetIsInGroup = false; for (const m of this.state.members) { - if (m.user_id == this.props.member.userId) { + if (m.userId == this.props.member.userId) { targetIsInGroup = true; } } diff --git a/src/components/views/groups/GroupMemberTile.js b/src/components/views/groups/GroupMemberTile.js index d129fb9440..35dbd8b531 100644 --- a/src/components/views/groups/GroupMemberTile.js +++ b/src/components/views/groups/GroupMemberTile.js @@ -44,10 +44,6 @@ export default withMatrixClient(React.createClass({ }); }, - 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'); @@ -60,7 +56,7 @@ export default withMatrixClient(React.createClass({ return ( ); From a15c2100d1de9d9cf8f3cfc7e9f6fbed01c4ffbb Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 15 Aug 2017 13:12:39 +0100 Subject: [PATCH 07/17] Sort out right panel callapsing on GroupView --- src/components/structures/GroupView.js | 40 ++++++++++++++++------- src/components/structures/LoggedInView.js | 3 +- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 20fc4841ba..14d5d24bb5 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -216,6 +216,10 @@ export default React.createClass({ }); }, + _onShowRhsClick: function(ev) { + dis.dispatch({ action: 'show_right_panel' }); + }, + _onEditClick: function() { this.setState({ editing: true, @@ -384,8 +388,8 @@ export default React.createClass({ let avatarNode; let nameNode; let shortDescNode; - let rightButtons; let roomBody; + let rightButtons = []; const headerClasses = { mx_GroupView_header: true, }; @@ -428,15 +432,19 @@ export default React.createClass({ placeholder={_t('Description')} tabIndex="2" />; - rightButtons = - + rightButtons.push( + {_t('Save')} - + ); + rightButtons.push( + {_t("Cancel")}/ - ; + ); roomBody =