From 26d8c4d2e65a32382660b27ab2048ccd98a9bcaf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 24 Jun 2021 10:03:32 +0100 Subject: [PATCH] Improve design of the multi inviter error dialog --- res/css/views/dialogs/_InviteDialog.scss | 39 +++++++++++ src/RoomInvite.tsx | 67 +++++++++++++++++-- src/components/views/dialogs/InviteDialog.tsx | 67 +++++++------------ src/i18n/strings/en_EN.json | 3 +- 4 files changed, 129 insertions(+), 47 deletions(-) diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss index 175b1cc556..ff7c232ad5 100644 --- a/res/css/views/dialogs/_InviteDialog.scss +++ b/res/css/views/dialogs/_InviteDialog.scss @@ -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; + } + } + } +} diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx index 16141a87e8..e04cd03254 100644 --- a/src/RoomInvite.tsx +++ b/src/RoomInvite.tsx @@ -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, +): 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(
)` anymore, so this is our solution - const description =
{errorList.map(e =>
{e}
)}
; + const description =
+

{ _t("We sent the others, but the below people couldn't be invited to ", {}, { + RoomName: () => { room.name }, + }) }

+
+ { 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
+
+ + { name } + { user.userId } +
+
+ { inviter.getErrorText(addr) } +
+
; + }) } +
+
; 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; +} diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index b24ed6e4ef..17b4a6dc47 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -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(this.state.targets.map(member => [member.userId, member])); + return !showAnyInviteErrors(result.states, room, result.inviter, userMap); } private convertFilter(): Member[] { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 429ffbedef..586031f27a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -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 ": "We sent the others, but the below people couldn't be invited to ", + "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.",