feat: Add theme/config-point capability and docs. (#2641)
This is the initial addition of the config point settings to allow run time loading of GUI settings via "theme" files/parameters. The intent is to add an example PR that shows when to use config point, how to configure it, write documentation for it etc.
This commit is contained in:
parent
84d9ec34fc
commit
72bb074f21
@ -186,7 +186,7 @@ const commandsModule = ({ servicesManager, commandsManager }) => {
|
||||
}
|
||||
|
||||
const viewportInfo = getEnabledElement(i);
|
||||
const hasCornerstoneContext =
|
||||
const hasCornerstoneContext = viewportInfo.context &&
|
||||
viewportInfo.context === 'ACTIVE_VIEWPORT::CORNERSTONE';
|
||||
|
||||
if (hasCornerstoneContext) {
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6",
|
||||
"config-point": "^0.3.4",
|
||||
"core-js": "^3.2.1",
|
||||
"cornerstone-core": "2.6.0",
|
||||
"wslink": "^0.1.8"
|
||||
@ -107,17 +108,17 @@
|
||||
"prettier": "^1.18.2",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"serve": "^11.1.0",
|
||||
"shader-loader": "^1.3.1",
|
||||
"start-server-and-test": "^1.10.0",
|
||||
"style-loader": "^1.0.0",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"unused-webpack-plugin": "2.4.0",
|
||||
"webpack": "^5.50.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^5.7.3",
|
||||
"workbox-webpack-plugin": "^6.1.5",
|
||||
"shader-loader": "^1.3.1",
|
||||
"webpack": "^5.50.0",
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
"husky": {
|
||||
|
||||
@ -82,7 +82,7 @@ const StackManager = {
|
||||
return stackMap[displaySetInstanceUID];
|
||||
},
|
||||
/**
|
||||
* Find a stack or reate one if it has not been created yet
|
||||
* Find a stack or create one if it has not been created yet
|
||||
* @param displaySet The set of images to make the stack from
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
|
||||
@ -10,6 +10,20 @@ makes it easier for our community members to keep their "secret sauce" private,
|
||||
and incentives contributions back to the platform. The `@ohif/viewer` project of
|
||||
the platform is the lynchpin that combines everything to create our application.
|
||||
|
||||
There are two configuration mechanisms, one for run-time configuration, allowing
|
||||
for changes to be decided based on including different configuration files as
|
||||
specified by the 'theme' URL parameters. This mechanism is intended for
|
||||
modifications of data exposed as configurable items by the existing code. See
|
||||
sections below on configuring this type of value.
|
||||
|
||||
The other mechanism is the code-configuration mechanism that specifies load
|
||||
time configuration. This is intended to load things that require code level
|
||||
changes to OHIF such as adding a new viewer configuration. This is also used
|
||||
for base definitions that are shared site-wide such as the data sources. This
|
||||
was the original configuration mechanism provided, and some of the configurations
|
||||
specified there are better suited to the run time loading, but are currently
|
||||
left alone as there hasn't been time to move them.
|
||||
|
||||
We maintain a number of common viewer application configurations at
|
||||
[`<root>/platform/viewer/public/configs`][config-dir].
|
||||
|
||||
@ -59,6 +73,40 @@ window.config = {
|
||||
};
|
||||
```
|
||||
|
||||
## Run Time Configuration (Config-Point)
|
||||
There is a library [config-point](https://github.com/OHIF/config-point)
|
||||
used to allow loading of configuration values dynamically,
|
||||
that is, at load time rather than being built into the runtime configuration.
|
||||
A user of OHIF can specify a dynamic configuration by adding one or more theme
|
||||
parameters, for example:
|
||||
```
|
||||
https://ohif.hospital.org/?theme=mgHP&theme=euroKeyboard
|
||||
```
|
||||
to load two hypothetical theme settings files mgHP and euroKeyboard to add
|
||||
mammographic hanging protocols and European keyboard settings.
|
||||
|
||||
A site can add such settings by creating custom files in the deployment
|
||||
directory (which is wherever the deployed OHIF is located.) For a deployment
|
||||
running off a straight build of OHIF, this would be:
|
||||
```
|
||||
...Viewers/platform/viewer/dist/theme/mgHP.json5
|
||||
...Viewers/platform/viewer/dist/theme/euroKeyboard.json5
|
||||
```
|
||||
A site might build such different themes to support various user preferences
|
||||
or site differences between users, such as themes to support specific clinics
|
||||
or differences in user groups such as left on right mammography viewing versus
|
||||
right on left mammography viewing.
|
||||
|
||||
The decision to use the JSON5 parser for this was primarily aimed at allowing
|
||||
comments in the configuration files, an important consideration for sites
|
||||
wanting to document their settings.
|
||||
|
||||
See [theme-configuration](theme-configuration.md) for more details on the
|
||||
specific configuration settings which can be applied.
|
||||
|
||||
See [config-point-service](../platform/services/config-point-service.md) for
|
||||
information on how to add your own config-point based extensions to the code.
|
||||
|
||||
<!--
|
||||
LINKS
|
||||
-->
|
||||
|
||||
287
platform/docs/docs/configuration/theme-configuration.md
Normal file
287
platform/docs/docs/configuration/theme-configuration.md
Normal file
@ -0,0 +1,287 @@
|
||||
# Theme Configuration
|
||||
When adding new theme extendible configuration items, please document
|
||||
them here. See [Theme Configuration with Config Point](#configPoint) on how to modify certain types of
|
||||
configuration values using the config-point defintions.
|
||||
|
||||
## Hanging Protocols
|
||||
It is possible to customize the available hanging protocols by defining them
|
||||
in a new theme file OR by defining the hanging protocols in a custom mode.
|
||||
If done correctly, the mode defined hanging protocols are automatically
|
||||
applied when using a particular view mode, allowing for further customization
|
||||
by theme files.
|
||||
|
||||
The default hanging protocols for the cornerstone mode is defined in
|
||||
themeProtocolProvider.js, in a configuration point named
|
||||
`ThemeProtocols.protocols` To define a new
|
||||
hanging protocol, it is possible to simply extend the protocols list with
|
||||
a new definition, for example, in the file mgHP.json5, the following definition
|
||||
would add a new hanging protocol:
|
||||
```js
|
||||
{
|
||||
ThemeProtocols: {
|
||||
protocols: {
|
||||
MG: {
|
||||
// This is a working HP, but isn't MG specific...
|
||||
id: 'MG',
|
||||
locked: true,
|
||||
hasUpdatedPriorsInformation: false,
|
||||
name: '1x2',
|
||||
createdDate: '2021-11-01T18:32:42.849Z',
|
||||
modifiedDate: '2021-11-01T18:32:42.849Z',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
protocolMatchingRules: [
|
||||
{
|
||||
id: 'NumberOfStudyRelatedSeries>1',
|
||||
weight: 10,
|
||||
attribute: 'NumberOfStudyRelatedSeries',
|
||||
constraint: {
|
||||
greaterThan: {
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
stages: [
|
||||
{
|
||||
name: 'OneByTwo',
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
},
|
||||
},
|
||||
viewport: {},
|
||||
},
|
||||
],
|
||||
numberOfPriorsReferenced: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
The MG protocol doesn't initially exist, so this would add a new hanging
|
||||
protocol, which would be defined in the normal hanging protocol definition.
|
||||
|
||||
## Query List
|
||||
One of the suggested areas for customization is the columns in the query table.
|
||||
TODO
|
||||
|
||||
## Demographics Overlay
|
||||
Another recommended change is to configure the demographics overlay using
|
||||
themes to allow site or mode specific demographics overlays.
|
||||
This should be done at several levels. An overall level, defining the system
|
||||
defaults, and then an overlay for each mode type, to allow mode specific information
|
||||
to be added to the general model. Note how that allows customizing at two
|
||||
or more levels to specify only the required change (aspect oriented programming).
|
||||
|
||||
# <a name="configPoint" />Theme Configuration with Config Point
|
||||
This section explains the syntax used for declaring various types of theme
|
||||
configurations, as well as where to place theme files.
|
||||
|
||||
The configuration schema is based on the
|
||||
[config-point](https://github.com/OHIF/config-point)
|
||||
library. This library is design to allow developers to create configuration
|
||||
points for their code by declaring static values, which can be modified externally.
|
||||
See [config-point-service](../platform/services/config-point-service.md) for
|
||||
internal details and development documentation.
|
||||
|
||||
## Theme Files
|
||||
Theme files are simple json5 definition files,
|
||||
available from the endpoint `https://ohif/theme/`
|
||||
for the site deployment. How the files get there is the responsibility of the
|
||||
site deployment. JSON5 is an extension of JSON,
|
||||
for which the primary addition here is the ability to use comments within the
|
||||
JSON structure. The attribute definition can also be an unquoted string when
|
||||
it is a simple attribute value.
|
||||
|
||||
The default example themes are located in `Viewers/platform/viewer/public/theme/`,
|
||||
for example, there is a theme there named 'theme.json5'. These files are
|
||||
automatically included in a default distribution.
|
||||
|
||||
Their content looks like a static object declaration in JavaScript, where
|
||||
the object has one or more attributes declared. The name of the attribute
|
||||
matches the configuration point for the given configuration item. For example:
|
||||
```js
|
||||
{
|
||||
// This extension modifies the ThemeProtocols
|
||||
// by adding a new hanging protocol for MG and modifying the existing
|
||||
// 2x2 layout to make it not preferred.
|
||||
ThemeProtocols: {
|
||||
// The original protocols declaration was a list,
|
||||
// doing this as an object matches by the id value specified here.
|
||||
// With no ID being found, creates a new entry.
|
||||
protocols: {
|
||||
// MG is just a referencable key, that matches the MG name here
|
||||
// In this case, it does not match any existing id, so it is net new.
|
||||
MG: {
|
||||
id: 'MG',
|
||||
// ... rest of definition of hanging protocol
|
||||
},
|
||||
// 2x2 is an existing hanging protocol, matching by id
|
||||
// Thus, it modifies values rather than replacing/updating it
|
||||
'2x2': {
|
||||
protocolMatchingRules: {
|
||||
// The actual change is weight:5 instead of weight:20, to make this
|
||||
// a non-preferred hanging protocol according to the HP definitions.
|
||||
'NumberOfStudyRelatedSeries>2': { weight: 5 },
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
that modifies the hanging protocols, both by adding and updating
|
||||
elements. Note how this is a deep modification to a value. The intent is
|
||||
to allow modifying configuration values which are heavily nested and/or list
|
||||
based.
|
||||
|
||||
## Working with Objects
|
||||
The object tree is matched by the simple attribute name. The attribute tree
|
||||
is then merged with the existing objects. That is, suppose we have:
|
||||
```js
|
||||
// base object definition
|
||||
baseObject: {
|
||||
value1: 5,
|
||||
value2: { subValue1: true, leftAlone: "value to be unchanged', },
|
||||
}
|
||||
```
|
||||
|
||||
then value1 and subValue1 can be changed by the following theme configuration:
|
||||
```js
|
||||
// ... base object extension:
|
||||
baseObject: {
|
||||
value1: 7,
|
||||
value2: {subValue1: false, subValue2: 'new value', },
|
||||
}
|
||||
```
|
||||
changing value1 to 7 and subValue1 to false, and adding subValue2.
|
||||
|
||||
This is the basic modification for all changes - match the path and replace one
|
||||
or more left (primitive) values.
|
||||
|
||||
## Working with Arrays
|
||||
Arrays in JSON and JSON5 only allow natural plus zero indices, and it isn't easy
|
||||
to specify sparse array values or "next" values. To address this, a base
|
||||
array, declared exactly as a normal array value can be extended with an
|
||||
object where the key of the object matches the extension value in some way.
|
||||
For example, the base array:
|
||||
```js
|
||||
array: ['value1', {id: 'value2', ...}, 'value3']
|
||||
// can be extended with the changes in an array, modifying only
|
||||
// the array[1] and adding an array[3] element.
|
||||
array: [null, {...extensions for value2},null,{id:'new array element'}]
|
||||
// Or, this can be re-written as:
|
||||
array: {
|
||||
'value1': 'new-string-for-value1',
|
||||
'value2': {...extensions for value2},
|
||||
'value4': {id:'new array element',...},
|
||||
}
|
||||
```
|
||||
|
||||
matches value1 by simple comparison, getting `array[0]` as the value to change.
|
||||
Then matching 'value2' to `array[1]` by `array[1].id==='value2'` and finally not
|
||||
matching any value with `id==='new array element'` so adding it to the end
|
||||
(warning, adding to the end MAY be replaced by adding in the middle of the
|
||||
array such that the id is between an id smaller than the new id and larger
|
||||
than the new id, still TBD along with one or two other small enhancements).
|
||||
|
||||
## Custom Mappings (configOperation)
|
||||
There are a number of default custom mappings available for config-point.
|
||||
The general format is:
|
||||
```js
|
||||
valueName: {configOperation: 'opName', value?: 'default-value', reference?: 'named-reference',
|
||||
source?: 'name-of-config-point-for-reference', ...}
|
||||
```
|
||||
which defines an operation to perform instead of using the value literally.
|
||||
The value provided can then be later extended/updated in the usual way, for
|
||||
example:
|
||||
```js
|
||||
valueName: 'alternateStringValue for valueName',
|
||||
```
|
||||
would replace valueName that the config operation acts on.
|
||||
|
||||
### Immediate operations
|
||||
Immediate operations perform the action immediately, and can thus be
|
||||
extended further. They support the additional keys:
|
||||
* position to modify something at a given position
|
||||
|
||||
The immediate operations are:
|
||||
* replace, insert, delete
|
||||
|
||||
For example, supposing there was a configuration point ModalityList, then
|
||||
the following extensions could be applied:
|
||||
```js
|
||||
// Replace the entire list
|
||||
ModalityList: {configOperation: 'replace', value: [
|
||||
'CT', 'MR', 'CR',
|
||||
]}
|
||||
|
||||
// Replace an item at position 3
|
||||
ModalityList: [ {configOperation: 'replace', position: 3, value: {id: 'MRI', description:'Magnetic resonance imaging'}}]
|
||||
|
||||
// Delete an item with value or id 'MR'
|
||||
ModalityList: {
|
||||
'MR': {configOperation: 'delete'},
|
||||
|
||||
// Insert before the item with id 'MG'
|
||||
ModalityList: {
|
||||
'MG': {configOperation: 'insert', value: 'MGTomo'},
|
||||
```
|
||||
|
||||
### Getter Operations
|
||||
Getter operations are performed at the time the attribute is accessed, and then
|
||||
are stored. These attributes typically have parameters:
|
||||
* reference to get a value relative to the current context or the source value.
|
||||
* source to get a value for reference from another ConfigPoint root
|
||||
* value to get a literal, immediate value
|
||||
* transform to modify the value(s)
|
||||
|
||||
The default getter operations are:
|
||||
* sort to generate a sorted list
|
||||
* reference to get another value from elsewhere
|
||||
but the configurations below may also list new getter operations.
|
||||
|
||||
An example for sort might be:
|
||||
```js
|
||||
// Define a basic list:
|
||||
list: ['CT', {id:'MR', name: 'Magnetic Resonance Imaging'}, 'CR']
|
||||
// Declared to be sorted like this (can be done before/after the base declaration)
|
||||
list: {configOperation: 'sort', sortKey: 'priority', usePositionAsSortKey: true},
|
||||
// Then extended via:
|
||||
list: {
|
||||
// Change the priority and add a name to the CT value
|
||||
// This makes it an object instead of a string
|
||||
'CT': {id:'CT', name:'Computed Tomography', priority: 3},
|
||||
}
|
||||
```
|
||||
|
||||
and and example for reference could be:
|
||||
```js
|
||||
// Base definition:
|
||||
MGHangingProtocol: // ... full definition of MG HP here
|
||||
// Reference to it
|
||||
ThemeProtocols: {
|
||||
protocols: {
|
||||
MGHangingProtocol: {configOperation:reference, source:"MGHangingProtocol"},
|
||||
}
|
||||
}
|
||||
|
||||
// Or, a bit of definition for a table element combining two values to render:
|
||||
StudyInstancesColumn: {
|
||||
id: 'StudyInstancesColumn',
|
||||
title: '# of Series and Instances',
|
||||
value: {
|
||||
configOperation: 'reference',
|
||||
value: '`Se: ${study.NumberOfStudyRelatedSeries} Obj: ${study.NumberOfStudyRelatedInstances}`',
|
||||
transform: ${
|
||||
configOperation: "reference",
|
||||
source: 'ConfigPointOperation',
|
||||
reference: 'safeFunction'}
|
||||
```
|
||||
Note how in the last example, the transform itself contains a reference. This
|
||||
is a function that generates a javascript function taking props, where the
|
||||
props are available directly. Thus, this props would need `study` containing
|
||||
the appropriate child objects.
|
||||
224
platform/docs/docs/platform/services/config-point-service.md
Normal file
224
platform/docs/docs/platform/services/config-point-service.md
Normal file
@ -0,0 +1,224 @@
|
||||
# Config Point Service
|
||||
The Config Point service is based on the external library
|
||||
[config-point](https://github.com/OHIF/config-point).
|
||||
It is a service that allows exposing internal "static" configuration data
|
||||
for modification by sites at load time by defining "theme" files. For
|
||||
information on the configuration side of things, see [theme-configuration](../../configuration/theme-conffiguration.md).
|
||||
|
||||
The service isn't a traditional OHIF service avaialble in the services
|
||||
deployment, but is rather a service which exposes static declarations of
|
||||
data as configurable data. For example, suppose a list of modalities
|
||||
was required for the search constraints. The core code might decide to
|
||||
supply such a list by default, but sites may want to customize it to
|
||||
only list the actual modalities they use. Further, they might want to
|
||||
change the name of some of these. The core code could declare the modalities
|
||||
list in a file like this:
|
||||
```js
|
||||
export default const { ModalitiesList } = ConfigPoint.register({
|
||||
ModalitiesList: [
|
||||
"CR",
|
||||
{id: "MR", description: "Magnetic Resonance Imaging"},
|
||||
{id: "CT", name: "Computed Tomography"},
|
||||
]
|
||||
})
|
||||
```
|
||||
The CR modality is just a plain name, whereas MR includes a description,
|
||||
and CT includes an alternate name. That list is used exactly as a straight
|
||||
list for display, so the code needs to understand both simple strings and
|
||||
the simple object definitions with id and/or name and description.
|
||||
|
||||
Now, a site might also happen to have an Ultrasound modality, so they would
|
||||
want to extend the list. They could then follow the instructions in the
|
||||
theme-configuration area to add the "US" device. One way of doing that is
|
||||
by editing the `platform/viewer/public/theme/theme.json5` file, and adding
|
||||
the following element to it:
|
||||
```js
|
||||
{
|
||||
...
|
||||
ModalitiesList: {
|
||||
US: {id: 'US', name: 'Ultrasound'},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The intent of the config point service is to expose a configuration point
|
||||
that can be further modified. The exposed point needs to be basically
|
||||
a constant/static definition. This may involve reworking some of the
|
||||
code design to extract the configuration value from the dynamic code. For
|
||||
example, in the above modalities list, the original declaration is:
|
||||
```js
|
||||
inputProps: {
|
||||
options: [
|
||||
{ value: 'AR', label: 'AR' },
|
||||
{ value: 'ASMT', label: 'ASMT' },
|
||||
...
|
||||
```
|
||||
so the constant should be extracted to it's own file, but it is a fairly
|
||||
simple list, so extracting it that way is fairly easy. A more complex example
|
||||
might be the columns displayed in the search page. These are directly
|
||||
referenced in the WorkList as bits of code that have both the configuration
|
||||
and the ReactJs functionality. To allow configuring this, the change would
|
||||
need to extract what should be displayed into a config point, from the actual
|
||||
rendering logic to render a table.
|
||||
|
||||
A table rendering component might be defined externally, something like:
|
||||
```js
|
||||
import {patientInfoFilter} from './filtersMeta.js';
|
||||
|
||||
export default const {WorkListColumns} = ConfigPoint.register({
|
||||
WorkListColumns: [
|
||||
{ // This one has custom row and query rendering
|
||||
id: 'PatientInfo',
|
||||
rowRender: (props) => {... function to render a row },
|
||||
queryRender: patientInfoFilter,
|
||||
},
|
||||
{ // This one defaults the row and query to fetching StudyDescription
|
||||
id: 'Description',
|
||||
rowData: 'StudyDescription',
|
||||
}
|
||||
... rest of columns
|
||||
],
|
||||
})
|
||||
```
|
||||
Note how this version includes functions as static data, as well as
|
||||
the basic data structure. It would then need to be rendered by iterating
|
||||
over the elements of the WorkListColumns.
|
||||
|
||||
Always the basic idea is that the code extracts a literal declaration of
|
||||
data, defaulting to the base behaviour of the application, allowing it to
|
||||
be exposed later to enhancements in a declarative fashion. The remaining
|
||||
sections below simply expand on the idea with some more advanced concepts.
|
||||
|
||||
## Automatic Sorting of Data
|
||||
Sometimes, data needs to be sorted in the provided order. The above
|
||||
examples show how that can be done, but in other cases, it is desirable
|
||||
to allow sorting the data, either with the initially provided sort order,
|
||||
or with additional sort constraints. This can be done by adding a
|
||||
configOperation to the literal value. The operation is applied at the
|
||||
time the value is first retrieved, and then is cached (unless the value is
|
||||
further updated, in which case it is re-calculated).
|
||||
|
||||
In the WorkListColumns example, this would look like:
|
||||
```js
|
||||
WorkListColumns: {
|
||||
configOperation: 'sort',
|
||||
sortKey: 'priority',
|
||||
usePositionAsSortKey: true,
|
||||
value: [ ... definition above of original columns]
|
||||
},
|
||||
```
|
||||
This would sort the value in the original order it was defined in, and then
|
||||
any news items would get sorted into position.
|
||||
|
||||
## Transform Data
|
||||
It is possible that you want a transformation of the data. For example,
|
||||
there might be a simple declarative form for a value, but you want a custom
|
||||
version to be used for display. For example, setting default values for
|
||||
missing attributes, or for turning a simple string into a full definition of
|
||||
a name. Here is a possible example:
|
||||
```js
|
||||
// Declared as part of the base configuration, which is always included
|
||||
// first
|
||||
configBase: {
|
||||
ModalitiesList: {
|
||||
configOperation: 'reference',
|
||||
transform: list => list.map(str => ({id:str, name:str, value:str})),
|
||||
},
|
||||
},
|
||||
// Then, for this example, the same name is referenced to assign the value,
|
||||
ModalitiesList: ["CR", "CT"],
|
||||
```
|
||||
This type of transform needs to be declared in code because of the need
|
||||
to reference a function, but allows for having a uniform format, but
|
||||
including an easy declarative form.
|
||||
|
||||
## Reference Data
|
||||
Sometimes you want to separate long declarations into their own configuration
|
||||
item. You can do this via the reference operation, for example:
|
||||
```js
|
||||
export default const { HangingProtocols } = ConfigPoint.register({
|
||||
MGHangingProtocol: {
|
||||
rightOnLeft: // Definition for MG Hanging Protocol right on left,
|
||||
leftOnLeft: // Definition for MG hanging protocol left on left
|
||||
},
|
||||
CRHangingProtocol: // Defn for CR hanging protocol
|
||||
...
|
||||
HangingProtocols: {
|
||||
default: // Definition of default HP
|
||||
protocols: [
|
||||
// Reference the child element rightOnLeft of MGHangingProtocols
|
||||
{configOperation: 'reference', source: "MGHangingProtocols", reference: "rightOnLeft"},
|
||||
// Just reference the entire object CRHangingProtocol
|
||||
{configOperation: 'reference', source: 'CRHangingProtocol',},
|
||||
// Reference default within the current config point instance
|
||||
{configOperation: 'reference', reference: 'default',}
|
||||
```
|
||||
|
||||
## Theme Load Timing
|
||||
The theme values are intended to be constants for a given instance of OHIF.
|
||||
However, because the actual theme files are loaded at startup time, if an initial
|
||||
render is performed before all the theme files have been fully loaded, it may
|
||||
be necessary to re-render after all the theme loads have completed. There is
|
||||
a listener service which can be used to listen for load events, and then to
|
||||
re-render the display. It works like:
|
||||
```js
|
||||
if (!this._listenThemeProtocols) {
|
||||
this._listenThemeProtocols = this.listenThemeProtocols.bind(this);
|
||||
}
|
||||
ConfigPoint.addLoadListener(ThemeProtocols, this._listenThemeProtocols);
|
||||
```
|
||||
|
||||
This is NOT intended for listening for programatic changes to the configuration,
|
||||
but is intended only for load time updates. Again, the idea is that the
|
||||
configuration points are constants loaded from configuration files.
|
||||
|
||||
## Mode Changes to Configuration Point
|
||||
If the config points are "constants", then one might ask how different modes
|
||||
can end up apply different configuration values. The answer to that is to
|
||||
add a new configuration point specific to that mode, and to change the referenced
|
||||
name of which configuration gets used. For example, suppose a "Mammo Mode" wanted
|
||||
to specify a bunch of customizations to configuration such as the set of
|
||||
hanging protocols to apply. One way to do that is to have the mode specify
|
||||
the name of the configuration, and then to use the initial/default configuration
|
||||
as a base, and extend it, something like this:
|
||||
```js
|
||||
const {MGHangingProtocols} = ConfigPoint.register({
|
||||
MGHangingProtocols: {
|
||||
// The config base says to START with the value from the named
|
||||
// config point as the base values.
|
||||
configBase: 'HangingProtocols',
|
||||
// Now, just extend the protocols list with the mg protocols.
|
||||
protocols: {
|
||||
mgProtocol1: ...
|
||||
mgProtocol2: ...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
As an alternative to extending the protocols, they can be replaced via:
|
||||
```js
|
||||
protocols: {
|
||||
configOperation: 'replace',
|
||||
value: [
|
||||
// mgProtocol1,
|
||||
// mgProtocol2
|
||||
]
|
||||
}
|
||||
```
|
||||
The replace operation is an immediate operation, and allows the protocols
|
||||
list to be extended in the normal fashion.
|
||||
|
||||
The MGHangingProtocols value would then just be set as the mode hanging
|
||||
protocols object, and it would then be used directly. It could also have
|
||||
been set by name instead of value, depending on the desired context.
|
||||
|
||||
The site can then extend the MGHangingProtocols in the usual way, by creating
|
||||
custom theme files.
|
||||
|
||||
# Concluding Remarks
|
||||
Use the configuration point service to extract things that might need to be
|
||||
configured by some sites by extracting the data into a constant declaration,
|
||||
and then register it with config point to expose it. Then, document
|
||||
your configuration points in the [theme-configuration](../../configuration/theme-configuration.md)
|
||||
guide. That will allow sites to make declarative configuration changes to
|
||||
the values.
|
||||
@ -57,6 +57,7 @@
|
||||
"@ohif/ui": "^2.0.0",
|
||||
"@types/react": "^16.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"config-point": "^0.3.4",
|
||||
"core-js": "^3.16.1",
|
||||
"cornerstone-math": "^0.1.9",
|
||||
"cornerstone-tools": "6.0.2",
|
||||
|
||||
3
platform/viewer/public/theme/theme.json5
Normal file
3
platform/viewer/public/theme/theme.json5
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
// Currently empty because nothing is configurable yet with themes
|
||||
}
|
||||
@ -17,6 +17,7 @@ import {
|
||||
errorHandler
|
||||
// utils,
|
||||
} from '@ohif/core';
|
||||
import ConfigPoint from 'config-point';
|
||||
|
||||
/**
|
||||
* @param {object|func} appConfigOrFunc - application configuration, or a function that returns application configuration
|
||||
@ -49,6 +50,10 @@ function appInit(appConfigOrFunc, defaultExtensions) {
|
||||
appConfig,
|
||||
});
|
||||
|
||||
// Load the default theme settings
|
||||
const defaultTheme = config && config.defaultTheme || 'theme';
|
||||
ConfigPoint.load(defaultTheme, '/theme', 'theme');
|
||||
|
||||
servicesManager.registerServices([
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
|
||||
29
yarn.lock
29
yarn.lock
@ -1114,7 +1114,7 @@
|
||||
core-js-pure "^3.15.0"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@7.1.2", "@babel/runtime@7.7.6", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@7.1.2", "@babel/runtime@7.7.6", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.7.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f"
|
||||
integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==
|
||||
@ -5238,9 +5238,9 @@ caniuse-api@^3.0.0:
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001181, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001243:
|
||||
version "1.0.30001244"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001244.tgz#a6dc49ad5fa02d81d04373ec3f5ceabc3da06abf"
|
||||
integrity sha512-Wb4UFZPkPoJoKKVfELPWytRzpemjP/s0pe22NriANru1NoI+5bGNxzKtk7edYL8rmCWTfQO8eRiF0pn1Dqzx7Q==
|
||||
version "1.0.30001291"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001291.tgz"
|
||||
integrity sha512-roMV5V0HNGgJ88s42eE70sstqGW/gwFndosYrikHthw98N5tLnOTxFqMLQjZVRxTWFlJ4rn+MsgXrR7MDPY4jA==
|
||||
|
||||
capture-exit@^2.0.0:
|
||||
version "2.0.0"
|
||||
@ -5872,6 +5872,14 @@ config-chain@^1.1.11:
|
||||
ini "^1.3.4"
|
||||
proto-list "~1.2.1"
|
||||
|
||||
config-point@^0.3.4:
|
||||
version "0.3.4"
|
||||
resolved "https://registry.yarnpkg.com/config-point/-/config-point-0.3.4.tgz#ac4ec8f39432400b6f31b0fe5d6edcc18fe8650a"
|
||||
integrity sha512-/5heLx9DqfZKduBF09w64FPxLzPBD+yYepNvGS6rexUVfIBCKiE0hQkALvriqEzX6MvEA5U2gxdrd+mQLlTCtw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.6"
|
||||
json5 "^2.2.0"
|
||||
|
||||
configstore@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
|
||||
@ -7000,9 +7008,9 @@ dezalgo@^1.0.0:
|
||||
wrappy "1"
|
||||
|
||||
dicom-parser@^1.8.9:
|
||||
version "1.8.9"
|
||||
resolved "https://registry.yarnpkg.com/dicom-parser/-/dicom-parser-1.8.9.tgz#c69b036b8234df1cfad9978d0d6f0caad1582e74"
|
||||
integrity sha512-OdLBIh460sy0aVeBZ/XSjbzEvXDS18XWxTdTinYmIHNi2jZfo6UuxscX4/VuYslduQ9LaeY6sdEXe9P9e6qlvA==
|
||||
version "1.8.11"
|
||||
resolved "https://registry.yarnpkg.com/dicom-parser/-/dicom-parser-1.8.11.tgz#ffd86289a6b85bb0047379084b57fcc1b28fe7f8"
|
||||
integrity sha512-KbxEDqv4Stynv5AxDh90+jY7MtPvI2uyOG62yD8zKu/pcZqoQYfPX44rB0ULjpxt6YIfXfajrs7B1L97RbjNsg==
|
||||
|
||||
dicomweb-client@^0.6.0:
|
||||
version "0.6.0"
|
||||
@ -15480,11 +15488,16 @@ regenerate@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
|
||||
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
|
||||
|
||||
regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4:
|
||||
regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4:
|
||||
version "0.13.7"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
|
||||
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
|
||||
|
||||
regenerator-runtime@^0.13.2:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
|
||||
|
||||
regenerator-transform@^0.14.2:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user