diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index 7ddb1e172..4a5623153 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -3,7 +3,7 @@ - [ ] Brief description of changes - [ ] Links to any relevant issues - [ ] 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 diff --git a/docs/latest/advanced/custom-tools.md b/docs/latest/advanced/custom-tools.md index bed6c344f..5afbfb2d5 100644 --- a/docs/latest/advanced/custom-tools.md +++ b/docs/latest/advanced/custom-tools.md @@ -2,7 +2,8 @@ This is not yet exposed in an easy/convenient way. Most tools are currently 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 Viewports (like our Cornerstone.js and VTK.js viewports). diff --git a/docs/latest/advanced/extensions.md b/docs/latest/advanced/extensions.md deleted file mode 100644 index e83ffad76..000000000 --- a/docs/latest/advanced/extensions.md +++ /dev/null @@ -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 - -![Cornerstone Viewport](../assets/img/extensions-viewport.png) - -
An example of three Viewports
- -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`. - -![Toolbar Extension](../assets/img/extensions-toolbar.gif) - -
A toolbar extension example
- -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" %} - - - - -[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 - diff --git a/docs/latest/assets/img/extensions-diagram.png b/docs/latest/assets/img/extensions-diagram.png new file mode 100644 index 000000000..c71be8692 Binary files /dev/null and b/docs/latest/assets/img/extensions-diagram.png differ diff --git a/docs/latest/deployment/index.md b/docs/latest/deployment/index.md index 0bad5e159..c4b1cc88c 100644 --- a/docs/latest/deployment/index.md +++ b/docs/latest/deployment/index.md @@ -35,8 +35,7 @@ benefits, but comes at the cost of time and complexity. Some benefits include: _Today:_ -- Leverage [extensions](/advanced/extensions.md) to drop-in powerful new - features +- Leverage [extensions](/extensions/index.md) to drop-in powerful new features - Add routes and customize the viewer's workflow - Finer control over styling and whitelabeling diff --git a/docs/latest/deployment/recipes/embedded-viewer.md b/docs/latest/deployment/recipes/embedded-viewer.md index f6a9e57de..3a299f9ea 100644 --- a/docs/latest/deployment/recipes/embedded-viewer.md +++ b/docs/latest/deployment/recipes/embedded-viewer.md @@ -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]). 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 diff --git a/docs/latest/extensions/_maintained-extensions-table.md b/docs/latest/extensions/_maintained-extensions-table.md new file mode 100644 index 000000000..43a558e46 --- /dev/null +++ b/docs/latest/extensions/_maintained-extensions-table.md @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionDescriptionModules
+ + Cornerstone + + + A viewport powered by cornerstone.js. Adds support for 2D DICOM rendering and manipulation, as well as support for the tools features in cornerstone-tools. Also adds "CINE Dialog" to the Toolbar. + Viewport, Toolbar
+ + VTK.js + + + A viewport powered by vtk.js. Adds support for volume renderings and advanced features like MPR. Also adds "3D Rotate" to the Toolbar. + Viewport, Toolbar
+ DICOM HTML + + Renders text and HTML content for specific SopClassUIDs. + Viewport, SopClassHandler
+ DICOM PDF + + Renders PDFs for a specific SopClassUID. + Viewport, SopClassHandler
+ DICOM Microscopy + + Renders Microscopy images for a specific SopClassUID. + Viewport, SopClassHandler
diff --git a/docs/latest/extensions/index.md b/docs/latest/extensions/index.md new file mode 100644 index 000000000..394f436c2 --- /dev/null +++ b/docs/latest/extensions/index.md @@ -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. + +
+ + Extensions Diagram + +
Diagram showing how extensions are configured and accessed.
+
+ +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 + `/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" %} + + + + +[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 + diff --git a/docs/latest/extensions/lifecycle/pre-registration.md b/docs/latest/extensions/lifecycle/pre-registration.md new file mode 100644 index 000000000..a4cef9957 --- /dev/null +++ b/docs/latest/extensions/lifecycle/pre-registration.md @@ -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(); + }, +}; +``` diff --git a/docs/latest/extensions/modules/commands.md b/docs/latest/extensions/modules/commands.md new file mode 100644 index 000000000..3bf68c2ba --- /dev/null +++ b/docs/latest/extensions/modules/commands.md @@ -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' + } + } +} +``` diff --git a/docs/latest/extensions/modules/panel.md b/docs/latest/extensions/modules/panel.md new file mode 100644 index 000000000..4604d4154 --- /dev/null +++ b/docs/latest/extensions/modules/panel.md @@ -0,0 +1,3 @@ +# Module: Panel + +... diff --git a/docs/latest/extensions/modules/sop-class-handler.md b/docs/latest/extensions/modules/sop-class-handler.md new file mode 100644 index 000000000..4a7e81699 --- /dev/null +++ b/docs/latest/extensions/modules/sop-class-handler.md @@ -0,0 +1,3 @@ +# Module: SOP Class Handler + +... diff --git a/docs/latest/extensions/modules/toolbar.md b/docs/latest/extensions/modules/toolbar.md new file mode 100644 index 000000000..92508cc76 --- /dev/null +++ b/docs/latest/extensions/modules/toolbar.md @@ -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`. + +![Toolbar Extension](../assets/img/extensions-toolbar.gif) + +
A toolbar extension example
+ +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 diff --git a/docs/latest/extensions/modules/viewport.md b/docs/latest/extensions/modules/viewport.md new file mode 100644 index 000000000..19b27b16c --- /dev/null +++ b/docs/latest/extensions/modules/viewport.md @@ -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 + +![Cornerstone Viewport](../../assets/img/extensions-viewport.png) + +
An example of three Viewports
+ +For a complete example implementation, +[check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/OHIFCornerstoneViewport.js). diff --git a/docs/latest/hotkeys/index.md b/docs/latest/hotkeys/index.md new file mode 100644 index 000000000..95a90d7d6 --- /dev/null +++ b/docs/latest/hotkeys/index.md @@ -0,0 +1,3 @@ +# Hotkeys + +... diff --git a/docs/latest/our-process.md b/docs/latest/our-process.md index 41dd4bbbd..2f16d1f63 100644 --- a/docs/latest/our-process.md +++ b/docs/latest/our-process.md @@ -95,11 +95,11 @@ appropriate: | [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. | | **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 | 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 quality and test coverage must not changed by a significant margin. For some repositories, visual screenshot-based tests are also included, and video diff --git a/docs/latest/services/ui/index.md b/docs/latest/services/ui/index.md index 2f24c5258..708c26ab0 100644 --- a/docs/latest/services/ui/index.md +++ b/docs/latest/services/ui/index.md @@ -33,7 +33,7 @@ The `ServicesManager` is: - Passed to the `ExtensionManager` - 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.) ## Example diff --git a/platform/core/src/services/UIDialogService/index.js b/platform/core/src/services/UIDialogService/index.js index ae09d9ca1..44e8c0297 100644 --- a/platform/core/src/services/UIDialogService/index.js +++ b/platform/core/src/services/UIDialogService/index.js @@ -16,7 +16,7 @@ * @property {ReactElement|HTMLElement} content The dialog content. * @property {Object} contentProps The dialog content props. * @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} 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.