Merge pull request #4006 from matrix-org/jryans/user-list-deco
Add shields to member list, move power label to text
This commit is contained in:
commit
68b2454920
9 changed files with 151 additions and 264 deletions
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
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.
|
||||||
|
@ -19,6 +20,15 @@ limitations under the License.
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
.mx_E2EIcon {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2px;
|
||||||
|
right: 7px;
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EntityTile:hover {
|
.mx_EntityTile:hover {
|
||||||
|
@ -30,7 +40,7 @@ limitations under the License.
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(50% - 8px); // center
|
top: calc(50% - 8px); // center
|
||||||
right: 10px;
|
right: -8px;
|
||||||
mask: url('$(res)/img/member_chevron.png');
|
mask: url('$(res)/img/member_chevron.png');
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
|
@ -64,14 +74,6 @@ limitations under the License.
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EntityTile_power {
|
|
||||||
position: absolute;
|
|
||||||
width: 16px;
|
|
||||||
height: 17px;
|
|
||||||
top: 0px;
|
|
||||||
right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EntityTile_name,
|
.mx_EntityTile_name,
|
||||||
.mx_GroupRoomTile_name {
|
.mx_GroupRoomTile_name {
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
|
@ -83,6 +85,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_EntityTile_details {
|
.mx_EntityTile_details {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EntityTile_ellipsis .mx_EntityTile_name {
|
.mx_EntityTile_ellipsis .mx_EntityTile_name {
|
||||||
|
@ -112,10 +115,6 @@ limitations under the License.
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EntityTile:not(.mx_EntityTile_noHover):hover .mx_EntityTile_name {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EntityTile_subtext {
|
.mx_EntityTile_subtext {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
@ -123,3 +122,17 @@ limitations under the License.
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: clip;
|
text-overflow: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EntityTile_power {
|
||||||
|
padding-inline-start: 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: $notice-secondary-color;
|
||||||
|
max-width: 6em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EntityTile:hover .mx_EntityTile_power {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg width="16px" height="17px" viewBox="-1 -1 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
|
||||||
<!-- Generator: sketchtool 3.4.4 (395) - http://www.bohemiancoding.com/sketch -->
|
|
||||||
<title>icons_owner</title>
|
|
||||||
<desc>Created with sketchtool.</desc>
|
|
||||||
<defs></defs>
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
|
||||||
<g id="02_19-Room-contextual-menu-hover" sketch:type="MSArtboardGroup" transform="translate(-1000.000000, -128.000000)">
|
|
||||||
<g id="people_open" sketch:type="MSLayerGroup" transform="translate(966.000000, 59.000000)">
|
|
||||||
<g id="icons_owner" transform="translate(35.000000, 70.000000)" sketch:type="MSShapeGroup">
|
|
||||||
<path d="M0.441894529,1.80537109 C2.59277353,3.03442388 4.25305977,2.17675781 5.9832796,0.805371094 C8.01666135,2.17675787 9.50756797,3.12670903 11.6293941,1.80537109 C11.6293941,7.01538067 11.9379879,12.2253912 5.9832796,12.2253906 C0.0285712975,12.2253901 0.441894531,7.01538067 0.441894529,1.80537109 Z" id="Path-2-Copy" stroke="#FFFFFF" fill="#F6A623"></path>
|
|
||||||
<polygon id="Star-1" fill="#FFFFFF" points="6 8.8 3.88397309 9.91246118 4.28809827 7.55623059 2.57619654 5.88753882 4.94198655 5.54376941 6 3.4 7.05801345 5.54376941 9.42380346 5.88753882 7.71190173 7.55623059 8.11602691 9.91246118 "></polygon>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg width="16px" height="17px" viewBox="-1 -1 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
|
||||||
<!-- Generator: sketchtool 3.4.4 (395) - http://www.bohemiancoding.com/sketch -->
|
|
||||||
<title>icons_admin</title>
|
|
||||||
<desc>Created with sketchtool.</desc>
|
|
||||||
<defs></defs>
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
|
||||||
<g id="02_19-Room-contextual-menu-hover" sketch:type="MSArtboardGroup" transform="translate(-1000.000000, -172.000000)" stroke="#FFFFFF" fill="#C2C5AF">
|
|
||||||
<g id="people_open" sketch:type="MSLayerGroup" transform="translate(966.000000, 59.000000)">
|
|
||||||
<g id="icons_admin" transform="translate(35.000000, 114.000000)" sketch:type="MSShapeGroup">
|
|
||||||
<path d="M0.441894529,1.80537109 C2.59277353,3.03442388 4.25305977,2.17675781 5.9832796,0.805371094 C8.01666135,2.17675787 9.50756797,3.12670903 11.6293941,1.80537109 C11.6293941,7.01538067 11.9379879,12.2253912 5.9832796,12.2253906 C0.0285712975,12.2253901 0.441894531,7.01538067 0.441894529,1.80537109 Z" id="Path-2-Copy-2"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.3 KiB |
137
src/Entities.js
137
src/Entities.js
|
@ -1,137 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015, 2016 OpenMarket 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 * as sdk from './index';
|
|
||||||
|
|
||||||
function isMatch(query, name, uid) {
|
|
||||||
query = query.toLowerCase();
|
|
||||||
name = name.toLowerCase();
|
|
||||||
uid = uid.toLowerCase();
|
|
||||||
|
|
||||||
// direct prefix matches
|
|
||||||
if (name.indexOf(query) === 0 || uid.indexOf(query) === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// strip @ on uid and try matching again
|
|
||||||
if (uid.length > 1 && uid[0] === "@" && uid.substring(1).indexOf(query) === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// split spaces in name and try matching constituent parts
|
|
||||||
const parts = name.split(" ");
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
|
||||||
if (parts[i].indexOf(query) === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Converts various data models to Entity objects.
|
|
||||||
*
|
|
||||||
* Entity objects provide an interface for UI components to use to display
|
|
||||||
* members in a data-agnostic way. This means they don't need to care if the
|
|
||||||
* underlying data model is a RoomMember, User or 3PID data structure, it just
|
|
||||||
* cares about rendering.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Entity {
|
|
||||||
constructor(model) {
|
|
||||||
this.model = model;
|
|
||||||
}
|
|
||||||
|
|
||||||
getJsx() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
matches(queryString) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MemberEntity extends Entity {
|
|
||||||
getJsx() {
|
|
||||||
const MemberTile = sdk.getComponent("rooms.MemberTile");
|
|
||||||
return (
|
|
||||||
<MemberTile key={this.model.userId} member={this.model} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
matches(queryString) {
|
|
||||||
return isMatch(queryString, this.model.name, this.model.userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserEntity extends Entity {
|
|
||||||
constructor(model, showInviteButton, inviteFn) {
|
|
||||||
super(model);
|
|
||||||
this.showInviteButton = Boolean(showInviteButton);
|
|
||||||
this.inviteFn = inviteFn;
|
|
||||||
this.onClick = this.onClick.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClick() {
|
|
||||||
if (this.inviteFn) {
|
|
||||||
this.inviteFn(this.model.userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getJsx() {
|
|
||||||
const UserTile = sdk.getComponent("rooms.UserTile");
|
|
||||||
return (
|
|
||||||
<UserTile key={this.model.userId} user={this.model}
|
|
||||||
showInviteButton={this.showInviteButton} onClick={this.onClick} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
matches(queryString) {
|
|
||||||
const name = this.model.displayName || this.model.userId;
|
|
||||||
return isMatch(queryString, name, this.model.userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function newEntity(jsx, matchFn) {
|
|
||||||
const entity = new Entity();
|
|
||||||
entity.getJsx = function() {
|
|
||||||
return jsx;
|
|
||||||
};
|
|
||||||
entity.matches = matchFn;
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {RoomMember[]} members
|
|
||||||
* @return {Entity[]}
|
|
||||||
*/
|
|
||||||
export function fromRoomMembers(members) {
|
|
||||||
return members.map(function(m) {
|
|
||||||
return new MemberEntity(m);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {User[]} users
|
|
||||||
* @param {boolean} showInviteButton
|
|
||||||
* @param {Function} inviteFn Called with the user ID.
|
|
||||||
* @return {Entity[]}
|
|
||||||
*/
|
|
||||||
export function fromUsers(users, showInviteButton, inviteFn) {
|
|
||||||
return users.map(function(u) {
|
|
||||||
return new UserEntity(u, showInviteButton, inviteFn);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
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.
|
||||||
|
@ -22,7 +23,7 @@ import * as sdk from '../../../index';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import E2EIcon from './E2EIcon';
|
||||||
|
|
||||||
const PRESENCE_CLASS = {
|
const PRESENCE_CLASS = {
|
||||||
"offline": "mx_EntityTile_offline",
|
"offline": "mx_EntityTile_offline",
|
||||||
|
@ -30,7 +31,6 @@ const PRESENCE_CLASS = {
|
||||||
"unavailable": "mx_EntityTile_unavailable",
|
"unavailable": "mx_EntityTile_unavailable",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function presenceClassForMember(presenceState, lastActiveAgo, showPresence) {
|
function presenceClassForMember(presenceState, lastActiveAgo, showPresence) {
|
||||||
if (showPresence === false) {
|
if (showPresence === false) {
|
||||||
return 'mx_EntityTile_online_beenactive';
|
return 'mx_EntityTile_online_beenactive';
|
||||||
|
@ -69,6 +69,7 @@ const EntityTile = createReactClass({
|
||||||
suppressOnHover: PropTypes.bool,
|
suppressOnHover: PropTypes.bool,
|
||||||
showPresence: PropTypes.bool,
|
showPresence: PropTypes.bool,
|
||||||
subtextLabel: PropTypes.string,
|
subtextLabel: PropTypes.string,
|
||||||
|
e2eStatus: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -156,18 +157,20 @@ const EntityTile = createReactClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let power;
|
let powerLabel;
|
||||||
const powerStatus = this.props.powerStatus;
|
const powerStatus = this.props.powerStatus;
|
||||||
if (powerStatus) {
|
if (powerStatus) {
|
||||||
const src = {
|
const powerText = {
|
||||||
[EntityTile.POWER_STATUS_MODERATOR]: require("../../../../res/img/mod.svg"),
|
[EntityTile.POWER_STATUS_MODERATOR]: _t("Mod"),
|
||||||
[EntityTile.POWER_STATUS_ADMIN]: require("../../../../res/img/admin.svg"),
|
|
||||||
}[powerStatus];
|
|
||||||
const alt = {
|
|
||||||
[EntityTile.POWER_STATUS_MODERATOR]: _t("Moderator"),
|
|
||||||
[EntityTile.POWER_STATUS_ADMIN]: _t("Admin"),
|
[EntityTile.POWER_STATUS_ADMIN]: _t("Admin"),
|
||||||
}[powerStatus];
|
}[powerStatus];
|
||||||
power = <img src={src} className="mx_EntityTile_power" width="16" height="17" alt={alt} />;
|
powerLabel = <div className="mx_EntityTile_power">{powerText}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let e2eIcon;
|
||||||
|
const { e2eStatus } = this.props;
|
||||||
|
if (e2eStatus) {
|
||||||
|
e2eIcon = <E2EIcon status={e2eStatus} isUser={true} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
@ -181,9 +184,10 @@ const EntityTile = createReactClass({
|
||||||
onClick={this.props.onClick}>
|
onClick={this.props.onClick}>
|
||||||
<div className="mx_EntityTile_avatar">
|
<div className="mx_EntityTile_avatar">
|
||||||
{ av }
|
{ av }
|
||||||
{ power }
|
{ e2eIcon }
|
||||||
</div>
|
</div>
|
||||||
{ nameEl }
|
{ nameEl }
|
||||||
|
{ powerLabel }
|
||||||
{ inviteButton }
|
{ inviteButton }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -194,5 +198,4 @@ const EntityTile = createReactClass({
|
||||||
EntityTile.POWER_STATUS_MODERATOR = "moderator";
|
EntityTile.POWER_STATUS_MODERATOR = "moderator";
|
||||||
EntityTile.POWER_STATUS_ADMIN = "admin";
|
EntityTile.POWER_STATUS_ADMIN = "admin";
|
||||||
|
|
||||||
|
|
||||||
export default EntityTile;
|
export default EntityTile;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
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.
|
||||||
|
@ -22,6 +22,7 @@ import createReactClass from 'create-react-class';
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import dis from "../../../dispatcher";
|
import dis from "../../../dispatcher";
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'MemberTile',
|
displayName: 'MemberTile',
|
||||||
|
@ -40,29 +41,101 @@ export default createReactClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
statusMessage: this.getStatusMessage(),
|
statusMessage: this.getStatusMessage(),
|
||||||
|
isRoomEncrypted: false,
|
||||||
|
e2eStatus: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
const cli = MatrixClientPeg.get();
|
||||||
return;
|
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||||
|
const { user } = this.props.member;
|
||||||
|
if (user) {
|
||||||
|
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const { user } = this.props.member;
|
|
||||||
if (!user) {
|
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||||
return;
|
const { roomId } = this.props.member;
|
||||||
|
if (roomId) {
|
||||||
|
const isRoomEncrypted = cli.isRoomEncrypted(roomId);
|
||||||
|
this.setState({
|
||||||
|
isRoomEncrypted,
|
||||||
|
});
|
||||||
|
if (isRoomEncrypted) {
|
||||||
|
cli.on("userTrustStatusChanged", this.onUserTrustStatusChanged);
|
||||||
|
this.updateE2EStatus();
|
||||||
|
} else {
|
||||||
|
// Listen for room to become encrypted
|
||||||
|
cli.on("RoomState.events", this.onRoomStateEvents);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
const { user } = this.props.member;
|
const { user } = this.props.member;
|
||||||
if (!user) {
|
if (user) {
|
||||||
|
user.removeListener(
|
||||||
|
"User._unstable_statusMessage",
|
||||||
|
this._onStatusMessageCommitted,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cli) {
|
||||||
|
cli.removeListener("RoomState.events", this.onRoomStateEvents);
|
||||||
|
cli.removeListener("userTrustStatusChanged", this.onUserTrustStatusChanged);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onRoomStateEvents: function(ev) {
|
||||||
|
if (ev.getType() !== "m.room.encryption") return;
|
||||||
|
const { roomId } = this.props.member;
|
||||||
|
if (ev.getRoomId() !== roomId) return;
|
||||||
|
|
||||||
|
// The room is encrypted now.
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
cli.removeListener("RoomState.events", this.onRoomStateEvents);
|
||||||
|
this.setState({
|
||||||
|
isRoomEncrypted: true,
|
||||||
|
});
|
||||||
|
this.updateE2EStatus();
|
||||||
|
},
|
||||||
|
|
||||||
|
onUserTrustStatusChanged: function(userId, trustStatus) {
|
||||||
|
if (userId !== this.props.member.userId) return;
|
||||||
|
this.updateE2EStatus();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateE2EStatus: async function() {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const { userId } = this.props.member;
|
||||||
|
const isMe = userId === cli.getUserId();
|
||||||
|
const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified();
|
||||||
|
if (!userVerified) {
|
||||||
|
this.setState({
|
||||||
|
e2eStatus: "normal",
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
user.removeListener(
|
|
||||||
"User._unstable_statusMessage",
|
const devices = await cli.getStoredDevicesForUser(userId);
|
||||||
this._onStatusMessageCommitted,
|
const anyDeviceUnverified = devices.some(device => {
|
||||||
);
|
const { deviceId } = device;
|
||||||
|
// For your own devices, we use the stricter check of cross-signing
|
||||||
|
// verification to encourage everyone to trust their own devices via
|
||||||
|
// cross-signing so that other users can then safely trust you.
|
||||||
|
// For other people's devices, the more general verified check that
|
||||||
|
// includes locally verified devices can be used.
|
||||||
|
const deviceTrust = cli.checkDeviceTrust(userId, deviceId);
|
||||||
|
return isMe ? !deviceTrust.isCrossSigningVerified() : !deviceTrust.isVerified();
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
e2eStatus: anyDeviceUnverified ? "warning" : "verified",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getStatusMessage() {
|
getStatusMessage() {
|
||||||
|
@ -94,6 +167,12 @@ export default createReactClass({
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
nextState.isRoomEncrypted !== this.state.isRoomEncrypted ||
|
||||||
|
nextState.e2eStatus !== this.state.e2eStatus
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -153,14 +232,26 @@ export default createReactClass({
|
||||||
|
|
||||||
const powerStatus = powerStatusMap.get(powerLevel);
|
const powerStatus = powerStatusMap.get(powerLevel);
|
||||||
|
|
||||||
|
let e2eStatus;
|
||||||
|
if (this.state.isRoomEncrypted) {
|
||||||
|
e2eStatus = this.state.e2eStatus;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityTile {...this.props} presenceState={presenceState}
|
<EntityTile
|
||||||
|
{...this.props}
|
||||||
|
presenceState={presenceState}
|
||||||
presenceLastActiveAgo={member.user ? member.user.lastActiveAgo : 0}
|
presenceLastActiveAgo={member.user ? member.user.lastActiveAgo : 0}
|
||||||
presenceLastTs={member.user ? member.user.lastPresenceTs : 0}
|
presenceLastTs={member.user ? member.user.lastPresenceTs : 0}
|
||||||
presenceCurrentlyActive={member.user ? member.user.currentlyActive : false}
|
presenceCurrentlyActive={member.user ? member.user.currentlyActive : false}
|
||||||
avatarJsx={av} title={this.getPowerLabel()} onClick={this.onClick}
|
avatarJsx={av}
|
||||||
name={name} powerStatus={powerStatus} showPresence={this.props.showPresence}
|
title={this.getPowerLabel()}
|
||||||
|
name={name}
|
||||||
|
powerStatus={powerStatus}
|
||||||
|
showPresence={this.props.showPresence}
|
||||||
subtextLabel={statusMessage}
|
subtextLabel={statusMessage}
|
||||||
|
e2eStatus={e2eStatus}
|
||||||
|
onClick={this.onClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
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 createReactClass from 'create-react-class';
|
|
||||||
import * as Avatar from '../../../Avatar';
|
|
||||||
import * as sdk from "../../../index";
|
|
||||||
|
|
||||||
export default createReactClass({
|
|
||||||
displayName: 'UserTile',
|
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
user: PropTypes.any.isRequired, // User
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
const EntityTile = sdk.getComponent("rooms.EntityTile");
|
|
||||||
const user = this.props.user;
|
|
||||||
const name = user.displayName || user.userId;
|
|
||||||
let active = -1;
|
|
||||||
|
|
||||||
// FIXME: make presence data update whenever User.presence changes...
|
|
||||||
active = user.lastActiveAgo ?
|
|
||||||
(Date.now() - (user.lastPresenceTs - user.lastActiveAgo)) : -1;
|
|
||||||
|
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
|
||||||
const avatarJsx = (
|
|
||||||
<BaseAvatar width={36} height={36} name={name} idName={user.userId}
|
|
||||||
url={Avatar.avatarUrlForUser(user, 36, 36, "crop")} />
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EntityTile {...this.props} presenceState={user.presence} presenceActiveAgo={active}
|
|
||||||
presenceCurrentlyActive={user.currentlyActive}
|
|
||||||
name={name} title={user.userId} avatarJsx={avatarJsx} />
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -909,6 +909,7 @@
|
||||||
"Some sessions in this encrypted room are not trusted": "Some sessions in this encrypted room are not trusted",
|
"Some sessions in this encrypted room are not trusted": "Some sessions in this encrypted room are not trusted",
|
||||||
"All sessions in this encrypted room are trusted": "All sessions in this encrypted room are trusted",
|
"All sessions in this encrypted room are trusted": "All sessions in this encrypted room are trusted",
|
||||||
"Edit message": "Edit message",
|
"Edit message": "Edit message",
|
||||||
|
"Mod": "Mod",
|
||||||
"This event could not be displayed": "This event could not be displayed",
|
"This event could not be displayed": "This event could not be displayed",
|
||||||
"%(senderName)s sent an image": "%(senderName)s sent an image",
|
"%(senderName)s sent an image": "%(senderName)s sent an image",
|
||||||
"%(senderName)s sent a video": "%(senderName)s sent a video",
|
"%(senderName)s sent a video": "%(senderName)s sent a video",
|
||||||
|
|
|
@ -112,7 +112,9 @@ describe("GroupMemberList", function() {
|
||||||
const memberList = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_MemberList_joined");
|
const memberList = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_MemberList_joined");
|
||||||
const memberListElement = ReactDOM.findDOMNode(memberList);
|
const memberListElement = ReactDOM.findDOMNode(memberList);
|
||||||
expect(memberListElement).toBeTruthy();
|
expect(memberListElement).toBeTruthy();
|
||||||
expect(memberListElement.textContent).toBe("Test");
|
const userNameElement = memberListElement.querySelector(".mx_EntityTile_name");
|
||||||
|
expect(userNameElement).toBeTruthy();
|
||||||
|
expect(userNameElement.textContent).toBe("Test");
|
||||||
});
|
});
|
||||||
|
|
||||||
httpBackend.when("GET", "/groups/" + groupIdEncoded + "/summary").respond(200, summaryResponse);
|
httpBackend.when("GET", "/groups/" + groupIdEncoded + "/summary").respond(200, summaryResponse);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue