docs: extensions (#1258)
* docs: process docs to include UX Stories requirement * docs: include note regarding different environments * Services init * Remove unused canny logos * docs: add ModalService diagram * docs: GIF of notification * docs: add ui-services page * docs: simplify ui services call out in the general services docs * docs: tips and tricks for UI services * docs: moar pages * docs: dialog gif * docs: services in summary/sidebar * docs: gif examples at top of dialog and notification pages * docs: add UIModal gif * docs: details for Dialog Service * docs: include usage information for ui modal service * docs: detailed information about our UI Notification Service * docs: services diagram * docs: remove unused links * docs: services example and image * docs: stubbing extension pages * docs: UX Stories --> User Cases * chore: eslint fix * docs: extensions overview + diagram * docs: update extensions in sidebar * docs: extension skeleton and registration info * docs: copy of maintained extensions table partial in the extensions folder * docs: swap out old extensions structure for new
This commit is contained in:
parent
fd1d29820a
commit
02572b4928
@ -3,7 +3,7 @@
|
|||||||
- [ ] Brief description of changes
|
- [ ] Brief description of changes
|
||||||
- [ ] Links to any relevant issues
|
- [ ] Links to any relevant issues
|
||||||
- [ ] Required status checks are passing
|
- [ ] Required status checks are passing
|
||||||
- [ ] UX Stories if changes impact the user's experience
|
- [ ] User cases if changes impact the user's experience
|
||||||
- [ ] `@mention` a maintainer to request a review
|
- [ ] `@mention` a maintainer to request a review
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@ -21,11 +21,18 @@
|
|||||||
- [Extensions](advanced/architecture.md#misc-extensions)
|
- [Extensions](advanced/architecture.md#misc-extensions)
|
||||||
- [Diagram](advanced/architecture.md#diagram)
|
- [Diagram](advanced/architecture.md#diagram)
|
||||||
- [Common Questions](advanced/architecture.md#common-questions)
|
- [Common Questions](advanced/architecture.md#common-questions)
|
||||||
- [Extensions](advanced/extensions.md)
|
- [Extensions](extensions/index.md)
|
||||||
- [Overview](advanced/extensions.md#overview)
|
- [Registering](extensions/index.md#registering-an-extension)
|
||||||
- [Modules](advanced/extensions.md#modules)
|
- [Lifecycle Hooks](extensions/index.md#lifecycle-hooks)
|
||||||
- [Registering](advanced/extensions.md#registering-extensions)
|
- [preRegistration](extensions/lifecycle/pre-registration.md)
|
||||||
- [OHIF Maintained](advanced/extensions.md#ohif-maintained-extensions)
|
- [Modules](extensions/index.md#modules)
|
||||||
|
- [Commands](extensions/modules/commands.md)
|
||||||
|
- [Panel](extensions/modules/panel.md)
|
||||||
|
- [SOP Class Handler](extensions/modules/sop-class-handler.md)
|
||||||
|
- [Toolbar](extensions/modules/toolbar.md)
|
||||||
|
- [Viewport](extensions/modules/viewport.md)
|
||||||
|
- [Contexts](extension/index.md#contexts)
|
||||||
|
- [OHIF Maintained](extension/index.md#maintained-extensions)
|
||||||
- [Services](services/index.md)
|
- [Services](services/index.md)
|
||||||
- [Default](services/default/index.md)
|
- [Default](services/default/index.md)
|
||||||
- [UI](services/ui/index.md)
|
- [UI](services/ui/index.md)
|
||||||
|
|||||||
@ -94,16 +94,16 @@ the viewer. Some common use cases include:
|
|||||||
- Adding surgical templating tools and reports
|
- Adding surgical templating tools and reports
|
||||||
- and many others
|
- and many others
|
||||||
|
|
||||||
We expose common integration points via [extensions](./extensions.md) to make
|
We expose common integration points via [extensions](../extensions/index.md) to
|
||||||
this possible. The viewer and many of our own extensions also offer
|
make this possible. The viewer and many of our own extensions also offer
|
||||||
[configuration][configuration]. For a list of extensions maintained by OHIF,
|
[configuration][configuration]. For a list of extensions maintained by OHIF,
|
||||||
[check out this helpful table](./extensions.html#ohif-maintained-extensions).
|
[check out this helpful table](../extensions/index.mdl#maintained-extensions).
|
||||||
|
|
||||||
If you find yourself thinking "I wish the Viewer could do X", and you can't
|
If you find yourself thinking "I wish the Viewer could do X", and you can't
|
||||||
accomplish it with an extension today, create a GitHub issue! We're actively
|
accomplish it with an extension today, create a GitHub issue! We're actively
|
||||||
looking for ways to improve our extensibility ^\_^
|
looking for ways to improve our extensibility ^\_^
|
||||||
|
|
||||||
[Click here to read more about extensions!](./extensions.md)
|
[Click here to read more about extensions!](../extensions/index.md)
|
||||||
|
|
||||||
### Diagram
|
### Diagram
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ you would need to recreate.
|
|||||||
[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer
|
[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer
|
||||||
[pwa]: https://developers.google.com/web/progressive-web-apps/
|
[pwa]: https://developers.google.com/web/progressive-web-apps/
|
||||||
[configuration]: ../essentials/configuration.md
|
[configuration]: ../essentials/configuration.md
|
||||||
[extensions]: ./extensions.md
|
[extensions]: ../extensions/index.md
|
||||||
[core-github]: https://github.com/OHIF/viewers/platform/core
|
[core-github]: https://github.com/OHIF/viewers/platform/core
|
||||||
[ui-github]: https://github.com/OHIF/Viewers/platform/ui
|
[ui-github]: https://github.com/OHIF/Viewers/platform/ui
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
|||||||
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
This is not yet exposed in an easy/convenient way. Most tools are currently
|
This is not yet exposed in an easy/convenient way. Most tools are currently
|
||||||
added by creating new Viewport, Toolbar, and SOPInstanceHandler extension
|
added by creating new Viewport, Toolbar, and SOPInstanceHandler extension
|
||||||
modules. You can read more about that approach in [extensions](./extensions.md).
|
modules. You can read more about that approach in
|
||||||
|
[extensions](../extensions/index.md).
|
||||||
|
|
||||||
In the near future, we intend to improve the extensibility of tools for existing
|
In the near future, we intend to improve the extensibility of tools for existing
|
||||||
Viewports (like our Cornerstone.js and VTK.js viewports).
|
Viewports (like our Cornerstone.js and VTK.js viewports).
|
||||||
|
|||||||
@ -1,263 +0,0 @@
|
|||||||
# Extensions
|
|
||||||
|
|
||||||
Extensions add new functionality to the viewer by registering one or more
|
|
||||||
modules. They go one step further than configuration in that they allow us to
|
|
||||||
inject custom React components, so long as they adhere to the module's
|
|
||||||
interface. This can be something as simple as adding a new button to the
|
|
||||||
toolbar, or as complex as a new viewport capable of rendering volumes in 3D.
|
|
||||||
|
|
||||||
- [Overview](#overview)
|
|
||||||
- [Modules](#modules)
|
|
||||||
- [Commands](#commands)
|
|
||||||
- [Hotkeys](#hotkeys)
|
|
||||||
- [Toolbar](#toolbar)
|
|
||||||
- [Panel](#panel)
|
|
||||||
- [Viewport](#viewport)
|
|
||||||
- [SOP Class Handler](#sopclasshandler)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
At a glance, an extension is a javascript object that has an `id` property, and
|
|
||||||
one or more "module" methods. You can find an abbreviated extension below, or
|
|
||||||
[view the source][example-ext-src] of our example extension.
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* Only required property. Should be a unique value across all extensions.
|
|
||||||
*/
|
|
||||||
id: 'example-extension',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers one or more named commands scoped to a context. Commands are
|
|
||||||
* the primary means for...
|
|
||||||
*/
|
|
||||||
getCommandsModule() {
|
|
||||||
return {
|
|
||||||
defaultContext: 'VIEWER'
|
|
||||||
actions: { ... },
|
|
||||||
definitions: { ... }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows you to provide toolbar definitions that will be merged with any
|
|
||||||
* existing application toolbar configuration. Used to determine which
|
|
||||||
* buttons should be visible when, their order, what happens when they're
|
|
||||||
* clicked, etc.
|
|
||||||
*/
|
|
||||||
getToolbarModule() {
|
|
||||||
return {
|
|
||||||
definitions: [ ... ],
|
|
||||||
defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not yet implemented
|
|
||||||
*/
|
|
||||||
getPanelModule: () => null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a ReactComponent that should be used to render data in a
|
|
||||||
* Viewport. The first registered viewport is our "default viewport". If
|
|
||||||
* more than one viewport is registered, we use `SopClassHandlers` to
|
|
||||||
* determine which viewport should be used.
|
|
||||||
*/
|
|
||||||
getViewportModule: () => reactViewportComponent,
|
|
||||||
|
|
||||||
/** Provides a whitelist of SOPClassUIDs the viewport is capable of rendering.
|
|
||||||
* Can modify default behavior for methods like `getDisplaySetFromSeries` */
|
|
||||||
getSopClassHandler: () => {
|
|
||||||
id: 'some-other-unique-id',
|
|
||||||
sopClassUids: [ ... ],
|
|
||||||
getDisplaySetFromSeries: (series, study, dicomWebClient, authorizationHeaders) => { ... }
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Modules
|
|
||||||
|
|
||||||
There are a few different module types. Each module type allows us to extend the
|
|
||||||
viewer in a different way, and provides a consistent API for us to do so. You
|
|
||||||
can find a full list of the different types of modules
|
|
||||||
[`in ohif-core`][module-types]. Information on each type of module, it's API,
|
|
||||||
and how we determine when/where it should be used is included below.
|
|
||||||
|
|
||||||
> NOTE: Modifying the extensions/modules registered to the OHIF Viewer currently
|
|
||||||
> requires us to import and pass extensions to the ExtensionManager in
|
|
||||||
> `src/App.js`, then rebuild the application. Long-term, we intend to make it
|
|
||||||
> possible to accomplish this without a build step.
|
|
||||||
|
|
||||||
#### Commands
|
|
||||||
|
|
||||||
The Commands Module allows us to register one or more commands scoped to
|
|
||||||
specific contexts. Commands can be run by [hotkeys][#], [toolbar buttons][#],
|
|
||||||
and any registered custom react component (like a [viewport][#] or [panel][#]).
|
|
||||||
Here is a simple example commands module:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
getCommandsModule() {
|
|
||||||
return {
|
|
||||||
actions: {
|
|
||||||
speak: ({ viewports, words }) => {
|
|
||||||
console.log(viewports, words);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
definitions: {
|
|
||||||
rotateViewportCW: {
|
|
||||||
commandFn: actions.rotateViewport,
|
|
||||||
storeContexts: ['viewports'],
|
|
||||||
options: { rotation: 90 }
|
|
||||||
},
|
|
||||||
rotateViewportCCW: {
|
|
||||||
commandFn: actions.rotateViewport,
|
|
||||||
storeContexts: ['viewports'],
|
|
||||||
options: { rotation: -90 },
|
|
||||||
context: 'ACTIVE_VIEWER::CORNERSTONE'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultContext: 'VIEWER'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Viewport
|
|
||||||
|
|
||||||
An extension can register a Viewport Module by providing a `getViewportModule()`
|
|
||||||
method that returns a React Component. The React component will receive the
|
|
||||||
following props:
|
|
||||||
|
|
||||||
```js
|
|
||||||
children: PropTypes.arrayOf(PropTypes.element)
|
|
||||||
studies: PropTypes.object,
|
|
||||||
displaySet: PropTypes.object,
|
|
||||||
viewportData: PropTypes.object, // { studies, displaySet }
|
|
||||||
viewportIndex: PropTypes.number,
|
|
||||||
children: PropTypes.node,
|
|
||||||
customProps: PropTypes.object
|
|
||||||
```
|
|
||||||
|
|
||||||
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 Viewports</i></center>
|
|
||||||
|
|
||||||
For a complete example implementation,
|
|
||||||
[check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/OHIFCornerstoneViewport.js).
|
|
||||||
|
|
||||||
#### Toolbar
|
|
||||||
|
|
||||||
An extension can register a Toolbar Module by providing a `getToolbarModule()`
|
|
||||||
method that returns a React Component. The component does not receive any props.
|
|
||||||
If you want to modify or react to state, you will need to connect to the redux
|
|
||||||
store. The given toolbar must determine its set of elements and the context of
|
|
||||||
them. The set of elements will be listed on toolbar `definitions`.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<center><i>A toolbar extension example</i></center>
|
|
||||||
|
|
||||||
Toolbar components are rendered in the `ToolbarRow` component.
|
|
||||||
|
|
||||||
For a complete example implementation,
|
|
||||||
[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/toolbarModule.js).
|
|
||||||
|
|
||||||
##### Toolbar Custom Component
|
|
||||||
|
|
||||||
Toolbar elements can define its own custom react component to be consumed when
|
|
||||||
rendering it. So far, it accepts `Functional` and `Class` Components. For that,
|
|
||||||
you just need to expose your `CustomToolbarComponent` as the value of key
|
|
||||||
`CustomComponent`. In case the property `CustomComponent` is not present, a
|
|
||||||
default toolbar component will be used to render it. See bellow
|
|
||||||
|
|
||||||
```js
|
|
||||||
definitions: [
|
|
||||||
...
|
|
||||||
{
|
|
||||||
id: 'Custom',
|
|
||||||
label: 'Custom',
|
|
||||||
icon: 'custom-icon',
|
|
||||||
CustomComponent: CustomToolbarComponent,
|
|
||||||
}
|
|
||||||
...
|
|
||||||
]
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
`CustomComponent` components will receive the following props:
|
|
||||||
|
|
||||||
- parentContext: parent context. (In most of the cases it will be a ToolbarRow
|
|
||||||
instance)
|
|
||||||
- toolbarClickCallback: callback method when clicking on toolbar
|
|
||||||
- button: its own definition object
|
|
||||||
- key: react key prop
|
|
||||||
- activeButtons: list of active elements
|
|
||||||
- isActive: if current
|
|
||||||
|
|
||||||
#### SopClassHandler
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
#### Panel
|
|
||||||
|
|
||||||
> The panel module is not yet in use.
|
|
||||||
|
|
||||||
#### Hotkeys
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
### Registering Extensions
|
|
||||||
|
|
||||||
Extensions are registered for the application at startup. The
|
|
||||||
`ExtensionManager`, exposed by `ohif-core`, registers a list of extensions with
|
|
||||||
our application's store. Each module provided by the extension becomes available
|
|
||||||
via `state.plugins.availablePlugins`, and consists of three parts: id, type
|
|
||||||
([PLUGIN_TYPE](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6)),
|
|
||||||
and the return value of the module method.
|
|
||||||
|
|
||||||
In a future version, we will likely expose a way to provide the extensions you
|
|
||||||
would like included at startup.
|
|
||||||
|
|
||||||
_app.js_
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { createStore, combineReducers } from 'redux';
|
|
||||||
import OHIF from '@ohif/core';
|
|
||||||
import OHIFCornerstoneExtension from 'ohif-cornerstone-extension';
|
|
||||||
|
|
||||||
const combined = combineReducers(OHIF.redux.reducers);
|
|
||||||
const store = createStore(combined);
|
|
||||||
const extensions = [new OHIFCornerstoneExtension()];
|
|
||||||
|
|
||||||
// Dispatches the `addPlugin` action to the store
|
|
||||||
// Adding extension modules to `state.plugins.availablePlugins`
|
|
||||||
ExtensionManager.registerExtensions(store, extensions);
|
|
||||||
```
|
|
||||||
|
|
||||||
## OHIF Maintained Extensions
|
|
||||||
|
|
||||||
A small number of powerful extensions for popular use cases are maintained by
|
|
||||||
OHIF. They're co-located in the
|
|
||||||
[`OHIF/Viewers`](https://github.com/OHIF/Viewers) repository, in the top level
|
|
||||||
[`extensions/`](https://github.com/OHIF/Viewers/tree/master/extensions)
|
|
||||||
directory.
|
|
||||||
|
|
||||||
{% include "./_maintained-extensions-table.md" %}
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Links
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
[example-ext-src]: https://github.com/OHIF/Viewers/tree/master/extensions/_example/src
|
|
||||||
[module-types]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/extensions/MODULE_TYPES.js
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
BIN
docs/latest/assets/img/extensions-diagram.png
Normal file
BIN
docs/latest/assets/img/extensions-diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@ -35,8 +35,7 @@ benefits, but comes at the cost of time and complexity. Some benefits include:
|
|||||||
|
|
||||||
_Today:_
|
_Today:_
|
||||||
|
|
||||||
- Leverage [extensions](/advanced/extensions.md) to drop-in powerful new
|
- Leverage [extensions](/extensions/index.md) to drop-in powerful new features
|
||||||
features
|
|
||||||
- Add routes and customize the viewer's workflow
|
- Add routes and customize the viewer's workflow
|
||||||
- Finer control over styling and whitelabeling
|
- Finer control over styling and whitelabeling
|
||||||
|
|
||||||
|
|||||||
@ -98,7 +98,7 @@ extension enabled here][whole-slide-ext-demo] ([source code][ext-code-sandbox])
|
|||||||
and [without it here][whole-slide-base-demo] ([source code][code-sandbox]).
|
and [without it here][whole-slide-base-demo] ([source code][code-sandbox]).
|
||||||
|
|
||||||
You can read more about extensions and how to create your own in our
|
You can read more about extensions and how to create your own in our
|
||||||
[extensions guide](/advanced/extensions.md).
|
[extensions guide](/extensions/index.md).
|
||||||
|
|
||||||
#### FAQ
|
#### FAQ
|
||||||
|
|
||||||
|
|||||||
62
docs/latest/extensions/_maintained-extensions-table.md
Normal file
62
docs/latest/extensions/_maintained-extensions-table.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Extension</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Modules</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- CORNERSTONE.js -->
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.npmjs.com/package/@ohif/extension-cornerstone">
|
||||||
|
Cornerstone
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
A viewport powered by <code>cornerstone.js</code>. Adds support for 2D DICOM rendering and manipulation, as well as support for the tools features in <a href="https://tools.cornerstonejs.org/examples/"><code>cornerstone-tools</code></a>. Also adds "CINE Dialog" to the Toolbar.
|
||||||
|
</td>
|
||||||
|
<td>Viewport, Toolbar</td>
|
||||||
|
</tr>
|
||||||
|
<!-- VTK.js -->
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.npmjs.com/package/@ohif/extension-vtk">
|
||||||
|
VTK.js
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
A viewport powered by <code>vtk.js</code>. Adds support for volume renderings and advanced features like MPR. Also adds "3D Rotate" to the Toolbar.
|
||||||
|
</td>
|
||||||
|
<td>Viewport, Toolbar</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-html">DICOM HTML</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
Renders text and HTML content for <a href="https://github.com/OHIF/Viewers/blob/master/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js#L4-L12">specific SopClassUIDs</a>.
|
||||||
|
</td>
|
||||||
|
<td>Viewport, SopClassHandler</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-pdf">DICOM PDF</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
Renders PDFs for a <a href="https://github.com/OHIF/Viewers/blob/master/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js#L4-L6">specific SopClassUID</a>.
|
||||||
|
</td>
|
||||||
|
<td>Viewport, SopClassHandler</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-microscopy">DICOM Microscopy</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
Renders Microscopy images for a <a href="https://github.com/OHIF/Viewers/blob/master/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js#L5-L7">specific SopClassUID</a>.
|
||||||
|
</td>
|
||||||
|
<td>Viewport, SopClassHandler</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
151
docs/latest/extensions/index.md
Normal file
151
docs/latest/extensions/index.md
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# Extensions
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Concepts](#concepts)
|
||||||
|
- [Extension Skeleton](#extension-skeleton)
|
||||||
|
- [Registering an Extension](#registering-an-extension)
|
||||||
|
- [Lifecylce Hooks](#lifecycle-hooks)
|
||||||
|
- [Modules](#modules)
|
||||||
|
- [Contexts](#contexts)
|
||||||
|
- [Consuming Extensions](#consuming-extensions)
|
||||||
|
- [Maintained Extensions](#maintained-extensions)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
We use extensions to help us isolate and package groups of related features.
|
||||||
|
Extensions provide functionality, ui components, and new behaviors.
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<a href="/assets/img/extensions-diagram.png">
|
||||||
|
<img src="/assets/img/extensions-diagram.png" alt="Extensions Diagram" style="margin: 0 auto; max-width: 500px;" />
|
||||||
|
</a>
|
||||||
|
<div><i>Diagram showing how extensions are configured and accessed.</i></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
The `@ohif/viewer`'s application level configuration gives us the ability to add
|
||||||
|
and configure extensions. When the application starts, extensions are registered
|
||||||
|
with the `ExtensionManager`. Different portions of the `@ohif/viewer` project
|
||||||
|
will use registered extensions to influence application behavior.
|
||||||
|
|
||||||
|
Extensions allow us to:
|
||||||
|
|
||||||
|
- Wrap and integrate functionality of 3rd party dependencies in a reusable way
|
||||||
|
- Change how application data is mapped and transformed
|
||||||
|
- Display a consistent/cohesive UI
|
||||||
|
- Inject custom components to override built-in components
|
||||||
|
|
||||||
|
Practical examples of extensions include:
|
||||||
|
|
||||||
|
- A set of segmentation tools that build on top of the `cornerstone` viewport
|
||||||
|
- Showing ML/AI report summaries for the selected study/series/image
|
||||||
|
- Support for parsing DICOM structured reports and displaying them in a user
|
||||||
|
friendly way
|
||||||
|
- [See our maintained extensions for more examples of what's possible](#maintained-extensions)
|
||||||
|
|
||||||
|
## Concepts
|
||||||
|
|
||||||
|
### Extension Skeleton
|
||||||
|
|
||||||
|
An extension is a plain JavaScript object has an `id` property, and one or more
|
||||||
|
"getModuleFunctions" and/or lifecycle hooks. You can read more about
|
||||||
|
[lifecycle hooks](#lifecycle-hooks) and [modules](#modules) further down.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// prettier-ignore
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* Only required property. Should be a unique value across all extensions.
|
||||||
|
*/
|
||||||
|
id: 'example-extension',
|
||||||
|
|
||||||
|
// Lifecyle
|
||||||
|
preRegistration() { /* */ },
|
||||||
|
// Modules
|
||||||
|
getCommandsModule() { /* */ },
|
||||||
|
getToolbarModule() { /* */ },
|
||||||
|
getPanelModule() { /* */ },
|
||||||
|
getSopClassHandler() { /* */ },
|
||||||
|
getViewportModule() { /* */ },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registering an Extension
|
||||||
|
|
||||||
|
There are two different ways to register and configure extensions. You can
|
||||||
|
leverage one or both strategies. Which one(s) you choose depend on your
|
||||||
|
application's requirements. Each [module](#modules) defined by the extension
|
||||||
|
becomes available to the core application via the `ExtensionManager`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// prettier-ignore
|
||||||
|
const config = {
|
||||||
|
extensions: [
|
||||||
|
MyFirstExtension,
|
||||||
|
[
|
||||||
|
MySecondExtension,
|
||||||
|
{ /* MySecondExtensions Configuration */ },
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Runtime Extensions
|
||||||
|
|
||||||
|
The `@ohif/viewer` uses a [configuration file](#) at startup. The schema for
|
||||||
|
that file includes an `Extensions` key that supports an array of extensions to
|
||||||
|
register.
|
||||||
|
|
||||||
|
#### Bundled Extensions
|
||||||
|
|
||||||
|
The `@ohif/viewer` works best when built as a "Progressive Web Application"
|
||||||
|
(PWA). If you know the extensions your application will need, you can specify
|
||||||
|
them at "build time" to leverage some advantaged afforded to us by modern
|
||||||
|
tooling:
|
||||||
|
|
||||||
|
- Code Splitting
|
||||||
|
- Tree Shaking
|
||||||
|
- Dependency deduplication
|
||||||
|
|
||||||
|
You can update the list of bundled extensions by:
|
||||||
|
|
||||||
|
1. Having your `@ohif/viewer` project depend on the extension
|
||||||
|
2. Importing and adding it to the list of extensions in the
|
||||||
|
`<repo-root>/platform/src/index.js` entrypoint.
|
||||||
|
|
||||||
|
### Lifecycle Hooks
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
### Modules
|
||||||
|
|
||||||
|
There are a few different module types. Each module type allows us to extend the
|
||||||
|
viewer in a different way, and provides a consistent API for us to do so. You
|
||||||
|
can find a full list of the different types of modules
|
||||||
|
[`in ohif-core`][module-types]. Information on each type of module, it's API,
|
||||||
|
and how we determine when/where it should be used is included below.
|
||||||
|
|
||||||
|
### Contexts
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## Consuming Extensions
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## Maintained Extensions
|
||||||
|
|
||||||
|
A small number of powerful extensions for popular use cases are maintained by
|
||||||
|
OHIF. They're co-located in the [`OHIF/Viewers`][viewers-repo] repository, in
|
||||||
|
the top level [`extensions/`][ext-source] directory.
|
||||||
|
|
||||||
|
{% include "./_maintained-extensions-table.md" %}
|
||||||
|
|
||||||
|
<!--
|
||||||
|
LINKS
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
[viewers-repo]: https://github.com/OHIF/Viewers
|
||||||
|
[ext-source]: https://github.com/OHIF/Viewers/tree/master/extensions
|
||||||
|
[module-types]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/extensions/MODULE_TYPES.js
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
21
docs/latest/extensions/lifecycle/pre-registration.md
Normal file
21
docs/latest/extensions/lifecycle/pre-registration.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Lifecylce Hook: preRegistration
|
||||||
|
|
||||||
|
If an extension defines the `preRegistration` lifecycle hook, it is called
|
||||||
|
before any modules are registered to the `ExtensionManager`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
id: 'MyExampleExtension',
|
||||||
|
|
||||||
|
preRegistration({ servicesManager, commandsManager, configuration }) {
|
||||||
|
console.log('Wiring up important stuff.');
|
||||||
|
|
||||||
|
window.importantStuff = () => {
|
||||||
|
console.log(configuration);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Important stuff has been wired.');
|
||||||
|
window.importantStuff();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
34
docs/latest/extensions/modules/commands.md
Normal file
34
docs/latest/extensions/modules/commands.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Module: Commands
|
||||||
|
|
||||||
|
The Commands Module allows us to register one or more commands scoped to
|
||||||
|
specific contexts. Commands can be run by [hotkeys][#], [toolbar buttons][#],
|
||||||
|
and any registered custom react component (like a [viewport][#] or [panel][#]).
|
||||||
|
Here is a simple example commands module:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
getCommandsModule() {
|
||||||
|
return {
|
||||||
|
actions: {
|
||||||
|
speak: ({ viewports, words }) => {
|
||||||
|
console.log(viewports, words);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
definitions: {
|
||||||
|
rotateViewportCW: {
|
||||||
|
commandFn: actions.rotateViewport,
|
||||||
|
storeContexts: ['viewports'],
|
||||||
|
options: { rotation: 90 }
|
||||||
|
},
|
||||||
|
rotateViewportCCW: {
|
||||||
|
commandFn: actions.rotateViewport,
|
||||||
|
storeContexts: ['viewports'],
|
||||||
|
options: { rotation: -90 },
|
||||||
|
context: 'ACTIVE_VIEWER::CORNERSTONE'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultContext: 'VIEWER'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
3
docs/latest/extensions/modules/panel.md
Normal file
3
docs/latest/extensions/modules/panel.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Module: Panel
|
||||||
|
|
||||||
|
...
|
||||||
3
docs/latest/extensions/modules/sop-class-handler.md
Normal file
3
docs/latest/extensions/modules/sop-class-handler.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Module: SOP Class Handler
|
||||||
|
|
||||||
|
...
|
||||||
48
docs/latest/extensions/modules/toolbar.md
Normal file
48
docs/latest/extensions/modules/toolbar.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Module: Toolbar
|
||||||
|
|
||||||
|
An extension can register a Toolbar Module by providing a `getToolbarModule()`
|
||||||
|
method that returns a React Component. The component does not receive any props.
|
||||||
|
If you want to modify or react to state, you will need to connect to the redux
|
||||||
|
store. The given toolbar must determine its set of elements and the context of
|
||||||
|
them. The set of elements will be listed on toolbar `definitions`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<center><i>A toolbar extension example</i></center>
|
||||||
|
|
||||||
|
Toolbar components are rendered in the `ToolbarRow` component.
|
||||||
|
|
||||||
|
For a complete example implementation,
|
||||||
|
[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/toolbarModule.js).
|
||||||
|
|
||||||
|
## Toolbar Custom Component
|
||||||
|
|
||||||
|
Toolbar elements can define its own custom react component to be consumed when
|
||||||
|
rendering it. So far, it accepts `Functional` and `Class` Components. For that,
|
||||||
|
you just need to expose your `CustomToolbarComponent` as the value of key
|
||||||
|
`CustomComponent`. In case the property `CustomComponent` is not present, a
|
||||||
|
default toolbar component will be used to render it. See bellow
|
||||||
|
|
||||||
|
```js
|
||||||
|
definitions: [
|
||||||
|
...
|
||||||
|
{
|
||||||
|
id: 'Custom',
|
||||||
|
label: 'Custom',
|
||||||
|
icon: 'custom-icon',
|
||||||
|
CustomComponent: CustomToolbarComponent,
|
||||||
|
}
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
`CustomComponent` components will receive the following props:
|
||||||
|
|
||||||
|
- parentContext: parent context. (In most of the cases it will be a ToolbarRow
|
||||||
|
instance)
|
||||||
|
- toolbarClickCallback: callback method when clicking on toolbar
|
||||||
|
- button: its own definition object
|
||||||
|
- key: react key prop
|
||||||
|
- activeButtons: list of active elements
|
||||||
|
- isActive: if current
|
||||||
29
docs/latest/extensions/modules/viewport.md
Normal file
29
docs/latest/extensions/modules/viewport.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Module: Viewport
|
||||||
|
|
||||||
|
An extension can register a Viewport Module by providing a `getViewportModule()`
|
||||||
|
method that returns a React Component. The React component will receive the
|
||||||
|
following props:
|
||||||
|
|
||||||
|
```js
|
||||||
|
children: PropTypes.arrayOf(PropTypes.element)
|
||||||
|
studies: PropTypes.object,
|
||||||
|
displaySet: PropTypes.object,
|
||||||
|
viewportData: PropTypes.object, // { studies, displaySet }
|
||||||
|
viewportIndex: PropTypes.number,
|
||||||
|
children: PropTypes.node,
|
||||||
|
customProps: PropTypes.object
|
||||||
|
```
|
||||||
|
|
||||||
|
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 Viewports</i></center>
|
||||||
|
|
||||||
|
For a complete example implementation,
|
||||||
|
[check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/OHIFCornerstoneViewport.js).
|
||||||
3
docs/latest/hotkeys/index.md
Normal file
3
docs/latest/hotkeys/index.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Hotkeys
|
||||||
|
|
||||||
|
...
|
||||||
@ -95,11 +95,11 @@ appropriate:
|
|||||||
| [PR: Awaiting Review 👀][awaiting-review] | The core team has not yet performed a code review. |
|
| [PR: Awaiting Review 👀][awaiting-review] | The core team has not yet performed a code review. |
|
||||||
| [PR: Awaiting Revisions 🖊][awaiting-revisions] | Following code review, this label is applied until the author has made sufficient changes. |
|
| [PR: Awaiting Revisions 🖊][awaiting-revisions] | Following code review, this label is applied until the author has made sufficient changes. |
|
||||||
| **QA** | |
|
| **QA** | |
|
||||||
| [PR: Awaiting UX Stories 💃][awaiting-stories] | The PR code changes need common language descriptions of impact to end users before the review can start |
|
| [PR: Awaiting User Cases 💃][awaiting-stories] | The PR code changes need common language descriptions of impact to end users before the review can start |
|
||||||
| [PR: No UX Impact 🙃][no-ux-impact] | The PR code changes do not impact the user's experience |
|
| [PR: No UX Impact 🙃][no-ux-impact] | The PR code changes do not impact the user's experience |
|
||||||
|
|
||||||
We rely on GitHub Checks and integrations with third party services to evaluate
|
We rely on GitHub Checks and integrations with third party services to evaluate
|
||||||
changes in code quality and test coverage. Tests must pass and UX stories must
|
changes in code quality and test coverage. Tests must pass and User cases must
|
||||||
be present (when applicable) before a PR can be merged to master, and code
|
be present (when applicable) before a PR can be merged to master, and code
|
||||||
quality and test coverage must not changed by a significant margin. For some
|
quality and test coverage must not changed by a significant margin. For some
|
||||||
repositories, visual screenshot-based tests are also included, and video
|
repositories, visual screenshot-based tests are also included, and video
|
||||||
|
|||||||
@ -33,7 +33,7 @@ The `ServicesManager` is:
|
|||||||
|
|
||||||
- Passed to the `ExtensionManager`
|
- Passed to the `ExtensionManager`
|
||||||
- The `ExtensionManager` makes the `ServicesManager` available to:
|
- The `ExtensionManager` makes the `ServicesManager` available to:
|
||||||
- All of it's lifecycle hooks (`preInit`)
|
- All of it's lifecycle hooks (`preRegistration`)
|
||||||
- Each "getModuleFunction" (`getToolbarModule`, `getPanelModule`, etc.)
|
- Each "getModuleFunction" (`getToolbarModule`, `getPanelModule`, etc.)
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
* @property {ReactElement|HTMLElement} content The dialog content.
|
* @property {ReactElement|HTMLElement} content The dialog content.
|
||||||
* @property {Object} contentProps The dialog content props.
|
* @property {Object} contentProps The dialog content props.
|
||||||
* @property {boolean} [isDraggable=true] Controls if dialog content is draggable or not.
|
* @property {boolean} [isDraggable=true] Controls if dialog content is draggable or not.
|
||||||
* @property {boolean} [showOverlay=false] Controls dialog overlay.
|
* @property {boolean} [showOverlay=false] Controls dialog overlay.
|
||||||
* @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at.
|
* @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at.
|
||||||
* @property {ElementPosition} position If this property is present, the item becomes 'controlled' and is not responsive to user input.
|
* @property {ElementPosition} position If this property is present, the item becomes 'controlled' and is not responsive to user input.
|
||||||
* @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.
|
* @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user