Improve design of the multi inviter error dialog

This commit is contained in:
Michael Telatynski 2021-06-24 10:03:32 +01:00
parent 0e2f617d94
commit 26d8c4d2e6
4 changed files with 129 additions and 47 deletions

View File

@ -317,3 +317,42 @@ limitations under the License.
.mx_InviteDialog_helpText .mx_AccessibleButton_kind_link {
padding: 0;
}
.mx_InviteDialog_multiInviterError {
> h4 {
font-size: $font-15px;
line-height: $font-24px;
color: $secondary-fg-color;
font-weight: normal;
}
> div {
.mx_InviteDialog_multiInviterError_entry {
margin-bottom: 24px;
.mx_InviteDialog_multiInviterError_entry_userProfile {
.mx_InviteDialog_multiInviterError_entry_name {
margin-left: 6px;
font-size: $font-15px;
line-height: $font-24px;
font-weight: $font-semi-bold;
color: $primary-fg-color;
}
.mx_InviteDialog_multiInviterError_entry_userId {
margin-left: 6px;
font-size: $font-12px;
line-height: $font-15px;
color: $tertiary-fg-color;
}
}
.mx_InviteDialog_multiInviterError_entry_error {
margin-left: 32px;
font-size: $font-15px;
line-height: $font-24px;
color: $notice-primary-color;
}
}
}
}

View File

