@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/dist/public.js ADDED
@@ -0,0 +1,4 @@
1
+ export { getExtensionNameFromId, registerExtension, attach, detach, detachAll, getAssignedExtensions, registerExtensionSlot } from "./extensions.js";
2
+ export { setLeftNav, unsetLeftNav } from "./left-nav.js";
3
+ export { renderExtension } from "./render.js";
4
+ export { getExtensionStore } from "./store.js";
@@ -0,0 +1,12 @@
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>;
12
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,OAAO,EAAmB,KAAK,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAK7E,MAAM,WAAW,aAAa;IAC5B,IAAI,IAAI,CAAC;CACV;AAID;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,WAAW,EACvB,iBAAiB,EAAE,MAAM,EACzB,uBAAuB,EAAE,MAAM,EAC/B,WAAW,EAAE,MAAM,EACnB,cAAc,GAAE,CAAC,WAAW,EAAE,YAAY,KAAK,YAAuB,EACtE,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GACxC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyDxB"}
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 { meta, moduleName, online, offline, load } = 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 lifecycle = await load();
42
+ const id = parcelCount++;
43
+ parcel = mountRootParcel(renderFunction({
44
+ ...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
+ }
@@ -0,0 +1,97 @@
1
+ import type { ConfigObject, ExtensionSlotConfig } from '@egen-civitas/esm-config';
2
+ import { type LifeCycles } from 'single-spa';
3
+ export interface ExtensionMeta {
4
+ [_: string]: any;
5
+ }
6
+ export interface ExtensionRegistration {
7
+ readonly name: string;
8
+ load(): Promise<LifeCycles>;
9
+ readonly moduleName: string;
10
+ readonly meta: Readonly<ExtensionMeta>;
11
+ readonly order?: number;
12
+ readonly online?: boolean;
13
+ readonly offline?: boolean;
14
+ readonly privileges?: string | Array<string>;
15
+ readonly featureFlag?: string;
16
+ readonly displayExpression?: string;
17
+ }
18
+ export interface ExtensionInfo extends ExtensionRegistration {
19
+ /**
20
+ * The instances where the extension has been rendered using `renderExtension`.
21
+ */
22
+ instances: Array<ExtensionInstance>;
23
+ }
24
+ export interface ExtensionInstance {
25
+ id: string;
26
+ slotName: string;
27
+ slotModuleName: string;
28
+ }
29
+ export interface ExtensionInternalStore {
30
+ /** Slots indexed by name */
31
+ slots: Record<string, ExtensionSlotInfo>;
32
+ /** Extensions indexed by name */
33
+ extensions: Record<string, ExtensionInfo>;
34
+ }
35
+ export type ExtensionSlotCustomState = Record<string | symbol | number, unknown> | undefined | null;
36
+ export interface ExtensionSlotInfo {
37
+ /**
38
+ * The module in which the extension slot exists. Undefined if the slot
39
+ * hasn't been registered yet (but it has been attached or assigned to
40
+ * an extension.
41
+ */
42
+ moduleName?: string;
43
+ /** The name under which the extension slot has been registered. */
44
+ name: string;
45
+ /**
46
+ * The set of extension IDs which have been attached to this slot using `attach`.
47
+ * However, not all of these extension IDs should be rendered.
48
+ * `assignedIds` is the set defining those.
49
+ */
50
+ attachedIds: Array<string>;
51
+ /** The configuration provided for this slot. `null` if not yet loaded. */
52
+ config: Omit<ExtensionSlotConfig, 'configuration'> | null;
53
+ state?: ExtensionSlotCustomState;
54
+ }
55
+ export interface ExtensionStore {
56
+ slots: Record<string, ExtensionSlotState>;
57
+ }
58
+ export interface ExtensionSlotState {
59
+ moduleName?: string;
60
+ assignedExtensions: Array<AssignedExtension>;
61
+ state?: ExtensionSlotCustomState;
62
+ }
63
+ export interface AssignedExtension {
64
+ readonly id: string;
65
+ readonly name: string;
66
+ readonly moduleName: string;
67
+ readonly meta: Readonly<ExtensionMeta>;
68
+ /** The extension's config. Note that this will be `null` until the slot is mounted. */
69
+ readonly config: Readonly<ConfigObject> | null;
70
+ readonly online?: boolean | object;
71
+ readonly offline?: boolean | object;
72
+ readonly featureFlag?: string;
73
+ }
74
+ /** @deprecated replaced with AssignedExtension */
75
+ export interface ConnectedExtension {
76
+ readonly id: string;
77
+ readonly name: string;
78
+ readonly moduleName: string;
79
+ readonly meta: Readonly<ExtensionMeta>;
80
+ /** The extension's config. Note that this will be `null` until the slot is mounted. */
81
+ readonly config: Readonly<ConfigObject> | null;
82
+ }
83
+ /**
84
+ * This gets the extension system's internal store. It is subject
85
+ * to change radically and without warning. It should not be used
86
+ * outside esm-core.
87
+ * @internal
88
+ */
89
+ export declare const getExtensionInternalStore: () => import("zustand").StoreApi<ExtensionInternalStore>;
90
+ /** @internal */
91
+ export declare function updateInternalExtensionStore(updater: (state: ExtensionInternalStore) => ExtensionInternalStore): void;
92
+ /**
93
+ * This returns a store that modules can use to get information about the
94
+ * state of the extension system.
95
+ */
96
+ export declare const getExtensionStore: () => import("zustand").StoreApi<ExtensionStore>;
97
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/dist/store.js ADDED
@@ -0,0 +1,54 @@
1
+ /** @module @category Extension */ import { isEqual } from "lodash-es";
2
+ import { configExtensionStore } from "@egen-civitas/esm-config";
3
+ import { createGlobalStore, getGlobalStore } from "@egen-civitas/esm-state";
4
+ const extensionInternalStore = createGlobalStore('extensionsInternal', {
5
+ slots: {},
6
+ extensions: {}
7
+ });
8
+ /**
9
+ * This gets the extension system's internal store. It is subject
10
+ * to change radically and without warning. It should not be used
11
+ * outside esm-core.
12
+ * @internal
13
+ */ export const getExtensionInternalStore = ()=>getGlobalStore('extensionsInternal', {
14
+ slots: {},
15
+ extensions: {}
16
+ });
17
+ /** @internal */ export function updateInternalExtensionStore(updater) {
18
+ // This is a function that updates the internal extension store.
19
+ const state = extensionInternalStore.getState();
20
+ const newState = updater(state);
21
+ if (newState !== state) {
22
+ extensionInternalStore.setState(newState);
23
+ }
24
+ }
25
+ /**
26
+ * This returns a store that modules can use to get information about the
27
+ * state of the extension system.
28
+ */ export const getExtensionStore = ()=>getGlobalStore('extensions', {
29
+ slots: {}
30
+ });
31
+ /**
32
+ * esm-config maintains its own store of the extension information it needs
33
+ * to generate extension configs. We keep it updated based on what's in
34
+ * `extensionStore`.
35
+ */ updateConfigExtensionStore(extensionInternalStore.getState());
36
+ extensionInternalStore.subscribe(updateConfigExtensionStore);
37
+ function updateConfigExtensionStore(extensionState) {
38
+ const configExtensionRecords = [];
39
+ for (let extensionInfo of Object.values(extensionState.extensions)){
40
+ for (let instance of extensionInfo.instances){
41
+ configExtensionRecords.push({
42
+ slotModuleName: instance.slotModuleName,
43
+ extensionModuleName: extensionInfo.moduleName,
44
+ slotName: instance.slotName,
45
+ extensionId: instance.id
46
+ });
47
+ }
48
+ }
49
+ if (!isEqual(configExtensionStore.getState().mountedExtensions, configExtensionRecords)) {
50
+ configExtensionStore.setState({
51
+ mountedExtensions: configExtensionRecords
52
+ });
53
+ }
54
+ }
@@ -0,0 +1,11 @@
1
+ export interface ExtensionData {
2
+ extensionSlotName: string;
3
+ extensionSlotModuleName: string;
4
+ extensionId: string;
5
+ }
6
+ export interface ComponentConfig {
7
+ moduleName: string;
8
+ featureName: string;
9
+ extension?: ExtensionData;
10
+ }
11
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uBAAuB,EAAE,MAAM,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,61 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type LifeCycles } from 'single-spa';
3
+ import { type WorkspaceGroupDefinition, type WorkspaceWindowState } from '@egen-civitas/esm-globals';
4
+ /** See [[WorkspaceDefinition]] for more information about these properties */
5
+ export interface WorkspaceRegistration {
6
+ name: string;
7
+ title: string;
8
+ titleNode?: ReactNode;
9
+ type: string;
10
+ canHide: boolean;
11
+ canMaximize: boolean;
12
+ width: 'narrow' | 'wider' | 'extra-wide';
13
+ preferredWindowSize: WorkspaceWindowState;
14
+ load(): Promise<LifeCycles>;
15
+ moduleName: string;
16
+ groups: Array<string>;
17
+ }
18
+ export type WorkspaceGroupRegistration = WorkspaceGroupDefinition & {
19
+ members: Array<string>;
20
+ };
21
+ /** See [[WorkspaceDefinition]] for more information about these properties */
22
+ export interface RegisterWorkspaceOptions {
23
+ name: string;
24
+ title: string;
25
+ type?: string;
26
+ canHide?: boolean;
27
+ canMaximize?: boolean;
28
+ width?: 'narrow' | 'wider' | 'extra-wide';
29
+ preferredWindowSize?: WorkspaceWindowState;
30
+ component: string;
31
+ moduleName: string;
32
+ groups?: Array<string>;
33
+ load(): Promise<LifeCycles>;
34
+ }
35
+ /**
36
+ * Tells the workspace system about a workspace. This is used by the app shell
37
+ * to register workspaces defined in the `routes.json` file.
38
+ * @internal
39
+ */
40
+ export declare function registerWorkspace(workspace: RegisterWorkspaceOptions): void;
41
+ /**
42
+ * Tells the workspace system about a workspace group. This is used by the app shell
43
+ * to register workspace groups defined in the `routes.json` file.
44
+ * @internal
45
+ */
46
+ export declare function registerWorkspaceGroup(workspaceGroup: WorkspaceGroupRegistration): void;
47
+ /**
48
+ * This exists for compatibility with the old way of registering
49
+ * workspaces (as extensions).
50
+ *
51
+ * @param name of the workspace
52
+ */
53
+ export declare function getWorkspaceRegistration(name: string): WorkspaceRegistration;
54
+ /**
55
+ * This provides the workspace group registration and is also compatible with the
56
+ * old way of registering workspace groups (as extensions), but isn't recommended.
57
+ *
58
+ * @param name of the workspace
59
+ */
60
+ export declare function getWorkspaceGroupRegistration(name: string): WorkspaceGroupRegistration;
61
+ //# sourceMappingURL=workspaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../src/workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,KAAK,wBAAwB,EAAE,KAAK,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAKrG,8EAA8E;AAC9E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,YAAY,CAAC;IACzC,mBAAmB,EAAE,oBAAoB,CAAC;IAC1C,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACvB;AAED,MAAM,MAAM,0BAA0B,GAAG,wBAAwB,GAAG;IAClE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACxB,CAAC;AAkBF,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,YAAY,CAAC;IAC1C,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,wBAAwB,QAgBpE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,0BAA0B,QA4BhF;AAGD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,CA6B5E;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,0BAA0B,CAOtF"}
@@ -0,0 +1,122 @@
1
+ import { getExtensionRegistration } from "./index.js";
2
+ import { createGlobalStore } from "@egen-civitas/esm-state";
3
+ import { translateFrom } from "@egen-civitas/esm-translations";
4
+ const workspaceRegistrationStore = createGlobalStore('workspaceRegistrations', {
5
+ workspaces: {}
6
+ });
7
+ const workspaceGroupStore = createGlobalStore('workspaceGroups', {
8
+ workspaceGroups: {}
9
+ });
10
+ /**
11
+ * Tells the workspace system about a workspace. This is used by the app shell
12
+ * to register workspaces defined in the `routes.json` file.
13
+ * @internal
14
+ */ export function registerWorkspace(workspace) {
15
+ workspaceRegistrationStore.setState((state)=>({
16
+ workspaces: {
17
+ ...state.workspaces,
18
+ [workspace.name]: {
19
+ ...workspace,
20
+ load: workspace.load,
21
+ preferredWindowSize: workspace.preferredWindowSize ?? 'normal',
22
+ type: workspace.type ?? 'form',
23
+ canHide: workspace.canHide ?? false,
24
+ canMaximize: workspace.canMaximize ?? false,
25
+ width: workspace.width ?? 'narrow',
26
+ groups: workspace.groups ?? []
27
+ }
28
+ }
29
+ }));
30
+ }
31
+ /**
32
+ * Tells the workspace system about a workspace group. This is used by the app shell
33
+ * to register workspace groups defined in the `routes.json` file.
34
+ * @internal
35
+ */ export function registerWorkspaceGroup(workspaceGroup) {
36
+ workspaceGroupStore.setState((state)=>{
37
+ const group = state.workspaceGroups[workspaceGroup.name];
38
+ if (group) {
39
+ // This condition occurs when a workspace with a `groups` property is registered before
40
+ // the corresponding workspace group. In such cases, the workspace group is registered
41
+ // by the `attachWorkspaceToGroup` function.
42
+ return {
43
+ workspaceGroups: {
44
+ ...state.workspaceGroups,
45
+ [workspaceGroup.name]: {
46
+ ...group,
47
+ members: Array.from(new Set([
48
+ ...group.members,
49
+ ...workspaceGroup.members
50
+ ]))
51
+ }
52
+ }
53
+ };
54
+ } else {
55
+ return {
56
+ workspaceGroups: {
57
+ ...state.workspaceGroups,
58
+ [workspaceGroup.name]: {
59
+ name: workspaceGroup.name,
60
+ members: workspaceGroup.members
61
+ }
62
+ }
63
+ };
64
+ }
65
+ });
66
+ }
67
+ const workspaceExtensionWarningsIssued = new Set();
68
+ /**
69
+ * This exists for compatibility with the old way of registering
70
+ * workspaces (as extensions).
71
+ *
72
+ * @param name of the workspace
73
+ */ export function getWorkspaceRegistration(name) {
74
+ const registeredWorkspaces = workspaceRegistrationStore.getState().workspaces;
75
+ if (registeredWorkspaces[name]) {
76
+ return registeredWorkspaces[name];
77
+ } else {
78
+ const workspaceExtension = getExtensionRegistration(name);
79
+ if (workspaceExtension) {
80
+ if (!workspaceExtensionWarningsIssued.has(name)) {
81
+ console.warn(`The workspace '${name}' is registered as an extension. This is deprecated. Please register it in the "workspaces" section of the routes.json file.`);
82
+ workspaceExtensionWarningsIssued.add(name);
83
+ }
84
+ return {
85
+ name: workspaceExtension.name,
86
+ title: getTitleFromExtension(workspaceExtension),
87
+ moduleName: workspaceExtension.moduleName,
88
+ preferredWindowSize: workspaceExtension.meta?.screenSize ?? 'normal',
89
+ load: workspaceExtension.load,
90
+ type: workspaceExtension.meta?.type ?? 'form',
91
+ canHide: workspaceExtension.meta?.canHide ?? false,
92
+ canMaximize: workspaceExtension.meta?.canMaximize ?? false,
93
+ width: workspaceExtension.meta?.width ?? 'narrow',
94
+ groups: workspaceExtension.meta?.groups ?? []
95
+ };
96
+ } else {
97
+ throw new Error(`No workspace named '${name}' has been registered.`);
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * This provides the workspace group registration and is also compatible with the
103
+ * old way of registering workspace groups (as extensions), but isn't recommended.
104
+ *
105
+ * @param name of the workspace
106
+ */ export function getWorkspaceGroupRegistration(name) {
107
+ const registeredWorkspaces = workspaceGroupStore.getState().workspaceGroups;
108
+ if (registeredWorkspaces[name]) {
109
+ return registeredWorkspaces[name];
110
+ } else {
111
+ throw new Error(`No workspace group named '${name}' has been registered.`);
112
+ }
113
+ }
114
+ function getTitleFromExtension(ext) {
115
+ const title = ext?.meta?.title;
116
+ if (typeof title === 'string') {
117
+ return title;
118
+ } else if (title && typeof title === 'object') {
119
+ return translateFrom(ext.moduleName, title.key, title.default);
120
+ }
121
+ return ext.name;
122
+ }
@@ -0,0 +1,61 @@
1
+ import { type WorkspaceDefinition2, type WorkspaceGroupDefinition2, type WorkspaceWindowDefinition2 } from '@egen-civitas/esm-globals';
2
+ export interface OpenedWorkspace {
3
+ workspaceName: string;
4
+ props: Record<string, any> | null;
5
+ hasUnsavedChanges: boolean;
6
+ /** Unique identifier for the workspace instance, used to track unique instances of the same workspace */
7
+ uuid: string;
8
+ }
9
+ export interface OpenedWindow {
10
+ windowName: string;
11
+ /** Root workspace at index 0, child workspaces follow */
12
+ openedWorkspaces: Array<OpenedWorkspace>;
13
+ props: Record<string, any> | null;
14
+ maximized: boolean;
15
+ }
16
+ export interface OpenedGroup {
17
+ groupName: string;
18
+ props: Record<string, any> | null;
19
+ }
20
+ export interface WorkspaceStoreState2 {
21
+ registeredGroupsByName: Record<string, WorkspaceGroupDefinition2 & {
22
+ moduleName: string;
23
+ }>;
24
+ registeredWindowsByName: Record<string, WorkspaceWindowDefinition2 & {
25
+ moduleName: string;
26
+ }>;
27
+ registeredWorkspacesByName: Record<string, WorkspaceDefinition2 & {
28
+ moduleName: string;
29
+ }>;
30
+ openedGroup: OpenedGroup | null;
31
+ /** Most recently opened window at the end of array. Each element has a unique windowName */
32
+ openedWindows: Array<OpenedWindow>;
33
+ /**
34
+ * While there can be multiple openedWindows, only the most recently opened window can be
35
+ * toggled shown or hidden, the rest are implicitly hidden.
36
+ **/
37
+ isMostRecentlyOpenedWindowHidden: boolean;
38
+ workspaceTitleByWorkspaceName: Record<string, string>;
39
+ }
40
+ export declare const workspace2Store: import("zustand").StoreApi<WorkspaceStoreState2>;
41
+ /**
42
+ * Given a workspace name, return the window that the workspace belongs to
43
+ * @param workspaceName
44
+ * @returns
45
+ */
46
+ export declare function getWindowByWorkspaceName(workspaceName: string): WorkspaceWindowDefinition2 & {
47
+ moduleName: string;
48
+ };
49
+ /**
50
+ * Given a window name, return the group that the window belongs to
51
+ * @param windowName
52
+ * @returns
53
+ */
54
+ export declare function getGroupByWindowName(windowName: string): WorkspaceGroupDefinition2 & {
55
+ moduleName: string;
56
+ };
57
+ export declare function getOpenedWindowIndexByWorkspace(workspaceName: string): number;
58
+ export declare function registerWorkspaceGroups2(appName: string, workspaceGroupDefs: Array<WorkspaceGroupDefinition2>): void;
59
+ export declare function registerWorkspaceWindows2(appName: string, workspaceWindowDefs: Array<WorkspaceWindowDefinition2>): void;
60
+ export declare function registerWorkspaces2(moduleName: string, workspaceDefs: Array<WorkspaceDefinition2>): void;
61
+ //# sourceMappingURL=workspaces2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspaces2.d.ts","sourceRoot":"","sources":["../src/workspaces2.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,EAChC,MAAM,2BAA2B,CAAC;AAGnC,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,yGAAyG;IACzG,IAAI,EAAE,MAAM,CAAC;CACd;AACD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,gBAAgB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IACzC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;CACnC;AACD,MAAM,WAAW,oBAAoB;IACnC,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3F,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,0BAA0B,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7F,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1F,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,4FAA4F;IAC5F,aAAa,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAEnC;;;QAGI;IACJ,gCAAgC,EAAE,OAAO,CAAC;IAE1C,6BAA6B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvD;AAYD,eAAO,MAAM,eAAe,kDAAsE,CAAC;AAEnG;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,aAAa,EAAE,MAAM;gBAhCuB,MAAM;EA6C1F;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM;gBArD4B,MAAM;EAkExF;AAED,wBAAgB,+BAA+B,CAAC,aAAa,EAAE,MAAM,UAKpE;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,CAAC,yBAAyB,CAAC,QAgB7G;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,KAAK,CAAC,0BAA0B,CAAC,QAiBhH;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,oBAAoB,CAAC,QAiBjG"}
@@ -0,0 +1,116 @@
1
+ import { createGlobalStore } from "@egen-civitas/esm-state";
2
+ const initialState = {
3
+ registeredGroupsByName: {},
4
+ registeredWindowsByName: {},
5
+ registeredWorkspacesByName: {},
6
+ openedGroup: null,
7
+ openedWindows: [],
8
+ workspaceTitleByWorkspaceName: {},
9
+ isMostRecentlyOpenedWindowHidden: false
10
+ };
11
+ export const workspace2Store = createGlobalStore('workspace2', initialState);
12
+ /**
13
+ * Given a workspace name, return the window that the workspace belongs to
14
+ * @param workspaceName
15
+ * @returns
16
+ */ export function getWindowByWorkspaceName(workspaceName) {
17
+ const { registeredWorkspacesByName, registeredWindowsByName } = workspace2Store.getState();
18
+ const workspace = registeredWorkspacesByName[workspaceName];
19
+ if (!workspace) {
20
+ throw new Error(`No workspace found with name: ${workspaceName}`);
21
+ } else {
22
+ const workspaceWindow = registeredWindowsByName[workspace.window];
23
+ if (!workspaceWindow) {
24
+ throw new Error(`No workspace window found with name: ${workspace.window} for workspace: ${workspaceName}`);
25
+ } else {
26
+ return workspaceWindow;
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * Given a window name, return the group that the window belongs to
32
+ * @param windowName
33
+ * @returns
34
+ */ export function getGroupByWindowName(windowName) {
35
+ const { registeredGroupsByName, registeredWindowsByName } = workspace2Store.getState();
36
+ const workspaceWindow = registeredWindowsByName[windowName];
37
+ if (!workspaceWindow) {
38
+ throw new Error(`No workspace window found with name: ${windowName}`);
39
+ } else {
40
+ const group = registeredGroupsByName[workspaceWindow.group];
41
+ if (!group) {
42
+ throw new Error(`No workspace group found with name: ${workspaceWindow.group} for window: ${windowName}`);
43
+ } else {
44
+ return group;
45
+ }
46
+ }
47
+ }
48
+ export function getOpenedWindowIndexByWorkspace(workspaceName) {
49
+ const { openedWindows } = workspace2Store.getState();
50
+ return openedWindows.findIndex((a)=>a.openedWorkspaces.find((openedWorkspace)=>openedWorkspace.workspaceName === workspaceName));
51
+ }
52
+ export function registerWorkspaceGroups2(appName, workspaceGroupDefs) {
53
+ if (workspaceGroupDefs.length == 0) {
54
+ return;
55
+ }
56
+ const { registeredGroupsByName } = workspace2Store.getState();
57
+ const newRegisteredGroupsByName = {
58
+ ...registeredGroupsByName
59
+ };
60
+ for (const workspaceGroupDef of workspaceGroupDefs){
61
+ if (newRegisteredGroupsByName[workspaceGroupDef.name]) {
62
+ throw new Error(`Cannot register workspace group ${workspaceGroupDef.name} more than once`);
63
+ }
64
+ newRegisteredGroupsByName[workspaceGroupDef.name] = {
65
+ ...workspaceGroupDef,
66
+ moduleName: appName
67
+ };
68
+ }
69
+ workspace2Store.setState({
70
+ registeredGroupsByName: newRegisteredGroupsByName
71
+ });
72
+ }
73
+ export function registerWorkspaceWindows2(appName, workspaceWindowDefs) {
74
+ if (workspaceWindowDefs.length == 0) {
75
+ return;
76
+ }
77
+ const { registeredWindowsByName } = workspace2Store.getState();
78
+ const newRegisteredWindowsByName = {
79
+ ...registeredWindowsByName
80
+ };
81
+ for (const windowDef of workspaceWindowDefs){
82
+ if (newRegisteredWindowsByName[windowDef.name]) {
83
+ throw new Error(`Cannot register workspace window ${windowDef.name} more than once`);
84
+ }
85
+ const registeredWindowDef = {
86
+ ...windowDef,
87
+ moduleName: appName
88
+ };
89
+ newRegisteredWindowsByName[windowDef.name] = registeredWindowDef;
90
+ }
91
+ workspace2Store.setState({
92
+ registeredWindowsByName: newRegisteredWindowsByName
93
+ });
94
+ }
95
+ export function registerWorkspaces2(moduleName, workspaceDefs) {
96
+ if (workspaceDefs.length == 0) {
97
+ return;
98
+ }
99
+ const { registeredWorkspacesByName } = workspace2Store.getState();
100
+ const newRegisteredWorkspacesByName = {
101
+ ...registeredWorkspacesByName
102
+ };
103
+ for (const workspaceDef of workspaceDefs){
104
+ if (newRegisteredWorkspacesByName[workspaceDef.name]) {
105
+ throw new Error(`Cannot register workspace ${workspaceDef.name} more than once`);
106
+ }
107
+ const workspaceDefWithLoader = {
108
+ ...workspaceDef,
109
+ moduleName
110
+ };
111
+ newRegisteredWorkspacesByName[workspaceDef.name] = workspaceDefWithLoader;
112
+ }
113
+ workspace2Store.setState({
114
+ registeredWorkspacesByName: newRegisteredWorkspacesByName
115
+ });
116
+ }
package/mock-jest.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { getGlobalStore } from '@egen-civitas/esm-state/mock';
2
+
3
+ export const attach = jest.fn();
4
+ export const detach = jest.fn();
5
+ export const detachAll = jest.fn();
6
+
7
+ export const switchTo = jest.fn();
8
+
9
+ export const getExtensionStore = () => getGlobalStore('extensions', { slots: {} });
10
+
11
+ export const getExtensionInternalStore = () =>
12
+ getGlobalStore('extensions-internal', {
13
+ slots: {},
14
+ extensions: {},
15
+ });
16
+
17
+ const mockExtensionRegistry = {};
18
+
19
+ export const getExtensionRegistration = jest.fn((name) => {
20
+ return mockExtensionRegistry[name];
21
+ });
22
+
23
+ export const registerExtension = jest.fn((ext) => {
24
+ mockExtensionRegistry[ext.name] = ext;
25
+ });
26
+
27
+ export function clearMockExtensionRegistry() {
28
+ Object.keys(mockExtensionRegistry).forEach((key) => {
29
+ delete mockExtensionRegistry[key];
30
+ });
31
+ }