chore: Clean up unused files, imports, and work on getting end-to-end tests and unit tests running (#2272)
This commit is contained in:
1 parent
414ebc6850
commit
0db81b30f3
374 files changed
+14138
-21288
No files matched your search
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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');
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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,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;
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -70,4 +70,6 @@ const paramString = {
|
||||
replaceParam,
|
||||
};
|
||||
|
||||
export { parse, queryString, paramString };
|
||||
const urlUtil = { parse, queryString, paramString };
|
||||
|
||||
export default urlUtil;
|
||||
Reference in new issue
Block a user