@openmrs/esm-extensions 6.3.1-pre.2965 → 6.3.1-pre.2997
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.swcrc +16 -0
- package/.turbo/turbo-build.log +3 -16
- package/dist/extensions.d.ts +74 -0
- package/dist/extensions.js +351 -0
- package/dist/helpers.d.ts +2 -0
- package/dist/helpers.js +7 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +12 -0
- package/dist/modals.d.ts +13 -0
- package/dist/modals.js +28 -0
- package/dist/public.d.ts +4 -0
- package/dist/public.js +3 -0
- package/dist/render.d.ts +11 -0
- package/dist/render.js +62 -0
- package/dist/store.d.ts +94 -0
- package/dist/store.js +53 -0
- package/dist/workspaces.d.ts +63 -0
- package/dist/workspaces.js +121 -0
- package/mock-jest.ts +31 -0
- package/mock.ts +8 -6
- package/package.json +40 -19
- package/src/extensions.test.ts +2 -1
- package/src/modals.ts +1 -1
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +3 -23
- package/vitest.config.ts +8 -0
- package/dist/openmrs-esm-extensions.js +0 -2
- package/dist/openmrs-esm-extensions.js.map +0 -1
- package/jest.config.js +0 -14
- package/webpack.config.js +0 -43
package/.swcrc
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://swc.rs/schema.json",
|
|
3
|
+
"exclude": [".*\\.test\\..*", "setup-tests\\..*"],
|
|
4
|
+
"module": {
|
|
5
|
+
"type": "es6",
|
|
6
|
+
"resolveFully": true
|
|
7
|
+
},
|
|
8
|
+
"jsc": {
|
|
9
|
+
"parser": {
|
|
10
|
+
"syntax": "typescript",
|
|
11
|
+
"tsx": false
|
|
12
|
+
},
|
|
13
|
+
"target": "es2020",
|
|
14
|
+
"baseUrl": "src"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
built modules 234 KiB [built]
|
|
5
|
-
modules by path external "@openmrs/ 252 bytes
|
|
6
|
-
external "@openmrs/esm-config" 42 bytes [built] [code generated]
|
|
7
|
-
external "@openmrs/esm-state" 42 bytes [built] [code generated]
|
|
8
|
-
external "@openmrs/esm-api" 42 bytes [built] [code generated]
|
|
9
|
-
external "@openmrs/esm-expression-evaluator" 42 bytes [built] [code generated]
|
|
10
|
-
external "@openmrs/esm-feature-flags" 42 bytes [built] [code generated]
|
|
11
|
-
external "@openmrs/esm-utils" 42 bytes [built] [code generated]
|
|
12
|
-
cacheable modules 234 KiB
|
|
13
|
-
./src/index.ts + 115 modules 227 KiB [built] [code generated]
|
|
14
|
-
../esm-globals/dist/openmrs-esm-globals.js 6.29 KiB [built] [code generated]
|
|
15
|
-
external "single-spa" 42 bytes [built] [code generated]
|
|
16
|
-
webpack 5.88.0 compiled successfully in 10431 ms
|
|
1
|
+
[0] Successfully compiled: 8 files with swc (123.88ms)
|
|
2
|
+
[0] swc --strip-leading-paths src -d dist exited with code 0
|
|
3
|
+
[1] tsc --project tsconfig.build.json exited with code 0
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** @module @category Extension */
|
|
2
|
+
import { type AssignedExtension } from '.';
|
|
3
|
+
import { type ExtensionRegistration, type ExtensionInternalStore } from './store';
|
|
4
|
+
/**
|
|
5
|
+
* Given an extension ID, which is a string uniquely identifying
|
|
6
|
+
* an instance of an extension within an extension slot, this
|
|
7
|
+
* returns the extension name.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```js
|
|
11
|
+
* getExtensionNameFromId("foo#bar")
|
|
12
|
+
* --> "foo"
|
|
13
|
+
* getExtensionNameFromId("baz")
|
|
14
|
+
* --> "baz"
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function getExtensionNameFromId(extensionId: string): string;
|
|
18
|
+
export declare function getExtensionRegistrationFrom(state: ExtensionInternalStore, extensionId: string): ExtensionRegistration | undefined;
|
|
19
|
+
export declare function getExtensionRegistration(extensionId: string): ExtensionRegistration | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Extensions must be registered in order to be rendered.
|
|
22
|
+
* This is handled by the app shell, when extensions are provided
|
|
23
|
+
* via the `setupOpenMRS` return object.
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export declare const registerExtension: (extensionRegistration: ExtensionRegistration) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Attach an extension to an extension slot.
|
|
29
|
+
*
|
|
30
|
+
* This will cause the extension to be rendered into the specified
|
|
31
|
+
* extension slot, unless it is removed by configuration. Using
|
|
32
|
+
* `attach` is an alternative to specifying the `slot` or `slots`
|
|
33
|
+
* in the extension declaration.
|
|
34
|
+
*
|
|
35
|
+
* It is particularly useful when creating a slot into which
|
|
36
|
+
* you want to render an existing extension. This enables you
|
|
37
|
+
* to do so without modifying the extension's declaration, which
|
|
38
|
+
* may be impractical or inappropriate, for example if you are
|
|
39
|
+
* writing a module for a specific implementation.
|
|
40
|
+
*
|
|
41
|
+
* @param slotName a name uniquely identifying the slot
|
|
42
|
+
* @param extensionId an extension name, with an optional #-suffix
|
|
43
|
+
* to distinguish it from other instances of the same extension
|
|
44
|
+
* attached to the same slot.
|
|
45
|
+
*/
|
|
46
|
+
export declare function attach(slotName: string, extensionId: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* @deprecated Avoid using this. Extension attachments should be considered declarative.
|
|
49
|
+
*/
|
|
50
|
+
export declare function detach(extensionSlotName: string, extensionId: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* @deprecated Avoid using this. Extension attachments should be considered declarative.
|
|
53
|
+
*/
|
|
54
|
+
export declare function detachAll(extensionSlotName: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Gets the list of extensions assigned to a given slot
|
|
57
|
+
*
|
|
58
|
+
* @param slotName The slot to load the assigned extensions for
|
|
59
|
+
* @returns An array of extensions assigned to the named slot
|
|
60
|
+
*/
|
|
61
|
+
export declare function getAssignedExtensions(slotName: string): Array<AssignedExtension>;
|
|
62
|
+
/**
|
|
63
|
+
* Used by by extension slots at mount time.
|
|
64
|
+
*
|
|
65
|
+
* @param moduleName The name of the module that contains the extension slot
|
|
66
|
+
* @param slotName The extension slot name that is actually used
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export declare const registerExtensionSlot: (moduleName: string, slotName: string) => void;
|
|
70
|
+
/**
|
|
71
|
+
* @internal
|
|
72
|
+
* Just for testing.
|
|
73
|
+
*/
|
|
74
|
+
export declare const reset: () => void;
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/** @module @category Extension */ /*
|
|
2
|
+
* We have the following extension modes:
|
|
3
|
+
*
|
|
4
|
+
* - attached (set via code in form of: attach, detach, ...)
|
|
5
|
+
* - configured (set via configuration in form of: added, removed, ...)
|
|
6
|
+
* - assigned (computed from attached and configured)
|
|
7
|
+
* - connected (computed from assigned using connectivity and online / offline)
|
|
8
|
+
*/ import { sessionStore, userHasAccess } from "@openmrs/esm-api";
|
|
9
|
+
import { getExtensionConfigFromStore, getExtensionsConfigStore, getExtensionSlotConfig, getExtensionSlotConfigFromStore, getExtensionSlotsConfigStore } from "@openmrs/esm-config";
|
|
10
|
+
import { evaluateAsBoolean } from "@openmrs/esm-expression-evaluator";
|
|
11
|
+
import { featureFlagsStore } from "@openmrs/esm-feature-flags";
|
|
12
|
+
import { subscribeConnectivityChanged } from "@openmrs/esm-globals";
|
|
13
|
+
import { isOnline as isOnlineFn } from "@openmrs/esm-utils";
|
|
14
|
+
import { isEqual } from "lodash-es";
|
|
15
|
+
import { checkStatusFor, getExtensionInternalStore } from "./index.js";
|
|
16
|
+
import { getExtensionStore, updateInternalExtensionStore } from "./store.js";
|
|
17
|
+
const extensionInternalStore = getExtensionInternalStore();
|
|
18
|
+
const extensionStore = getExtensionStore();
|
|
19
|
+
const slotsConfigStore = getExtensionSlotsConfigStore();
|
|
20
|
+
const extensionsConfigStore = getExtensionsConfigStore();
|
|
21
|
+
// Keep the output store updated
|
|
22
|
+
function updateExtensionOutputStore(internalState, extensionSlotConfigs, extensionsConfigStore, featureFlagStore, sessionStore) {
|
|
23
|
+
const slots = {};
|
|
24
|
+
const isOnline = isOnlineFn();
|
|
25
|
+
const enabledFeatureFlags = Object.entries(featureFlagStore.flags).filter(([, { enabled }])=>enabled).map(([name])=>name);
|
|
26
|
+
for (let [slotName, slot] of Object.entries(internalState.slots)){
|
|
27
|
+
const { config } = getExtensionSlotConfigFromStore(extensionSlotConfigs, slot.name);
|
|
28
|
+
const assignedExtensions = getAssignedExtensionsFromSlotData(slotName, internalState, config, extensionsConfigStore, enabledFeatureFlags, isOnline, sessionStore.session);
|
|
29
|
+
slots[slotName] = {
|
|
30
|
+
moduleName: slot.moduleName,
|
|
31
|
+
assignedExtensions
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (!isEqual(extensionStore.getState().slots, slots)) {
|
|
35
|
+
extensionStore.setState({
|
|
36
|
+
slots
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
extensionInternalStore.subscribe((internalStore)=>{
|
|
41
|
+
updateExtensionOutputStore(internalStore, slotsConfigStore.getState(), extensionsConfigStore.getState(), featureFlagsStore.getState(), sessionStore.getState());
|
|
42
|
+
});
|
|
43
|
+
slotsConfigStore.subscribe((slotConfigs)=>{
|
|
44
|
+
updateExtensionOutputStore(extensionInternalStore.getState(), slotConfigs, extensionsConfigStore.getState(), featureFlagsStore.getState(), sessionStore.getState());
|
|
45
|
+
});
|
|
46
|
+
extensionsConfigStore.subscribe((extensionConfigs)=>{
|
|
47
|
+
updateExtensionOutputStore(extensionInternalStore.getState(), slotsConfigStore.getState(), extensionConfigs, featureFlagsStore.getState(), sessionStore.getState());
|
|
48
|
+
});
|
|
49
|
+
featureFlagsStore.subscribe((featureFlagStore)=>{
|
|
50
|
+
updateExtensionOutputStore(extensionInternalStore.getState(), slotsConfigStore.getState(), extensionsConfigStore.getState(), featureFlagStore, sessionStore.getState());
|
|
51
|
+
});
|
|
52
|
+
sessionStore.subscribe((session)=>{
|
|
53
|
+
updateExtensionOutputStore(extensionInternalStore.getState(), slotsConfigStore.getState(), extensionsConfigStore.getState(), featureFlagsStore.getState(), session);
|
|
54
|
+
});
|
|
55
|
+
function updateOutputStoreToCurrent() {
|
|
56
|
+
updateExtensionOutputStore(extensionInternalStore.getState(), slotsConfigStore.getState(), extensionsConfigStore.getState(), featureFlagsStore.getState(), sessionStore.getState());
|
|
57
|
+
}
|
|
58
|
+
updateOutputStoreToCurrent();
|
|
59
|
+
subscribeConnectivityChanged(updateOutputStoreToCurrent);
|
|
60
|
+
function createNewExtensionSlotInfo(slotName, moduleName) {
|
|
61
|
+
return {
|
|
62
|
+
moduleName,
|
|
63
|
+
name: slotName,
|
|
64
|
+
attachedIds: [],
|
|
65
|
+
config: null
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Given an extension ID, which is a string uniquely identifying
|
|
70
|
+
* an instance of an extension within an extension slot, this
|
|
71
|
+
* returns the extension name.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```js
|
|
75
|
+
* getExtensionNameFromId("foo#bar")
|
|
76
|
+
* --> "foo"
|
|
77
|
+
* getExtensionNameFromId("baz")
|
|
78
|
+
* --> "baz"
|
|
79
|
+
* ```
|
|
80
|
+
*/ export function getExtensionNameFromId(extensionId) {
|
|
81
|
+
const [extensionName] = extensionId.split('#');
|
|
82
|
+
return extensionName;
|
|
83
|
+
}
|
|
84
|
+
export function getExtensionRegistrationFrom(state, extensionId) {
|
|
85
|
+
const name = getExtensionNameFromId(extensionId);
|
|
86
|
+
return state.extensions[name];
|
|
87
|
+
}
|
|
88
|
+
export function getExtensionRegistration(extensionId) {
|
|
89
|
+
const state = extensionInternalStore.getState();
|
|
90
|
+
return getExtensionRegistrationFrom(state, extensionId);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Extensions must be registered in order to be rendered.
|
|
94
|
+
* This is handled by the app shell, when extensions are provided
|
|
95
|
+
* via the `setupOpenMRS` return object.
|
|
96
|
+
* @internal
|
|
97
|
+
*/ export const registerExtension = (extensionRegistration)=>extensionInternalStore.setState((state)=>{
|
|
98
|
+
state.extensions[extensionRegistration.name] = {
|
|
99
|
+
...extensionRegistration,
|
|
100
|
+
instances: []
|
|
101
|
+
};
|
|
102
|
+
return state;
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* Attach an extension to an extension slot.
|
|
106
|
+
*
|
|
107
|
+
* This will cause the extension to be rendered into the specified
|
|
108
|
+
* extension slot, unless it is removed by configuration. Using
|
|
109
|
+
* `attach` is an alternative to specifying the `slot` or `slots`
|
|
110
|
+
* in the extension declaration.
|
|
111
|
+
*
|
|
112
|
+
* It is particularly useful when creating a slot into which
|
|
113
|
+
* you want to render an existing extension. This enables you
|
|
114
|
+
* to do so without modifying the extension's declaration, which
|
|
115
|
+
* may be impractical or inappropriate, for example if you are
|
|
116
|
+
* writing a module for a specific implementation.
|
|
117
|
+
*
|
|
118
|
+
* @param slotName a name uniquely identifying the slot
|
|
119
|
+
* @param extensionId an extension name, with an optional #-suffix
|
|
120
|
+
* to distinguish it from other instances of the same extension
|
|
121
|
+
* attached to the same slot.
|
|
122
|
+
*/ export function attach(slotName, extensionId) {
|
|
123
|
+
updateInternalExtensionStore((state)=>{
|
|
124
|
+
const existingSlot = state.slots[slotName];
|
|
125
|
+
if (!existingSlot) {
|
|
126
|
+
return {
|
|
127
|
+
...state,
|
|
128
|
+
slots: {
|
|
129
|
+
...state.slots,
|
|
130
|
+
[slotName]: {
|
|
131
|
+
...createNewExtensionSlotInfo(slotName),
|
|
132
|
+
attachedIds: [
|
|
133
|
+
extensionId
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
} else {
|
|
139
|
+
return {
|
|
140
|
+
...state,
|
|
141
|
+
slots: {
|
|
142
|
+
...state.slots,
|
|
143
|
+
[slotName]: {
|
|
144
|
+
...existingSlot,
|
|
145
|
+
attachedIds: [
|
|
146
|
+
...existingSlot.attachedIds,
|
|
147
|
+
extensionId
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* @deprecated Avoid using this. Extension attachments should be considered declarative.
|
|
157
|
+
*/ export function detach(extensionSlotName, extensionId) {
|
|
158
|
+
updateInternalExtensionStore((state)=>{
|
|
159
|
+
const existingSlot = state.slots[extensionSlotName];
|
|
160
|
+
if (existingSlot && existingSlot.attachedIds.includes(extensionId)) {
|
|
161
|
+
return {
|
|
162
|
+
...state,
|
|
163
|
+
slots: {
|
|
164
|
+
...state.slots,
|
|
165
|
+
[extensionSlotName]: {
|
|
166
|
+
...existingSlot,
|
|
167
|
+
attachedIds: existingSlot.attachedIds.filter((id)=>id !== extensionId)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
} else {
|
|
172
|
+
return state;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* @deprecated Avoid using this. Extension attachments should be considered declarative.
|
|
178
|
+
*/ export function detachAll(extensionSlotName) {
|
|
179
|
+
updateInternalExtensionStore((state)=>{
|
|
180
|
+
const existingSlot = state.slots[extensionSlotName];
|
|
181
|
+
if (existingSlot) {
|
|
182
|
+
return {
|
|
183
|
+
...state,
|
|
184
|
+
slots: {
|
|
185
|
+
...state.slots,
|
|
186
|
+
[extensionSlotName]: {
|
|
187
|
+
...existingSlot,
|
|
188
|
+
attachedIds: []
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
} else {
|
|
193
|
+
return state;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get an order index for the extension. This will
|
|
199
|
+
* come from either its configured order, its registered order
|
|
200
|
+
* parameter, or the order in which it happened to be attached.
|
|
201
|
+
*/ function getOrder(extensionId, configuredOrder, registeredOrderIndex, attachedOrder) {
|
|
202
|
+
const configuredIndex = configuredOrder.indexOf(extensionId);
|
|
203
|
+
if (configuredIndex !== -1) {
|
|
204
|
+
return configuredIndex;
|
|
205
|
+
} else if (registeredOrderIndex !== undefined) {
|
|
206
|
+
// extensions that don't have a configured order should appear after those that do
|
|
207
|
+
return 1000 + registeredOrderIndex;
|
|
208
|
+
} else {
|
|
209
|
+
const assignedIndex = attachedOrder.indexOf(extensionId);
|
|
210
|
+
if (assignedIndex !== -1) {
|
|
211
|
+
// extensions that have neither a configured nor registered order should appear
|
|
212
|
+
// after all others
|
|
213
|
+
return 2000 + assignedIndex;
|
|
214
|
+
} else {
|
|
215
|
+
return -1;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function getAssignedExtensionsFromSlotData(slotName, internalState, config, extensionConfigStoreState, enabledFeatureFlags, isOnline, session) {
|
|
220
|
+
const attachedIds = internalState.slots[slotName].attachedIds;
|
|
221
|
+
const assignedIds = calculateAssignedIds(config, attachedIds);
|
|
222
|
+
const extensions = [];
|
|
223
|
+
for (let id of assignedIds){
|
|
224
|
+
const { config: extensionConfig } = getExtensionConfigFromStore(extensionConfigStoreState, slotName, id);
|
|
225
|
+
const name = getExtensionNameFromId(id);
|
|
226
|
+
const extension = internalState.extensions[name];
|
|
227
|
+
// if the extension has not been registered yet, do not include it
|
|
228
|
+
if (extension) {
|
|
229
|
+
const requiredPrivileges = extensionConfig?.['Display conditions']?.privileges ?? extension.privileges ?? [];
|
|
230
|
+
if (requiredPrivileges && (typeof requiredPrivileges === 'string' || Array.isArray(requiredPrivileges) && requiredPrivileges.length > 0)) {
|
|
231
|
+
if (!session?.user) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (!userHasAccess(requiredPrivileges, session.user)) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const displayConditionExpression = extensionConfig?.['Display conditions']?.expression ?? null;
|
|
239
|
+
if (displayConditionExpression !== null) {
|
|
240
|
+
try {
|
|
241
|
+
if (!evaluateAsBoolean(displayConditionExpression, {
|
|
242
|
+
session
|
|
243
|
+
})) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
} catch (e) {
|
|
247
|
+
console.error(`Error while evaluating expression ${displayConditionExpression}`, e);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (extension.featureFlag && !enabledFeatureFlags.includes(extension.featureFlag)) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (window.offlineEnabled && !checkStatusFor(isOnline, extension.online, extension.offline)) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
extensions.push({
|
|
258
|
+
id,
|
|
259
|
+
name,
|
|
260
|
+
moduleName: extension.moduleName,
|
|
261
|
+
config: extensionConfig,
|
|
262
|
+
featureFlag: extension.featureFlag,
|
|
263
|
+
meta: extension.meta,
|
|
264
|
+
online: extensionConfig?.['Display conditions']?.online ?? extension.online ?? true,
|
|
265
|
+
offline: extensionConfig?.['Display conditions']?.offline ?? extension.offline ?? false
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return extensions;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Gets the list of extensions assigned to a given slot
|
|
273
|
+
*
|
|
274
|
+
* @param slotName The slot to load the assigned extensions for
|
|
275
|
+
* @returns An array of extensions assigned to the named slot
|
|
276
|
+
*/ export function getAssignedExtensions(slotName) {
|
|
277
|
+
const internalState = extensionInternalStore.getState();
|
|
278
|
+
const { config: slotConfig } = getExtensionSlotConfig(slotName);
|
|
279
|
+
const extensionStoreState = extensionsConfigStore.getState();
|
|
280
|
+
const featureFlagState = featureFlagsStore.getState();
|
|
281
|
+
const sessionState = sessionStore.getState();
|
|
282
|
+
const isOnline = isOnlineFn();
|
|
283
|
+
const enabledFeatureFlags = Object.entries(featureFlagState.flags).filter(([, { enabled }])=>enabled).map(([name])=>name);
|
|
284
|
+
return getAssignedExtensionsFromSlotData(slotName, internalState, slotConfig, extensionStoreState, enabledFeatureFlags, isOnline, sessionState.session);
|
|
285
|
+
}
|
|
286
|
+
function calculateAssignedIds(config, attachedIds) {
|
|
287
|
+
const addedIds = config.add || [];
|
|
288
|
+
const removedIds = config.remove || [];
|
|
289
|
+
const idOrder = config.order || [];
|
|
290
|
+
const { extensions } = extensionInternalStore.getState();
|
|
291
|
+
return [
|
|
292
|
+
...attachedIds,
|
|
293
|
+
...addedIds
|
|
294
|
+
].filter((id)=>!removedIds.includes(id)).sort((idA, idB)=>{
|
|
295
|
+
const ai = getOrder(idA, idOrder, extensions[getExtensionNameFromId(idA)]?.order, attachedIds);
|
|
296
|
+
const bi = getOrder(idB, idOrder, extensions[getExtensionNameFromId(idB)]?.order, attachedIds);
|
|
297
|
+
if (bi === -1) {
|
|
298
|
+
return -1;
|
|
299
|
+
} else if (ai === -1) {
|
|
300
|
+
return 1;
|
|
301
|
+
} else {
|
|
302
|
+
return ai - bi;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Used by by extension slots at mount time.
|
|
308
|
+
*
|
|
309
|
+
* @param moduleName The name of the module that contains the extension slot
|
|
310
|
+
* @param slotName The extension slot name that is actually used
|
|
311
|
+
* @internal
|
|
312
|
+
*/ export const registerExtensionSlot = (moduleName, slotName)=>extensionInternalStore.setState((state)=>{
|
|
313
|
+
const existingModuleName = state.slots[slotName]?.moduleName;
|
|
314
|
+
if (existingModuleName && existingModuleName != moduleName) {
|
|
315
|
+
console.warn(`An extension slot with the name '${slotName}' already exists. Refusing to register the same slot name twice (in "registerExtensionSlot"). The existing one is from module ${existingModuleName}.`);
|
|
316
|
+
return state;
|
|
317
|
+
}
|
|
318
|
+
if (existingModuleName && existingModuleName == moduleName) {
|
|
319
|
+
// Re-rendering an existing slot
|
|
320
|
+
return state;
|
|
321
|
+
}
|
|
322
|
+
if (state.slots[slotName]) {
|
|
323
|
+
return {
|
|
324
|
+
...state,
|
|
325
|
+
slots: {
|
|
326
|
+
...state.slots,
|
|
327
|
+
[slotName]: {
|
|
328
|
+
...state.slots[slotName],
|
|
329
|
+
moduleName
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const slot = createNewExtensionSlotInfo(slotName, moduleName);
|
|
335
|
+
return {
|
|
336
|
+
...state,
|
|
337
|
+
slots: {
|
|
338
|
+
...state.slots,
|
|
339
|
+
[slotName]: slot
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
/**
|
|
344
|
+
* @internal
|
|
345
|
+
* Just for testing.
|
|
346
|
+
*/ export const reset = ()=>extensionStore.setState(()=>{
|
|
347
|
+
return {
|
|
348
|
+
slots: {},
|
|
349
|
+
extensions: {}
|
|
350
|
+
};
|
|
351
|
+
});
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { isOnline } from "@openmrs/esm-utils";
|
|
2
|
+
export function checkStatus(online = true, offline = false) {
|
|
3
|
+
return checkStatusFor(isOnline(), online, offline);
|
|
4
|
+
}
|
|
5
|
+
export function checkStatusFor(status, online = true, offline = false) {
|
|
6
|
+
return Boolean(status ? online : offline);
|
|
7
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './store';
|
|
2
|
+
export * from './extensions';
|
|
3
|
+
export * from './modals';
|
|
4
|
+
export * from './workspaces';
|
|
5
|
+
export * from './helpers';
|
|
6
|
+
export * from './render';
|
|
7
|
+
/** @deprecated Use `getExtensionStore`. The structure of this store has also changed. */
|
|
8
|
+
declare const internalStore: import("zustand").StoreApi<import("./store").ExtensionInternalStore>;
|
|
9
|
+
export { internalStore as extensionStore };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./store.js";
|
|
2
|
+
export * from "./extensions.js";
|
|
3
|
+
export * from "./modals.js";
|
|
4
|
+
export * from "./workspaces.js";
|
|
5
|
+
export * from "./helpers.js";
|
|
6
|
+
export * from "./render.js";
|
|
7
|
+
// Temporary compatibility hack
|
|
8
|
+
// What is now `extensionInternalStore` used to be exposed
|
|
9
|
+
// and used as `extensionStore`.
|
|
10
|
+
import { getExtensionInternalStore } from "./store.js";
|
|
11
|
+
/** @deprecated Use `getExtensionStore`. The structure of this store has also changed. */ const internalStore = getExtensionInternalStore();
|
|
12
|
+
export { internalStore as extensionStore };
|
package/dist/modals.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LifeCycles } from 'single-spa';
|
|
2
|
+
/** @internal */
|
|
3
|
+
export interface ModalRegistration {
|
|
4
|
+
name: string;
|
|
5
|
+
load(): Promise<{
|
|
6
|
+
default?: LifeCycles;
|
|
7
|
+
} & LifeCycles>;
|
|
8
|
+
moduleName: string;
|
|
9
|
+
}
|
|
10
|
+
/** @internal */
|
|
11
|
+
export declare function registerModal(modalRegistration: ModalRegistration): void;
|
|
12
|
+
/** @internal */
|
|
13
|
+
export declare function getModalRegistration(modalName: string): ModalRegistration | undefined;
|
package/dist/modals.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createGlobalStore } from "@openmrs/esm-state";
|
|
2
|
+
import { getExtensionRegistration } from "./index.js";
|
|
3
|
+
const modalRegistryStore = createGlobalStore('modalRegistry', {
|
|
4
|
+
modals: {}
|
|
5
|
+
});
|
|
6
|
+
/** @internal */ export function registerModal(modalRegistration) {
|
|
7
|
+
modalRegistryStore.setState((state)=>{
|
|
8
|
+
state.modals[modalRegistration.name] = modalRegistration;
|
|
9
|
+
return state;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/** @internal */ export function getModalRegistration(modalName) {
|
|
13
|
+
let modalRegistration = modalRegistryStore.getState().modals[modalName];
|
|
14
|
+
if (!modalRegistration) {
|
|
15
|
+
const extensionRegistration = getExtensionRegistration(modalName);
|
|
16
|
+
if (extensionRegistration) {
|
|
17
|
+
modalRegistration = {
|
|
18
|
+
name: modalName,
|
|
19
|
+
load: extensionRegistration.load,
|
|
20
|
+
moduleName: extensionRegistration.moduleName
|
|
21
|
+
};
|
|
22
|
+
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.`);
|
|
23
|
+
// Register it so the warning only appears once
|
|
24
|
+
registerModal(modalRegistration);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return modalRegistration;
|
|
28
|
+
}
|
package/dist/public.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { getExtensionNameFromId, registerExtension, attach, detach, detachAll, getAssignedExtensions, registerExtensionSlot, } from './extensions';
|
|
2
|
+
export { type CancelLoading, renderExtension } from './render';
|
|
3
|
+
export { type ExtensionMeta, type ExtensionRegistration, type ExtensionStore, type AssignedExtension, type ConnectedExtension, type ExtensionSlotState, getExtensionStore, } from './store';
|
|
4
|
+
export { type WorkspaceRegistration } from './workspaces';
|
package/dist/public.js
ADDED
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** @module @category Extension */
|
|
2
|
+
import { type Parcel, type ParcelConfig } from 'single-spa';
|
|
3
|
+
export interface CancelLoading {
|
|
4
|
+
(): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Mounts into a DOM node (representing an extension slot)
|
|
8
|
+
* a lazy-loaded component from *any* frontend module
|
|
9
|
+
* that registered an extension component for this slot.
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderExtension(domElement: HTMLElement, extensionSlotName: string, extensionSlotModuleName: string, extensionId: string, renderFunction?: (application: ParcelConfig) => ParcelConfig, additionalProps?: Record<string, any>): Promise<Parcel | null>;
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** @module @category Extension */ import { mountRootParcel } from "single-spa";
|
|
2
|
+
import { getExtensionNameFromId, getExtensionRegistration } from "./extensions.js";
|
|
3
|
+
import { checkStatus } from "./helpers.js";
|
|
4
|
+
import { updateInternalExtensionStore } from "./store.js";
|
|
5
|
+
let parcelCount = 0;
|
|
6
|
+
/**
|
|
7
|
+
* Mounts into a DOM node (representing an extension slot)
|
|
8
|
+
* a lazy-loaded component from *any* frontend module
|
|
9
|
+
* that registered an extension component for this slot.
|
|
10
|
+
*/ export async function renderExtension(domElement, extensionSlotName, extensionSlotModuleName, extensionId, renderFunction = (x)=>x, additionalProps = {}) {
|
|
11
|
+
const extensionName = getExtensionNameFromId(extensionId);
|
|
12
|
+
const extensionRegistration = getExtensionRegistration(extensionId);
|
|
13
|
+
let parcel = null;
|
|
14
|
+
if (domElement) {
|
|
15
|
+
if (!extensionRegistration) {
|
|
16
|
+
throw Error(`Couldn't find extension '${extensionName}' to attach to '${extensionSlotName}'`);
|
|
17
|
+
}
|
|
18
|
+
const { load, meta, moduleName, online, offline } = extensionRegistration;
|
|
19
|
+
if (checkStatus(online, offline)) {
|
|
20
|
+
updateInternalExtensionStore((state)=>{
|
|
21
|
+
const instance = {
|
|
22
|
+
domElement,
|
|
23
|
+
id: extensionId,
|
|
24
|
+
slotName: extensionSlotName,
|
|
25
|
+
slotModuleName: extensionSlotModuleName
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
...state,
|
|
29
|
+
extensions: {
|
|
30
|
+
...state.extensions,
|
|
31
|
+
[extensionName]: {
|
|
32
|
+
...state.extensions[extensionName],
|
|
33
|
+
instances: [
|
|
34
|
+
...state.extensions[extensionName].instances,
|
|
35
|
+
instance
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
const { default: result, ...lifecycle } = await load();
|
|
42
|
+
const id = parcelCount++;
|
|
43
|
+
parcel = mountRootParcel(renderFunction({
|
|
44
|
+
...result ?? lifecycle,
|
|
45
|
+
name: `${extensionSlotName}/${extensionName}-${id}`
|
|
46
|
+
}), {
|
|
47
|
+
...additionalProps,
|
|
48
|
+
_meta: meta,
|
|
49
|
+
_extensionContext: {
|
|
50
|
+
extensionId,
|
|
51
|
+
extensionSlotName,
|
|
52
|
+
extensionSlotModuleName,
|
|
53
|
+
extensionModuleName: moduleName
|
|
54
|
+
},
|
|
55
|
+
domElement
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
console.warn(`Tried to render ${extensionId} into ${extensionSlotName} but no DOM element was available.`);
|
|
60
|
+
}
|
|
61
|
+
return parcel;
|
|
62
|
+
}
|