Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/framework/esm-extensions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './helpers';
export * from './left-nav';
export * from './modals';
export * from './workspaces';
export * from './workspaces2';
export * from './render';
export * from './store';
export * from './types';
2 changes: 1 addition & 1 deletion packages/framework/esm-extensions/src/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export function getWorkspaceRegistration(name: string): WorkspaceRegistration {
}

/**
* This provides the workspace group registration and is also compatibile with the
* This provides the workspace group registration and is also compatible with the
* old way of registering workspace groups (as extensions), but isn't recommended.
*
* @param name of the workspace
Expand Down
149 changes: 149 additions & 0 deletions packages/framework/esm-extensions/src/workspaces2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import {
type WorkspaceDefinition2,
type WorkspaceGroupDefinition2,
type WorkspaceWindowDefinition2,
} from '@openmrs/esm-globals';
import { createGlobalStore } from '@openmrs/esm-state';

export interface OpenedWorkspace {
workspaceName: string;
props: Record<string, any> | null;
hasUnsavedChanges: boolean;
/** Unique identifier for the workspace instance, used to track unique instances of the same workspace */
uuid: string;
}
export interface OpenedWindow {
windowName: string;
/** Root workspace at index 0, child workspaces follow */
openedWorkspaces: Array<OpenedWorkspace>;
props: Record<string, any> | null;
maximized: boolean;
hidden: boolean;
}
export interface WorkspaceStoreState2 {
registeredGroupsByName: Record<string, WorkspaceGroupDefinition2>;
registeredWindowsByName: Record<string, WorkspaceWindowDefinition2 & { moduleName: string }>;
registeredWorkspacesByName: Record<string, WorkspaceDefinition2 & { moduleName: string }>;
openedGroup: {
groupName: string;
props: Record<string, any> | null;
} | null;
/** Most recently opened window at the end of array. Each element has a unique windowName */
openedWindows: Array<OpenedWindow>;

workspaceTitleByWorkspaceName: Record<string, string>;
}

const initialState: WorkspaceStoreState2 = {
registeredGroupsByName: {},
registeredWindowsByName: {},
registeredWorkspacesByName: {},
openedGroup: null,
openedWindows: [],
workspaceTitleByWorkspaceName: {},
};

export const workspace2Store = createGlobalStore<WorkspaceStoreState2>('workspace2', initialState);

/**
* Given a workspace name, return the window that the workspace belongs to
* @param workspaceName
* @returns
*/
export function getWindowByWorkspaceName(workspaceName: string) {
const { registeredWorkspacesByName, registeredWindowsByName } = workspace2Store.getState();
const workspace = registeredWorkspacesByName[workspaceName];
if (!workspace) {
throw new Error(`No workspace found with name: ${workspaceName}`);
} else {
const workspaceWindow = registeredWindowsByName[workspace.window];
if (!workspaceWindow) {
throw new Error(`No workspace window found with name: ${workspace.window} for workspace: ${workspaceName}`);
} else {
return workspaceWindow;
}
}
}

/**
* Given a window name, return the group that the window belongs to
* @param windowName
* @returns
*/
export function getGroupByWindowName(windowName: string) {
const { registeredGroupsByName, registeredWindowsByName } = workspace2Store.getState();
const workspaceWindow = registeredWindowsByName[windowName];
if (!workspaceWindow) {
throw new Error(`No workspace window found with name: ${windowName}`);
} else {
const group = registeredGroupsByName[workspaceWindow.group];
if (!group) {
throw new Error(`No workspace group found with name: ${workspaceWindow.group} for window: ${windowName}`);
} else {
return group;
}
}
}

export function getOpenedWindowIndexByWorkspace(workspaceName: string) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually, we should add TSDoc comments to anything in the public API, which I assume covers all of this.

const { openedWindows } = workspace2Store.getState();
return openedWindows.findIndex((a) =>
a.openedWorkspaces.find((openedWorkspace) => openedWorkspace.workspaceName === workspaceName),
);
}

export function registerWorkspaceGroups2(workspaceGroupDefs: Array<WorkspaceGroupDefinition2>) {
if (workspaceGroupDefs.length == 0) {
return;
}
const { registeredGroupsByName } = workspace2Store.getState();
const newRegisteredGroupsByName = { ...registeredGroupsByName };
for (const workspaceGroupDef of workspaceGroupDefs) {
if (newRegisteredGroupsByName[workspaceGroupDef.name]) {
throw new Error(`Cannot register workspace group ${workspaceGroupDef.name} more than once`);
}
newRegisteredGroupsByName[workspaceGroupDef.name] = workspaceGroupDef;
}

workspace2Store.setState({
registeredGroupsByName: newRegisteredGroupsByName,
});
}

