feat(WorkList): New Study List (WorkList based on ui-next); old study list renamed to LegacyWorklist (#6005)

---------

Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-05-28 12:26:30 -04:00
1 parent 9e7a3586ce
commit daae4c144e
121 files changed
+8071 -1468

No files matched your search

@@ -132,7 +132,7 @@ Here are a list of some options available:
- `requestTransferSyntaxUID` : Request a specific Transfer syntax from dicom web server ex: 1.2.840.10008.1.2.4.80 (applied only if acceptHeader is not set)
- `omitQuotationForMultipartRequest`: Some servers (e.g., .NET) require the `multipart/related` request to be sent without quotation marks. Defaults to `false`. If your server doesn't require this, then setting this flag to `true` might improve performance (by removing the need for preflight requests). Also note that
if auth headers are used, a preflight request is required.
- `maxNumRequests`: The maximum number of requests to allow in parallel. It is an object with keys of `interaction`, `thumbnail`, and `prefetch`. You can specify a specific number for each type.
- `maxNumRequests`: The maximum number of requests to allow in parallel. It is an object with keys of `interaction`, `thumbnail`, and `prefetch`. You can specify a specific number for each type. For `thumbnail`, a small pool (around `5`) is recommended: the study list preview panel fetches a thumbnail per series in parallel, and a larger pool yields little throughput benefit while risking server overload and contention with `interaction`/`prefetch` requests.
- `modesConfiguration`: Allows overriding modes configuration.
- Example config:
```js
@@ -187,6 +187,43 @@ For DICOM video and PDF it has been found that Orthanc delivers multipart, while
To learn more about how you can configure the OHIF Viewer, check out our
[Configuration Guide](../configurationFiles.md).
#### `thumbnailRendering`
Optional. Controls how thumbnail images are requested.
| Value | Behavior |
| ----- | -------- |
| `wadors` (default) | Uses WADO-RS retrieval for thumbnail image content |
| `thumbnailDirect` | Uses the thumbnail URL directly as the image source. Use this only when the archive serves that URL without auth headers |
| `thumbnail` | Uses the WADO-RS `.../thumbnail` endpoint |
| `rendered` | Uses the WADO-RS `.../rendered` endpoint |
Use `thumbnail` or `rendered` when the archive supports dedicated thumbnail/rendered responses. If either is selected, `thumbnailRequestStrategy` controls how the image data is fetched.
#### `thumbnailRequestStrategy`
Optional. Controls how the app retrieves the data for various thumbnails (e.g. side study panel study browser and study list preview panel) when
`thumbnailRendering` is `thumbnail` or `rendered`. It does not apply to `wadors` or `thumbnailDirect`.
| Value | Behavior |
| ----- | -------- |
| `bulkDataRetrieve` (default) | Uses the DICOMweb client's bulk data retrieve API, consistent with other bulk data reads |
| `fetch` | Performs an authenticated HTTP `GET` to the WADO-RS `.../thumbnail` or `.../rendered` URL and builds a blob URL from the JPEG response. Prefer this when the archive serves a plain image body and bulk data retrieve is incompatible or unreliable. |
Example (Orthanc-style plain fetch for thumbnails):
```js
thumbnailRendering: 'rendered',
thumbnailRequestStrategy: 'fetch',
```
#### `queryLimit`
Optional. The maximum number of studies requested from the server for the study list, passed as the `limit` query parameter to data sources that honor it. Paging is handled in the OHIF client, so this value caps how many studies a single search returns. When not specified, it defaults to `101`.
```js
queryLimit: 101,
```
### DICOM PDF
See the [`singlepart`](#singlepart) data source configuration option.
@@ -0,0 +1,45 @@
---
sidebar_position: 6
sidebar_label: Study List paging
title: Study list paging and the query limit
---
# Study list paging and the query limit
The study-list data fetch in `DataSourceWrapper` was simplified. This affects **both** the new `WorkList` and the `LegacyWorkList`, since both receive their studies from `DataSourceWrapper`.
## What changed
Previously, for data sources that support `offset`/`limit`, the wrapper derived a server-side `offset` from the current page and re-queried as you paged — a "rolling window" that let you page past the first result window.
Now the wrapper issues a **single query** with `offset: 0` and `limit: queryLimit`, and the study list paginates **client-side** over the returned studies. Changing pages no longer re-queries the server.
That cap was previously a **hard-coded `101`** in the application. It is now the per-data-source `queryLimit` configuration option, still defaulting to `101` — so it can be raised (or lowered) per data source instead of requiring a code change.
On data sources that honor `offset`, this is a deliberate behavior change: studies beyond `queryLimit` are no longer reachable from the study list, whereas previously you could page into them.
## Why the change
The previous, server-paged behavior was difficult to reason about and not consistently accurate:
- **Sorting applied only to the fetched window.** Sorting was performed client-side over the returned results (and only when the total was below the limit), so with large result sets the on-screen order did not represent a true sort across all matching studies — even though the sort controls implied that it did.
- **The study count was approximate.** The total handed to the pager was an estimate rather than the real number of matching studies.
- **Paging across the window boundary was fragile.** It relied on the page size evenly dividing the window and on the server honoring `offset`, so navigating near or beyond the result cap could surface duplicate or empty pages.
The new single-fetch, client-paginated model trades reach (a hard `queryLimit` cap) for predictable, consistent ordering and paging within that capped set.
## What you may need to do
The list can show at most `queryLimit` studies (default **101**). If a deployment needs to reach more studies than that, either:
- **narrow the search** (date range, MRN, accession, modality) so the matching studies fall under the cap, or
- **raise the cap** with the per-data-source `queryLimit` option:
```js
configuration: {
// ...
queryLimit: 500,
},
```
`queryLimit` is only honored by servers that support the `limit` query parameter; servers that ignore it return whatever they return. See the [DICOMweb data source options](../../configuration/dataSources/dicom-web.md).
@@ -0,0 +1,31 @@
---
sidebar_position: 5
sidebar_label: formatDICOMDate
title: formatDICOMDate options object
---
# formatDICOMDate options object
`formatDICOMDate` (exported from `@ohif/ui-next`) now takes its optional
arguments as a single options object instead of positional parameters.
**Before (3.12):**
```ts
formatDICOMDate(date, 'YYYY-MM-DD');
```
**After (3.13):**
```ts
formatDICOMDate(date, { strFormat: 'YYYY-MM-DD' });
```
The available options are:
- `strFormat` — explicit output format; overrides the locale's `Common:localDateFormat`.
- `fallbackFormat` — format used only when the active locale doesn't define `Common:localDateFormat` (defaults to `MMM D, YYYY`).
- `invalidFallback` — value returned for empty/unparseable input; when omitted, the prior lenient behavior is preserved.
Callers that passed only the `date` argument (including passing `formatDICOMDate`
directly as a `formatDate` formatter) are unaffected.
@@ -0,0 +1,49 @@
---
sidebar_position: 4
sidebar_label: WorkList
title: WorkList route rename
---
# WorkList route rename
3.13 ships a new study-list at `/`. The 3.12 study-list code has been preserved and renamed to `LegacyWorkList`; what is now mounted at `/` by default is the new `WorkList`.
If you imported the 3.12 `WorkList` directly from `platform/app`, update the import path:
**Before (3.12):**
```ts
import WorkList from 'path/to/routes/WorkList/WorkList';
```
**After (3.13):**
```ts
import LegacyWorkList from 'path/to/routes/LegacyWorkList/LegacyWorkList';
```
## Opting back into the legacy study list
If you need more time to migrate, set the new `workList.variant` customization to `'legacy'` to mount `LegacyWorkList` at `/`:
```js
window.config = {
customizationService: [
{
'workList.variant': {
$set: 'legacy',
},
},
],
};
```
See the [Work List customization docs](../../platform/services/customization-service/WorkList.md) for details.
## Thumbnail request concurrency
The new study list's preview panel fetches a thumbnail for each series in the selected study, in parallel. To keep that from saturating the connection and delaying viewer navigation, the parallel thumbnail pool is now bounded (mirroring Cornerstone3D's `imageLoadPoolManager` thumbnail limit), and the shipped example configs set `maxNumRequests.thumbnail` to `5`.
The shipped configs previously used `75`. In practice that was higher than this workload benefits from — beyond a handful of concurrent requests there is little throughput gain, while a large pool can crowd out interaction and prefetch requests and put unnecessary load on the server. If your configuration is based on a shipped config (or otherwise sets `maxNumRequests.thumbnail`), consider lowering it to around `5`.
When the value is not configured, the preview panel already defaults to `5`, so this only affects configurations that set it explicitly.
@@ -0,0 +1,175 @@
---
title: Work List Customization
summary: Documentation for configuring the OHIF WorkList study-list route — selecting between the new (default) and legacy variants, the preview panel's series view (thumbnails, list, or both), and the columns shown in the study-list table.
sidebar_position: 9
---
# Work List
The `workList.*` namespace customizes the WorkList study-list route used as the default landing page in OHIF.
With the exception of `workList.variant` itself, the customizations below only apply when `workList.variant` is `'default'`; they are ignored when the legacy study list is mounted.
## `workList.variant`
Selects which study-list route is mounted at `/`.
- `'default'` (default customization value): the new ui-next WorkList, introduced in 3.13.
- `'legacy'`: the pre-3.13 WorkList (internally `LegacyWorkList`). Use this as an opt-out while migrating to the new study list.
The customization is read once during route registration, so changing it requires a reload.
## `workList.previewSeriesView`
Controls which series views are available in the preview panel that opens to the right of the study list.
- `'all'` (default customization value): the thumbnails/list toggle is visible. The initial preview view is thumbnails.
- `'thumbnails'`: the toggle is hidden; the preview is locked to thumbnails.
- `'list'`: the toggle is hidden; the preview is locked to the series list.
Note: the preview is forced to `'list'` when the active data source either declares `thumbnailRendering` as `'wadors'` or `'thumbnailDirect'`, or declares `thumbnailRequestStrategy` as `'bulkDataRetrieve'` (the default value for `thumbnailRequestStrategy`). In those cases, the customization is ignored and the technical override wins.
:::tip
The customizations below often involve functions — `workList.renderPreviewContent` and `workList.settingsMenuItems` are functions, and `workList.columns` (a value) commonly carries cell/header renderers or an `$apply` transform. Those functions frequently need access to React hooks, the services manager, or the commands manager (e.g. to open modals, navigate, run commands, or build translated labels). They can be set from `window.config`, but they're generally easier to author in a custom extension's `getCustomizationModule`, where the services manager is in scope and components can use hooks normally. Plain config is best suited to simple tweaks like reordering, removing, or inserting items that only need static handlers.
:::
## `workList.columns`
The column set for the WorkList table, registered as a **value** — `ColumnDef<StudyRow, unknown>[]` — rather than a function. The default is `StudyList.defaultColumns`, so out of the box the table shows `Patient`, `MRN`, `Study Date`, `Modalities`, `Description`, `Accession`, `Instances`, and a trailing actions column in that order.
Because it is a plain array, you customize it with [immutability-helper](https://github.com/kolodny/immutability-helper) commands:
| Command | Use |
| --- | --- |
| `$splice` | reorder, insert, or remove columns |
| `$set` / `$merge` | tweak a column's `meta` (label, `minWidth`, `priority`, `align`) |
| `$set` a `cell`/`header` | replace a column's renderer |
| `$apply: (cols) => cols` | run your own function over the current columns and return the new array |
The first three commands cover the common, declarative tweaks. `$apply` is the general-purpose option for anything they don't express cleanly: instead of describing the change with a command, you receive the current `ColumnDef[]` and return the array you want. Because it's a plain function you can use normal JavaScript — `find`, `filter`, `map`, `slice`, conditionals — which makes it the right choice for moves, conditional inserts, or any edit that should be driven by a column's `id` rather than its position. For example, reordering by id:
```ts
'workList.columns': {
$apply: columns => {
// Move "Modalities" to the front, leaving everything else in order.
const modalities = columns.find(c => c.id === 'modalities');
return modalities ? [modalities, ...columns.filter(c => c.id !== 'modalities')] : columns;
},
}
```
For a simple, display-only column, `StudyList.textColumn(id, label, meta?)` fills in the accessor/header/cell wiring for you:
```ts
window.config = {
customizationService: [
{
'workList.columns': {
// Insert before the trailing actions column so it stays at row end.
$apply: columns => {
const at = columns.findIndex(c => c.id === 'actions');
const referring = StudyList.textColumn('referringPhysicianName', 'Referring Physician');
return [...columns.slice(0, at), referring, ...columns.slice(at)];
},
},
},
],
};
```
A pure-data tweak (e.g. relabel) needs no function at all:
```ts
'workList.columns': { 2: { meta: { label: { $set: 'Study Date / Time' } } } }
```
### Surfacing an attribute the data source doesn't map yet
A column can only display data that's already on the study row. The default DICOMweb data source maps a fixed set of fields (`patientName`, `mrn`, `date`/`time`, `accession`, `description`, `modalities`, `instances`, `studyInstanceUid`, `referringPhysicianName`). To add a column for a DICOM attribute outside that set — say **Requesting Physician** `(0032,1032)` — extend the QIDO handling in `extensions/default/src/DicomWebDataSource/qido.js`:
1. **Request the tag** — add it to `includefield` in `mapParams`, so the server is asked to return it:
```js
const commaSeparatedFields = [
'00081030', // Study Description
'00080060', // Modality
'00080090', // Referring Physician's Name
'00321032', // Requesting Physician
].join(',');
```
2. **Map it onto the row** — in `processResults` (a `PN`-VR field, so it goes through `formatPN`/`getName` like `patientName`):
```js
requestingPhysician: utils.formatPN(getName(qidoStudy['00321032'])) || '',
```
Now any column can read `row.requestingPhysician` (e.g. `StudyList.textColumn('requestingPhysician', 'Requesting Physician')`). Note that **`StudyRow` does not need editing** — it carries an index signature, so data-source-mapped fields are readable without a type change.
Two caveats: the server must actually return the tag (it has to support `includefield` — see the data source's `qidoSupportsIncludeField` — and the studies must carry the attribute), and this is a data-source-wide change, not scoped to the worklist.
### Gotchas and limitations
- **Renderers aren't serializable.** A column's `accessorFn`, `cell`, `header`, `filterFn`, and `sortingFn` are functions. `$set`/`$push` accept them, but a column that renders anything beyond plain text still requires code — you can't express it as pure JSON config. `StudyList.textColumn` covers the simple text case.
- **The `actions` column should stay last (cosmetic).** Its hover menu is right-aligned to anchor the row end, so placing it mid-row just looks wrong — it's not a functional requirement. Insert new columns *before* it (e.g. `$splice` at its index, or the `$apply` pattern above); a bare `$push` lands *after* it, leaving the actions menu mid-row.
- **Index-based commands are position-fragile.** `{ 2: { … } }` targets whatever is at index 2, which shifts if earlier columns are added/removed. Prefer `$apply` with a `findIndex`/`id` lookup for edits that should survive reordering.
- If the merged value is not an array, WorkList falls back to `StudyList.defaultColumns`.
## `workList.renderPreviewContent`
Render function for the preview panel that opens to the right of the study list. The customization receives the host React and the same data the built-in renderer uses — series and thumbnails are fetched by the `SidePanelPreview` shell and passed in as props.
```ts
type PreviewContentProps = {
study: StudyRow | null;
series: any[];
seriesView: 'all' | 'thumbnails' | 'list';
onThumbnailImageError: (seriesUID: string) => void;
};
type RenderPreviewContent = (
React: typeof import('react'),
props: PreviewContentProps
) => React.ReactNode;
```
### Props
- **`study`** — the currently selected `StudyRow` (`null` when no study is selected). Useful fields include `studyInstanceUid`, `patientName`, `mrn`, `date`, `modalities`, `description`, `accession`, and `instances`.
- **`series`** — the series belonging to `study`, sorted by series date. Each item has the raw fields returned by the data source (`seriesInstanceUid`, `modality`, `description`, `seriesDate`, `seriesNumber`, `numSeriesInstances`, etc.) plus a `thumbnailStatus` added by the shell:
- `{ status: 'loading' }` — a thumbnail fetch is in flight.
- `{ status: 'ready', src }` — `src` is the URL (often a `blob:` URL) you can render in an `<img>`.
- `{ status: 'notAvailable' }` — the fetch failed or `onThumbnailImageError` was called for this series.
- `{ status: 'notApplicable' }` — the modality has no displayable thumbnail (e.g. SR, KO).
- **`seriesView`** — `'all' | 'thumbnails' | 'list'`. Resolved from `workList.previewSeriesView`, with `'list'` forced when the active data source uses `wadors`/`thumbnailDirect` rendering or `bulkDataRetrieve` retrieval. Honor it if you want to respect the user's toggle and the data-source constraints; ignore it if your custom layout doesn't have a thumbnails/list distinction.
- **`onThumbnailImageError`** — call with a series UID when an `<img>` you render fails to load. The shell marks that series as `notAvailable` and revokes its blob URL if needed. Wire it to your image element's `onError` to keep the state consistent.
Use this customization to change the preview layout (e.g. a different patient summary, a custom series grid) while keeping the fetch, abort-on-selection-change, and bounded thumbnail worker pool intact. When the customization is unset (the default) or not a function, WorkList uses the built-in `<StudyList.PreviewContainer>` layout.
## `workList.settingsMenuItems`
Builds the items in the WorkList settings popover (the gear menu in the top right). The customization is a function that receives the default items and must return a `SettingsMenuItem[]`.
```ts
type SettingsMenuItem = {
id: string;
label: React.ReactNode;
onClick: () => void;
};
type WorkListSettingsMenuItems = (defaults: SettingsMenuItem[]) => SettingsMenuItem[];
```
The default items are:
- `about` — opens the About modal (`ohif.aboutModal` customization).
- `userPreferences` — opens the User Preferences modal (`ohif.userPreferencesModal` customization).
- `logout` — only included when `appConfig.oidc` is configured; navigates to `/logout`.
Use it to reorder, remove, or insert items (e.g. a "Help" link, a "Send feedback" action) without rebuilding the popover shell. If the customization returns a non-array value, WorkList falls back to the defaults.
import { workListCustomizations, TableGenerator } from './sampleCustomizations';
{TableGenerator(workListCustomizations)}
@@ -356,6 +356,215 @@ window.config = {
},
];
export const workListCustomizations = [
{
id: 'workList.variant',
description: (
<>
Selects which study-list route is mounted at <code>/</code>. Use <code>'default'</code>{' '}
(default customization value) for the new ui-next WorkList introduced in 3.13. Use{' '}
<code>'legacy'</code> to mount the pre-3.13 WorkList (internally{' '}
<code>LegacyWorkList</code>) as an opt-out while migrating. The customization is read once
during route registration, so changing it requires a reload.
</>
),
default: 'default',
configuration: `
window.config = {
// rest of window config
customizationService: [
{
'workList.variant': {
$set: 'legacy',
},
},
],
};
`,
},
{
id: 'workList.previewSeriesView',
description: (
<>
Controls which series views are available in the WorkList preview panel. Use{' '}
<code>'all'</code> (default customization value) to show the thumbnails/list toggle. The
initial preview view is thumbnails. Use <code>'thumbnails'</code> to lock the preview to
thumbnails, or <code>'list'</code> to lock it to the series list. The preview is forced to{' '}
<code>'list'</code> when the active data source declares <code>thumbnailRendering</code> as{' '}
<code>'wadors'</code> or <code>'thumbnailDirect'</code>, or declares{' '}
<code>thumbnailRequestStrategy</code> as <code>'bulkDataRetrieve'</code> (its default
value), regardless of this setting. Currently only applies when <code>workList.variant</code> is{' '}
<code>'default'</code>.
</>
),
default: 'all',
configuration: `
window.config = {
// rest of window config
customizationService: [
{
'workList.previewSeriesView': {
$set: 'list',
},
},
],
};
`,
},
{
id: 'workList.columns',
description: (
<>
The column set for the WorkList table, as a <code>ColumnDef[]</code> value (default:{' '}
<code>StudyList.defaultColumns</code>). Because it is a plain array, override it with
immutability-helper commands — <code>$splice</code> to reorder/insert/remove,{' '}
<code>$set</code>/<code>$merge</code> to tweak <code>meta</code> (label, width, priority),
or <code>$apply</code> — a function that receives the current columns and returns the new
array — for anything the other commands don't express cleanly (moves, conditional inserts,
or edits keyed off a column's <code>id</code> rather than its position). Use{' '}
<code>StudyList.textColumn(id, label, meta?)</code> for a simple display-only column.
Gotchas: a column's <code>cell</code>/<code>accessorFn</code>/etc. are functions (not
serializable, so non-text columns still need code); the trailing <code>actions</code>{' '}
column should stay last for correct layout (cosmetic, not required), so insert{' '}
<em>before</em> it with <code>$splice</code> rather than <code>$push</code>; and
index-based edits are position-fragile (prefer <code>$apply</code>{' '}
for id-based changes). If the merged value is not an array, WorkList falls back to the
defaults. Currently only applies when <code>workList.variant</code> is{' '}
<code>'default'</code>.
</>
),
default: 'StudyList.defaultColumns',
configuration: `
window.config = {
// rest of window config
customizationService: [
{
'workList.columns': {
// Insert a Referring Physician column before the trailing actions column.
$apply: (columns) => {
const actionsIndex = columns.findIndex((c) => c.id === 'actions');
const at = actionsIndex === -1 ? columns.length : actionsIndex;
const referring = StudyList.textColumn('referringPhysicianName', 'Referring Physician');
return [...columns.slice(0, at), referring, ...columns.slice(at)];
},
},
},
],
};
`,
},
{
id: 'workList.renderPreviewContent',
description: (
<>
Render function for the preview panel content. Receives the host React and{' '}
<code>{'{ study, series, seriesView, onThumbnailImageError }'}</code> — the same data the
built-in renderer uses, with series and thumbnails already fetched by the{' '}
<code>SidePanelPreview</code> shell.
<ul>
<li>
<code>study</code>: the selected <code>StudyRow</code>, or <code>null</code>.
</li>
<li>
<code>series</code>: the study's series with raw data-source fields (
<code>seriesInstanceUid</code>, <code>modality</code>, <code>description</code>,{' '}
<code>seriesDate</code>, <code>seriesNumber</code>, <code>numSeriesInstances</code>,
etc.) plus a <code>thumbnailStatus</code> added by the shell, which is one of{' '}
<code>{"{ status: 'loading' }"}</code>, <code>{"{ status: 'ready', src }"}</code>,{' '}
<code>{"{ status: 'notAvailable' }"}</code>, or{' '}
<code>{"{ status: 'notApplicable' }"}</code>. Use <code>src</code> from the{' '}
<code>'ready'</code> form as the <code>{'<img>'}</code> source.
</li>
<li>
<code>seriesView</code>: <code>'all' | 'thumbnails' | 'list'</code>, resolved from{' '}
<code>workList.previewSeriesView</code> with <code>'list'</code> forced for data
sources that can't produce thumbnails.
</li>
<li>
<code>onThumbnailImageError(seriesUID)</code>: call when an <code>{'<img>'}</code>{' '}
you render fails to load; the shell marks that series as <code>notAvailable</code>{' '}
and revokes its blob URL if needed.
</li>
</ul>
Use this to change the preview layout while keeping the fetch, abort, and thumbnail
worker-pool logic intact. When unset, the built-in{' '}
<code>{'<StudyList.PreviewContainer>'}</code> layout is used. Currently only applies when{' '}
<code>workList.variant</code> is <code>'default'</code>.
</>
),
default: 'undefined',
configuration: `
window.config = {
// rest of window config
customizationService: [
{
'workList.renderPreviewContent': {
$set: function (React, { study, series, seriesView, onThumbnailImageError }) {
// Render whatever layout you like using the data the shell provides.
return React.createElement(
'div',
{ className: 'flex h-full flex-col bg-black p-4 text-white' },
React.createElement('h2', null, study?.patientName ?? 'No study selected'),
React.createElement(
'ul',
{ className: 'mt-2 flex flex-col gap-2' },
series.map((s) =>
React.createElement(
'li',
{ key: s.seriesInstanceUid },
s.description
)
)
)
);
},
},
},
],
};
`,
},
{
id: 'workList.settingsMenuItems',
description: (
<>
Builds the items in the WorkList settings popover (the gear menu in the top right). The
customization is a function that receives the default items and must return a{' '}
<code>SettingsMenuItem[]</code> (each <code>{'{ id, label, onClick }'}</code>). The
defaults are <code>about</code>, <code>userPreferences</code>, and (when{' '}
<code>appConfig.oidc</code> is configured) <code>logout</code>. Use it to reorder, remove,
or insert items without rebuilding the popover shell. If the customization returns a
non-array value, WorkList falls back to the defaults. Currently only applies when{' '}
<code>workList.variant</code> is <code>'default'</code>.
</>
),
default: '(defaults) => defaults',
configuration: `
window.config = {
// rest of window config
customizationService: [
{
'workList.settingsMenuItems': {
// Remove "User Preferences" and add a custom "Help" item at the top.
$set: (defaults) => {
const filtered = defaults.filter((i) => i.id !== 'userPreferences');
return [
{
id: 'help',
label: 'Help',
onClick: () => window.open('https://docs.example.com', '_blank'),
},
...filtered,
];
},
},
},
],
};
`,
},
];
export const customizations = [
{
id: 'ohif.hotkeyBindings',
@@ -1022,89 +1231,47 @@ window.config = {
},
{
id: 'ohif.aboutModal',
description: 'The About modal',
description: (
<>
Replaces the About modal. The customization value is a React component; see{' '}
<code>extensions/default/src/customizations/aboutModalCustomization.tsx</code> for the
default and the <code>AboutModal</code> compound API. The consumer also reads optional{' '}
<code>title</code> and <code>containerClassName</code> static properties off the
component; both fall back to sensible defaults when omitted.
</>
),
image: aboutModal,
default: 'Our own default component',
configurationIntro: (
<p style={{ margin: 0 }}>
Easiest to register from a custom extension's <code>getCustomizationModule</code>, where
JSX and the <code>@ohif/ui-next</code> imports work normally. See{' '}
<code>aboutModalCustomization.tsx</code> for the full default and the rest of the{' '}
<code>AboutModal</code> compound API.
</p>
),
configuration: `
window.config = {
// rest of window config
import { AboutModal } from '@ohif/ui-next';
// You can use the component from AboutModal
// to build your own custom component
customizationService: [
{
'ohif.aboutModal': {
$set: CustomizedComponent,
},
},
],
};
`,
},
{
id: 'viewportDownload.warningMessage',
description: 'Customizes the warning message for the viewport download form.',
image: viewportDownloadWarning,
default: {
enabled: true,
value: 'Not For Diagnostic Use',
},
configuration: `
window.config = {
// rest of window config
customizationService: [
{
'viewportDownload.warningMessage': {
$set: {
enabled: true,
value: 'Careful! This is not for diagnostic use.',
},
},
},
],
};
`,
},
{
id: 'ohif.captureViewportModal',
description: 'The modal for capturing the viewport image.',
image: captureViewportModal,
default: 'Our own default component',
configuration: `
window.config = {
// rest of window config
function MyAboutModal() {
return (
<AboutModal className="w-[400px]">
<AboutModal.ProductName>My Custom Viewer</AboutModal.ProductName>
<AboutModal.ProductVersion>1.2.3</AboutModal.ProductVersion>
</AboutModal>
);
}
// You can use the component from ImageModal and FooterAction
// to build your own custom component
customizationService: [
{
'ohif.captureViewportModal': {
$set: CustomizedComponent,
},
},
],
};
`,
},
{
id: 'ohif.aboutModal',
description: 'The About modal',
image: aboutModal,
default: 'Our own default component',
configuration: `
window.config = {
// rest of window config
// Optional: override the modal title and container size.
MyAboutModal.title = 'About My Custom Viewer';
MyAboutModal.containerClassName = 'max-w-md';
// You can use the component from AboutModal
// to build your own custom component
customizationService: [
{
'ohif.aboutModal': {
$set: CustomizedComponent,
},
},
],
};
window.config = {
// rest of window config
customizationService: [
{ 'ohif.aboutModal': { $set: MyAboutModal } },
],
};
`,
},
{
@@ -185,67 +185,53 @@ export const ModalConsumer = ModalContext.Consumer;
```
Therefore, anywhere in the app that we have access to react context we can use
it by calling the `useModal` from `@ohif/ui`. As a matter of fact, we are
it by calling the `useModal` from `@ohif/ui-next`. As a matter of fact, we are
utilizing the modal for the preference window which shows the hotkeys after
clicking on the gear button on the right side of the header.
A `simplified` code for our worklist is:
A `simplified` code for our viewer header is:
```js title="platform/app/src/routes/WorkList/WorkList.jsx"
import { useModal, Header } from '@ohif/ui';
```tsx title="extensions/default/src/ViewerLayout/ViewerHeader.tsx"
import { Header, useModal } from '@ohif/ui-next';
import { useSystem } from '@ohif/core';
function WorkList({
history,
data: studies,
dataTotal: studiesTotal,
isLoadingData,
dataSource,
hotkeysManager,
}) {
const { show, hide } = useModal();
function ViewerHeader({ appConfig }) {
const { servicesManager } = useSystem();
const { customizationService } = servicesManager.services;
const { show } = useModal();
/** ... **/
const AboutModal = customizationService.getCustomization('ohif.aboutModal');
const UserPreferencesModal = customizationService.getCustomization(
'ohif.userPreferencesModal'
);
const menuOptions = [
{
title: t('Header:About'),
title: AboutModal?.menuTitle ?? t('Header:About'),
icon: 'info',
onClick: () => show({ content: AboutModal, title: 'About OHIF Viewer' }),
onClick: () =>
show({
content: AboutModal,
title: AboutModal?.title ?? t('AboutModal:About OHIF Viewer'),
containerClassName: AboutModal?.containerClassName ?? 'max-w-md',
}),
},
{
title: t('Header:Preferences'),
title: UserPreferencesModal.menuTitle ?? t('Header:Preferences'),
icon: 'settings',
onClick: () =>
show({
title: t('UserPreferencesModal:User Preferences'),
content: UserPreferences,
contentProps: {
hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions(
hotkeyDefaults
),
hotkeyDefinitions,
onCancel: hide,
currentLanguage: currentLanguage(),
availableLanguages,
defaultLanguage,
onSubmit: state => {
i18n.changeLanguage(state.language.value);
hotkeysManager.setHotkeys(state.hotkeyDefinitions);
hide();
},
onReset: () => hotkeysManager.restoreDefaultBindings(),
},
content: UserPreferencesModal,
title: UserPreferencesModal.title ?? t('UserPreferencesModal:User preferences'),
containerClassName:
UserPreferencesModal?.containerClassName ?? 'flex max-w-4xl p-6 flex-col',
}),
},
];
/** ... **/
return (
<div>
/** ... **/
<Header isSticky menuOptions={menuOptions} isReturnEnabled={false} />
/** ... **/
</div>
);
return <Header menuOptions={menuOptions} /** ... **/ />;
}
```
+9
View File
@@ -13,6 +13,15 @@ server for the `OHIF Viewer`.
![user-study-list](../assets/img/user-study-list.png)
## Study Limit
To accommodate the various data sources that support various query parameters for
paging and limiting results it was decided to handle paging in the OHIF client.
This nicely handles those data sources that do not support paging and query limits.
For those that support the `limit` query parameter, there is a data source
configuration parameter `queryLimit` that will be passed for study list searches
so as to cap the result. When not specified, this value defaults to `101`.
## Sorting
When the Study List is opened, the application queries the PACS for 101 studies