fix: Several new worklist issues (#6130)

* fix: Several new worklist issues

* refactor: Export a single OnStudyDoubleClick type from the StudyList barrel

Addresses PR review feedback: the double-click handler signature was
written out in both TableProps and the WorkList customization cast,
so the two could drift apart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: Run worklist study double-click as a command, registrable by modes

- Modes can now export getCommandsModule on their definition; appInit
  registers it (via the new ExtensionManager.registerCommandsModule)
  before the mode is instantiated, in a new 'WORKLIST' commands context,
  so the commands are available on the worklist before any mode route
  is entered.
- The workList.onStudyDoubleClick customization is now a command run
  input (name/options) instead of a bare function, defaulting to the
  new launchDefaultMode command, which launches the default workflow
  falling back to the first applicable one. commandOptions.workflowId
  overrides it to a specific mode.
- Duplicate mode ids are now skipped before running their modeFactory
  rather than after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bill WallaceandClaude Fable 5 authored and GitHub committed 2026-07-10 08:16:16 -04:00
1 parent dbb8b1526e
commit 6b6761088b
11 files changed
+230 -27

No files matched your search

+30 -10
View File
@@ -130,19 +130,39 @@ async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) {
}
const { id } = mode;
if (mode.modeFactory) {
// If the appConfig contains configuration for this mode, use it.
const modeConfiguration =
appConfig.modesConfiguration && appConfig.modesConfiguration[id]
? appConfig.modesConfiguration[id]
: {};
mode = await mode.modeFactory({ modeConfiguration, loadModules });
}
if (modesById.has(id)) {
continue;
}
// If the appConfig contains configuration for this mode, use it.
const modeConfiguration =
appConfig.modesConfiguration && appConfig.modesConfiguration[id]
? appConfig.modesConfiguration[id]
: {};
// Mirrors the extension commands path for modes, but registers before the
// mode is instantiated so the commands are usable on the worklist, ahead
// of any mode route being entered. Definitions land in the 'WORKLIST'
// context unless the module (or an individual command) declares its own.
if (typeof mode.getCommandsModule === 'function') {
extensionManager.registerCommandsModule(
mode.getCommandsModule({
appConfig,
commandsManager,
servicesManager,
serviceProvidersManager,
hotkeysManager,
extensionManager,
modeConfiguration,
}),
'WORKLIST'
);
}
if (mode.modeFactory) {
mode = await mode.modeFactory({ modeConfiguration, loadModules });
}
// Prevent duplication
modesById.add(id);
if (!mode || typeof mode !== 'object') {
+27 -2
View File
@@ -1,10 +1,17 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useAppConfig } from '@state';
import type { RunInput } from '@ohif/core/src/classes/CommandsManager';
import { preserveQueryParameters } from '../../utils/preserveQueryParameters';
import { useStudyListStateSync, useWorkListToolbarActions } from '../../hooks';
import { StudyList, Icons, InvestigationalUseDialog, type StudyRow } from '@ohif/ui-next';
import {
StudyList,
Icons,
InvestigationalUseDialog,
type StudyRow,
type OnStudyDoubleClick,
} from '@ohif/ui-next';
import { StudyListSettingsPopover } from './StudyListSettingsPopover';
import { SidePanelPreview } from './SidePanelPreview';
@@ -26,6 +33,7 @@ export default function WorkList({
onRefresh,
servicesManager,
extensionManager,
commandsManager,
}: Props) {
const [appConfig] = useAppConfig();
const { customizationService } = servicesManager.services;
@@ -47,6 +55,22 @@ export default function WorkList({
const [selected, setSelected] = useState<StudyRow | null>(null);
const [isPreviewOpen, setPreviewOpen] = useState(true);
// `workList.onStudyDoubleClick` is the command (or command list) run when a
// study row is double-clicked — by default `launchDefaultMode`, which
// launches the default workflow, falling back to the first applicable one.
// The study and its applicable workflows are merged into the command options
// at call time, so an override only needs to name a command and any static
// options (e.g. a specific `workflowId`).
const studyDoubleClickCommand = customizationService.getCustomization(
'workList.onStudyDoubleClick'
) as RunInput;
const onStudyDoubleClick = useCallback<OnStudyDoubleClick>(
(study, { defaultWorkflow, workflows }) => {
commandsManager.run(studyDoubleClickCommand, { study, defaultWorkflow, workflows });
},
[commandsManager, studyDoubleClickCommand]
);
const columns = useMemo(() => {
// `workList.columns` is registered as a value (StudyList.defaultColumns) and
// merged via customization commands, so we read the result directly.
@@ -122,6 +146,7 @@ export default function WorkList({
)
}
title={'Study List'}
onStudyDoubleClick={studyDoubleClickCommand ? onStudyDoubleClick : undefined}
onSelectionChange={sel => setSelected((sel as StudyRow[])[0] ?? null)}
toolbarLeftComponent={logoComponent}
toolbarRightActionsComponent={toolbarActions}
+1 -1
View File
@@ -131,7 +131,7 @@ const createRoutes = ({
path: '/',
children: DataSourceWrapper,
private: true,
props: { children: WorkListComponent, servicesManager, extensionManager },
props: { children: WorkListComponent, servicesManager, extensionManager, commandsManager },
};
const customRoutes = customizationService.getCustomization('routes.customRoutes');