chore: Clean up unused files, imports, and work on getting end-to-end tests and unit tests running (#2272)
This commit is contained in:
1 parent
414ebc6850
commit
0db81b30f3
374 files changed
+14138
-21288
No files matched your search
@@ -0,0 +1,158 @@
|
||||
# Module: Commands
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Command Definitions](#command-definitions)
|
||||
- [Commands Manager](#commands-manager)
|
||||
- [Instantiating](#instatiating)
|
||||
- [Public API](#public-api)
|
||||
- [Contexts](#contexts)
|
||||
|
||||
## Overview
|
||||
|
||||
An extension can register a Commands Module by defining a `getCommandsModule`
|
||||
method. The Commands Module allows us to register one or more commands scoped to
|
||||
specific [contexts](./../index.md#contexts). Commands have several unique
|
||||
characteristics that make them tremendously powerful:
|
||||
|
||||
- Multiple implementations for the same command can be defined
|
||||
- Only the correct command's implementation will be run, dependent on the
|
||||
application's "context"
|
||||
- Commands can be called from extensions, modules, and the consuming application
|
||||
|
||||
Here is a simple example commands module:
|
||||
|
||||
```js
|
||||
export default {
|
||||
id: 'example-commands-module',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
*/
|
||||
getCommandsModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
definitions: {
|
||||
sayHello: {
|
||||
commandFn: ({ words }) => {
|
||||
console.log(words);
|
||||
},
|
||||
options: { words: 'Hello!' },
|
||||
},
|
||||
},
|
||||
defaultContext: 'VIEWER',
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Each definition returned by the Commands Module is registered to the
|
||||
`ExtensionManager`'s `CommandsManager`.
|
||||
|
||||
## Command Definitions
|
||||
|
||||
The command definition consists of a named command (`myCommandName` below) and a
|
||||
`commandFn`. The command name is used to call the command, and the `commandFn`
|
||||
is the "command" that is actioned.
|
||||
|
||||
```js
|
||||
myCommandName: {
|
||||
commandFn: ({ viewports, other, options }) => { },
|
||||
storeContexts: ['viewports'],
|
||||
options: { words: 'Just kidding! Goodbye!' },
|
||||
context: 'ACTIVE_VIEWPORT::CORNERSTONE',
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| --------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `commandFn` | func | The function to call when command is run. Receives `options` and `storeContexts`. |
|
||||
| `storeContexts` | string[] | (optional) Expected state objects to be passed in as props. Located using `getAppState` fn defined at `CommandsManager`'s instatiation. |
|
||||
| `options` | object | (optional) Arguments to pass at the time of calling to the `commandFn` |
|
||||
| `context` | string[] or string | (optional) Overrides the `defaultContext`. Let's us know if command is currently "available" to be run. |
|
||||
|
||||
## Command Behavior
|
||||
|
||||
**I have many similar commands. How can I share their `commandFn` and make it
|
||||
reusable?**
|
||||
|
||||
This is where `storeContexts` and `options` come in. We use these in our
|
||||
`setToolActive` command. `storeContexts` helps us identify our `activeViewport`,
|
||||
and `options` allow us to pass in the name of a tool we would like to set as
|
||||
active.
|
||||
|
||||
**If there are multiple valid commands for the application's active contexts**
|
||||
|
||||
- What happens: all commands are run
|
||||
- When to use: A `clearData` command that cleans up state for multiple
|
||||
extensions
|
||||
|
||||
**If no commands are valid for the application's active contexts**
|
||||
|
||||
- What happens: a warning is printed to the console
|
||||
- When to use: a `hotkey` (like "invert") that doesn't make sense for the
|
||||
current viewport (PDF or HTML)
|
||||
|
||||
## `CommandsManager`
|
||||
|
||||
The `CommandsManager` is a class defined in the `@ohif/core` project. A single
|
||||
instance of it should be defined in the consuming application, and it should be
|
||||
used when constructing the `ExtensionManager`.
|
||||
|
||||
### Instantiating
|
||||
|
||||
When we instantiate the `CommandsManager`, we need to pass it two methods:
|
||||
|
||||
- `getAppState` - Should return the application's state when called
|
||||
- `getActiveContexts` - Should return the application's active contexts when
|
||||
called
|
||||
|
||||
These methods are used internally to help determine which commands are currently
|
||||
valid, and how to provide them with any state they may need at the time they are
|
||||
called.
|
||||
|
||||
```js
|
||||
const commandsManager = new CommandsManager({
|
||||
getAppState,
|
||||
getActiveContexts,
|
||||
});
|
||||
```
|
||||
|
||||
### Public API
|
||||
|
||||
If you would like to run a command in the consuming app or an extension, you can
|
||||
use one of the following methods:
|
||||
|
||||
```js
|
||||
// Returns all commands for a given context
|
||||
commandsManager.getContext('string');
|
||||
|
||||
// Attempts to run a command
|
||||
commandsManager.runCommand('speak', { command: 'hello' });
|
||||
|
||||
// Run command, but override the active contexts
|
||||
commandsManager.runCommand('speak', { command: 'hello' }, ['VIEWER']);
|
||||
```
|
||||
|
||||
The `ExtensionManager` handles registering commands and creating contexts, so
|
||||
most consumer's won't need these methods. If you find yourself using these, ask
|
||||
yourself "why can't I register these commands via an extension?"
|
||||
|
||||
```js
|
||||
// Used by the `ExtensionManager` to register new commands
|
||||
commandsManager.registerCommand('context', 'name', commandDefinition);
|
||||
|
||||
// Creates a new context; clears the context if it already exists
|
||||
commandsManager.createContext('string');
|
||||
```
|
||||
|
||||
### Contexts
|
||||
|
||||
It is up to the consuming application to define what contexts are possible, and
|
||||
which ones are currently active. As extensions depend heavily on these, we will
|
||||
likely publish guidance around creating contexts, and ways to override extension
|
||||
defined contexts in the near future. If you would like to discuss potential
|
||||
changes to how contexts work, please don't hesistate to createa new GitHub
|
||||
issue.
|
||||
|
||||
[Some additional information on Contexts can be found here.](./../index.md#contexts)
|
||||
@@ -0,0 +1,61 @@
|
||||
# Module: Panel
|
||||
|
||||
An extension can register a Panel Module by defining a `getPanelModule` method.
|
||||
The panel module provides the ability to define `menuOptions` and `components`
|
||||
that can be used by the consuming application. `components` are React Components
|
||||
that can be displayed in the consuming application's "Panel" Component.
|
||||
|
||||

|
||||
|
||||
<center><i>A panel extension example</i></center>
|
||||
|
||||
The `menuOptions`'s `target` key points to a registered `components`'s `id`. A
|
||||
`defaultContext` is applied to all `menuOption`s; however, each `menuOption` can
|
||||
optional provide it's own `context` value.
|
||||
|
||||
The `getPanelModule` receives an object containing the `ExtensionManager`'s
|
||||
associated `ServicesManager` and `CommandsManager`.
|
||||
|
||||
```js
|
||||
import MyComponent from './MyComponent.js';
|
||||
|
||||
export default {
|
||||
id: 'example-panel-module',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
*/
|
||||
getPanelModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
menuOptions: [
|
||||
{
|
||||
// A suggested icon
|
||||
// Available icons determined by consuming app
|
||||
icon: 'list',
|
||||
// A suggested label
|
||||
label: 'Magic',
|
||||
// 'right' or 'left'
|
||||
from: 'right',
|
||||
// The target component to toggle open/close
|
||||
target: 'target-component-id',
|
||||
// UI Hint; If the target panel is in a "disabled" state
|
||||
isDisabled: studies => {
|
||||
return false;
|
||||
},
|
||||
// Overrides `defaultContext`, if specified
|
||||
context: ['ACTIVE_VIEWPORT:MAGIC'],
|
||||
},
|
||||
],
|
||||
components: [
|
||||
{
|
||||
id: 'target-component-id',
|
||||
component: MyComponent,
|
||||
},
|
||||
],
|
||||
defaultContext: ['ROUTE:VIEWER'],
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,105 @@
|
||||
# Module: SOP Class Handler
|
||||
|
||||
An extension can register a [SOP Class][sop-class-link] Handler Module by
|
||||
defining a `getSopClassHandlerModule` method. The [SOP Class][sop-class-link]
|
||||
Handler is a bit different from the other modules, as it doesn't provide a `1:1`
|
||||
schema for UI or provide it's own components. It instead defines:
|
||||
|
||||
- `sopClassUIDs`: an array of string SOP Class UIDs that the
|
||||
`getDisplaySetFromSeries` method should be applied to.
|
||||
- `getDisplaySetFromSeries`: a method that maps series and study metadata to a
|
||||
display set
|
||||
|
||||
A `displaySet` has the following shape:
|
||||
|
||||
```js
|
||||
return {
|
||||
plugin: 'html',
|
||||
Modality: 'SR',
|
||||
displaySetInstanceUID: 0,
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
```
|
||||
|
||||
Where the `plugin` key is used to influence the default `ViewportComponent` for
|
||||
rendering the `displaySet`. Additional properties are passed to the
|
||||
`ViewportComponent` and used by the default `StudyBrowser` to render
|
||||
"thumbnails" for each `displaySet`
|
||||
|
||||
## Example SOP Class Handler Module
|
||||
|
||||
```js
|
||||
const SOP_CLASS_UIDS = {
|
||||
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
|
||||
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
|
||||
};
|
||||
|
||||
export default {
|
||||
id: 'example-sop-class-handler-module',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
*/
|
||||
getSopClassHandlerModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
id: 'OHIFDicomHtmlSopClassHandler',
|
||||
sopClassUIDs: Object.values(SOP_CLASS_UIDS),
|
||||
|
||||
/**
|
||||
* @param {object} series -
|
||||
* @param {object} study -
|
||||
* @param {object} dicomWebClient -
|
||||
* @param {object} authorizationHeaders -
|
||||
*/
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
return {
|
||||
plugin: 'html',
|
||||
displaySetInstanceUID: 0,
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### More examples :
|
||||
|
||||
- [Dicom-HTML SOP][dicom-html-sop]
|
||||
- [Dicom-PDF SOP][dicom-pdf-sop]
|
||||
- [Dicom-Microscopy SOP][dicom-micro-sop]
|
||||
- [Dicom-Segmentation SOP][dicom-seg-sop]
|
||||
|
||||
## `@ohif/viewer` usage
|
||||
|
||||
We use the `sopClassHandlerModule`s in three different places:
|
||||
|
||||
- `ViewerLocalFileData.js`
|
||||
- `ViewerRetrieveStudyData.js`
|
||||
- `StandaloneRouting.js`
|
||||
|
||||
Each time, it is used to map study and series data to `displaySets`. It does
|
||||
this by working alongside the `StudyMetadataManager` in `@ohif/core`. That
|
||||
manager has the method `createDisplaySets` that takes an array of
|
||||
`sopClassHandlerModules`.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[sop-class-link]: http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_B.5.html
|
||||
[dicom-html-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js#L4-L12
|
||||
[dicom-pdf-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js#L4-L6
|
||||
[dicom-micro-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js#L5-L7
|
||||
[dicom-seg-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js#L5-L7
|
||||
<!-- prettier-ignore-end -->
|
||||
@@ -0,0 +1,130 @@
|
||||
# Module: Toolbar
|
||||
|
||||
An extension can register a Toolbar Module by defining a `getToolbarModule`
|
||||
method. This module is commonly used to define:
|
||||
|
||||
- [Toolbar buttons](#button-definitions)
|
||||
- [Nested toolbar menus](#nested-toolbar-menus)
|
||||
- [Custom components](#custom-components)
|
||||
|
||||

|
||||
|
||||
<center><i>Example toolbar button using the Dialog Service to show CINE controls.</i></center>
|
||||
|
||||
## Example Toolbar Module
|
||||
|
||||
The Toolbar Module should return an array of `definitions` and a
|
||||
`defaultContext`. There are currently a few different variations of definitions,
|
||||
each one is detailed further down.
|
||||
|
||||
```js
|
||||
export default {
|
||||
id: 'example-toolbar-module',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
*/
|
||||
getToolbarModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
definitions: [
|
||||
/* Array of definitions */
|
||||
],
|
||||
defaultContext: ['ROUTE:VIEWER'],
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Button Definitions
|
||||
|
||||
The simplest definition has the following properties:
|
||||
|
||||
```js
|
||||
{
|
||||
id: 'StackScroll',
|
||||
label: 'Stack Scroll',
|
||||
icon: 'bars',
|
||||
type: 'setToolActive',
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'StackScroll' },
|
||||
},
|
||||
```
|
||||
|
||||
| property | description | values |
|
||||
| ---------------- | ----------------------------------------------------------------- | ----------------------------------------- |
|
||||
| `id` | Unique string identifier for the definition | \* |
|
||||
| `label` | User/display friendly to show in UI | \* |
|
||||
| `icon` | A string name for an icon supported by the consuming application. | \* |
|
||||
| `type` | Used to determine the button's component and behavior | `"setToolActive"`, `"command"` |
|
||||
| `commandName` | (optional) The command to run when the button is used. | Any command registed by a `CommandModule` |
|
||||
| `commandOptions` | (optional) Options to pass the target `commandName` | \* |
|
||||
| `context` | (optional) Overrides module's `defaultContext` | Array of string context names |
|
||||
|
||||
Where a button with a `type` of `setToolActive` has an "active" styling applied
|
||||
when clicked; removing the active styling from all other buttons.
|
||||
|
||||
## Nested Toolbar Menus
|
||||
|
||||
You can indicate that buttons should be grouped and nested in a submenu by
|
||||
including `buttons` property in a definition:
|
||||
|
||||
```js
|
||||
{
|
||||
id: 'More',
|
||||
label: 'More',
|
||||
icon: 'ellipse-circle',
|
||||
buttons: [
|
||||
{
|
||||
id: 'cstInvert',
|
||||
label: 'Invert',
|
||||
icon: 'circle',
|
||||
type: 'command',
|
||||
commandName: 'invertViewport',
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||

|
||||
|
||||
<center><i>Example toolbar button demonstrating nested buttons.</i></center>
|
||||
|
||||
## Custom Components
|
||||
|
||||
The Toolbar Modules supports rendering custom components in place of the
|
||||
application's default. In place of the `type`, `commandName`, and
|
||||
`commandOptions` properties, we instead specify a `CustomComponent`.
|
||||
|
||||
```js
|
||||
{
|
||||
id: 'Custom',
|
||||
label: 'Custom',
|
||||
icon: 'custom-icon',
|
||||
CustomComponent: CustomToolbarComponent,
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The `CustomComponent` components will receive the following props:
|
||||
|
||||
```html
|
||||
<CustomComponent
|
||||
parentContext="{parentContext}"
|
||||
toolbarClickCallback="{_handleToolbarButtonClick.bind(this)}"
|
||||
button="{button}"
|
||||
key="{button.id}"
|
||||
activeButtons="{activeButtonsIds}"
|
||||
isActive="{isActive}"
|
||||
/>
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------------- | -------- | ------------------------------- |
|
||||
| `activeButtons` | string[] | list of active buttons |
|
||||
| `button` | object | its own definition object |
|
||||
| `key` | string | React key prop |
|
||||
| `isActive` | boolean | If current button is active |
|
||||
| `parentContext` | ? | The parent component's context? |
|
||||
| `toolbarClickCallback` | func | Callback method for clicks |
|
||||
@@ -0,0 +1,46 @@
|
||||
# Module: Viewport
|
||||
|
||||
An extension can register a Viewport Module by defining a `getViewportModule`
|
||||
method that returns a React component. Currently, we use viewport components to
|
||||
add support for:
|
||||
|
||||
- 2D Medical Image Viewing (cornerstone ext.)
|
||||
- Structured Reports as HTML (dicom html ext.)
|
||||
- Encapsulated PDFs as PDFs (dicom pdf ext.)
|
||||
- Whole Slide Microscopy Viewing (whole slide ext.)
|
||||
- etc.
|
||||
|
||||
The general pattern is, the [`sopClassHandlerModule`](#) helps us determine
|
||||
which Viewport Component a set of `sopClassUIDs` should default to. The Viewport
|
||||
Component receives props containing a display set it should know how to render.
|
||||
|
||||
## Viewport Component Props
|
||||
|
||||
Each `ViewportComponent` will receive the following props:
|
||||
|
||||
```html
|
||||
<viewportComponent
|
||||
viewportData="{viewportData}"
|
||||
viewportIndex="{viewportIndex}"
|
||||
children="{[children]}"
|
||||
/>
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| --------------- | --------------- | --------------------------------- |
|
||||
| `children` | React.element[] | |
|
||||
| `viewportData` | object | `viewportSpecificData` (probably) |
|
||||
| `viewportIndex` | number | |
|
||||
|
||||
### `@ohif/viewer`
|
||||
|
||||
Viewport components are managed by the `ViewportGrid` Component. Which Viewport
|
||||
component is used depends on:
|
||||
|
||||
- The Layout Configuration
|
||||
- Registered SopClassHandlers
|
||||
- The SopClassUID for visible/selected datasets
|
||||
|
||||

|
||||
|
||||
<center><i>An example of three cornerstone Viewports</i></center>
|
||||
Reference in new issue
Block a user