From 823c631e1e5db4b316b69f248c2a0900d16bcf9e Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Wed, 9 Aug 2023 17:35:07 +0200 Subject: [PATCH 1/6] Implement updated open dialog method Signed-off-by: Dominik Henneke --- .../views/dialogs/ModuleUiDialog.tsx | 37 ++++++++++++++----- .../views/dialogs/ScrollableBaseModal.tsx | 3 +- src/modules/ProxiedModuleApi.ts | 15 ++++---- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/components/views/dialogs/ModuleUiDialog.tsx b/src/components/views/dialogs/ModuleUiDialog.tsx index acf9d1eda49..47101c829e4 100644 --- a/src/components/views/dialogs/ModuleUiDialog.tsx +++ b/src/components/views/dialogs/ModuleUiDialog.tsx @@ -17,15 +17,21 @@ limitations under the License. import React, { createRef } from "react"; import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent"; import { logger } from "matrix-js-sdk/src/logger"; +import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi"; +import { ModuleUiDialogOptions } from "@matrix-org/react-sdk-module-api/lib/types/ModuleUiDialogOptions"; import ScrollableBaseModal, { IScrollableBaseState } from "./ScrollableBaseModal"; import { _t } from "../../../languageHandler"; interface IProps

> { contentFactory: (props: P, ref: React.RefObject) => React.ReactNode; - contentProps: P; + contentProps: Omit | undefined; title: string; - onFinished(ok?: boolean, model?: Awaited["trySubmit"]>>): void; + actionLabel?: string; + cancelLabel?: string; + canSubmit?: boolean; + moduleApi: ModuleApi; + onFinished(ok?: boolean, model?: Awaited["trySubmit"]>>): void; } interface IState extends IScrollableBaseState { @@ -43,8 +49,9 @@ export class ModuleUiDialog

> e this.state = { title: this.props.title, - canSubmit: true, - actionLabel: _t("OK"), + actionLabel: this.props.actionLabel ?? _t("OK"), + cancelLabel: this.props.cancelLabel, + canSubmit: this.props.canSubmit ?? true, }; } @@ -61,11 +68,23 @@ export class ModuleUiDialog

