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:
Alireza authored and GitHub committed 2021-06-15 11:15:29 -04:00
1 parent 1bf651e763
commit 5643f8f6d2
142 files changed
+5251 -1523

No files matched your search

+52
View File
@@ -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>
+179
View File
@@ -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');
```
+50
View File
@@ -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,
};
};
```
+75
View File
@@ -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.
+25
View File
@@ -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 -->
+158
View File
@@ -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(/*...*/);
}
}
```