chore: Clean up unused files, imports, and work on getting end-to-end tests and unit tests running (#2272)

This commit is contained in:
Erik Ziegler authored and GitHub committed 2021-03-01 16:52:54 +01:00
1 parent 414ebc6850
commit 0db81b30f3
374 files changed
+14138 -21288

No files matched your search

+6 -4
View File
@@ -37,15 +37,17 @@
},
"dependencies": {
"@babel/runtime": "7.7.6",
"ajv": "^6.10.0",
"dcmjs": "0.16.1",
"dicomweb-client": "^0.6.0",
"immer": "6.0.2",
"isomorphic-base64": "^1.0.2",
"lodash.clonedeep": "^4.5.0",
"lodash.merge": "^4.6.1",
"moment": "^2.24.0",
"mousetrap": "^1.6.3",
"validate.js": "^0.12.0"
"query-string": "^6.14.0",
"object-hash": "2.1.1",
"cornerstone-math": "0.1.9"
},
"devDependencies": {
"webpack-merge": "5.7.3"
}
}
+3 -4
View File
@@ -1,5 +1,4 @@
import log from '../log';
// import studies from '../studies';
import utils from '../utils';
// import {
// retrieveMeasurementFromSR,
@@ -26,7 +25,7 @@ const { MeasurementReport } = dcmjs.adapters.Cornerstone;
* @param {serverType} server
* @returns {Promise} Should resolve with OHIF measurementData object
*/
const retrieveMeasurements = server => {
/*const retrieveMeasurements = server => {
log.info('[DICOMSR] retrieveMeasurements');
if (!server || server.type !== 'dicomWeb') {
@@ -42,7 +41,7 @@ const retrieveMeasurements = server => {
if (!latestSeries) return Promise.resolve({});
return retrieveMeasurementFromSR(latestSeries, studies, serverUrl);
};
};*/
/**
*
@@ -238,4 +237,4 @@ function _getFilteredCornerstoneToolState(
return filteredToolState;
}
export { retrieveMeasurements, storeMeasurements, downloadReport };
export { storeMeasurements, downloadReport };
+2 -2
View File
@@ -1,12 +1,12 @@
import {
retrieveMeasurements,
//retrieveMeasurements,
storeMeasurements,
downloadReport,
} from './dataExchange';
import isToolSupported from './utils/isToolSupported';
const DICOMSR = {
retrieveMeasurements,
//retrieveMeasurements,
storeMeasurements,
downloadReport,
isToolSupported,
@@ -1,3 +1,4 @@
import 'isomorphic-base64'
import user from '../user';
/**
@@ -1,4 +1,4 @@
import { DicomMetadataStore } from '@ohif/core';
import { DicomMetadataStore } from '../services/DicomMetadataStore';
// TODO: Use above to inject so dependent datasources don't need to import or
// depend on @ohif/core?
@@ -1,229 +0,0 @@
import { CriteriaEvaluator } from './CriteriaEvaluator';
import * as initialEvaluations from './evaluations';
import log from '../../log';
const evaluations = Object.assign({}, initialEvaluations);
const BASELINE = 'baseline';
const FOLLOWUP = 'followup';
const BOTH = 'both';
const TARGETS = 'targets';
const NONTARGETS = 'nonTargets';
class ConformanceCriteria {
constructor(measurementApi, timepointApi, options = {}) {
this.measurementApi = measurementApi;
this.timepointApi = timepointApi;
this.nonconformities = [];
this.groupedNonConformities = [];
this.maxTargets = null;
this.maxNewTargets = null;
this.options = options;
}
loadStudy(StudyInstanceUID) {
if (typeof this.options.loadStudy !== 'function') {
throw new Error('loadStudy callback is not defined');
}
return this.options.loadStudy(null, StudyInstanceUID);
}
async validate(trialCriteriaType) {
const baselinePromise = this.getData(BASELINE);
const followupPromise = this.getData(FOLLOWUP);
const [baselineData, followupData] = await Promise.all([
baselinePromise,
followupPromise,
]);
const mergedData = {
targets: [],
nonTargets: [],
};
mergedData.targets = mergedData.targets.concat(baselineData.targets);
mergedData.targets = mergedData.targets.concat(followupData.targets);
mergedData.nonTargets = mergedData.nonTargets.concat(
baselineData.nonTargets
);
mergedData.nonTargets = mergedData.nonTargets.concat(
followupData.nonTargets
);
this.maxTargets = null;
this.maxNewTargets = null;
const resultBoth = this.validateTimepoint(
BOTH,
trialCriteriaType,
mergedData
);
const resultBaseline = this.validateTimepoint(
BASELINE,
trialCriteriaType,
baselineData
);
const resultFollowup = this.validateTimepoint(
FOLLOWUP,
trialCriteriaType,
followupData
);
const nonconformities = resultBaseline
.concat(resultFollowup)
.concat(resultBoth);
const groupedNonConformities = this.groupNonConformities(nonconformities);
// Keep both? Group the data only on viewer/measurementTable views?
// Work with not grouped data (worse lookup performance on measurementTableRow)?
this.nonconformities = nonconformities;
this.groupedNonConformities = groupedNonConformities;
// console.warn('nonconformities');
// console.warn(nonconformities);
// console.warn('groupedNonConformities');
// console.warn(groupedNonConformities);
return nonconformities;
}
groupNonConformities(nonconformities) {
const groups = {};
const toolsGroupsMap = this.measurementApi.toolsGroupsMap;
nonconformities.forEach(nonConformity => {
if (nonConformity.isGlobal) {
groups.globals = groups.globals || { messages: [] };
groups.globals.messages.push(nonConformity.message);
return;
}
nonConformity.measurements.forEach(measurement => {
const groupName = toolsGroupsMap[measurement.toolType];
groups[groupName] = groups[groupName] || { measurementNumbers: {} };
const group = groups[groupName];
const measureNumber = measurement.measurementNumber;
let measurementNumbers = group.measurementNumbers[measureNumber];
if (!measurementNumbers) {
measurementNumbers = group.measurementNumbers[measureNumber] = {
messages: [],
measurements: [],
};
}
measurementNumbers.messages.push(nonConformity.message);
measurementNumbers.measurements.push(measurement);
});
});
return groups;
}
validateTimepoint(timepointType, trialCriteriaType, data) {
const evaluators = this.getEvaluators(timepointType, trialCriteriaType);
let nonconformities = [];
evaluators.forEach(evaluator => {
const maxTargets = evaluator.getMaxTargets(false);
const maxNewTargets = evaluator.getMaxTargets(true);
if (maxTargets) {
this.maxTargets = maxTargets;
}
if (maxNewTargets) {
this.maxNewTargets = maxNewTargets;
}
const result = evaluator.evaluate(data);
if (result.length > 0) {
result.forEach(resultItem => {
resultItem.timepointType = timepointType;
});
}
nonconformities = nonconformities.concat(result);
});
return nonconformities;
}
getEvaluators(timepointType, trialCriteriaType) {
const evaluators = [];
console.warn(evaluations);
const trialCriteriaTypeId = trialCriteriaType.id.toLowerCase();
const evaluation = evaluations[trialCriteriaTypeId];
if (evaluation) {
const evaluationTimepoint = evaluation[timepointType];
if (evaluationTimepoint) {
evaluators.push(new CriteriaEvaluator(evaluationTimepoint));
}
}
return evaluators;
}
/*
* Build the data that will be used to do the conformance criteria checks
*/
async getData(timepointType) {
const data = {
targets: [],
nonTargets: [],
};
const studyPromises = [];
const fillData = measurementType => {
const measurements = this.measurementApi.fetch(measurementType);
measurements.forEach(measurement => {
const { StudyInstanceUID } = measurement;
const timepointId = measurement.timepointId;
const timepoint =
timepointId &&
this.timepointApi.timepoints.find(a => a.timepointId === timepointId);
if (
!timepoint ||
(timepointType !== BOTH && timepoint.timepointType !== timepointType)
) {
return;
}
const promise = this.loadStudy(StudyInstanceUID);
promise.then(
studyMetadata => {
data[measurementType].push({
measurement,
metadata: studyMetadata.getFirstInstance(),
timepoint,
});
},
error => {
throw new Error(error);
}
);
studyPromises.push(promise);
});
};
fillData(TARGETS);
fillData(NONTARGETS);
await Promise.all(studyPromises);
return data;
}
static setEvaluationDefinitions(evaluationKey, evaluationDefinitions) {
evaluations[evaluationKey] = evaluationDefinitions;
}
}
export default ConformanceCriteria;
//OHIF.measurements.ConformanceCriteria = ConformanceCriteria;
-29
View File
@@ -1,29 +0,0 @@
import { QIDO, WADO } from './services/';
import {
deleteStudyMetadataPromise,
retrieveStudyMetadata,
} from './retrieveStudyMetadata.js';
import getStudyBoxData from './getStudyBoxData';
import retrieveStudiesMetadata from './retrieveStudiesMetadata.js';
import searchStudies from './searchStudies';
import { sortStudy, sortStudySeries, sortStudyInstances, sortingCriteria } from './sortStudy';
const studies = {
services: {
QIDO,
WADO,
},
loadingDict: {},
retrieveStudyMetadata,
deleteStudyMetadataPromise,
retrieveStudiesMetadata,
getStudyBoxData,
searchStudies,
sortStudy,
sortStudySeries,
sortStudyInstances,
sortingCriteria,
};
export default studies;
@@ -1,181 +0,0 @@
import { api } from 'dicomweb-client';
import dcmjs from 'dcmjs';
import DICOMWeb from '../../../DICOMWeb/';
import RetrieveMetadataLoader from './retrieveMetadataLoader';
import { sortStudySeries, sortingCriteria } from '../../sortStudy';
import {
createStudyFromSOPInstanceList,
addInstancesToStudy,
} from './studyInstanceHelpers';
import errorHandler from '../../../errorHandler';
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
/**
* Map series to an array of SeriesInstanceUID
* @param {Arrays} series list of Series Instance UIDs
* @returns {Arrays} A list of Series Instance UIDs
*/
function mapStudySeries(series) {
return series.map(series => getSeriesInfo(series).SeriesInstanceUID);
}
function attachSeriesLoader(server, study, seriesLoader) {
study.seriesLoader = Object.freeze({
hasNext() {
return seriesLoader.hasNext();
},
async next() {
const series = await seriesLoader.next();
await addInstancesToStudy(server, study, series.sopInstances);
return study.seriesMap[series.seriesInstanceUID];
},
});
}
/**
* Creates an immutable series loader object which loads each series sequentially using the iterator interface
* @param {DICOMWebClient} dicomWebClient The DICOMWebClient instance to be used for series load
* @param {string} studyInstanceUID The Study Instance UID from which series will be loaded
* @param {Array} seriesInstanceUIDList A list of Series Instance UIDs
* @returns {Object} Returns an object which supports loading of instances from each of given Series Instance UID
*/
function makeSeriesAsyncLoader(
dicomWebClient,
studyInstanceUID,
seriesInstanceUIDList
) {
return Object.freeze({
hasNext() {
return seriesInstanceUIDList.length > 0;
},
async next() {
const seriesInstanceUID = seriesInstanceUIDList.shift();
const sopInstances = await dicomWebClient.retrieveSeriesMetadata({
studyInstanceUID,
seriesInstanceUID,
});
return { studyInstanceUID, seriesInstanceUID, sopInstances };
},
});
}
/**
* Class for async load of study metadata.
* It inherits from RetrieveMetadataLoader
*
* It loads the one series and then append to seriesLoader the others to be consumed/loaded
*/
export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader {
configLoad() {
const { server } = this;
const client = new api.DICOMwebClient({
url: server.qidoRoot,
headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
});
this.client = client;
}
/**
* @returns {Array} Array of preLoaders. To be consumed as queue
*/
*getPreLoaders() {
const preLoaders = [];
const {
studyInstanceUID,
filters: { seriesInstanceUID } = {},
client,
} = this;
if (seriesInstanceUID) {
const options = {
studyInstanceUID,
queryParams: { SeriesInstanceUID: seriesInstanceUID },
};
preLoaders.push(client.searchForSeries.bind(client, options));
}
// Fallback preloader
preLoaders.push(client.searchForSeries.bind(client, { studyInstanceUID }));
yield* preLoaders;
}
async preLoad() {
const preLoaders = this.getPreLoaders();
// seriesData is the result of the QIDO-RS Search For Series request
// It's an array of Objects containing DICOM Tag values at the Series level
const seriesData = await this.runLoaders(preLoaders);
const seriesSorted = sortStudySeries(
seriesData,
sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria
);
const seriesInstanceUIDsMap = mapStudySeries(seriesSorted);
return {
seriesInstanceUIDsMap,
seriesData,
};
}
async load(preLoadData) {
const { client, studyInstanceUID } = this;
const seriesAsyncLoader = makeSeriesAsyncLoader(
client,
studyInstanceUID,
preLoadData.seriesInstanceUIDsMap
);
const firstSeries = await seriesAsyncLoader.next();
return {
sopInstances: firstSeries.sopInstances,
asyncLoader: seriesAsyncLoader,
seriesData: preLoadData.seriesData,
};
}
async posLoad(loadData) {
const { server } = this;
const { sopInstances, asyncLoader, seriesData } = loadData;
const study = await createStudyFromSOPInstanceList(server, sopInstances);
// TODO: Should this be in a helper
const seriesDataNaturalized = seriesData.map(naturalizeDataset);
seriesDataNaturalized.forEach((series, idx) => {
const seriesDataFromQIDO = {
SeriesInstanceUID: series.SeriesInstanceUID,
SeriesDescription: series.SeriesDescription,
SeriesNumber: series.SeriesNumber,
Modality: series.Modality,
instances: [],
};
if (study.series[idx]) {
study.series[idx] = Object.assign(
seriesDataFromQIDO,
study.series[idx]
);
} else {
study.series[idx] = seriesDataFromQIDO;
}
study.seriesMap[series.SeriesInstanceUID] = study.series[idx];
});
if (asyncLoader.hasNext()) {
attachSeriesLoader(server, study, asyncLoader);
}
return study;
}
}
@@ -1,97 +0,0 @@
import isLowPriorityModality from '../utils/isLowPriorityModality';
/**
* Series sorting criteria: series considered low priority are moved to the end
* of the list and series number is used to break ties
* @param {Object} firstSeries
* @param {Object} secondSeries
*/
function seriesInfoSortingCriteria(firstSeries, secondSeries) {
const aLowPriority = isLowPriorityModality(firstSeries.Modality);
const bLowPriority = isLowPriorityModality(secondSeries.Modality);
if (!aLowPriority && bLowPriority) {
return -1;
}
if (aLowPriority && !bLowPriority) {
return 1;
}
return firstSeries.SeriesNumber - secondSeries.SeriesNumber;
}
const seriesSortCriteria = {
default: (a, b) => a.SeriesNumber - b.SeriesNumber,
seriesInfoSortingCriteria,
};
const instancesSortCriteria = {
default: (a, b) => a.InstanceNumber - b.InstanceNumber,
};
const sortingCriteria = {
seriesSortCriteria,
instancesSortCriteria,
};
/**
* Sorts given series (given param is modified)
* The default criteria is based on series number in ascending order.
*
* @param {Array} series List of series
* @param {function} seriesSortingCriteria method for sorting
* @returns {Array} sorted series object
*/
const sortStudySeries = (
series,
seriesSortingCriteria = seriesSortCriteria.default
) => {
return series.sort(seriesSortingCriteria);
};
/**
* Sorts given instancesList (given param is modified)
* The default criteria is based on instance number in ascending order.
*
* @param {Array} instancesList List of series
* @param {function} instancesSortingCriteria method for sorting
* @returns {Array} sorted instancesList object
*/
const sortStudyInstances = (
instancesList,
instancesSortingCriteria = instancesSortCriteria.default
) => {
return instancesList.sort(instancesSortingCriteria);
};
/**
* Sorts the series and instances (by default) inside a study instance based on sortingCriteria (given param is modified)
* The default criteria is based on series and instance numbers in ascending order.
*
* @param {Object} study The study instance
* @param {boolean} [deepSort = true] to sort instance also
* @param {function} [seriesSortingCriteria = seriesSortCriteria.default] method for sorting series
* @param {function} [instancesSortingCriteria = instancesSortCriteria.default] method for sorting instances
* @returns {Object} sorted study object
*/
export default function sortStudy(
study,
deepSort = true,
seriesSortingCriteria = seriesSortCriteria.default,
instancesSortingCriteria = instancesSortCriteria.default
) {
if (!study || !study.series) {
throw new Error('Insufficient study data was provided to sortStudy');
}
sortStudySeries(study.series, seriesSortingCriteria);
if (deepSort) {
study.series.forEach(series => {
sortStudyInstances(series.instances, instancesSortingCriteria);
});
}
return study;
}
export { sortStudy, sortStudySeries, sortStudyInstances, sortingCriteria };
-39
View File
@@ -1,39 +0,0 @@
import React, { Component, useContext } from 'react';
/// TODO MAKE THIS PRETTY DANNY
const ViewModelContext = React.createContext({
displaySetInstanceUIDs: [],
setDisplaySetInstanceUIDs: () => {},
});
ViewModelContext.displayName = 'ViewModelContext';
class ViewModelProvider extends Component {
state = {
displaySetInstanceUIDs: [],
};
render() {
const setDisplaySetInstanceUIDs = displaySetInstanceUIDs => {
this.setState({ displaySetInstanceUIDs });
};
return (
<ViewModelContext.Provider
value={{
displaySetInstanceUIDs: this.state.displaySetInstanceUIDs,
setDisplaySetInstanceUIDs,
}}
>
{this.props.children}
</ViewModelContext.Provider>
);
}
}
const useViewModel = () => useContext(ViewModelContext);
export default ViewModelContext;
export { ViewModelProvider, useViewModel };
+4 -5
View File
@@ -18,16 +18,15 @@ import log from '../log.js';
* to extend this class, please check it's source before adding new methods.
*/
export class CommandsManager {
constructor({ getAppState, getActiveContexts } = {}) {
constructor({ getActiveContexts } = {}) {
this.contexts = {};
if (!getAppState || !getActiveContexts) {
log.warn(
'CommandsManager was instantiated without getAppState() or getActiveContexts()'
if (!getActiveContexts) {
throw new Error(
'CommandsManager was instantiated without getActiveContexts()'
);
}
this._getAppState = getAppState;
this._getActiveContexts = getActiveContexts;
}
@@ -12,11 +12,6 @@ describe('CommandsManager', () => {
options: { passMeToCommandFn: ':wave:' },
},
commandsManagerConfig = {
getAppState: () => {
return {
viewers: 'Test',
};
},
getActiveContexts: () => ['VIEWER', 'ACTIVE_VIEWER::CORNERSTONE'],
};
@@ -34,10 +29,10 @@ describe('CommandsManager', () => {
expect(localCommandsManager.contexts).toEqual({});
});
it('logs a warning if instantiated without getAppState or getActiveContexts', () => {
new CommandsManager();
expect(log.warn.mock.calls.length).toBe(1);
it('throws Error if instantiated without getActiveContexts', () => {
expect(() => {
new CommandsManager();
}).toThrow(new Error('CommandsManager was instantiated without getActiveContexts()'));
});
describe('createContext()', () => {
@@ -180,16 +175,6 @@ describe('CommandsManager', () => {
expect(command.commandFn.mock.calls.length).toBe(1);
});
it('Calls commandFn w/ properties from appState', () => {
commandsManager.registerCommand('VIEWER', 'TestCommand', command);
commandsManager.runCommand('TestCommand', {}, 'VIEWER');
expect(command.commandFn.mock.calls.length).toBe(1);
expect(command.commandFn.mock.calls[0][0].viewers).toEqual(
commandsManagerConfig.getAppState().viewers
);
});
it('Calls commandFn w/ command definition options', () => {
commandsManager.registerCommand('VIEWER', 'TestCommand', command);
commandsManager.runCommand('TestCommand', {}, 'VIEWER');
+4 -5
View File
@@ -19,8 +19,8 @@ export class HotkeysManager {
this.isEnabled = true;
if (!commandsManager) {
log.warn(
'[hotkeys] HotkeysManager instantiated without a commandsManager. Hotkeys will be unable to find and run commands.'
throw new Error(
'HotkeysManager instantiated without a commandsManager. Hotkeys will be unable to find and run commands.'
);
}
@@ -67,7 +67,7 @@ export class HotkeysManager {
const { UINotificationService } = this._servicesManager.services;
UINotificationService.show({
title: 'Hotkeys Manager',
message: 'Erro while setting hotkeys',
message: 'Error while setting hotkeys',
type: 'error',
});
}
@@ -165,8 +165,7 @@ export class HotkeysManager {
*/
registerHotkeys({ commandName, commandOptions = {}, keys, label, isEditable } = {}, extension) {
if (!commandName) {
log.warn(`[hotkeys] No command was defined for hotkey "${keys}"`);
return;
throw new Error(`No command was defined for hotkey "${keys}"`);
}
const commandHash = objectHash({ commandName, commandOptions });
@@ -2,6 +2,7 @@ import CommandsManager from './CommandsManager.js';
import HotkeysManager from './HotkeysManager.js';
import hotkeys from './../utils/hotkeys';
import log from './../log.js';
import objectHash from "object-hash";
jest.mock('./CommandsManager.js');
jest.mock('./../utils/hotkeys');
@@ -34,11 +35,10 @@ describe('HotkeysManager', () => {
expect(containsAllExpectedProperties).toBe(true);
});
it('logs a warning if instantiated without a commandsManager', () => {
new HotkeysManager();
expect(log.warn.mock.calls.length).toBe(1);
expect(log.warn.mock.calls[0][0]).toEqual(
it('throws Error if instantiated without a commandsManager', () => {
expect(() => {
new HotkeysManager();
}).toThrow(
'HotkeysManager instantiated without a commandsManager. Hotkeys will be unable to find and run commands.'
);
});
@@ -97,6 +97,7 @@ describe('HotkeysManager', () => {
expect(firstCallArgs).toEqual(hotkeyDefinitions[0]);
expect(secondCallArgs).toEqual(hotkeyDefinitions[1]);
});
it('does not set this.hotkeyDefaults when calling setHotKeys', () => {
const hotkeyDefinitions = [{ commandName: 'dance', keys: '+' }];
@@ -117,30 +118,32 @@ describe('HotkeysManager', () => {
});
describe('registerHotkeys()', () => {
it('logs a warning and returns undefined if a commandName is not provided', () => {
it('throws an Error if a commandName is not provided', () => {
const definition = { commandName: undefined, keys: '+' };
const result = hotkeysManager.registerHotkeys(definition);
expect(result).toBe(undefined);
expect(log.warn.mock.calls.length).toBe(1);
expect(() => {
hotkeysManager.registerHotkeys(definition)
}).toThrow();
});
it('updates hotkeyDefinitions property with registered keys', () => {
const definition = { commandName: 'dance', label: 'hello', keys: '+' };
const expectedHotkeyDefinition = { label: 'hello', keys: '+' };
const definition = { commandName: 'dance', commandOptions: {}, label: 'hello', keys: '+', };
hotkeysManager.registerHotkeys(definition);
const numOfHotkeyDefinitions = Object.keys(
hotkeysManager.hotkeyDefinitions
).length;
const commandHash = objectHash({
commandName: definition.commandName,
commandOptions: definition.commandOptions
});
const hotkeyDefinitionForRegisteredCommand =
hotkeysManager.hotkeyDefinitions[definition.commandName];
hotkeysManager.hotkeyDefinitions[commandHash];
expect(numOfHotkeyDefinitions).toBe(1);
expect(hotkeyDefinitionForRegisteredCommand).toEqual(
expectedHotkeyDefinition
);
expect(Object.keys(hotkeysManager.hotkeyDefinitions)[0]).toEqual(commandHash);
expect(hotkeyDefinitionForRegisteredCommand).toEqual(definition);
});
it('calls hotkeys.bind for the group of keys', () => {
const definition = { commandName: 'dance', keys: ['shift', 'e'] };
+1 -2
View File
@@ -1,5 +1,4 @@
import guid from '../utils/guid.js';
import OHIFError from './OHIFError';
import { Vector3 } from 'cornerstone-math';
const OBJECT = 'object';
@@ -13,7 +12,7 @@ const OBJECT = 'object';
class ImageSet {
constructor(images) {
if (Array.isArray(images) !== true) {
throw new OHIFError('ImageSet expects an array of images');
throw new Error('ImageSet expects an array of images');
}
// @property "images"
@@ -1,4 +1,3 @@
import dcmjs from 'dcmjs';
import queryString from 'query-string';
import dicomParser from 'dicom-parser';
import getPixelSpacingInformation from '../utils/metadataProvider/getPixelSpacingInformation';
@@ -6,8 +5,6 @@ import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPal
import fetchOverlayData from '../utils/metadataProvider/fetchOverlayData';
import DicomMetadataStore from '../services/DicomMetadataStore';
window.dcmjs = dcmjs;
class MetadataProvider {
constructor() {
// Define the main "metadataLookup" private property as an immutable property.
@@ -426,6 +423,8 @@ class MetadataProvider {
};
break;
default:
return;
}
return metadata;
@@ -441,8 +440,7 @@ class MetadataProvider {
SeriesInstanceUID: splitImageId[2], // Note: splitImageId[3] === 'instances'
SOPInstanceUID: splitImageId[4],
};
}
if (imageId.includes('wado?requestType=WADO')) {
} else if (imageId.includes('?requestType=WADO')) {
const qs = queryString.parse(imageId);
return {
@@ -450,10 +448,10 @@ class MetadataProvider {
SeriesInstanceUID: qs.seriesUID,
SOPInstanceUID: qs.objectUID,
};
} else {
// Maybe its a non-standard imageId
return this.imageIdToUIDs.get(imageId);
}
// Maybe its a non-standard imageId
return this.imageIdToUIDs.get(imageId);
}
}
-14
View File
@@ -1,14 +0,0 @@
// @TODO: improve this object
/**
* Objects to be used to throw errors
*/
class OHIFError extends Error {
constructor(message) {
super();
this.message = message;
this.stack = new Error().stack;
this.name = this.constructor.name;
}
}
export default OHIFError;
-3
View File
@@ -2,14 +2,12 @@ import CommandsManager from './CommandsManager.js';
import HotkeysManager from './HotkeysManager.js';
import ImageSet from './ImageSet';
import MetadataProvider from './MetadataProvider';
import OHIFError from './OHIFError.js';
export {
MetadataProvider,
CommandsManager,
HotkeysManager,
ImageSet,
OHIFError,
};
const classes = {
@@ -17,7 +15,6 @@ const classes = {
CommandsManager,
HotkeysManager,
ImageSet,
OHIFError,
};
export default classes;
@@ -2,7 +2,7 @@ import MODULE_TYPES from './MODULE_TYPES.js';
import log from './../log.js';
export default class ExtensionManager {
constructor({ commandsManager, servicesManager, hotkeysManager, api, appConfig = {} }) {
constructor({ commandsManager, servicesManager, hotkeysManager, appConfig = {} }) {
this.modules = {};
this.registeredExtensionIds = [];
this.moduleTypeNames = Object.values(MODULE_TYPES);
@@ -11,7 +11,6 @@ export default class ExtensionManager {
this._servicesManager = servicesManager;
this._hotkeysManager = hotkeysManager;
this._appConfig = appConfig;
this._api = api;
this.modulesMap = {};
this.moduleTypeNames.forEach(moduleType => {
@@ -95,15 +94,15 @@ export default class ExtensionManager {
*
* @param {Object[]} extensions - Array of extensions
*/
registerExtensions = (extensions, dataSources) => {
registerExtensions = (extensions, dataSources = []) => {
extensions.forEach(extension => {
const hasConfiguration = Array.isArray(extension);
if (hasConfiguration) {
const [ohifExtension, configuration] = extension;
this.registerExtension(ohifExtension, dataSources, configuration);
this.registerExtension(ohifExtension, configuration, dataSources);
} else {
this.registerExtension(extension, dataSources);
this.registerExtension(extension, {}, dataSources);
}
});
};
@@ -114,12 +113,11 @@ export default class ExtensionManager {
* @param {Object} extension
* @param {Object} configuration
*/
registerExtension = (extension, dataSources, configuration = {}) => {
registerExtension = (extension, configuration = {}, dataSources = []) => {
if (!extension) {
log.warn(
'Attempting to register a null/undefined extension. Exiting early.'
throw new Error(
'Attempting to register a null/undefined extension.'
);
return;
}
let extensionId = extension.id;
@@ -193,6 +191,8 @@ export default class ExtensionManager {
] = element;
});
break;
default:
throw new Error(`Module type invalid: ${moduleType}`);
}
this.modules[moduleType].push({
@@ -241,13 +241,11 @@ export default class ExtensionManager {
try {
const extensionModule = getModuleFn({
appConfig: this._appConfig,
getDataSources: this.getDataSources, // Why pass this in if we're passing in `extensionManager`?
commandsManager: this._commandsManager,
servicesManager: this._servicesManager,
hotkeysManager: this._hotkeysManager,
extensionManager: this,
configuration,
api: this._api,
});
if (!extensionModule) {
@@ -258,14 +256,13 @@ export default class ExtensionManager {
return extensionModule;
} catch (ex) {
log.error(
throw new Error(
`Exception thrown while trying to call ${getModuleFnName} for the ${extensionId} extension`
);
log.error(ex);
}
};
_initDataSourcesModule(extensionModule, extensionId, dataSources) {
_initDataSourcesModule(extensionModule, extensionId, dataSources = []) {
extensionModule.forEach(element => {
const namespace = `${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`;
@@ -1,4 +1,3 @@
import { Exception } from 'handlebars';
import ExtensionManager from './ExtensionManager.js';
import MODULE_TYPES from './MODULE_TYPES.js';
import log from './../log.js';
@@ -70,7 +69,7 @@ describe('ExtensionManager.js', () => {
describe('registerExtension()', () => {
it('calls preRegistration() for extension', () => {
// SUT
const fakeExtension = { one: '1', preRegistration: jest.fn() };
const fakeExtension = { id: '1', preRegistration: jest.fn() };
extensionManager.registerExtension(fakeExtension);
// Assert
@@ -81,7 +80,7 @@ describe('ExtensionManager.js', () => {
const extensionConfiguration = { config: 'Some configuration' };
// SUT
const extension = { one: '1', preRegistration: jest.fn() };
const extension = { id: '1', preRegistration: jest.fn() };
extensionManager.registerExtension(extension, extensionConfiguration);
// Assert
@@ -97,18 +96,22 @@ describe('ExtensionManager.js', () => {
const undefinedExtension = undefined;
const nullExtension = null;
extensionManager.registerExtension(undefinedExtension);
extensionManager.registerExtension(nullExtension);
expect(() => {
extensionManager.registerExtension(undefinedExtension);
}).toThrow('Attempting to register a null/undefined extension.');
expect(log.warn.mock.calls.length).toBe(2);
expect(() => {
extensionManager.registerExtension(nullExtension);
}).toThrow('Attempting to register a null/undefined extension.');
});
it('logs a warning if the extension does not have an id', () => {
const extensionWithoutId = {};
extensionManager.registerExtension(extensionWithoutId);
expect(log.warn.mock.calls.length).toBe(1);
expect(() => {
extensionManager.registerExtension(extensionWithoutId);
}).toThrow(new Error('Extension ID not set'));
});
it('tracks which extensions have been registered', () => {
@@ -151,16 +154,13 @@ describe('ExtensionManager.js', () => {
const extensionWithBadModule = {
id: 'hello-world',
getViewportModule: () => {
throw new Exception('Hello World');
throw new Error('Hello World');
},
};
extensionManager.registerExtension(extensionWithBadModule);
expect(log.error.mock.calls.length).toBe(1);
expect(log.error.mock.calls[0][0]).toContain(
'Exception thrown while trying to call'
);
expect(() => {
extensionManager.registerExtension(extensionWithBadModule);
}).toThrow();
});
it('successfully passes dependencies to each module along with extension configuration', () => {
@@ -182,8 +182,10 @@ describe('ExtensionManager.js', () => {
expect(extension[module].mock.calls[0][0]).toEqual({
servicesManager,
commandsManager,
hotkeysManager: undefined,
appConfig,
configuration: extensionConfiguration,
extensionManager
});
}
});
@@ -193,20 +195,29 @@ describe('ExtensionManager.js', () => {
const extension = {
id: 'hello-world',
getViewportModule: () => {
return {};
return [{}];
},
getSopClassHandlerModule: () => {
return {};
return [{}];
},
getPanelModule: () => {
return {};
return [{}];
},
getToolbarModule: () => {
return {};
return [{}];
},
getCommandsModule: () => {
return {};
return [{}];
},
getLayoutTemplateModule: () => {
return [{}];
},
getDataSourcesModule: () => {
return [{}];
},
getContextModule: () => {
return [{}];
}
};
extensionManager.registerExtension(extension);
-6
View File
@@ -9,7 +9,6 @@ import log from './log.js';
import object from './object.js';
import string from './string.js';
import user from './user.js';
import { ViewModelProvider, useViewModel } from './ViewModelContext';
import utils from './utils/';
import defaults from './defaults';
@@ -68,9 +67,6 @@ const OHIF = {
HangingProtocolService,
IWebApiDataSource,
DicomMetadataStore,
//
ViewModelProvider,
useViewModel,
};
export {
@@ -105,8 +101,6 @@ export {
HangingProtocolService,
IWebApiDataSource,
DicomMetadataStore,
ViewModelProvider,
useViewModel,
};
export { OHIF };
+10 -8
View File
@@ -10,32 +10,34 @@ describe('Top level exports', () => {
'HotkeysManager',
'ServicesManager',
//
'DicomMetadataStore',
//
'CineService',
'DisplaySetService',
'HangingProtocolService',
'ToolBarService',
'UINotificationService',
'UIModalService',
'UIDialogService',
'UIViewportDialogService',
'MeasurementService',
'ViewportGridService',
//
'IWebApiDataSource',
//
'defaults',
'utils',
'hotkeys',
'studies',
'redux',
'classes',
'metadata',
'header',
'cornerstone',
'default', //
'errorHandler',
'string',
'ui',
'user',
'object',
'log',
'DICOMWeb',
'DICOMSR',
'OHIF', //
'measurements',
'hangingProtocols',
].sort();
const exports = Object.keys(OHIF).sort();
@@ -108,7 +108,7 @@ const BaseImplementation = {
);
if (!existingStudy) {
const newStudy = new StudyMetadata(StudyInstanceUID);
const newStudy = createStudyMetadata(StudyInstanceUID);
newStudy.PatientID = study.PatientID;
newStudy.PatientName = study.PatientName;
@@ -130,13 +130,11 @@ class MeasurementService {
*/
createSource(name, version) {
if (!name) {
log.warn('Source name not provided. Exiting early.');
return;
throw new Error('Source name not provided.');
}
if (!version) {
log.warn('Source version not provided. Exiting early.');
return;
throw new Error('Source version not provided.');
}
const id = guid();
@@ -205,23 +203,23 @@ class MeasurementService {
toMeasurementSchema
) {
if (!this._isValidSource(source)) {
log.warn('Invalid source. Exiting early.');
return;
throw new Error('Invalid source.');
}
if (!matchingCriteria) {
log.warn('Matching criteria not provided. Exiting early.');
return;
throw new Error('Matching criteria not provided.');
}
if (!definition) {
log.warn('Definition not provided. Exiting early.');
return;
throw new Error('Definition not provided.');
}
if (!toSourceSchema) {
throw new Error('Mapping function to source schema not provided.');
}
if (!toMeasurementSchema) {
log.warn('Measurement mapping function not provided. Exiting early.');
return;
throw new Error('Measurement mapping function not provided.');
}
const mapping = {
@@ -297,7 +295,7 @@ class MeasurementService {
this.measurements[id] = updatedMeasurement;
this._broadcastChange(
this._broadcastEvent(
// Add an internal flag to say the measurement has not yet been updated at source.
this.EVENTS.MEASUREMENT_UPDATED,
{
@@ -380,14 +378,14 @@ class MeasurementService {
newMeasurement
);
this.measurements[internalId] = newMeasurement;
this._broadcastChange(this.EVENTS.MEASUREMENT_UPDATED, {
this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {
source,
measurement: newMeasurement,
});
} else {
log.info(`Measurement added.`, newMeasurement);
this.measurements[internalId] = newMeasurement;
this._broadcastChange(this.EVENTS.MEASUREMENT_ADDED, {
this._broadcastEvent(this.EVENTS.MEASUREMENT_ADDED, {
source,
measurement: newMeasurement,
});
@@ -406,22 +404,19 @@ class MeasurementService {
*/
addOrUpdate(source, definition, sourceMeasurement) {
if (!this._isValidSource(source)) {
log.warn('Invalid source. Exiting early.');
return;
throw new Error('Invalid source.');
}
if (!definition) {
throw new Error('No source definition provided.');
}
const sourceInfo = this._getSourceInfo(source);
if (!definition) {
log.warn('No source definition provided. Exiting early.');
return;
}
if (!this._sourceHasMappings(source)) {
log.warn(
throw new Error(
`No measurement mappings found for '${sourceInfo}' source. Exiting early.`
);
return;
}
let measurement = {};
@@ -437,24 +432,22 @@ class MeasurementService {
/* Assign measurement source instance */
measurement.source = source;
} catch (error) {
log.warn(
throw new Error(
`Failed to map '${sourceInfo}' measurement for definition ${definition}:`,
error.message
);
return;
}
if (!this._isValidMeasurement(measurement)) {
log.warn(
throw new Error(
`Attempting to add or update a invalid measurement provided by '${sourceInfo}'. Exiting early.`
);
return;
}
let internalId = sourceMeasurement.id;
if (!internalId) {
internalId = guid();
log.warn(`Measurement ID not found. Generating UID: ${internalId}`);
log.info(`Measurement ID not found. Generating UID: ${internalId}`);
}
const newMeasurement = {
@@ -469,15 +462,15 @@ class MeasurementService {
newMeasurement
);
this.measurements[internalId] = newMeasurement;
this._broadcastChange(this.EVENTS.MEASUREMENT_UPDATED, {
this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {
source,
measurement: newMeasurement,
notYetUpdatedAtSource: false,
});
} else {
log.info(`Measurement added.`, newMeasurement);
log.info('Measurement added.', newMeasurement);
this.measurements[internalId] = newMeasurement;
this._broadcastChange(this.EVENTS.MEASUREMENT_ADDED, {
this._broadcastEvent(this.EVENTS.MEASUREMENT_ADDED, {
source,
measurement: newMeasurement,
});
@@ -500,7 +493,7 @@ class MeasurementService {
}
delete this.measurements[id];
this._broadcastChange(this.EVENTS.MEASUREMENT_REMOVED, {
this._broadcastEvent(this.EVENTS.MEASUREMENT_REMOVED, {
source,
measurement: id, // This is weird :shrug:
});
@@ -508,7 +501,7 @@ class MeasurementService {
clearMeasurements() {
this.measurements = {};
this._broadcastChange(this.EVENTS.MEASUREMENTS_CLEARED);
this._broadcastEvent(this.EVENTS.MEASUREMENTS_CLEARED);
}
jumpToMeasurement(viewportIndex, id) {
@@ -559,7 +552,7 @@ class MeasurementService {
*/
clear() {
this.measurements = {};
this._broadcastChange(this.EVENTS.MEASUREMENTS_CLEARED);
this._broadcastEvent(this.EVENTS.MEASUREMENTS_CLEARED);
}
/**
@@ -618,27 +611,6 @@ class MeasurementService {
);
}
/**
* Broadcasts measurement changes.
*
* @param {string} eventName The event name.add
* @param {object} eventData.source The measurement source.
* @param {object} eventData.measurement The measurement.
* @param {boolean} eventData.notYetUpdatedAtSource True if the measurement was edited
* within the measurement service and the source needs to update.
* @return void
*/
_broadcastChange(eventName, eventData) {
const hasListeners = Object.keys(this.listeners).length > 0;
const hasCallbacks = Array.isArray(this.listeners[eventName]);
if (hasListeners && hasCallbacks) {
this.listeners[eventName].forEach(listener => {
listener.callback(eventData);
});
}
}
/**
* Check if a given measurement data is valid.
*
@@ -13,7 +13,7 @@ describe('MeasurementService.js', () => {
let source;
let definition;
let matchingCriteria;
let toAnnotation;
let toSourceSchema;
let toMeasurement;
let annotation;
@@ -40,8 +40,14 @@ describe('MeasurementService.js', () => {
],
source: source,
};
toAnnotation = () => annotation;
toMeasurement = () => measurement;
toSourceSchema = () => annotation;
toMeasurement = () => {
if (Object.keys(measurement).includes('invalidProperty')) {
throw new Error('Measurement does not match schema');
}
return measurement;
}
matchingCriteria = {
valueType: measurementService.VALUE_TYPES.POLYLINE,
points: 2,
@@ -55,16 +61,16 @@ describe('MeasurementService.js', () => {
measurementService.createSource('Testing', '1');
});
it('logs warning and return early if no name provided', () => {
measurementService.createSource(null, '1');
expect(log.warn.mock.calls.length).toBe(1);
it('throws Error if no name provided', () => {
expect(() => {
measurementService.createSource(null, '1')
}).toThrow(new Error('Source name not provided.'));
});
it('logs warning and return early if no version provided', () => {
measurementService.createSource('Testing', null);
expect(log.warn.mock.calls.length).toBe(1);
it('throws Error if no version provided', () => {
expect(() => {
measurementService.createSource('Testing', null)
}).toThrow(new Error('Source version not provided.'));
});
});
@@ -74,83 +80,84 @@ describe('MeasurementService.js', () => {
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
});
it('logs warning and return early if no matching criteria provided', () => {
measurementService.addMapping(
source,
definition,
null,
toAnnotation,
toMeasurement
);
it('throws Error if invalid source provided', () => {
expect(() => {
const invalidSource = {};
expect(log.warn.mock.calls.length).toBe(1);
measurementService.addMapping(
invalidSource,
definition,
matchingCriteria,
toSourceSchema,
toMeasurement
);
}).toThrow(new Error('Invalid source.'));
});
it('logs warning and return early if invalid source provided', () => {
const invalidSoure = {};
measurementService.addMapping(
invalidSoure,
definition,
matchingCriteria,
toAnnotation,
toMeasurement
);
expect(log.warn.mock.calls.length).toBe(1);
it('throws Error if no matching criteria provided', () => {
expect(() => {
measurementService.addMapping(
source,
definition,
null,
toSourceSchema,
toMeasurement
);
}).toThrow(new Error('Matching criteria not provided.'));
});
it('logs warning and return early if no source provided', () => {
measurementService.addMapping(
null /* source */,
definition,
matchingCriteria,
toAnnotation,
toMeasurement
);
expect(log.warn.mock.calls.length).toBe(1);
it('throws Error if no source provided', () => {
expect(() => {
measurementService.addMapping(
null /* source */,
definition,
matchingCriteria,
toSourceSchema,
toMeasurement
);
}).toThrow(new Error('Invalid source.'));
});
it('logs warning and return early if no definition provided', () => {
measurementService.addMapping(
source,
null /* definition */,
matchingCriteria,
toAnnotation,
toMeasurement
);
expect(log.warn.mock.calls.length).toBe(1);
expect(() => {
measurementService.addMapping(
source,
null /* definition */,
matchingCriteria,
toSourceSchema,
toMeasurement
);
}).toThrow(new Error('Definition not provided.'));
});
it('logs warning and return early if no measurement mapping function provided', () => {
measurementService.addMapping(
source,
definition,
matchingCriteria,
null /* toAnnotation */,
toMeasurement
);
expect(log.warn.mock.calls.length).toBe(1);
it('throws Error if no measurement mapping function provided', () => {
expect(() => {
measurementService.addMapping(
source,
definition,
matchingCriteria,
null /* toSourceSchema */,
toMeasurement
);
}).toThrow(new Error('Mapping function to source schema not provided.'));
});
it('logs warning and return early if no annotation mapping function provided', () => {
measurementService.addMapping(
source,
definition,
matchingCriteria,
toAnnotation,
null /* toMeasurement */
);
expect(log.warn.mock.calls.length).toBe(1);
it('throws Error if no annotation mapping function provided', () => {
expect(() => {
measurementService.addMapping(
source,
definition,
matchingCriteria,
toSourceSchema,
null /* toMeasurement */
);
}).toThrow(new Error('Measurement mapping function not provided.'));
});
});
@@ -160,7 +167,7 @@ describe('MeasurementService.js', () => {
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
const measurementId = source.addOrUpdate(definition, annotation);
@@ -174,7 +181,7 @@ describe('MeasurementService.js', () => {
source,
definition,
{},
toAnnotation,
toSourceSchema,
toMeasurement
);
const measurementId = source.addOrUpdate(definition, annotation);
@@ -196,7 +203,7 @@ describe('MeasurementService.js', () => {
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
@@ -215,7 +222,7 @@ describe('MeasurementService.js', () => {
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
@@ -235,7 +242,7 @@ describe('MeasurementService.js', () => {
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
@@ -248,9 +255,9 @@ describe('MeasurementService.js', () => {
});
it('fails to add new measurements when no mapping', () => {
source.addOrUpdate(definition, measurement);
expect(log.warn.mock.calls.length).toBe(1);
expect(() => {
source.addOrUpdate(definition, measurement);
}).toThrow()
});
it('fails to add new measurements when invalid mapping function', () => {
@@ -258,13 +265,13 @@ describe('MeasurementService.js', () => {
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
1 /* Invalid */
);
source.addOrUpdate(definition, measurement);
expect(log.warn.mock.calls.length).toBe(1);
expect(() => {
source.addOrUpdate(definition, measurement);
}).toThrow()
});
it('adds new measurement with custom id', () => {
@@ -274,7 +281,7 @@ describe('MeasurementService.js', () => {
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
@@ -291,28 +298,28 @@ describe('MeasurementService.js', () => {
expect(newMeasurement).toEqual(savedMeasurement);
});
it('logs warning and return if adding invalid measurement', () => {
it('throws Error if adding invalid measurement', () => {
measurement.invalidProperty = {};
measurementService.addMapping(
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
source.addOrUpdate(definition, measurement);
expect(log.warn.mock.calls.length).toBe(2);
expect(() => {
source.addOrUpdate(definition, measurement);
}).toThrow()
});
it('updates existent measurement', () => {
it('updates existing measurement', () => {
measurementService.addMapping(
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
@@ -333,7 +340,7 @@ describe('MeasurementService.js', () => {
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
@@ -357,7 +364,7 @@ describe('MeasurementService.js', () => {
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
@@ -384,7 +391,7 @@ describe('MeasurementService.js', () => {
source,
definition,
matchingCriteria,
toAnnotation,
toSourceSchema,
toMeasurement
);
@@ -65,6 +65,8 @@ export default class ToolBarService {
: !this.state.toggles[itemId];
break;
}
default:
throw new Error(`Invalid interaction type: ${interactionType}`);
}
// Run command if there's one associated
@@ -83,7 +85,7 @@ export default class ToolBarService {
this.state.groups[groupId] = itemId;
}
this._broadcastChange(this.EVENTS.TOOL_BAR_STATE_MODIFIED, {});
this._broadcastEvent(this.EVENTS.TOOL_BAR_STATE_MODIFIED, {});
}
getButtons() {
@@ -97,7 +99,7 @@ export default class ToolBarService {
setButton(id, button) {
if (this.buttons[id]) {
this.buttons[id] = merge(this.buttons[id], button);
this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, {
this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {
buttons: this.buttons,
button: this.buttons[id],
buttonSections: this.buttonSections,
@@ -107,7 +109,7 @@ export default class ToolBarService {
setButtons(buttons) {
this.buttons = buttons;
this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, {
this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {
buttons: this.buttons,
buttonSections: this.buttonSections,
});
@@ -138,7 +140,7 @@ export default class ToolBarService {
// Props check important for validation here...
this.buttonSections[key] = buttons;
this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, {});
this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {});
}
/**
@@ -178,26 +180,9 @@ export default class ToolBarService {
}
});
this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, {});
this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {});
}
/**
* Broadcasts toolbarService changes.
*
* @param {string} eventName The event name
* @return void
*/
_broadcastChange = (eventName, callbackProps) => {
const hasListeners = Object.keys(this.listeners).length > 0;
const hasCallbacks = Array.isArray(this.listeners[eventName]);
if (hasListeners && hasCallbacks) {
this.listeners[eventName].forEach(listener => {
listener.callback(callbackProps);
});
}
};
/**
*
* @param {*} btn
@@ -28,6 +28,7 @@ const serviceImplementation = {
*/
function _show({
viewportIndex,
id,
type,
message,
actions,
@@ -36,6 +37,7 @@ function _show({
}) {
return serviceImplementation._show({
viewportIndex,
id,
type,
message,
actions,
-2
View File
@@ -1,4 +1,3 @@
// TODO: This is duplicated in TypeSafeCollection
function isObject(subject) {
return (
subject instanceof Object ||
@@ -6,7 +5,6 @@ function isObject(subject) {
);
}
// TODO: This is duplicated in TypeSafeCollection
function isString(subject) {
return typeof subject === 'string';
}
+12 -12
View File
@@ -21,12 +21,12 @@ describe('Queue', () => {
const mockedTimeout = jest.fn(timeout);
const timer = queue.bind(mockedTimeout);
const start = Date.now();
timer(120).then(now => {
timer(1200).then(now => {
const elapsed = now - start;
expect(elapsed >= 120 && elapsed < 240).toBe(true);
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
});
const end = await timer(120);
expect(end - start > 240).toBe(true);
const end = await timer(1200);
expect(end - start > 2400).toBe(true);
expect(mockedTimeout).toBeCalledTimes(2);
});
it('should prevent task execution when queue limit is reached', async () => {
@@ -34,15 +34,15 @@ describe('Queue', () => {
const mockedTimeout = jest.fn(timeout);
const timer = queue.bind(mockedTimeout);
const start = Date.now();
const promise = timer(120).then(time => time - start);
const promise = timer(1200).then(time => time - start);
try {
await timer(120);
await timer(1200);
} catch (e) {
expect(Date.now() - start < 120).toBe(true);
expect(Date.now() - start < 1200).toBe(true);
expect(e.message).toBe('Queue limit reached');
}
const elapsed = await promise;
expect(elapsed >= 120 && elapsed < 240).toBe(true);
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
expect(mockedTimeout).toBeCalledTimes(1);
});
it('should safely bind tasks to the queue', async () => {
@@ -51,16 +51,16 @@ describe('Queue', () => {
const mockedTimeout = jest.fn(timeout);
const timer = queue.bindSafe(mockedTimeout, mockedErrorHandler);
const start = Date.now();
const promise = timer(120).then(time => time - start);
await timer(120);
expect(Date.now() - start < 120).toBe(true);
const promise = timer(1200).then(time => time - start);
await timer(1200);
expect(Date.now() - start < 1200).toBe(true);
expect(mockedErrorHandler).toBeCalledTimes(1);
expect(mockedErrorHandler).nthCalledWith(
1,
expect.objectContaining({ message: 'Queue limit reached' })
);
const elapsed = await promise;
expect(elapsed >= 120 && elapsed < 240).toBe(true);
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
expect(mockedTimeout).toBeCalledTimes(1);
});
});
+1 -3
View File
@@ -1,5 +1,3 @@
import OHIFError from '../classes/OHIFError.js';
let stackMap = {};
let configuration = {};
const stackUpdatedCallbacks = [];
@@ -110,7 +108,7 @@ const StackManager = {
*/
addStackUpdatedCallback(callback) {
if (typeof callback !== 'function') {
throw new OHIFError('callback must be provided as a function');
throw new Error('callback must be provided as a function');
}
stackUpdatedCallbacks.push(callback);
},
@@ -190,4 +190,6 @@ function toSublist(value) {
* Exports
*/
const hierarchicalListUtils = { addToList, getItem, forEach, print };
export { addToList, getItem, forEach, print };
export default hierarchicalListUtils;
+46 -34
View File
@@ -1,28 +1,41 @@
"use strict";
/**
* This extension allows you to record a sequence using Mousetrap.
* {@link https://craig.is/killing/mice}
*
* @author Dan Tao <daniel.tao@gmail.com>
*/
export default function(Mousetrap) {
module.exports = function (Mousetrap, options) {
/**
* the default configurations, we merge with user options
*
* @type {Array}
*/
var _config = Object.assign({
timeout: 1000
}, options);
/**
* the sequence currently being recorded
*
* @type {Array}
*/
var _recordedSequence = [],
/**
* a callback to invoke after recording a sequence
*
* @type {Function|null}
*/
_recordedSequenceCallback = null,
/**
* a list of all of the keys currently held down
*
* @type {Array}
*/
_currentRecordedKeys = [],
/**
* temporary state where we remember if we've already captured a
* character key in the current combo
@@ -30,19 +43,28 @@ export default function(Mousetrap) {
* @type {boolean}
*/
_recordedCharacterKey = false,
/**
* a handle for the timer of the current recording
*
* @type {null|number}
*/
_recordTimer = null,
/**
* the original handleKey method to override when Mousetrap.record() is
* called
*
* @type {Function}
*/
_origHandleKey = Mousetrap.prototype.handleKey;
_origHandleKey = Mousetrap.prototype.handleKey,
/**
* the timeout that timer will wait for a key
*
* @type {number}
*/
_recordTimeout = _config.timeout;
/**
* handles a character key event
@@ -61,19 +83,19 @@ export default function(Mousetrap) {
}
// remember this character if we're currently recording a sequence
if (e.type == 'keydown') {
if (e.type === 'keydown') {
if (character.length === 1 && _recordedCharacterKey) {
_recordCurrentCombo();
}
for (let i = 0; i < modifiers.length; ++i) {
for (var i = 0; i < modifiers.length; ++i) {
_recordKey(modifiers[i]);
}
_recordKey(character);
// once a key is released, all keys that were held down at the time
// count as a keypress
} else if (e.type == 'keyup' && _currentRecordedKeys.length > 0) {
} else if (e.type === 'keyup' && _currentRecordedKeys.length > 0) {
_recordCurrentCombo();
}
}
@@ -85,8 +107,10 @@ export default function(Mousetrap) {
* @returns void
*/
function _recordKey(key) {
var i;
// one-off implementation of Array.indexOf, since IE6-9 don't support it
for (let i = 0; i < _currentRecordedKeys.length; ++i) {
for (i = 0; i < _currentRecordedKeys.length; ++i) {
if (_currentRecordedKeys[i] === key) {
return;
}
@@ -109,7 +133,7 @@ export default function(Mousetrap) {
_recordedSequence.push(_currentRecordedKeys);
_currentRecordedKeys = [];
_recordedCharacterKey = false;
_finishRecording();
_restartRecordTimer();
}
/**
@@ -122,7 +146,9 @@ export default function(Mousetrap) {
* @returns void
*/
function _normalizeSequence(sequence) {
for (let i = 0; i < sequence.length; ++i) {
var i;
for (i = 0; i < sequence.length; ++i) {
sequence[i].sort(function(x, y) {
// modifier keys always come first, in alphabetical order
if (x.length > 1 && y.length === 1) {
@@ -168,7 +194,7 @@ export default function(Mousetrap) {
*/
function _restartRecordTimer() {
clearTimeout(_recordTimer);
_recordTimer = setTimeout(_finishRecording, 1000);
_recordTimer = setTimeout(_finishRecording, _recordTimeout);
}
/**
@@ -178,41 +204,27 @@ export default function(Mousetrap) {
* @param {Function} callback
* @returns void
*/
Mousetrap.prototype.record = function(callback) {
Mousetrap.prototype.record = function(callback, timeout) {
var self = this;
self.recording = true;
// if the user doesn't want to change the timeout
// we still need to guarantee that it gets the default timeout
_recordTimeout = timeout || _config.timeout;
_recordedSequenceCallback = function() {
self.recording = false;
callback.apply(self, arguments);
};
};
/**
* stop recording
*
* @param {Function} callback
* @returns void
*/
Mousetrap.prototype.stopRecord = function() {
var self = this;
self.recording = false;
};
/**
* start recording
*
* @param {Function} callback
* @returns void
*/
Mousetrap.prototype.startRecording = function() {
var self = this;
self.recording = true;
};
Mousetrap.prototype.handleKey = function() {
var self = this;
_handleKey.apply(self, arguments);
};
Mousetrap.init();
}
return Mousetrap;
};
+3 -8
View File
@@ -1,7 +1,6 @@
import ObjectPath from './objectPath';
import StackManager from './StackManager.js';
import absoluteUrl from './absoluteUrl';
import addServers from './addServers';
import guid from './guid';
import sortBy from './sortBy.js';
import sortBySeriesDate from './sortBySeriesDate.js';
@@ -9,7 +8,7 @@ import writeScript from './writeScript.js';
import DicomLoaderService from './dicomLoaderService.js';
import b64toBlob from './b64toBlob.js';
//import loadAndCacheDerivedDisplaySets from './loadAndCacheDerivedDisplaySets.js';
import * as urlUtil from './urlUtil';
import urlUtil from './urlUtil';
import makeDeferred from './makeDeferred';
import makeCancelable from './makeCancelable';
import hotkeys from './hotkeys';
@@ -18,19 +17,17 @@ import isDicomUid from './isDicomUid';
import formatDate from './formatDate';
import formatPN from './formatPN';
import resolveObjectPath from './resolveObjectPath';
import * as hierarchicalListUtils from './hierarchicalListUtils';
import * as progressTrackingUtils from './progressTrackingUtils';
import hierarchicalListUtils from './hierarchicalListUtils';
import progressTrackingUtils from './progressTrackingUtils';
import isLowPriorityModality from './isLowPriorityModality';
// Commented out unused functionality.
// Now that we have the DicomMetadataStore, we no longer need the studyMetadataManager
// Need to implement new mechanism for dervived displaySets using the displaySetManager.
const utils = {
guid,
ObjectPath,
absoluteUrl,
addServers,
sortBy,
sortBySeriesDate,
writeScript,
@@ -38,7 +35,6 @@ const utils = {
formatPN,
b64toBlob,
StackManager,
//studyMetadataManager,
DicomLoaderService,
urlUtil,
//loadAndCacheDerivedDisplaySets,
@@ -57,7 +53,6 @@ export {
guid,
ObjectPath,
absoluteUrl,
addServers,
sortBy,
formatDate,
writeScript,
+5 -3
View File
@@ -6,13 +6,15 @@ describe('Top level exports', () => {
'guid',
'ObjectPath',
'absoluteUrl',
'addServers',
'sortBy',
'sortBySeriesDate',
'isLowPriorityModality',
'writeScript',
'b64toBlob',
'StackManager',
'studyMetadataManager',
'loadAndCacheDerivedDisplaySets',
'formatDate',
'formatPN',
//'loadAndCacheDerivedDisplaySets',
'DicomLoaderService',
'urlUtil',
'makeDeferred',
@@ -3,7 +3,6 @@ import log from '../../log';
export default function getPixelSpacingInformation(instance) {
// See http://gdcm.sourceforge.net/wiki/index.php/Imager_Pixel_Spacing
// TODO: Add Ultrasound region spacing
// TODO: Add manual calibration
// TODO: Use ENUMS from dcmjs
@@ -318,7 +318,7 @@ function notify(list, data) {
* Exports
*/
export {
const progressTrackingUtils = {
createList,
isList,
createTask,
@@ -334,3 +334,5 @@ export {
addObserver,
removeObserver,
};
export default progressTrackingUtils;
@@ -1,4 +1,4 @@
import * as utils from './progressTrackingUtils';
import utils from './progressTrackingUtils';
describe('progressTrackingUtils', () => {
describe('Creation of lists of tasks to be tracked', () => {
+3 -1
View File
@@ -70,4 +70,6 @@ const paramString = {
replaceParam,
};
export { parse, queryString, paramString };
const urlUtil = { parse, queryString, paramString };
export default urlUtil;