ohif-viewer/platform/app/src/appInit.js
Bill Wallace 6b6761088b
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>
2026-07-10 08:16:16 -04:00

188 lines
6.0 KiB
JavaScript

import {
CommandsManager,
ExtensionManager,
ServicesManager,
ServiceProvidersManager,
HotkeysManager,
UINotificationService,
UIModalService,
UIDialogService,
UIViewportDialogService,
MeasurementService,
DisplaySetService,
ToolbarService,
ViewportGridService,
HangingProtocolService,
CineService,
UserAuthenticationService,
errorHandler,
CustomizationService,
PanelService,
WorkflowStepsService,
StudyPrefetcherService,
MultiMonitorService,
// utils,
} from '@ohif/core';
import loadModules, { loadModule as peerImport } from './pluginImports';
import { publicUrl } from './utils/publicUrl';
/**
* @param {object|function} appConfigOrFunc - application configuration, or a function that returns application configuration
* @param {object[]} defaultExtensions - array of extension objects
*/
async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) {
const commandsManagerConfig = {
getAppState: () => {},
};
const commandsManager = new CommandsManager(commandsManagerConfig);
const servicesManager = new ServicesManager(commandsManager);
const serviceProvidersManager = new ServiceProvidersManager();
const hotkeysManager = new HotkeysManager(commandsManager, servicesManager);
const appConfig = {
...(typeof appConfigOrFunc === 'function'
? await appConfigOrFunc({ servicesManager, peerImport })
: appConfigOrFunc),
};
// Default the peer import function
appConfig.peerImport ||= peerImport;
appConfig.measurementTrackingMode ||= 'standard';
appConfig.routerBasename ||= publicUrl;
const extensionManager = new ExtensionManager({
commandsManager,
servicesManager,
serviceProvidersManager,
hotkeysManager,
appConfig,
});
servicesManager.setExtensionManager(extensionManager);
servicesManager.registerServices([
[MultiMonitorService.REGISTRATION, appConfig.multimonitor],
UINotificationService.REGISTRATION,
UIModalService.REGISTRATION,
UIDialogService.REGISTRATION,
UIViewportDialogService.REGISTRATION,
MeasurementService.REGISTRATION,
DisplaySetService.REGISTRATION,
[CustomizationService.REGISTRATION, appConfig.customizationService],
ToolbarService.REGISTRATION,
ViewportGridService.REGISTRATION,
HangingProtocolService.REGISTRATION,
CineService.REGISTRATION,
UserAuthenticationService.REGISTRATION,
PanelService.REGISTRATION,
WorkflowStepsService.REGISTRATION,
[StudyPrefetcherService.REGISTRATION, appConfig.studyPrefetcher],
]);
errorHandler.getHTTPErrorHandler = () => {
if (typeof appConfig.httpErrorHandler === 'function') {
return appConfig.httpErrorHandler;
}
};
/**
* Example: [ext1, ext2, ext3]
* Example2: [[ext1, config], ext2, [ext3, config]]
*/
const loadedExtensions = await loadModules([...defaultExtensions, ...appConfig.extensions]);
const { customizationService } = servicesManager.services;
// Resolve every customization module up front — from
// `appConfig.customizationService.requires` and the `?customization=` URL
// parameter — long before any mode loads, then apply the `bootstrap` phase
// BEFORE extensions register so it is in place while they initialize. Modules
// are only loaded when `appConfig.customizationUrlPrefixes` allows their
// prefix; the feature is off by default, and a value with an unconfigured
// prefix throws here (aborting startup) rather than being silently ignored.
await customizationService.loadAndApplyBootstrapCustomizations(extensionManager);
await extensionManager.registerExtensions(loadedExtensions, appConfig.dataSources);
// Merge extension default/global modules, then layer on the `global` phase of
// the structured config + the URL modules resolved above (so `$apply`-style
// overrides see the extension-provided defaults they build on).
customizationService.init(extensionManager);
customizationService.applyGlobalCustomizations();
// TODO: We no longer use `utils.addServer`
// TODO: We no longer init webWorkers at app level
// TODO: We no longer init the user Manager
if (!appConfig.modes) {
throw new Error('No modes are defined! Check your app-config.js');
}
const loadedModes = await loadModules([...(appConfig.modes || []), ...defaultModes]);
// This is the name for the loaded instance object
appConfig.loadedModes = [];
const modesById = new Set();
for (let i = 0; i < loadedModes.length; i++) {
let mode = loadedModes[i];
if (!mode) {
continue;
}
const { id } = mode;
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') {
continue;
}
appConfig.loadedModes.push(mode);
}
// Hack alert - don't touch the original modes definition,
// but there are still dependencies on having the appConfig modes defined
appConfig.modes = appConfig.loadedModes;
return {
appConfig,
commandsManager,
extensionManager,
servicesManager,
serviceProvidersManager,
hotkeysManager,
};
}
export default appInit;