> e this.props.onFinished(false); } + protected setOptions(options: ModuleUiDialogOptions): void { + this.setState((state) => ({ ...state, ...options })); + } + protected renderContent(): React.ReactNode { - return ( -

- {this.props.contentFactory(this.props.contentProps, this.contentRef)} -
- ); + const dialogProps: DialogProps = { + moduleApi: this.props.moduleApi, + setOptions: this.setOptions.bind(this), + cancel: this.cancel.bind(this), + }; + + // Typescript isn't very happy understanding that `contentProps` satisfies `Omit` + const contentProps: P = { + ...this.props.contentProps, + ...dialogProps, + } as unknown as P; + + return
{this.props.contentFactory(contentProps, this.contentRef)}
; } } diff --git a/src/components/views/dialogs/ScrollableBaseModal.tsx b/src/components/views/dialogs/ScrollableBaseModal.tsx index be068074087..5ffdfafb712 100644 --- a/src/components/views/dialogs/ScrollableBaseModal.tsx +++ b/src/components/views/dialogs/ScrollableBaseModal.tsx @@ -29,6 +29,7 @@ export interface IScrollableBaseState { canSubmit: boolean; title: string; actionLabel: string; + cancelLabel?: string; } /** @@ -103,7 +104,7 @@ export default abstract class ScrollableBaseModal<
{this.renderContent()}
- {_t("Cancel")} + {this.state.cancelLabel ?? _t("Cancel")} >( - title: string, + titleOrOptions: string | ModuleUiDialogOptions, body: (props: P, ref: React.RefObject) => React.ReactNode, props?: Omit, ): Promise<{ didOkOrSubmit: boolean; model: M }> { + const options: ModuleUiDialogOptions = + typeof titleOrOptions === "string" ? { title: titleOrOptions } : titleOrOptions; + return new Promise<{ didOkOrSubmit: boolean; model: M }>((resolve) => { Modal.createDialog( ModuleUiDialog, { - title: title, + ...options, contentFactory: body, - // Typescript isn't very happy understanding that `props` satisfies `Omit` - contentProps: { - ...props, - moduleApi: this, - } as unknown as P, + moduleApi: this, + contentProps: props, }, "mx_CompoundDialog", ).finished.then(([didOkOrSubmit, model]) => { From 739794e8fac5c198881df33ceb6fdc40b79ab17d Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Mon, 14 Aug 2023 15:02:43 +0200 Subject: [PATCH 2/6] Apply the review comments Signed-off-by: Dominik Henneke --- src/components/views/dialogs/ModuleUiDialog.tsx | 13 +++++-------- src/modules/ProxiedModuleApi.ts | 8 ++++---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/components/views/dialogs/ModuleUiDialog.tsx b/src/components/views/dialogs/ModuleUiDialog.tsx index 47101c829e4..1f3680ccf15 100644 --- a/src/components/views/dialogs/ModuleUiDialog.tsx +++ b/src/components/views/dialogs/ModuleUiDialog.tsx @@ -26,10 +26,7 @@ import { _t } from "../../../languageHandler"; interface IProps

> { contentFactory: (props: P, ref: React.RefObject) => React.ReactNode; contentProps: Omit | undefined; - title: string; - actionLabel?: string; - cancelLabel?: string; - canSubmit?: boolean; + initialOptions: ModuleUiDialogOptions, moduleApi: ModuleApi; onFinished(ok?: boolean, model?: Awaited["trySubmit"]>>): void; } @@ -48,10 +45,10 @@ export class ModuleUiDialog

> e super(props); this.state = { - title: this.props.title, - actionLabel: this.props.actionLabel ?? _t("OK"), - cancelLabel: this.props.cancelLabel, - canSubmit: this.props.canSubmit ?? true, + title: this.props.initialOptions.title, + actionLabel: this.props.initialOptions.actionLabel ?? _t("OK"), + cancelLabel: this.props.initialOptions.cancelLabel, + canSubmit: this.props.initialOptions.canSubmit ?? true, }; } diff --git a/src/modules/ProxiedModuleApi.ts b/src/modules/ProxiedModuleApi.ts index 15234ddb305..0216bfad11a 100644 --- a/src/modules/ProxiedModuleApi.ts +++ b/src/modules/ProxiedModuleApi.ts @@ -82,18 +82,18 @@ export class ProxiedModuleApi implements ModuleApi { * @override */ public openDialog>( - titleOrOptions: string | ModuleUiDialogOptions, + initialTitleOrOptions: string | ModuleUiDialogOptions, body: (props: P, ref: React.RefObject) => React.ReactNode, props?: Omit, ): Promise<{ didOkOrSubmit: boolean; model: M }> { - const options: ModuleUiDialogOptions = - typeof titleOrOptions === "string" ? { title: titleOrOptions } : titleOrOptions; + const initialOptions: ModuleUiDialogOptions = + typeof initialTitleOrOptions === "string" ? { title: initialTitleOrOptions } : initialTitleOrOptions; return new Promise<{ didOkOrSubmit: boolean; model: M }>((resolve) => { Modal.createDialog( ModuleUiDialog, { - ...options, + initialOptions, contentFactory: body, moduleApi: this, contentProps: props, From 39beba04d8da50f7ea377684e54efad6f3472b4c Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Mon, 14 Aug 2023 17:02:21 +0200 Subject: [PATCH 3/6] Add unit tests for the module system dialog Signed-off-by: Dominik Henneke --- test/modules/ProxiedModuleApi-test.ts | 105 ---------- test/modules/ProxiedModuleApi-test.tsx | 257 +++++++++++++++++++++++++ 2 files changed, 257 insertions(+), 105 deletions(-) delete mode 100644 test/modules/ProxiedModuleApi-test.ts create mode 100644 test/modules/ProxiedModuleApi-test.tsx diff --git a/test/modules/ProxiedModuleApi-test.ts b/test/modules/ProxiedModuleApi-test.ts deleted file mode 100644 index 881e98c9dff..00000000000 --- a/test/modules/ProxiedModuleApi-test.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* -Copyright 2022 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations"; -import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo"; - -import { ProxiedModuleApi } from "../../src/modules/ProxiedModuleApi"; -import { stubClient } from "../test-utils"; -import { setLanguage } from "../../src/languageHandler"; -import { ModuleRunner } from "../../src/modules/ModuleRunner"; -import { registerMockModule } from "./MockModule"; -import defaultDispatcher from "../../src/dispatcher/dispatcher"; -import { Action } from "../../src/dispatcher/actions"; - -describe("ProxiedApiModule", () => { - afterEach(() => { - ModuleRunner.instance.reset(); - }); - - // Note: Remainder is implicitly tested from end-to-end tests of modules. - - describe("translations", () => { - it("should cache translations", () => { - const api = new ProxiedModuleApi(); - expect(api.translations).toBeFalsy(); - - const translations: TranslationStringsObject = { - ["custom string"]: { - en: "custom string", - fr: "custom french string", - }, - }; - api.registerTranslations(translations); - expect(api.translations).toBe(translations); - }); - - it("should overwriteAccountAuth", async () => { - const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch"); - - const api = new ProxiedModuleApi(); - const accountInfo = {} as unknown as AccountAuthInfo; - const promise = api.overwriteAccountAuth(accountInfo); - - expect(dispatchSpy).toHaveBeenCalledWith( - expect.objectContaining({ - action: Action.OverwriteLogin, - credentials: { - ...accountInfo, - guest: false, - }, - }), - expect.anything(), - ); - - defaultDispatcher.fire(Action.OnLoggedIn); - - await expect(promise).resolves.toBeUndefined(); - }); - - describe("integration", () => { - it("should translate strings using translation system", async () => { - // Test setup - stubClient(); - - // Set up a module to pull translations through - const module = registerMockModule(); - const en = "custom string"; - const de = "custom german string"; - const enVars = "custom variable %(var)s"; - const varVal = "string"; - const deVars = "custom german variable %(var)s"; - const deFull = `custom german variable ${varVal}`; - expect(module.apiInstance).toBeInstanceOf(ProxiedModuleApi); - module.apiInstance.registerTranslations({ - [en]: { - en: en, - de: de, - }, - [enVars]: { - en: enVars, - de: deVars, - }, - }); - await setLanguage("de"); // calls `registerCustomTranslations()` for us - - // See if we can pull the German string out - expect(module.apiInstance.translateString(en)).toEqual(de); - expect(module.apiInstance.translateString(enVars, { var: varVal })).toEqual(deFull); - }); - }); - }); -}); diff --git a/test/modules/ProxiedModuleApi-test.tsx b/test/modules/ProxiedModuleApi-test.tsx new file mode 100644 index 00000000000..de796048a1b --- /dev/null +++ b/test/modules/ProxiedModuleApi-test.tsx @@ -0,0 +1,257 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations"; +import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo"; +import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent"; +import { screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import { ProxiedModuleApi } from "../../src/modules/ProxiedModuleApi"; +import { stubClient } from "../test-utils"; +import { setLanguage } from "../../src/languageHandler"; +import { ModuleRunner } from "../../src/modules/ModuleRunner"; +import { registerMockModule } from "./MockModule"; +import defaultDispatcher from "../../src/dispatcher/dispatcher"; +import { Action } from "../../src/dispatcher/actions"; + +describe("ProxiedApiModule", () => { + afterEach(() => { + ModuleRunner.instance.reset(); + }); + + // Note: Remainder is implicitly tested from end-to-end tests of modules. + + describe("translations", () => { + it("should cache translations", () => { + const api = new ProxiedModuleApi(); + expect(api.translations).toBeFalsy(); + + const translations: TranslationStringsObject = { + ["custom string"]: { + en: "custom string", + fr: "custom french string", + }, + }; + api.registerTranslations(translations); + expect(api.translations).toBe(translations); + }); + + it("should overwriteAccountAuth", async () => { + const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch"); + + const api = new ProxiedModuleApi(); + const accountInfo = {} as unknown as AccountAuthInfo; + const promise = api.overwriteAccountAuth(accountInfo); + + expect(dispatchSpy).toHaveBeenCalledWith( + expect.objectContaining({ + action: Action.OverwriteLogin, + credentials: { + ...accountInfo, + guest: false, + }, + }), + expect.anything(), + ); + + defaultDispatcher.fire(Action.OnLoggedIn); + + await expect(promise).resolves.toBeUndefined(); + }); + + describe("integration", () => { + it("should translate strings using translation system", async () => { + // Test setup + stubClient(); + + // Set up a module to pull translations through + const module = registerMockModule(); + const en = "custom string"; + const de = "custom german string"; + const enVars = "custom variable %(var)s"; + const varVal = "string"; + const deVars = "custom german variable %(var)s"; + const deFull = `custom german variable ${varVal}`; + expect(module.apiInstance).toBeInstanceOf(ProxiedModuleApi); + module.apiInstance.registerTranslations({ + [en]: { + en: en, + de: de, + }, + [enVars]: { + en: enVars, + de: deVars, + }, + }); + await setLanguage("de"); // calls `registerCustomTranslations()` for us + + // See if we can pull the German string out + expect(module.apiInstance.translateString(en)).toEqual(de); + expect(module.apiInstance.translateString(enVars, { var: varVal })).toEqual(deFull); + }); + + afterEach(async () => { + await setLanguage("en"); // reset the language + }); + }); + }); + + describe("openDialog", () => { + it("should open dialog with a custom title and default options", async () => { + class MyDialogContent extends DialogContent { + trySubmit = async () => ({ result: true }); + render = () =>

This is my example content.

; + } + + const api = new ProxiedModuleApi(); + + const resultPromise = api.openDialog<{ result: boolean }, DialogProps, MyDialogContent>( + "My Dialog Title", + (props, ref) => , + ); + + const dialog = await screen.findByRole("dialog"); + + expect(within(dialog).getByRole("heading", { name: "My Dialog Title" })).toBeInTheDocument(); + expect(within(dialog).getByText("This is my example content.")).toBeInTheDocument(); + expect(within(dialog).getByRole("button", { name: "Cancel" })).toBeInTheDocument(); + + await userEvent.click(within(dialog).getByRole("button", { name: "OK" })); + + expect(await resultPromise).toEqual({ + didOkOrSubmit: true, + model: { result: true }, + }); + + expect(dialog).not.toBeInTheDocument(); + }); + + it("should open dialog with custom options", async () => { + class MyDialogContent extends DialogContent { + trySubmit = async () => ({ result: true }); + render = () =>

This is my example content.

; + } + + const api = new ProxiedModuleApi(); + + const resultPromise = api.openDialog<{ result: boolean }, DialogProps, MyDialogContent>( + { + title: "My Custom Dialog Title", + actionLabel: "Submit it", + cancelLabel: "Cancel it", + canSubmit: false, + }, + (props, ref) => , + ); + + const dialog = await screen.findByRole("dialog"); + + expect(within(dialog).getByRole("heading", { name: "My Custom Dialog Title" })).toBeInTheDocument(); + expect(within(dialog).getByText("This is my example content.")).toBeInTheDocument(); + expect(within(dialog).getByRole("button", { name: "Submit it" })).toBeDisabled(); + + await userEvent.click(within(dialog).getByRole("button", { name: "Cancel it" })); + + expect(await resultPromise).toEqual({ didOkOrSubmit: false }); + + expect(dialog).not.toBeInTheDocument(); + }); + + it("should update the options from the opened dialog", async () => { + class MyDialogContent extends DialogContent { + trySubmit = async () => ({ result: true }); + render = () => { + const onClick = () => { + this.props.setOptions({ + title: "My New Title", + actionLabel: "New Action", + cancelLabel: "New Cancel", + }); + + // check if delta updates work + this.props.setOptions({ + canSubmit: false, + }); + }; + + return ( + + ); + }; + } + + const api = new ProxiedModuleApi(); + + const resultPromise = api.openDialog<{ result: boolean }, DialogProps, MyDialogContent>( + "My Dialog Title", + (props, ref) => , + ); + + const dialog = await screen.findByRole("dialog"); + + expect(within(dialog).getByRole("heading", { name: "My Dialog Title" })).toBeInTheDocument(); + expect(within(dialog).getByRole("button", { name: "Cancel" })).toBeInTheDocument(); + expect(within(dialog).getByRole("button", { name: "OK" })).toBeEnabled(); + + await userEvent.click(within(dialog).getByRole("button", { name: "Change the settings!" })); + + expect(within(dialog).getByRole("heading", { name: "My New Title" })).toBeInTheDocument(); + expect(within(dialog).getByRole("button", { name: "New Action" })).toBeDisabled(); + + await userEvent.click(within(dialog).getByRole("button", { name: "New Cancel" })); + + expect(await resultPromise).toEqual({ + didOkOrSubmit: false, + model: undefined, + }); + + expect(dialog).not.toBeInTheDocument(); + }); + + it("should cancel the dialog from within the dialog", async () => { + class MyDialogContent extends DialogContent { + trySubmit = async () => ({ result: true }); + render = () => ( + + ); + } + + const api = new ProxiedModuleApi(); + + const resultPromise = api.openDialog<{ result: boolean }, DialogProps, MyDialogContent>( + "My Dialog Title", + (props, ref) => , + ); + + const dialog = await screen.findByRole("dialog"); + + await userEvent.click(within(dialog).getByRole("button", { name: "No need for action" })); + + expect(await resultPromise).toEqual({ + didOkOrSubmit: false, + model: undefined, + }); + + expect(dialog).not.toBeInTheDocument(); + }); + }); +}); From e4b999a679affb0d03e6076e8af43a77e0c1e092 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 17 Aug 2023 12:24:38 +0200 Subject: [PATCH 4/6] Bump @matrix-org/react-sdk-module-api from 1.0.0 to 2.0.0 Signed-off-by: Dominik Henneke --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e25b8215168..e2c65cd9cf5 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@babel/runtime": "^7.12.5", "@matrix-org/analytics-events": "^0.6.0", "@matrix-org/matrix-wysiwyg": "^2.4.1", - "@matrix-org/react-sdk-module-api": "^1.0.0", + "@matrix-org/react-sdk-module-api": "^2.0.0", "@sentry/browser": "^7.0.0", "@sentry/tracing": "^7.0.0", "@testing-library/react-hooks": "^8.0.1", diff --git a/yarn.lock b/yarn.lock index d40f0ced664..612d830c53c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1867,10 +1867,10 @@ version "3.2.14" resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz#acd96c00a881d0f462e1f97a56c73742c8dbc984" -"@matrix-org/react-sdk-module-api@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-1.0.0.tgz#de73e163a439fe330f6971a6a0cef2ccb090d616" - integrity sha512-drhPkoPWitAv9bXS2q8cyaqPta/KGF+Ph3aZSmaYiOPyY5S84e4Ju3JI6/HExqF8+HyBsajlCKtyvTZsMsTIFA== +"@matrix-org/react-sdk-module-api@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-2.0.0.tgz#f894af429ad352d5151dc7240cc2f987d9dab780" + integrity sha512-o/M+IfB3bu4S3yTO10zMRiEtTQagV9AJ9cNmq8a/ksniCx3QLShtzWeL5FkTa8co0ab/VdxdqTlEux0aStT/dg== dependencies: "@babel/runtime" "^7.17.9" From 1a8e65b339e2440742f0ddabe7cf202020d38481 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 17 Aug 2023 12:44:36 +0200 Subject: [PATCH 5/6] Run prettier Signed-off-by: Dominik Henneke --- src/components/views/dialogs/ModuleUiDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/ModuleUiDialog.tsx b/src/components/views/dialogs/ModuleUiDialog.tsx index 1f3680ccf15..6fbcf1602be 100644 --- a/src/components/views/dialogs/ModuleUiDialog.tsx +++ b/src/components/views/dialogs/ModuleUiDialog.tsx @@ -26,7 +26,7 @@ import { _t } from "../../../languageHandler"; interface IProps

> { contentFactory: (props: P, ref: React.RefObject) => React.ReactNode; contentProps: Omit | undefined; - initialOptions: ModuleUiDialogOptions, + initialOptions: ModuleUiDialogOptions; moduleApi: ModuleApi; onFinished(ok?: boolean, model?: Awaited["trySubmit"]>>): void; } From 2c7c0ed74a40a234c226b3cd36ec6189ea7f7f52 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 18 Aug 2023 13:54:02 +0200 Subject: [PATCH 6/6] Apply review comments Signed-off-by: Dominik Henneke --- src/components/views/dialogs/ModuleUiDialog.tsx | 8 ++++---- src/modules/ProxiedModuleApi.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/views/dialogs/ModuleUiDialog.tsx b/src/components/views/dialogs/ModuleUiDialog.tsx index 6fbcf1602be..b43356666a7 100644 --- a/src/components/views/dialogs/ModuleUiDialog.tsx +++ b/src/components/views/dialogs/ModuleUiDialog.tsx @@ -25,7 +25,7 @@ import { _t } from "../../../languageHandler"; interface IProps

> { contentFactory: (props: P, ref: React.RefObject) => React.ReactNode; - contentProps: Omit | undefined; + additionalContentProps: Omit | undefined; initialOptions: ModuleUiDialogOptions; moduleApi: ModuleApi; onFinished(ok?: boolean, model?: Awaited["trySubmit"]>>): void; @@ -65,7 +65,7 @@ export class ModuleUiDialog

> e this.props.onFinished(false); } - protected setOptions(options: ModuleUiDialogOptions): void { + private setOptions(options: ModuleUiDialogOptions): void { this.setState((state) => ({ ...state, ...options })); } @@ -76,9 +76,9 @@ export class ModuleUiDialog

> e cancel: this.cancel.bind(this), }; - // Typescript isn't very happy understanding that `contentProps` satisfies `Omit` + // Typescript isn't very happy understanding that `contentProps` satisfies `P` const contentProps: P = { - ...this.props.contentProps, + ...this.props.additionalContentProps, ...dialogProps, } as unknown as P; diff --git a/src/modules/ProxiedModuleApi.ts b/src/modules/ProxiedModuleApi.ts index 0216bfad11a..e7d7cb4b442 100644 --- a/src/modules/ProxiedModuleApi.ts +++ b/src/modules/ProxiedModuleApi.ts @@ -96,7 +96,7 @@ export class ProxiedModuleApi implements ModuleApi { initialOptions, contentFactory: body, moduleApi: this, - contentProps: props, + additionalContentProps: props, }, "mx_CompoundDialog", ).finished.then(([didOkOrSubmit, model]) => {