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:
parent
6849afd9fc
commit
90419bdffd
9 changed files with 234 additions and 133 deletions
|
@ -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)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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 });
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue