mirror of
https://github.com/vector-im/element-web.git
synced 2025-12-15 22:31:32 +01:00
Modal: remove support for onFinished callback (#29852)
* Fix up type for `finished` result of Modal The `finished` promise can be called with an empty array, for example if the dialog is closed by a background click. This was not correctly represented in the typing. Fix that, and add some documentation while we're at it. * Type fixes to onFinished callbacks from Modal These can all be called with zero arguments, despite what the type annotations may say, so mark them accordingly. * Remove uses of Modal `onFinished` property ... because it is confusing. Instead, use the `finished` promise returned by `createDialog`. * Modal: remove support for now-unused `onFinished` prop * StopGapWidgetDriver: use `await` instead of promise chaining * Fix up unit tests
This commit is contained in:
parent
ce1055f5fe
commit
f25fbdebc7
@ -679,7 +679,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
|
||||
private showICEFallbackPrompt(): void {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
Modal.createDialog(
|
||||
const { finished } = Modal.createDialog(
|
||||
QuestionDialog,
|
||||
{
|
||||
title: _t("voip|misconfigured_server"),
|
||||
@ -703,13 +703,14 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
server: new URL(FALLBACK_ICE_SERVER).pathname,
|
||||
}),
|
||||
cancelButton: _t("action|ok"),
|
||||
onFinished: (allow) => {
|
||||
SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow);
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
finished.then(([allow]) => {
|
||||
SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow);
|
||||
});
|
||||
}
|
||||
|
||||
private showMediaCaptureError(call: MatrixCall): void {
|
||||
|
||||
@ -469,12 +469,15 @@ type TryAgainFunction = () => void;
|
||||
* @param tryAgain OPTIONAL function to call on try again button from error dialog
|
||||
*/
|
||||
function onFailedDelegatedAuthLogin(description: string | ReactNode, tryAgain?: TryAgainFunction): void {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
const { finished } = Modal.createDialog(ErrorDialog, {
|
||||
title: _t("auth|oidc|error_title"),
|
||||
description,
|
||||
button: _t("action|try_again"),
|
||||
});
|
||||
|
||||
finished.then(([shouldTryAgain]) => {
|
||||
// if we have a tryAgain callback, call it the primary 'try again' button was clicked in the dialog
|
||||
onFinished: tryAgain ? (shouldTryAgain?: boolean) => shouldTryAgain && tryAgain() : undefined,
|
||||
if (shouldTryAgain) tryAgain?.();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -30,12 +30,25 @@ export type ComponentType =
|
||||
}>
|
||||
| React.ComponentType<any>;
|
||||
|
||||
// Generic type which returns the props of the Modal component with the onFinished being optional.
|
||||
/**
|
||||
* The parameter types of the `onFinished` callback property exposed by the component which forms the
|
||||
* body of the dialog.
|
||||
*
|
||||
* @typeParam C - The type of the React component which forms the body of the dialog.
|
||||
*/
|
||||
type OnFinishedParams<C extends ComponentType> = Parameters<React.ComponentProps<C>["onFinished"]>;
|
||||
|
||||
/**
|
||||
* The properties exposed by the `props` argument to {@link Modal.createDialog}: the same as
|
||||
* those exposed by the underlying component, with the exception of `onFinished`, which is provided by
|
||||
* `createDialog`.
|
||||
*
|
||||
* @typeParam C - The type of the React component which forms the body of the dialog.
|
||||
*/
|
||||
export type ComponentProps<C extends ComponentType> = Defaultize<
|
||||
Omit<React.ComponentProps<C>, "onFinished">,
|
||||
C["defaultProps"]
|
||||
> &
|
||||
Partial<Pick<React.ComponentProps<C>, "onFinished">>;
|
||||
>;
|
||||
|
||||
export interface IModal<C extends ComponentType> {
|
||||
elem: React.ReactNode;
|
||||
@ -43,15 +56,44 @@ export interface IModal<C extends ComponentType> {
|
||||
beforeClosePromise?: Promise<boolean>;
|
||||
closeReason?: ModalCloseReason;
|
||||
onBeforeClose?(reason?: ModalCloseReason): Promise<boolean>;
|
||||
onFinished: ComponentProps<C>["onFinished"];
|
||||
close(...args: Parameters<ComponentProps<C>["onFinished"]>): void;
|
||||
|
||||
/**
|
||||
* Run the {@link deferred} with the given arguments, and close this modal.
|
||||
*
|
||||
* This method is passed as the `onFinished` callback to the underlying component,
|
||||
* as well as being returned by {@link Modal.createDialog} to the caller.
|
||||
*/
|
||||
close(...args: OnFinishedParams<C> | []): void;
|
||||
|
||||
hidden?: boolean;
|
||||
deferred?: IDeferred<Parameters<ComponentProps<C>["onFinished"]>>;
|
||||
|
||||
/** A deferred to resolve when the dialog closes, with the results as provided by
|
||||
* the call to {@link close} (normally from the `onFinished` callback).
|
||||
*/
|
||||
deferred?: IDeferred<OnFinishedParams<C> | []>;
|
||||
}
|
||||
|
||||
/** The result of {@link Modal.createDialog}.
|
||||
*
|
||||
* @typeParam C - The type of the React component which forms the body of the dialog.
|
||||
*/
|
||||
export interface IHandle<C extends ComponentType> {
|
||||
finished: Promise<Parameters<ComponentProps<C>["onFinished"]>>;
|
||||
close(...args: Parameters<ComponentProps<C>["onFinished"]>): void;
|
||||
/**
|
||||
* A promise which will resolve when the dialog closes.
|
||||
*
|
||||
* If the dialog body component calls the `onFinished` property, or the caller calls {@link close},
|
||||
* the promise resolves with an array holding the arguments to that call.
|
||||
*
|
||||
* If the dialog is closed by clicking in the background, the promise resolves with an empty array.
|
||||
*/
|
||||
finished: Promise<OnFinishedParams<C> | []>;
|
||||
|
||||
/**
|
||||
* A function which, if called, will close the dialog.
|
||||
*
|
||||
* @param args - Arguments to return to {@link finished}.
|
||||
*/
|
||||
close(...args: OnFinishedParams<C>): void;
|
||||
}
|
||||
|
||||
interface IOptions<C extends ComponentType> {
|
||||
@ -164,7 +206,6 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
const modals = filterBoolean([...this.modals, this.staticModal, this.priorityModal]);
|
||||
for (const modal of modals) {
|
||||
modal.deferred?.resolve([]);
|
||||
if (modal.onFinished) modal.onFinished.apply(null);
|
||||
this.emitClosed();
|
||||
}
|
||||
|
||||
@ -188,7 +229,6 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
onFinishedProm: IHandle<C>["finished"];
|
||||
} {
|
||||
const modal = {
|
||||
onFinished: props?.onFinished,
|
||||
onBeforeClose: options?.onBeforeClose,
|
||||
className,
|
||||
|
||||
@ -196,8 +236,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
elem: null,
|
||||
} as IModal<C>;
|
||||
|
||||
// never call this from onFinished() otherwise it will loop
|
||||
const [closeDialog, onFinishedProm] = this.getCloseFn<C>(modal, props);
|
||||
const [closeDialog, onFinishedProm] = this.getCloseFn<C>(modal);
|
||||
|
||||
// don't attempt to reuse the same AsyncWrapper for different dialogs,
|
||||
// otherwise we'll get confused.
|
||||
@ -214,13 +253,10 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
return { modal, closeDialog, onFinishedProm };
|
||||
}
|
||||
|
||||
private getCloseFn<C extends ComponentType>(
|
||||
modal: IModal<C>,
|
||||
props?: ComponentProps<C>,
|
||||
): [IHandle<C>["close"], IHandle<C>["finished"]] {
|
||||
modal.deferred = defer<Parameters<ComponentProps<C>["onFinished"]>>();
|
||||
private getCloseFn<C extends ComponentType>(modal: IModal<C>): [IHandle<C>["close"], IHandle<C>["finished"]] {
|
||||
modal.deferred = defer<OnFinishedParams<C> | []>();
|
||||
return [
|
||||
async (...args: Parameters<ComponentProps<C>["onFinished"]>): Promise<void> => {
|
||||
async (...args: OnFinishedParams<C>): Promise<void> => {
|
||||
if (modal.beforeClosePromise) {
|
||||
await modal.beforeClosePromise;
|
||||
} else if (modal.onBeforeClose) {
|
||||
@ -232,7 +268,6 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
}
|
||||
}
|
||||
modal.deferred?.resolve(args);
|
||||
if (props?.onFinished) props.onFinished.apply(null, args);
|
||||
const i = this.modals.indexOf(modal);
|
||||
if (i >= 0) {
|
||||
this.modals.splice(i, 1);
|
||||
@ -280,7 +315,8 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
* using React.lazy to async load the component.
|
||||
* e.g. `lazy(() => import('./MyComponent'))`
|
||||
*
|
||||
* @param props properties to pass to the displayed component. (We will also pass an 'onFinished' property.)
|
||||
* @param props properties to pass to the displayed component. (We will also pass an `onFinished` property; when
|
||||
* called, that property will close the dialog and return the results to the caller via {@link IHandle.finished}.)
|
||||
*
|
||||
* @param className CSS class to apply to the modal wrapper
|
||||
*
|
||||
@ -295,7 +331,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
* static at a time.
|
||||
* @param options? extra options for the dialog
|
||||
* @param options.onBeforeClose a callback to decide whether to close the dialog
|
||||
* @returns Object with 'close' parameter being a function that will close the dialog
|
||||
* @returns {@link IHandle} object.
|
||||
*/
|
||||
public createDialog<C extends ComponentType>(
|
||||
component: C,
|
||||
|
||||
@ -62,14 +62,14 @@ export async function startAnyRegistrationFlow(
|
||||
</button>,
|
||||
]
|
||||
: [],
|
||||
onFinished: (proceed) => {
|
||||
if (proceed) {
|
||||
dis.dispatch({ action: "start_login", screenAfterLogin: options.screen_after });
|
||||
} else if (options.go_home_on_cancel) {
|
||||
dis.dispatch({ action: Action.ViewHomePage });
|
||||
} else if (options.go_welcome_on_cancel) {
|
||||
dis.dispatch({ action: "view_welcome_page" });
|
||||
}
|
||||
},
|
||||
});
|
||||
modal.finished.then(([proceed]) => {
|
||||
if (proceed) {
|
||||
dis.dispatch({ action: "start_login", screenAfterLogin: options.screen_after });
|
||||
} else if (options.go_home_on_cancel) {
|
||||
dis.dispatch({ action: Action.ViewHomePage });
|
||||
} else if (options.go_welcome_on_cancel) {
|
||||
dis.dispatch({ action: "view_welcome_page" });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -49,15 +49,8 @@ export default function NewRecoveryMethodDialog({ onFinished }: NewRecoveryMetho
|
||||
if (isKeyBackupEnabled) {
|
||||
onFinished();
|
||||
} else {
|
||||
Modal.createDialog(
|
||||
RestoreKeyBackupDialog,
|
||||
{
|
||||
onFinished,
|
||||
},
|
||||
undefined,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
const { finished } = Modal.createDialog(RestoreKeyBackupDialog, {}, undefined, false, true);
|
||||
finished.then(onFinished);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1229,7 +1229,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
const warnings = this.leaveRoomWarnings(roomId);
|
||||
|
||||
const isSpace = roomToLeave?.isSpaceRoom();
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: isSpace ? _t("space|leave_dialog_action") : _t("action|leave_room"),
|
||||
description: (
|
||||
<span>
|
||||
@ -1245,16 +1245,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
),
|
||||
button: _t("action|leave"),
|
||||
danger: warnings.length > 0,
|
||||
onFinished: async (shouldLeave) => {
|
||||
if (shouldLeave) {
|
||||
await leaveRoomBehaviour(cli, roomId);
|
||||
});
|
||||
|
||||
dis.dispatch<AfterLeaveRoomPayload>({
|
||||
action: Action.AfterLeaveRoom,
|
||||
room_id: roomId,
|
||||
});
|
||||
}
|
||||
},
|
||||
finished.then(async ([shouldLeave]) => {
|
||||
if (shouldLeave) {
|
||||
await leaveRoomBehaviour(cli, roomId);
|
||||
|
||||
dis.dispatch<AfterLeaveRoomPayload>({
|
||||
action: Action.AfterLeaveRoom,
|
||||
room_id: roomId,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1558,7 +1559,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
});
|
||||
});
|
||||
cli.on(HttpApiEvent.NoConsent, function (message, consentUri) {
|
||||
Modal.createDialog(
|
||||
const { finished } = Modal.createDialog(
|
||||
QuestionDialog,
|
||||
{
|
||||
title: _t("terms|tac_title"),
|
||||
@ -1569,16 +1570,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
),
|
||||
button: _t("terms|tac_button"),
|
||||
cancelButton: _t("action|dismiss"),
|
||||
onFinished: (confirmed) => {
|
||||
if (confirmed) {
|
||||
const wnd = window.open(consentUri, "_blank")!;
|
||||
wnd.opener = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
finished.then(([confirmed]) => {
|
||||
if (confirmed) {
|
||||
const wnd = window.open(consentUri, "_blank")!;
|
||||
wnd.opener = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
DecryptionFailureTracker.instance
|
||||
|
||||
@ -1753,7 +1753,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
}
|
||||
|
||||
if (reportRoom !== false) {
|
||||
actions.push(this.context.client.reportRoom(this.state.room.roomId, reportRoom));
|
||||
actions.push(this.context.client.reportRoom(this.state.room.roomId, reportRoom!));
|
||||
}
|
||||
|
||||
actions.push(this.context.client.leave(this.state.room.roomId));
|
||||
|
||||
@ -1505,11 +1505,13 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||
description = _t("timeline|load_error|unable_to_find");
|
||||
}
|
||||
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
const { finished } = Modal.createDialog(ErrorDialog, {
|
||||
title: _t("timeline|load_error|title"),
|
||||
description,
|
||||
onFinished,
|
||||
});
|
||||
if (onFinished) {
|
||||
finished.then(onFinished);
|
||||
}
|
||||
};
|
||||
|
||||
// if we already have the event in question, TimelineWindow.load
|
||||
|
||||
@ -90,14 +90,15 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
||||
// We need to call onFinished now to close this dialog, and
|
||||
// again later to signal that the verification is complete.
|
||||
this.props.onFinished();
|
||||
Modal.createDialog(VerificationRequestDialog, {
|
||||
const { finished: verificationFinished } = Modal.createDialog(VerificationRequestDialog, {
|
||||
verificationRequestPromise: requestPromise,
|
||||
member: cli.getUser(userId) ?? undefined,
|
||||
onFinished: async (): Promise<void> => {
|
||||
const request = await requestPromise;
|
||||
request.cancel();
|
||||
this.props.onFinished();
|
||||
},
|
||||
});
|
||||
|
||||
verificationFinished.then(async () => {
|
||||
const request = await requestPromise;
|
||||
request.cancel();
|
||||
this.props.onFinished();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -89,13 +89,12 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
private onClearAll = (): void => {
|
||||
Modal.createDialog(ConfirmWipeDeviceDialog, {
|
||||
onFinished: (wipeData) => {
|
||||
if (!wipeData) return;
|
||||
const { finished } = Modal.createDialog(ConfirmWipeDeviceDialog);
|
||||
finished.then(([wipeData]) => {
|
||||
if (!wipeData) return;
|
||||
|
||||
logger.log("Clearing data from soft-logged-out session");
|
||||
Lifecycle.logout(this.context.oidcClientStore);
|
||||
},
|
||||
logger.log("Clearing data from soft-logged-out session");
|
||||
Lifecycle.logout(this.context.oidcClientStore);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -127,19 +127,11 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
|
||||
|
||||
if (this.state.email === "") {
|
||||
if (this.showEmail()) {
|
||||
Modal.createDialog(RegistrationEmailPromptDialog, {
|
||||
onFinished: async (confirmed: boolean, email?: string): Promise<void> => {
|
||||
if (confirmed && email !== undefined) {
|
||||
this.setState(
|
||||
{
|
||||
email,
|
||||
},
|
||||
() => {
|
||||
this.doSubmit(ev);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
const { finished } = Modal.createDialog(RegistrationEmailPromptDialog);
|
||||
finished.then(async ([confirmed, email]) => {
|
||||
if (confirmed && email !== undefined) {
|
||||
this.setState({ email }, () => this.doSubmit(ev));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// user can't set an e-mail so don't prompt them to
|
||||
|
||||
@ -25,7 +25,6 @@ export const DeveloperToolsOption: React.FC<Props> = ({ onFinished, roomId }) =>
|
||||
Modal.createDialog(
|
||||
DevtoolsDialog,
|
||||
{
|
||||
onFinished: () => {},
|
||||
roomId: roomId,
|
||||
},
|
||||
"mx_DevtoolsDialog_wrapper",
|
||||
|
||||
@ -187,14 +187,15 @@ export const WidgetContextMenu: React.FC<IProps> = ({
|
||||
onDeleteClick();
|
||||
} else if (roomId) {
|
||||
// Show delete confirmation dialog
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: _t("widget|context_menu|delete"),
|
||||
description: _t("widget|context_menu|delete_warning"),
|
||||
button: _t("widget|context_menu|delete"),
|
||||
onFinished: (confirmed) => {
|
||||
if (!confirmed) return;
|
||||
WidgetUtils.setRoomWidget(cli, roomId, app.id);
|
||||
},
|
||||
});
|
||||
|
||||
finished.then(([confirmed]) => {
|
||||
if (!confirmed) return;
|
||||
WidgetUtils.setRoomWidget(cli, roomId, app.id);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import React from "react";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import Modal, { type ComponentProps } from "../../../Modal";
|
||||
import Modal, { type ComponentProps, type IHandle } from "../../../Modal";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { getPolicyUrl } from "../../../toasts/AnalyticsToast";
|
||||
import ExternalLink from "../elements/ExternalLink";
|
||||
@ -91,10 +91,10 @@ export const AnalyticsLearnMoreDialog: React.FC<IProps> = ({
|
||||
|
||||
export const showDialog = (
|
||||
props: Omit<ComponentProps<typeof AnalyticsLearnMoreDialog>, "cookiePolicyUrl" | "analyticsOwner">,
|
||||
): void => {
|
||||
): IHandle<typeof AnalyticsLearnMoreDialog> => {
|
||||
const privacyPolicyUrl = getPolicyUrl();
|
||||
const analyticsOwner = SdkConfig.get("analytics_owner") ?? SdkConfig.get("brand");
|
||||
Modal.createDialog(
|
||||
return Modal.createDialog(
|
||||
AnalyticsLearnMoreDialog,
|
||||
{
|
||||
privacyPolicyUrl,
|
||||
|
||||
@ -58,37 +58,32 @@ export function createRedactEventDialog({
|
||||
const roomId = mxEvent.getRoomId();
|
||||
|
||||
if (!roomId) throw new Error(`cannot redact event ${mxEvent.getId()} without room ID`);
|
||||
Modal.createDialog(
|
||||
ConfirmRedactDialog,
|
||||
{
|
||||
event: mxEvent,
|
||||
onFinished: async (proceed, reason): Promise<void> => {
|
||||
if (!proceed) return;
|
||||
const { finished } = Modal.createDialog(ConfirmRedactDialog, { event: mxEvent }, "mx_Dialog_confirmredact");
|
||||
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const withRelTypes: Pick<IRedactOpts, "with_rel_types"> = {};
|
||||
finished.then(async ([proceed, reason]) => {
|
||||
if (!proceed) return;
|
||||
|
||||
try {
|
||||
onCloseDialog?.();
|
||||
await cli.redactEvent(roomId, eventId, undefined, {
|
||||
...(reason ? { reason } : {}),
|
||||
...withRelTypes,
|
||||
});
|
||||
} catch (e: any) {
|
||||
const code = e.errcode || e.statusCode;
|
||||
// only show the dialog if failing for something other than a network error
|
||||
// (e.g. no errcode or statusCode) as in that case the redactions end up in the
|
||||
// detached queue and we show the room status bar to allow retry
|
||||
if (typeof code !== "undefined") {
|
||||
// display error message stating you couldn't delete this.
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("common|error"),
|
||||
description: _t("redact|error", { code }),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
"mx_Dialog_confirmredact",
|
||||
);
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const withRelTypes: Pick<IRedactOpts, "with_rel_types"> = {};
|
||||
|
||||
try {
|
||||
onCloseDialog?.();
|
||||
await cli.redactEvent(roomId, eventId, undefined, {
|
||||
...(reason ? { reason } : {}),
|
||||
...withRelTypes,
|
||||
});
|
||||
} catch (e: any) {
|
||||
const code = e.errcode || e.statusCode;
|
||||
// only show the dialog if failing for something other than a network error
|
||||
// (e.g. no errcode or statusCode) as in that case the redactions end up in the
|
||||
// detached queue and we show the room status bar to allow retry
|
||||
if (typeof code !== "undefined") {
|
||||
// display error message stating you couldn't delete this.
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("common|error"),
|
||||
description: _t("redact|error", { code }),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -31,13 +31,13 @@ export default class SessionRestoreErrorDialog extends React.Component<IProps> {
|
||||
};
|
||||
|
||||
private onClearStorageClick = (): void => {
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: _t("action|sign_out"),
|
||||
description: <div>{_t("error|session_restore|clear_storage_description")}</div>,
|
||||
button: _t("action|sign_out"),
|
||||
danger: true,
|
||||
onFinished: this.props.onFinished,
|
||||
});
|
||||
finished.then(([ok]) => this.props.onFinished(ok));
|
||||
};
|
||||
|
||||
private onRefreshClick = (): void => {
|
||||
|
||||
@ -66,12 +66,12 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||
this.addThreepid = new AddThreepid(MatrixClientPeg.safeGet());
|
||||
this.addThreepid.addEmailAddress(emailAddress).then(
|
||||
() => {
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: _t("auth|set_email|verification_pending_title"),
|
||||
description: _t("auth|set_email|verification_pending_description"),
|
||||
button: _t("action|continue"),
|
||||
onFinished: this.onEmailDialogFinished,
|
||||
});
|
||||
finished.then(([ok]) => this.onEmailDialogFinished(ok));
|
||||
},
|
||||
(err) => {
|
||||
this.setState({ emailBusy: false });
|
||||
@ -89,7 +89,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
private onEmailDialogFinished = (ok: boolean): void => {
|
||||
private onEmailDialogFinished = (ok?: boolean): void => {
|
||||
if (ok) {
|
||||
this.verifyEmailAddress();
|
||||
} else {
|
||||
@ -115,12 +115,12 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||
_t("settings|general|error_email_verification") +
|
||||
" " +
|
||||
_t("auth|set_email|verification_pending_description");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: _t("auth|set_email|verification_pending_title"),
|
||||
description: message,
|
||||
button: _t("action|continue"),
|
||||
onFinished: this.onEmailDialogFinished,
|
||||
});
|
||||
finished.then(([ok]) => this.onEmailDialogFinished(ok));
|
||||
} else {
|
||||
logger.error("Unable to verify email address: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
|
||||
@ -222,10 +222,10 @@ export const NetworkDropdown: React.FC<IProps> = ({ protocols, config, setConfig
|
||||
const [ok, newServer] = await finished;
|
||||
if (!ok) return;
|
||||
|
||||
if (!allServers.includes(newServer)) {
|
||||
setUserDefinedServers([...userDefinedServers, newServer]);
|
||||
if (!allServers.includes(newServer!)) {
|
||||
setUserDefinedServers([...userDefinedServers, newServer!]);
|
||||
setConfig({
|
||||
roomServer: newServer,
|
||||
roomServer: newServer!,
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
||||
@ -167,18 +167,18 @@ export default class PollCreateDialog extends ScrollableBaseModal<IProps, IState
|
||||
.then(() => this.props.onFinished(true))
|
||||
.catch((e) => {
|
||||
console.error("Failed to post poll:", e);
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: _t("poll|failed_send_poll_title"),
|
||||
description: _t("poll|failed_send_poll_description"),
|
||||
button: _t("action|try_again"),
|
||||
cancelButton: _t("action|cancel"),
|
||||
onFinished: (tryAgain: boolean) => {
|
||||
if (!tryAgain) {
|
||||
this.cancel();
|
||||
} else {
|
||||
this.setState({ busy: false, canSubmit: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
finished.then(([tryAgain]) => {
|
||||
if (!tryAgain) {
|
||||
this.cancel();
|
||||
} else {
|
||||
this.setState({ busy: false, canSubmit: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -28,9 +28,10 @@ interface IProps {
|
||||
const showPickerDialog = (
|
||||
title: string | undefined,
|
||||
serverConfig: ValidatedServerConfig,
|
||||
onFinished: (config: ValidatedServerConfig) => void,
|
||||
onFinished: (config?: ValidatedServerConfig) => void,
|
||||
): void => {
|
||||
Modal.createDialog(ServerPickerDialog, { title, serverConfig, onFinished });
|
||||
const { finished } = Modal.createDialog(ServerPickerDialog, { title, serverConfig });
|
||||
finished.then(([config]) => onFinished(config));
|
||||
};
|
||||
|
||||
const onHelpClick = (): void => {
|
||||
|
||||
@ -235,7 +235,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||
scalarClient?.connect().then(() => {
|
||||
const completeUrl = scalarClient.getStarterLink(starterLink);
|
||||
const integrationsUrl = integrationManager!.uiUrl;
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: _t("timeline|scalar_starter_link|dialog_title"),
|
||||
description: (
|
||||
<div>
|
||||
@ -243,18 +243,19 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||
</div>
|
||||
),
|
||||
button: _t("action|continue"),
|
||||
onFinished(confirmed) {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
const width = window.screen.width > 1024 ? 1024 : window.screen.width;
|
||||
const height = window.screen.height > 800 ? 800 : window.screen.height;
|
||||
const left = (window.screen.width - width) / 2;
|
||||
const top = (window.screen.height - height) / 2;
|
||||
const features = `height=${height}, width=${width}, top=${top}, left=${left},`;
|
||||
const wnd = window.open(completeUrl, "_blank", features)!;
|
||||
wnd.opener = null;
|
||||
},
|
||||
});
|
||||
|
||||
finished.then(([confirmed]) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
const width = window.screen.width > 1024 ? 1024 : window.screen.width;
|
||||
const height = window.screen.height > 800 ? 800 : window.screen.height;
|
||||
const left = (window.screen.width - width) / 2;
|
||||
const top = (window.screen.height - height) / 2;
|
||||
const features = `height=${height}, width=${width}, top=${top}, left=${left},`;
|
||||
const wnd = window.open(completeUrl, "_blank", features)!;
|
||||
wnd.opener = null;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -119,15 +119,14 @@ export default class EventIndexPanel extends React.Component<EmptyObject, IState
|
||||
};
|
||||
|
||||
private confirmEventStoreReset = (): void => {
|
||||
const { close } = Modal.createDialog(SeshatResetDialog, {
|
||||
onFinished: async (success): Promise<void> => {
|
||||
if (success) {
|
||||
await SettingsStore.setValue("enableEventIndexing", null, SettingLevel.DEVICE, false);
|
||||
await EventIndexPeg.deleteEventIndex();
|
||||
await this.onEnable();
|
||||
close();
|
||||
}
|
||||
},
|
||||
const { finished, close } = Modal.createDialog(SeshatResetDialog);
|
||||
finished.then(async ([success]) => {
|
||||
if (success) {
|
||||
await SettingsStore.setValue("enableEventIndexing", null, SettingLevel.DEVICE, false);
|
||||
await EventIndexPeg.deleteEventIndex();
|
||||
await this.onEnable();
|
||||
close();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ import { type AuthDict, type IAuthData } from "matrix-js-sdk/src/interactive-aut
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import Modal from "../../../../Modal";
|
||||
import { type InteractiveAuthCallback } from "../../../structures/InteractiveAuth";
|
||||
import { SSOAuthEntry } from "../../auth/InteractiveAuthEntryComponents";
|
||||
import InteractiveAuthDialog from "../../dialogs/InteractiveAuthDialog";
|
||||
|
||||
@ -24,7 +23,7 @@ const makeDeleteRequest =
|
||||
export const deleteDevicesWithInteractiveAuth = async (
|
||||
matrixClient: MatrixClient,
|
||||
deviceIds: string[],
|
||||
onFinished: InteractiveAuthCallback<void>,
|
||||
onFinished: (success?: boolean) => Promise<void>,
|
||||
): Promise<void> => {
|
||||
if (!deviceIds.length) {
|
||||
return;
|
||||
@ -32,7 +31,7 @@ export const deleteDevicesWithInteractiveAuth = async (
|
||||
try {
|
||||
await makeDeleteRequest(matrixClient, deviceIds)(null);
|
||||
// no interactive auth needed
|
||||
await onFinished(true, undefined);
|
||||
await onFinished(true);
|
||||
} catch (error) {
|
||||
if (!(error instanceof MatrixError) || error.httpStatus !== 401 || !error.data?.flows) {
|
||||
// doesn't look like an interactive-auth failure
|
||||
@ -62,16 +61,16 @@ export const deleteDevicesWithInteractiveAuth = async (
|
||||
continueKind: "danger",
|
||||
},
|
||||
};
|
||||
Modal.createDialog(InteractiveAuthDialog, {
|
||||
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
||||
title: _t("common|authentication"),
|
||||
matrixClient: matrixClient,
|
||||
authData: error.data as IAuthData,
|
||||
onFinished,
|
||||
makeRequest: makeDeleteRequest(matrixClient, deviceIds),
|
||||
aestheticsForStagePhases: {
|
||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||
},
|
||||
});
|
||||
finished.then(([success]) => onFinished(success));
|
||||
}
|
||||
};
|
||||
|
||||
@ -155,7 +155,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
if (!confirm) return;
|
||||
}
|
||||
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: _t("room_settings|security|enable_encryption_confirm_title"),
|
||||
description: _t(
|
||||
"room_settings|security|enable_encryption_confirm_description",
|
||||
@ -164,23 +164,23 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
a: (sub) => <ExternalLink href={SdkConfig.get("help_encryption_url")}>{sub}</ExternalLink>,
|
||||
},
|
||||
),
|
||||
onFinished: (confirm) => {
|
||||
if (!confirm) {
|
||||
this.setState({ encrypted: false });
|
||||
return;
|
||||
}
|
||||
});
|
||||
finished.then(([confirm]) => {
|
||||
if (!confirm) {
|
||||
this.setState({ encrypted: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const beforeEncrypted = this.state.encrypted;
|
||||
this.setState({ encrypted: true });
|
||||
this.context
|
||||
.sendStateEvent(this.props.room.roomId, EventType.RoomEncryption, {
|
||||
algorithm: MEGOLM_ENCRYPTION_ALGORITHM,
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
this.setState({ encrypted: beforeEncrypted });
|
||||
});
|
||||
},
|
||||
const beforeEncrypted = this.state.encrypted;
|
||||
this.setState({ encrypted: true });
|
||||
this.context
|
||||
.sendStateEvent(this.props.room.roomId, EventType.RoomEncryption, {
|
||||
algorithm: MEGOLM_ENCRYPTION_ALGORITHM,
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
this.setState({ encrypted: beforeEncrypted });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -213,9 +213,9 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
|
||||
const [shouldCreate, opts] = await modal.finished;
|
||||
if (shouldCreate) {
|
||||
await createRoom(this.context, opts);
|
||||
await createRoom(this.context, opts!);
|
||||
}
|
||||
return shouldCreate;
|
||||
return shouldCreate ?? false;
|
||||
};
|
||||
|
||||
private onHistoryRadioToggle = (history: HistoryVisibility): void => {
|
||||
|
||||
@ -165,10 +165,9 @@ const AccountUserSettingsTab: React.FC<IProps> = ({ closeSettingsFn }) => {
|
||||
}, []);
|
||||
|
||||
const onDeactivateClicked = useCallback((): void => {
|
||||
Modal.createDialog(DeactivateAccountDialog, {
|
||||
onFinished: (success) => {
|
||||
if (success) closeSettingsFn();
|
||||
},
|
||||
const { finished } = Modal.createDialog(DeactivateAccountDialog);
|
||||
finished.then(([success]) => {
|
||||
if (success) closeSettingsFn();
|
||||
});
|
||||
}, [closeSettingsFn]);
|
||||
|
||||
|
||||
@ -247,7 +247,10 @@ function SetUpEncryptionPanel({ onFinish }: SetUpEncryptionPanelProps): JSX.Elem
|
||||
<Button
|
||||
size="sm"
|
||||
Icon={ComputerIcon}
|
||||
onClick={() => Modal.createDialog(SetupEncryptionDialog, { onFinished: onFinish })}
|
||||
onClick={() => {
|
||||
const { finished } = Modal.createDialog(SetupEncryptionDialog);
|
||||
finished.then(onFinish);
|
||||
}}
|
||||
>
|
||||
{_t("settings|encryption|device_not_verified_button")}
|
||||
</Button>
|
||||
|
||||
@ -100,7 +100,7 @@ const useSignOut = (
|
||||
} else {
|
||||
const deferredSuccess = defer<boolean>();
|
||||
await deleteDevicesWithInteractiveAuth(matrixClient, deviceIds, async (success) => {
|
||||
deferredSuccess.resolve(success);
|
||||
deferredSuccess.resolve(!!success);
|
||||
});
|
||||
success = await deferredSuccess.promise;
|
||||
}
|
||||
@ -203,7 +203,8 @@ const SessionManagerTab: React.FC<{
|
||||
const shouldShowOtherSessions = otherSessionsCount > 0;
|
||||
|
||||
const onVerifyCurrentDevice = (): void => {
|
||||
Modal.createDialog(SetupEncryptionDialog, { onFinished: refreshDevices });
|
||||
const { finished } = Modal.createDialog(SetupEncryptionDialog);
|
||||
finished.then(refreshDevices);
|
||||
};
|
||||
|
||||
const onTriggerDeviceVerification = useCallback(
|
||||
@ -212,14 +213,14 @@ const SessionManagerTab: React.FC<{
|
||||
return;
|
||||
}
|
||||
const verificationRequestPromise = requestDeviceVerification(deviceId);
|
||||
Modal.createDialog(VerificationRequestDialog, {
|
||||
const { finished } = Modal.createDialog(VerificationRequestDialog, {
|
||||
verificationRequestPromise,
|
||||
member: currentUserMember,
|
||||
onFinished: async (): Promise<void> => {
|
||||
const request = await verificationRequestPromise;
|
||||
request.cancel();
|
||||
await refreshDevices();
|
||||
},
|
||||
});
|
||||
finished.then(async () => {
|
||||
const request = await verificationRequestPromise;
|
||||
request.cancel();
|
||||
await refreshDevices();
|
||||
});
|
||||
},
|
||||
[requestDeviceVerification, refreshDevices, currentUserMember],
|
||||
|
||||
@ -124,18 +124,16 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||
request.roomId,
|
||||
);
|
||||
} else {
|
||||
Modal.createDialog(
|
||||
const { finished } = Modal.createDialog(
|
||||
VerificationRequestDialog,
|
||||
{
|
||||
verificationRequest: request,
|
||||
onFinished: () => {
|
||||
request.cancel();
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
/* priority = */ false,
|
||||
/* static = */ true,
|
||||
);
|
||||
finished.then(() => request.cancel());
|
||||
}
|
||||
await request.accept();
|
||||
} catch (err) {
|
||||
|
||||
@ -49,16 +49,14 @@ export const singleMxcUpload = async (cli: MatrixClient): Promise<string | null>
|
||||
const file = (ev as HTMLInputEvent).target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
Modal.createDialog(UploadConfirmDialog, {
|
||||
file,
|
||||
onFinished: async (shouldContinue): Promise<void> => {
|
||||
if (shouldContinue) {
|
||||
const { content_uri: uri } = await cli.uploadContent(file);
|
||||
resolve(uri);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
},
|
||||
const { finished } = Modal.createDialog(UploadConfirmDialog, { file });
|
||||
finished.then(async ([shouldContinue]) => {
|
||||
if (shouldContinue) {
|
||||
const { content_uri: uri } = await cli.uploadContent(file);
|
||||
resolve(uri);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -61,18 +61,18 @@ export class ModalWidgetStore extends AsyncStoreWithClient<IState> {
|
||||
widgetDefinition: { ...requestData },
|
||||
widgetRoomId,
|
||||
sourceWidgetId: sourceWidget.id,
|
||||
onFinished: (success, data) => {
|
||||
this.closeModalWidget(sourceWidget, widgetRoomId, success && data ? data : { "m.exited": true });
|
||||
|
||||
this.openSourceWidgetId = null;
|
||||
this.openSourceWidgetRoomId = null;
|
||||
this.modalInstance = null;
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
/* priority = */ false,
|
||||
/* static = */ true,
|
||||
);
|
||||
this.modalInstance!.finished.then(([success, data]) => {
|
||||
this.closeModalWidget(sourceWidget, widgetRoomId, success && data ? data : { "m.exited": true });
|
||||
|
||||
this.openSourceWidgetId = null;
|
||||
this.openSourceWidgetRoomId = null;
|
||||
this.modalInstance = null;
|
||||
});
|
||||
};
|
||||
|
||||
public closeModalWidget = (
|
||||
|
||||
@ -557,19 +557,17 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||
|
||||
observer.update({ state: OpenIDRequestState.PendingUserConfirmation });
|
||||
|
||||
Modal.createDialog(WidgetOpenIDPermissionsDialog, {
|
||||
const { finished } = Modal.createDialog(WidgetOpenIDPermissionsDialog, {
|
||||
widget: this.forWidget,
|
||||
widgetKind: this.forWidgetKind,
|
||||
inRoomId: this.inRoomId,
|
||||
|
||||
onFinished: async (confirm): Promise<void> => {
|
||||
if (!confirm) {
|
||||
return observer.update({ state: OpenIDRequestState.Blocked });
|
||||
}
|
||||
|
||||
return observer.update({ state: OpenIDRequestState.Allowed, token: await getToken() });
|
||||
},
|
||||
});
|
||||
const [confirm] = await finished;
|
||||
if (!confirm) {
|
||||
observer.update({ state: OpenIDRequestState.Blocked });
|
||||
} else {
|
||||
observer.update({ state: OpenIDRequestState.Allowed, token: await getToken() });
|
||||
}
|
||||
}
|
||||
|
||||
public async navigate(uri: string): Promise<void> {
|
||||
|
||||
@ -34,34 +34,36 @@ const onReject = (): void => {
|
||||
};
|
||||
|
||||
const onLearnMoreNoOptIn = (): void => {
|
||||
showAnalyticsLearnMoreDialog({
|
||||
onFinished: (buttonClicked?: ButtonClicked) => {
|
||||
if (buttonClicked === ButtonClicked.Primary) {
|
||||
// user clicked "Enable"
|
||||
onAccept();
|
||||
}
|
||||
// otherwise, the user either clicked "Cancel", or closed the dialog without making a choice,
|
||||
// leave the toast open
|
||||
},
|
||||
const { finished } = showAnalyticsLearnMoreDialog({
|
||||
primaryButton: _t("action|enable"),
|
||||
});
|
||||
|
||||
finished.then(([buttonClicked]) => {
|
||||
if (buttonClicked === ButtonClicked.Primary) {
|
||||
// user clicked "Enable"
|
||||
onAccept();
|
||||
}
|
||||
// otherwise, the user either clicked "Cancel", or closed the dialog without making a choice,
|
||||
// leave the toast open
|
||||
});
|
||||
};
|
||||
|
||||
const onLearnMorePreviouslyOptedIn = (): void => {
|
||||
showAnalyticsLearnMoreDialog({
|
||||
onFinished: (buttonClicked?: ButtonClicked) => {
|
||||
if (buttonClicked === ButtonClicked.Primary) {
|
||||
// user clicked "That's fine"
|
||||
onAccept();
|
||||
} else if (buttonClicked === ButtonClicked.Cancel) {
|
||||
// user clicked "Stop"
|
||||
onReject();
|
||||
}
|
||||
// otherwise, the user closed the dialog without making a choice, leave the toast open
|
||||
},
|
||||
const { finished } = showAnalyticsLearnMoreDialog({
|
||||
primaryButton: _t("analytics|accept_button"),
|
||||
cancelButton: _t("action|stop"),
|
||||
});
|
||||
|
||||
finished.then(([buttonClicked]) => {
|
||||
if (buttonClicked === ButtonClicked.Primary) {
|
||||
// user clicked "That's fine"
|
||||
onAccept();
|
||||
} else if (buttonClicked === ButtonClicked.Cancel) {
|
||||
// user clicked "Stop"
|
||||
onReject();
|
||||
}
|
||||
// otherwise, the user closed the dialog without making a choice, leave the toast open
|
||||
});
|
||||
};
|
||||
|
||||
const TOAST_KEY = "analytics";
|
||||
|
||||
@ -32,27 +32,27 @@ export const showToast = (version: string, newVersion: string, releaseNotes?: st
|
||||
let acceptLabel = _t("update|see_changes_button");
|
||||
if (releaseNotes) {
|
||||
onAccept = () => {
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: _t("update|release_notes_toast_title"),
|
||||
description: <pre>{releaseNotes}</pre>,
|
||||
button: _t("action|update"),
|
||||
onFinished: (update) => {
|
||||
if (update && PlatformPeg.get()) {
|
||||
PlatformPeg.get()!.installUpdate();
|
||||
}
|
||||
},
|
||||
});
|
||||
finished.then(([update]) => {
|
||||
if (update && PlatformPeg.get()) {
|
||||
PlatformPeg.get()!.installUpdate();
|
||||
}
|
||||
});
|
||||
};
|
||||
} else if (checkVersion(version) && checkVersion(newVersion)) {
|
||||
onAccept = () => {
|
||||
Modal.createDialog(ChangelogDialog, {
|
||||
const { finished } = Modal.createDialog(ChangelogDialog, {
|
||||
version,
|
||||
newVersion,
|
||||
onFinished: (update) => {
|
||||
if (update && PlatformPeg.get()) {
|
||||
PlatformPeg.get()!.installUpdate();
|
||||
}
|
||||
},
|
||||
});
|
||||
finished.then(([update]) => {
|
||||
if (update && PlatformPeg.get()) {
|
||||
PlatformPeg.get()!.installUpdate();
|
||||
}
|
||||
});
|
||||
};
|
||||
} else {
|
||||
|
||||
@ -119,7 +119,7 @@ export class DialogOpener {
|
||||
break;
|
||||
case Action.OpenAddToExistingSpaceDialog: {
|
||||
const space = payload.space;
|
||||
Modal.createDialog(
|
||||
const { finished } = Modal.createDialog(
|
||||
AddExistingToSpaceDialog,
|
||||
{
|
||||
onCreateRoomClick: (ev: ButtonEvent) => {
|
||||
@ -128,14 +128,14 @@ export class DialogOpener {
|
||||
},
|
||||
onAddSubspaceClick: () => showAddExistingSubspace(space),
|
||||
space,
|
||||
onFinished: (added: boolean) => {
|
||||
if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
|
||||
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||
}
|
||||
},
|
||||
},
|
||||
"mx_AddExistingToSpaceDialog_wrapper",
|
||||
);
|
||||
finished.then(([added]) => {
|
||||
if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
|
||||
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,20 +171,20 @@ export async function leaveRoomBehaviour(
|
||||
}
|
||||
|
||||
export const leaveSpace = (space: Room): void => {
|
||||
Modal.createDialog(
|
||||
const { finished } = Modal.createDialog(
|
||||
LeaveSpaceDialog,
|
||||
{
|
||||
space,
|
||||
onFinished: async (leave: boolean, rooms: Room[]): Promise<void> => {
|
||||
if (!leave) return;
|
||||
await bulkSpaceBehaviour(space, rooms, (room) => leaveRoomBehaviour(space.client, room.roomId));
|
||||
|
||||
dis.dispatch<AfterLeaveRoomPayload>({
|
||||
action: Action.AfterLeaveRoom,
|
||||
room_id: space.roomId,
|
||||
});
|
||||
},
|
||||
},
|
||||
"mx_LeaveSpaceDialog_wrapper",
|
||||
);
|
||||
finished.then(async ([leave, rooms]) => {
|
||||
if (!leave) return;
|
||||
await bulkSpaceBehaviour(space, rooms!, (room) => leaveRoomBehaviour(space.client, room.roomId));
|
||||
|
||||
dis.dispatch<AfterLeaveRoomPayload>({
|
||||
action: Action.AfterLeaveRoom,
|
||||
room_id: space.roomId,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -75,7 +75,7 @@ export const showCreateNewRoom = async (space: Room, type?: RoomType): Promise<b
|
||||
});
|
||||
const [shouldCreate, opts] = await modal.finished;
|
||||
if (shouldCreate) {
|
||||
await createRoom(space.client, opts);
|
||||
await createRoom(space.client, opts!);
|
||||
}
|
||||
return !!shouldCreate;
|
||||
};
|
||||
@ -106,35 +106,35 @@ export const showSpaceInvite = (space: Room, initialText = ""): void => {
|
||||
};
|
||||
|
||||
export const showAddExistingSubspace = (space: Room): void => {
|
||||
Modal.createDialog(
|
||||
const { finished } = Modal.createDialog(
|
||||
AddExistingSubspaceDialog,
|
||||
{
|
||||
space,
|
||||
onCreateSubspaceClick: () => showCreateNewSubspace(space),
|
||||
onFinished: (added: boolean) => {
|
||||
if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
|
||||
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||
}
|
||||
},
|
||||
},
|
||||
"mx_AddExistingToSpaceDialog_wrapper",
|
||||
);
|
||||
finished.then(([added]) => {
|
||||
if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
|
||||
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const showCreateNewSubspace = (space: Room): void => {
|
||||
Modal.createDialog(
|
||||
const { finished } = Modal.createDialog(
|
||||
CreateSubspaceDialog,
|
||||
{
|
||||
space,
|
||||
onAddExistingSpaceClick: () => showAddExistingSubspace(space),
|
||||
onFinished: (added: boolean) => {
|
||||
if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
|
||||
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||
}
|
||||
},
|
||||
},
|
||||
"mx_CreateSubspaceDialog_wrapper",
|
||||
);
|
||||
finished.then(([added]) => {
|
||||
if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
|
||||
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const bulkSpaceBehaviour = async (
|
||||
|
||||
@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import Modal from "../../src/Modal";
|
||||
import QuestionDialog from "../../src/components/views/dialogs/QuestionDialog";
|
||||
import defaultDispatcher from "../../src/dispatcher/dispatcher";
|
||||
import { flushPromises } from "../test-utils";
|
||||
|
||||
describe("Modal", () => {
|
||||
test("forceCloseAllModals should close all open modals", () => {
|
||||
@ -23,7 +24,7 @@ describe("Modal", () => {
|
||||
expect(Modal.hasDialogs()).toBe(false);
|
||||
});
|
||||
|
||||
test("open modals should be closed on logout", () => {
|
||||
test("open modals should be closed on logout", async () => {
|
||||
const modal1OnFinished = jest.fn();
|
||||
const modal2OnFinished = jest.fn();
|
||||
|
||||
@ -31,18 +32,18 @@ describe("Modal", () => {
|
||||
title: "Test dialog 1",
|
||||
description: "This is a test dialog",
|
||||
button: "Word",
|
||||
onFinished: modal1OnFinished,
|
||||
});
|
||||
}).finished.then(modal1OnFinished);
|
||||
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Test dialog 2",
|
||||
description: "This is a test dialog",
|
||||
button: "Word",
|
||||
onFinished: modal2OnFinished,
|
||||
});
|
||||
}).finished.then(modal2OnFinished);
|
||||
|
||||
defaultDispatcher.dispatch({ action: "logout" }, true);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(modal1OnFinished).toHaveBeenCalled();
|
||||
expect(modal2OnFinished).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@ -35,7 +35,7 @@ describe("deleteDevices()", () => {
|
||||
await deleteDevicesWithInteractiveAuth(mockClient, deviceIds, onFinished);
|
||||
|
||||
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(deviceIds, undefined);
|
||||
expect(onFinished).toHaveBeenCalledWith(true, undefined);
|
||||
expect(onFinished).toHaveBeenCalledWith(true);
|
||||
|
||||
// didnt open modal
|
||||
expect(modalSpy).not.toHaveBeenCalled();
|
||||
|
||||
@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { fireEvent, render, screen, within } from "jest-matrix-react";
|
||||
import React from "react";
|
||||
import { type MatrixClient, ThreepidMedium } from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { type MockedObject } from "jest-mock";
|
||||
@ -153,7 +154,8 @@ describe("<AccountUserSettingsTab />", () => {
|
||||
(settingName) => settingName === UIFeature.Deactivate,
|
||||
);
|
||||
|
||||
const createDialogFn = jest.fn();
|
||||
const finishedDeferred = defer<[boolean]>();
|
||||
const createDialogFn = jest.fn().mockReturnValue({ finished: finishedDeferred.promise });
|
||||
jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
|
||||
|
||||
render(getComponent());
|
||||
@ -167,14 +169,16 @@ describe("<AccountUserSettingsTab />", () => {
|
||||
(settingName) => settingName === UIFeature.Deactivate,
|
||||
);
|
||||
|
||||
const createDialogFn = jest.fn();
|
||||
const finishedDeferred = defer<[boolean]>();
|
||||
const createDialogFn = jest.fn().mockReturnValue({ finished: finishedDeferred.promise });
|
||||
jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
|
||||
|
||||
render(getComponent());
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Deactivate Account" }));
|
||||
|
||||
createDialogFn.mock.calls[0][1].onFinished(true);
|
||||
finishedDeferred.resolve([true]);
|
||||
await flushPromises();
|
||||
|
||||
expect(defaultProps.closeSettingsFn).toHaveBeenCalled();
|
||||
});
|
||||
@ -183,14 +187,16 @@ describe("<AccountUserSettingsTab />", () => {
|
||||
(settingName) => settingName === UIFeature.Deactivate,
|
||||
);
|
||||
|
||||
const createDialogFn = jest.fn();
|
||||
const finishedDeferred = defer<[boolean]>();
|
||||
const createDialogFn = jest.fn().mockReturnValue({ finished: finishedDeferred.promise });
|
||||
jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
|
||||
|
||||
render(getComponent());
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Deactivate Account" }));
|
||||
|
||||
createDialogFn.mock.calls[0][1].onFinished(false);
|
||||
finishedDeferred.resolve([false]);
|
||||
await flushPromises();
|
||||
|
||||
expect(defaultProps.closeSettingsFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@ -62,7 +62,7 @@ describe("<EncryptionUserSettingsTab />", () => {
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
const spy = jest.spyOn(Modal, "createDialog").mockReturnValue({} as any);
|
||||
const spy = jest.spyOn(Modal, "createDialog").mockReturnValue({ finished: new Promise(() => {}) } as any);
|
||||
await user.click(screen.getByText("Verify this device"));
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@ -631,9 +631,10 @@ describe("<SessionManagerTab />", () => {
|
||||
// click verify button from current session section
|
||||
fireEvent.click(getByTestId(`verification-status-button-${alicesMobileDevice.device_id}`));
|
||||
|
||||
const { onFinished: modalOnFinished } = modalSpy.mock.calls[0][1] as any;
|
||||
// simulate modal completing process
|
||||
await modalOnFinished();
|
||||
// close the modal
|
||||
const { close: closeModal } = modalSpy.mock.results[0].value;
|
||||
closeModal();
|
||||
await flushPromises();
|
||||
|
||||
// cancelled in case it was a failure exit from modal
|
||||
expect(mockVerificationRequest.cancel).toHaveBeenCalled();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user