@ -17,6 +17,7 @@ limitations under the License.
import React from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { User } from "matrix-js-sdk/src/models/user";
import { MatrixClientPeg } from './MatrixClientPeg';
import MultiInviter, { CompletionStates } from './utils/MultiInviter';
@ -26,6 +27,8 @@ import { _t } from './languageHandler';
import InviteDialog, { KIND_DM, KIND_INVITE } from "./components/views/dialogs/InviteDialog";
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
import BaseAvatar from "./components/views/avatars/BaseAvatar";
import { mediaFromMxc } from "./customisations/Media";
export interface IInviteResult {
states: CompletionStates;
@ -116,7 +119,12 @@ export function inviteUsersToRoom(roomId: string, userIds: string[]): Promise<vo
});
}
export function showAnyInviteErrors(states: CompletionStates, room: Room, inviter: MultiInviter): boolean {
export function showAnyInviteErrors(
states: CompletionStates,
room: Room,
inviter: MultiInviter,
userMap?: Map<string, Member>,
): boolean {
// Show user any errors
const failedUsers = Object.keys(states).filter(a => states[a] === 'error');
if (failedUsers.length === 1 && inviter.fatal) {
@ -138,13 +146,41 @@ export function showAnyInviteErrors(states: CompletionStates, room: Room, invite
}
}
const cli = MatrixClientPeg.get();
if (errorList.length > 0) {
// React 16 doesn't let us use `errorList.join(<br />)` anymore, so this is our solution
const description = <div>{errorList.map(e => <div key={e}>{e}</div>)}</div>;
const description = <div className="mx_InviteDialog_multiInviterError">
<h4>{ _t("We sent the others, but the below people couldn't be invited to <RoomName/>", {}, {
RoomName: () => <b>{ room.name }</b>,
}) }</h4>
<div>
{ failedUsers.map(addr => {
const user = userMap?.get(addr) || cli.getUser(addr);
const name = (user as Member).name || (user as User).rawDisplayName;
const avatarUrl = (user as Member).getMxcAvatarUrl?.() || (user as User).avatarUrl;
return <div key={addr} className="mx_InviteDialog_multiInviterError_entry">
<div className="mx_InviteDialog_multiInviterError_entry_userProfile">
<BaseAvatar
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null}
name={name}
idName={user.userId}
width={24}
height={24}
/>
<span className="mx_InviteDialog_multiInviterError_entry_name">{ name }</span>
<span className="mx_InviteDialog_multiInviterError_entry_userId">{ user.userId }</span>
</div>
<div className="mx_InviteDialog_multiInviterError_entry_error">
{ inviter.getErrorText(addr) }
</div>
</div>;
}) }
</div>
</div>;
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite the following users to the room', '', ErrorDialog, {
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
Modal.createTrackedDialog("Some invites could not be sent", "", ErrorDialog, {
title: _t("Some invites couldn't be sent"),
description,
});
return false;
@ -153,3 +189,26 @@ export function showAnyInviteErrors(states: CompletionStates, room: Room, invite
return true;
}
// This is the interface that is expected by various components in the Invite Dialog and RoomInvite.
// It is a bit awkward because it also matches the RoomMember class from the js-sdk with some extra support
// for 3PIDs/email addresses.
export abstract class Member {
/**
* The display name of this Member. For users this should be their profile's display
* name or user ID if none set. For 3PIDs this should be the 3PID address (email).
*/
public abstract get name(): string;
/**
* The ID of this Member. For users this should be their user ID. For 3PIDs this should
* be the 3PID address (email).
*/
public abstract get userId(): string;
/**
* Gets the MXC URL of this Member's avatar. For users this should be their profile's
* avatar MXC URL or null if none set. For 3PIDs this should always be null.
*/
public abstract getMxcAvatarUrl(): string;
}

View File

@ -17,42 +17,46 @@ limitations under the License.
import React, { createRef } from 'react';
import classNames from 'classnames';
import {_t, _td} from "../../../languageHandler";
import { _t, _td } from "../../../languageHandler";
import * as sdk from "../../../index";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { makeRoomPermalink, makeUserPermalink } from "../../../utils/permalinks/Permalinks";
import DMRoomMap from "../../../utils/DMRoomMap";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import SdkConfig from "../../../SdkConfig";
import * as Email from "../../../email";
import {getDefaultIdentityServerUrl, useDefaultIdentityServer} from "../../../utils/IdentityServerUtils";
import {abbreviateUrl} from "../../../utils/UrlUtils";
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from "../../../utils/IdentityServerUtils";
import { abbreviateUrl } from "../../../utils/UrlUtils";
import dis from "../../../dispatcher/dispatcher";
import IdentityAuthClient from "../../../IdentityAuthClient";
import Modal from "../../../Modal";
import {humanizeTime} from "../../../utils/humanize";
import { humanizeTime } from "../../../utils/humanize";
import createRoom, {
canEncryptToAllUsers, ensureDMExists, findDMForUser, privateShouldBeEncrypted,
canEncryptToAllUsers,
ensureDMExists,
findDMForUser,
privateShouldBeEncrypted,
} from "../../../createRoom";
import {
IInviteResult,
inviteMultipleToRoom,
Member,
showAnyInviteErrors,
showCommunityInviteDialog,
} from "../../../RoomInvite";
import {Key} from "../../../Keyboard";
import {Action} from "../../../dispatcher/actions";
import {DefaultTagID} from "../../../stores/room-list/models";
import { Key } from "../../../Keyboard";
import { Action } from "../../../dispatcher/actions";
import { DefaultTagID } from "../../../stores/room-list/models";
import RoomListStore from "../../../stores/room-list/RoomListStore";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
import SettingsStore from "../../../settings/SettingsStore";
import {UIFeature} from "../../../settings/UIFeature";
import { UIFeature } from "../../../settings/UIFeature";
import CountlyAnalytics from "../../../CountlyAnalytics";
import {Room} from "matrix-js-sdk/src/models/room";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
import {getAddressType} from "../../../UserAddress";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { mediaFromMxc } from "../../../customisations/Media";
import { getAddressType } from "../../../UserAddress";
import BaseAvatar from '../avatars/BaseAvatar';
import AccessibleButton from '../elements/AccessibleButton';
import { compare } from '../../../utils/strings';
@ -79,35 +83,13 @@ export const KIND_CALL_TRANSFER = "call_transfer";
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
// This is the interface that is expected by various components in this file. It is a bit
// awkward because it also matches the RoomMember class from the js-sdk with some extra support
// for 3PIDs/email addresses.
abstract class Member {
/**
* The display name of this Member. For users this should be their profile's display
* name or user ID if none set. For 3PIDs this should be the 3PID address (email).
*/
public abstract get name(): string;
/**
* The ID of this Member. For users this should be their user ID. For 3PIDs this should
* be the 3PID address (email).
*/
public abstract get userId(): string;
/**
* Gets the MXC URL of this Member's avatar. For users this should be their profile's
* avatar MXC URL or null if none set. For 3PIDs this should always be null.
*/
public abstract getMxcAvatarUrl(): string;
}
class DirectoryMember extends Member {
private readonly _userId: string;
private readonly displayName: string;
private readonly avatarUrl: string;
constructor(userDirResult: {user_id: string, display_name: string, avatar_url: string}) {
// eslint-disable-next-line camelcase
constructor(userDirResult: { user_id: string, display_name: string, avatar_url: string }) {
super();
this._userId = userDirResult.user_id;
this.displayName = userDirResult.display_name;
@ -608,7 +590,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
private shouldAbortAfterInviteError(result: IInviteResult, room: Room): boolean {
this.setState({ busy: false });
return !showAnyInviteErrors(result.states, room, result.inviter);
const userMap = new Map<string, Member>(this.state.targets.map(member => [member.userId, member]));
return !showAnyInviteErrors(result.states, room, result.inviter, userMap);
}
private convertFilter(): Member[] {

View File

@ -396,7 +396,8 @@
"Failed to invite": "Failed to invite",
"Operation failed": "Operation failed",
"Failed to invite users to the room:": "Failed to invite users to the room:",
"Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:",
"We sent the others, but the below people couldn't be invited to <RoomName/>": "We sent the others, but the below people couldn't be invited to <RoomName/>",
"Some invites couldn't be sent": "Some invites couldn't be sent",
"You need to be logged in.": "You need to be logged in.",
"You need to be able to invite users to do that.": "You need to be able to invite users to do that.",
"Unable to create widget.": "Unable to create widget.",