Implement new unreachable state and fix broken string ref (#11748)

* Fix string ref issue

* Implement unreachable state

* Fix eslint failure

* Fix i18n

* Fix i18n again

* Write cypress test

* Write jest test

* Write more jest tests

* Update method name

* Use unstable prefix

* Always use prefix

This is never to going to be in the spec so always use the io.element
prefix

* Update tests

* Remove redundant code from cypress test

* Use unstable prefix

* Refactor code

* Remove supressOnHover prop

* Remove sub-text label

* Join lines

* Remove blank line

* Add jsdoc
This commit is contained in:
R Midhun Suresh 2023-11-07 15:44:30 +05:30 committed by GitHub
parent 6849afd9fc
commit 90419bdffd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 234 additions and 133 deletions

View file

@ -264,8 +264,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
<BaseAvatar url={require("../../../../res/img/ellipsis.svg").default} name="..." size="36px" />
}
name={text}
presenceState="online"
suppressOnHover={true}
showPresence={false}
onClick={() => setTruncateAt(totalCount)}
/>
);

View file

@ -35,12 +35,13 @@ const PowerLabel: Record<PowerStatus, TranslationKey> = {
[PowerStatus.Moderator]: _td("power_level|mod"),
};
export type PresenceState = "offline" | "online" | "unavailable";
export type PresenceState = "offline" | "online" | "unavailable" | "io.element.unreachable";
const PRESENCE_CLASS: Record<PresenceState, string> = {
offline: "mx_EntityTile_offline",
online: "mx_EntityTile_online",
unavailable: "mx_EntityTile_unavailable",
"offline": "mx_EntityTile_offline",
"online": "mx_EntityTile_online",
"unavailable": "mx_EntityTile_unavailable",
"io.element.unreachable": "mx_EntityTile_unreachable",
};
function presenceClassForMember(presenceState?: PresenceState, lastActiveAgo?: number, showPresence?: boolean): string {
@ -75,7 +76,6 @@ interface IProps {
presenceCurrentlyActive?: boolean;
showInviteButton: boolean;
onClick(): void;
suppressOnHover: boolean;
showPresence: boolean;
subtextLabel?: string;
e2eStatus?: E2EState;
@ -93,7 +93,6 @@ export default class EntityTile extends React.PureComponent<IProps, IState> {
presenceLastActiveAgo: 0,
presenceLastTs: 0,
showInviteButton: false,
suppressOnHover: false,
showPresence: true,
};
@ -105,10 +104,27 @@ export default class EntityTile extends React.PureComponent<IProps, IState> {
};
}
/**
* Creates the PresenceLabel component if needed
* @returns The PresenceLabel component if we need to render it, undefined otherwise
*/
private getPresenceLabel(): JSX.Element | undefined {
if (!this.props.showPresence) return;
const activeAgo = this.props.presenceLastActiveAgo
? Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)
: -1;
return (
<PresenceLabel
activeAgo={activeAgo}
currentlyActive={this.props.presenceCurrentlyActive}
presenceState={this.props.presenceState}
/>
);
}
public render(): React.ReactNode {
const mainClassNames: Record<string, boolean> = {
mx_EntityTile: true,
mx_EntityTile_noHover: !!this.props.suppressOnHover,
};
if (this.props.className) mainClassNames[this.props.className] = true;
@ -119,43 +135,13 @@ export default class EntityTile extends React.PureComponent<IProps, IState> {
);
mainClassNames[presenceClass] = true;
let nameEl;
const name = this.props.nameJSX || this.props.name;
if (!this.props.suppressOnHover) {
const activeAgo = this.props.presenceLastActiveAgo
? Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)
: -1;
let presenceLabel: JSX.Element | undefined;
if (this.props.showPresence) {
presenceLabel = (
<PresenceLabel
activeAgo={activeAgo}
currentlyActive={this.props.presenceCurrentlyActive}
presenceState={this.props.presenceState}
/>
);
}
if (this.props.subtextLabel) {
presenceLabel = <span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>;
}
nameEl = (
<div className="mx_EntityTile_details">
<div className="mx_EntityTile_name">{name}</div>
{presenceLabel}
</div>
);
} else if (this.props.subtextLabel) {
nameEl = (
<div className="mx_EntityTile_details">
<div className="mx_EntityTile_name">{name}</div>
<span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>
</div>
);
} else {
nameEl = <div className="mx_EntityTile_name">{name}</div>;
}
const nameAndPresence = (
<div className="mx_EntityTile_details">
<div className="mx_EntityTile_name">{name}</div>
{this.getPresenceLabel()}
</div>
);
let inviteButton;
if (this.props.showInviteButton) {
@ -198,7 +184,7 @@ export default class EntityTile extends React.PureComponent<IProps, IState> {
{av}
{e2eIcon}
</div>
{nameEl}
{nameAndPresence}
{powerLabel}
{inviteButton}
</AccessibleButton>

View file

@ -81,6 +81,7 @@ export default class MemberList extends React.Component<IProps, IState> {
public static contextType = SDKContext;
public context!: React.ContextType<typeof SDKContext>;
private tiles: Map<string, MemberTile> = new Map();
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
super(props);
@ -154,7 +155,7 @@ export default class MemberList extends React.Component<IProps, IState> {
// Attach a SINGLE listener for global presence changes then locate the
// member tile and re-render it. This is more efficient than every tile
// ever attaching their own listener.
const tile = this.refs[user.userId];
const tile = this.tiles.get(user.userId);
if (tile) {
this.updateList(); // reorder the membership list
}
@ -245,8 +246,7 @@ export default class MemberList extends React.Component<IProps, IState> {
<BaseAvatar url={require("../../../../res/img/ellipsis.svg").default} name="..." size="36px" />
}
name={text}
presenceState="online"
suppressOnHover={true}
showPresence={false}
onClick={onClick}
/>
);
@ -307,14 +307,24 @@ export default class MemberList extends React.Component<IProps, IState> {
return members.map((m) => {
if (m instanceof RoomMember) {
// Is a Matrix invite
return <MemberTile key={m.userId} member={m} ref={m.userId} showPresence={this.showPresence} />;
return (
<MemberTile
key={m.userId}
member={m}
ref={(tile) => {
if (tile) this.tiles.set(m.userId, tile);
else this.tiles.delete(m.userId);
}}
showPresence={this.showPresence}
/>
);
} else {
// Is a 3pid invite
return (
<EntityTile
key={m.getStateKey()}
name={m.getContent().display_name}
suppressOnHover={true}
showPresence={false}
onClick={() => this.onPending3pidInviteClick(m)}
/>
);

View file

@ -44,6 +44,8 @@ export default class PresenceLabel extends React.Component<IProps> {
// the 'active ago' ends up being 0.
if (presence && BUSY_PRESENCE_NAME.matches(presence)) return _t("presence|busy");
if (presence === "io.element.unreachable") return _t("presence|unreachable");
if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
const duration = formatDuration(activeAgo);
if (presence === "online") return _t("presence|online_for", { duration: duration });