diff --git a/packages/apps/esm-devtools-app/src/routes.json b/packages/apps/esm-devtools-app/src/routes.json index 8addd4cdb..ff40cfa8d 100644 --- a/packages/apps/esm-devtools-app/src/routes.json +++ b/packages/apps/esm-devtools-app/src/routes.json @@ -8,7 +8,7 @@ "offline": true } ], - "extensions": [ + "modals": [ { "name": "importmap-override-modal", "component": "importmapOverrideModal" diff --git a/packages/apps/esm-offline-tools-app/src/routes.json b/packages/apps/esm-offline-tools-app/src/routes.json index c6f314480..022350c73 100644 --- a/packages/apps/esm-offline-tools-app/src/routes.json +++ b/packages/apps/esm-offline-tools-app/src/routes.json @@ -25,12 +25,6 @@ "online": true, "offline": true }, - { - "name": "offline-tools-confirmation-modal", - "component": "offlineToolsConfirmationModal", - "online": true, - "offline": true - }, { "name": "offline-tools-dashboard-patients-card", "slot": "offline-tools-dashboard-cards", @@ -140,5 +134,11 @@ "offline": true, "order": 1 } + ], + "modals": [ + { + "name": "offline-tools-confirmation-modal", + "component": "offlineToolsConfirmationModal" + } ] } diff --git a/packages/apps/esm-primary-navigation-app/src/routes.json b/packages/apps/esm-primary-navigation-app/src/routes.json index 959147387..9d0c648dd 100644 --- a/packages/apps/esm-primary-navigation-app/src/routes.json +++ b/packages/apps/esm-primary-navigation-app/src/routes.json @@ -36,12 +36,6 @@ "offline": true, "order": 1 }, - { - "name": "change-language-modal", - "component": "changeLanguageModal", - "online": true, - "offline": true - }, { "name": "offline-banner", "slot": "user-panel-slot", @@ -56,5 +50,11 @@ "online": true, "offline": true } + ], + "modals": [ + { + "name": "change-language-modal", + "component": "changeLanguageModal" + } ] } diff --git a/packages/framework/esm-framework/docs/API.md b/packages/framework/esm-framework/docs/API.md index 5e00451e5..63c94c367 100644 --- a/packages/framework/esm-framework/docs/API.md +++ b/packages/framework/esm-framework/docs/API.md @@ -432,7 +432,7 @@ ___ #### Defined in -[packages/framework/esm-config/src/types.ts:38](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L38) +[packages/framework/esm-config/src/types.ts:40](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L40) ___ @@ -468,6 +468,18 @@ ___ ___ +### ModalDefintion + +Ƭ **ModalDefintion**: { `name`: `string` } & { `component`: `string` } \| { `component?`: `never` } + +A definition of an modal as extracted from an app's routes.json + +#### Defined in + +[packages/framework/esm-globals/src/types.ts:237](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L237) + +___ + ### OpenmrsRoutes Ƭ **OpenmrsRoutes**: `Record`<`string`, [`OpenmrsAppRoutes`](interfaces/OpenmrsAppRoutes.md)\> @@ -477,7 +489,7 @@ Basically, this is the same as the app routes, with each routes definition keyed #### Defined in -[packages/framework/esm-globals/src/types.ts:260](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L260) +[packages/framework/esm-globals/src/types.ts:295](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L295) ___ @@ -506,7 +518,7 @@ ___ #### Defined in -[packages/framework/esm-config/src/types.ts:60](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L60) +[packages/framework/esm-config/src/types.ts:62](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L62) ___ @@ -562,7 +574,7 @@ ___ #### Defined in -[packages/framework/esm-config/src/types.ts:67](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L67) +[packages/framework/esm-config/src/types.ts:69](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L69) ___ @@ -586,7 +598,7 @@ ___ #### Defined in -[packages/framework/esm-config/src/types.ts:65](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L65) +[packages/framework/esm-config/src/types.ts:67](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L67) ___ @@ -4749,17 +4761,20 @@ ___ ### showModal -▸ **showModal**(`extensionId`, `props?`, `onClose?`): () => `void` +▸ **showModal**(`modalName`, `props?`, `onClose?`): () => `void` + +Shows a modal dialog. -Shows the provided extension component in a modal dialog. +The modal must have been registered by name. This should be done in the `routes.json` file of the +app that defines the modal. #### Parameters | Name | Type | Description | | :------ | :------ | :------ | -| `extensionId` | `string` | The id of the extension to show. | -| `props` | `Record`<`string`, `any`\> | The optional props to provide to the extension. | -| `onClose` | () => `void` | The optional notification to receive when the modal is closed. | +| `modalName` | `string` | The name of the modal to show. | +| `props` | `Record`<`string`, `any`\> | The optional props to provide to the modal. | +| `onClose` | () => `void` | The optional callback to call when the modal is closed. | #### Returns @@ -4775,7 +4790,7 @@ The dispose function to force closing the modal dialog. #### Defined in -[packages/framework/esm-styleguide/src/modals/index.tsx:160](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/modals/index.tsx#L160) +[packages/framework/esm-styleguide/src/modals/index.tsx:208](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/modals/index.tsx#L208) ___ diff --git a/packages/framework/esm-framework/docs/interfaces/DisplayConditionsConfigObject.md b/packages/framework/esm-framework/docs/interfaces/DisplayConditionsConfigObject.md index 4839ada0c..c267bdf0e 100644 --- a/packages/framework/esm-framework/docs/interfaces/DisplayConditionsConfigObject.md +++ b/packages/framework/esm-framework/docs/interfaces/DisplayConditionsConfigObject.md @@ -6,10 +6,32 @@ ### Properties +- [offline](DisplayConditionsConfigObject.md#offline) +- [online](DisplayConditionsConfigObject.md#online) - [privileges](DisplayConditionsConfigObject.md#privileges) ## Properties +### offline + +• `Optional` **offline**: `boolean` + +#### Defined in + +[packages/framework/esm-config/src/types.ts:37](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L37) + +___ + +### online + +• `Optional` **online**: `boolean` + +#### Defined in + +[packages/framework/esm-config/src/types.ts:36](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L36) + +___ + ### privileges • `Optional` **privileges**: `string`[] diff --git a/packages/framework/esm-framework/docs/interfaces/ExtensionSlotConfig.md b/packages/framework/esm-framework/docs/interfaces/ExtensionSlotConfig.md index 2201e9904..d20b09a62 100644 --- a/packages/framework/esm-framework/docs/interfaces/ExtensionSlotConfig.md +++ b/packages/framework/esm-framework/docs/interfaces/ExtensionSlotConfig.md @@ -19,7 +19,7 @@ #### Defined in -[packages/framework/esm-config/src/types.ts:41](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L41) +[packages/framework/esm-config/src/types.ts:43](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L43) ___ @@ -29,7 +29,7 @@ ___ #### Defined in -[packages/framework/esm-config/src/types.ts:44](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L44) +[packages/framework/esm-config/src/types.ts:46](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L46) ___ @@ -39,7 +39,7 @@ ___ #### Defined in -[packages/framework/esm-config/src/types.ts:43](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L43) +[packages/framework/esm-config/src/types.ts:45](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L45) ___ @@ -49,4 +49,4 @@ ___ #### Defined in -[packages/framework/esm-config/src/types.ts:42](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L42) +[packages/framework/esm-config/src/types.ts:44](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L44) diff --git a/packages/framework/esm-framework/docs/interfaces/ExtensionSlotConfigObject.md b/packages/framework/esm-framework/docs/interfaces/ExtensionSlotConfigObject.md index 589caaf53..d75ea70b3 100644 --- a/packages/framework/esm-framework/docs/interfaces/ExtensionSlotConfigObject.md +++ b/packages/framework/esm-framework/docs/interfaces/ExtensionSlotConfigObject.md @@ -20,7 +20,7 @@ Additional extension IDs to assign to this slot, in addition to those `attach`ed #### Defined in -[packages/framework/esm-config/src/types.ts:53](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L53) +[packages/framework/esm-config/src/types.ts:55](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L55) ___ @@ -32,7 +32,7 @@ Overrides the default ordering of extensions. #### Defined in -[packages/framework/esm-config/src/types.ts:57](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L57) +[packages/framework/esm-config/src/types.ts:59](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L59) ___ @@ -44,4 +44,4 @@ Extension IDs which were `attach`ed to the slot but which should not be assigned #### Defined in -[packages/framework/esm-config/src/types.ts:55](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L55) +[packages/framework/esm-config/src/types.ts:57](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-config/src/types.ts#L57) diff --git a/packages/framework/esm-framework/docs/interfaces/OpenmrsAppRoutes.md b/packages/framework/esm-framework/docs/interfaces/OpenmrsAppRoutes.md index e4786c5d0..ea66734d1 100644 --- a/packages/framework/esm-framework/docs/interfaces/OpenmrsAppRoutes.md +++ b/packages/framework/esm-framework/docs/interfaces/OpenmrsAppRoutes.md @@ -10,6 +10,7 @@ This interface describes the format of the routes provided by an app - [backendDependencies](OpenmrsAppRoutes.md#backenddependencies) - [extensions](OpenmrsAppRoutes.md#extensions) +- [modals](OpenmrsAppRoutes.md#modals) - [pages](OpenmrsAppRoutes.md#pages) - [version](OpenmrsAppRoutes.md#version) @@ -23,7 +24,7 @@ A list of backend modules necessary for this frontend module and the correspondi #### Defined in -[packages/framework/esm-globals/src/types.ts:245](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L245) +[packages/framework/esm-globals/src/types.ts:276](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L276) ___ @@ -35,7 +36,19 @@ An array of all extensions supported by this frontend module. Extensions can be #### Defined in -[packages/framework/esm-globals/src/types.ts:253](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L253) +[packages/framework/esm-globals/src/types.ts:284](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L284) + +___ + +### modals + +• `Optional` **modals**: [`ModalDefintion`](../API.md#modaldefintion)[] + +An array of all modals supported by this frontend module. Modals can be launched by name. + +#### Defined in + +[packages/framework/esm-globals/src/types.ts:288](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L288) ___ @@ -47,7 +60,7 @@ An array of all pages supported by this frontend module. Pages are automatically #### Defined in -[packages/framework/esm-globals/src/types.ts:249](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L249) +[packages/framework/esm-globals/src/types.ts:280](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L280) ___ @@ -59,4 +72,4 @@ The version of this frontend module. #### Defined in -[packages/framework/esm-globals/src/types.ts:241](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L241) +[packages/framework/esm-globals/src/types.ts:272](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L272) diff --git a/packages/framework/esm-framework/docs/interfaces/ResourceLoader.md b/packages/framework/esm-framework/docs/interfaces/ResourceLoader.md index 6921586e3..a3f056776 100644 --- a/packages/framework/esm-framework/docs/interfaces/ResourceLoader.md +++ b/packages/framework/esm-framework/docs/interfaces/ResourceLoader.md @@ -20,4 +20,4 @@ #### Defined in -[packages/framework/esm-globals/src/types.ts:263](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L263) +[packages/framework/esm-globals/src/types.ts:298](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-globals/src/types.ts#L298) diff --git a/packages/framework/esm-globals/src/types.ts b/packages/framework/esm-globals/src/types.ts index b9357766e..da2bc2df1 100644 --- a/packages/framework/esm-globals/src/types.ts +++ b/packages/framework/esm-globals/src/types.ts @@ -231,6 +231,37 @@ export type ExtensionDefinition = { } ); +/** + * A definition of an modal as extracted from an app's routes.json + */ +export type ModalDefintion = { + /** + * The name of this modal. This is used to launch the modal. + */ + name: string; +} & ( + | { + /** + * The name of the component exported by this frontend module. + */ + component: string; + /** + * @internal + */ + load?: never; + } + | { + /** + * The name of the component exported by this frontend module. + */ + component?: never; + /** + * @internal + */ + load: () => Promise<{ default?: LifeCycles } & LifeCycles>; + } +); + /** * This interface describes the format of the routes provided by an app */ @@ -251,6 +282,10 @@ export interface OpenmrsAppRoutes { * An array of all extensions supported by this frontend module. Extensions can be mounted in extension slots, either via declarations in this file or configuration. */ extensions?: Array; + /** + * An array of all modals supported by this frontend module. Modals can be launched by name. + */ + modals?: Array; } /** diff --git a/packages/framework/esm-styleguide/src/modals/index.tsx b/packages/framework/esm-styleguide/src/modals/index.tsx index b3fe6ea04..72014f97a 100644 --- a/packages/framework/esm-styleguide/src/modals/index.tsx +++ b/packages/framework/esm-styleguide/src/modals/index.tsx @@ -1,7 +1,9 @@ /** @module @category UI */ -import { renderExtension } from '@openmrs/esm-extensions'; +import { mountRootParcel, type Parcel } from 'single-spa'; import { createGlobalStore } from '@openmrs/esm-state'; -import type { Parcel } from 'single-spa'; +import { getModalRegistration } from './registry'; +import { reportError } from '@openmrs/esm-error-handling'; +export * from './registry'; type ModalInstanceState = 'NEW' | 'MOUNTED' | 'TO_BE_DELETED'; @@ -10,7 +12,7 @@ interface ModalInstance { state: ModalInstanceState; onClose: () => void; parcel?: Parcel | null; - extensionId: string; + modalName: string; props: Record; } @@ -19,7 +21,7 @@ interface ModalState { modalStack: Array; } -const modalStore = createGlobalStore('globalModalState', { +const modalStore = createGlobalStore('modalState', { modalContainer: null, modalStack: [], }); @@ -51,13 +53,52 @@ function createModalFrame() { return { outer, contentContainer }; } +let parcelCount = 0; + +/** + * Mounts the named modal into the specified DOM element + */ +async function renderModalIntoDOM( + domElement: HTMLElement, + modalName: string, + additionalProps: Record = {}, +): Promise { + const modalRegistration = getModalRegistration(modalName); + let parcel: Parcel | null = null; + + if (domElement) { + if (!modalRegistration) { + throw Error(`No modal named '${modalName}' has been registered.`); + } + + const { load } = modalRegistration; + + const { default: result, ...lifecycle } = await load(); + const id = parcelCount++; + parcel = mountRootParcel( + { + ...(result ?? lifecycle), + name: `${modalName}-${id}`, + }, + { + ...additionalProps, + domElement, + }, + ); + } else { + reportError(`Failed to launch modal. Please notify your administrator. Modal name: ${modalName}`); + } + + return parcel; +} + const original = window.getComputedStyle(document.body).overflow; function handleModalStateUpdate({ modalStack, modalContainer }: ModalState) { if (!modalContainer) return; if (modalStack.length) { - // spin up the container if it was hidden previously + // ensure the container is visible if (!modalContainer.style.visibility) { addEventListener('keydown', handleEscKey); document.body.style.overflow = 'hidden'; @@ -69,7 +110,7 @@ function handleModalStateUpdate({ modalStack, modalContainer }: ModalState) { case 'NEW': { const { outer, contentContainer } = createModalFrame(); instance.container = outer; - renderExtension(contentContainer, '', '', instance.extensionId, undefined, instance.props).then((parcel) => { + renderModalIntoDOM(contentContainer, instance.modalName, instance.props).then((parcel) => { instance.parcel = parcel; instance.state = 'MOUNTED'; modalContainer.prepend(outer); @@ -104,16 +145,6 @@ function handleModalStateUpdate({ modalStack, modalContainer }: ModalState) { } } -export function renderModals(modalContainer: HTMLElement | null) { - if (modalContainer) { - modalStore.subscribe(handleModalStateUpdate); - - modalStore.setState({ - ...modalStore.getState(), - modalContainer, - }); - } -} function openInstance(instance: ModalInstance) { const state = modalStore.getState(); const modalStack = [instance, ...state.modalStack]; @@ -151,13 +182,30 @@ function handleEscKey(e: KeyboardEvent) { } /** - * Shows the provided extension component in a modal dialog. - * @param extensionId The id of the extension to show. - * @param props The optional props to provide to the extension. - * @param onClose The optional notification to receive when the modal is closed. + * @internal + * Sets up the modals system. Should be called in the app shell during initialization. + */ +export function setupModals(modalContainer: HTMLElement | null) { + modalStore.subscribe(handleModalStateUpdate); + + modalStore.setState({ + ...modalStore.getState(), + modalContainer, + }); +} + +/** + * Shows a modal dialog. + * + * The modal must have been registered by name. This should be done in the `routes.json` file of the + * app that defines the modal. + * + * @param modalName The name of the modal to show. + * @param props The optional props to provide to the modal. + * @param onClose The optional callback to call when the modal is closed. * @returns The dispose function to force closing the modal dialog. */ -export function showModal(extensionId: string, props: Record = {}, onClose: () => void = () => {}) { +export function showModal(modalName: string, props: Record = {}, onClose: () => void = () => {}) { const close = () => { const state = modalStore.getState(); const item = state.modalStack.find((m) => m.onClose === onClose); @@ -167,16 +215,20 @@ export function showModal(extensionId: string, props: Record = {}, } }; - openInstance({ - state: 'NEW', - onClose, - extensionId, - props: { - extensionId, - close, - ...props, - }, - }); + const modalRegistration = getModalRegistration(modalName); + if (!modalRegistration) { + reportError(`Failed to launch modal. Please notify your administrator. Modal name: "${modalName}"`); + } else { + openInstance({ + state: 'NEW', + onClose, + modalName, + props: { + close, + ...props, + }, + }); + } return close; } diff --git a/packages/framework/esm-styleguide/src/modals/registry.ts b/packages/framework/esm-styleguide/src/modals/registry.ts new file mode 100644 index 000000000..24c09d6f2 --- /dev/null +++ b/packages/framework/esm-styleguide/src/modals/registry.ts @@ -0,0 +1,48 @@ +import { getExtensionRegistration } from '@openmrs/esm-extensions'; +import { createGlobalStore } from '@openmrs/esm-state'; +import type { LifeCycles } from 'single-spa'; + +/** @internal */ +export interface ModalRegistration { + name: string; + load(): Promise<{ default?: LifeCycles } & LifeCycles>; + moduleName: string; +} + +interface ModalRegistry { + /** Modals indexed by name */ + modals: Record; +} + +const modalRegistryStore = createGlobalStore('modalRegistry', { + modals: {}, +}); + +/** @internal */ +export function registerModal(modalRegistration: ModalRegistration) { + modalRegistryStore.setState((state) => { + state.modals[modalRegistration.name] = modalRegistration; + return state; + }); +} + +/** @internal */ +export function getModalRegistration(modalName: string): ModalRegistration | undefined { + let modalRegistration = modalRegistryStore.getState().modals[modalName]; + if (!modalRegistration) { + const extensionRegistration = getExtensionRegistration(modalName); + if (extensionRegistration) { + modalRegistration = { + name: modalName, + load: extensionRegistration.load, + moduleName: extensionRegistration.moduleName, + }; + console.warn( + `Modal ${modalName} was registered as an extension. This is deprecated and will be removed in the future. Please register it in the "modals" section of routes.json instead of the "extensions" section.`, + ); + // Register it so the warning only appears once + registerModal(modalRegistration); + } + } + return modalRegistration; +} diff --git a/packages/shell/esm-app-shell/src/apps.ts b/packages/shell/esm-app-shell/src/apps.ts index 08a23b079..e6e1fbe31 100644 --- a/packages/shell/esm-app-shell/src/apps.ts +++ b/packages/shell/esm-app-shell/src/apps.ts @@ -4,11 +4,12 @@ import type { OpenmrsAppRoutes, RouteDefinition, ExtensionRegistration, + ModalDefintion, } from '@openmrs/esm-framework'; import { attach, registerExtension, importDynamic } from '@openmrs/esm-framework'; import { type ActivityFn, type LifeCycles, pathToActiveWhen, registerApplication } from 'single-spa'; import { emptyLifecycle, routeRegex } from './helpers'; -import { registerModuleWithConfigSystem } from '@openmrs/esm-framework/src/internal'; +import { registerModal, registerModuleWithConfigSystem } from '@openmrs/esm-framework/src/internal'; const pages: Array = []; @@ -131,6 +132,7 @@ export function registerApp(appName: string, routes: OpenmrsAppRoutes) { registerModuleWithConfigSystem(appName); const availableExtensions: Array = routes.extensions ?? []; + const availableModals: Array = routes.modals ?? []; routes.pages?.forEach((p) => { if ( @@ -162,6 +164,17 @@ export function registerApp(appName: string, routes: OpenmrsAppRoutes) { ); } }); + + availableModals.forEach((modal) => { + if (modal && typeof modal === 'object' && Object.hasOwn(modal, 'name') && Object.hasOwn(modal, 'component')) { + tryRegisterModal(appName, modal); + } else { + console.warn( + `A modal for ${appName} could not be registered as it does not appear to have the required properties`, + modal, + ); + } + }); } } @@ -203,12 +216,12 @@ export function finishRegisteringAllApps() { } /** - * This function actually converts each page definition into a single-spa application + * This function converts each page definition into a single-spa application * if that's possible. After this point, pages are rendered using single-spa's * routing logic. * * @param appName The name of the app containing this page - * @param page A Javascript object that describes the page defintion, derived from `routes.json` + * @param page An object that describes the page, derived from `routes.json` */ export function tryRegisterPage(appName: string, page: RegisteredPageDefinition) { const route = @@ -242,11 +255,11 @@ To fix this, ensure that you define the "component" field inside the page defini } /** - * This function actually registers an extension definition with the framework and will + * This function registers an extension definition with the framework and will * attach the extension to any configured slots. * - * @param appName The name of the app containing this page - * @param extension A Javascript object that describes the extension defintion, derived from `routes.json` + * @param appName The name of the app containing this extension + * @param extension An object that describes the extension, derived from `routes.json` */ export function tryRegisterExtension(appName: string, extension: ExtensionDefinition) { const name = extension.name; @@ -309,3 +322,53 @@ supported, so the extension will not be loaded.`, attach(slot, name); } } + +/** + * This function actually registers a modal definition with the framework so that it can be launched. + * + * @param appName The name of the app defining this modal + * @param modal An object that describes the modal, derived from `routes.json` + */ +export function tryRegisterModal(appName: string, modal: ModalDefintion) { + const name = modal.name; + if (!name) { + console.error( + `A modal definition in ${appName} is missing an name and thus cannot be +registered. To fix this, ensure that you define the "name" field inside the +modal definition.`, + modal, + ); + return; + } + + if (!modal.component && !modal.load) { + console.error( + `The modal ${name} from ${appName} is missing a 'component' entry and thus cannot be registered. +To fix this, ensure that you define a 'component' field inside the modal definition.`, + modal, + ); + return; + } + + let loader: ExtensionRegistration['load'] | undefined = undefined; + if (modal.component) { + loader = getLoader(appName, modal.component); + } else if (modal.load) { + if (typeof modal.load !== 'function') { + console.error( + `The modal ${name} from ${appName} declares a 'load' property that is not a function. This is not +supported, so the modal will not be loaded.`, + ); + return; + } + loader = modal.load; + } + + if (loader) { + registerModal({ + name, + load: loader, + moduleName: appName, + }); + } +} diff --git a/packages/shell/esm-app-shell/src/run.ts b/packages/shell/esm-app-shell/src/run.ts index 687f6e1aa..3b97b4d24 100644 --- a/packages/shell/esm-app-shell/src/run.ts +++ b/packages/shell/esm-app-shell/src/run.ts @@ -23,7 +23,7 @@ import { messageOmrsServiceWorker, subscribeConnectivity, getCurrentUser, - renderModals, + setupModals, dispatchPrecacheStaticDependencies, activateOfflineCapability, subscribePrecacheStaticDependencies, @@ -288,7 +288,7 @@ function showSnackbars() { } function showModals() { - renderModals(document.querySelector('.omrs-modals-container')); + setupModals(document.querySelector('.omrs-modals-container')); } function showLoadingSpinner() {