export function registerWorkspaceWindows2(appName: string, workspaceWindowDefs: Array<WorkspaceWindowDefinition2>) {
if (workspaceWindowDefs.length == 0) {
return;
}
const { registeredWindowsByName } = workspace2Store.getState();
const newRegisteredWindowsByName = { ...registeredWindowsByName };
for (const windowDef of workspaceWindowDefs) {
if (newRegisteredWindowsByName[windowDef.name]) {
throw new Error(`Cannot register workspace window ${windowDef.name} more than once`);
}
const registeredWindowDef = { ...windowDef, moduleName: appName };
newRegisteredWindowsByName[windowDef.name] = registeredWindowDef;
}

workspace2Store.setState({
registeredWindowsByName: newRegisteredWindowsByName,
});
}

export function registerWorkspaces2(moduleName: string, workspaceDefs: Array<WorkspaceDefinition2>) {
if (workspaceDefs.length == 0) {
return;
}
const { registeredWorkspacesByName } = workspace2Store.getState();
const newRegisteredWorkspacesByName = { ...registeredWorkspacesByName };
for (const workspaceDef of workspaceDefs) {
if (newRegisteredWorkspacesByName[workspaceDef.name]) {
throw new Error(`Cannot register workspace ${workspaceDef.name} more than once`);
}
const workspaceDefWithLoader = { ...workspaceDef, moduleName };
newRegisteredWorkspacesByName[workspaceDef.name] = workspaceDefWithLoader;
}

workspace2Store.setState({
registeredWorkspacesByName: newRegisteredWorkspacesByName,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[O3 Framework](../API.md) / closeWorkspaceGroup2

# Function: closeWorkspaceGroup2()

> **closeWorkspaceGroup2**(): `Promise`\<`boolean`\>

Defined in: [packages/framework/esm-styleguide/src/workspaces2/workspace2.ts:69](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces2/workspace2.ts#L69)

**`Experimental`**

Closes the workspace group that is currently opened. Note that only one workspace group
may be opened at any given time

## Returns

`Promise`\<`boolean`\>

a Promise that resolves to true if there is no opened group to begin with or we successfully closed
the opened group; false otherwise.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ invalid key to this function will result in a type error.

### key

`"error"` | `"delete"` | `"actions"` | `"address"` | `"age"` | `"cancel"` | `"change"` | `"Clinic"` | `"close"` | `"confirm"` | `"contactAdministratorIfIssuePersists"` | `"contactDetails"` | `"edit"` | `"errorCopy"` | `"female"` | `"loading"` | `"male"` | `"other"` | `"patientIdentifierSticker"` | `"patientLists"` | `"print"` | `"printError"` | `"printErrorExplainer"` | `"printIdentifierSticker"` | `"printing"` | `"relationships"` | `"resetOverrides"` | `"save"` | `"scriptLoadingFailed"` | `"scriptLoadingError"` | `"seeMoreLists"` | `"sex"` | `"showLess"` | `"showMore"` | `"toggleDevTools"` | `"unknown"` | `"closeAllOpenedWorkspaces"` | `"closingAllWorkspacesPromptBody"` | `"closingAllWorkspacesPromptTitle"` | `"discard"` | `"hide"` | `"maximize"` | `"minimize"` | `"openAnyway"` | `"unsavedChangesInOpenedWorkspace"` | `"unsavedChangesInWorkspace"` | `"unsavedChangesTitleText"` | `"workspaceHeader"` | `"address1"` | `"address2"` | `"address3"` | `"address4"` | `"address5"` | `"address6"` | `"city"` | `"cityVillage"` | `"country"` | `"countyDistrict"` | `"district"` | `"postalCode"` | `"state"` | `"stateProvince"`
`"error"` | `"delete"` | `"actions"` | `"address"` | `"age"` | `"cancel"` | `"change"` | `"Clinic"` | `"close"` | `"confirm"` | `"contactAdministratorIfIssuePersists"` | `"contactDetails"` | `"edit"` | `"errorCopy"` | `"female"` | `"loading"` | `"male"` | `"other"` | `"patientIdentifierSticker"` | `"patientLists"` | `"print"` | `"printError"` | `"printErrorExplainer"` | `"printIdentifierSticker"` | `"printing"` | `"relationships"` | `"resetOverrides"` | `"save"` | `"scriptLoadingFailed"` | `"scriptLoadingError"` | `"seeMoreLists"` | `"sex"` | `"showLess"` | `"showMore"` | `"toggleDevTools"` | `"unknown"` | `"closeWorkspaces2PromptTitle"` | `"closeWorkspaces2PromptBody"` | `"closeAllOpenedWorkspaces"` | `"closingAllWorkspacesPromptBody"` | `"closingAllWorkspacesPromptTitle"` | `"discard"` | `"hide"` | `"maximize"` | `"minimize"` | `"openAnyway"` | `"unsavedChangesInOpenedWorkspace"` | `"unsavedChangesInWorkspace"` | `"unsavedChangesTitleText"` | `"workspaceHeader"` | `"address1"` | `"address2"` | `"address3"` | `"address4"` | `"address5"` | `"address6"` | `"city"` | `"cityVillage"` | `"country"` | `"countyDistrict"` | `"district"` | `"postalCode"` | `"state"` | `"stateProvince"`

### defaultText?

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
[O3 Framework](../API.md) / launchWorkspace2

# Function: launchWorkspace2()

> **launchWorkspace2**\<`WorkspaceProps`, `WindowProps`, `GroupProp`\>(`workspaceName`, `workspaceProps`, `windowProps`, `groupProps`): `Promise`\<`boolean`\>

Defined in: [packages/framework/esm-styleguide/src/workspaces2/workspace2.ts:127](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces2/workspace2.ts#L127)

**`Experimental`**

Attempts to launch the specified workspace with the given workspace props. This also implicitly opens
the workspace window to which the workspace belongs (if it's not opened already),
and the workspace group to which the window belongs (if it's not opened already).

When calling `launchWorkspace2`, we need to also pass in the workspace props. While not required,
we can also pass in the window props (shared by other workspaces in the window) and the group props
(shared by all windows and their workspaces). Omitting the window props or the group props[^1] means the caller
explicitly does not care what the current window props and group props are, and that they may be set
by other actions (like calling `launchWorkspace2` on a different workspace with those props passed in)
at a later time.

If there is already an opened workspace group, and it's not the group the workspace belongs to
or has incompatible[^2] group props, then we prompt the user to close the group (and its windows and their workspaces).
On user confirm, the existing opened group is closed and the new workspace, along with its window and its group,
is opened.

If the window is already opened, but with incompatible window props, we prompt the user to close
the window (and all its opened workspaces), and reopen the window with (only) the newly requested workspace.

If the workspace is already opened, but with incompatible workspace props, we also prompt the user to close
the **window** (and all its opened workspaces), and reopen the window with (only) the newly requested workspace.
This is true regardless of whether the already opened workspace has any child workspaces.

Note that calling this function *never* results in creating a child workspace in the affected window.
To do so, we need to call `launchChildWorkspace` instead.

[^1] Omitting window or group props is useful for workspaces that don't have ties to the window or group "context" (props).
For example, in the patient chart, the visit notes / clinical forms / order basket action menu button all share
a "group context" of the current visit. However, the "patient list" action menu button does not need to share that group
context, so opening that workspace should not need to cause other workspaces / windows / groups to potentially close.
The "patient search" workspace in the queues and ward apps is another example.

[^2] 2 sets of props are compatible if either one is nullish, or if they are shallow equal.

## Type Parameters

### WorkspaceProps

`WorkspaceProps` *extends* `object`

### WindowProps

`WindowProps` *extends* `object`

### GroupProp

`GroupProp` *extends* `object`

## Parameters

### workspaceName

`string`

### workspaceProps

`null` | `WorkspaceProps`

### windowProps

`null` | `WindowProps`

### groupProps

`null` | `GroupProp`

## Returns

`Promise`\<`boolean`\>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[O3 Framework](../API.md) / launchWorkspaceGroup2

# Function: launchWorkspaceGroup2()

> **launchWorkspaceGroup2**\<`GroupProps`\>(`groupName`, `groupProps`): `Promise`\<`boolean`\>

Defined in: [packages/framework/esm-styleguide/src/workspaces2/workspace2.ts:29](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces2/workspace2.ts#L29)

**`Experimental`**

Attempts to launch the specified workspace group with the given group props. Note that only one workspace group
may be opened at any given time. If a workspace group is already opened, calling `launchWorkspaceGroup2` with
either a different group name, or same group name but different incompatible props**, will result in prompting to
confirm closing workspaces. If the user confirms, the opened group, along with its windows (and their workspaces), is closed, and
the requested group is immediately opened.

** 2 sets of props are compatible if either one is nullish, or if they are shallow equal.

## Type Parameters

### GroupProps

`GroupProps` *extends* `object`

## Parameters

### groupName

`string`

### groupProps

`null` | `GroupProps`

## Returns

`Promise`\<`boolean`\>

a Promise that resolves to true if the specified workspace group with the specified group props
is successfully opened, or that it already is opened.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Interface: FeatureFlagDefinition

Defined in: packages/framework/esm-globals/dist/types.d.ts:296
Defined in: packages/framework/esm-globals/dist/types.d.ts:316

A definition of a feature flag extracted from the routes.json

Expand All @@ -12,7 +12,7 @@ A definition of a feature flag extracted from the routes.json

> **description**: `string`

Defined in: packages/framework/esm-globals/dist/types.d.ts:302
Defined in: packages/framework/esm-globals/dist/types.d.ts:322

An explanation of what the flag does, which will be displayed in the Implementer Tools

Expand All @@ -22,7 +22,7 @@ An explanation of what the flag does, which will be displayed in the Implementer

> **flagName**: `string`

Defined in: packages/framework/esm-globals/dist/types.d.ts:298
Defined in: packages/framework/esm-globals/dist/types.d.ts:318

A code-friendly name for the flag, which will be used to reference it in code

Expand All @@ -32,6 +32,6 @@ A code-friendly name for the flag, which will be used to reference it in code

> **label**: `string`

Defined in: packages/framework/esm-globals/dist/types.d.ts:300
Defined in: packages/framework/esm-globals/dist/types.d.ts:320

A human-friendly name which will be displayed in the Implementer Tools
Loading
Loading