Merge pull request #6251 from SimonBrandner/ts/entity-and-member-tile

pull/21833/head
Michael Telatynski 2021-06-24 17:05:16 +01:00 committed by GitHub
commit 8f6d31b73c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 107 deletions

View File

@ -17,13 +17,23 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import AccessibleButton from '../elements/AccessibleButton';
import { _t } from '../../../languageHandler';
import { _td } from '../../../languageHandler';
import classNames from "classnames";
import E2EIcon from './E2EIcon';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseAvatar from '../avatars/BaseAvatar';
import PresenceLabel from "./PresenceLabel";
export enum PowerStatus {
Admin = "admin",
Moderator = "moderator",
}
const PowerLabel: Record<PowerStatus, string> = {
[PowerStatus.Admin]: _td("Admin"),
[PowerStatus.Moderator]: _td("Mod"),
}
const PRESENCE_CLASS = {
"offline": "mx_EntityTile_offline",
@ -31,14 +41,14 @@ const PRESENCE_CLASS = {
"unavailable": "mx_EntityTile_unavailable",
};
function presenceClassForMember(presenceState, lastActiveAgo, showPresence) {
function presenceClassForMember(presenceState: string, lastActiveAgo: number, showPresence: boolean): string {
if (showPresence === false) {
return 'mx_EntityTile_online_beenactive';
}
// offline is split into two categories depending on whether we have
// a last_active_ago for them.
if (presenceState == 'offline') {
if (presenceState === 'offline') {
if (lastActiveAgo) {
return PRESENCE_CLASS['offline'] + '_beenactive';
} else {
@ -51,29 +61,32 @@ function presenceClassForMember(presenceState, lastActiveAgo, showPresence) {
}
}
@replaceableComponent("views.rooms.EntityTile")
class EntityTile extends React.Component {
static propTypes = {
name: PropTypes.string,
title: PropTypes.string,
avatarJsx: PropTypes.any, // <BaseAvatar />
className: PropTypes.string,
presenceState: PropTypes.string,
presenceLastActiveAgo: PropTypes.number,
presenceLastTs: PropTypes.number,
presenceCurrentlyActive: PropTypes.bool,
showInviteButton: PropTypes.bool,
shouldComponentUpdate: PropTypes.func,
onClick: PropTypes.func,
suppressOnHover: PropTypes.bool,
showPresence: PropTypes.bool,
subtextLabel: PropTypes.string,
e2eStatus: PropTypes.string,
};
interface IProps {
name?: string;
title?: string;
avatarJsx?: JSX.Element; // <BaseAvatar />
className?: string;
presenceState?: string;
presenceLastActiveAgo?: number;
presenceLastTs?: number;
presenceCurrentlyActive?: boolean;
showInviteButton?: boolean;
onClick?(): void;
suppressOnHover?: boolean;
showPresence?: boolean;
subtextLabel?: string;
e2eStatus?: string;
powerStatus?: PowerStatus;
}
interface IState {
hover: boolean;
}
@replaceableComponent("views.rooms.EntityTile")
export default class EntityTile extends React.PureComponent<IProps, IState> {
static defaultProps = {
shouldComponentUpdate: function(nextProps, nextState) { return true; },
onClick: function() {},
onClick: () => {},
presenceState: "offline",
presenceLastActiveAgo: 0,
presenceLastTs: 0,
@ -82,13 +95,12 @@ class EntityTile extends React.Component {
showPresence: true,
};
state = {
hover: false,
};
constructor(props: IProps) {
super(props);
shouldComponentUpdate(nextProps, nextState) {
if (this.state.hover !== nextState.hover) return true;
return this.props.shouldComponentUpdate(nextProps, nextState);
this.state = {
hover: false,
};
}
render() {
@ -110,7 +122,6 @@ class EntityTile extends React.Component {
const activeAgo = this.props.presenceLastActiveAgo ?
(Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)) : -1;
const PresenceLabel = sdk.getComponent("rooms.PresenceLabel");
let presenceLabel = null;
if (this.props.showPresence) {
presenceLabel = <PresenceLabel activeAgo={activeAgo}
@ -155,10 +166,7 @@ class EntityTile extends React.Component {
let powerLabel;
const powerStatus = this.props.powerStatus;
if (powerStatus) {
const powerText = {
[EntityTile.POWER_STATUS_MODERATOR]: _t("Mod"),
[EntityTile.POWER_STATUS_ADMIN]: _t("Admin"),
}[powerStatus];
const powerText = PowerLabel[powerStatus];
powerLabel = <div className="mx_EntityTile_power">{powerText}</div>;
}
@ -168,14 +176,12 @@ class EntityTile extends React.Component {
e2eIcon = <E2EIcon status={e2eStatus} isUser={true} bordered={true} />;
}
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const av = this.props.avatarJsx ||
<BaseAvatar name={this.props.name} width={36} height={36} aria-hidden="true" />;
// The wrapping div is required to make the magic mouse listener work, for some reason.
return (
<div ref={(c) => this.container = c} >
<div>
<AccessibleButton
className={classNames(mainClassNames)}
title={this.props.title}
@ -193,8 +199,3 @@ class EntityTile extends React.Component {
);
}
}
EntityTile.POWER_STATUS_MODERATOR = "moderator";
EntityTile.POWER_STATUS_ADMIN = "admin";
export default EntityTile;

View File

@ -17,20 +17,33 @@ limitations under the License.
import SettingsStore from "../../../settings/SettingsStore";
import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from "../../../index";
import dis from "../../../dispatcher/dispatcher";
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import {Action} from "../../../dispatcher/actions";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import EntityTile, { PowerStatus } from "./EntityTile";
import MemberAvatar from "./../avatars/MemberAvatar";
interface IProps {
member: RoomMember;
showPresence?: boolean;
}
interface IState {
statusMessage: string;
isRoomEncrypted: boolean;
e2eStatus: string;
}
@replaceableComponent("views.rooms.MemberTile")
export default class MemberTile extends React.Component {
static propTypes = {
member: PropTypes.any.isRequired, // RoomMember
showPresence: PropTypes.bool,
};
export default class MemberTile extends React.Component<IProps, IState> {
private userLastModifiedTime: number;
private memberLastModifiedTime: number;
static defaultProps = {
showPresence: true,
@ -52,7 +65,7 @@ export default class MemberTile extends React.Component {
if (SettingsStore.getValue("feature_custom_status")) {
const { user } = this.props.member;
if (user) {
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
user.on("User._unstable_statusMessage", this.onStatusMessageCommitted);
}
}
@ -80,7 +93,7 @@ export default class MemberTile extends React.Component {
if (user) {
user.removeListener(
"User._unstable_statusMessage",
this._onStatusMessageCommitted,
this.onStatusMessageCommitted,
);
}
@ -91,8 +104,8 @@ export default class MemberTile extends React.Component {
}
}
onRoomStateEvents = ev => {
if (ev.getType() !== "m.room.encryption") return;
private onRoomStateEvents = (ev: MatrixEvent): void => {
if (ev.getType() !== EventType.RoomEncryption) return;
const { roomId } = this.props.member;
if (ev.getRoomId() !== roomId) return;
@ -105,17 +118,17 @@ export default class MemberTile extends React.Component {
this.updateE2EStatus();
};
onUserTrustStatusChanged = (userId, trustStatus) => {
private onUserTrustStatusChanged = (userId: string, trustStatus: string): void => {
if (userId !== this.props.member.userId) return;
this.updateE2EStatus();
};
onDeviceVerificationChanged = (userId, deviceId, deviceInfo) => {
private onDeviceVerificationChanged = (userId: string, deviceId: string, deviceInfo: DeviceInfo): void => {
if (userId !== this.props.member.userId) return;
this.updateE2EStatus();
};
async updateE2EStatus() {
private async updateE2EStatus(): Promise<void> {
const cli = MatrixClientPeg.get();
const { userId } = this.props.member;
const isMe = userId === cli.getUserId();
@ -143,32 +156,32 @@ export default class MemberTile extends React.Component {
});
}
getStatusMessage() {
private getStatusMessage(): string {
const { user } = this.props.member;
if (!user) {
return "";
}
return user._unstable_statusMessage;
return user.unstable_statusMessage;
}
_onStatusMessageCommitted = () => {
private onStatusMessageCommitted = (): void => {
// The `User` object has observed a status message change.
this.setState({
statusMessage: this.getStatusMessage(),
});
};
shouldComponentUpdate(nextProps, nextState) {
shouldComponentUpdate(nextProps: IProps, nextState: IState): boolean {
if (
this.member_last_modified_time === undefined ||
this.member_last_modified_time < nextProps.member.getLastModifiedTime()
this.memberLastModifiedTime === undefined ||
this.memberLastModifiedTime < nextProps.member.getLastModifiedTime()
) {
return true;
}
if (
nextProps.member.user &&
(this.user_last_modified_time === undefined ||
this.user_last_modified_time < nextProps.member.user.getLastModifiedTime())
(this.userLastModifiedTime === undefined ||
this.userLastModifiedTime < nextProps.member.user.getLastModifiedTime())
) {
return true;
}
@ -181,18 +194,18 @@ export default class MemberTile extends React.Component {
return false;
}
onClick = e => {
private onClick = (): void => {
dis.dispatch({
action: Action.ViewUser,
member: this.props.member,
});
};
_getDisplayName() {
private getDisplayName(): string {
return this.props.member.name;
}
getPowerLabel() {
private getPowerLabel(): string {
return _t("%(userName)s (power %(powerLevelNumber)s)", {
userName: this.props.member.userId,
powerLevelNumber: this.props.member.powerLevel,
@ -200,11 +213,8 @@ export default class MemberTile extends React.Component {
}
render() {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const EntityTile = sdk.getComponent('rooms.EntityTile');
const member = this.props.member;
const name = this._getDisplayName();
const name = this.getDisplayName();
const presenceState = member.user ? member.user.presence : null;
let statusMessage = null;
@ -217,13 +227,13 @@ export default class MemberTile extends React.Component {
);
if (member.user) {
this.user_last_modified_time = member.user.getLastModifiedTime();
this.userLastModifiedTime = member.user.getLastModifiedTime();
}
this.member_last_modified_time = member.getLastModifiedTime();
this.memberLastModifiedTime = member.getLastModifiedTime();
const powerStatusMap = new Map([
[100, EntityTile.POWER_STATUS_ADMIN],
[50, EntityTile.POWER_STATUS_MODERATOR],
[100, PowerStatus.Admin],
[50, PowerStatus.Moderator],
]);
// Find the nearest power level with a badge

View File

@ -15,26 +15,23 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
// number of milliseconds ago this user was last active.
// zero = unknown
activeAgo?: number;
// if true, activeAgo is an approximation and "Now" should
// be shown instead
currentlyActive?: boolean;
// offline, online, etc
presenceState?: string;
}
@replaceableComponent("views.rooms.PresenceLabel")
export default class PresenceLabel extends React.Component {
static propTypes = {
// number of milliseconds ago this user was last active.
// zero = unknown
activeAgo: PropTypes.number,
// if true, activeAgo is an approximation and "Now" should
// be shown instead
currentlyActive: PropTypes.bool,
// offline, online, etc
presenceState: PropTypes.string,
};
export default class PresenceLabel extends React.Component<IProps> {
static defaultProps = {
activeAgo: -1,
presenceState: null,
@ -42,29 +39,29 @@ export default class PresenceLabel extends React.Component {
// Return duration as a string using appropriate time units
// XXX: This would be better handled using a culture-aware library, but we don't use one yet.
getDuration(time) {
private getDuration(time: number): string {
if (!time) return;
const t = parseInt(time / 1000);
const t = time / 1000;
const s = t % 60;
const m = parseInt(t / 60) % 60;
const h = parseInt(t / (60 * 60)) % 24;
const d = parseInt(t / (60 * 60 * 24));
const m = t / 60 % 60;
const h = t / (60 * 60) % 24;
const d = t / (60 * 60 * 24);
if (t < 60) {
if (t < 0) {
return _t("%(duration)ss", {duration: 0});
return _t("%(duration)ss", { duration: 0 });
}
return _t("%(duration)ss", {duration: s});
return _t("%(duration)ss", { duration: s });
}
if (t < 60 * 60) {
return _t("%(duration)sm", {duration: m});
return _t("%(duration)sm", { duration: m });
}
if (t < 24 * 60 * 60) {
return _t("%(duration)sh", {duration: h});
return _t("%(duration)sh", { duration: h });
}
return _t("%(duration)sd", {duration: d});
return _t("%(duration)sd", { duration: d });
}
getPrettyPresence(presence, activeAgo, currentlyActive) {
private getPrettyPresence(presence: string, activeAgo: number, currentlyActive: boolean): string {
if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
const duration = this.getDuration(activeAgo);
if (presence === "online") return _t("Online for %(duration)s", { duration: duration });