@egen-civitas/esm-extensions 1.0.0
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/README.md +4 -0
- package/dist/extensions.d.ts +92 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +394 -0
- package/dist/helpers.d.ts +3 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +7 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/left-nav.d.ts +38 -0
- package/dist/left-nav.d.ts.map +1 -0
- package/dist/left-nav.js +28 -0
- package/dist/modals.d.ts +12 -0
- package/dist/modals.d.ts.map +1 -0
- package/dist/modals.js +28 -0
- package/dist/public.d.ts +7 -0
- package/dist/public.d.ts.map +1 -0
- package/dist/public.js +4 -0
- package/dist/render.d.ts +12 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +62 -0
- package/dist/store.d.ts +97 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +54 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/workspaces.d.ts +61 -0
- package/dist/workspaces.d.ts.map +1 -0
- package/dist/workspaces.js +122 -0
- package/dist/workspaces2.d.ts +61 -0
- package/dist/workspaces2.d.ts.map +1 -0
- package/dist/workspaces2.js +116 -0
- package/mock-jest.ts +31 -0
- package/mock.ts +33 -0
- package/package.json +96 -0
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** @module @category Extension */
|
|
2
|
+
import { type AssignedExtension, type ExtensionInternalStore, type ExtensionRegistration, type ExtensionSlotCustomState } from './store.js';
|
|
3
|
+
/**
|
|
4
|
+
* Given an extension ID, which is a string uniquely identifying
|
|
5
|
+
* an instance of an extension within an extension slot, this
|
|
6
|
+
* returns the extension name.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```js
|
|
10
|
+
* getExtensionNameFromId("foo#bar")
|
|
11
|
+
* --> "foo"
|
|
12
|
+
* getExtensionNameFromId("baz")
|
|
13
|
+
* --> "baz"
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function getExtensionNameFromId(extensionId: string): string;
|
|
17
|
+
export declare function getExtensionRegistrationFrom(state: ExtensionInternalStore, extensionId: string): ExtensionRegistration | undefined;
|
|
18
|
+
export declare function getExtensionRegistration(extensionId: string): ExtensionRegistration | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Extensions must be registered in order to be rendered.
|
|
21
|
+
* This is handled by the app shell, when extensions are provided
|
|
22
|
+
* via the `routes.json` file and registered through `registerApp()`.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export declare const registerExtension: (extensionRegistration: ExtensionRegistration) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Attach an extension to an extension slot.
|
|
28
|
+
*
|
|
29
|
+
* This will cause the extension to be rendered into the specified
|
|
30
|
+
* extension slot, unless it is removed by configuration. Using
|
|
31
|
+
* `attach` is an alternative to specifying the `slot` or `slots`
|
|
32
|
+
* in the extension declaration.
|
|
33
|
+
*
|
|
34
|
+
* It is particularly useful when creating a slot into which
|
|
35
|
+
* you want to render an existing extension. This enables you
|
|
36
|
+
* to do so without modifying the extension's declaration, which
|
|
37
|
+
* may be impractical or inappropriate, for example if you are
|
|
38
|
+
* writing a module for a specific implementation.
|
|
39
|
+
*
|
|
40
|
+
* @param slotName a name uniquely identifying the slot
|
|
41
|
+
* @param extensionId an extension name, with an optional #-suffix
|
|
42
|
+
* to distinguish it from other instances of the same extension
|
|
43
|
+
* attached to the same slot.
|
|
44
|
+
*/
|
|
45
|
+
export declare function attach(slotName: string, extensionId: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Detaches an extension from an extension slot.
|
|
48
|
+
*
|
|
49
|
+
* @param extensionSlotName The name of the extension slot to detach from.
|
|
50
|
+
* @param extensionId The ID of the extension to detach.
|
|
51
|
+
*
|
|
52
|
+
* @deprecated Avoid using this. Extension attachments should be considered declarative.
|
|
53
|
+
*/
|
|
54
|
+
export declare function detach(extensionSlotName: string, extensionId: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Detaches all extensions from an extension slot.
|
|
57
|
+
*
|
|
58
|
+
* @param extensionSlotName The name of the extension slot to clear.
|
|
59
|
+
*
|
|
60
|
+
* @deprecated Avoid using this. Extension attachments should be considered declarative.
|
|
61
|
+
*/
|
|
62
|
+
export declare function detachAll(extensionSlotName: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Gets the list of extensions assigned to a given slot
|
|
65
|
+
*
|
|
66
|
+
* @param slotName The slot to load the assigned extensions for
|
|
67
|
+
* @returns An array of extensions assigned to the named slot
|
|
68
|
+
*/
|
|
69
|
+
export declare function getAssignedExtensions(slotName: string): Array<AssignedExtension>;
|
|
70
|
+
/**
|
|
71
|
+
* Used by by extension slots at mount time.
|
|
72
|
+
*
|
|
73
|
+
* @param moduleName The name of the module that contains the extension slot
|
|
74
|
+
* @param slotName The extension slot name that is actually used
|
|
75
|
+
* @param state Optional custom state for the slot, which will be stored in the extension store.
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
export declare const registerExtensionSlot: (moduleName: string, slotName: string, state?: ExtensionSlotCustomState) => void;
|
|
79
|
+
/**
|
|
80
|
+
* Used by extension slots to update the copy of the state for the extension slot
|
|
81
|
+
*
|
|
82
|
+
* @param slotName The name of the slot with state to update
|
|
83
|
+
* @param state A copy of the new state
|
|
84
|
+
* @param partial Whether this should be applied as a partial
|
|
85
|
+
*/
|
|
86
|
+
export declare function updateExtensionSlotState(slotName: string, state: ExtensionSlotCustomState, partial?: boolean): void;
|
|
87
|
+
/**
|
|
88
|
+
* @internal
|
|
89
|
+
* Just for testing.
|
|
90
|
+
*/
|
|
91
|
+
export declare const reset: () => void;
|
|
92
|
+
//# sourceMappingURL=extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../src/extensions.ts"],"names":[],"mappings":"AAAA,kCAAkC;AA4BlC,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAM9B,MAAM,YAAY,CAAC;AAsHpB;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,UAGzD;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,sBAAsB,EAC7B,WAAW,EAAE,MAAM,GAClB,qBAAqB,GAAG,SAAS,CAGnC;AAED,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAG/F;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,CAAC,qBAAqB,EAAE,qBAAqB,KAAK,IAO9E,CAAC;AAEL;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,QA4B3D;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,iBAAiB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,QAmBpE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,iBAAiB,EAAE,MAAM,QAmBlD;AAqHD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC,iBAAiB,CAAC,CAoBhF;AAwBD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,wBAAwB,KAAK,IA2C5G,CAAC;AAEL;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,wBAAwB,EAAE,OAAO,GAAE,OAAe,QAcnH;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,MAAM,IAMtB,CAAC"}
|
|
@@ -0,0 +1,394 @@
|
|
|
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 "@egen-civitas/esm-api";
|
|
9
|
+
import { getExtensionConfigFromExtensionSlotStore, getExtensionConfigFromStore, getExtensionSlotConfig, getExtensionSlotConfigFromStore, getExtensionSlotsConfigStore, getExtensionsConfigStore } from "@egen-civitas/esm-config";
|
|
10
|
+
import { evaluateAsBoolean } from "@egen-civitas/esm-expression-evaluator";
|
|
11
|
+
import { featureFlagsStore } from "@egen-civitas/esm-feature-flags";
|
|
12
|
+
import { subscribeConnectivityChanged } from "@egen-civitas/esm-globals";
|
|
13
|
+
import { isOnline as isOnlineFn } from "@egen-civitas/esm-utils";
|
|
14
|
+
import { isEqual, merge } from "lodash-es";
|
|
15
|
+
import { checkStatusFor } from "./helpers.js";
|
|
16
|
+
import { getExtensionInternalStore, 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, state) {
|
|
61
|
+
return {
|
|
62
|
+
moduleName,
|
|
63
|
+
name: slotName,
|
|
64
|
+
attachedIds: [],
|
|
65
|
+
config: null,
|
|
66
|
+
state
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Given an extension ID, which is a string uniquely identifying
|
|
71
|
+
* an instance of an extension within an extension slot, this
|
|
72
|
+
* returns the extension name.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```js
|
|
76
|
+
* getExtensionNameFromId("foo#bar")
|
|
77
|
+
* --> "foo"
|
|
78
|
+
* getExtensionNameFromId("baz")
|
|
79
|
+
* --> "baz"
|
|
80
|
+
* ```
|
|
81
|
+
*/ export function getExtensionNameFromId(extensionId) {
|
|
82
|
+
const [extensionName] = extensionId.split('#');
|
|
83
|
+
return extensionName;
|
|
84
|
+
}
|
|
85
|
+
export function getExtensionRegistrationFrom(state, extensionId) {
|
|
86
|
+
const name = getExtensionNameFromId(extensionId);
|
|
87
|
+
return state.extensions[name];
|
|
88
|
+
}
|
|
89
|
+
export function getExtensionRegistration(extensionId) {
|
|
90
|
+
const state = extensionInternalStore.getState();
|
|
91
|
+
return getExtensionRegistrationFrom(state, extensionId);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Extensions must be registered in order to be rendered.
|
|
95
|
+
* This is handled by the app shell, when extensions are provided
|
|
96
|
+
* via the `routes.json` file and registered through `registerApp()`.
|
|
97
|
+
* @internal
|
|
98
|
+
*/ export const registerExtension = (extensionRegistration)=>extensionInternalStore.setState((state)=>{
|
|
99
|
+
state.extensions[extensionRegistration.name] = {
|
|
100
|
+
...extensionRegistration,
|
|
101
|
+
instances: []
|
|
102
|
+
};
|
|
103
|
+
return state;
|
|
104
|
+
});
|
|
105
|
+
/**
|
|
106
|
+
* Attach an extension to an extension slot.
|
|
107
|
+
*
|
|
108
|
+
* This will cause the extension to be rendered into the specified
|
|
109
|
+
* extension slot, unless it is removed by configuration. Using
|
|
110
|
+
* `attach` is an alternative to specifying the `slot` or `slots`
|
|
111
|
+
* in the extension declaration.
|
|
112
|
+
*
|
|
113
|
+
* It is particularly useful when creating a slot into which
|
|
114
|
+
* you want to render an existing extension. This enables you
|
|
115
|
+
* to do so without modifying the extension's declaration, which
|
|
116
|
+
* may be impractical or inappropriate, for example if you are
|
|
117
|
+
* writing a module for a specific implementation.
|
|
118
|
+
*
|
|
119
|
+
* @param slotName a name uniquely identifying the slot
|
|
120
|
+
* @param extensionId an extension name, with an optional #-suffix
|
|
121
|
+
* to distinguish it from other instances of the same extension
|
|
122
|
+
* attached to the same slot.
|
|
123
|
+
*/ export function attach(slotName, extensionId) {
|
|
124
|
+
updateInternalExtensionStore((state)=>{
|
|
125
|
+
const existingSlot = state.slots[slotName];
|
|
126
|
+
if (!existingSlot) {
|
|
127
|
+
return {
|
|
128
|
+
...state,
|
|
129
|
+
slots: {
|
|
130
|
+
...state.slots,
|
|
131
|
+
[slotName]: {
|
|
132
|
+
...createNewExtensionSlotInfo(slotName),
|
|
133
|
+
attachedIds: [
|
|
134
|
+
extensionId
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
} else {
|
|
140
|
+
return {
|
|
141
|
+
...state,
|
|
142
|
+
slots: {
|
|
143
|
+
...state.slots,
|
|
144
|
+
[slotName]: {
|
|
145
|
+
...existingSlot,
|
|
146
|
+
attachedIds: [
|
|
147
|
+
...existingSlot.attachedIds,
|
|
148
|
+
extensionId
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Detaches an extension from an extension slot.
|
|
158
|
+
*
|
|
159
|
+
* @param extensionSlotName The name of the extension slot to detach from.
|
|
160
|
+
* @param extensionId The ID of the extension to detach.
|
|
161
|
+
*
|
|
162
|
+
* @deprecated Avoid using this. Extension attachments should be considered declarative.
|
|
163
|
+
*/ export function detach(extensionSlotName, extensionId) {
|
|
164
|
+
updateInternalExtensionStore((state)=>{
|
|
165
|
+
const existingSlot = state.slots[extensionSlotName];
|
|
166
|
+
if (existingSlot && existingSlot.attachedIds.includes(extensionId)) {
|
|
167
|
+
return {
|
|
168
|
+
...state,
|
|
169
|
+
slots: {
|
|
170
|
+
...state.slots,
|
|
171
|
+
[extensionSlotName]: {
|
|
172
|
+
...existingSlot,
|
|
173
|
+
attachedIds: existingSlot.attachedIds.filter((id)=>id !== extensionId)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
} else {
|
|
178
|
+
return state;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Detaches all extensions from an extension slot.
|
|
184
|
+
*
|
|
185
|
+
* @param extensionSlotName The name of the extension slot to clear.
|
|
186
|
+
*
|
|
187
|
+
* @deprecated Avoid using this. Extension attachments should be considered declarative.
|
|
188
|
+
*/ export function detachAll(extensionSlotName) {
|
|
189
|
+
updateInternalExtensionStore((state)=>{
|
|
190
|
+
const existingSlot = state.slots[extensionSlotName];
|
|
191
|
+
if (existingSlot) {
|
|
192
|
+
return {
|
|
193
|
+
...state,
|
|
194
|
+
slots: {
|
|
195
|
+
...state.slots,
|
|
196
|
+
[extensionSlotName]: {
|
|
197
|
+
...existingSlot,
|
|
198
|
+
attachedIds: []
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
} else {
|
|
203
|
+
return state;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get an order index for the extension. This will
|
|
209
|
+
* come from either its configured order, its registered order
|
|
210
|
+
* parameter, or the order in which it happened to be attached.
|
|
211
|
+
*/ function getOrder(extensionId, configuredOrder, registeredOrderIndex, attachedOrder) {
|
|
212
|
+
const configuredIndex = configuredOrder.indexOf(extensionId);
|
|
213
|
+
if (configuredIndex !== -1) {
|
|
214
|
+
return configuredIndex;
|
|
215
|
+
} else if (registeredOrderIndex !== undefined) {
|
|
216
|
+
// extensions that don't have a configured order should appear after those that do
|
|
217
|
+
return 1000 + registeredOrderIndex;
|
|
218
|
+
} else {
|
|
219
|
+
const assignedIndex = attachedOrder.indexOf(extensionId);
|
|
220
|
+
if (assignedIndex !== -1) {
|
|
221
|
+
// extensions that have neither a configured nor registered order should appear
|
|
222
|
+
// after all others
|
|
223
|
+
return 2000 + assignedIndex;
|
|
224
|
+
} else {
|
|
225
|
+
return -1;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function getAssignedExtensionsFromSlotData(slotName, internalState, config, extensionConfigStoreState, enabledFeatureFlags, isOnline, session) {
|
|
230
|
+
const attachedIds = internalState.slots[slotName].attachedIds;
|
|
231
|
+
const assignedIds = calculateAssignedIds(config, attachedIds);
|
|
232
|
+
const extensions = [];
|
|
233
|
+
// Create context once for all extensions in this slot
|
|
234
|
+
const slotState = internalState.slots[slotName]?.state;
|
|
235
|
+
const expressionContext = slotState && typeof slotState === 'object' ? {
|
|
236
|
+
session,
|
|
237
|
+
...slotState
|
|
238
|
+
} : {
|
|
239
|
+
session
|
|
240
|
+
};
|
|
241
|
+
for (let id of assignedIds){
|
|
242
|
+
const { config: rawExtensionConfig } = getExtensionConfigFromStore(extensionConfigStoreState, slotName, id);
|
|
243
|
+
const rawExtensionSlotExtensionConfig = getExtensionConfigFromExtensionSlotStore(config, slotName, id);
|
|
244
|
+
const extensionConfig = merge(rawExtensionConfig, rawExtensionSlotExtensionConfig);
|
|
245
|
+
const name = getExtensionNameFromId(id);
|
|
246
|
+
const extension = internalState.extensions[name];
|
|
247
|
+
// if the extension has not been registered yet, do not include it
|
|
248
|
+
if (extension) {
|
|
249
|
+
const requiredPrivileges = extensionConfig?.['Display conditions']?.privileges ?? extension.privileges ?? [];
|
|
250
|
+
if (requiredPrivileges && (typeof requiredPrivileges === 'string' || Array.isArray(requiredPrivileges) && requiredPrivileges.length > 0)) {
|
|
251
|
+
if (!session?.user) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (!userHasAccess(requiredPrivileges, session.user)) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const displayConditionExpression = extensionConfig?.['Display conditions']?.expression || extension.displayExpression;
|
|
259
|
+
if (displayConditionExpression !== undefined && typeof displayConditionExpression === 'string' && displayConditionExpression.trim().length > 0) {
|
|
260
|
+
try {
|
|
261
|
+
if (!evaluateAsBoolean(displayConditionExpression, expressionContext)) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
} catch (e) {
|
|
265
|
+
console.error(`Error while evaluating expression '${displayConditionExpression}' for extension ${name} in slot ${slotName}`, e);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (extension.featureFlag && !enabledFeatureFlags.includes(extension.featureFlag)) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (window.offlineEnabled && !checkStatusFor(isOnline, extension.online, extension.offline)) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
extensions.push({
|
|
276
|
+
id,
|
|
277
|
+
name,
|
|
278
|
+
moduleName: extension.moduleName,
|
|
279
|
+
config: extensionConfig,
|
|
280
|
+
featureFlag: extension.featureFlag,
|
|
281
|
+
meta: extension.meta,
|
|
282
|
+
online: extensionConfig?.['Display conditions']?.online ?? extension.online ?? true,
|
|
283
|
+
offline: extensionConfig?.['Display conditions']?.offline ?? extension.offline ?? false
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return extensions;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Gets the list of extensions assigned to a given slot
|
|
291
|
+
*
|
|
292
|
+
* @param slotName The slot to load the assigned extensions for
|
|
293
|
+
* @returns An array of extensions assigned to the named slot
|
|
294
|
+
*/ export function getAssignedExtensions(slotName) {
|
|
295
|
+
const internalState = extensionInternalStore.getState();
|
|
296
|
+
const { config: slotConfig } = getExtensionSlotConfig(slotName);
|
|
297
|
+
const extensionStoreState = extensionsConfigStore.getState();
|
|
298
|
+
const featureFlagState = featureFlagsStore.getState();
|
|
299
|
+
const sessionState = sessionStore.getState();
|
|
300
|
+
const isOnline = isOnlineFn();
|
|
301
|
+
const enabledFeatureFlags = Object.entries(featureFlagState.flags).filter(([, { enabled }])=>enabled).map(([name])=>name);
|
|
302
|
+
return getAssignedExtensionsFromSlotData(slotName, internalState, slotConfig, extensionStoreState, enabledFeatureFlags, isOnline, sessionState.session);
|
|
303
|
+
}
|
|
304
|
+
function calculateAssignedIds(config, attachedIds) {
|
|
305
|
+
const addedIds = config.add || [];
|
|
306
|
+
const removedIds = config.remove || [];
|
|
307
|
+
const idOrder = config.order || [];
|
|
308
|
+
const { extensions } = extensionInternalStore.getState();
|
|
309
|
+
return [
|
|
310
|
+
...attachedIds,
|
|
311
|
+
...addedIds
|
|
312
|
+
].filter((id)=>!removedIds.includes(id)).sort((idA, idB)=>{
|
|
313
|
+
const ai = getOrder(idA, idOrder, extensions[getExtensionNameFromId(idA)]?.order, attachedIds);
|
|
314
|
+
const bi = getOrder(idB, idOrder, extensions[getExtensionNameFromId(idB)]?.order, attachedIds);
|
|
315
|
+
if (bi === -1) {
|
|
316
|
+
return -1;
|
|
317
|
+
} else if (ai === -1) {
|
|
318
|
+
return 1;
|
|
319
|
+
} else {
|
|
320
|
+
return ai - bi;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Used by by extension slots at mount time.
|
|
326
|
+
*
|
|
327
|
+
* @param moduleName The name of the module that contains the extension slot
|
|
328
|
+
* @param slotName The extension slot name that is actually used
|
|
329
|
+
* @param state Optional custom state for the slot, which will be stored in the extension store.
|
|
330
|
+
* @internal
|
|
331
|
+
*/ export const registerExtensionSlot = (moduleName, slotName, state)=>extensionInternalStore.setState((currentState)=>{
|
|
332
|
+
const existingModuleName = currentState.slots[slotName]?.moduleName;
|
|
333
|
+
if (existingModuleName && existingModuleName != moduleName) {
|
|
334
|
+
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}.`);
|
|
335
|
+
return currentState;
|
|
336
|
+
}
|
|
337
|
+
if (existingModuleName && existingModuleName == moduleName) {
|
|
338
|
+
// Re-rendering an existing slot
|
|
339
|
+
return currentState;
|
|
340
|
+
}
|
|
341
|
+
if (currentState.slots[slotName]) {
|
|
342
|
+
return {
|
|
343
|
+
...currentState,
|
|
344
|
+
slots: {
|
|
345
|
+
...currentState.slots,
|
|
346
|
+
[slotName]: {
|
|
347
|
+
...currentState.slots[slotName],
|
|
348
|
+
moduleName,
|
|
349
|
+
state
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
const slot = createNewExtensionSlotInfo(slotName, moduleName, state);
|
|
355
|
+
return {
|
|
356
|
+
...currentState,
|
|
357
|
+
slots: {
|
|
358
|
+
...currentState.slots,
|
|
359
|
+
[slotName]: {
|
|
360
|
+
...slot
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
});
|
|
365
|
+
/**
|
|
366
|
+
* Used by extension slots to update the copy of the state for the extension slot
|
|
367
|
+
*
|
|
368
|
+
* @param slotName The name of the slot with state to update
|
|
369
|
+
* @param state A copy of the new state
|
|
370
|
+
* @param partial Whether this should be applied as a partial
|
|
371
|
+
*/ export function updateExtensionSlotState(slotName, state, partial = false) {
|
|
372
|
+
extensionInternalStore.setState((currentState)=>{
|
|
373
|
+
const newState = partial ? merge(currentState.slots[slotName].state, state) : state;
|
|
374
|
+
return {
|
|
375
|
+
...currentState,
|
|
376
|
+
slots: {
|
|
377
|
+
...currentState.slots,
|
|
378
|
+
[slotName]: {
|
|
379
|
+
...currentState.slots[slotName],
|
|
380
|
+
state: newState
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* @internal
|
|
388
|
+
* Just for testing.
|
|
389
|
+
*/ export const reset = ()=>extensionStore.setState(()=>{
|
|
390
|
+
return {
|
|
391
|
+
slots: {},
|
|
392
|
+
extensions: {}
|
|
393
|
+
};
|
|
394
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,CAAC,MAAM,GAAE,OAAO,GAAG,MAAa,EAAE,OAAO,GAAE,OAAO,GAAG,MAAc,WAE7F;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,GAAE,OAAO,GAAG,MAAa,EAAE,OAAO,GAAE,OAAO,GAAG,MAAc,WAEjH"}
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { isOnline } from "@egen-civitas/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,10 @@
|
|
|
1
|
+
export * from './extensions.js';
|
|
2
|
+
export * from './helpers.js';
|
|
3
|
+
export * from './left-nav.js';
|
|
4
|
+
export * from './modals.js';
|
|
5
|
+
export * from './workspaces.js';
|
|
6
|
+
export * from './workspaces2.js';
|
|
7
|
+
export * from './render.js';
|
|
8
|
+
export * from './store.js';
|
|
9
|
+
export * from './types.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./extensions.js";
|
|
2
|
+
export * from "./helpers.js";
|
|
3
|
+
export * from "./left-nav.js";
|
|
4
|
+
export * from "./modals.js";
|
|
5
|
+
export * from "./workspaces.js";
|
|
6
|
+
export * from "./workspaces2.js";
|
|
7
|
+
export * from "./render.js";
|
|
8
|
+
export * from "./store.js";
|
|
9
|
+
export * from "./types.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ComponentConfig } from './types.js';
|
|
2
|
+
import { type ExtensionSlotState } from './store.js';
|
|
3
|
+
type LeftNavMode = 'normal' | 'collapsed' | 'hidden';
|
|
4
|
+
export interface LeftNavStore {
|
|
5
|
+
slotName: string | null;
|
|
6
|
+
basePath: string;
|
|
7
|
+
mode: LeftNavMode;
|
|
8
|
+
componentContext?: ComponentConfig;
|
|
9
|
+
state?: ExtensionSlotState;
|
|
10
|
+
}
|
|
11
|
+
/** @internal */
|
|
12
|
+
export declare const leftNavStore: import("zustand").StoreApi<LeftNavStore>;
|
|
13
|
+
export interface SetLeftNavParams {
|
|
14
|
+
name: string;
|
|
15
|
+
basePath: string;
|
|
16
|
+
/**
|
|
17
|
+
* In normal mode, the left nav is shown in desktop mode, and collapse into hamburger menu button in tablet mode
|
|
18
|
+
* In collapsed mode, the left nav is always collapsed, regardless of desktop / tablet mode.
|
|
19
|
+
* In hidden mode, the left nav is not shown at all.
|
|
20
|
+
*/
|
|
21
|
+
mode?: LeftNavMode;
|
|
22
|
+
componentContext?: ComponentConfig;
|
|
23
|
+
state?: ExtensionSlotState;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sets the current left nav context. Must be paired with {@link unsetLeftNav}.
|
|
27
|
+
*
|
|
28
|
+
* @deprecated Please use {@link useLeftNav} instead. This function will be made internal in a future release.
|
|
29
|
+
*/
|
|
30
|
+
export declare function setLeftNav({ name, basePath, mode, componentContext, state }: SetLeftNavParams): void;
|
|
31
|
+
/**
|
|
32
|
+
* Unsets the left nav context if the current context is for the supplied name.
|
|
33
|
+
*
|
|
34
|
+
* @deprecated Please use {@link useLeftNav} instead. This function will be made internal in a future release.
|
|
35
|
+
*/
|
|
36
|
+
export declare function unsetLeftNav(name: string): void;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=left-nav.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"left-nav.d.ts","sourceRoot":"","sources":["../src/left-nav.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,KAAK,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;AACrD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;IAClB,gBAAgB,CAAC,EAAE,eAAe,CAAC;IACnC,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAED,gBAAgB;AAChB,eAAO,MAAM,YAAY,0CAIvB,CAAC;AAEH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,gBAAgB,CAAC,EAAE,eAAe,CAAC;IACnC,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,EAAE,gBAAgB,QAE7F;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,QAIxC"}
|
package/dist/left-nav.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createGlobalStore } from "@egen-civitas/esm-state";
|
|
2
|
+
/** @internal */ export const leftNavStore = createGlobalStore('left-nav', {
|
|
3
|
+
slotName: null,
|
|
4
|
+
basePath: window.spaBase,
|
|
5
|
+
mode: 'normal'
|
|
6
|
+
});
|
|
7
|
+
/**
|
|
8
|
+
* Sets the current left nav context. Must be paired with {@link unsetLeftNav}.
|
|
9
|
+
*
|
|
10
|
+
* @deprecated Please use {@link useLeftNav} instead. This function will be made internal in a future release.
|
|
11
|
+
*/ export function setLeftNav({ name, basePath, mode, componentContext, state }) {
|
|
12
|
+
leftNavStore.setState({
|
|
13
|
+
slotName: name,
|
|
14
|
+
basePath,
|
|
15
|
+
mode: mode ?? 'normal',
|
|
16
|
+
componentContext,
|
|
17
|
+
state
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Unsets the left nav context if the current context is for the supplied name.
|
|
22
|
+
*
|
|
23
|
+
* @deprecated Please use {@link useLeftNav} instead. This function will be made internal in a future release.
|
|
24
|
+
*/ export function unsetLeftNav(name) {
|
|
25
|
+
if (leftNavStore.getState().slotName === name) {
|
|
26
|
+
leftNavStore.setState(leftNavStore.getInitialState(), true);
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/modals.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type LifeCycles } from 'single-spa';
|
|
2
|
+
/** @internal */
|
|
3
|
+
export interface ModalRegistration {
|
|
4
|
+
name: string;
|
|
5
|
+
load(): Promise<LifeCycles>;
|
|
6
|
+
moduleName: string;
|
|
7
|
+
}
|
|
8
|
+
/** @internal */
|
|
9
|
+
export declare function registerModal(modalRegistration: ModalRegistration): void;
|
|
10
|
+
/** @internal */
|
|
11
|
+
export declare function getModalRegistration(modalName: string): ModalRegistration | undefined;
|
|
12
|
+
//# sourceMappingURL=modals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modals.d.ts","sourceRoot":"","sources":["../src/modals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,gBAAgB;AAChB,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAWD,gBAAgB;AAChB,wBAAgB,aAAa,CAAC,iBAAiB,EAAE,iBAAiB,QAKjE;AAED,gBAAgB;AAChB,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAkBrF"}
|
package/dist/modals.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createGlobalStore } from "@egen-civitas/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,7 @@
|
|
|
1
|
+
export { getExtensionNameFromId, registerExtension, attach, detach, detachAll, getAssignedExtensions, registerExtensionSlot, } from './extensions.js';
|
|
2
|
+
export { type LeftNavStore, setLeftNav, unsetLeftNav, type SetLeftNavParams } from './left-nav.js';
|
|
3
|
+
export { type CancelLoading, renderExtension } from './render.js';
|
|
4
|
+
export { type ExtensionMeta, type ExtensionRegistration, type ExtensionStore, type AssignedExtension, type ConnectedExtension, type ExtensionSlotState, getExtensionStore, } from './store.js';
|
|
5
|
+
export { type WorkspaceRegistration } from './workspaces.js';
|
|
6
|
+
export { type ExtensionData, type ComponentConfig } from './types.js';
|
|
7
|
+
//# sourceMappingURL=public.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../src/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,SAAS,EACT,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,KAAK,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC"}
|