mirror of https://github.com/vector-im/riot-web
Improve design of the multi inviter error dialog
parent
0e2f617d94
commit
26d8c4d2e6
|
@ -317,3 +317,42 @@ limitations under the License.
|
||||||
.mx_InviteDialog_helpText .mx_AccessibleButton_kind_link {
|
.mx_InviteDialog_helpText .mx_AccessibleButton_kind_link {
|
||||||
padding: 0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
import { User } from "matrix-js-sdk/src/models/user";
|
||||||
|
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
import MultiInviter, { CompletionStates } from './utils/MultiInviter';
|
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 InviteDialog, { KIND_DM, KIND_INVITE } from "./components/views/dialogs/InviteDialog";
|
||||||
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
||||||
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
|
||||||
|
import BaseAvatar from "./components/views/avatars/BaseAvatar";
|
||||||
|
import { mediaFromMxc } from "./customisations/Media";
|
||||||
|
|
||||||
export interface IInviteResult {
|
export interface IInviteResult {
|
||||||
states: CompletionStates;
|
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
|
// Show user any errors
|
||||||
const failedUsers = Object.keys(states).filter(a => states[a] === 'error');
|
const failedUsers = Object.keys(states).filter(a => states[a] === 'error');
|
||||||
if (failedUsers.length === 1 && inviter.fatal) {
|
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) {
|
if (errorList.length > 0) {
|
||||||
// React 16 doesn't let us use `errorList.join(<br />)` anymore, so this is our solution
|
// 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");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Failed to invite the following users to the room', '', ErrorDialog, {
|
Modal.createTrackedDialog("Some invites could not be sent", "", ErrorDialog, {
|
||||||
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
|
title: _t("Some invites couldn't be sent"),
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
|
@ -153,3 +189,26 @@ export function showAnyInviteErrors(states: CompletionStates, room: Room, invite
|
||||||
|
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -17,42 +17,46 @@ limitations under the License.
|
||||||
import React, { createRef } from 'react';
|
import React, { createRef } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import {_t, _td} from "../../../languageHandler";
|
import { _t, _td } from "../../../languageHandler";
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
|
import { makeRoomPermalink, makeUserPermalink } from "../../../utils/permalinks/Permalinks";
|
||||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
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 SdkConfig from "../../../SdkConfig";
|
||||||
import * as Email from "../../../email";
|
import * as Email from "../../../email";
|
||||||
import {getDefaultIdentityServerUrl, useDefaultIdentityServer} from "../../../utils/IdentityServerUtils";
|
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from "../../../utils/IdentityServerUtils";
|
||||||
import {abbreviateUrl} from "../../../utils/UrlUtils";
|
import { abbreviateUrl } from "../../../utils/UrlUtils";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import {humanizeTime} from "../../../utils/humanize";
|
import { humanizeTime } from "../../../utils/humanize";
|
||||||
import createRoom, {
|
import createRoom, {
|
||||||
canEncryptToAllUsers, ensureDMExists, findDMForUser, privateShouldBeEncrypted,
|
canEncryptToAllUsers,
|
||||||
|
ensureDMExists,
|
||||||
|
findDMForUser,
|
||||||
|
privateShouldBeEncrypted,
|
||||||
} from "../../../createRoom";
|
} from "../../../createRoom";
|
||||||
import {
|
import {
|
||||||
IInviteResult,
|
IInviteResult,
|
||||||
inviteMultipleToRoom,
|
inviteMultipleToRoom,
|
||||||
|
Member,
|
||||||
showAnyInviteErrors,
|
showAnyInviteErrors,
|
||||||
showCommunityInviteDialog,
|
showCommunityInviteDialog,
|
||||||
} from "../../../RoomInvite";
|
} from "../../../RoomInvite";
|
||||||
import {Key} from "../../../Keyboard";
|
import { Key } from "../../../Keyboard";
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import {DefaultTagID} from "../../../stores/room-list/models";
|
import { DefaultTagID } from "../../../stores/room-list/models";
|
||||||
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||||
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {UIFeature} from "../../../settings/UIFeature";
|
import { UIFeature } from "../../../settings/UIFeature";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
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 { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import {mediaFromMxc} from "../../../customisations/Media";
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
import {getAddressType} from "../../../UserAddress";
|
import { getAddressType } from "../../../UserAddress";
|
||||||
import BaseAvatar from '../avatars/BaseAvatar';
|
import BaseAvatar from '../avatars/BaseAvatar';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import { compare } from '../../../utils/strings';
|
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 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
|
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 {
|
class DirectoryMember extends Member {
|
||||||
private readonly _userId: string;
|
private readonly _userId: string;
|
||||||
private readonly displayName: string;
|
private readonly displayName: string;
|
||||||
private readonly avatarUrl: 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();
|
super();
|
||||||
this._userId = userDirResult.user_id;
|
this._userId = userDirResult.user_id;
|
||||||
this.displayName = userDirResult.display_name;
|
this.displayName = userDirResult.display_name;
|
||||||
|
@ -608,7 +590,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
|
|
||||||
private shouldAbortAfterInviteError(result: IInviteResult, room: Room): boolean {
|
private shouldAbortAfterInviteError(result: IInviteResult, room: Room): boolean {
|
||||||
this.setState({ busy: false });
|
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[] {
|
private convertFilter(): Member[] {
|
||||||
|
|
|
@ -396,7 +396,8 @@
|
||||||
"Failed to invite": "Failed to invite",
|
"Failed to invite": "Failed to invite",
|
||||||
"Operation failed": "Operation failed",
|
"Operation failed": "Operation failed",
|
||||||
"Failed to invite users to the room:": "Failed to invite users to the room:",
|
"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 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.",
|
"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.",
|
"Unable to create widget.": "Unable to create widget.",
|
||||||
|
|
Loading…
Reference in New Issue