feat(toolbar): new Toolbar to enable reactive state synchronization (#3983)
This commit is contained in:
1 parent
79d5c36bda
commit
566b25a544
155 files changed
+6311
-6577
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 441 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 654 KiB |
@@ -649,7 +649,6 @@ onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => {
|
||||
// Init tool groups (see cornerstone3D for more details)
|
||||
initToolGroups(extensionManager, toolGroupService, commandsManager);
|
||||
|
||||
toolbarService.init(extensionManager);
|
||||
toolbarService.addButtons(toolbarButtons);
|
||||
toolbarService.createButtonSection('primary', [
|
||||
'MeasurementTools',
|
||||
|
||||
@@ -6,29 +6,24 @@ sidebar_label: Toolbar
|
||||
# Module: Toolbar
|
||||
|
||||
An extension can register a Toolbar Module by defining a `getToolbarModule`
|
||||
method. `OHIF-v3`'s `default` extension (`"@ohif/extension-default"`) provides 5 main
|
||||
toolbar button types:
|
||||
method. `OHIF-v3`'s `default` extension (`"@ohif/extension-default"`) provides the
|
||||
following toolbar button `uiTypes`:
|
||||
|
||||

|
||||
- `ohif.radioGroup`: which is a simple button that can be clicked
|
||||
- `ohif.splitButton`: which is a button with a dropdown menu
|
||||
- `ohif.divider`: which is a simple divider
|
||||
|
||||
## Example Toolbar Module
|
||||
|
||||
The Toolbar Module should return an array of `objects`. There are currently a
|
||||
few different variations of definitions, each one is detailed further down.
|
||||
There are two things that the toolbar module can provide, first
|
||||
a component, and second evaluators.
|
||||
|
||||
### Components
|
||||
```js
|
||||
export default function getToolbarModule({ commandsManager, servicesManager }) {
|
||||
return [
|
||||
{
|
||||
name: 'ohif.divider',
|
||||
defaultComponent: ToolbarDivider,
|
||||
clickHandler: () => {},
|
||||
},
|
||||
{
|
||||
name: 'ohif.action',
|
||||
defaultComponent: ToolbarButton,
|
||||
clickHandler: () => {},
|
||||
},
|
||||
{
|
||||
name: 'ohif.radioGroup',
|
||||
defaultComponent: ToolbarButton,
|
||||
@@ -53,214 +48,7 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
|
||||
}
|
||||
```
|
||||
|
||||
## Toolbar buttons consumed in modes
|
||||
|
||||
Below we can see a simplified version of the `longitudinal` mode that shows how
|
||||
a mode can add buttons to the toolbar by calling
|
||||
`ToolBarService.addButtons(toolbarButtons)`. `toolbarButtons` is an array of
|
||||
`toolDefinitions` which we will learn next.
|
||||
|
||||
```js
|
||||
function modeFactory({ modeConfiguration }) {
|
||||
return {
|
||||
id: 'viewer',
|
||||
displayName: 'Basic Viewer',
|
||||
|
||||
onModeEnter: ({ servicesManager, extensionManager }) => {
|
||||
const { ToolBarService } = servicesManager.services;
|
||||
|
||||
ToolBarService.init(extensionManager);
|
||||
ToolBarService.addButtons(toolbarButtons);
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
path: 'longitudinal',
|
||||
layoutTemplate: ({ location, servicesManager }) => {
|
||||
return {
|
||||
/* */
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Button Definitions
|
||||
|
||||
The simplest toolbarButtons definition has the following properties:
|
||||
|
||||

|
||||
|
||||
```js
|
||||
{
|
||||
"id": "Zoom",
|
||||
"type": "ohif.radioGroup",
|
||||
"props": {
|
||||
"type": "tool",
|
||||
"icon": "tool-zoom",
|
||||
"label": "Zoom",
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "Zoom"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| property | description | values |
|
||||
| ---------------- | ----------------------------------------------------------------- | ------------------------------------------- |
|
||||
| `id` | Unique string identifier for the definition | \* |
|
||||
| `type` | Used to determine the button's behaviour | "tool", "toggle", "action" |
|
||||
| `icon` | A string name for an icon supported by the consuming application. | \* |
|
||||
| `label` | User/display friendly to show in UI | \* |
|
||||
| `commands` | (optional) The commands to run when the button is used. It include a commandName, commandOptions, and/or a context | Any command registered by a `CommandModule` |
|
||||
|
||||
There are three main types of toolbar buttons:
|
||||
|
||||
- `tool`: buttons that enable a tool by running the `setToolActive` command with
|
||||
the `commandOptions`
|
||||
- `toggle`: buttons that acts as a toggle: e.g., linking viewports
|
||||
- `action`: buttons that executes an action: e.g., capture button to save
|
||||
screenshot
|
||||
|
||||
## Nested Buttons
|
||||
|
||||
You can use the `ohif.splitButton` type to build a button with extra tools in
|
||||
the dropdown.
|
||||
|
||||
- First you need to give your `primary` tool definition to the split button. The primary
|
||||
tool can specify a `uiType` property which can be one of the button types returned by
|
||||
`getToolbarModule` that is a variation of `ToolbarButton`. If `uiType` is omitted then
|
||||
`ToolbarButton` is used by default.
|
||||
- The `secondary` properties can be a simple arrow down (`chevron-down` icon)
|
||||
- For adding the extra tools add them to the `items` list.
|
||||
|
||||
You can see below how `longitudinal` mode is using the available toolbarModule
|
||||
to create `MeasurementTools` nested button
|
||||
|
||||

|
||||
|
||||
```js title="modes/longitudinal/src/toolbarButtons.js"
|
||||
{
|
||||
"id": "MeasurementTools",
|
||||
"type": "ohif.splitButton",
|
||||
"props": {
|
||||
"groupId": "MeasurementTools",
|
||||
"isRadio": true,
|
||||
"primary": {
|
||||
"id": "Length",
|
||||
"icon": "tool-length",
|
||||
"label": "Length",
|
||||
"type": "tool",
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "Length"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
},
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "SRLength",
|
||||
"toolGroupId": "SRToolGroup"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
}
|
||||
],
|
||||
"tooltip": "Length"
|
||||
},
|
||||
"secondary": {
|
||||
"icon": "chevron-down",
|
||||
"label": "",
|
||||
"isActive": true,
|
||||
"tooltip": "More Measure Tools"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": "Bidirectional",
|
||||
"icon": "tool-bidirectional",
|
||||
"label": "Bidirectional",
|
||||
"type": "tool",
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "Bidirectional"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
},
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "SRBidirectional",
|
||||
"toolGroupId": "SRToolGroup"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
}
|
||||
],
|
||||
"tooltip": "Bidirectional Tool"
|
||||
},
|
||||
{
|
||||
"id": "ArrowAnnotate",
|
||||
"icon": "tool-annotate",
|
||||
"label": "Annotation",
|
||||
"type": "tool",
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "ArrowAnnotate"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
},
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "SRArrowAnnotate",
|
||||
"toolGroupId": "SRToolGroup"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
}
|
||||
],
|
||||
"tooltip": "Arrow Annotate"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
|
||||
<iframe src="https://player.vimeo.com/video/547957214?badge=0&autopause=0&player_id=0&app_id=58479" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="measurement-report"></iframe>
|
||||
</div>
|
||||
|
||||
## Layout Template
|
||||
|
||||
Layout selector button and logic is also provided by the OHIF-v3 `default`
|
||||
extension. To use it, you can just add the following definition to the list of
|
||||
`toolDefinitions`
|
||||
|
||||

|
||||
|
||||
```js
|
||||
{
|
||||
id: 'Layout',
|
||||
type: 'ohif.layoutSelector',
|
||||
}
|
||||
```
|
||||
|
||||
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
|
||||
<iframe src="https://player.vimeo.com/video/545993263?badge=0&autopause=0&player_id=0&app_id=58479" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="measurement-report"></iframe>
|
||||
</div>
|
||||
|
||||
## Custom Button
|
||||
### Custom Components
|
||||
|
||||
You can also create your own extension, and add your new custom tool appearance
|
||||
(e.g., split horizontally instead of vertically for split tool). Simply add
|
||||
@@ -283,6 +71,323 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
|
||||
}
|
||||
```
|
||||
|
||||
## Custom tool
|
||||
Check out how to assemble the toolbar in the [modes](../../modes/index.md) section.
|
||||
|
||||
**I want to create a new tool**
|
||||
|
||||
### Evaluators
|
||||
Buttons may be equipped with evaluators, which are functions invoked by the toolbarService to assess the button's status. These evaluators are expected to return an object of `{className}` and may include additional details, as elaborated in the subsequent section.
|
||||
|
||||
Evaluators play a crucial role in determining the button's status based on the viewport. For example, users should be restricted from clicking on the mpr if the displaySet is not reconstructable. Additionally, certain buttons within the toolbar may be associated with specific toolGroups and should remain inactive for certain viewports.
|
||||
|
||||
Let's look at one of the evaluators (for `evaluate.cornerstoneTool`)
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'evaluate.cornerstoneTool',
|
||||
evaluate: ({ viewportId, button }) => {
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
if (!toolGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolName = getToolNameForButton(button);
|
||||
|
||||
if (!toolGroup || !toolGroup.hasTool(toolName)) {
|
||||
return {
|
||||
disabled: true,
|
||||
className: '!text-common-bright ohif-disabled',
|
||||
};
|
||||
}
|
||||
|
||||
const isPrimaryActive = toolGroup.getActivePrimaryMouseButtonTool() === toolName;
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
className: isPrimaryActive
|
||||
? '!text-black bg-primary-light'
|
||||
: '!text-common-bright hover:!bg-primary-dark hover:!text-primary-light',
|
||||
};
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
as you can see the job of this evaluator is to determine if the button should be disabled or not. It does so by checking the `toolGroup` and the `toolName` and then returns an object with `disabled` and `className` properties.
|
||||
|
||||
The following evaluators are provided by us:
|
||||
|
||||
- `evaluate.cornerstoneTool`: If assigned to a button (see next), it will make the button react to the active viewport state based on its toolGroup.
|
||||
- `evaluate.cornerstoneTool.toggle`: It is designed to consider tools with toggle behavior, such as reference lines and image overlay (either on or off).
|
||||
- `evaluate.cornerstone.synchronizer`: This is designed to consider the synchronizer state of the viewport, whether it is synced or not.
|
||||
- `evaluate.viewportProperties.toggle`: Some properties of the viewport are toggleable, such as invert, flip, rotate, etc. By assigning this evaluator to those buttons, they will react to the active viewport state based on its properties. This allows for dynamic buttons that change their appearance based on the active viewport state.
|
||||
- `evaluate.mpr`: special evaluator for MPR since it needs to check if the displaySet is reconstructable or not.
|
||||
|
||||
|
||||
Sometime you want to use the same `evaluator` for different purposes, in that case you can use an object
|
||||
with `name` and `options` properties. For example, in `'evaluate.cornerstone.segmentation'` we use
|
||||
this pattern, where multiple toolbar buttons are using the same evaluator but with different options (
|
||||
in this case `toolNames`
|
||||
)
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'evaluate.cornerstone.segmentation',
|
||||
options: {
|
||||
toolNames: ['CircleBrush' , 'SphereBrush']
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
#### Group evaluators
|
||||
Split buttons (see in [ToolbarService](../../services/data/ToolbarService.md) on how to define one) may feature a group evaluator, we provide two of them and you can write your own.
|
||||
|
||||
|
||||
- `evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList`: determine the outcome of user interactions with the split buttons on what button should be promoted to the primary section. In the example above, the cornerstone tool's status is checked, and if it is not active in the list of buttons, the button is promoted to the primary section.
|
||||
- `evaluate.group.promoteToPrimary`: disregarding the cornerstone tool's status and promoting the button to the primary section regardless.
|
||||
|
||||
Failure to specify a group evaluator will result in no action, leaving the button in the secondary section.
|
||||
|
||||
:::note
|
||||
As you have learned so far, the extension modules only 'provides' the functionality
|
||||
and it is the mode's job to consume it. You can next learn how to consume these components
|
||||
and evaluators to build a toolbar in the
|
||||
:::
|
||||
|
||||
|
||||
#### Custom Evaluators
|
||||
You can create your own evaluators. For instance, you have the option to design tri-state buttons, which are buttons with three states such as Show All, Show Some, or Show None of the Viewport Overlays.
|
||||
|
||||
|
||||
## Toolbar buttons consumed in modes
|
||||
Providing just the components is not enough. You need to add the buttons to the toolbar service and decide which ones are used for each section.
|
||||
|
||||
Below we can see a simplified version of the `longitudinal` (basic viewer) mode that shows how
|
||||
a mode can add buttons to the toolbar by calling
|
||||
`ToolBarService.addButtons(toolbarButtons)`. `toolbarButtons` is an array of
|
||||
`toolDefinitions` which we will learn next.
|
||||
|
||||
```js
|
||||
function modeFactory({ modeConfiguration }) {
|
||||
return {
|
||||
id: 'viewer',
|
||||
displayName: 'Basic Viewer',
|
||||
|
||||
onModeEnter: ({ servicesManager, extensionManager }) => {
|
||||
const { toolBarService } = servicesManager.services;
|
||||
|
||||
toolbarService.addButtons([...toolbarButtons, ...moreTools]);
|
||||
toolbarService.createButtonSection('primary', [
|
||||
'MeasurementTools',
|
||||
'Zoom',
|
||||
'info',
|
||||
'WindowLevel',
|
||||
'Pan',
|
||||
'Capture',
|
||||
'Layout',
|
||||
'MPR',
|
||||
'Crosshairs',
|
||||
'MoreTools',
|
||||
]);
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
path: 'longitudinal',
|
||||
layoutTemplate: ({ location, servicesManager }) => {
|
||||
return {
|
||||
/* */
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
:::note
|
||||
By default OHIF's default layout (`extensions/default/src/ViewerLayout/index.tsx`) which is used in all modes use a Toolbar component that creates a
|
||||
`primary` section for tools. That is why we are creating a `primary` section in the example above.
|
||||
|
||||
Layouts are also customizable, and you can create your own layout in your extensions and provide it to your modes view `getLayoutTemplateModule` module.
|
||||
|
||||
By default we use `@ohif/extension-default.layoutTemplateModule.viewerLayout` to use the default layout which provides a
|
||||
|
||||
- Header (with logo on left, toolbar in the middle and user menu on the right)
|
||||
- Left panel
|
||||
- Main viewport grid area
|
||||
- Right panel
|
||||
:::
|
||||
|
||||
|
||||
## Alternative Toolbar sections
|
||||
|
||||
In your UI component, such as panels, you have the option to include a toolbar section template.
|
||||
This allows you to easily add buttons to it later on. To ensure that the buttons are added properly
|
||||
to the toolbar, respond to interactions correctly, and evaluate states accurately, simply utilize the `useToolbar` hook.
|
||||
This hook grants you access to the `onInteraction` function and the `toolbarButtons` array, which you can customize within your UI as needed.
|
||||
|
||||
```js
|
||||
|
||||
function myCustomPanel({servicesManager}){
|
||||
const { onInteraction, toolbarButtons } = useToolbar({
|
||||
servicesManager,
|
||||
buttonSection: 'myCustomSectionName'
|
||||
});
|
||||
|
||||
// map the buttons to the UI
|
||||
return (
|
||||
<div>
|
||||
{toolbarButtons.map((button, index) => {
|
||||
return (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onInteraction(button)}
|
||||
>
|
||||
{button.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
We have provided a common component for toolbar buttons called `Toolbox`.
|
||||
The Toolbox component serves as a versatile and configurable container for toolbar tools within your application.
|
||||
It is designed to work in conjunction with the useToolbar hook to manage tool states, handle user interactions, and memorize options
|
||||
using context API.
|
||||
|
||||
|
||||
The `Toolbox` can be easily integrated into your application UI, requiring only the necessary services (servicesManager, commandsManager) and configuration parameters (buttonSectionId, title). Here's a simple usage scenario:
|
||||
|
||||
|
||||
|
||||
```js
|
||||
function MyApplication({ servicesManager, commandsManager }) {
|
||||
// Configuration for the toolbox container
|
||||
const config = {
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
buttonSectionId: 'customButtonSection',
|
||||
title: 'My Toolbox',
|
||||
};
|
||||
|
||||
return <Toolbox {...config} />;
|
||||
}
|
||||
```
|
||||
|
||||
Then in your modes you can edit the tools in that button section.
|
||||
|
||||
```js
|
||||
|
||||
onModeEnter: ({ servicesManager, extensionManager }) => {
|
||||
const { toolBarService } = servicesManager.services;
|
||||
|
||||
toolbarService.addButtons([...toolbarButtons, ...moreTools]);
|
||||
toolbarService.createButtonSection('customButtonSection', [
|
||||
'MeasurementTools',
|
||||
'Zoom',
|
||||
'info',
|
||||
]);
|
||||
},
|
||||
```
|
||||
|
||||
Another example might be you want to open a modal to show some tool options when a button is clicked.
|
||||
You can use this pattern
|
||||
|
||||
|
||||
```js
|
||||
// ToolbarButton in mode
|
||||
{
|
||||
id: 'Others',
|
||||
uiType: 'ohif.radioGroup',
|
||||
props: {
|
||||
icon: 'info-action',
|
||||
label: 'Others',
|
||||
commands: 'showOthersModal',
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
and inside your mode factory
|
||||
|
||||
```js
|
||||
// adding the 'Others' button to the primary section
|
||||
toolbarService.createButtonSection('primary', [
|
||||
'Others', // --------> this one
|
||||
]);
|
||||
|
||||
// adding the shapes button to the 'Other' section
|
||||
toolbarService.createButtonSection('other', ['Shapes']);
|
||||
```
|
||||
|
||||
here as you see we are using a command `showOthersModal` which is defined in the commands module.
|
||||
|
||||
```js
|
||||
// inside commandsModule of your extension
|
||||
showOthersModal: () => {
|
||||
const { uiModalService } = servicesManager.services;
|
||||
uiModalService.show({
|
||||
content: OthersModal,
|
||||
title: 'Others',
|
||||
customClassName: 'w-8',
|
||||
movable: true,
|
||||
contentProps: {
|
||||
onClose: uiModalService.hide,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
},
|
||||
containerDimensions: 'h-[125px] w-[300px]',
|
||||
contentDimensions: 'h-[125px] w-[300px]',
|
||||
});
|
||||
},
|
||||
```
|
||||
|
||||
as you see it is opening a modal with `OthersModal` component (below) which contains the
|
||||
`Toolbox` component.
|
||||
|
||||
```js
|
||||
// Others modal
|
||||
import { Toolbox } from '@ohif/ui';
|
||||
|
||||
function OthersModal({ servicesManager, commandsManager }) {
|
||||
return (
|
||||
<div className="px-2">
|
||||
<Toolbox
|
||||
buttonSectionId={'other'}
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
title={'other'}
|
||||
useCollapsedPanel={false}
|
||||
></Toolbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The result would be a modal with a toolbox inside it when the `Others` button is clicked, and the
|
||||
state will get synchronized with the toolbar service automatically.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## Change Toolbar with hanging protocols
|
||||
|
||||
If you want to change the toolbar based on the hanging protocol, you can do a pattern like this.
|
||||
|
||||
```js
|
||||
|
||||
const { unsubscribe } = hangingProtocolService.subscribe(
|
||||
hangingProtocolService.EVENTS.PROTOCOL_CHANGED,
|
||||
() => {
|
||||
toolbarService.createButtonSection('primary', [
|
||||
'MeasurementTools',
|
||||
'Zoom',
|
||||
'WindowLevel',
|
||||
]);
|
||||
}
|
||||
);
|
||||
```
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"label": "Managers",
|
||||
"position": 11
|
||||
"position": 10
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"label": "Modes",
|
||||
"position": 10
|
||||
"position": 12
|
||||
}
|
||||
@@ -362,6 +362,11 @@ function modeFactory() {
|
||||
// exports
|
||||
```
|
||||
|
||||
### Toolbar
|
||||
|
||||
|
||||
|
||||
|
||||
## Registration
|
||||
|
||||
Similar to extension registration, `viewer` will look inside the `pluginConfig.json` to
|
||||
|
||||
@@ -56,7 +56,6 @@ function modeFactory() {
|
||||
// Init Default and SR ToolGroups
|
||||
initToolGroups(extensionManager, ToolGroupService);
|
||||
|
||||
ToolBarService.init(extensionManager);
|
||||
ToolBarService.addButtons(toolbarButtons);
|
||||
ToolBarService.createButtonSection('primary', [
|
||||
'MeasurementTools',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"label": "Services",
|
||||
"position": 12
|
||||
"position": 11
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
---
|
||||
sidebar_position: 8
|
||||
sidebar_label: SyncGroup Service
|
||||
---
|
||||
|
||||
# Sync Group Service
|
||||
|
||||
## Overview
|
||||
|
||||
The `SyncGroupService` is responsible for managing synchronization groups in the OHIF Viewer. Synchronization groups allow multiple viewports to be synchronized based on various criteria, such as camera position, window level, zoom/pan, and image slice position. This service provides a centralized way to create, update, and manage synchronization groups.
|
||||
|
||||
Right now, synchronization groups can be defined in the hanging protocols or manually assigning buttons.
|
||||
|
||||
|
||||
|
||||
|
||||
## API
|
||||
|
||||
- `getSyncCreatorForType(type)`: Returns the synchronizer creator function for the specified type.
|
||||
- `addSynchronizerType(type, creator)`: Adds a new synchronizer type with a custom creator function.
|
||||
- `getSynchronizer(id)`: Retrieves a synchronizer by its ID.
|
||||
- `getSynchronizersOfType(type)`: Retrieves an array of synchronizers of the specified type.
|
||||
- `addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups)`: Adds a viewport to one or more synchronization groups.
|
||||
- `destroy()`: Destroys all synchronizers.
|
||||
- `getSynchronizersForViewport(viewportId)`: Retrieves an array of synchronizers associated with the specified viewport.
|
||||
- `removeViewportFromSyncGroup(viewportId, renderingEngineId, syncGroupId?)`: Removes a viewport from a specific synchronization group or all synchronization groups if no group ID is provided.
|
||||
|
||||
## Usage
|
||||
### Via hanging protocols
|
||||
You can set up different types of synchronization groups for your viewports. For example, in the TMTV hanging protocol (`extensions/tmtv/src/getHangingProtocolModule.js`), we can see how different synchronization groups are defined for various viewports:
|
||||
|
||||
```javascript
|
||||
const ptAXIAL = {
|
||||
viewportOptions: {
|
||||
// ...
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'cameraPosition',
|
||||
id: 'axialSync',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'ptWLSync',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'ptFusionWLSync',
|
||||
source: true,
|
||||
target: false,
|
||||
options: {
|
||||
syncInvertState: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
|
||||
In this example, the `ptAXIAL` viewport is part of three synchronization groups:
|
||||
|
||||
1. `cameraPosition` group with the ID `'axialSync'`: This group synchronizes the camera position across viewports that are both source and target.
|
||||
2. `voi` (Window Level) group with the ID `'ptWLSync'`: This group synchronizes the window level settings across viewports that are both source and target.
|
||||
3. `voi` group with the ID `'ptFusionWLSync'`: This group synchronizes the window level settings, but the `ptAXIAL` viewport is only a source, not a target.
|
||||
|
||||
|
||||
:::tip
|
||||
You can control the state of the synchronizer via a toolbar button after you define the synchronization group in the hanging protocol.
|
||||
|
||||
```js
|
||||
{
|
||||
id: 'SyncToggle',
|
||||
uiType: 'ohif.radioGroup',
|
||||
props: {
|
||||
icon: 'tool-info',
|
||||
label: 'toggle',
|
||||
commands: {
|
||||
commandName: 'toggleSynchronizer',
|
||||
commandOptions: {
|
||||
syncId: 'axialSync'
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
as you can see by using the `toggleSynchronizer` command you can toggle the state of the synchronizer for the specified syncId.
|
||||
|
||||
:::
|
||||
|
||||
### Manually through a button
|
||||
You can create a button on the toolbar that you provice the synchronization group type,
|
||||
and it applys it to all viewports.
|
||||
|
||||
:::note
|
||||
Currently we don't have a proper way to select viewports to apply the synchronization group to. It is applied to all applicable viewports
|
||||
:::
|
||||
|
||||
For instance look at `imageSliceSync` button in the longitudinal mode (`modes/longitudinal/src/moreTools.ts`) and how it runs a command
|
||||
|
||||
```js
|
||||
ToolbarService.createButton({
|
||||
id: 'ImageSliceSync',
|
||||
icon: 'link',
|
||||
label: 'Image Slice Sync',
|
||||
tooltip: 'Enable position synchronization on stack viewports',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'toggleSynchronizer',
|
||||
commandOptions: {
|
||||
type: 'imageSlice',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
You can create another button to toggle 'voi' synchronization. Currently we group
|
||||
viewports by modality and apply the voi synchronization to all viewports of the same modality.
|
||||
|
||||
```js
|
||||
ToolbarService.createButton({
|
||||
id: 'VoiSync',
|
||||
icon: 'link',
|
||||
label: 'VOI Sync',
|
||||
tooltip: 'Enable VOI synchronization on viewports',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'toggleSynchronizer',
|
||||
commandOptions: {
|
||||
type: 'voi',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
:::tip
|
||||
For your custom synchronization groups, you can create a new synchronizer type and follow the
|
||||
same pattern as the existing synchronizers.
|
||||
:::
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
sidebar_label: ToolGroup Service
|
||||
---
|
||||
|
||||
# Tool Group Service
|
||||
|
||||
## Overview
|
||||
|
||||
The `ToolGroupService` is responsible for managing tool groups in the OHIF Viewer.
|
||||
|
||||
:::tip
|
||||
Read more about toolGroups [here](https://www.cornerstonejs.org/docs/concepts/cornerstone-tools/toolGroups)
|
||||
:::
|
||||
|
||||
It allows you to create, update, and manage tool groups and the tools associated with them. Tool groups are used to organize and control the behavior of various tools in the viewer, such as window level, pan, zoom, measurements, and annotations.
|
||||
|
||||
## Events
|
||||
|
||||
The `ToolGroupService` emits the following events:
|
||||
|
||||
| Event | Description |
|
||||
| ---------------------------------- | ----------------------------------------------- |
|
||||
| `VIEWPORT_ADDED` | Fires when a viewport is added to a tool group |
|
||||
| `TOOLGROUP_CREATED` | Fires when a new tool group is created |
|
||||
|
||||
## API
|
||||
|
||||
- `getToolGroup(toolGroupId?)`: Retrieves a tool group by its ID. If no ID is provided, it returns the tool group for the active viewport.
|
||||
- `getToolGroupIds()`: Returns an array of all tool group IDs.
|
||||
- `getToolGroupForViewport(viewportId)`: Returns the tool group associated with the specified viewport.
|
||||
- `getActiveToolForViewport(viewportId)`: Returns the active tool for the specified viewport.
|
||||
- `destroy()`: Destroys all tool groups.
|
||||
- `destroyToolGroup(toolGroupId)`: Destroys the specified tool group.
|
||||
- `removeViewportFromToolGroup(viewportId, renderingEngineId, deleteToolGroupIfEmpty?)`: Removes a viewport from a tool group. If `deleteToolGroupIfEmpty` is true and the tool group becomes empty after removing the viewport, it will be destroyed.
|
||||
- `addViewportToToolGroup(viewportId, renderingEngineId, toolGroupId?)`: Adds a viewport to a tool group. If `toolGroupId` is not provided, the viewport will be added to all tool groups.
|
||||
- `createToolGroup(toolGroupId)`: Creates a new tool group with the specified ID.
|
||||
- `addToolsToToolGroup(toolGroupId, tools, configs?)`: Adds tools to the specified tool group with optional configurations.
|
||||
- `createToolGroupAndAddTools(toolGroupId, tools)`: Creates a new tool group and adds the specified tools to it.
|
||||
- `getToolConfiguration(toolGroupId, toolName)`: Retrieves the configuration for the specified tool in the given tool group.
|
||||
- `setToolConfiguration(toolGroupId, toolName, config)`: Sets the configuration for the specified tool in the given tool group.
|
||||
|
||||
## Usage
|
||||
|
||||
Here's an example of how to create a new tool group and add tools to it in our basic viewer mode (modes/longitudinal/src/initToolGroups.js)
|
||||
|
||||
```js
|
||||
import { initToolGroups } from '@ohif/extension-cornerstone';
|
||||
import { ToolGroupService } from '@ohif/core';
|
||||
|
||||
const toolGroupService = new ToolGroupService();
|
||||
|
||||
// Create a new tool group
|
||||
const defaultToolGroup = toolGroupService.createToolGroup('default');
|
||||
|
||||
// Define tools for the tool group
|
||||
const tools = {
|
||||
active: [
|
||||
{ toolName: 'WindowLevel', bindings: [{ mouseButton: 1 }] },
|
||||
{ toolName: 'Pan', bindings: [{ mouseButton: 2 }] },
|
||||
{ toolName: 'Zoom', bindings: [{ mouseButton: 3 }] },
|
||||
],
|
||||
passive: [
|
||||
{ toolName: 'Length' },
|
||||
{ toolName: 'ArrowAnnotate' },
|
||||
{ toolName: 'Bidirectional' },
|
||||
],
|
||||
};
|
||||
|
||||
// Add tools to the tool group
|
||||
toolGroupService.addToolsToToolGroup('default', tools);
|
||||
```
|
||||
|
||||
In this example, we create a new `ToolGroupService` instance and use it to create a new tool group with the ID `'default'`. We then define an object `tools` that contains the active and passive tools we want to add to the tool group. Finally, we call the `addToolsToToolGroup` method to add the tools to the newly created tool group.
|
||||
|
||||
:::tip
|
||||
You can begin the viewer with certain toggle tools already active. For example, if you have your 'referencelines' tool enabled, it will be active when the viewer starts, and the icon state will be correctly set to active as well.
|
||||
:::
|
||||
|
||||
```js
|
||||
const tools = {
|
||||
// the reset
|
||||
// enabled
|
||||
enabled: [{ toolName: toolNames.ImageOverlayViewer }, { toolName: toolNames.ReferenceLines }],
|
||||
};
|
||||
```
|
||||
|
||||
|
||||

|
||||
@@ -3,16 +3,15 @@ sidebar_position: 5
|
||||
sidebar_label: Toolbar Service
|
||||
---
|
||||
|
||||
# Toolbar Service
|
||||
# Toolbar **Service**
|
||||
|
||||
## Overview
|
||||
|
||||
`ToolBarService` handles the toolbar section buttons, and what happens when a
|
||||
button is clicked by the user.
|
||||
The `ToolBarService` is a straightforward service designed to handle the toolbar. Its main tasks include adding buttons, configuring them, and organizing button sections. When a button is clicked, it executes the designated commands. In the past, this service was more intricate, managing button states and logic. However, all that functionality has now been transferred to the `ToolBarModule` and evaluators.
|
||||
|
||||
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
|
||||
<iframe src="https://player.vimeo.com/video/547957214?badge=0&autopause=0&player_id=0&app_id=58479" frameBorder="0" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="measurement-report"></iframe>
|
||||
</div>
|
||||
<!-- <div style={{padding:"56.25% 0 0 0", position:"relative"}}>
|
||||
<iframe src="https://player.vimeo.com/video/547957214?badge=0&autopause=0&player_id=0&app_id=58479" frameBorder="0" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} **title**="measurement-report"></iframe>
|
||||
</div> -->
|
||||
|
||||
## Events
|
||||
|
||||
@@ -23,20 +22,7 @@ button is clicked by the user.
|
||||
|
||||
## API
|
||||
|
||||
- `recordInteraction(interaction)`: executes the provided interaction which is
|
||||
an object providing the following properties to the ToolBarService:
|
||||
|
||||
- `interactionType`: can be `tool`, `toggle` and `action`. We will discuss
|
||||
more each type below.
|
||||
- `itemId`: tool name
|
||||
- `groupId`: the Id for the tool button group; e.g., `Wwwc` which holds
|
||||
presets.
|
||||
- `commandName`: if tool has a command attached to run
|
||||
- `commandOptions`: arguments for the command.
|
||||
- `setActive`: Sets a given tool active (not as primary but as secondary)
|
||||
|
||||
- `reset`: reset the state of the toolbarService, set the primary tool to be
|
||||
`Wwwc` and unsubscribe tools that have registered their functions.
|
||||
- `createButtonSection(key, buttons)` : creates a section of buttons in the toolbar with the given key and button Ids
|
||||
|
||||
- `addButtons`: add the button definition to the service.
|
||||
[See below for button definition](#button-definitions).
|
||||
@@ -44,86 +30,26 @@ button is clicked by the user.
|
||||
- `setButtons`: sets the buttons defined in the service. It overrides all the
|
||||
previous buttons
|
||||
|
||||
- `getActiveTools`: returns the active tool + all the toggled-on tools
|
||||
|
||||
- `setDefaultTool`: sets the default tool that will be activated whenever the primary tool is deactivated without activating another/different tool
|
||||
|
||||
## State
|
||||
|
||||
ToolBarService has an internal state that gets updated per tool interaction and
|
||||
tracks the active toolId, state of the buttons that have toggled state, and the
|
||||
group buttons and which tool in each group is active.
|
||||
|
||||
```js
|
||||
state = {
|
||||
primaryToolId: 'Wwwc',
|
||||
toggles: {
|
||||
/* id: true/false */
|
||||
},
|
||||
groups: {
|
||||
/* track most recent click per group...*/
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Interaction type
|
||||
|
||||
There are three main types that a tool can have which is defined in the
|
||||
interaction object.
|
||||
|
||||
- `tool`: setting a tool to be active; e.g., measurement tools
|
||||
- `toggle`: toggling state of a tool; e.g., viewport link (sync)
|
||||
- `action`: performs a registered action outside of the ToolBarService; e.g.,
|
||||
capture
|
||||
|
||||
A _simplified_ implementation of the ToolBarService is:
|
||||
|
||||
```js
|
||||
export default class ToolBarService {
|
||||
/** ... **/
|
||||
recordInteraction(interaction) {
|
||||
/** ... **/
|
||||
switch (interactionType) {
|
||||
case 'action': {
|
||||
break;
|
||||
}
|
||||
case 'tool': {
|
||||
this.state.primaryToolId = itemId;
|
||||
|
||||
commandsManager.runCommand('setToolActive', interaction.commandOptions);
|
||||
break;
|
||||
}
|
||||
case 'toggle': {
|
||||
this.state.toggles[itemId] =
|
||||
this.state.toggles[itemId] === undefined
|
||||
? true
|
||||
: !this.state.toggles[itemId];
|
||||
interaction.commandOptions.toggledState = this.state.toggles[itemId];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Invalid interaction type: ${interactionType}`);
|
||||
}
|
||||
/** ... **/
|
||||
}
|
||||
/** ... **/
|
||||
}
|
||||
```
|
||||
|
||||
## Button Definitions
|
||||
|
||||
|
||||
### Basic
|
||||
|
||||
The simplest toolbarButtons definition has the following properties:
|
||||
|
||||

|
||||
|
||||
|
||||
```js
|
||||
{
|
||||
"id": "Zoom",
|
||||
"type": "ohif.radioGroup",
|
||||
"props": {
|
||||
"type": "tool",
|
||||
"icon": "tool-zoom",
|
||||
"label": "Zoom",
|
||||
id: 'Zoom',
|
||||
uiType: 'ohif.radioGroup',
|
||||
props: {
|
||||
icon: 'tool-zoom',
|
||||
label: 'Zoom',
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
@@ -133,27 +59,20 @@ The simplest toolbarButtons definition has the following properties:
|
||||
"context": "CORNERSTONE"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
evaluate: 'evaluate.cornerstoneTool',
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
| property | description | values |
|
||||
| ---------------- | ----------------------------------------------------------------- | ------------------------------------------- |
|
||||
| `id` | Unique string identifier for the definition | \* |
|
||||
| `type` | Used to determine the button's behaviour | "tool", "toggle", "action" |
|
||||
| `icon` | A string name for an icon supported by the consuming application. | \* |
|
||||
| `label` | User/display friendly to show in UI | \* |
|
||||
| `commands` | (optional) The commands to run when the button is used. It include a commandName, commandOptions, and/or a context | Any command registered by a `CommandModule` |
|
||||
|
||||
There are three main types of toolbar buttons:
|
||||
|
||||
- `tool`: buttons that enable a tool by running the `setToolActive` command with
|
||||
the `commandOptions`
|
||||
- `toggle`: buttons that acts as a toggle: e.g., linking viewports
|
||||
- `action`: buttons that executes an action: e.g., capture button to save
|
||||
screenshot
|
||||
|
||||
## Nested Buttons
|
||||
### Nested (dropdown)
|
||||
|
||||
You can use the `ohif.splitButton` type to build a button with extra tools in
|
||||
the dropdown.
|
||||
@@ -169,91 +88,178 @@ to create `MeasurementTools` nested button
|
||||
|
||||
```js title="modes/longitudinal/src/toolbarButtons.js"
|
||||
{
|
||||
"id": "MeasurementTools",
|
||||
"type": "ohif.splitButton",
|
||||
"props": {
|
||||
"groupId": "MeasurementTools",
|
||||
"isRadio": true,
|
||||
"primary": {
|
||||
"id": "Length",
|
||||
"icon": "tool-length",
|
||||
"label": "Length",
|
||||
"type": "tool",
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "Length"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
},
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "SRLength",
|
||||
"toolGroupId": "SRToolGroup"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
}
|
||||
id: 'MeasurementTools',
|
||||
uiType: 'ohif.splitButton',
|
||||
props: {
|
||||
groupId: 'MeasurementToolsGroupId',
|
||||
// group evaluate to determine which item should move to the top
|
||||
evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList',
|
||||
primary: ToolbarService.createButton({
|
||||
id: 'Length',
|
||||
icon: 'tool-length',
|
||||
label: 'Length',
|
||||
tooltip: 'Length Tool',
|
||||
commands: _createSetToolActiveCommands('Length'),
|
||||
evaluate: 'evaluate.cornerstoneTool',
|
||||
}),
|
||||
secondary: {
|
||||
icon: 'chevron-down',
|
||||
tooltip: 'More Measure Tools',
|
||||
},
|
||||
items: [
|
||||
ToolbarService.createButton({
|
||||
id: 'Length',
|
||||
icon: 'tool-length',
|
||||
label: 'Length',
|
||||
tooltip: 'Length Tool',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: {
|
||||
toolName: 'Length',
|
||||
},
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
{
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: {
|
||||
toolName: 'SRLength',
|
||||
toolGroupId: 'SRToolGroup',
|
||||
},
|
||||
// we can use the setToolActive command for this from Cornerstone commandsModule
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
],
|
||||
evaluate: 'evaluate.cornerstoneTool',
|
||||
}),
|
||||
ToolbarService.createButton({
|
||||
id: 'Bidirectional',
|
||||
icon: 'tool-bidirectional',
|
||||
label: 'Bidirectional',
|
||||
tooltip: 'Bidirectional Tool',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: {
|
||||
toolName: 'Bidirectional',
|
||||
},
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
{
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: {
|
||||
toolName: 'SRBidirectional',
|
||||
toolGroupId: 'SRToolGroup',
|
||||
},
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
],
|
||||
evaluate: 'evaluate.cornerstoneTool',
|
||||
}),
|
||||
],
|
||||
"tooltip": "Length"
|
||||
},
|
||||
"secondary": {
|
||||
"icon": "chevron-down",
|
||||
"label": "",
|
||||
"isActive": true,
|
||||
"tooltip": "More Measure Tools"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": "Bidirectional",
|
||||
"icon": "tool-bidirectional",
|
||||
"label": "Bidirectional",
|
||||
"type": "tool",
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "Bidirectional"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
},
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "SRBidirectional",
|
||||
"toolGroupId": "SRToolGroup"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
}
|
||||
],
|
||||
"tooltip": "Bidirectional Tool"
|
||||
},
|
||||
{
|
||||
"id": "ArrowAnnotate",
|
||||
"icon": "tool-annotate",
|
||||
"label": "Annotation",
|
||||
"type": "tool",
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "ArrowAnnotate"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
},
|
||||
{
|
||||
"commandName": "setToolActive",
|
||||
"commandOptions": {
|
||||
"toolName": "SRArrowAnnotate",
|
||||
"toolGroupId": "SRToolGroup"
|
||||
},
|
||||
"context": "CORNERSTONE"
|
||||
}
|
||||
],
|
||||
"tooltip": "Arrow Annotate"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
:::tip
|
||||
split buttons can have a group evaluator (in the above example `evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList`) which can decide what happens
|
||||
when the user interacts with the buttons. In the above example, we are promoting the button to the primary section if the cornerstone tool is not active in the list of buttons.
|
||||
|
||||
There are other evaluators for instance `evaluate.group.promoteToPrimary`
|
||||
which does not care about the cornerstone tool and promotes the button to the primary section anyway
|
||||
:::
|
||||
|
||||
:::tip
|
||||
If you don't provide a group evaluator nothing would happen and the button will stay in the secondary section.
|
||||
:::
|
||||
|
||||
## Listeners
|
||||
Sometimes you need a tool to listen to specific events in order to react properly.
|
||||
You can add `listeners` for this purpose. We use this pattern for referencelineTools
|
||||
which should set its source of reference upon active viewport change
|
||||
|
||||
Currently you can subscribe to the following events:
|
||||
- `ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED`: when the active viewport changes
|
||||
- `ViewportGridService.EVENTS.VIEWPORTS_READY`: when the viewports are ready in the grid
|
||||
|
||||
|
||||
```js
|
||||
|
||||
const ReferenceLinesListeners: RunCommand = [
|
||||
{
|
||||
commandName: 'setSourceViewportForReferenceLinesTool',
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
];
|
||||
|
||||
ToolbarService.createButton({
|
||||
id: 'ReferenceLines',
|
||||
icon: 'tool-referenceLines',
|
||||
label: 'Reference Lines',
|
||||
tooltip: 'Show Reference Lines',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'setToolEnabled',
|
||||
commandOptions: {
|
||||
toolName: 'ReferenceLines',
|
||||
toggle: true,
|
||||
},
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
],
|
||||
listeners: {
|
||||
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesListeners,
|
||||
[ViewportGridService.EVENTS.VIEWPORTS_READY]: ReferenceLinesListeners,
|
||||
},
|
||||
evaluate: 'evaluate.cornerstoneTool.toggle',
|
||||
}),
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Button Sections
|
||||
In order to organize the buttons, you can create button sections in the toolbar. And
|
||||
assign buttons to each section separately.
|
||||
|
||||
OHIF provides a `primary` section by default. You can add more sections as needed in your UI
|
||||
and use toolbarService to create and manage them. (You can look at the toolBox implementation
|
||||
which take advantage of having a dedicated section for the tools with advanced options,
|
||||
we use that in the segmentation mode).
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
For instance in `longitudinal` mode we are using the `onModeEnter` hook to
|
||||
add the buttons to the toolbarService and assign them to the primary section.
|
||||
|
||||
```js title="modes/longitudinal/src/index.js"
|
||||
toolbarService.addButtons([...toolbarButtons, ...moreTools]);
|
||||
toolbarService.createButtonSection('primary', [
|
||||
'MeasurementTools',
|
||||
'Zoom',
|
||||
'info',
|
||||
'WindowLevel',
|
||||
'Pan',
|
||||
'Capture',
|
||||
'Layout',
|
||||
'MPR',
|
||||
'Crosshairs',
|
||||
'MoreTools',
|
||||
]);
|
||||
```
|
||||
|
||||
as you see we creating the button section and assigning buttons based on their Ids.
|
||||
|
||||
:::tip
|
||||
You can even duplicate the same button in different sections and the button will be
|
||||
in sync in all sections (thanks to the evaluation system).
|
||||
:::
|
||||
|
||||
:::tip
|
||||
we will add more section in the toolbar (other than primary) in the future.
|
||||
:::
|
||||
|
||||
:::note
|
||||
Don't forget to set up your toolGroups to ensure that your buttons function correctly. Buttons serve as a visual interface. When you interact with them, they execute their commands, and evaluators determine their state post-interaction.
|
||||
:::
|
||||
@@ -18,6 +18,8 @@ There are seven events that get publish in `ViewportGridService `:
|
||||
| ACTIVE_VIEWPORT_ID_CHANGED | Fires the Id of the active viewport is changed |
|
||||
| LAYOUT_CHANGED | Fires the layout is changed |
|
||||
| GRID_STATE_CHANGED | Fires when the entire grid state is changed |
|
||||
| VIEWPORTS_READY | Fires when the viewports are ready in the grid |
|
||||
|
||||
## Interface
|
||||
|
||||
For a more detailed look on the options and return values each of these methods
|
||||
|
||||
Reference in new issue
Block a user