feat: Added documentation for OHIF-v3 (#2450)
* Added docs with new screenshots * Added doc to architecture * Added documentations to various extension modules * Added more documentation to modes * Added docs to managers * Added docs for services * Fixed deployment docs * Added white labelling documentation * Added i18n docs and measurement export
This commit is contained in:
1 parent
1bf651e763
commit
5643f8f6d2
142 files changed
+5251
-1523
No files matched your search
@@ -0,0 +1,52 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Manager</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="./extension.md">
|
||||
Extension Manager
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
Aggregating and exposing modules and features through out the app
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="./service.md">
|
||||
Services Manager
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
Single point of registration for all internal and external services
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="./commands.md">
|
||||
Commands Manager
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
Register commands with specific context and run commands in the app
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="./hotkeys.md">
|
||||
Hotkeys Manager
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
For keyboard keys assignment to commands
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,179 @@
|
||||
# Commands Manager
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
The `CommandsManager` is a class defined in the `@ohif/core` project. The Commands Manager tracks named commands (or functions) that are scoped to
|
||||
a context. When we attempt to run a command with a given name, we look for it
|
||||
in our active contexts. If found, we run the command, passing in any application
|
||||
or call specific data specified in the command's definition.
|
||||
|
||||
> Note: A single instance of `CommandsManager` should be defined in the consuming application, and it is used when constructing the `ExtensionManager`.
|
||||
|
||||
A `simplified skeleton` of the `CommandsManager` is shown below:
|
||||
|
||||
```js
|
||||
export class CommandsManager {
|
||||
constructor({ getActiveContexts } = {}) {
|
||||
this.contexts = {};
|
||||
this._getActiveContexts = getActiveContexts;
|
||||
}
|
||||
|
||||
getContext(contextName) {
|
||||
const context = this.contexts[contextName];
|
||||
return context;
|
||||
}
|
||||
|
||||
/**...**/
|
||||
|
||||
createContext(contextName) {
|
||||
/** ... **/
|
||||
this.contexts[contextName] = {};
|
||||
}
|
||||
|
||||
|
||||
registerCommand(contextName, commandName, definition) {
|
||||
/**...**/
|
||||
const context = this.getContext(contextName);
|
||||
/**...**/
|
||||
context[commandName] = definition;
|
||||
}
|
||||
|
||||
runCommand(commandName, options = {}, contextName) {
|
||||
const definition = this.getCommand(commandName, contextName);
|
||||
/**...**/
|
||||
const { commandFn } = definition;
|
||||
const commandParams = Object.assign(
|
||||
{},
|
||||
definition.options, // "Command configuration"
|
||||
options // "Time of call" info
|
||||
);
|
||||
/**...**/
|
||||
return commandFn(commandParams);
|
||||
}
|
||||
/**...**/
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### Instantiating
|
||||
|
||||
When we instantiate the `CommandsManager`, we are passing two methods:
|
||||
|
||||
- `getAppState` - Should return the application's state when called (Not implemented in `v3`)
|
||||
- `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
|
||||
// platform/viewer/src/appInit.js
|
||||
|
||||
const commandsManagerConfig = {
|
||||
getAppState: () => {},
|
||||
/** Used by commands to determine active context */
|
||||
getActiveContexts: () => [
|
||||
'VIEWER',
|
||||
'DEFAULT',
|
||||
'ACTIVE_VIEWPORT::CORNERSTONE',
|
||||
],
|
||||
};
|
||||
|
||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||
```
|
||||
|
||||
|
||||
## Commands/Context Registration
|
||||
The `ExtensionManager` handles registering commands and creating contexts, so you don't need to register all your commands manually. Simply, create a `commandsModule` in your extension, and it will get automatically registered in the `context` provided.
|
||||
|
||||
A *simplified version* of this registration is shown below to give an idea about the process.
|
||||
|
||||
|
||||
```js
|
||||
export default class ExtensionManager {
|
||||
constructor({ commandsManager }) {
|
||||
this._commandsManager = commandsManager
|
||||
}
|
||||
/** ... **/
|
||||
registerExtension = (extension, configuration = {}, dataSources = []) => {
|
||||
let extensionId = extension.id
|
||||
/** ... **/
|
||||
|
||||
// Register Modules provided by the extension
|
||||
moduleTypeNames.forEach((moduleType) => {
|
||||
const extensionModule = this._getExtensionModule(
|
||||
moduleType,
|
||||
extension,
|
||||
extensionId,
|
||||
configuration
|
||||
)
|
||||
|
||||
if (moduleType === 'commandsModule') {
|
||||
this._initCommandsModule(extensionModule)
|
||||
}
|
||||
/** registering other modules **/
|
||||
})
|
||||
}
|
||||
|
||||
_initCommandsModule = (extensionModule) => {
|
||||
let { definitions, defaultContext } = extensionModule
|
||||
defaultContext = defaultContext || 'VIEWER'
|
||||
|
||||
if (!this._commandsManager.getContext(defaultContext)) {
|
||||
this._commandsManager.createContext(defaultContext)
|
||||
}
|
||||
|
||||
Object.keys(definitions).forEach((commandName) => {
|
||||
const commandDefinition = definitions[commandName]
|
||||
const commandHasContextThatDoesNotExist =
|
||||
commandDefinition.context &&
|
||||
!this._commandsManager.getContext(commandDefinition.context)
|
||||
|
||||
if (commandHasContextThatDoesNotExist) {
|
||||
this._commandsManager.createContext(commandDefinition.context)
|
||||
}
|
||||
|
||||
this._commandsManager.registerCommand(
|
||||
commandDefinition.context || defaultContext,
|
||||
commandName,
|
||||
commandDefinition
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
If you find yourself in a situation where you want to register a command/context manually, ask
|
||||
yourself "why can't I register these commands via an extension?", but if you insist, you can use the `CommandsManager` API to do so:
|
||||
|
||||
```js
|
||||
// Command Registration
|
||||
commandsManager.registerCommand('context', 'name', commandDefinition);
|
||||
|
||||
// Context Creation
|
||||
commandsManager.createContext('string');
|
||||
```
|
||||
|
||||
## `CommandsManager` Public API
|
||||
|
||||
If you would like to run a command in the consuming app or an extension, you can
|
||||
use `runCommand(commandName, options = {}, contextName)`.
|
||||
|
||||
|
||||
```js
|
||||
|
||||
// Run a command, it will run all the `speak` commands in all contexts
|
||||
commandsManager.runCommand('speak', { command: 'hello' });
|
||||
|
||||
// Run command, from Default context
|
||||
commandsManager.runCommand('speak', { command: 'hello' }, ['DEFAULT']);
|
||||
|
||||
// Returns all commands for a given context
|
||||
commandsManager.getContext('string');
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
# Extension Manager
|
||||
|
||||
## Overview
|
||||
The `ExtensionManager` is a class made available to us via the `@ohif/core`
|
||||
project (platform/core). Our application instantiates a single instance of it,
|
||||
and provides a `ServicesManager` and `CommandsManager` along with the
|
||||
application's configuration through the appConfig key (optional).
|
||||
|
||||
```js
|
||||
const commandsManager = new CommandsManager();
|
||||
const servicesManager = new ServicesManager();
|
||||
const extensionManager = new ExtensionManager({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
appConfig,
|
||||
});
|
||||
```
|
||||
|
||||
The `ExtensionManager` only has a few public members:
|
||||
|
||||
- `setActiveDataSource` - Sets the active data source for the application
|
||||
- `getDataSources` - Returns the registered data sources
|
||||
- `getActiveDataSource` - Returns the currently active data source
|
||||
- `getModuleEntry` - Returns the module entry by the give id.
|
||||
|
||||
|
||||
## Accessing Modules
|
||||
|
||||
We use `getModuleEntry` in our `ViewerLayout` logic to find the panels based on the
|
||||
provided IDs in the mode's configuration.
|
||||
|
||||
|
||||
For instance: `extensionManager.getModuleEntry("org.ohif.measurement-tracking.panelModule.seriesList")`
|
||||
accesses the `seriesList` panel from `panelModule` of the `org.ohif.measurement-tracking` extension.
|
||||
|
||||
|
||||
```js
|
||||
const getPanelData = id => {
|
||||
const entry = extensionManager.getModuleEntry(id);
|
||||
const content = entry.component;
|
||||
|
||||
return {
|
||||
iconName: entry.iconName,
|
||||
iconLabel: entry.iconLabel,
|
||||
label: entry.label,
|
||||
name: entry.name,
|
||||
content,
|
||||
};
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,75 @@
|
||||
# Hotkeys Managers
|
||||
|
||||
## Overview
|
||||
`HotkeysManager` handles all the logics for adding, setting and enabling/disabling
|
||||
the hotkeys.
|
||||
|
||||
|
||||
|
||||
## Instantiation
|
||||
`HotkeysManager` is instantiated in the `appInit` similar to the other managers.
|
||||
|
||||
```js
|
||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||
const servicesManager = new ServicesManager(commandsManager);
|
||||
const hotkeysManager = new HotkeysManager(commandsManager, servicesManager);
|
||||
const extensionManager = new ExtensionManager({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
hotkeysManager,
|
||||
appConfig,
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Hotkeys Manager API
|
||||
|
||||
- `setHotkeys`: The most important method in the `HotkeysManager` which binds the keys with commands.
|
||||
- `setDefaultHotKeys`: set the defaultHotkeys **property**. Note that, this method **does not** bind the provided hotkeys; however, when `restoreDefaultBindings`
|
||||
is called, the provided defaultHotkeys will get binded.
|
||||
- `destroy`: reset the HotkeysManager, and remove the set hotkeys and empty out the `defaultHotkeys`
|
||||
|
||||
|
||||
|
||||
## Structure of a Hotkey Definition
|
||||
A hotkey definition should have the following properties:
|
||||
|
||||
- `commandName`: name of the registered command
|
||||
- `commandOptions`: extra arguments to the commands
|
||||
- `keys`: an array defining the key to get binded to the command
|
||||
- `label`: label to be shown in the hotkeys preference panel
|
||||
- `isEditable`: whether the key can be edited by the user in the hotkey panel
|
||||
|
||||
|
||||
### Default hotkeysBindings
|
||||
The default key bindings can be find in `hotkeyBindings.js`
|
||||
|
||||
```js
|
||||
// platform/core/src/defaults/hotkeyBindings.js
|
||||
|
||||
export default [
|
||||
/**..**/
|
||||
{
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Zoom' },
|
||||
label: 'Zoom',
|
||||
keys: ['z'],
|
||||
isEditable: true,
|
||||
},
|
||||
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
isEditable: true,
|
||||
},
|
||||
/**..**/
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
## Behind the Scene
|
||||
When you `setHotkeys`, the `commandName` gets registered with the `commandsManager` and
|
||||
get run after the key is pressed.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Managers
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
`OHIF` uses `Managers` to accomplish various purposes such as registering new services, dependency injection, and aggregating and exposing `extension` features.
|
||||
|
||||
`OHIF-v3` provides the following managers which we will discuss in depth.
|
||||
|
||||
{% include "./_managers.md" %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
LINKS
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[core-services]: https://github.com/OHIF/Viewers/tree/master/platform/core/src/services
|
||||
[services-manager]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/ServicesManager.js
|
||||
[cross-cutting-concerns]: https://en.wikipedia.org/wiki/Cross-cutting_concern
|
||||
<!-- prettier-ignore-end -->
|
||||
@@ -0,0 +1,158 @@
|
||||
# Services Manager
|
||||
|
||||
## Overview
|
||||
Services manager is the single point of service registration. Each service needs to implement a `create` method which gets called inside `ServicesManager` to instantiate the service. In the app, you can get access to a registered service via the `services` property of the `ServicesManager`.
|
||||
|
||||
## Skeleton
|
||||
*Simplified* skeleton of `ServicesManager` is shown below. There are two public methods:
|
||||
|
||||
- `registerService`: registering a new service with/without a configuration
|
||||
- `registerServices`: registering batch of services
|
||||
|
||||
|
||||
```js
|
||||
export default class ServicesManager {
|
||||
constructor(commandsManager) {
|
||||
this._commandsManager = commandsManager
|
||||
this.services = {}
|
||||
this.registeredServiceNames = []
|
||||
}
|
||||
|
||||
registerService(service, configuration = {}) {
|
||||
/** validation checks **/
|
||||
this.services[service.name] = service.create({
|
||||
configuration,
|
||||
commandsManager: this._commandsManager,
|
||||
})
|
||||
|
||||
/* Track service registration */
|
||||
this.registeredServiceNames.push(service.name)
|
||||
}
|
||||
|
||||
registerServices(services) {
|
||||
/** ... **/
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Default Registered Services
|
||||
By default, `OHIF-v3` registers the following services in the `appInit`.
|
||||
|
||||
```js
|
||||
// platform/viewer/src/appInit.js
|
||||
|
||||
servicesManager.registerServices([
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
UIViewportDialogService,
|
||||
MeasurementService,
|
||||
DisplaySetService,
|
||||
ToolBarService,
|
||||
ViewportGridService,
|
||||
HangingProtocolService,
|
||||
CineService,
|
||||
]);
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Service Architecture
|
||||
If you take a look at the folder of each service implementation above, you will
|
||||
find out that services need to be exported as an object with `name` and `create` method.
|
||||
|
||||
For instance, `ToolbarService` is exported as:
|
||||
|
||||
```js
|
||||
// platform/core/src/services/ToolBarService/index.js
|
||||
|
||||
import ToolBarService from './ToolBarService';
|
||||
|
||||
export default {
|
||||
name: 'ToolBarService',
|
||||
create: ({ configuration = {}, commandsManager }) => {
|
||||
return new ToolBarService(commandsManager);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
and the implementation of `ToolbarService` lies in the same folder at `./ToolbarSerivce.js`.
|
||||
|
||||
> Note, the create method is critical for any custom service that you write and
|
||||
> want to add to the list of services
|
||||
|
||||
|
||||
|
||||
## Accessing Services
|
||||
Throughout the app you can use `services` property of the service manager to access
|
||||
the desired service.
|
||||
|
||||
For instance in the `PanelMeasurementTableTracking` which is the right panel in the
|
||||
`longitudinal` mode, we have the *simplified code below* for downloading the drawn measurements.
|
||||
|
||||
```js
|
||||
function PanelMeasurementTableTracking({ servicesManager }) {
|
||||
const { MeasurementService } = servicesManager.services
|
||||
/** ... **/
|
||||
|
||||
async function exportReport() {
|
||||
const measurements = MeasurementService.getMeasurements()
|
||||
/** ... **/
|
||||
downloadCSVReport(measurements, MeasurementService)
|
||||
}
|
||||
|
||||
/** ... **/
|
||||
return <> /** ... **/ </>
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Registering Custom Services
|
||||
You might need to write you own custom service in an extension. `preRegistration` hook inside your extension is the place for registering your custom service.
|
||||
|
||||
```js
|
||||
// extensions/customExtension/src/index.js
|
||||
|
||||
import WrappedBackEndService from './services/backEndService'
|
||||
|
||||
export default {
|
||||
// ID of the extension
|
||||
id: 'myExtension',
|
||||
preRegistration({ servicesManager }) {
|
||||
servicesManager.registerService(WrappedBackEndService(servicesManager));
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
and the logic for your service shall be
|
||||
|
||||
|
||||
```js
|
||||
// extensions/customExtension/src/services/backEndService/index.js
|
||||
import backEndService from './backEndService';
|
||||
|
||||
export default function WrappedBackEndService(serviceManager) {
|
||||
return {
|
||||
name: 'myService',
|
||||
create: ({ configuration = {} }) => {
|
||||
return new backEndService(serviceManager);
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
with implementation of
|
||||
|
||||
```js
|
||||
export default class backEndService {
|
||||
constructor(serviceManager) {
|
||||
this.serviceManager = serviceManager;
|
||||
}
|
||||
|
||||
putAnnotations() {
|
||||
return post(/*...*/);
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in new issue
Block a user