* 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>
188 lines
6.0 KiB
JavaScript
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;
|