\n );\n },\n },\n });\n });\n}\n","import { hydrateStructuredReport } from '@ohif/extension-cornerstone-dicom-sr';\n\nconst RESPONSE = {\n NO_NEVER: -1,\n CANCEL: 0,\n CREATE_REPORT: 1,\n ADD_SERIES: 2,\n SET_STUDY_AND_SERIES: 3,\n NO_NOT_FOR_SERIES: 4,\n HYDRATE_REPORT: 5,\n};\n\nfunction promptHydrateStructuredReport(\n { servicesManager, extensionManager },\n ctx,\n evt\n) {\n const {\n uiViewportDialogService,\n displaySetService,\n } = servicesManager.services;\n const { viewportIndex, displaySetInstanceUID } = evt;\n const srDisplaySet = displaySetService.getDisplaySetByUID(\n displaySetInstanceUID\n );\n\n return new Promise(async function(resolve, reject) {\n const promptResult = await _askTrackMeasurements(\n uiViewportDialogService,\n viewportIndex\n );\n\n // Need to do action here... So we can set state...\n let StudyInstanceUID, SeriesInstanceUIDs;\n\n if (promptResult === RESPONSE.HYDRATE_REPORT) {\n console.warn('!! HYDRATING STRUCTURED REPORT');\n const hydrationResult = hydrateStructuredReport(\n { servicesManager, extensionManager },\n displaySetInstanceUID\n );\n\n StudyInstanceUID = hydrationResult.StudyInstanceUID;\n SeriesInstanceUIDs = hydrationResult.SeriesInstanceUIDs;\n }\n\n resolve({\n userResponse: promptResult,\n displaySetInstanceUID: evt.displaySetInstanceUID,\n srSeriesInstanceUID: srDisplaySet.SeriesInstanceUID,\n viewportIndex,\n StudyInstanceUID,\n SeriesInstanceUIDs,\n });\n });\n}\n\nfunction _askTrackMeasurements(uiViewportDialogService, viewportIndex) {\n return new Promise(function(resolve, reject) {\n const message =\n 'Do you want to continue tracking measurements for this study?';\n const actions = [\n {\n type: 'secondary',\n text: 'No',\n value: RESPONSE.CANCEL,\n },\n {\n type: 'primary',\n text: 'Yes',\n value: RESPONSE.HYDRATE_REPORT,\n },\n ];\n const onSubmit = result => {\n uiViewportDialogService.hide();\n resolve(result);\n };\n\n uiViewportDialogService.show({\n viewportIndex,\n type: 'info',\n message,\n actions,\n onSubmit,\n onOutsideClick: () => {\n uiViewportDialogService.hide();\n resolve(RESPONSE.CANCEL);\n },\n });\n });\n}\n\nexport default promptHydrateStructuredReport;\n","import { hydrateStructuredReport as baseHydrateStructuredReport } from '@ohif/extension-cornerstone-dicom-sr';\n\nfunction hydrateStructuredReport(\n { servicesManager, extensionManager },\n ctx,\n evt\n) {\n const { displaySetService } = servicesManager.services;\n const { viewportIndex, displaySetInstanceUID } = evt;\n const srDisplaySet = displaySetService.getDisplaySetByUID(\n displaySetInstanceUID\n );\n\n return new Promise((resolve, reject) => {\n const hydrationResult = baseHydrateStructuredReport(\n { servicesManager, extensionManager },\n displaySetInstanceUID\n );\n\n const StudyInstanceUID = hydrationResult.StudyInstanceUID;\n const SeriesInstanceUIDs = hydrationResult.SeriesInstanceUIDs;\n\n resolve({\n displaySetInstanceUID: evt.displaySetInstanceUID,\n srSeriesInstanceUID: srDisplaySet.SeriesInstanceUID,\n viewportIndex,\n StudyInstanceUID,\n SeriesInstanceUIDs,\n });\n });\n}\n\nexport default hydrateStructuredReport;\n","import React, { useContext, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport { Machine } from 'xstate';\nimport { useMachine } from '@xstate/react';\nimport { useViewportGrid } from '@ohif/ui';\nimport {\n machineConfiguration,\n defaultOptions,\n} from './measurementTrackingMachine';\nimport promptBeginTracking from './promptBeginTracking';\nimport promptTrackNewSeries from './promptTrackNewSeries';\nimport promptTrackNewStudy from './promptTrackNewStudy';\nimport promptSaveReport from './promptSaveReport';\nimport promptHydrateStructuredReport from './promptHydrateStructuredReport';\nimport hydrateStructuredReport from './hydrateStructuredReport';\n\nconst TrackedMeasurementsContext = React.createContext();\nTrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext';\nconst useTrackedMeasurements = () => useContext(TrackedMeasurementsContext);\n\nconst SR_SOPCLASSHANDLERID =\n '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr';\n\n/**\n *\n * @param {*} param0\n */\nfunction TrackedMeasurementsContextProvider(\n { servicesManager, commandsManager, extensionManager }, // Bound by consumer\n { children } // Component props\n) {\n const [viewportGrid, viewportGridService] = useViewportGrid();\n const { activeViewportIndex, viewports } = viewportGrid;\n\n const machineOptions = Object.assign({}, defaultOptions);\n machineOptions.actions = Object.assign({}, machineOptions.actions, {\n jumpToFirstMeasurementInActiveViewport: (ctx, evt) => {\n const { measurementService } = servicesManager.services;\n\n const { trackedStudy, trackedSeries } = ctx;\n const measurements = measurementService.getMeasurements();\n const trackedMeasurements = measurements.filter(\n m =>\n trackedStudy === m.referenceStudyUID &&\n trackedSeries.includes(m.referenceSeriesUID)\n );\n\n const uid = trackedMeasurements[0].uid;\n\n measurementService.jumpToMeasurement(\n viewportGrid.activeViewportIndex,\n uid\n );\n },\n showStructuredReportDisplaySetInActiveViewport: (ctx, evt) => {\n if (evt.data.createdDisplaySetInstanceUIDs.length > 0) {\n const StructuredReportDisplaySetInstanceUID =\n evt.data.createdDisplaySetInstanceUIDs[0].displaySetInstanceUID;\n\n viewportGridService.setDisplaySetsForViewport({\n viewportIndex: evt.data.viewportIndex,\n displaySetInstanceUIDs: [StructuredReportDisplaySetInstanceUID],\n });\n }\n },\n discardPreviouslyTrackedMeasurements: (ctx, evt) => {\n const { measurementService } = servicesManager.services;\n const measurements = measurementService.getMeasurements();\n const filteredMeasurements = measurements.filter(ms =>\n ctx.prevTrackedSeries.includes(ms.referenceSeriesUID)\n );\n const measurementIds = filteredMeasurements.map(fm => fm.id);\n\n for (let i = 0; i < measurementIds.length; i++) {\n measurementService.remove(measurementIds[i]);\n }\n },\n clearAllMeasurements: (ctx, evt) => {\n const { measurementService } = servicesManager.services;\n const measurements = measurementService.getMeasurements();\n const measurementIds = measurements.map(fm => fm.uid);\n\n for (let i = 0; i < measurementIds.length; i++) {\n measurementService.remove(measurementIds[i]);\n }\n },\n });\n machineOptions.services = Object.assign({}, machineOptions.services, {\n promptBeginTracking: promptBeginTracking.bind(null, {\n servicesManager,\n extensionManager,\n }),\n promptTrackNewSeries: promptTrackNewSeries.bind(null, {\n servicesManager,\n extensionManager,\n }),\n promptTrackNewStudy: promptTrackNewStudy.bind(null, {\n servicesManager,\n extensionManager,\n }),\n promptSaveReport: promptSaveReport.bind(null, {\n servicesManager,\n commandsManager,\n extensionManager,\n }),\n promptHydrateStructuredReport: promptHydrateStructuredReport.bind(null, {\n servicesManager,\n extensionManager,\n }),\n hydrateStructuredReport: hydrateStructuredReport.bind(null, {\n servicesManager,\n extensionManager,\n }),\n });\n\n // TODO: IMPROVE\n // - Add measurement_updated to cornerstone; debounced? (ext side, or consumption?)\n // - Friendlier transition/api in front of measurementTracking machine?\n // - Blocked: viewport overlay shouldn't clip when resized\n // TODO: PRIORITY\n // - Fix \"ellipses\" series description dynamic truncate length\n // - Fix viewport border resize\n // - created/destroyed hooks for extensions (cornerstone measurement subscriptions in it's `init`)\n\n const measurementTrackingMachine = Machine(\n machineConfiguration,\n machineOptions\n );\n\n const [\n trackedMeasurements,\n sendTrackedMeasurementsEvent,\n trackedMeasurementsService,\n ] = useMachine(measurementTrackingMachine);\n\n // ~~ Listen for changes to ViewportGrid for potential SRs hung in panes when idle\n useEffect(() => {\n if (viewports.length > 0) {\n const activeViewport = viewports[activeViewportIndex];\n\n if (!activeViewport || !activeViewport?.displaySetInstanceUIDs?.length) {\n return;\n }\n\n // Todo: Getting the first displaySetInstanceUID is wrong, but we don't have\n // tracking fusion viewports yet. This should change when we do.\n const { displaySetService } = servicesManager.services;\n const displaySet = displaySetService.getDisplaySetByUID(\n activeViewport.displaySetInstanceUIDs[0]\n );\n\n // If this is an SR produced by our SR SOPClassHandler,\n // and it hasn't been loaded yet, do that now so we\n // can check if it can be rehydrated or not.\n //\n // Note: This happens:\n // - If the viewport is not currently an OHIFCornerstoneSRViewport\n // - If the displaySet has never been hung\n //\n // Otherwise, the displaySet will be loaded by the useEffect handler\n // listening to displaySet changes inside OHIFCornerstoneSRViewport.\n // The issue here is that this handler in TrackedMeasurementsContext\n // ends up occurring before the Viewport is created, so the displaySet\n // is not loaded yet, and isRehydratable is undefined unless we call load().\n if (\n displaySet.SOPClassHandlerId === SR_SOPCLASSHANDLERID &&\n !displaySet.isLoaded &&\n displaySet.load\n ) {\n displaySet.load();\n }\n\n // Magic string\n // load function added by our sopClassHandler module\n if (\n displaySet.SOPClassHandlerId === SR_SOPCLASSHANDLERID &&\n displaySet.isRehydratable === true\n ) {\n console.log('sending event...', trackedMeasurements);\n sendTrackedMeasurementsEvent('PROMPT_HYDRATE_SR', {\n displaySetInstanceUID: displaySet.displaySetInstanceUID,\n SeriesInstanceUID: displaySet.SeriesInstanceUID,\n viewportIndex: activeViewportIndex,\n });\n }\n }\n }, [\n activeViewportIndex,\n sendTrackedMeasurementsEvent,\n servicesManager.services,\n viewports,\n ]);\n\n return (\n \n {children}\n \n );\n}\n\nTrackedMeasurementsContextProvider.propTypes = {\n children: PropTypes.oneOf([PropTypes.func, PropTypes.node]),\n servicesManager: PropTypes.object.isRequired,\n commandsManager: PropTypes.object.isRequired,\n extensionManager: PropTypes.object.isRequired,\n};\n\nexport {\n TrackedMeasurementsContext,\n TrackedMeasurementsContextProvider,\n useTrackedMeasurements,\n};\n","import {\n TrackedMeasurementsContext,\n TrackedMeasurementsContextProvider,\n useTrackedMeasurements,\n} from './contexts';\n\nfunction getContextModule({\n servicesManager,\n extensionManager,\n commandsManager,\n}) {\n const BoundTrackedMeasurementsContextProvider = TrackedMeasurementsContextProvider.bind(\n null,\n { servicesManager, extensionManager, commandsManager }\n );\n\n return [\n {\n name: 'TrackedMeasurementsContext',\n context: TrackedMeasurementsContext,\n provider: BoundTrackedMeasurementsContextProvider,\n },\n ];\n}\n\nexport { useTrackedMeasurements };\nexport default getContextModule;\n","import getAttribute from './getAttribute.js';\r\nimport getAuthorizationHeader from './getAuthorizationHeader.js';\r\nimport getModalities from './getModalities.js';\r\nimport getName from './getName.js';\r\nimport getNumber from './getNumber.js';\r\nimport getString from './getString.js';\r\n\r\nconst DICOMWeb = {\r\n getAttribute,\r\n getAuthorizationHeader,\r\n getModalities,\r\n getName,\r\n getNumber,\r\n getString,\r\n};\r\n\r\nexport {\r\n getAttribute,\r\n getAuthorizationHeader,\r\n getModalities,\r\n getName,\r\n getNumber,\r\n getString,\r\n};\r\n\r\nexport default DICOMWeb;\r\n","/**\r\n * Returns the specified element as a dicom attribute group/element.\r\n *\r\n * @param element - The group/element of the element (e.g. '00280009')\r\n * @param [defaultValue] - The value to return if the element is not present\r\n * @returns {*}\r\n */\r\nexport default function getAttribute(element, defaultValue) {\r\n if (!element) {\r\n return defaultValue;\r\n }\r\n // Value is not present if the attribute has a zero length value\r\n if (!element.Value) {\r\n return defaultValue;\r\n }\r\n // Sanity check to make sure we have at least one entry in the array.\r\n if (!element.Value.length) {\r\n return defaultValue;\r\n }\r\n\r\n return convertToInt(element.Value);\r\n}\r\n\r\nfunction convertToInt(input) {\r\n function padFour(input) {\r\n const l = input.length;\r\n\r\n if (l == 0) return '0000';\r\n if (l == 1) return '000' + input;\r\n if (l == 2) return '00' + input;\r\n if (l == 3) return '0' + input;\r\n\r\n return input;\r\n }\r\n\r\n let output = '';\r\n for (let i = 0; i < input.length; i++) {\r\n for (let j = 0; j < input[i].length; j++) {\r\n output += padFour(input[i].charCodeAt(j).toString(16));\r\n }\r\n }\r\n\r\n return parseInt(output, 16);\r\n}\r\n","import 'isomorphic-base64'\r\nimport user from '../user';\r\n\r\n/**\r\n * Returns the Authorization header as part of an Object.\r\n *\r\n * @export\r\n * @param {Object} [server={}]\r\n * @param {Object} [server.requestOptions]\r\n * @param {string|function} [server.requestOptions.auth]\r\n * @returns {Object} { Authorization }\r\n */\r\nexport default function getAuthorizationHeader({ requestOptions } = {}, user) {\r\n const headers = {};\r\n\r\n // Check for OHIF.user since this can also be run on the server\r\n const accessToken = user && user.getAccessToken && user.getAccessToken();\r\n\r\n // Auth for a specific server\r\n if (requestOptions && requestOptions.auth) {\r\n if (typeof requestOptions.auth === 'function') {\r\n // Custom Auth Header\r\n headers.Authorization = requestOptions.auth(requestOptions);\r\n } else {\r\n // HTTP Basic Auth (user:password)\r\n headers.Authorization = `Basic ${btoa(requestOptions.auth)}`;\r\n }\r\n }\r\n // Auth for the user's default\r\n else if (accessToken) {\r\n headers.Authorization = `Bearer ${accessToken}`;\r\n }\r\n\r\n return headers;\r\n}\r\n","export default function getModalities(Modality, ModalitiesInStudy) {\r\n if (!Modality && !ModalitiesInStudy) {\r\n return {};\r\n }\r\n\r\n const modalities = Modality || {\r\n vr: 'CS',\r\n Value: [],\r\n };\r\n\r\n // Rare case, depending on the DICOM server we are using, but sometimes,\r\n // modalities.Value is undefined or null.\r\n modalities.Value = modalities.Value || [];\r\n\r\n if (ModalitiesInStudy) {\r\n if (modalities.vr && modalities.vr === ModalitiesInStudy.vr) {\r\n for (let i = 0; i < ModalitiesInStudy.Value.length; i++) {\r\n const value = ModalitiesInStudy.Value[i];\r\n if (modalities.Value.indexOf(value) === -1) {\r\n modalities.Value.push(value);\r\n }\r\n }\r\n } else {\r\n return ModalitiesInStudy;\r\n }\r\n }\r\n\r\n return modalities;\r\n}\r\n","/**\r\n * Returns the Alphabetic version of a PN\r\n *\r\n * @param element - The group/element of the element (e.g. '00200013')\r\n * @param [defaultValue] - The default value to return if the element is not found\r\n * @returns {*}\r\n */\r\nexport default function getName(element, defaultValue) {\r\n if (!element) {\r\n return defaultValue;\r\n }\r\n // Value is not present if the attribute has a zero length value\r\n if (!element.Value) {\r\n return defaultValue;\r\n }\r\n // Sanity check to make sure we have at least one entry in the array.\r\n if (!element.Value.length) {\r\n return defaultValue;\r\n }\r\n // Return the Alphabetic component group\r\n if (element.Value[0].Alphabetic) {\r\n return element.Value[0].Alphabetic;\r\n }\r\n // Orthanc does not return PN properly so this is a temporary workaround\r\n return element.Value[0];\r\n}\r\n","/**\r\n * Returns the first string value as a Javascript Number\r\n * @param element - The group/element of the element (e.g. '00200013')\r\n * @param [defaultValue] - The default value to return if the element does not exist\r\n * @returns {*}\r\n */\r\nexport default function getNumber(element, defaultValue) {\r\n if (!element) {\r\n return defaultValue;\r\n }\r\n // Value is not present if the attribute has a zero length value\r\n if (!element.Value) {\r\n return defaultValue;\r\n }\r\n // Sanity check to make sure we have at least one entry in the array.\r\n if (!element.Value.length) {\r\n return defaultValue;\r\n }\r\n\r\n return parseFloat(element.Value[0]);\r\n}\r\n","/**\r\n * Returns the specified element as a string. Multi-valued elements will be separated by a backslash\r\n *\r\n * @param element - The group/element of the element (e.g. '00200013')\r\n * @param [defaultValue] - The value to return if the element is not present\r\n * @returns {*}\r\n */\r\nexport default function getString(element, defaultValue) {\r\n if (!element) {\r\n return defaultValue;\r\n }\r\n // Value is not present if the attribute has a zero length value\r\n if (!element.Value) {\r\n return defaultValue;\r\n }\r\n // Sanity check to make sure we have at least one entry in the array.\r\n if (!element.Value.length) {\r\n return defaultValue;\r\n }\r\n // Join the array together separated by backslash\r\n // NOTE: Orthanc does not correctly split values into an array so the join is a no-op\r\n return element.Value.join('\\\\');\r\n}\r\n","import { DicomMetadataStore } from '../services/DicomMetadataStore';\r\n// TODO: Use above to inject so dependent datasources don't need to import or\r\n// depend on @ohif/core?\r\n\r\n/**\r\n * Factory function that creates a new \"Web API\" data source.\r\n * A \"Web API\" data source is any source that fetches data over\r\n * HTTP. This function serves as an \"adapter\" to wrap those calls\r\n * so that all \"Web API\" data sources have the same interface and can\r\n * be used interchangeably.\r\n *\r\n * It's worth noting that a single implementation of this interface\r\n * can define different underlying sources for \"read\" and \"write\" operations.\r\n */\r\nfunction create({\r\n query,\r\n retrieve,\r\n store,\r\n reject,\r\n initialize,\r\n deleteStudyMetadataPromise,\r\n getImageIdsForDisplaySet,\r\n getImageIdsForInstance,\r\n}) {\r\n const defaultQuery = {\r\n studies: {\r\n /**\r\n * @param {string} params.patientName\r\n * @param {string} params.mrn\r\n * @param {object} params.studyDate\r\n * @param {string} params.description\r\n * @param {string} params.modality\r\n * @param {string} params.accession\r\n * @param {string} params.sortBy\r\n * @param {string} params.sortDirection -\r\n * @param {number} params.page\r\n * @param {number} params.resultsPerPage\r\n */\r\n mapParams: params => params,\r\n requestResults: () => {},\r\n processResults: results => results,\r\n },\r\n series: {},\r\n instances: {},\r\n };\r\n\r\n const defaultRetrieve = {\r\n series: {},\r\n };\r\n\r\n const defaultStore = {\r\n dicom: async naturalizedDataset => {\r\n throw new Error(\r\n 'store.dicom(naturalizedDicom, StudyInstanceUID) not implemented for dataSource.'\r\n );\r\n },\r\n };\r\n\r\n const defaultReject = {};\r\n return {\r\n query: query || defaultQuery,\r\n retrieve: retrieve || defaultRetrieve,\r\n reject: reject || defaultReject,\r\n store: store || defaultStore,\r\n initialize,\r\n deleteStudyMetadataPromise,\r\n getImageIdsForDisplaySet,\r\n getImageIdsForInstance,\r\n };\r\n}\r\n\r\nconst IWebApiDataSource = {\r\n create,\r\n};\r\n\r\nexport default IWebApiDataSource;\r\n","import log from '../log.js';\r\nimport { Command, Commands } from '../types/Command';\r\n\r\n/**\r\n * The definition of a command\r\n *\r\n * @typedef {Object} CommandDefinition\r\n * @property {Function} commandFn - Command to call\r\n * @property {Object} options - Object of params to pass action\r\n */\r\n\r\n/**\r\n * The Commands Manager tracks named commands (or functions) that are scoped to\r\n * a context. When we attempt to run a command with a given name, we look for it\r\n * in our active contexts. If found, we run the command, passing in any application\r\n * or call specific data specified in the command's definition.\r\n *\r\n * NOTE: A more robust version of the CommandsManager lives in v1. If you're looking\r\n * to extend this class, please check it's source before adding new methods.\r\n */\r\nexport class CommandsManager {\r\n constructor({ } = {}) {\r\n this.contexts = {};\r\n }\r\n\r\n /**\r\n * Allows us to create commands \"per context\". An example would be the \"Cornerstone\"\r\n * context having a `SaveImage` command, and the \"VTK\" context having a `SaveImage`\r\n * command. The distinction of a context allows us to call the command in either\r\n * context, and have faith that the correct command will be run.\r\n *\r\n * @method\r\n * @param {string} contextName - Namespace for commands\r\n * @returns {undefined}\r\n */\r\n createContext(contextName) {\r\n if (!contextName) {\r\n return;\r\n }\r\n\r\n if (this.contexts[contextName]) {\r\n return this.clearContext(contextName);\r\n }\r\n\r\n this.contexts[contextName] = {};\r\n }\r\n\r\n /**\r\n * Returns all command definitions for a given context\r\n *\r\n * @method\r\n * @param {string} contextName - Namespace for commands\r\n * @returns {Object} - the matched context\r\n */\r\n getContext(contextName) {\r\n const context = this.contexts[contextName];\r\n\r\n if (!context) {\r\n return;\r\n }\r\n\r\n return context;\r\n }\r\n\r\n /**\r\n * Clears all registered commands for a given context.\r\n *\r\n * @param {string} contextName - Namespace for commands\r\n * @returns {undefined}\r\n */\r\n clearContext(contextName) {\r\n if (!contextName) {\r\n return;\r\n }\r\n\r\n this.contexts[contextName] = {};\r\n }\r\n\r\n /**\r\n * Register a new command with the command manager. Scoped to a context, and\r\n * with a definition to assist command callers w/ providing the necessary params\r\n *\r\n * @method\r\n * @param {string} contextName - Namespace for command; often scoped to the extension that added it\r\n * @param {string} commandName - Unique name identifying the command\r\n * @param {CommandDefinition} definition - {@link CommandDefinition}\r\n */\r\n registerCommand(contextName, commandName, definition) {\r\n if (typeof definition !== 'object') {\r\n return;\r\n }\r\n\r\n const context = this.getContext(contextName);\r\n if (!context) {\r\n return;\r\n }\r\n\r\n context[commandName] = definition;\r\n }\r\n\r\n /**\r\n * Finds a command with the provided name if it exists in the specified context,\r\n * or a currently active context.\r\n *\r\n * @method\r\n * @param {String} commandName - Command to find\r\n * @param {String} [contextName] - Specific command to look in. Defaults to current activeContexts\r\n */\r\n getCommand = (commandName: string, contextName?: string) => {\r\n const contexts = [];\r\n\r\n if (contextName) {\r\n const context = this.getContext(contextName);\r\n if (context) {\r\n contexts.push(context);\r\n }\r\n } else {\r\n Object.keys(this.contexts).forEach(contextName => {\r\n contexts.push(this.getContext(contextName));\r\n });\r\n }\r\n\r\n if (contexts.length === 0) {\r\n return;\r\n }\r\n\r\n let foundCommand;\r\n contexts.forEach(context => {\r\n if (context[commandName]) {\r\n foundCommand = context[commandName];\r\n }\r\n });\r\n\r\n return foundCommand;\r\n };\r\n\r\n /**\r\n *\r\n * @method\r\n * @param {String} commandName\r\n * @param {Object} [options={}] - Extra options to pass the command. Like a mousedown event\r\n * @param {String} [contextName]\r\n */\r\n public runCommand(commandName: string, options = {}, contextName?: string) {\r\n const definition = this.getCommand(commandName, contextName);\r\n if (!definition) {\r\n log.warn(`Command \"${commandName}\" not found in current context`);\r\n return;\r\n }\r\n\r\n const { commandFn } = definition;\r\n const commandParams = Object.assign(\r\n {},\r\n definition.options, // \"Command configuration\"\r\n options // \"Time of call\" info\r\n );\r\n\r\n if (typeof commandFn !== 'function') {\r\n log.warn(`No commandFn was defined for command \"${commandName}\"`);\r\n return;\r\n } else {\r\n return commandFn(commandParams);\r\n }\r\n }\r\n\r\n /**\r\n * Executar one or more commands with specified extra options.\r\n * Returns the result of the last command run.\r\n *\r\n * @param toRun - A specification of one or more commands\r\n * @param options - to include in the commands run beyond\r\n * the commandOptions specified in the base.\r\n */\r\n public run(\r\n toRun: Command | Commands | Command[] | undefined,\r\n options?: Record\r\n ): unknown {\r\n if (!toRun) return;\r\n const commands =\r\n (Array.isArray(toRun) && toRun) ||\r\n ((toRun as Command).commandName && [toRun]) ||\r\n (Array.isArray((toRun as Commands).commands) &&\r\n (toRun as Commands).commands);\r\n if (!commands) {\r\n console.log(\"Command isn't runnable\", toRun);\r\n return;\r\n }\r\n\r\n let result;\r\n (commands as Command[]).forEach(\r\n ({ commandName, commandOptions, context }) => {\r\n if (commandName) {\r\n result = this.runCommand(\r\n commandName,\r\n {\r\n ...commandOptions,\r\n ...options,\r\n },\r\n context\r\n );\r\n } else {\r\n console.warn('No command name supplied in', toRun);\r\n }\r\n }\r\n );\r\n\r\n return result;\r\n }\r\n}\r\n\r\nexport default CommandsManager;\r\n","import objectHash from 'object-hash';\r\nimport log from '../log.js';\r\nimport { hotkeys } from '../utils';\r\nimport isequal from 'lodash.isequal';\r\nimport Hotkey from './Hotkey';\r\nimport ServicesManager from '../services/ServicesManager';\r\n\r\n/**\r\n *\r\n *\r\n * @typedef {Object} HotkeyDefinition\r\n * @property {String} commandName - Command to call\r\n * @property {Object} commandOptions - Command options\r\n * @property {String} label - Display name for hotkey\r\n * @property {String[]} keys - Keys to bind; Follows Mousetrap.js binding syntax\r\n */\r\n\r\nexport class HotkeysManager {\r\n private _servicesManager: ServicesManager;\r\n\r\n constructor(commandsManager, servicesManager) {\r\n this.hotkeyDefinitions = {};\r\n this.hotkeyDefaults = [];\r\n this.isEnabled = true;\r\n\r\n if (!commandsManager) {\r\n throw new Error(\r\n 'HotkeysManager instantiated without a commandsManager. Hotkeys will be unable to find and run commands.'\r\n );\r\n }\r\n\r\n this._servicesManager = servicesManager;\r\n this._commandsManager = commandsManager;\r\n }\r\n\r\n /**\r\n * Exposes Mousetrap.js's `.record` method, added by the record plugin.\r\n *\r\n * @param {*} event\r\n */\r\n record(event) {\r\n return hotkeys.record(event);\r\n }\r\n\r\n /**\r\n * Disables all hotkeys. Hotkeys added while disabled will not listen for\r\n * input.\r\n */\r\n disable() {\r\n this.isEnabled = false;\r\n hotkeys.pause();\r\n }\r\n\r\n /**\r\n * Enables all hotkeys.\r\n */\r\n enable() {\r\n this.isEnabled = true;\r\n hotkeys.unpause();\r\n }\r\n\r\n /**\r\n * Registers a list of hotkeydefinitions.\r\n *\r\n * @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions\r\n */\r\n setHotkeys(hotkeyDefinitions = [], name = 'hotkey-definitions') {\r\n try {\r\n const definitions = this.getValidDefinitions(hotkeyDefinitions);\r\n if (isequal(definitions, this.hotkeyDefaults)) {\r\n localStorage.removeItem(name);\r\n } else {\r\n localStorage.setItem(name, JSON.stringify(definitions));\r\n }\r\n definitions.forEach(definition => this.registerHotkeys(definition));\r\n } catch (error) {\r\n const { uiNotificationService } = this._servicesManager.services;\r\n uiNotificationService.show({\r\n title: 'Hotkeys Manager',\r\n message: 'Error while setting hotkeys',\r\n type: 'error',\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Set default hotkey bindings. These\r\n * values are used in `this.restoreDefaultBindings`.\r\n *\r\n * @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions\r\n */\r\n setDefaultHotKeys(hotkeyDefinitions = []) {\r\n const definitions = this.getValidDefinitions(hotkeyDefinitions);\r\n this.hotkeyDefaults = definitions;\r\n }\r\n\r\n /**\r\n * Take hotkey definitions that can be an array or object and make sure that it\r\n * returns an array of hotkeys\r\n *\r\n * @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions\r\n */\r\n getValidDefinitions(hotkeyDefinitions) {\r\n const definitions = Array.isArray(hotkeyDefinitions)\r\n ? [...hotkeyDefinitions]\r\n : this._parseToArrayLike(hotkeyDefinitions);\r\n\r\n return definitions;\r\n }\r\n\r\n /**\r\n * Take hotkey definitions that can be an array and make sure that it\r\n * returns an object of hotkeys definitions\r\n *\r\n * @param {HotkeyDefinition[]} [hotkeyDefinitions=[]] Contains hotkeys definitions\r\n * @returns {Object}\r\n */\r\n getValidHotkeyDefinitions(hotkeyDefinitions) {\r\n const definitions = this.getValidDefinitions(hotkeyDefinitions);\r\n const objectDefinitions = {};\r\n definitions.forEach(definition => {\r\n const { commandName, commandOptions } = definition;\r\n const commandHash = objectHash({ commandName, commandOptions });\r\n objectDefinitions[commandHash] = definition;\r\n });\r\n return objectDefinitions;\r\n }\r\n\r\n /**\r\n * It parses given object containing hotkeyDefinition to array like.\r\n * Each property of given object will be mapped to an object of an array. And its property name will be the value of a property named as commandName\r\n *\r\n * @param {HotkeyDefinition[] | Object} [hotkeyDefinitions={}] Contains hotkeys definitions\r\n * @returns {HotkeyDefinition[]}\r\n */\r\n _parseToArrayLike(hotkeyDefinitionsObj = {}) {\r\n const copy = { ...hotkeyDefinitionsObj };\r\n return Object.entries(copy).map(entryValue =>\r\n this._parseToHotKeyObj(entryValue[0], entryValue[1])\r\n );\r\n }\r\n\r\n /**\r\n * Return HotkeyDefinition object like based on given property name and property value\r\n * @param {string} propertyName property name of hotkey definition object\r\n * @param {object} propertyValue property value of hotkey definition object\r\n *\r\n * @example\r\n *\r\n * const hotKeyObj = {hotKeyDefA: {keys:[],....}}\r\n *\r\n * const parsed = _parseToHotKeyObj(Object.keys(hotKeyDefA)[0], hotKeyObj[hotKeyDefA]);\r\n * {\r\n * commandName: hotKeyDefA,\r\n * keys: [],\r\n * ....\r\n * }\r\n *\r\n */\r\n _parseToHotKeyObj(propertyName, propertyValue) {\r\n return {\r\n commandName: propertyName,\r\n ...propertyValue,\r\n };\r\n }\r\n\r\n /**\r\n * (unbinds and) binds the specified command to one or more key combinations.\r\n * When a hotkey combination is triggered, the command name and active contexts\r\n * are used to locate the correct command to call.\r\n *\r\n * @param {HotkeyDefinition} command\r\n * @param {String} extension\r\n * @returns {undefined}\r\n */\r\n registerHotkeys(\r\n {\r\n commandName,\r\n commandOptions = {},\r\n context,\r\n keys,\r\n label,\r\n isEditable,\r\n }: Hotkey = {},\r\n extension\r\n ) {\r\n if (!commandName) {\r\n throw new Error(`No command was defined for hotkey \"${keys}\"`);\r\n }\r\n\r\n const commandHash = objectHash({ commandName, commandOptions });\r\n const options = Object.keys(commandOptions).length\r\n ? JSON.stringify(commandOptions)\r\n : 'no';\r\n const previouslyRegisteredDefinition = this.hotkeyDefinitions[commandHash];\r\n\r\n if (previouslyRegisteredDefinition) {\r\n const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys;\r\n this._unbindHotkeys(commandName, previouslyRegisteredKeys);\r\n // log.info(\r\n // `[hotkeys] Unbinding ${commandName} with ${options} options from ${previouslyRegisteredKeys}`\r\n // );\r\n }\r\n\r\n // Set definition & bind\r\n this.hotkeyDefinitions[commandHash] = {\r\n commandName,\r\n commandOptions,\r\n keys,\r\n label,\r\n isEditable,\r\n };\r\n this._bindHotkeys(commandName, commandOptions, context, keys);\r\n // log.info(\r\n // `[hotkeys] Binding ${commandName} with ${options} from ${context ||\r\n // 'default'} options to ${keys}`\r\n // );\r\n }\r\n\r\n /**\r\n * Uses most recent\r\n *\r\n * @returns {undefined}\r\n */\r\n restoreDefaultBindings() {\r\n this.setHotkeys(this.hotkeyDefaults);\r\n }\r\n\r\n /**\r\n *\r\n */\r\n destroy() {\r\n this.hotkeyDefaults = [];\r\n this.hotkeyDefinitions = {};\r\n hotkeys.reset();\r\n }\r\n\r\n /**\r\n * Binds one or more set of hotkey combinations for a given command\r\n *\r\n * @private\r\n * @param {string} commandName - The name of the command to trigger when hotkeys are used\r\n * @param {string[]} keys - One or more key combinations that should trigger command\r\n * @returns {undefined}\r\n */\r\n _bindHotkeys(commandName, commandOptions = {}, context, keys) {\r\n const isKeyDefined = keys === '' || keys === undefined;\r\n if (isKeyDefined) {\r\n return;\r\n }\r\n\r\n const isKeyArray = keys instanceof Array;\r\n const combinedKeys = isKeyArray ? keys.join('+') : keys;\r\n\r\n hotkeys.bind(combinedKeys, evt => {\r\n evt.preventDefault();\r\n evt.stopPropagation();\r\n this._commandsManager.runCommand(\r\n commandName,\r\n { evt, ...commandOptions },\r\n context\r\n );\r\n });\r\n }\r\n\r\n /**\r\n * unbinds one or more set of hotkey combinations for a given command\r\n *\r\n * @private\r\n * @param {string} commandName - The name of the previously bound command\r\n * @param {string[]} keys - One or more sets of previously bound keys\r\n * @returns {undefined}\r\n */\r\n _unbindHotkeys(commandName, keys) {\r\n const isKeyDefined = keys !== '' && keys !== undefined;\r\n if (!isKeyDefined) {\r\n return;\r\n }\r\n\r\n const isKeyArray = keys instanceof Array;\r\n if (isKeyArray) {\r\n const combinedKeys = keys.join('+');\r\n this._unbindHotkeys(commandName, combinedKeys);\r\n return;\r\n }\r\n\r\n hotkeys.unbind(keys);\r\n }\r\n}\r\n\r\nexport default HotkeysManager;\r\n\r\n// Commands Contexts:\r\n\r\n// --> Name and Priority\r\n// GLOBAL: 0\r\n// VIEWER::CORNERSTONE: 1\r\n// VIEWER::VTK: 1\r\n","import guid from '../utils/guid.js';\r\nimport { Vector3 } from 'cornerstone-math';\r\n\r\nconst OBJECT = 'object';\r\n\r\n/**\r\n * This class defines an ImageSet object which will be used across the viewer. This object represents\r\n * a list of images that are associated by any arbitrary criteria being thus content agnostic. Besides the\r\n * main attributes (images and uid) it allows additional attributes to be appended to it (currently\r\n * indiscriminately, but this should be changed).\r\n */\r\nclass ImageSet {\r\n constructor(images) {\r\n if (Array.isArray(images) !== true) {\r\n throw new Error('ImageSet expects an array of images');\r\n }\r\n\r\n // @property \"images\"\r\n Object.defineProperty(this, 'images', {\r\n enumerable: false,\r\n configurable: false,\r\n writable: false,\r\n value: images,\r\n });\r\n\r\n // @property \"uid\"\r\n Object.defineProperty(this, 'uid', {\r\n enumerable: false,\r\n configurable: false,\r\n writable: false,\r\n value: guid(), // Unique ID of the instance\r\n });\r\n }\r\n\r\n getUID() {\r\n return this.uid;\r\n }\r\n\r\n setAttribute(attribute, value) {\r\n this[attribute] = value;\r\n }\r\n\r\n getAttribute(attribute) {\r\n return this[attribute];\r\n }\r\n\r\n setAttributes(attributes) {\r\n if (typeof attributes === OBJECT && attributes !== null) {\r\n const imageSet = this,\r\n hasOwn = Object.prototype.hasOwnProperty;\r\n for (let attribute in attributes) {\r\n if (hasOwn.call(attributes, attribute)) {\r\n imageSet[attribute] = attributes[attribute];\r\n }\r\n }\r\n }\r\n }\r\n\r\n getNumImages = () => this.images.length\r\n\r\n getImage(index) {\r\n return this.images[index];\r\n }\r\n\r\n sortBy(sortingCallback) {\r\n return this.images.sort(sortingCallback);\r\n }\r\n\r\n sortByImagePositionPatient() {\r\n const images = this.images;\r\n const referenceImagePositionPatient = _getImagePositionPatient(images[0]);\r\n\r\n const refIppVec = new Vector3(\r\n referenceImagePositionPatient[0],\r\n referenceImagePositionPatient[1],\r\n referenceImagePositionPatient[2]\r\n );\r\n\r\n const ImageOrientationPatient = _getImageOrientationPatient(images[0]);\r\n\r\n const scanAxisNormal = new Vector3(\r\n ImageOrientationPatient[0],\r\n ImageOrientationPatient[1],\r\n ImageOrientationPatient[2]\r\n ).cross(\r\n new Vector3(\r\n ImageOrientationPatient[3],\r\n ImageOrientationPatient[4],\r\n ImageOrientationPatient[5]\r\n )\r\n );\r\n\r\n const distanceImagePairs = images.map(function(image) {\r\n const ippVec = new Vector3(..._getImagePositionPatient(image));\r\n const positionVector = refIppVec.clone().sub(ippVec);\r\n const distance = positionVector.dot(scanAxisNormal);\r\n\r\n return {\r\n distance,\r\n image,\r\n };\r\n });\r\n\r\n distanceImagePairs.sort(function(a, b) {\r\n return b.distance - a.distance;\r\n });\r\n\r\n const sortedImages = distanceImagePairs.map(a => a.image);\r\n\r\n images.sort(function(a, b) {\r\n return sortedImages.indexOf(a) - sortedImages.indexOf(b);\r\n });\r\n }\r\n}\r\n\r\nfunction _getImagePositionPatient(image) {\r\n return image.getData().metadata.ImagePositionPatient;\r\n}\r\n\r\nfunction _getImageOrientationPatient(image) {\r\n return image.getData().metadata.ImageOrientationPatient;\r\n}\r\n\r\nexport default ImageSet;\r\n","/**\r\n * Gets the palette color data for the specified tag - red/green/blue,\r\n * either from the given UID or from the tag itself.\r\n * Returns an array if the data is immediately available, or a promise\r\n * which resolves to the data if the data needs to be loaded.\r\n * Returns undefined if the palette isn't specified.\r\n *\r\n * @param {*} item containing the palette colour data and description\r\n * @param {*} tag is the tag for the palette data\r\n * @param {*} descriptorTag is the tag for the descriptor\r\n * @returns Array view containing the palette data, or a promise to return one.\r\n * Returns undefined if the palette data is absent.\r\n */\r\nexport default function fetchPaletteColorLookupTableData(\r\n item,\r\n tag,\r\n descriptorTag\r\n) {\r\n const { PaletteColorLookupTableUID } = item;\r\n const paletteData = item[tag];\r\n if (paletteData === undefined && PaletteColorLookupTableUID === undefined)\r\n return;\r\n // performance optimization - read UID and cache by UID\r\n return _getPaletteColor(item[tag], item[descriptorTag]);\r\n}\r\n\r\nfunction _getPaletteColor(paletteColorLookupTableData, lutDescriptor) {\r\n //const numLutEntries = lutDescriptor[0];\r\n const numLutEntries = lutDescriptor[0] === 0 ? 65535 : lutDescriptor[0]; //Correção DEV-2518\r\n const bits = lutDescriptor[2];\r\n\r\n if (!paletteColorLookupTableData) return undefined;\r\n\r\n const arrayBufferToPaletteColorLUT = arraybuffer => {\r\n const lut = [];\r\n\r\n if (bits === 16) {\r\n let j = 0;\r\n for (let i = 0; i < numLutEntries; i++) {\r\n lut[i] = (arraybuffer[j++] + arraybuffer[j++]) << 8;\r\n }\r\n } else {\r\n for (let i = 0; i < numLutEntries; i++) {\r\n lut[i] = byteArray[i];\r\n }\r\n }\r\n return lut;\r\n };\r\n\r\n if (paletteColorLookupTableData.palette) {\r\n return paletteColorLookupTableData.palette;\r\n }\r\n\r\n if (paletteColorLookupTableData.InlineBinary) {\r\n try {\r\n const arraybuffer = Uint8Array.from(\r\n atob(paletteColorLookupTableData.InlineBinary),\r\n c => c.charCodeAt(0)\r\n );\r\n return (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT(\r\n arraybuffer\r\n ));\r\n } catch (e) {\r\n console.log(\r\n \"Couldn't decode\",\r\n paletteColorLookupTableData.InlineBinary,\r\n e\r\n );\r\n return undefined;\r\n }\r\n }\r\n\r\n if (paletteColorLookupTableData.retrieveBulkData) {\r\n return paletteColorLookupTableData\r\n .retrieveBulkData()\r\n .then(\r\n val =>\r\n (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT(\r\n val\r\n ))\r\n );\r\n }\r\n\r\n console.error(`No data found for ${paletteColorLookupTableData} palette`);\r\n}\r\n","/**\r\n * Combine the Per instance frame data, the shared frame data\r\n * and the root data objects.\r\n * The data is combined by taking nested sequence objects within\r\n * the functional group sequences. Data that is directly contained\r\n * within the functional group sequences, such as private creators\r\n * will be ignored.\r\n * This can be safely called with an undefined frame in order to handle\r\n * single frame data. (eg frame is undefined is the same as frame===1).\r\n */\r\nconst combineFrameInstance = (frame, instance) => {\r\n const {\r\n PerFrameFunctionalGroupsSequence,\r\n SharedFunctionalGroupsSequence,\r\n NumberOfFrames,\r\n } = instance;\r\n\r\n if (PerFrameFunctionalGroupsSequence === undefined\r\n || PerFrameFunctionalGroupsSequence === null\r\n || SharedFunctionalGroupsSequence === undefined\r\n || SharedFunctionalGroupsSequence === null)\r\n return instance;\r\n\r\n if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) {\r\n try {\r\n let frameNumber = Number.parseInt(frame || 1);\r\n frameNumber = frameNumber - 1;\r\n frameNumber = frameNumber === undefined ? 0 : frameNumber;\r\n const shared = (SharedFunctionalGroupsSequence\r\n //? SharedFunctionalGroupsSequence[0] && Object.values(SharedFunctionalGroupsSequence[0])\r\n ? SharedFunctionalGroupsSequence[0]\r\n : []\r\n )\r\n .map(it => it[0])\r\n .filter(it => it !== undefined && typeof it === 'object');\r\n const perFrame = (PerFrameFunctionalGroupsSequence\r\n ? PerFrameFunctionalGroupsSequence[frameNumber] && Object.values(PerFrameFunctionalGroupsSequence[frameNumber])\r\n : []\r\n )\r\n .map(it => it[0])\r\n .filter(it => it !== undefined && typeof it === 'object');\r\n\r\n return Object.assign(\r\n { frameNumber: frameNumber },\r\n instance,\r\n ...Object.values(shared),\r\n ...Object.values(perFrame)\r\n );\r\n } catch (error) {\r\n return instance;\r\n }\r\n } else {\r\n return instance;\r\n }\r\n};\r\n\r\nexport default combineFrameInstance;\r\n","import queryString from 'query-string';\r\nimport dicomParser from 'dicom-parser';\r\nimport { imageIdToURI } from '../utils';\r\nimport getPixelSpacingInformation from '../utils/metadataProvider/getPixelSpacingInformation';\r\nimport DicomMetadataStore from '../services/DicomMetadataStore';\r\nimport fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';\r\nimport toNumber from '../utils/toNumber';\r\nimport combineFrameInstance from '../utils/combineFrameInstance';\r\n\r\nclass MetadataProvider {\r\n constructor() {\r\n // Define the main \"metadataLookup\" private property as an immutable property.\r\n Object.defineProperty(this, 'studies', {\r\n configurable: false,\r\n enumerable: false,\r\n writable: false,\r\n value: new Map(),\r\n });\r\n Object.defineProperty(this, 'imageURIToUIDs', {\r\n configurable: false,\r\n enumerable: false,\r\n writable: false,\r\n value: new Map(),\r\n });\r\n // Can be used to store custom metadata for a specific type.\r\n // For instance, the scaling metadata for PET can be stored here\r\n // as type \"scalingModule\"\r\n //\r\n Object.defineProperty(this, 'customMetadata', {\r\n configurable: false,\r\n enumerable: false,\r\n writable: false,\r\n value: new Map(),\r\n });\r\n }\r\n\r\n addImageIdToUIDs(imageId, uids) {\r\n // This method is a fallback for when you don't have WADO-URI or WADO-RS.\r\n // You can add instances fetched by any method by calling addInstance, and hook an imageId to point at it here.\r\n // An example would be dicom hosted at some random site.\r\n const imageURI = imageIdToURI(imageId);\r\n this.imageURIToUIDs.set(imageURI, uids);\r\n }\r\n\r\n addCustomMetadata(imageId, type, metadata) {\r\n const imageURI = imageIdToURI(imageId);\r\n if (!this.customMetadata.has(type)) {\r\n this.customMetadata.set(type, {});\r\n }\r\n\r\n this.customMetadata.get(type)[imageURI] = metadata;\r\n }\r\n\r\n _getInstance(imageId) {\r\n const uids = this.getUIDsFromImageID(imageId);\r\n\r\n if (!uids) {\r\n return;\r\n }\r\n\r\n const {\r\n StudyInstanceUID,\r\n SeriesInstanceUID,\r\n SOPInstanceUID,\r\n frameNumber,\r\n } = uids;\r\n\r\n const instance = DicomMetadataStore.getInstance(\r\n StudyInstanceUID,\r\n SeriesInstanceUID,\r\n SOPInstanceUID\r\n );\r\n return (\r\n (frameNumber && combineFrameInstance(frameNumber, instance)) || instance\r\n );\r\n }\r\n\r\n get(query, imageId, options = { fallback: false }) {\r\n const instance = this._getInstance(imageId);\r\n\r\n if (query === INSTANCE) { // Excluir Medidas (DEV-2758) * NÃO REMOVER.\r\n return instance;\r\n }\r\n\r\n // check inside custom metadata\r\n if (this.customMetadata.has(query)) {\r\n const customMetadata = this.customMetadata.get(query);\r\n const imageURI = imageIdToURI(imageId);\r\n if (customMetadata[imageURI]) {\r\n return customMetadata[imageURI];\r\n }\r\n }\r\n\r\n return this.getTagFromInstance(query, instance, options);\r\n }\r\n\r\n getTag(query, imageId, options) {\r\n return this.get(query, imageId, options);\r\n }\r\n\r\n getInstance(imageId) {\r\n return this.get(INSTANCE, imageId);\r\n }\r\n\r\n getTagFromInstance(\r\n naturalizedTagOrWADOImageLoaderTag,\r\n instance,\r\n options = { fallback: false }\r\n ) {\r\n if (!instance) {\r\n return;\r\n }\r\n\r\n // If its a naturalized dcmjs tag present on the instance, return.\r\n if (instance[naturalizedTagOrWADOImageLoaderTag]) {\r\n return instance[naturalizedTagOrWADOImageLoaderTag];\r\n }\r\n\r\n // Maybe its a legacy dicomImageLoader tag then:\r\n return this._getCornerstoneWADOImageLoaderTag(\r\n naturalizedTagOrWADOImageLoaderTag,\r\n instance\r\n );\r\n }\r\n\r\n _getCornerstoneWADOImageLoaderTag(wadoImageLoaderTag, instance) {\r\n let metadata;\r\n\r\n switch (wadoImageLoaderTag) {\r\n case WADO_IMAGE_LOADER_TAGS.GENERAL_SERIES_MODULE:\r\n const { SeriesDate, SeriesTime } = instance;\r\n\r\n let seriesDate;\r\n let seriesTime;\r\n\r\n if (SeriesDate) {\r\n seriesDate = dicomParser.parseDA(SeriesDate);\r\n }\r\n\r\n if (SeriesTime) {\r\n seriesTime = dicomParser.parseTM(SeriesTime);\r\n }\r\n\r\n metadata = {\r\n modality: instance.Modality,\r\n seriesInstanceUID: instance.SeriesInstanceUID,\r\n seriesNumber: toNumber(instance.SeriesNumber),\r\n studyInstanceUID: instance.StudyInstanceUID,\r\n seriesDate,\r\n seriesTime,\r\n };\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.PATIENT_STUDY_MODULE:\r\n metadata = {\r\n patientAge: toNumber(instance.PatientAge),\r\n patientSize: toNumber(instance.PatientSize),\r\n patientWeight: toNumber(instance.PatientWeight),\r\n };\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.PATIENT_DEMOGRAPHIC_MODULE:\r\n metadata = {\r\n patientSex: instance.PatientSex,\r\n };\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.IMAGE_PLANE_MODULE:\r\n const { ImageOrientationPatient } = instance;\r\n\r\n // Fallback for DX images.\r\n // TODO: We should use the rest of the results of this function\r\n // to update the UI somehow\r\n const { PixelSpacing } = getPixelSpacingInformation(instance);\r\n\r\n let rowPixelSpacing;\r\n let columnPixelSpacing;\r\n\r\n let rowCosines;\r\n let columnCosines;\r\n\r\n if (PixelSpacing) {\r\n rowPixelSpacing = PixelSpacing[0];\r\n columnPixelSpacing = PixelSpacing[1];\r\n }\r\n\r\n if (ImageOrientationPatient) {\r\n rowCosines = ImageOrientationPatient.slice(0, 3);\r\n columnCosines = ImageOrientationPatient.slice(3, 6);\r\n }\r\n\r\n metadata = {\r\n frameOfReferenceUID: instance.FrameOfReferenceUID,\r\n rows: toNumber(instance.Rows),\r\n columns: toNumber(instance.Columns),\r\n imageOrientationPatient: toNumber(ImageOrientationPatient),\r\n rowCosines: toNumber(rowCosines || [0, 1, 0]),\r\n columnCosines: toNumber(columnCosines || [0, 0, -1]),\r\n imagePositionPatient: toNumber(\r\n instance.ImagePositionPatient || [0, 0, 0]\r\n ),\r\n sliceThickness: toNumber(instance.SliceThickness),\r\n sliceLocation: toNumber(instance.SliceLocation),\r\n //TODO: Implementado na DEV-2302.\r\n pixelSpacing: PixelSpacing ? toNumber(PixelSpacing) : 0,\r\n rowPixelSpacing: PixelSpacing ? toNumber(rowPixelSpacing || 1) : 0,\r\n columnPixelSpacing: PixelSpacing ? toNumber(columnPixelSpacing || 1) : 0,\r\n };\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.IMAGE_PIXEL_MODULE:\r\n metadata = {\r\n samplesPerPixel: toNumber(instance.SamplesPerPixel),\r\n photometricInterpretation: instance.PhotometricInterpretation,\r\n rows: toNumber(instance.Rows),\r\n columns: toNumber(instance.Columns),\r\n bitsAllocated: toNumber(instance.BitsAllocated),\r\n bitsStored: toNumber(instance.BitsStored),\r\n highBit: toNumber(instance.HighBit),\r\n pixelRepresentation: toNumber(instance.PixelRepresentation),\r\n planarConfiguration: toNumber(instance.PlanarConfiguration),\r\n pixelAspectRatio: toNumber(instance.PixelAspectRatio),\r\n smallestPixelValue: toNumber(instance.SmallestPixelValue),\r\n largestPixelValue: toNumber(instance.LargestPixelValue),\r\n redPaletteColorLookupTableDescriptor: toNumber(\r\n instance.RedPaletteColorLookupTableDescriptor\r\n ),\r\n greenPaletteColorLookupTableDescriptor: toNumber(\r\n instance.GreenPaletteColorLookupTableDescriptor\r\n ),\r\n bluePaletteColorLookupTableDescriptor: toNumber(\r\n instance.BluePaletteColorLookupTableDescriptor\r\n ),\r\n redPaletteColorLookupTableData: fetchPaletteColorLookupTableData(\r\n instance,\r\n 'RedPaletteColorLookupTableData',\r\n 'RedPaletteColorLookupTableDescriptor'\r\n ),\r\n greenPaletteColorLookupTableData: fetchPaletteColorLookupTableData(\r\n instance,\r\n 'GreenPaletteColorLookupTableData',\r\n 'GreenPaletteColorLookupTableDescriptor'\r\n ),\r\n bluePaletteColorLookupTableData: fetchPaletteColorLookupTableData(\r\n instance,\r\n 'BluePaletteColorLookupTableData',\r\n 'BluePaletteColorLookupTableDescriptor'\r\n ),\r\n };\r\n\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.VOI_LUT_MODULE:\r\n const { WindowCenter, WindowWidth, VOILUTFunction } = instance;\r\n if (WindowCenter === undefined || WindowWidth === undefined) {\r\n return;\r\n }\r\n const windowCenter = Array.isArray(WindowCenter)\r\n ? WindowCenter\r\n : [WindowCenter];\r\n const windowWidth = Array.isArray(WindowWidth)\r\n ? WindowWidth\r\n : [WindowWidth];\r\n\r\n metadata = {\r\n windowCenter: toNumber(windowCenter),\r\n windowWidth: toNumber(windowWidth),\r\n voiLUTFunction: VOILUTFunction,\r\n };\r\n\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.MODALITY_LUT_MODULE:\r\n const { RescaleIntercept, RescaleSlope } = instance;\r\n if (RescaleIntercept === undefined || RescaleSlope === undefined) {\r\n return;\r\n }\r\n\r\n metadata = {\r\n rescaleIntercept: toNumber(instance.RescaleIntercept),\r\n rescaleSlope: toNumber(instance.RescaleSlope),\r\n rescaleType: instance.RescaleType,\r\n };\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.SOP_COMMON_MODULE:\r\n metadata = {\r\n sopClassUID: instance.SOPClassUID,\r\n sopInstanceUID: instance.SOPInstanceUID,\r\n };\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.PET_ISOTOPE_MODULE:\r\n const { RadiopharmaceuticalInformationSequence } = instance;\r\n\r\n if (RadiopharmaceuticalInformationSequence) {\r\n const RadiopharmaceuticalInformation = Array.isArray(\r\n RadiopharmaceuticalInformationSequence\r\n )\r\n ? RadiopharmaceuticalInformationSequence[0]\r\n : RadiopharmaceuticalInformationSequence;\r\n\r\n const {\r\n RadiopharmaceuticalStartTime,\r\n RadionuclideTotalDose,\r\n RadionuclideHalfLife,\r\n } = RadiopharmaceuticalInformation;\r\n\r\n const radiopharmaceuticalInfo = {\r\n radiopharmaceuticalStartTime: dicomParser.parseTM(\r\n RadiopharmaceuticalStartTime\r\n ),\r\n radionuclideTotalDose: RadionuclideTotalDose,\r\n radionuclideHalfLife: RadionuclideHalfLife,\r\n };\r\n metadata = {\r\n radiopharmaceuticalInfo,\r\n };\r\n }\r\n\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.OVERLAY_PLANE_MODULE:\r\n const overlays = [];\r\n\r\n for (\r\n let overlayGroup = 0x00;\r\n overlayGroup <= 0x1e;\r\n overlayGroup += 0x02\r\n ) {\r\n let groupStr = `60${overlayGroup.toString(16)}`;\r\n\r\n if (groupStr.length === 3) {\r\n groupStr = `600${overlayGroup.toString(16)}`;\r\n }\r\n\r\n const OverlayDataTag = `${groupStr}3000`;\r\n const OverlayData = instance[OverlayDataTag];\r\n\r\n if (!OverlayData) {\r\n continue;\r\n }\r\n\r\n const OverlayRowsTag = `${groupStr}0010`;\r\n const OverlayColumnsTag = `${groupStr}0011`;\r\n const OverlayType = `${groupStr}0040`;\r\n const OverlayOriginTag = `${groupStr}0050`;\r\n const OverlayDescriptionTag = `${groupStr}0022`;\r\n const OverlayLabelTag = `${groupStr}1500`;\r\n const ROIAreaTag = `${groupStr}1301`;\r\n const ROIMeanTag = `${groupStr}1302`;\r\n const ROIStandardDeviationTag = `${groupStr}1303`;\r\n const OverlayOrigin = instance[OverlayOriginTag];\r\n\r\n const overlay = {\r\n rows: instance[OverlayRowsTag],\r\n columns: instance[OverlayColumnsTag],\r\n type: instance[OverlayType],\r\n x: OverlayOrigin[0],\r\n y: OverlayOrigin[1],\r\n pixelData: OverlayData,\r\n description: instance[OverlayDescriptionTag],\r\n label: instance[OverlayLabelTag],\r\n roiArea: instance[ROIAreaTag],\r\n roiMean: instance[ROIMeanTag],\r\n roiStandardDeviation: instance[ROIStandardDeviationTag],\r\n };\r\n\r\n overlays.push(overlay);\r\n }\r\n\r\n metadata = {\r\n overlays,\r\n };\r\n\r\n break;\r\n\r\n case WADO_IMAGE_LOADER_TAGS.PATIENT_MODULE:\r\n const { PatientName } = instance;\r\n\r\n let patientName;\r\n if (PatientName) {\r\n patientName = PatientName.Alphabetic;\r\n }\r\n\r\n metadata = {\r\n patientName,\r\n patientId: instance.PatientID,\r\n };\r\n\r\n break;\r\n\r\n case WADO_IMAGE_LOADER_TAGS.GENERAL_IMAGE_MODULE:\r\n metadata = {\r\n sopInstanceUid: instance.SOPInstanceUID,\r\n instanceNumber: toNumber(instance.InstanceNumber),\r\n lossyImageCompression: instance.LossyImageCompression,\r\n lossyImageCompressionRatio: instance.LossyImageCompressionRatio,\r\n lossyImageCompressionMethod: instance.LossyImageCompressionMethod,\r\n };\r\n\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.GENERAL_STUDY_MODULE:\r\n metadata = {\r\n studyDescription: instance.StudyDescription,\r\n studyDate: instance.StudyDate,\r\n studyTime: instance.StudyTime,\r\n accessionNumber: instance.AccessionNumber,\r\n };\r\n\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.CINE_MODULE:\r\n metadata = {\r\n frameTime: instance.FrameTime,\r\n };\r\n\r\n break;\r\n case WADO_IMAGE_LOADER_TAGS.PER_SERIES_MODULE:\r\n metadata = {\r\n correctedImage: instance.CorrectedImage,\r\n units: instance.Units,\r\n decayCorrection: instance.DecayCorrection,\r\n };\r\n break;\r\n\r\n default:\r\n return;\r\n }\r\n\r\n return metadata;\r\n }\r\n\r\n /**\r\n * Retrieves the frameNumber information, depending on the url style\r\n * wadors /frames/1\r\n * wadouri &frame=1\r\n * @param {*} imageId\r\n * @returns\r\n */\r\n getFrameInformationFromURL(imageId) {\r\n function getInformationFromURL(informationString, separator) {\r\n let result = '';\r\n const splittedStr = imageId.split(informationString)[1];\r\n if (splittedStr.includes(separator)) {\r\n result = splittedStr.split(separator)[0];\r\n } else {\r\n result = splittedStr;\r\n }\r\n return result;\r\n }\r\n\r\n if (imageId.includes('/frames')) {\r\n return getInformationFromURL('/frames', '/');\r\n }\r\n if (imageId.includes('&frame=')) {\r\n return getInformationFromURL('&frame=', '&');\r\n }\r\n return;\r\n }\r\n\r\n getUIDsFromImageID(imageId) {\r\n if (!imageId) throw new Error('MetadataProvider::Empty imageId');\r\n // TODO: adding csiv here is not really correct. Probably need to use\r\n // metadataProvider.addImageIdToUIDs(imageId, {\r\n // StudyInstanceUID,\r\n // SeriesInstanceUID,\r\n // SOPInstanceUID,\r\n // })\r\n // somewhere else\r\n if (imageId.startsWith('wadors:')) {\r\n const strippedImageId = imageId.split('/studies/')[1];\r\n const splitImageId = strippedImageId.split('/');\r\n\r\n return {\r\n StudyInstanceUID: splitImageId[0], // Note: splitImageId[1] === 'series'\r\n SeriesInstanceUID: splitImageId[2], // Note: splitImageId[3] === 'instances'\r\n SOPInstanceUID: splitImageId[4],\r\n frameNumber: splitImageId[6],\r\n };\r\n } else if (imageId.includes('?requestType=WADO')) {\r\n const qs = queryString.parse(imageId);\r\n\r\n return {\r\n StudyInstanceUID: qs.studyUID,\r\n SeriesInstanceUID: qs.seriesUID,\r\n SOPInstanceUID: qs.objectUID,\r\n frameNumber: qs.frameNumber,\r\n };\r\n }\r\n\r\n // Maybe its a non-standard imageId\r\n // check if the imageId starts with http:// or https:// using regex\r\n // Todo: handle non http imageIds\r\n let imageURI;\r\n const urlRegex = /^(http|https):\\/\\//;\r\n if (urlRegex.test(imageId)) {\r\n imageURI = imageId;\r\n } else {\r\n imageURI = imageIdToURI(imageId);\r\n }\r\n\r\n // remove &frame=number from imageId\r\n imageURI = imageURI.split('&frame=')[0];\r\n\r\n const uids = this.imageURIToUIDs.get(imageURI);\r\n const frameNumber = imageId.split(/\\/frames\\//)[1];\r\n\r\n if (uids && frameNumber !== undefined) {\r\n return { ...uids, frameNumber };\r\n }\r\n return uids;\r\n }\r\n}\r\n\r\nconst metadataProvider = new MetadataProvider();\r\n\r\nexport default metadataProvider;\r\n\r\nconst WADO_IMAGE_LOADER_TAGS = {\r\n // dicomImageLoader specific\r\n GENERAL_SERIES_MODULE: 'generalSeriesModule',\r\n PATIENT_STUDY_MODULE: 'patientStudyModule',\r\n IMAGE_PLANE_MODULE: 'imagePlaneModule',\r\n IMAGE_PIXEL_MODULE: 'imagePixelModule',\r\n VOI_LUT_MODULE: 'voiLutModule',\r\n MODALITY_LUT_MODULE: 'modalityLutModule',\r\n SOP_COMMON_MODULE: 'sopCommonModule',\r\n PET_ISOTOPE_MODULE: 'petIsotopeModule',\r\n PER_SERIES_MODULE: 'petSeriesModule',\r\n OVERLAY_PLANE_MODULE: 'overlayPlaneModule',\r\n PATIENT_DEMOGRAPHIC_MODULE: 'patientDemographicModule',\r\n\r\n // react-cornerstone-viewport specifc\r\n PATIENT_MODULE: 'patientModule',\r\n GENERAL_IMAGE_MODULE: 'generalImageModule',\r\n GENERAL_STUDY_MODULE: 'generalStudyModule',\r\n CINE_MODULE: 'cineModule',\r\n};\r\n\r\nconst INSTANCE = 'instance';\r\n","import log from '../../log';\r\n\r\nexport default function getPixelSpacingInformation(instance) {\r\n // See http://gdcm.sourceforge.net/wiki/index.php/Imager_Pixel_Spacing\r\n\r\n // TODO: Add Ultrasound region spacing\r\n // TODO: Add manual calibration\r\n\r\n // TODO: Use ENUMS from dcmjs\r\n\r\n const projectionRadiographSOPClassUIDs = [\r\n '1.2.840.10008.5.1.4.1.1.1', //\tCR Image Storage\r\n '1.2.840.10008.5.1.4.1.1.1.1', //\tDigital X-Ray Image Storage – for Presentation\r\n '1.2.840.10008.5.1.4.1.1.1.1.1', //\tDigital X-Ray Image Storage – for Processing\r\n '1.2.840.10008.5.1.4.1.1.1.2', //\tDigital Mammography X-Ray Image Storage – for Presentation\r\n '1.2.840.10008.5.1.4.1.1.1.2.1', //\tDigital Mammography X-Ray Image Storage – for Processing\r\n '1.2.840.10008.5.1.4.1.1.1.3', //\tDigital Intra – oral X-Ray Image Storage – for Presentation\r\n '1.2.840.10008.5.1.4.1.1.1.3.1', //\tDigital Intra – oral X-Ray Image Storage – for Processing\r\n '1.2.840.10008.5.1.4.1.1.12.1', //\tX-Ray Angiographic Image Storage\r\n '1.2.840.10008.5.1.4.1.1.12.1.1', //\tEnhanced XA Image Storage\r\n '1.2.840.10008.5.1.4.1.1.12.2', //\tX-Ray Radiofluoroscopic Image Storage\r\n '1.2.840.10008.5.1.4.1.1.12.2.1', //\tEnhanced XRF Image Storage\r\n '1.2.840.10008.5.1.4.1.1.12.3', // X-Ray Angiographic Bi-plane Image Storage\tRetired\r\n '1.2.840.10008.5.1.4.1.1.6.1', // Ultrasound Image IOD\r\n '1.2.840.10008.5.1.4.1.1.3.1', // Ultrasound Multiframe Image Storage\r\n ];\r\n\r\n const {\r\n PixelSpacing,\r\n ImagerPixelSpacing,\r\n SOPClassUID,\r\n PixelSpacingCalibrationType,\r\n PixelSpacingCalibrationDescription,\r\n EstimatedRadiographicMagnificationFactor,\r\n SequenceOfUltrasoundRegions,\r\n } = instance;\r\n const isProjection = projectionRadiographSOPClassUIDs.includes(SOPClassUID);\r\n\r\n const TYPES = {\r\n NOT_APPLICABLE: 'NOT_APPLICABLE',\r\n UNKNOWN: 'UNKNOWN',\r\n CALIBRATED: 'CALIBRATED',\r\n DETECTOR: 'DETECTOR',\r\n };\r\n\r\n if (isProjection && !ImagerPixelSpacing) {\r\n // If only Pixel Spacing is present, and this is a projection radiograph,\r\n // PixelSpacing should be used, but the user should be informed that\r\n // what it means is unknown\r\n return {\r\n PixelSpacing,\r\n type: TYPES.UNKNOWN,\r\n isProjection,\r\n };\r\n } else if (\r\n PixelSpacing &&\r\n ImagerPixelSpacing &&\r\n PixelSpacing === ImagerPixelSpacing\r\n ) {\r\n // If Imager Pixel Spacing and Pixel Spacing are present and they have the same values,\r\n // then the user should be informed that the measurements are at the detector plane\r\n return {\r\n PixelSpacing,\r\n type: TYPES.DETECTOR,\r\n isProjection,\r\n };\r\n } else if (\r\n PixelSpacing &&\r\n ImagerPixelSpacing &&\r\n PixelSpacing !== ImagerPixelSpacing\r\n ) {\r\n // If Imager Pixel Spacing and Pixel Spacing are present and they have different values,\r\n // then the user should be informed that these are \"calibrated\"\r\n // (in some unknown manner if Pixel Spacing Calibration Type and/or\r\n // Pixel Spacing Calibration Description are absent)\r\n return {\r\n PixelSpacing,\r\n type: TYPES.CALIBRATED,\r\n isProjection,\r\n PixelSpacingCalibrationType,\r\n PixelSpacingCalibrationDescription,\r\n };\r\n } else if (!PixelSpacing && ImagerPixelSpacing) {\r\n let CorrectedImagerPixelSpacing = ImagerPixelSpacing;\r\n if (EstimatedRadiographicMagnificationFactor) {\r\n // Note that in IHE Mammo profile compliant displays, the value of Imager Pixel Spacing is required to be corrected by\r\n // Estimated Radiographic Magnification Factor and the user informed of that.\r\n // TODO: should this correction be done before all of this logic?\r\n CorrectedImagerPixelSpacing = ImagerPixelSpacing.map(\r\n pixelSpacing => pixelSpacing / EstimatedRadiographicMagnificationFactor\r\n );\r\n } else {\r\n log.info(\r\n 'EstimatedRadiographicMagnificationFactor was not present. Unable to correct ImagerPixelSpacing.'\r\n );\r\n }\r\n\r\n return {\r\n PixelSpacing: CorrectedImagerPixelSpacing,\r\n isProjection,\r\n };\r\n } else if (\r\n SequenceOfUltrasoundRegions &&\r\n typeof SequenceOfUltrasoundRegions === 'object'\r\n ) {\r\n // const { PhysicalDeltaX, PhysicalDeltaY } = SequenceOfUltrasoundRegions;\r\n // const USPixelSpacing = [PhysicalDeltaX * 10, PhysicalDeltaY * 10];\r\n log.warn(\r\n 'Sequence of Ultrasound Regions. This is not yet implemented, all measurements will be shown in pixels.'\r\n );\r\n return {\r\n PixelSpacing,//: USPixelSpacing,\r\n };\r\n } else if (\r\n SequenceOfUltrasoundRegions &&\r\n Array.isArray(SequenceOfUltrasoundRegions) &&\r\n SequenceOfUltrasoundRegions.length > 1\r\n ) {\r\n log.warn(\r\n 'Sequence of Ultrasound Regions > one entry. This is not yet implemented, all measurements will be shown in pixels.'\r\n );\r\n } else if (isProjection === false && !ImagerPixelSpacing) {\r\n // If only Pixel Spacing is present, and this is not a projection radiograph,\r\n // we can stop here\r\n return {\r\n PixelSpacing,\r\n type: TYPES.NOT_APPLICABLE,\r\n isProjection,\r\n };\r\n }\r\n\r\n log.info(\r\n 'Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing.'\r\n );\r\n}\r\n","import CommandsManager from './CommandsManager';\r\nimport HotkeysManager from './HotkeysManager';\r\nimport ImageSet from './ImageSet';\r\nimport MetadataProvider from './MetadataProvider';\r\n\r\nexport { MetadataProvider, CommandsManager, HotkeysManager, ImageSet };\r\n\r\nconst classes = {\r\n MetadataProvider,\r\n CommandsManager,\r\n HotkeysManager,\r\n ImageSet,\r\n};\r\n\r\nexport default classes;\r\n","export default {\r\n 0: { description: 'Report Dose', window: '600', level: '-600' },\r\n 1: { description: 'Soft tissue', window: '400', level: '40' },\r\n 2: { description: 'Lung', window: '1500', level: '-600' },\r\n 3: { description: 'Liver', window: '150', level: '90' },\r\n 4: { description: 'Bone', window: '2500', level: '480' },\r\n 5: { description: 'Brain', window: '80', level: '40' },\r\n 6: { description: 'Trest', window: '1', level: '1' },\r\n 7: { description: 'Empty1', window: 'Empty1', level: 'Empty1' },\r\n 8: { description: 'Empty2', window: 'Empty2', level: 'Empty2' },\r\n 9: { description: 'Empty3', window: 'Empty3', level: 'Empty3' },\r\n 10: { description: 'Empty4', window: 'Empty4', level: 'Empty4' },\r\n};\r\n","import hotkeyBindings from './hotkeyBindings';\r\nimport windowLevelPresets from './windowLevelPresets';\r\nexport { hotkeyBindings, windowLevelPresets };\r\nexport default { hotkeyBindings, windowLevelPresets };\r\n","import windowLevelPresets from './windowLevelPresets';\r\n\r\n/*\r\n * Supported Keys: https://craig.is/killing/mice\r\n */\r\nconst bindings = [\r\n {\r\n commandName: 'setToolActive',\r\n commandOptions: { toolName: 'Zoom' },\r\n label: 'Zoom',\r\n keys: ['z'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'scaleUpViewport',\r\n label: 'Zoom In',\r\n keys: ['+'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'scaleDownViewport',\r\n label: 'Zoom Out',\r\n keys: ['-'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'fitViewportToWindow',\r\n label: 'Zoom to Fit',\r\n keys: ['='],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'rotateViewportCW',\r\n label: 'Rotate Right',\r\n keys: ['r'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'rotateViewportCCW',\r\n label: 'Rotate Left',\r\n keys: ['l'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'flipViewportHorizontal',\r\n label: 'Flip Horizontally',\r\n keys: ['h'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'flipViewportVertical',\r\n label: 'Flip Vertically',\r\n keys: ['v'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'toggleCine',\r\n label: 'Cine',\r\n keys: ['c'],\r\n },\r\n {\r\n commandName: 'invertViewport',\r\n label: 'Invert',\r\n keys: ['i'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'incrementActiveViewport',\r\n label: 'Next Image Viewport',\r\n keys: ['right'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'decrementActiveViewport',\r\n label: 'Previous Image Viewport',\r\n keys: ['left'],\r\n isEditable: true,\r\n },\r\n // {\r\n // commandName: 'nextViewportDisplaySet',\r\n // label: 'Next Series',\r\n // keys: ['pageup'],\r\n // isEditable: true,\r\n // },\r\n // {\r\n // commandName: 'previousViewportDisplaySet',\r\n // label: 'Previous Series',\r\n // keys: ['pagedown'],\r\n // isEditable: true,\r\n // },\r\n {\r\n commandName: 'nextStage',\r\n context: 'DEFAULT',\r\n label: 'Next Stage',\r\n keys: ['.'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'previousStage',\r\n context: 'DEFAULT',\r\n label: 'Previous Stage',\r\n keys: [','],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'nextImage',\r\n label: 'Next Image',\r\n keys: ['down'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'previousImage',\r\n label: 'Previous Image',\r\n keys: ['up'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'firstImage',\r\n label: 'First Image',\r\n keys: ['home'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'lastImage',\r\n label: 'Last Image',\r\n keys: ['end'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'resetViewport',\r\n label: 'Reset',\r\n keys: ['space'],\r\n isEditable: true,\r\n },\r\n {\r\n commandName: 'cancelMeasurement',\r\n label: 'Cancel Cornerstone Measurement',\r\n keys: ['esc'],\r\n },\r\n {\r\n commandName: 'setWindowLevel',\r\n commandOptions: windowLevelPresets[0],\r\n label: 'W/L Preset 0',\r\n keys: ['0'],\r\n },\r\n {\r\n commandName: 'setWindowLevel',\r\n commandOptions: windowLevelPresets[1],\r\n label: 'W/L Preset 1',\r\n keys: ['1'],\r\n },\r\n {\r\n commandName: 'setWindowLevel',\r\n commandOptions: windowLevelPresets[2],\r\n label: 'W/L Preset 2',\r\n keys: ['2'],\r\n },\r\n {\r\n commandName: 'setWindowLevel',\r\n commandOptions: windowLevelPresets[3],\r\n label: 'W/L Preset 3',\r\n keys: ['3'],\r\n },\r\n {\r\n commandName: 'setWindowLevel',\r\n commandOptions: windowLevelPresets[4],\r\n label: 'W/L Preset 4',\r\n keys: ['4'],\r\n },\r\n {\r\n commandName: 'setWindowLevel',\r\n commandOptions: windowLevelPresets[5],\r\n label: 'W/L Preset 5',\r\n keys: ['5'],\r\n },\r\n {\r\n commandName: 'setWindowLevel',\r\n commandOptions: windowLevelPresets[6],\r\n label: 'W/L Preset 6',\r\n keys: ['6'],\r\n },\r\n {\r\n commandName: 'setWindowLevel',\r\n commandOptions: windowLevelPresets[7],\r\n label: 'W/L Preset 7',\r\n keys: ['7'],\r\n },\r\n {\r\n commandName: 'setWindowLevel',\r\n commandOptions: windowLevelPresets[8],\r\n label: 'W/L Preset 8',\r\n keys: ['8'],\r\n },\r\n {\r\n commandName: 'setWindowLevel',\r\n commandOptions: windowLevelPresets[9],\r\n label: 'W/L Preset 9',\r\n keys: ['9'],\r\n },\r\n];\r\n\r\nexport default bindings;\r\n","// These should be overridden by the implementation\r\nconst errorHandler = {\r\n getHTTPErrorHandler: () => null,\r\n};\r\n\r\nexport default errorHandler;\r\n","import MODULE_TYPES from './MODULE_TYPES';\r\nimport log from '../log';\r\nimport { AppConfig } from '../types/AppConfig';\r\nimport { ServicesManager } from '../services';\r\nimport { HotkeysManager, CommandsManager } from '../classes';\r\n\r\n/**\r\n * This is the arguments given to create the extension.\r\n */\r\nexport interface ExtensionConstructor {\r\n servicesManager: ServicesManager;\r\n commandsManager: CommandsManager;\r\n hotkeysManager: HotkeysManager;\r\n appConfig: AppConfig;\r\n}\r\n\r\n/**\r\n * The configuration of an extension.\r\n * This uses type as the extension manager only knows that the configuration\r\n * is an object of some sort, and doesn't know anything else about it.\r\n */\r\nexport type ExtensionConfiguration = Record;\r\n\r\n/**\r\n * The parameters passed to the extension.\r\n */\r\nexport interface ExtensionParams extends ExtensionConstructor {\r\n extensionManager: ExtensionManager;\r\n configuration?: ExtensionConfiguration;\r\n}\r\n\r\n/**\r\n * The type of an actual extension instance.\r\n * This is an interface as it declares possible calls, but extensions can\r\n * have more values than this.\r\n */\r\nexport interface Extension {\r\n id: string;\r\n preRegistration?: (p: ExtensionParams) => Promise | void;\r\n onModeExit?: () => void;\r\n getHangingProtocolModule?: (p: ExtensionParams) => unknown;\r\n getCommandsModule?: (p: ExtensionParams) => CommandsModule;\r\n}\r\n\r\nexport type ExtensionRegister = {\r\n id: string;\r\n create: (p: ExtensionParams) => Extension;\r\n};\r\n\r\nexport type CommandsModule = {\r\n actions: Record;\r\n definitions: Record;\r\n defaultContext?: string;\r\n};\r\n\r\nexport default class ExtensionManager {\r\n private _commandsManager: CommandsManager;\r\n private _servicesManager: ServicesManager;\r\n private _hotkeysManager: HotkeysManager;\r\n\r\n constructor({\r\n commandsManager,\r\n servicesManager,\r\n hotkeysManager,\r\n appConfig = {},\r\n }: ExtensionConstructor) {\r\n this.modules = {};\r\n this.registeredExtensionIds = [];\r\n this.moduleTypeNames = Object.values(MODULE_TYPES);\r\n //\r\n this._commandsManager = commandsManager;\r\n this._servicesManager = servicesManager;\r\n this._hotkeysManager = hotkeysManager;\r\n this._appConfig = appConfig;\r\n\r\n this.modulesMap = {};\r\n this.moduleTypeNames.forEach(moduleType => {\r\n this.modules[moduleType] = [];\r\n });\r\n this._extensionLifeCycleHooks = { onModeEnter: {}, onModeExit: {} };\r\n this.dataSourceMap = {};\r\n this.dataSourceDefs = {};\r\n this.defaultDataSourceName = appConfig.defaultDataSourceName;\r\n this.activeDataSource = undefined;\r\n }\r\n\r\n public setActiveDataSource(dataSourceName: string): void {\r\n this.activeDataSource = dataSourceName;\r\n }\r\n\r\n /**\r\n * Calls all the services and extension on mode enters.\r\n * The service onModeEnter is called first\r\n * Then registered extensions onModeEnter is called\r\n * This is supposed to setup the extension for a standard entry.\r\n */\r\n public onModeEnter(): void {\r\n const {\r\n registeredExtensionIds,\r\n _servicesManager,\r\n _commandsManager,\r\n _hotkeysManager,\r\n _extensionLifeCycleHooks,\r\n } = this;\r\n\r\n // The onModeEnter of the service must occur BEFORE the extension\r\n // onModeEnter in order to reset the state to a standard state\r\n // before the extension restores and cached data.\r\n for (const service of Object.values(_servicesManager.services)) {\r\n service?.onModeEnter?.();\r\n }\r\n\r\n registeredExtensionIds.forEach(extensionId => {\r\n const onModeEnter = _extensionLifeCycleHooks.onModeEnter[extensionId];\r\n\r\n if (typeof onModeEnter === 'function') {\r\n onModeEnter({\r\n servicesManager: _servicesManager,\r\n commandsManager: _commandsManager,\r\n hotkeysManager: _hotkeysManager,\r\n });\r\n }\r\n });\r\n }\r\n\r\n public onModeExit(): void {\r\n const {\r\n registeredExtensionIds,\r\n _servicesManager,\r\n _commandsManager,\r\n _extensionLifeCycleHooks,\r\n } = this;\r\n\r\n registeredExtensionIds.forEach(extensionId => {\r\n const onModeExit = _extensionLifeCycleHooks.onModeExit[extensionId];\r\n\r\n if (typeof onModeExit === 'function') {\r\n onModeExit({\r\n servicesManager: _servicesManager,\r\n commandsManager: _commandsManager,\r\n });\r\n }\r\n });\r\n\r\n // The service onModeExit calls must occur after the extension ones\r\n // so that extension ones can store/restore data.\r\n for (const service of Object.values(_servicesManager.services)) {\r\n try {\r\n service?.onModeExit?.();\r\n } catch (e) {\r\n console.warn('onModeExit caught', e);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * An array of extensions, or an array of arrays that contains extension\r\n * configuration pairs.\r\n *\r\n * @param {Object[]} extensions - Array of extensions\r\n */\r\n public registerExtensions = async (\r\n extensions: (\r\n | ExtensionRegister\r\n | [ExtensionRegister, ExtensionConfiguration]\r\n )[],\r\n dataSources: unknown[] = []\r\n ): Promise => {\r\n // Todo: we ideally should be able to run registrations in parallel\r\n // but currently since some extensions need to be registered before\r\n // others, we need to run them sequentially. We need a postInit hook\r\n // to avoid this sequential async registration\r\n for (let i = 0; i < extensions.length; i++) {\r\n const extension = extensions[i];\r\n const hasConfiguration = Array.isArray(extension);\r\n try {\r\n if (hasConfiguration) {\r\n // Important: for some reason in the line below the type\r\n // of extension is not recognized as [ExtensionRegister,\r\n // ExtensionConfiguration] by babel DON\"T CHANGE IT\r\n // Same for the for loop above don't use\r\n // for (const extension of extensions)\r\n const ohifExtension = extension[0];\r\n const configuration = extension[1];\r\n await this.registerExtension(\r\n ohifExtension,\r\n configuration,\r\n dataSources\r\n );\r\n } else {\r\n await this.registerExtension(extension, {}, dataSources);\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n };\r\n\r\n /**\r\n *\r\n * TODO: Id Management: SopClassHandlers currently refer to viewport module by id; setting the extension id as viewport module id is a workaround for now\r\n * @param {Object} extension\r\n * @param {Object} configuration\r\n */\r\n public registerExtension = async (\r\n extension: ExtensionRegister,\r\n configuration = {},\r\n dataSources = []\r\n ): Promise => {\r\n if (!extension) {\r\n throw new Error('Attempting to register a null/undefined extension.');\r\n }\r\n\r\n const extensionId = extension.id;\r\n\r\n if (!extensionId) {\r\n // Note: Mode framework cannot function without IDs.\r\n log.warn(extension);\r\n throw new Error(`Extension ID not set`);\r\n }\r\n\r\n if (this.registeredExtensionIds.includes(extensionId)) {\r\n log.warn(\r\n `Extension ID ${extensionId} has already been registered. Exiting before duplicating modules.`\r\n );\r\n return;\r\n }\r\n\r\n // preRegistrationHook\r\n if (extension.preRegistration) {\r\n await extension.preRegistration({\r\n servicesManager: this._servicesManager,\r\n commandsManager: this._commandsManager,\r\n hotkeysManager: this._hotkeysManager,\r\n extensionManager: this,\r\n appConfig: this._appConfig,\r\n configuration,\r\n });\r\n }\r\n\r\n if (extension.onModeEnter) {\r\n this._extensionLifeCycleHooks.onModeEnter[extensionId] =\r\n extension.onModeEnter;\r\n }\r\n\r\n if (extension.onModeExit) {\r\n this._extensionLifeCycleHooks.onModeExit[extensionId] =\r\n extension.onModeExit;\r\n }\r\n\r\n // Register Modules\r\n this.moduleTypeNames.forEach(moduleType => {\r\n const extensionModule = this._getExtensionModule(\r\n moduleType,\r\n extension,\r\n extensionId,\r\n configuration\r\n );\r\n\r\n if (extensionModule) {\r\n switch (moduleType) {\r\n case MODULE_TYPES.COMMANDS:\r\n this._initCommandsModule(extensionModule);\r\n break;\r\n case MODULE_TYPES.DATA_SOURCE:\r\n this._initDataSourcesModule(\r\n extensionModule,\r\n extensionId,\r\n dataSources\r\n );\r\n break;\r\n case MODULE_TYPES.HANGING_PROTOCOL:\r\n this._initHangingProtocolsModule(extensionModule, extensionId);\r\n case MODULE_TYPES.TOOLBAR:\r\n case MODULE_TYPES.VIEWPORT:\r\n case MODULE_TYPES.PANEL:\r\n case MODULE_TYPES.SOP_CLASS_HANDLER:\r\n case MODULE_TYPES.CONTEXT:\r\n case MODULE_TYPES.LAYOUT_TEMPLATE:\r\n case MODULE_TYPES.CUSTOMIZATION:\r\n case MODULE_TYPES.STATE_SYNC:\r\n case MODULE_TYPES.UTILITY:\r\n // Default for most extension points,\r\n // Just adds each entry ready for consumption by mode.\r\n extensionModule.forEach(element => {\r\n const id = `${extensionId}.${moduleType}.${element.name}`;\r\n element.id = id;\r\n this.modulesMap[id] = element;\r\n });\r\n break;\r\n default:\r\n throw new Error(`Module type invalid: ${moduleType}`);\r\n }\r\n\r\n this.modules[moduleType].push({\r\n extensionId,\r\n module: extensionModule,\r\n });\r\n }\r\n });\r\n\r\n // Track extension registration\r\n this.registeredExtensionIds.push(extensionId);\r\n };\r\n\r\n getModuleEntry = stringEntry => {\r\n return this.modulesMap[stringEntry];\r\n };\r\n\r\n getDataSources = dataSourceName => {\r\n if (dataSourceName === undefined) {\r\n // Default to the activeDataSource\r\n dataSourceName = this.activeDataSource;\r\n }\r\n\r\n // Note: this currently uses the data source name, which feels weird...\r\n return this.dataSourceMap[dataSourceName];\r\n };\r\n\r\n getActiveDataSource = () => {\r\n return this.dataSourceMap[this.activeDataSource];\r\n };\r\n\r\n getDataSource = () => {\r\n return this.dataSourceMap[this.activeDataSource];\r\n };\r\n\r\n /**\r\n * @private\r\n * @param {string} moduleType\r\n * @param {Object} extension\r\n * @param {string} extensionId - Used for logging warnings\r\n */\r\n _getExtensionModule = (moduleType, extension, extensionId, configuration) => {\r\n const getModuleFnName = 'get' + _capitalizeFirstCharacter(moduleType);\r\n const getModuleFn = extension[getModuleFnName];\r\n\r\n if (!getModuleFn) {\r\n return;\r\n }\r\n\r\n try {\r\n const extensionModule = extension[getModuleFnName]({\r\n appConfig: this._appConfig,\r\n commandsManager: this._commandsManager,\r\n servicesManager: this._servicesManager,\r\n hotkeysManager: this._hotkeysManager,\r\n extensionManager: this,\r\n configuration,\r\n });\r\n\r\n if (!extensionModule) {\r\n log.warn(\r\n `Null or undefined returned when registering the ${getModuleFnName} module for the ${extensionId} extension`\r\n );\r\n }\r\n\r\n return extensionModule;\r\n } catch (ex) {\r\n console.log(ex);\r\n throw new Error(\r\n `Exception thrown while trying to call ${getModuleFnName} for the ${extensionId} extension`\r\n );\r\n }\r\n };\r\n\r\n _initHangingProtocolsModule = (extensionModule, extensionId) => {\r\n const { hangingProtocolService } = this._servicesManager.services;\r\n extensionModule.forEach(({ id, protocol }) => {\r\n if (protocol) {\r\n // Only auto-register if protocol specified, otherwise let mode register\r\n hangingProtocolService.addProtocol(id, protocol);\r\n }\r\n });\r\n };\r\n\r\n _initDataSourcesModule(extensionModule, extensionId, dataSources = []) {\r\n const { userAuthenticationService } = this._servicesManager.services;\r\n dataSources.forEach(dataSource => {\r\n this.dataSourceDefs[dataSource.sourceName] = dataSource;\r\n });\r\n\r\n extensionModule.forEach(element => {\r\n const namespace = `${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`;\r\n\r\n dataSources.forEach(dataSource => {\r\n if (dataSource.namespace === namespace) {\r\n const dataSourceInstance = element.createDataSource(\r\n dataSource.configuration,\r\n userAuthenticationService\r\n );\r\n\r\n if (this.dataSourceMap[dataSource.sourceName]) {\r\n this.dataSourceMap[dataSource.sourceName].push(dataSourceInstance);\r\n } else {\r\n this.dataSourceMap[dataSource.sourceName] = [dataSourceInstance];\r\n }\r\n }\r\n });\r\n });\r\n\r\n extensionModule.forEach(element => {\r\n this.modulesMap[\r\n `${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`\r\n ] = element;\r\n });\r\n }\r\n\r\n /**\r\n *\r\n * @private\r\n * @param {Object[]} commandDefinitions\r\n */\r\n _initCommandsModule = extensionModule => {\r\n let { definitions, defaultContext } = extensionModule;\r\n if (!definitions || Object.keys(definitions).length === 0) {\r\n log.warn('Commands Module contains no command definitions');\r\n return;\r\n }\r\n\r\n defaultContext = defaultContext || 'VIEWER';\r\n\r\n if (!this._commandsManager.getContext(defaultContext)) {\r\n this._commandsManager.createContext(defaultContext);\r\n }\r\n\r\n Object.keys(definitions).forEach(commandName => {\r\n const commandDefinition = definitions[commandName];\r\n const commandHasContextThatDoesNotExist =\r\n commandDefinition.context &&\r\n !this._commandsManager.getContext(commandDefinition.context);\r\n\r\n if (commandHasContextThatDoesNotExist) {\r\n this._commandsManager.createContext(commandDefinition.context);\r\n }\r\n\r\n this._commandsManager.registerCommand(\r\n commandDefinition.context || defaultContext,\r\n commandName,\r\n commandDefinition\r\n );\r\n });\r\n };\r\n}\r\n\r\n/**\r\n * @private\r\n * @param {string} lower\r\n */\r\nfunction _capitalizeFirstCharacter(lower) {\r\n return lower.charAt(0).toUpperCase() + lower.substring(1);\r\n}\r\n","export default {\r\n COMMANDS: 'commandsModule',\r\n CUSTOMIZATION: 'customizationModule',\r\n STATE_SYNC: 'stateSyncModule',\r\n DATA_SOURCE: 'dataSourcesModule',\r\n PANEL: 'panelModule',\r\n SOP_CLASS_HANDLER: 'sopClassHandlerModule',\r\n TOOLBAR: 'toolbarModule',\r\n VIEWPORT: 'viewportModule',\r\n CONTEXT: 'contextModule',\r\n LAYOUT_TEMPLATE: 'layoutTemplateModule',\r\n HANGING_PROTOCOL: 'hangingProtocolModule',\r\n UTILITY: 'utilityModule',\r\n};\r\n","// Transforms a shallow object with keys separated by \".\" into a nested object\r\nfunction getNestedObject(shallowObject) {\r\n const nestedObject = {};\r\n for (let key in shallowObject) {\r\n if (!shallowObject.hasOwnProperty(key)) continue;\r\n const value = shallowObject[key];\r\n const propertyArray = key.split('.');\r\n let currentObject = nestedObject;\r\n while (propertyArray.length) {\r\n const currentProperty = propertyArray.shift();\r\n if (!propertyArray.length) {\r\n currentObject[currentProperty] = value;\r\n } else {\r\n if (!currentObject[currentProperty]) {\r\n currentObject[currentProperty] = {};\r\n }\r\n\r\n currentObject = currentObject[currentProperty];\r\n }\r\n }\r\n }\r\n\r\n return nestedObject;\r\n}\r\n\r\n// Transforms a nested object into a shallowObject merging its keys with \".\" character\r\nfunction getShallowObject(nestedObject) {\r\n const shallowObject = {};\r\n const putValues = (baseKey, nestedObject, resultObject) => {\r\n for (let key in nestedObject) {\r\n if (!nestedObject.hasOwnProperty(key)) continue;\r\n let currentKey = baseKey ? `${baseKey}.${key}` : key;\r\n const currentValue = nestedObject[key];\r\n if (typeof currentValue === 'object') {\r\n if (currentValue instanceof Array) {\r\n currentKey += '[]';\r\n }\r\n\r\n putValues(currentKey, currentValue, resultObject);\r\n } else {\r\n resultObject[currentKey] = currentValue;\r\n }\r\n }\r\n };\r\n\r\n putValues('', nestedObject, shallowObject);\r\n return shallowObject;\r\n}\r\n\r\nconst object = {\r\n getNestedObject,\r\n getShallowObject,\r\n};\r\n\r\nexport default object;\r\n","function isObject(subject) {\r\n return (\r\n subject instanceof Object ||\r\n (typeof subject === 'object' && subject !== null)\r\n );\r\n}\r\n\r\nfunction isString(subject) {\r\n return typeof subject === 'string';\r\n}\r\n\r\n// Search for some string inside any object or array\r\nfunction search(object, query, property = null, result = []) {\r\n // Create the search pattern\r\n const pattern = new RegExp(query.trim(), 'i');\r\n\r\n Object.keys(object).forEach(key => {\r\n const item = object[key];\r\n\r\n // Stop here if item is empty\r\n if (!item) {\r\n return;\r\n }\r\n\r\n // Get the value to be compared\r\n const value = isString(property) ? item[property] : item;\r\n\r\n // Check if the value match the pattern\r\n if (isString(value) && pattern.test(value)) {\r\n // Add the current item to the result\r\n result.push(item);\r\n }\r\n\r\n if (isObject(item)) {\r\n // Search recursively the item if the current item is an object\r\n search(item, query, property, result);\r\n }\r\n });\r\n\r\n // Return the found items\r\n return result;\r\n}\r\n\r\n// Encode any string into a safe format for HTML id attribute\r\nfunction encodeId(input) {\r\n const string = input && input.toString ? input.toString() : input;\r\n\r\n // Return an underscore if the given string is empty or if it's not a string\r\n if (string === '' || typeof string !== 'string') {\r\n return '_';\r\n }\r\n\r\n // Create a converter to replace non accepted chars\r\n const converter = match => '_' + match[0].charCodeAt(0).toString(16) + '_';\r\n\r\n // Encode the given string and return it\r\n return string.replace(/[^a-zA-Z0-9-]/g, converter);\r\n}\r\n\r\nconst string = {\r\n search,\r\n encodeId,\r\n};\r\n\r\nexport default string;\r\n","// These should be overridden by the implementation\r\nlet user = {\r\n userLoggedIn: () => false,\r\n getUserId: () => null,\r\n getName: () => null,\r\n getAccessToken: () => null,\r\n login: () => new Promise((resolve, reject) => reject()),\r\n logout: () => new Promise((resolve, reject) => reject()),\r\n getData: key => null,\r\n setData: (key, value) => null,\r\n};\r\n\r\nexport default user;\r\n","import { ExtensionManager, MODULE_TYPES } from './extensions';\r\nimport { ServicesManager } from './services';\r\nimport classes, { CommandsManager, HotkeysManager } from './classes';\r\n\r\nimport DICOMWeb from './DICOMWeb';\r\nimport errorHandler from './errorHandler.js';\r\nimport log from './log.js';\r\nimport object from './object.js';\r\nimport string from './string.js';\r\nimport user from './user.js';\r\nimport utils from './utils';\r\nimport defaults from './defaults';\r\nimport * as Types from './types';\r\n\r\nimport {\r\n CineService,\r\n UIDialogService,\r\n UIModalService,\r\n UINotificationService,\r\n UIViewportDialogService,\r\n //\r\n DicomMetadataStore,\r\n DisplaySetService,\r\n ToolbarService,\r\n MeasurementService,\r\n ViewportGridService,\r\n HangingProtocolService,\r\n pubSubServiceInterface,\r\n PubSubService,\r\n UserAuthenticationService,\r\n CustomizationService,\r\n StateSyncService,\r\n PanelService,\r\n} from './services';\r\n\r\nimport IWebApiDataSource from './DataSources/IWebApiDataSource';\r\n\r\nconst hotkeys = {\r\n ...utils.hotkeys,\r\n defaults: { hotkeyBindings: defaults.hotkeyBindings },\r\n};\r\n\r\nconst OHIF = {\r\n MODULE_TYPES,\r\n //\r\n CommandsManager,\r\n ExtensionManager,\r\n HotkeysManager,\r\n ServicesManager,\r\n //\r\n defaults,\r\n utils,\r\n hotkeys,\r\n classes,\r\n string,\r\n user,\r\n errorHandler,\r\n object,\r\n log,\r\n DICOMWeb,\r\n viewer: {},\r\n //\r\n CineService,\r\n CustomizationService,\r\n StateSyncService,\r\n UIDialogService,\r\n UIModalService,\r\n UINotificationService,\r\n UIViewportDialogService,\r\n DisplaySetService,\r\n MeasurementService,\r\n ToolbarService,\r\n ViewportGridService,\r\n HangingProtocolService,\r\n UserAuthenticationService,\r\n IWebApiDataSource,\r\n DicomMetadataStore,\r\n pubSubServiceInterface,\r\n PubSubService,\r\n PanelService,\r\n};\r\n\r\nexport {\r\n MODULE_TYPES,\r\n //\r\n CommandsManager,\r\n ExtensionManager,\r\n HotkeysManager,\r\n ServicesManager,\r\n //\r\n defaults,\r\n utils,\r\n hotkeys,\r\n classes,\r\n string,\r\n user,\r\n errorHandler,\r\n object,\r\n log,\r\n DICOMWeb,\r\n //\r\n CineService,\r\n CustomizationService,\r\n StateSyncService,\r\n UIDialogService,\r\n UIModalService,\r\n UINotificationService,\r\n UIViewportDialogService,\r\n DisplaySetService,\r\n MeasurementService,\r\n ToolbarService,\r\n ViewportGridService,\r\n HangingProtocolService,\r\n UserAuthenticationService,\r\n IWebApiDataSource,\r\n DicomMetadataStore,\r\n pubSubServiceInterface,\r\n PubSubService,\r\n Types,\r\n PanelService,\r\n};\r\n\r\nexport { OHIF };\r\n\r\nexport default OHIF;\r\n","const log = {\r\n error: console.error,\r\n warn: console.warn,\r\n info: console.log,\r\n trace: console.trace,\r\n debug: console.debug,\r\n time: console.time,\r\n timeEnd: console.timeEnd,\r\n};\r\n\r\nexport default log;\r\n","const name = 'CineService';\r\n\r\nconst publicAPI = {\r\n name,\r\n getState: _getState,\r\n setCine: _setCine,\r\n setIsCineEnabled: _setIsCineEnabled,\r\n playClip: _playClip,\r\n stopClip: _stopClip,\r\n setServiceImplementation,\r\n};\r\n\r\nconst serviceImplementation = {\r\n _getState: () => console.warn('getState() NOT IMPLEMENTED'),\r\n _setCine: () => console.warn('setCine() NOT IMPLEMENTED'),\r\n _playClip: () => console.warn('playClip() NOT IMPLEMENTED'),\r\n _stopClip: () => console.warn('stopClip() NOT IMPLEMENTED'),\r\n _setIsCineEnabled: () => console.warn('setIsCineEnabled() NOT IMPLEMENTED'),\r\n};\r\n\r\nfunction _getState() {\r\n return serviceImplementation._getState();\r\n}\r\n\r\nfunction _setCine({ id, frameRate, isPlaying }) {\r\n return serviceImplementation._setCine({ id, frameRate, isPlaying });\r\n}\r\n\r\nfunction _setIsCineEnabled(isCineEnabled) {\r\n return serviceImplementation._setIsCineEnabled(isCineEnabled);\r\n}\r\n\r\nfunction _playClip(element, playClipOptions) {\r\n return serviceImplementation._playClip(element, playClipOptions);\r\n}\r\n\r\nfunction _stopClip(element) {\r\n return serviceImplementation._stopClip(element);\r\n}\r\n\r\nfunction setServiceImplementation({\r\n getState: getStateImplementation,\r\n setCine: setCineImplementation,\r\n setIsCineEnabled: setIsCineEnabledImplementation,\r\n playClip: playClipImplementation,\r\n stopClip: stopClipImplementation,\r\n}) {\r\n if (getStateImplementation) {\r\n serviceImplementation._getState = getStateImplementation;\r\n }\r\n if (setCineImplementation) {\r\n serviceImplementation._setCine = setCineImplementation;\r\n }\r\n if (setIsCineEnabledImplementation) {\r\n serviceImplementation._setIsCineEnabled = setIsCineEnabledImplementation;\r\n }\r\n\r\n if (playClipImplementation) {\r\n serviceImplementation._playClip = playClipImplementation;\r\n }\r\n\r\n if (stopClipImplementation) {\r\n serviceImplementation._stopClip = stopClipImplementation;\r\n }\r\n}\r\n\r\nconst CineService = {\r\n REGISTRATION: {\r\n altName: name,\r\n name: 'cineService',\r\n create: ({ configuration = {} }) => {\r\n return publicAPI;\r\n },\r\n },\r\n};\r\n\r\nexport default CineService;\r\n","import CineService from './CineService';\r\nexport default CineService;\r\n","import merge from 'lodash.merge';\r\nimport { PubSubService } from '../_shared/pubSubServiceInterface';\r\nimport { Customization, NestedStrings, Obj } from './types';\r\nimport { CommandsManager } from '../../classes';\r\n\r\nconst EVENTS = {\r\n MODE_CUSTOMIZATION_MODIFIED: 'event::CustomizationService:modeModified',\r\n GLOBAL_CUSTOMIZATION_MODIFIED: 'event::CustomizationService:globalModified',\r\n};\r\n\r\nconst flattenNestedStrings = (\r\n strs: NestedStrings | string,\r\n ret?: Record\r\n): Record => {\r\n if (!ret) ret = {};\r\n if (!strs) return ret;\r\n if (Array.isArray(strs)) {\r\n for (const val of strs) {\r\n flattenNestedStrings(val, ret);\r\n }\r\n } else {\r\n ret[strs] = strs;\r\n }\r\n return ret;\r\n};\r\n\r\n/**\r\n * The CustomizationService allows for retrieving of custom components\r\n * and configuration for mode and global values.\r\n * The intent of the items is to provide a react component. This can be\r\n * done by straight out providing an entire react component or else can be\r\n * done by configuring a react component, or configuring a part of a react\r\n * component. These are intended to be fairly indistinguishable in use of\r\n * it, although the internals of how that is implemented may need to know\r\n * about the customization service.\r\n *\r\n * A customization value can be:\r\n * 1. React function, taking (React, props) and returning a rendered component\r\n * For example, createLogoComponentFn renders a component logo for display\r\n * 2. Custom UI component configuration, as defined by the component which uses it.\r\n * For example, context menus define a complex structure allowing site-determined\r\n * context menus to be set.\r\n * 3. A string name, being the extension id for retrieving one of the above.\r\n *\r\n * The default values for the extension come from the app_config value 'whiteLabeling',\r\n * The whiteLabelling can have lists of extensions to load for the default global and\r\n * mode extensions. These are:\r\n * 'globalExtensions' which is a list of extension id's to load for global values\r\n * 'modeExtensions' which is a list of extension id's to load for mode values\r\n * They default to the list ['*'] if not otherwise provided, which means to check\r\n * every module for the given id and to load it/add it to the extensions.\r\n */\r\nexport default class CustomizationService extends PubSubService {\r\n public static REGISTRATION = {\r\n name: 'customizationService',\r\n create: ({ configuration = {}, commandsManager }) => {\r\n return new CustomizationService({ configuration, commandsManager });\r\n },\r\n };\r\n\r\n commandsManager: CommandsManager;\r\n extensionManager: Record;\r\n\r\n modeCustomizations: Record = {};\r\n globalCustomizations: Record = {};\r\n configuration: CustomizationConfiguration;\r\n\r\n constructor({ configuration, commandsManager }) {\r\n super(EVENTS);\r\n this.commandsManager = commandsManager;\r\n this.configuration = configuration || {};\r\n }\r\n\r\n public init(extensionManager: ExtensionManager): void {\r\n this.extensionManager = extensionManager;\r\n this.initDefaults();\r\n this.addReferences(this.configuration);\r\n }\r\n\r\n initDefaults(): void {\r\n this.extensionManager.registeredExtensionIds.forEach(extensionId => {\r\n const key = `${extensionId}.customizationModule.default`;\r\n const defaultCustomizations = this.findExtensionValue(key);\r\n if (!defaultCustomizations) return;\r\n const { value } = defaultCustomizations;\r\n this.addReference(value, true);\r\n });\r\n }\r\n\r\n findExtensionValue(value: string): Obj | void {\r\n const entry = this.extensionManager.getModuleEntry(value);\r\n return entry;\r\n }\r\n\r\n public onModeEnter(): void {\r\n super.reset();\r\n this.modeCustomizations = {};\r\n }\r\n\r\n public getModeCustomizations(): Record {\r\n return this.modeCustomizations;\r\n }\r\n\r\n public setModeCustomization(\r\n customizationId: string,\r\n customization: Customization\r\n ): void {\r\n this.modeCustomizations[customizationId] = merge(\r\n this.modeCustomizations[customizationId] || {},\r\n customization\r\n );\r\n this._broadcastEvent(this.EVENTS.CUSTOMIZATION_MODIFIED, {\r\n buttons: this.modeCustomizations,\r\n button: this.modeCustomizations[customizationId],\r\n });\r\n }\r\n\r\n /** This is the preferred getter for all customizations,\r\n * getting mode customizations first and otherwise global customizations.\r\n *\r\n * @param customizationId - the customization id to look for\r\n * @param defaultValue - is the default value to return. Note this value\r\n * may have been extended with any customizationType extensions provided,\r\n * so you cannot just use `|| defaultValue`\r\n * @return A customization to use if one is found, or the default customization,\r\n * both enhanced with any customizationType inheritance (see transform)\r\n */\r\n public getCustomization(\r\n customizationId: string,\r\n defaultValue?: Customization\r\n ): Customization | void {\r\n return this.getModeCustomization(customizationId, defaultValue);\r\n }\r\n\r\n /** Mode customizations are changes to the behaviour of the extensions\r\n * when running in a given mode. Reset clears mode customizations.\r\n * Note that global customizations over-ride mode customizations.\r\n * @param defautlValue to return if no customization specified.\r\n */\r\n public getModeCustomization(\r\n customizationId: string,\r\n defaultValue?: Customization\r\n ): Customization | void {\r\n const customization =\r\n this.globalCustomizations[customizationId] ??\r\n this.modeCustomizations[customizationId] ??\r\n defaultValue;\r\n return this.transform(customization);\r\n }\r\n\r\n public hasModeCustomization(customizationId: string) {\r\n return (\r\n this.globalCustomizations[customizationId] ||\r\n this.modeCustomizations[customizationId]\r\n );\r\n }\r\n /**\r\n * get is an alias for getModeCustomization, as it is the generic getter\r\n * which will return both mode and global customizations, and should be\r\n * used generally.\r\n * Note that the second parameter, defaultValue, will be expanded to include\r\n * any customizationType values defined in it, so it is not the same as doing:\r\n * `customizationService.get('key') || defaultValue`\r\n * unless the defaultValue does not contain any customizationType definitions.\r\n */\r\n public get = this.getModeCustomization;\r\n\r\n /**\r\n * Applies any inheritance due to UI Type customization.\r\n * This will look for customizationType in the customization object\r\n * and if that is found, will assign all iterable values from that\r\n * type into the new type, allowing default behaviour to be configured.\r\n */\r\n public transform(customization: Customization): Customization {\r\n if (!customization) return customization;\r\n const { customizationType } = customization;\r\n if (!customizationType) return customization;\r\n const parent = this.getCustomization(customizationType);\r\n const result = parent\r\n ? Object.assign(Object.create(parent), customization)\r\n : customization;\r\n // Execute an nested type information\r\n return result.transform?.(this) || result;\r\n }\r\n\r\n public addModeCustomizations(modeCustomizations): void {\r\n if (!modeCustomizations) {\r\n return;\r\n }\r\n this.addReferences(modeCustomizations, false);\r\n\r\n this._broadcastModeCustomizationModified();\r\n }\r\n\r\n _broadcastModeCustomizationModified(): void {\r\n this._broadcastEvent(EVENTS.MODE_CUSTOMIZATION_MODIFIED, {\r\n modeCustomizations: this.modeCustomizations,\r\n globalCustomizations: this.globalCustomizations,\r\n });\r\n }\r\n\r\n /** Global customizations are those that affect parts of the GUI other than\r\n * the modes. They include things like settings for the search screen.\r\n * Reset does NOT clear global customizations.\r\n */\r\n getGlobalCustomization(\r\n id: string,\r\n defaultValue?: Customization\r\n ): Customization | void {\r\n return this.transform(this.globalCustomizations[id] ?? defaultValue);\r\n }\r\n\r\n setGlobalCustomization(id: string, value: Customization): void {\r\n this.globalCustomizations[id] = value;\r\n this._broadcastGlobalCustomizationModified();\r\n }\r\n\r\n protected setConfigGlobalCustomization(\r\n configuration: AppConfigCustomization\r\n ): void {\r\n this.globalCustomizations = {};\r\n const keys = flattenNestedStrings(configuration.globalCustomizations);\r\n this.readCustomizationTypes(\r\n v => keys[v.name] && v.customization,\r\n this.globalCustomizations\r\n );\r\n\r\n // TODO - iterate over customizations, loading them from the extension\r\n // manager.\r\n this._broadcastGlobalCustomizationModified();\r\n }\r\n\r\n _broadcastGlobalCustomizationModified(): void {\r\n this._broadcastEvent(EVENTS.GLOBAL_CUSTOMIZATION_MODIFIED, {\r\n modeCustomizations: this.modeCustomizations,\r\n globalCustomizations: this.globalCustomizations,\r\n });\r\n }\r\n\r\n /**\r\n * A single reference is either an string to be loaded from a module,\r\n * or a customization itself.\r\n */\r\n addReference(value?: Obj | string, isGlobal = true, id?: string): void {\r\n if (!value) return;\r\n if (typeof value === 'string') {\r\n const extensionValue = this.findExtensionValue(value);\r\n // The child of a reference is only a set of references when an array,\r\n // so call the addReference direct. It could be a secondary reference perhaps\r\n this.addReference(extensionValue.value, isGlobal, extensionValue.name);\r\n } else if (Array.isArray(value)) {\r\n this.addReferences(value, isGlobal);\r\n } else {\r\n const useId = value.id || id;\r\n this[isGlobal ? 'setGlobalCustomization' : 'setModeCustomization'](\r\n useId as string,\r\n value\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Customizations can be specified as an array of strings or customizations,\r\n * or as an object whose key is the reference id, and the value is the string\r\n * or customization.\r\n */\r\n addReferences(references?: Obj | Obj[], isGlobal = true): void {\r\n if (!references) return;\r\n if (Array.isArray(references)) {\r\n references.forEach(item => {\r\n this.addReference(item, isGlobal);\r\n });\r\n } else {\r\n for (const key of Object.keys(references)) {\r\n const value = references[key];\r\n this.addReference(value, isGlobal, key);\r\n }\r\n }\r\n }\r\n}\r\n","import CustomizationService from './CustomizationService';\r\n\r\nexport default CustomizationService;\r\n","function createSeriesMetadata(instances) {\r\n const { SeriesInstanceUID } = instances[0];\r\n\r\n return {\r\n SeriesInstanceUID,\r\n instances,\r\n };\r\n}\r\n\r\nexport default createSeriesMetadata;\r\n","import createSeriesMetadata from './createSeriesMetadata';\r\n\r\nfunction createStudyMetadata(StudyInstanceUID) {\r\n return {\r\n StudyInstanceUID,\r\n StudyDescription: '',\r\n ModalitiesInStudy: [],\r\n isLoaded: false,\r\n series: [],\r\n /**\r\n *\r\n * @param {object} instance\r\n * @returns {bool} true if series were added; false if series already exist\r\n */\r\n addInstanceToSeries: function (instance) {\r\n const { SeriesInstanceUID } = instance;\r\n if (!this.StudyDescription) {\r\n this.StudyDescription = instance.StudyDescription;\r\n }\r\n const existingSeries = this.series.find(\r\n s => s.SeriesInstanceUID === SeriesInstanceUID\r\n );\r\n\r\n if (existingSeries) {\r\n existingSeries.instances.push(instance);\r\n } else {\r\n const series = createSeriesMetadata([instance]);\r\n this.series.push(series);\r\n const { Modality } = series;\r\n if (this.ModalitiesInStudy.indexof(Modality) === -1) {\r\n this.ModalitiesInStudy.push(Modality);\r\n }\r\n }\r\n },\r\n /**\r\n *\r\n * @param {object[]} instances\r\n * @param {string} instances[].SeriesInstanceUID\r\n * @param {string} instances[].StudyDescription\r\n * @returns {bool} true if series were added; false if series already exist\r\n */\r\n addInstancesToSeries: function (instances) {\r\n const { SeriesInstanceUID } = instances[0];\r\n if (!this.StudyDescription) {\r\n this.StudyDescription = instances[0].StudyDescription;\r\n }\r\n const existingSeries = this.series.find(\r\n s => s.SeriesInstanceUID === SeriesInstanceUID\r\n );\r\n\r\n if (existingSeries) {\r\n // Only add instances not already present, so generate a map\r\n // of existing instances and filter the to add by things\r\n // already present.\r\n const sopMap = {};\r\n existingSeries.instances.forEach(\r\n it => (sopMap[it.SOPInstanceUID] = it)\r\n );\r\n const newInstances = instances.filter(it => !sopMap[it.SOPInstanceUID]);\r\n existingSeries.instances.push(...newInstances);\r\n } else {\r\n const series = createSeriesMetadata(instances);\r\n this.series.push(series);\r\n }\r\n },\r\n\r\n setSeriesMetadata: function (SeriesInstanceUID, seriesMetadata) {\r\n let existingSeries = this.series.find(\r\n s => s.SeriesInstanceUID === SeriesInstanceUID\r\n );\r\n\r\n if (existingSeries) {\r\n existingSeries = Object.assign(existingSeries, seriesMetadata);\r\n } else {\r\n this.series.push(Object.assign({ instances: [] }, seriesMetadata));\r\n }\r\n },\r\n };\r\n}\r\n\r\nexport default createStudyMetadata;\r\n","import dcmjs from 'dcmjs';\r\n\r\nimport pubSubServiceInterface from '../_shared/pubSubServiceInterface';\r\nimport createStudyMetadata from './createStudyMetadata';\r\n\r\nconst EVENTS = {\r\n STUDY_ADDED: 'event::dicomMetadataStore:studyAdded',\r\n INSTANCES_ADDED: 'event::dicomMetadataStore:instancesAdded',\r\n SERIES_ADDED: 'event::dicomMetadataStore:seriesAdded',\r\n SERIES_UPDATED: 'event::dicomMetadataStore:seriesUpdated',\r\n};\r\n\r\n/**\r\n * @example\r\n * studies: [\r\n * {\r\n * StudyInstanceUID: string,\r\n * isLoaded: boolean,\r\n * series: [\r\n * {\r\n * Modality: string,\r\n * SeriesInstanceUID: string,\r\n * SeriesNumber: number,\r\n * SeriesDescription: string,\r\n * instances: [\r\n * {\r\n * // naturalized instance metadata\r\n * SOPInstanceUID: string,\r\n * SOPClassUID: string,\r\n * Rows: number,\r\n * Columns: number,\r\n * PatientSex: string,\r\n * Modality: string,\r\n * InstanceNumber: string,\r\n * },\r\n * {\r\n * // instance 2\r\n * },\r\n * ],\r\n * },\r\n * {\r\n * // series 2\r\n * },\r\n * ],\r\n * },\r\n * ],\r\n */\r\nconst _model = {\r\n studies: [],\r\n};\r\n\r\nfunction _getStudyInstanceUIDs() {\r\n return _model.studies.map(aStudy => aStudy.StudyInstanceUID);\r\n}\r\n\r\nfunction _getStudy(StudyInstanceUID) {\r\n return _model.studies.find(\r\n aStudy => aStudy.StudyInstanceUID === StudyInstanceUID\r\n );\r\n}\r\n\r\nfunction _getSeries(StudyInstanceUID, SeriesInstanceUID) {\r\n const study = _getStudy(StudyInstanceUID);\r\n\r\n if (!study) {\r\n return;\r\n }\r\n\r\n return study.series.find(\r\n aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID\r\n );\r\n}\r\n\r\n\r\nfunction _getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) {\r\n const series = _getSeries(StudyInstanceUID, SeriesInstanceUID);\r\n\r\n if (!series) {\r\n return;\r\n }\r\n\r\n return series.instances.find(\r\n instance => instance.SOPInstanceUID === SOPInstanceUID\r\n );\r\n}\r\n\r\nfunction _getInstanceByImageId(imageId) {\r\n for (let study of _model.studies) {\r\n for (let series of study.series) {\r\n for (let instance of series.instances) {\r\n if (instance.imageId === imageId) {\r\n return instance;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Update the metadata of a specific series\r\n * @param {*} StudyInstanceUID\r\n * @param {*} SeriesInstanceUID\r\n * @param {*} metadata metadata inform of key value pairs\r\n * @returns\r\n */\r\nfunction _updateMetadataForSeries(\r\n StudyInstanceUID,\r\n SeriesInstanceUID,\r\n metadata\r\n) {\r\n const study = _getStudy(StudyInstanceUID);\r\n\r\n if (!study) {\r\n return;\r\n }\r\n\r\n const series = study.series.find(\r\n aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID\r\n );\r\n\r\n const { instances } = series;\r\n // update all instances metadata for this series with the new metadata\r\n instances.forEach(instance => {\r\n Object.keys(metadata).forEach(key => {\r\n // if metadata[key] is an object, we need to merge it with the existing\r\n // metadata of the instance\r\n if (typeof metadata[key] === 'object') {\r\n instance[key] = { ...instance[key], ...metadata[key] };\r\n }\r\n // otherwise, we just replace the existing metadata with the new one\r\n else {\r\n instance[key] = metadata[key];\r\n }\r\n });\r\n });\r\n\r\n // broadcast the series updated event\r\n this._broadcastEvent(EVENTS.SERIES_UPDATED, {\r\n SeriesInstanceUID,\r\n StudyInstanceUID,\r\n madeInClient: true,\r\n });\r\n}\r\n\r\nconst BaseImplementation = {\r\n EVENTS,\r\n listeners: {},\r\n addInstance(dicomJSONDatasetOrP10ArrayBuffer) {\r\n let dicomJSONDataset;\r\n\r\n // If Arraybuffer, parse to DICOMJSON before naturalizing.\r\n if (dicomJSONDatasetOrP10ArrayBuffer instanceof ArrayBuffer) {\r\n const dicomData = dcmjs.data.DicomMessage.readFile(\r\n dicomJSONDatasetOrP10ArrayBuffer\r\n );\r\n\r\n dicomJSONDataset = dicomData.dict;\r\n } else {\r\n dicomJSONDataset = dicomJSONDatasetOrP10ArrayBuffer;\r\n }\r\n\r\n let naturalizedDataset;\r\n\r\n if (dicomJSONDataset['SeriesInstanceUID'] === undefined) {\r\n naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(\r\n dicomJSONDataset\r\n );\r\n } else {\r\n naturalizedDataset = dicomJSONDataset;\r\n }\r\n\r\n const { StudyInstanceUID } = naturalizedDataset;\r\n\r\n let study = _model.studies.find(\r\n study => study.StudyInstanceUID === StudyInstanceUID\r\n );\r\n\r\n if (!study) {\r\n _model.studies.push(createStudyMetadata(StudyInstanceUID));\r\n study = _model.studies[_model.studies.length - 1];\r\n }\r\n\r\n study.addInstanceToSeries(naturalizedDataset);\r\n },\r\n addInstances(instances, madeInClient = false) {\r\n const { StudyInstanceUID, SeriesInstanceUID } = instances[0];\r\n\r\n let study = _model.studies.find(\r\n study => study.StudyInstanceUID === StudyInstanceUID\r\n );\r\n\r\n if (!study) {\r\n _model.studies.push(createStudyMetadata(StudyInstanceUID));\r\n\r\n study = _model.studies[_model.studies.length - 1];\r\n }\r\n\r\n study.addInstancesToSeries(instances);\r\n\r\n // Broadcast an event even if we used cached data.\r\n // This is because the mode needs to listen to instances that are added to build up its active displaySets.\r\n // It will see there are cached displaySets and end early if this Series has already been fired in this\r\n // Mode session for some reason.\r\n this._broadcastEvent(EVENTS.INSTANCES_ADDED, {\r\n StudyInstanceUID,\r\n SeriesInstanceUID,\r\n madeInClient,\r\n });\r\n },\r\n addSeriesMetadata(seriesSummaryMetadata, madeInClient = false) {\r\n const { StudyInstanceUID } = seriesSummaryMetadata[0];\r\n let study = _getStudy(StudyInstanceUID);\r\n if (!study) {\r\n study = createStudyMetadata(StudyInstanceUID);\r\n // Will typically be undefined with a compliant DICOMweb server, reset later\r\n study.StudyDescription = seriesSummaryMetadata[0].StudyDescription;\r\n seriesSummaryMetadata.forEach(item => {\r\n if (study.ModalitiesInStudy.indexOf(item.Modality) === -1) {\r\n study.ModalitiesInStudy.push(item.Modality);\r\n }\r\n });\r\n study.NumberOfStudyRelatedSeries = seriesSummaryMetadata.length;\r\n _model.studies.push(study);\r\n }\r\n\r\n seriesSummaryMetadata.forEach(series => {\r\n const { SeriesInstanceUID } = series;\r\n\r\n study.setSeriesMetadata(SeriesInstanceUID, series);\r\n });\r\n\r\n this._broadcastEvent(EVENTS.SERIES_ADDED, {\r\n StudyInstanceUID,\r\n madeInClient,\r\n });\r\n },\r\n addStudy(study) {\r\n const { StudyInstanceUID } = study;\r\n\r\n let existingStudy = _model.studies.find(\r\n study => study.StudyInstanceUID === StudyInstanceUID\r\n );\r\n\r\n if (!existingStudy) {\r\n const newStudy = createStudyMetadata(StudyInstanceUID);\r\n\r\n newStudy.PatientID = study.PatientID;\r\n newStudy.PatientName = study.PatientName;\r\n newStudy.StudyDate = study.StudyDate;\r\n newStudy.ModalitiesInStudy = study.ModalitiesInStudy;\r\n newStudy.StudyDescription = study.StudyDescription;\r\n newStudy.AccessionNumber = study.AccessionNumber;\r\n newStudy.NumInstances = study.NumInstances; // todo: Correct naming?\r\n\r\n _model.studies.push(newStudy);\r\n }\r\n },\r\n getStudyInstanceUIDs: _getStudyInstanceUIDs,\r\n getStudy: _getStudy,\r\n getSeries: _getSeries,\r\n getInstance: _getInstance,\r\n getInstanceByImageId: _getInstanceByImageId,\r\n updateMetadataForSeries: _updateMetadataForSeries,\r\n};\r\nconst DicomMetadataStore = Object.assign(\r\n // get study\r\n\r\n // iterate over all series\r\n\r\n {},\r\n BaseImplementation,\r\n pubSubServiceInterface\r\n);\r\n\r\nexport { DicomMetadataStore };\r\nexport default DicomMetadataStore;\r\n","import DicomMetadataStore from './DicomMetadataStore';\r\n\r\nexport { DicomMetadataStore };\r\nexport default DicomMetadataStore;\r\n","const EVENTS = {\r\n DISPLAY_SETS_ADDED: 'event::displaySetService:displaySetsAdded',\r\n DISPLAY_SETS_CHANGED: 'event::displaySetService:displaySetsChanged',\r\n DISPLAY_SETS_REMOVED: 'event::displaySetService:displaySetsRemoved',\r\n DISPLAY_SET_SERIES_METADATA_INVALIDATED:\r\n 'event::displaySetService:displaySetSeriesMetadataInvalidated',\r\n};\r\n\r\nexport default EVENTS;\r\n","import { PubSubService } from '../_shared/pubSubServiceInterface';\r\nimport EVENTS from './EVENTS';\r\n\r\nconst displaySetCache = [];\r\n\r\n/**\r\n * Find an instance in a list of instances, comparing by SOP instance UID\r\n */\r\nconst findInSet = (instance, list) => {\r\n if (!list) return false;\r\n for (const elem of list) {\r\n if (!elem) continue;\r\n if (elem === instance) return true;\r\n if (elem.SOPInstanceUID === instance.SOPInstanceUID) return true;\r\n }\r\n return false;\r\n};\r\n\r\n/**\r\n * Find an instance in a display set\r\n * @returns true if found\r\n */\r\nconst findInstance = (instance, displaySets) => {\r\n for (const displayset of displaySets) {\r\n if (findInSet(instance, displayset.images)) return true;\r\n if (findInSet(instance, displayset.others)) return true;\r\n }\r\n return false;\r\n};\r\n\r\nexport default class DisplaySetService extends PubSubService {\r\n public static REGISTRATION = {\r\n altName: 'DisplaySetService',\r\n name: 'displaySetService',\r\n create: ({ configuration = {} }) => {\r\n return new DisplaySetService();\r\n },\r\n };\r\n\r\n public activeDisplaySets = [];\r\n constructor() {\r\n super(EVENTS);\r\n }\r\n\r\n public init(extensionManager, SOPClassHandlerIds): void {\r\n this.extensionManager = extensionManager;\r\n this.SOPClassHandlerIds = SOPClassHandlerIds;\r\n this.activeDisplaySets = [];\r\n }\r\n\r\n _addDisplaySetsToCache(displaySets) {\r\n displaySets.forEach(displaySet => {\r\n displaySetCache.push(displaySet);\r\n });\r\n }\r\n\r\n _addActiveDisplaySets(displaySets) {\r\n const activeDisplaySets = this.activeDisplaySets;\r\n\r\n displaySets.forEach(displaySet => {\r\n // This test makes adding display sets an N^2 operation, so it might\r\n // become important to do this in an efficient manner for large\r\n // numbers of display sets.\r\n if (!activeDisplaySets.includes(displaySet)) {\r\n activeDisplaySets.push(displaySet);\r\n }\r\n });\r\n }\r\n\r\n getDisplaySetCache() {\r\n return displaySetCache;\r\n }\r\n\r\n getMostRecentDisplaySet() {\r\n return this.activeDisplaySets[this.activeDisplaySets.length - 1];\r\n }\r\n\r\n getActiveDisplaySets() {\r\n return this.activeDisplaySets;\r\n }\r\n\r\n getDisplaySetsForSeries = SeriesInstanceUID => {\r\n return displaySetCache.filter(\r\n displaySet => displaySet.SeriesInstanceUID === SeriesInstanceUID\r\n );\r\n };\r\n\r\n getDisplaySetForSOPInstanceUID(\r\n SOPInstanceUID,\r\n SeriesInstanceUID,\r\n frameNumber\r\n ) {\r\n const displaySets = SeriesInstanceUID\r\n ? this.getDisplaySetsForSeries(SeriesInstanceUID)\r\n : this.getDisplaySetCache();\r\n\r\n const displaySet = displaySets.find(ds => {\r\n return (\r\n ds.images && ds.images.some(i => i.SOPInstanceUID === SOPInstanceUID)\r\n );\r\n });\r\n\r\n return displaySet;\r\n }\r\n\r\n setDisplaySetMetadataInvalidated(displaySetInstanceUID) {\r\n const displaySet = this.getDisplaySetByUID(displaySetInstanceUID);\r\n\r\n if (!displaySet) {\r\n return;\r\n }\r\n\r\n // broadcast event to update listeners with the new displaySets\r\n this._broadcastEvent(\r\n EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED,\r\n displaySetInstanceUID\r\n );\r\n }\r\n\r\n deleteDisplaySet(displaySetInstanceUID) {\r\n const { activeDisplaySets } = this;\r\n\r\n const displaySetCacheIndex = displaySetCache.findIndex(\r\n ds => ds.displaySetInstanceUID === displaySetInstanceUID\r\n );\r\n\r\n const activeDisplaySetsIndex = activeDisplaySets.findIndex(\r\n ds => ds.displaySetInstanceUID === displaySetInstanceUID\r\n );\r\n\r\n displaySetCache.splice(displaySetCacheIndex, 1);\r\n activeDisplaySets.splice(activeDisplaySetsIndex, 1);\r\n\r\n this._broadcastEvent(EVENTS.DISPLAY_SETS_CHANGED, this.activeDisplaySets);\r\n this._broadcastEvent(EVENTS.DISPLAY_SETS_REMOVED, {\r\n displaySetInstanceUIDs: [displaySetInstanceUID],\r\n });\r\n }\r\n\r\n /**\r\n * @param {string} displaySetInstanceUID\r\n * @returns {object} displaySet\r\n */\r\n getDisplaySetByUID = displaySetInstanceUid =>\r\n displaySetCache.find(\r\n displaySet => displaySet.displaySetInstanceUID === displaySetInstanceUid\r\n );\r\n\r\n /**\r\n *\r\n * @param {*} input\r\n * @param {*} param1: settings: initialViewportSettings by HP or callbacks after rendering\r\n * @returns {string[]} - added displaySetInstanceUIDs\r\n */\r\n makeDisplaySets = (\r\n input,\r\n { batch = false, madeInClient = false, settings = {} } = {}\r\n ) => {\r\n if (!input || !input.length) {\r\n throw new Error('No instances were provided.');\r\n }\r\n\r\n if (batch && !input[0].length) {\r\n throw new Error(\r\n 'Batch displaySet creation does not contain array of array of instances.'\r\n );\r\n }\r\n\r\n // If array of instances => One instance.\r\n let displaySetsAdded = [];\r\n\r\n if (batch) {\r\n for (let i = 0; i < input.length; i++) {\r\n const instances = input[i];\r\n const displaySets = this.makeDisplaySetForInstances(\r\n instances,\r\n settings\r\n );\r\n\r\n displaySetsAdded = [...displaySetsAdded, displaySets];\r\n }\r\n } else {\r\n const displaySets = this.makeDisplaySetForInstances(input, settings);\r\n\r\n displaySetsAdded = displaySets;\r\n }\r\n\r\n const options = {};\r\n\r\n if (madeInClient) {\r\n options.madeInClient = true;\r\n }\r\n\r\n // TODO: This is tricky. How do we know we're not resetting to the same/existing DSs?\r\n // TODO: This is likely run anytime we touch DicomMetadataStore. How do we prevent unnecessary broadcasts?\r\n if (displaySetsAdded && displaySetsAdded.length) {\r\n this._broadcastEvent(EVENTS.DISPLAY_SETS_CHANGED, this.activeDisplaySets);\r\n this._broadcastEvent(EVENTS.DISPLAY_SETS_ADDED, {\r\n displaySetsAdded,\r\n options,\r\n });\r\n\r\n return displaySetsAdded;\r\n }\r\n };\r\n\r\n /**\r\n * The onModeExit returns the display set service to the initial state,\r\n * that is without any display sets. To avoid recreating display sets,\r\n * the mode specific onModeExit is called before this method and should\r\n * store the active display sets and the cached data.\r\n */\r\n onModeExit() {\r\n this.getDisplaySetCache().length = 0;\r\n this.activeDisplaySets.length = 0;\r\n }\r\n\r\n makeDisplaySetForInstances(instancesSrc, settings) {\r\n let instances = instancesSrc;\r\n const instance = instances[0];\r\n\r\n const existingDisplaySets =\r\n this.getDisplaySetsForSeries(instance.SeriesInstanceUID) || [];\r\n\r\n const SOPClassHandlerIds = this.SOPClassHandlerIds;\r\n let allDisplaySets;\r\n\r\n for (let i = 0; i < SOPClassHandlerIds.length; i++) {\r\n const SOPClassHandlerId = SOPClassHandlerIds[i];\r\n const handler = this.extensionManager.getModuleEntry(SOPClassHandlerId);\r\n\r\n if (handler.sopClassUids.includes(instance.SOPClassUID)) {\r\n // Check if displaySets are already created using this SeriesInstanceUID/SOPClassHandler pair.\r\n let displaySets = existingDisplaySets.filter(\r\n displaySet => displaySet.SOPClassHandlerId === SOPClassHandlerId\r\n );\r\n\r\n if (displaySets.length) {\r\n this._addActiveDisplaySets(displaySets);\r\n } else {\r\n displaySets = handler.getDisplaySetsFromSeries(instances);\r\n\r\n if (!displaySets || !displaySets.length) continue;\r\n\r\n // applying hp-defined viewport settings to the displaysets\r\n displaySets.forEach(ds => {\r\n Object.keys(settings).forEach(key => {\r\n ds[key] = settings[key];\r\n });\r\n });\r\n\r\n this._addDisplaySetsToCache(displaySets);\r\n this._addActiveDisplaySets(displaySets);\r\n\r\n instances = instances.filter(\r\n instance => !findInstance(instance, displaySets)\r\n );\r\n }\r\n\r\n allDisplaySets = allDisplaySets\r\n ? [...allDisplaySets, ...displaySets]\r\n : displaySets;\r\n\r\n if (!instances.length) return allDisplaySets;\r\n }\r\n }\r\n return allDisplaySets;\r\n }\r\n}\r\n","import DisplaySetService from './DisplaySetService';\r\n\r\nexport default DisplaySetService;\r\n","import validate from 'validate.js';\r\n\r\nvalidate.validators.equals = function(value, options, key, attributes) {\r\n const testValue = options?.value ?? options;\r\n if (value !== testValue) {\r\n return key + 'must equal ' + testValue;\r\n }\r\n};\r\n\r\nvalidate.validators.doesNotEqual = function(value, options, key) {\r\n const testValue = options?.value ?? options;\r\n if (value === testValue) {\r\n return key + 'cannot equal ' + testValue;\r\n }\r\n};\r\n\r\n// Ignore case contains.\r\n// options testValue MUST be in lower case already, otherwise it won't match\r\nvalidate.validators.containsI = function (value, options, key) {\r\n const testValue = options?.value ?? options;\r\n if (Array.isArray(value)) {\r\n if (\r\n value.some(\r\n item => !validate.validators.containsI(item.toLowerCase(), options, key)\r\n )\r\n ) {\r\n return undefined;\r\n }\r\n return `No item of ${value.join(',')} contains ${JSON.stringify(\r\n testValue\r\n )}`;\r\n }\r\n if (Array.isArray(testValue)) {\r\n if (\r\n testValue.some(\r\n subTest => !validate.validators.containsI(value, subTest, key)\r\n )\r\n ) {\r\n return;\r\n }\r\n return `${key} must contain at least one of ${testValue.join(',')}`;\r\n }\r\n if (\r\n testValue &&\r\n value.indexOf &&\r\n value.toLowerCase().indexOf(testValue) === -1\r\n ) {\r\n return key + 'must contain any case of' + testValue;\r\n }\r\n};\r\n\r\nvalidate.validators.contains = function(value, options, key) {\r\n const testValue = options?.value ?? options;\r\n if (Array.isArray(value)) {\r\n if (value.some(item => !validate.validators.contains(item, options, key))) {\r\n return undefined;\r\n }\r\n return `No item of ${value.join(',')} contains ${JSON.stringify(\r\n testValue\r\n )}`;\r\n }\r\n if (Array.isArray(testValue)) {\r\n if (\r\n testValue.some(\r\n subTest => !validate.validators.contains(value, subTest, key)\r\n )\r\n ) {\r\n return;\r\n }\r\n return `${key} must contain at least one of ${testValue.join(',')}`;\r\n }\r\n if (testValue && value.indexOf && value.indexOf(testValue) === -1) {\r\n return key + 'must contain ' + testValue;\r\n }\r\n};\r\n\r\nvalidate.validators.doesNotContain = function(value, options, key) {\r\n if (options && value.indexOf && value.indexOf(options.value) !== -1) {\r\n return key + 'cannot contain ' + options.value;\r\n }\r\n};\r\n\r\nvalidate.validators.startsWith = function(value, options, key) {\r\n if (options && value.startsWith && !value.startsWith(options.value)) {\r\n return key + 'must start with ' + options.value;\r\n }\r\n};\r\n\r\nvalidate.validators.endsWith = function(value, options, key) {\r\n if (options && value.endsWith && !value.endsWith(options.value)) {\r\n return key + 'must end with ' + options.value;\r\n }\r\n};\r\n\r\nconst getTestValue = options => options?.value ?? options;\r\n\r\nvalidate.validators.greaterThan = function(value, options, key) {\r\n const testValue = getTestValue(options);\r\n if (value === undefined || value === null || value <= testValue) {\r\n return key + 'with value ' + value + ' must be greater than ' + testValue;\r\n }\r\n};\r\n\r\nvalidate.validators.range = function(value, options, key) {\r\n const testValue = getTestValue(options);\r\n if (value === undefined || value < testValue[0] || value > testValue[1]) {\r\n return (\r\n key +\r\n 'with value ' +\r\n value +\r\n ' must be between ' +\r\n testValue[0] +\r\n ' and ' +\r\n testValue[1]\r\n );\r\n }\r\n};\r\n\r\nvalidate.validators.notNull = value =>\r\n value === null || value === undefined ? 'Value is null' : undefined;\r\n\r\nexport default validate;\r\n","import validate from './lib/validator';\r\n\r\n/**\r\n * Match a Metadata instance against rules using Validate.js for validation.\r\n * @param {InstanceMetadata} metadataInstance Metadata instance object\r\n * @param {Array} rules Array of MatchingRules instances (StudyMatchingRule|SeriesMatchingRule|ImageMatchingRule) for the match\r\n * @param {object} options is an object containing additional information\r\n * @param {object[]} options.studies is a list of all the studies\r\n * @param {object[]} options.displaySets is a list of the display sets\r\n * @return {Object} Matching Object with score and details (which rule passed or failed)\r\n */\r\nconst match = (\r\n metadataInstance,\r\n rules = [],\r\n customAttributeRetrievalCallbacks,\r\n options\r\n) => {\r\n const validateOptions = {\r\n format: 'grouped',\r\n };\r\n\r\n const details = {\r\n passed: [],\r\n failed: [],\r\n };\r\n\r\n const readValues = {};\r\n\r\n let requiredFailed = false;\r\n let score = 0;\r\n\r\n // Allow for matching against current or prior specifically\r\n const prior = options?.studies?.[1];\r\n const current = options?.studies?.[0];\r\n const instance = (metadataInstance.images || metadataInstance.others)?.[0];\r\n const fromSrc = {\r\n prior,\r\n current,\r\n instance,\r\n ...options,\r\n options,\r\n metadataInstance,\r\n };\r\n\r\n rules.forEach(rule => {\r\n const { attribute, from = 'metadataInstance' } = rule;\r\n // Do not use the custom attribute from the metadataInstance since it is subject to change\r\n if (customAttributeRetrievalCallbacks.hasOwnProperty(attribute)) {\r\n readValues[attribute] = customAttributeRetrievalCallbacks[\r\n attribute\r\n ].callback.call(rule, metadataInstance, options);\r\n } else {\r\n readValues[attribute] =\r\n fromSrc[from]?.[attribute] ?? instance?.[attribute];\r\n }\r\n\r\n // Format the constraint as required by Validate.js\r\n const testConstraint = {\r\n [attribute]: rule.constraint,\r\n };\r\n\r\n // Create a single attribute object to be validated, since metadataInstance is an\r\n // instance of Metadata (StudyMetadata, SeriesMetadata or InstanceMetadata)\r\n let attributeValue = readValues[attribute];\r\n const attributeMap = {\r\n [attribute]: attributeValue,\r\n };\r\n\r\n // Use Validate.js to evaluate the constraints on the specified metadataInstance\r\n let errorMessages;\r\n try {\r\n errorMessages = validate(attributeMap, testConstraint, [validateOptions]);\r\n } catch (e) {\r\n errorMessages = ['Something went wrong during validation.', e];\r\n }\r\n\r\n // console.log(\r\n // 'Test',\r\n // `${from}.${attribute}`,\r\n // readValues[attribute],\r\n // JSON.stringify(rule.constraint),\r\n // !errorMessages\r\n // );\r\n\r\n if (!errorMessages) {\r\n // If no errorMessages were returned, then validation passed.\r\n\r\n // Add the rule's weight to the total score\r\n score += parseInt(rule.weight || 1, 10);\r\n // Log that this rule passed in the matching details object\r\n details.passed.push({\r\n rule,\r\n });\r\n } else {\r\n // If errorMessages were present, then validation failed\r\n\r\n // If the rule that failed validation was Required, then\r\n // mark that a required Rule has failed\r\n if (rule.required) {\r\n requiredFailed = true;\r\n }\r\n\r\n // Log that this rule failed in the matching details object\r\n // and include any error messages\r\n details.failed.push({\r\n rule,\r\n errorMessages,\r\n });\r\n }\r\n });\r\n\r\n // If a required Rule has failed Validation, set the matching score to zero\r\n if (requiredFailed) {\r\n score = 0;\r\n }\r\n\r\n return {\r\n score,\r\n details,\r\n requiredFailed,\r\n };\r\n};\r\n\r\nconst HPMatcher = {\r\n match,\r\n};\r\n\r\nexport { HPMatcher };\r\n","import { HPMatcher } from './HPMatcher.js';\r\nimport { sortByScore } from './lib/sortByScore';\r\n\r\nexport default class ProtocolEngine {\r\n constructor(protocols, customAttributeRetrievalCallbacks) {\r\n this.protocols = protocols;\r\n this.customAttributeRetrievalCallbacks = customAttributeRetrievalCallbacks;\r\n this.matchedProtocols = new Map();\r\n this.matchedProtocolScores = {};\r\n this.study = undefined;\r\n }\r\n\r\n /** Evaluate the hanging protocol matches on the given:\r\n * @param props.studies is a list of studies to compare against (for priors evaluation)\r\n * @param props.activeStudy is the current metadata for the study to display.\r\n * @param props.displaySets are the list of display sets which can be modified.\r\n */\r\n run({ studies, displaySets, activeStudy }) {\r\n this.studies = studies;\r\n this.study = activeStudy || studies[0];\r\n this.displaySets = displaySets;\r\n return this.getBestProtocolMatch();\r\n }\r\n\r\n // /**\r\n // * Resets the ProtocolEngine to the best match\r\n // */\r\n // reset() {\r\n // const protocol = this.getBestProtocolMatch();\r\n\r\n // this.setHangingProtocol(protocol);\r\n // }\r\n\r\n /**\r\n * Return the best matched Protocol to the current study or set of studies\r\n * @returns {*}\r\n */\r\n getBestProtocolMatch() {\r\n // Executar the matching to populate matchedProtocols Set and Map\r\n this.updateProtocolMatches();\r\n\r\n // Retrieve the highest scoring Protocol\r\n const bestMatch = this._getHighestScoringProtocol();\r\n\r\n console.log('ProtocolEngine::getBestProtocolMatch bestMatch', bestMatch);\r\n\r\n return bestMatch;\r\n }\r\n\r\n /**\r\n * Populates the MatchedProtocols Collection by running the matching procedure\r\n */\r\n updateProtocolMatches() {\r\n console.log('ProtocolEngine::updateProtocolMatches');\r\n\r\n // Clear all data currently in matchedProtocols\r\n this._clearMatchedProtocols();\r\n\r\n // TODO: handle more than one study - this.studies has the list of studies\r\n const matched = this.findMatchByStudy(this.study, {\r\n studies: this.studies,\r\n displaySets: this.displaySets,\r\n });\r\n\r\n // For each matched protocol, check if it is already in MatchedProtocols\r\n matched.forEach(matchedDetail => {\r\n const protocol = matchedDetail.protocol;\r\n if (!protocol) {\r\n return;\r\n }\r\n\r\n // If it is not already in the MatchedProtocols Collection, insert it with its score\r\n if (!this.matchedProtocols.has(protocol.id)) {\r\n console.log(\r\n 'ProtocolEngine::updateProtocolMatches inserting protocol match',\r\n matchedDetail\r\n );\r\n this.matchedProtocols.set(protocol.id, protocol);\r\n this.matchedProtocolScores[protocol.id] = matchedDetail.score;\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * finds the match results against the given display set or\r\n * study instance by testing the given rules against this, and using\r\n * the provided options for testing.\r\n *\r\n * @param {*} metaData to match against as primary value\r\n * @param {*} rules to apply\r\n * @param {*} options are additional values that can be used for matching\r\n * @returns\r\n */\r\n findMatch(metaData, rules, options) {\r\n return HPMatcher.match(\r\n metaData,\r\n rules,\r\n this.customAttributeRetrievalCallbacks,\r\n options\r\n );\r\n }\r\n\r\n /**\r\n * Finds the best protocols from Protocol Store, matching each protocol matching rules\r\n * with the given study. The best protocol are ordered by score and returned in an array\r\n * @param {Object} study StudyMetadata instance object\r\n * @param {object} options containing additional matching data.\r\n * @return {Array} Array of match objects or an empty array if no match was found\r\n * Each match object has the score of the matching and the matched\r\n * protocol\r\n */\r\n findMatchByStudy(study, options) {\r\n const matched = [];\r\n\r\n this.protocols.forEach(protocol => {\r\n // Clone the protocol's protocolMatchingRules array\r\n // We clone it so that we don't accidentally add the\r\n // numberOfPriorsReferenced rule to the Protocol itself.\r\n let rules = protocol.protocolMatchingRules.slice();\r\n if (!rules || !rules.length) {\r\n console.warn(\r\n 'ProtocolEngine::findMatchByStudy no matching rules - specify protocolMatchingRules for',\r\n protocol.id\r\n );\r\n return;\r\n }\r\n\r\n // Executar the matcher and get matching details\r\n const matchedDetails = this.findMatch(study, rules, options);\r\n const score = matchedDetails.score;\r\n\r\n // The protocol matched some rule, add it to the matched list\r\n if (score > 0) {\r\n matched.push({\r\n score,\r\n protocol,\r\n });\r\n }\r\n });\r\n\r\n // If no matches were found, select the default protocol if provided\r\n // if not select the first protocol in the list\r\n if (!matched.length) {\r\n const protocol =\r\n this.protocols.find(protocol => protocol.id === 'default') ??\r\n this.protocols[0];\r\n console.log('No protocol matches, defaulting to', protocol);\r\n return [\r\n {\r\n score: 0,\r\n protocol,\r\n },\r\n ];\r\n }\r\n\r\n // Sort the matched list by score\r\n sortByScore(matched);\r\n\r\n console.log('ProtocolEngine::findMatchByStudy matched', matched);\r\n\r\n return matched;\r\n }\r\n\r\n _clearMatchedProtocols() {\r\n this.matchedProtocols.clear();\r\n this.matchedProtocolScores = {};\r\n }\r\n\r\n _largestKeyByValue(obj) {\r\n return Object.keys(obj).reduce((a, b) => (obj[a] > obj[b] ? a : b));\r\n }\r\n\r\n _getHighestScoringProtocol() {\r\n if (!Object.keys(this.matchedProtocolScores).length) {\r\n return;\r\n }\r\n const highestScoringProtocolId = this._largestKeyByValue(\r\n this.matchedProtocolScores\r\n );\r\n return this.matchedProtocols.get(highestScoringProtocolId);\r\n }\r\n}\r\n","// Sorts an array by score\r\nconst sortByScore = arr => {\r\n arr.sort((a, b) => {\r\n return b.score - a.score;\r\n });\r\n};\r\n\r\nexport { sortByScore };\r\n","import { PubSubService } from '../_shared/pubSubServiceInterface';\r\nimport sortBy from '../../utils/sortBy';\r\nimport ProtocolEngine from './ProtocolEngine';\r\nimport { StudyMetadata } from '../../types/StudyMetadata';\r\nimport IDisplaySet from '../DisplaySetService/IDisplaySet';\r\nimport { CommandsManager } from '../../classes';\r\nimport ServicesManager from '../ServicesManager';\r\nimport * as HangingProtocol from '../../types/HangingProtocol';\r\n\r\ntype Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator;\r\n\r\nconst DEFAULT_VIEWPORT_OPTIONS: HangingProtocol.ViewportOptions = {\r\n toolGroupId: 'default',\r\n viewportType: 'stack',\r\n};\r\n\r\nexport default class HangingProtocolService extends PubSubService {\r\n static EVENTS = {\r\n // The PROTOCOL_CHANGED event is fired when the protocol changes\r\n // and should be immediately applied\r\n PROTOCOL_CHANGED: 'event::hanging_protocol_changed',\r\n // The PROTOCOL_RESTORED event is fired instead of a changed event to indicate\r\n // that an earlier state has been restored as part of a state update, but\r\n // is not being directly re-applied, but just restored.\r\n PROTOCOL_RESTORED: 'event::hanging_protocol_restore',\r\n // The layout has been decided for the hanging protocol - deprecated\r\n NEW_LAYOUT: 'event::hanging_protocol_new_layout',\r\n // Fired when the stages within the current protocol are known to have\r\n // the status set - that is, they are activated (or deactivated).\r\n STAGE_ACTIVATION: 'event::hanging_protocol_stage_activation',\r\n CUSTOM_IMAGE_LOAD_PERFORMED:\r\n 'event::hanging_protocol_custom_image_load_performed',\r\n };\r\n\r\n public static REGISTRATION = {\r\n name: 'hangingProtocolService',\r\n altName: 'HangingProtocolService',\r\n create: ({ configuration = {}, commandsManager, servicesManager }) => {\r\n return new HangingProtocolService(commandsManager, servicesManager);\r\n },\r\n };\r\n\r\n studies: StudyMetadata[];\r\n // stores all the protocols (object or function that returns an object) in a map\r\n protocols: Map;\r\n // Contains the list of currently active keys\r\n activeProtocolIds: string[];\r\n // the current protocol that is being applied to the viewports in object format\r\n protocol: HangingProtocol.Protocol;\r\n stageIndex = 0;\r\n _commandsManager: CommandsManager;\r\n _servicesManager: ServicesManager;\r\n protocolEngine: ProtocolEngine;\r\n customViewportSettings = [];\r\n displaySets: IDisplaySet[] = [];\r\n activeStudy: StudyMetadata;\r\n debugLogging: false;\r\n\r\n customAttributeRetrievalCallbacks = {\r\n NumberOfStudyRelatedSeries: {\r\n name: 'The number of series in the study',\r\n callback: metadata =>\r\n metadata.NumberOfStudyRelatedSeries ?? metadata.series?.length,\r\n },\r\n NumberOfSeriesRelatedInstances: {\r\n name: 'The number of instances in the display set',\r\n callback: metadata => metadata.numImageFrames,\r\n },\r\n ModalitiesInStudy: {\r\n name: 'Gets the array of the modalities for the series',\r\n callback: metadata =>\r\n metadata.ModalitiesInStudy ??\r\n (metadata.series || []).reduce((prev, curr) => {\r\n const { Modality } = curr;\r\n if (Modality && prev.indexOf(Modality) == -1) prev.push(Modality);\r\n return prev;\r\n }, []),\r\n },\r\n isReconstructable: {\r\n name: 'Checks if the display set is reconstructable',\r\n // we can add more advanced checking here\r\n callback: displaySet => displaySet.isReconstructable ?? false,\r\n },\r\n };\r\n listeners = {};\r\n registeredImageLoadStrategies = {};\r\n activeImageLoadStrategyName = null;\r\n customImageLoadPerformed = false;\r\n\r\n /**\r\n * displaySetMatchDetails = \r\n * DisplaySetId is the id defined in the hangingProtocol object itself\r\n * and match is an object that contains information about\r\n */\r\n displaySetMatchDetails: Map<\r\n string, // protocol displaySetId in the displayset selector\r\n HangingProtocol.DisplaySetMatchDetails\r\n > = new Map();\r\n\r\n /**\r\n * An array that contains for each viewport (viewportIndex) specified in the\r\n * hanging protocol, an object of the form\r\n */\r\n viewportMatchDetails: Map<\r\n number, // viewportIndex\r\n HangingProtocol.ViewportMatchDetails\r\n > = new Map();\r\n\r\n constructor(commandsManager: CommandsManager, servicesManager) {\r\n super(HangingProtocolService.EVENTS);\r\n this._commandsManager = commandsManager;\r\n this._servicesManager = servicesManager;\r\n this.protocols = new Map();\r\n this.protocolEngine = undefined;\r\n this.protocol = undefined;\r\n this.stageIndex = undefined;\r\n\r\n this.studies = [];\r\n }\r\n\r\n public destroy(): void {\r\n this.reset();\r\n this.protocols = new Map();\r\n }\r\n\r\n public reset(): void {\r\n this.studies = [];\r\n this.viewportMatchDetails = new Map();\r\n this.displaySetMatchDetails = new Map();\r\n }\r\n\r\n /** Leave the hanging protocol in the initialized state */\r\n public onModeEnter(): void {\r\n this.reset();\r\n }\r\n\r\n /**\r\n * Gets the active protocol information directly, including the direct\r\n * protocol, stage and active study objects.\r\n * Should NOT be stored longer term as the protocol\r\n * object can change internally or be regenerated.\r\n * Can be used to store the state to recover from exceptions.\r\n *\r\n * @returns protocol, stage, activeStudy\r\n */\r\n public getActiveProtocol(): {\r\n protocol: HangingProtocol.Protocol;\r\n stage: HangingProtocol.ProtocolStage;\r\n stageIndex: number;\r\n activeStudy?: StudyMetadata;\r\n viewportMatchDetails: Map;\r\n displaySetMatchDetails: Map;\r\n activeImageLoadStrategyName: string;\r\n } {\r\n return {\r\n protocol: this.protocol,\r\n stage: this.protocol?.stages?.[this.stageIndex],\r\n stageIndex: this.stageIndex,\r\n activeStudy: this.activeStudy,\r\n viewportMatchDetails: this.viewportMatchDetails,\r\n displaySetMatchDetails: this.displaySetMatchDetails,\r\n activeImageLoadStrategyName: this.activeImageLoadStrategyName,\r\n };\r\n }\r\n\r\n /** Gets the hanging protocol state information, which is a storable\r\n * state information for the hanging protocol consisting of the:\r\n * protocolId, stageIndex, stageId and activeStudyUID\r\n */\r\n public getState(): HangingProtocol.HPInfo {\r\n if (!this.protocol) return;\r\n return {\r\n protocolId: this.protocol.id,\r\n stageIndex: this.stageIndex,\r\n stageId: this.protocol.stages[this.stageIndex].id,\r\n activeStudyUID: this.activeStudy?.StudyInstanceUID,\r\n };\r\n }\r\n\r\n /** Gets the protocol with id 'default' */\r\n public getDefaultProtocol(): HangingProtocol.Protocol {\r\n return this.getProtocolById('default');\r\n }\r\n\r\n /** Gets the viewport match details.\r\n * @deprecated because this method is expected to go away as the HP service\r\n * becomes more stateless.\r\n */\r\n public getMatchDetails(): HangingProtocol.HangingProtocolMatchDetails {\r\n return {\r\n viewportMatchDetails: this.viewportMatchDetails,\r\n displaySetMatchDetails: this.displaySetMatchDetails,\r\n };\r\n }\r\n\r\n /**\r\n * It loops over the protocols map object, and checks whether the protocol\r\n * is a function, if so, it executes it and returns the result as a protocol object\r\n * otherwise it returns the protocol object itself\r\n *\r\n * @returns all the hanging protocol registered in the HangingProtocolService\r\n */\r\n public getProtocols(): HangingProtocol.Protocol[] {\r\n // this.protocols is a map of protocols with the protocol id as the key\r\n // and the protocol or a function that returns a protocol as the value\r\n const protocols = [];\r\n const keys = this.activeProtocolIds || this.protocols.keys();\r\n // @ts-ignore\r\n for (const protocolId of keys) {\r\n const protocol = this.getProtocolById(protocolId);\r\n if (protocol) {\r\n protocols.push(protocol);\r\n }\r\n }\r\n\r\n return protocols;\r\n }\r\n\r\n /**\r\n * Returns the protocol with the given id, it will get the protocol from the\r\n * protocols map object and if it is a function, it will execute it and return\r\n * the result as a protocol object\r\n *\r\n * @param protocolId - the id of the protocol\r\n * @returns protocol - the protocol with the given id\r\n */\r\n public getProtocolById(protocolId: string): HangingProtocol.Protocol {\r\n if (!protocolId) return;\r\n if (protocolId === this.protocol?.id) return this.protocol;\r\n const protocol = this.protocols.get(protocolId);\r\n if (!protocol) {\r\n throw new Error(`No protocol ${protocolId} found`);\r\n }\r\n\r\n if (protocol instanceof Function) {\r\n try {\r\n const { protocol: generatedProtocol } = this._getProtocolFromGenerator(\r\n protocol\r\n );\r\n\r\n return generatedProtocol;\r\n } catch (error) {\r\n console.warn(\r\n `Error while executing protocol generator for protocol ${protocolId}: ${error}`\r\n );\r\n }\r\n } else {\r\n return this._validateProtocol(protocol);\r\n }\r\n }\r\n\r\n /**\r\n * It adds a protocol to the protocols map object. If a protocol with the given\r\n * id already exists, warn the user and overwrite it. This can be used to\r\n * set a new \"default\" protocol.\r\n *\r\n * @param {string} protocolId - The id of the protocol.\r\n * @param {Protocol} protocol - Protocol - This is the protocol that you want to\r\n * add to the protocol manager.\r\n */\r\n public addProtocol(protocolId: string, protocol: Protocol): void {\r\n if (this.protocols.has(protocolId)) {\r\n console.warn(\r\n `A protocol with id ${protocolId} already exists. It will be overwritten.`\r\n );\r\n }\r\n\r\n if (!(protocol instanceof Function)) {\r\n protocol = this._validateProtocol(protocol as HangingProtocol.Protocol);\r\n }\r\n\r\n this.protocols.set(protocolId, protocol);\r\n }\r\n\r\n /**\r\n * Add a given protocol object as active.\r\n * If active protocols ids is null right now, then the specified\r\n * protocol will become the only active protocol.\r\n */\r\n public addActiveProtocolId(id: string): void {\r\n if (!id) {\r\n return;\r\n }\r\n if (!this.activeProtocolIds) {\r\n this.activeProtocolIds = [];\r\n }\r\n this.activeProtocolIds.push(id);\r\n }\r\n\r\n /**\r\n * Sets the active hanging protocols to use, by name. If the value is empty,\r\n * then resets the active protocols to all the named items.\r\n */\r\n public setActiveProtocolIds(protocolId?: string[] | string): void {\r\n if (!protocolId || !protocolId.length) {\r\n this.activeProtocolIds = null;\r\n console.log('No active protocols, setting all to active');\r\n return;\r\n }\r\n if (typeof protocolId === 'string') {\r\n this.setActiveProtocolIds([protocolId]);\r\n return;\r\n }\r\n this.activeProtocolIds = [...protocolId];\r\n }\r\n\r\n /**\r\n * Sets the active study.\r\n * This is the study that the hanging protocol will consider active and\r\n * may or may not be the study that is being shown by the protocol currently,\r\n * for example, a prior view hanging protocol will NOT show the active study\r\n * specifically, but will show another study instead.\r\n */\r\n public setActiveStudyUID(activeStudyUID: string): void {\r\n this.activeStudy = this.studies.find(\r\n it => it.StudyInstanceUID === activeStudyUID\r\n );\r\n }\r\n\r\n /**\r\n * Executar the hanging protocol decisions tree on the active study,\r\n * studies list and display sets, firing a PROTOCOL_CHANGED event when\r\n * complete to indicate the hanging protocol is ready, and which stage\r\n * got applied/activated.\r\n *\r\n * Also fires a STAGES_ACTIVE event to indicate which stages are able to be\r\n * activated.\r\n *\r\n * @param params is the dataset to run the hanging protocol on.\r\n * @param params.activeStudy is the \"primary\" study to hang This may or may\r\n * not be displayed by the actual viewports.\r\n * @param params.studies is the list of studies to hang. If absent, will re-use the previous set.\r\n * @param params.displaySets is the list of display sets associated with\r\n * the studies to display in viewports.\r\n * @param protocol is a specific protocol to apply.\r\n */\r\n public run({ studies, displaySets, activeStudy }, protocolId) {\r\n this.studies = [...(studies || this.studies)];\r\n this.displaySets = displaySets;\r\n this.setActiveStudyUID((activeStudy || studies[0])?.StudyInstanceUID);\r\n\r\n this.protocolEngine = new ProtocolEngine(\r\n this.getProtocols(),\r\n this.customAttributeRetrievalCallbacks\r\n );\r\n\r\n if (protocolId && typeof protocolId === 'string') {\r\n const protocol = this.getProtocolById(protocolId);\r\n this._setProtocol(protocol);\r\n return;\r\n }\r\n\r\n const matchedProtocol = this.protocolEngine.run({\r\n studies: this.studies,\r\n activeStudy,\r\n displaySets,\r\n });\r\n this._setProtocol(matchedProtocol);\r\n }\r\n\r\n /**\r\n * Returns true, if the hangingProtocol has a custom loading strategy for the images\r\n * and its callback has been added to the HangingProtocolService\r\n * @returns {boolean} true\r\n */\r\n public hasCustomImageLoadStrategy(): boolean {\r\n return (\r\n this.activeImageLoadStrategyName !== null &&\r\n this.registeredImageLoadStrategies[\r\n this.activeImageLoadStrategyName\r\n ] instanceof Function\r\n );\r\n }\r\n\r\n public getCustomImageLoadPerformed(): boolean {\r\n return this.customImageLoadPerformed;\r\n }\r\n\r\n /**\r\n * Set the strategy callback for loading images to the HangingProtocolService\r\n * @param {string} name strategy name\r\n * @param {Function} callback image loader callback\r\n */\r\n public registerImageLoadStrategy(name, callback): void {\r\n if (callback instanceof Function && name) {\r\n this.registeredImageLoadStrategies[name] = callback;\r\n }\r\n }\r\n\r\n /**\r\n * Adds a custom attribute to be used in the HangingProtocol UI and matching rules, including a\r\n * callback that will be used to calculate the attribute value.\r\n *\r\n * @param attributeId The ID used to refer to the attribute (e.g. 'timepointType')\r\n * @param attributeName The name of the attribute to be displayed (e.g. 'Timepoint Type')\r\n * @param callback The function used to calculate the attribute value from the other attributes at its level (e.g. study/series/image)\r\n * @param options to add to the \"this\" object for the custom attribute retriever\r\n */\r\n public addCustomAttribute(\r\n attributeId: string,\r\n attributeName: string,\r\n callback: (\r\n metadata: Record,\r\n extraData?: Record\r\n ) => unknown,\r\n options: Record = {}\r\n ): void {\r\n this.customAttributeRetrievalCallbacks[attributeId] = {\r\n ...options,\r\n id: attributeId,\r\n name: attributeName,\r\n callback,\r\n };\r\n }\r\n\r\n /**\r\n * Executes the callback function for the custom loading strategy for the images\r\n * if no strategy is set, the default strategy is used\r\n */\r\n runImageLoadStrategy(data): boolean {\r\n const loader = this.registeredImageLoadStrategies[\r\n this.activeImageLoadStrategyName\r\n ];\r\n const loadedData = loader({\r\n data,\r\n displaySetsMatchDetails: this.displaySetMatchDetails,\r\n viewportMatchDetails: this.viewportMatchDetails,\r\n });\r\n\r\n // if loader successfully re-arranged the data with the custom strategy\r\n // and returned the new props, then broadcast them\r\n if (!loadedData) {\r\n console.warn('Not able to load data with custom strategy');\r\n return false;\r\n }\r\n\r\n this.customImageLoadPerformed = true;\r\n this._broadcastEvent(this.EVENTS.CUSTOM_IMAGE_LOAD_PERFORMED, loadedData);\r\n return true;\r\n }\r\n\r\n _validateProtocol(\r\n protocol: HangingProtocol.Protocol\r\n ): HangingProtocol.Protocol {\r\n protocol.id = protocol.id || protocol.name;\r\n const defaultViewportOptions = {\r\n toolGroupId: 'default',\r\n viewportType: 'stack',\r\n };\r\n // Automatically compute some number of attributes if they\r\n // aren't present. Makes defining new HPs easier.\r\n protocol.name = protocol.name || protocol.id;\r\n const { stages } = protocol;\r\n\r\n if (!stages) {\r\n console.warn('Protocol has not stages:', protocol.id, protocol);\r\n return;\r\n }\r\n\r\n for (const id of Object.keys(protocol.displaySetSelectors)) {\r\n const selector = protocol.displaySetSelectors[id];\r\n selector.id = id;\r\n const { seriesMatchingRules } = selector;\r\n if (!seriesMatchingRules) {\r\n console.warn('Selector has no series matching rules', protocol.id, id);\r\n return;\r\n }\r\n }\r\n\r\n // Generate viewports automatically as required.\r\n stages.forEach(stage => {\r\n if (!stage.viewports) {\r\n stage.name = stage.name || stage.id;\r\n stage.viewports = [];\r\n const { rows, columns } = stage.viewportStructure.properties;\r\n\r\n for (let i = 0; i < rows * columns; i++) {\r\n stage.viewports.push({\r\n viewportOptions: defaultViewportOptions,\r\n displaySets: [],\r\n });\r\n }\r\n } else {\r\n stage.viewports.forEach(viewport => {\r\n viewport.viewportOptions =\r\n viewport.viewportOptions || defaultViewportOptions;\r\n if (!viewport.displaySets) {\r\n viewport.displaySets = [];\r\n } else {\r\n viewport.displaySets.forEach(displaySet => {\r\n displaySet.options = displaySet.options || {};\r\n });\r\n }\r\n });\r\n }\r\n });\r\n\r\n return protocol;\r\n }\r\n\r\n private _getProtocolFromGenerator(\r\n protocolGenerator: HangingProtocol.ProtocolGenerator\r\n ): {\r\n protocol: HangingProtocol.Protocol;\r\n } {\r\n const { protocol } = protocolGenerator({\r\n servicesManager: this._servicesManager,\r\n commandsManager: this._commandsManager,\r\n });\r\n\r\n const validatedProtocol = this._validateProtocol(protocol);\r\n\r\n return {\r\n protocol: validatedProtocol,\r\n };\r\n }\r\n\r\n getViewportsRequireUpdate(viewportIndex, displaySetInstanceUID) {\r\n const newDisplaySetInstanceUID = displaySetInstanceUID;\r\n const protocol = this.protocol;\r\n const protocolStage = protocol.stages[this.stageIndex];\r\n const protocolViewports = protocolStage.viewports;\r\n const protocolViewport = protocolViewports[viewportIndex];\r\n\r\n const defaultReturn = [\r\n {\r\n viewportIndex,\r\n displaySetInstanceUIDs: [newDisplaySetInstanceUID],\r\n },\r\n ];\r\n\r\n // if no viewport, then we can assume there is no predefined set of\r\n // rules that should be applied to this viewport while matching\r\n if (!protocolViewport) {\r\n return defaultReturn;\r\n }\r\n\r\n // no support for drag and drop into fusion viewports yet\r\n // Todo: smart drag and drop would look at the displaySets and\r\n // replace the same modality type, but later\r\n if (protocolViewport.displaySets.length > 1) {\r\n throw new Error('Cannot update viewport with multiple displaySets yet');\r\n }\r\n\r\n // If there is no displaySet, then we can assume that the viewport\r\n // is empty and we can just add the new displaySet to it\r\n if (protocolViewport.displaySets.length === 0) {\r\n return defaultReturn;\r\n }\r\n\r\n // If the viewport options says to allow any instance, then we can assume\r\n // it just updates this viewport\r\n if (protocolViewport.viewportOptions.allowUnmatchedView) {\r\n return defaultReturn;\r\n }\r\n\r\n // if the viewport is not empty, then we check the displaySets it is showing\r\n // currently, which means we need to check if the requested updated displaySet\r\n // follow the same rules as the current displaySets\r\n const {\r\n id: displaySetSelectorId,\r\n matchedDisplaySetsIndex = 0,\r\n } = protocolViewport.displaySets[0];\r\n const displaySetSelector =\r\n protocol.displaySetSelectors[displaySetSelectorId];\r\n\r\n if (!displaySetSelector) {\r\n return defaultReturn;\r\n }\r\n\r\n // so let's check if the new displaySetInstanceUIDs follow the same rules\r\n this._validateViewportSpecificMatch(\r\n {\r\n displaySetInstanceUIDs: [newDisplaySetInstanceUID],\r\n viewportOptions: {},\r\n displaySetOptions: [],\r\n },\r\n protocolViewport,\r\n protocol.displaySetSelectors\r\n );\r\n // if we reach here, it means there are some rules that should be applied\r\n\r\n // if we don't have any match details for the displaySetSelector the viewport\r\n // is currently showing, then we can assume that the new displaySetInstanceUID\r\n // does not\r\n if (!this.displaySetMatchDetails.get(displaySetSelectorId)) {\r\n return defaultReturn;\r\n }\r\n\r\n // if we reach here, it means that the displaySetInstanceUIDs to be dropped\r\n // in the viewportIndex are valid, and we can proceed with the update. However\r\n // we need to check if the displaySets that the viewport were showing\r\n // was also referenced by other viewports, and if so, we need to update those\r\n // viewports as well\r\n\r\n // check if displaySetSelectors are used by other viewports, and\r\n // store the viewportIndex and displaySetInstanceUIDs that need to be updated\r\n\r\n const viewportsToUpdate = [];\r\n protocolViewports.forEach((viewport, index) => {\r\n let viewportNeedsUpdate;\r\n for (const displaySet of viewport.displaySets) {\r\n if (\r\n displaySet.id === displaySetSelectorId &&\r\n (displaySet.matchedDisplaySetsIndex || 0) === matchedDisplaySetsIndex\r\n ) {\r\n viewportNeedsUpdate = true;\r\n break;\r\n }\r\n }\r\n\r\n if (viewportNeedsUpdate) {\r\n // we can then loop over the displaySets and choose all of them,\r\n // but for the one that matches the oldDisplaySetInstanceUID we need to\r\n // replace it with the newDisplaySetInstanceUID\r\n const {\r\n displaySetInstanceUIDs,\r\n displaySetOptions,\r\n } = viewport.displaySets.reduce(\r\n (acc, displaySet) => {\r\n const { id } = displaySet;\r\n\r\n let {\r\n displaySetInstanceUID: displaySetInstanceUIDToUse,\r\n } = this.displaySetMatchDetails.get(id);\r\n\r\n if (displaySet.id === displaySetSelectorId) {\r\n displaySetInstanceUIDToUse = newDisplaySetInstanceUID;\r\n }\r\n\r\n acc.displaySetInstanceUIDs.push(displaySetInstanceUIDToUse);\r\n acc.displaySetOptions.push(displaySet);\r\n\r\n return acc;\r\n },\r\n { displaySetInstanceUIDs: [], displaySetOptions: [] }\r\n );\r\n\r\n viewportsToUpdate.push({\r\n viewportIndex: index,\r\n displaySetInstanceUIDs,\r\n viewportOptions: viewport.viewportOptions,\r\n displaySetOptions,\r\n });\r\n }\r\n });\r\n\r\n return viewportsToUpdate;\r\n }\r\n\r\n /**\r\n * It applied the protocol to the current studies and display sets based on the\r\n * protocolId that is provided.\r\n * @param protocolId - name of the registered protocol to be set\r\n * @param options - options to be passed to the protocol, this is either an array\r\n * of the displaySetInstanceUIDs to be set on ALL VIEWPORTS OF THE PROTOCOL or an object\r\n * that contains viewportIndex as the key and displaySetInstanceUIDs as the value\r\n * for each viewport that needs to be set.\r\n * @param errorCallback - callback to be called if there is an error\r\n * during the protocol application\r\n *\r\n * @returns boolean - true if the protocol was applied and no errors were found\r\n */\r\n public setProtocol(\r\n protocolId: string,\r\n options = {} as HangingProtocol.SetProtocolOptions,\r\n errorCallback = null\r\n ): void {\r\n const foundProtocol = this.protocols.get(protocolId);\r\n\r\n if (!foundProtocol) {\r\n console.warn(\r\n `ProtocolEngine::setProtocol - Protocol with id ${protocolId} not found - you should register it first via addProtocol`\r\n );\r\n return;\r\n }\r\n\r\n try {\r\n const protocol = this._validateProtocol(foundProtocol);\r\n\r\n if (options) {\r\n this._validateOptions(options);\r\n }\r\n\r\n this._setProtocol(protocol, options);\r\n } catch (error) {\r\n console.log(error);\r\n\r\n if (errorCallback) {\r\n errorCallback(error);\r\n }\r\n\r\n throw new Error(error);\r\n }\r\n }\r\n\r\n protected matchActivation(\r\n matchedViewports: number,\r\n activation: HangingProtocol.StageActivation = {},\r\n minViewportsMatched: number\r\n ): boolean {\r\n const { displaySetSelectors } = this.protocol;\r\n\r\n const { displaySetSelectorsMatched = [] } = activation;\r\n for (const dsName of displaySetSelectorsMatched) {\r\n const displaySetSelector = displaySetSelectors[dsName];\r\n if (!displaySetSelector) {\r\n console.warn('No display set selector for', dsName);\r\n return false;\r\n }\r\n const { bestMatch } = this._matchImages(displaySetSelector);\r\n if (!bestMatch) {\r\n return false;\r\n }\r\n }\r\n const min = activation.minViewportsMatched ?? minViewportsMatched;\r\n\r\n return matchedViewports >= min;\r\n }\r\n /**\r\n * Updates the stage activation, setting the stageActivation values to\r\n * 'disabled', 'active', 'passive' where:\r\n * * disabled means there are insufficient viewports filled to show this\r\n * * passive means there aren't enough preferred viewports filled to show\r\n * this stage by default, but it can be manually selected\r\n * * enabled means there are enough viewports to select this viewport by default\r\n *\r\n * The logic is currently simple, just count how many viewports would be\r\n * filled, and compare to the required/preferred count, but the intent is\r\n * to allow more complex rules in the future as required.\r\n *\r\n * @returns the stage number to apply initially, given the options.\r\n */\r\n private _updateStageStatus(\r\n options = null as HangingProtocol.SetProtocolOptions\r\n ) {\r\n const stages = this.protocol.stages;\r\n for (let i = 0; i < stages.length; i++) {\r\n const stage = stages[i];\r\n\r\n const { matchedViewports } = this._matchAllViewports(\r\n stage,\r\n options,\r\n new Map()\r\n );\r\n const activation = stage.stageActivation || {};\r\n if (this.matchActivation(matchedViewports, activation.passive, 0)) {\r\n if (this.matchActivation(matchedViewports, activation.enabled, 1)) {\r\n stage.status = 'enabled';\r\n } else {\r\n stage.status = 'passive';\r\n }\r\n } else {\r\n stage.status = 'disabled';\r\n }\r\n }\r\n\r\n this._broadcastEvent(this.EVENTS.STAGE_ACTIVATION, {\r\n protocol: this.protocol,\r\n stages: this.protocol.stages,\r\n });\r\n }\r\n\r\n private _findStageIndex(\r\n options = null as HangingProtocol.SetProtocolOptions\r\n ): number | void {\r\n const stageId = options?.stageId;\r\n const protocol = this.protocol;\r\n const stages = protocol.stages;\r\n\r\n if (stageId) {\r\n for (let i = 0; i < stages.length; i++) {\r\n const stage = stages[i];\r\n if (stage.id === stageId && stage.status !== 'disabled') return i;\r\n }\r\n return;\r\n }\r\n\r\n const stageIndex = options?.stageIndex;\r\n if (stageIndex !== undefined) {\r\n return stages[stageIndex]?.status !== 'disabled' ? stageIndex : undefined;\r\n }\r\n\r\n let firstNotDisabled: number;\r\n\r\n for (let i = 0; i < stages.length; i++) {\r\n if (stages[i].status === 'enabled') return i;\r\n if (firstNotDisabled === undefined && stages[i].status !== 'disabled') {\r\n firstNotDisabled = i;\r\n }\r\n }\r\n\r\n return firstNotDisabled;\r\n }\r\n\r\n private _setProtocol(\r\n protocol: HangingProtocol.Protocol,\r\n options = null as HangingProtocol.SetProtocolOptions\r\n ): void {\r\n const old = this.getActiveProtocol();\r\n\r\n try {\r\n if (!this.protocol || this.protocol.id !== protocol.id) {\r\n this.stageIndex = options?.stageIndex || 0;\r\n this.protocol = this._copyProtocol(protocol);\r\n\r\n const { imageLoadStrategy } = protocol;\r\n if (imageLoadStrategy) {\r\n // check if the imageLoadStrategy is a valid strategy\r\n if (\r\n this.registeredImageLoadStrategies[imageLoadStrategy] instanceof\r\n Function\r\n ) {\r\n this.activeImageLoadStrategyName = imageLoadStrategy;\r\n }\r\n }\r\n\r\n this._updateStageStatus(options);\r\n }\r\n\r\n const stage = this._findStageIndex(options);\r\n if (stage === undefined) {\r\n throw new Error(\r\n `Can't find applicable stage ${protocol.id} ${options?.stageIndex}`\r\n );\r\n }\r\n this.stageIndex = stage as number;\r\n this._updateViewports(options);\r\n } catch (error) {\r\n console.log(error);\r\n Object.assign(this, old);\r\n throw new Error(error);\r\n }\r\n\r\n if (options?.restoreProtocol !== true) {\r\n this._broadcastEvent(HangingProtocolService.EVENTS.PROTOCOL_CHANGED, {\r\n viewportMatchDetails: this.viewportMatchDetails,\r\n displaySetMatchDetails: this.displaySetMatchDetails,\r\n protocol: this.protocol,\r\n stageIdx: this.stageIndex,\r\n stage: this.protocol.stages[this.stageIndex],\r\n activeStudyUID: this.activeStudy?.StudyInstanceUID,\r\n });\r\n } else {\r\n this._broadcastEvent(HangingProtocolService.EVENTS.PROTOCOL_RESTORED, {\r\n protocol: this.protocol,\r\n stageIdx: this.stageIndex,\r\n stage: this.protocol.stages[this.stageIndex],\r\n activeStudyUID: this.activeStudy?.StudyInstanceUID,\r\n });\r\n }\r\n }\r\n\r\n public getStageIndex(protocolId: string, options): number {\r\n const protocol = this.getProtocolById(protocolId);\r\n const { stageId, stageIndex } = options;\r\n if (stageId !== undefined) {\r\n return protocol.stages.findIndex(it => it.id === stageId);\r\n }\r\n if (stageIndex !== undefined) return stageIndex;\r\n return 0;\r\n }\r\n\r\n /**\r\n * Retrieves the number of Stages in the current Protocol or\r\n * undefined if no protocol or stages are set\r\n */\r\n _getNumProtocolStages() {\r\n if (\r\n !this.protocol ||\r\n !this.protocol.stages ||\r\n !this.protocol.stages.length\r\n ) {\r\n return;\r\n }\r\n\r\n return this.protocol.stages.length;\r\n }\r\n\r\n /**\r\n * Retrieves the current Stage from the current Protocol and stage index\r\n *\r\n * @returns {*} The Stage model for the currently displayed Stage\r\n */\r\n _getCurrentStageModel() {\r\n return this.protocol.stages[this.stageIndex];\r\n }\r\n\r\n /**\r\n * Gets a new viewport object for missing viewports. Used to fill\r\n * new viewports.\r\n * Looks first for the stage, to see if there is a missingViewport defined,\r\n * and secondly looks to the overall protocol.\r\n *\r\n * Returns a matchInfo object, which can be used to create the actual\r\n * viewport object (which this class knows nothing about).\r\n */\r\n public getMissingViewport(\r\n protocolId: string,\r\n stageIdx: number,\r\n options\r\n ): HangingProtocol.ViewportMatchDetails {\r\n if (this.protocol.id !== protocolId) {\r\n throw new Error(\r\n `Currently applied protocol ${this.protocol.id} is different from ${protocolId}`\r\n );\r\n }\r\n const protocol = this.protocol;\r\n const stage = protocol.stages[stageIdx];\r\n const defaultViewport = stage.defaultViewport || protocol.defaultViewport;\r\n if (!defaultViewport) return;\r\n\r\n const useViewport = { ...defaultViewport };\r\n return this._matchViewport(useViewport, options);\r\n }\r\n\r\n /**\r\n * Updates the viewports with the selected protocol stage.\r\n */\r\n _updateViewports(options = null as HangingProtocol.SetProtocolOptions): void {\r\n // Make sure we have an active protocol with a non-empty array of display sets\r\n if (!this._getNumProtocolStages()) {\r\n throw new Error('No protocol or stages found');\r\n }\r\n\r\n // each time we are updating the viewports, we need to reset the\r\n // matching applied\r\n this.viewportMatchDetails = new Map();\r\n this.displaySetMatchDetails = new Map();\r\n this.customImageLoadPerformed = false;\r\n\r\n // Retrieve the current stage\r\n const stageModel = this._getCurrentStageModel();\r\n\r\n // If the current stage does not fulfill the requirements to be displayed,\r\n // stop here.\r\n if (\r\n !stageModel ||\r\n !stageModel.viewportStructure ||\r\n !stageModel.viewports ||\r\n !stageModel.viewports.length\r\n ) {\r\n console.log('Stage cannot be applied', stageModel);\r\n return;\r\n }\r\n\r\n const { layoutType } = stageModel.viewportStructure;\r\n // Retrieve the properties associated with the current display set's viewport structure template\r\n // If no such layout properties exist, stop here.\r\n const layoutProps = stageModel.viewportStructure.properties;\r\n if (!layoutProps) {\r\n console.log('No viewportStructure.properties in', stageModel);\r\n return;\r\n }\r\n\r\n const { columns: numCols, rows: numRows, layoutOptions = [] } = layoutProps;\r\n\r\n this._broadcastEvent(this.EVENTS.NEW_LAYOUT, {\r\n layoutType,\r\n numRows,\r\n numCols,\r\n layoutOptions,\r\n });\r\n\r\n // Loop through each viewport\r\n this._matchAllViewports(this.protocol.stages[this.stageIndex], options);\r\n }\r\n\r\n private _matchAllViewports(\r\n stageModel: HangingProtocol.ProtocolStage,\r\n options?: HangingProtocol.SetProtocolOptions,\r\n viewportMatchDetails = this.viewportMatchDetails,\r\n displaySetMatchDetails = this.displaySetMatchDetails\r\n ): {\r\n matchedViewports: number;\r\n viewportMatchDetails: Map;\r\n displaySetMatchDetails: Map;\r\n } {\r\n let matchedViewports = 0;\r\n stageModel.viewports.forEach((viewport, viewportIndex) => {\r\n const matchDetails = this._matchViewport(\r\n viewport,\r\n options,\r\n viewportMatchDetails,\r\n displaySetMatchDetails\r\n );\r\n if (matchDetails) {\r\n if (\r\n matchDetails.displaySetsInfo?.length &&\r\n matchDetails.displaySetsInfo[0].displaySetInstanceUID\r\n ) {\r\n matchedViewports++;\r\n } else {\r\n console.log(\r\n 'Adding an empty set of display sets for mapping purposes'\r\n );\r\n matchDetails.displaySetsInfo = viewport.displaySets.map(it => ({\r\n displaySetOptions: it,\r\n }));\r\n }\r\n viewportMatchDetails.set(viewportIndex, matchDetails);\r\n }\r\n });\r\n return { matchedViewports, viewportMatchDetails, displaySetMatchDetails };\r\n }\r\n\r\n protected findDeduplicatedMatchDetails(\r\n matchDetails: HangingProtocol.DisplaySetMatchDetails,\r\n offset: number,\r\n options: HangingProtocol.SetProtocolOptions = {}\r\n ): HangingProtocol.DisplaySetMatchDetails {\r\n if (!matchDetails) return;\r\n if (offset === 0) return matchDetails;\r\n const { matchingScores = [] } = matchDetails;\r\n if (offset === -1) {\r\n const { inDisplay } = options;\r\n if (!inDisplay) return matchDetails;\r\n for (let i = 0; i < matchDetails.matchingScores.length; i++) {\r\n if (\r\n inDisplay.indexOf(\r\n matchDetails.matchingScores[i].displaySetInstanceUID\r\n ) === -1\r\n ) {\r\n const match = matchDetails.matchingScores[i];\r\n return match.matchingScore > 0\r\n ? { matchingScores, ...matchDetails.matchingScores[i] }\r\n : null;\r\n }\r\n }\r\n return;\r\n }\r\n const matchFound = matchingScores[offset];\r\n return matchFound ? { ...matchFound, matchingScores } : undefined;\r\n }\r\n\r\n protected validateDisplaySetSelectMatch(\r\n match: HangingProtocol.DisplaySetMatchDetails,\r\n id: string,\r\n displaySetUID: string\r\n ): void {\r\n if (match.displaySetInstanceUID === displaySetUID) return;\r\n if (!match.matchingScores) {\r\n throw new Error('No matchingScores found in ' + match);\r\n }\r\n for (const subMatch of match.matchingScores) {\r\n if (subMatch.displaySetInstanceUID === displaySetUID) return;\r\n }\r\n throw new Error(\r\n `Reused viewport details ${id} with ds ${displaySetUID} not valid`\r\n );\r\n }\r\n\r\n protected _matchViewport(\r\n viewport: HangingProtocol.Viewport,\r\n options: HangingProtocol.SetProtocolOptions,\r\n viewportMatchDetails = this.viewportMatchDetails,\r\n displaySetMatchDetails = this.displaySetMatchDetails\r\n ): HangingProtocol.ViewportMatchDetails {\r\n const displaySetSelectorMap = options?.displaySetSelectorMap || {};\r\n const { displaySetSelectors = {} } = this.protocol;\r\n\r\n // Matching the displaySets\r\n for (const displaySet of viewport.displaySets) {\r\n const { id: displaySetId } = displaySet;\r\n\r\n const displaySetSelector = displaySetSelectors[displaySetId];\r\n\r\n if (!displaySetSelector) {\r\n console.warn('No display set selector for', displaySetId);\r\n continue;\r\n }\r\n const { bestMatch, matchingScores } = this._matchImages(\r\n displaySetSelector\r\n );\r\n displaySetMatchDetails.set(displaySetId, bestMatch);\r\n\r\n if (bestMatch) {\r\n bestMatch.matchingScores = matchingScores;\r\n }\r\n }\r\n\r\n // Loop through each viewport\r\n const { viewportOptions = DEFAULT_VIEWPORT_OPTIONS } = viewport;\r\n // DisplaySets for the viewport, Note: this is not the actual displaySet,\r\n // but it is a info to locate the displaySet from the displaySetService\r\n const displaySetsInfo = [];\r\n const { StudyInstanceUID: activeStudyUID } = this.activeStudy;\r\n viewport.displaySets.forEach(displaySetOptions => {\r\n const { id, matchedDisplaySetsIndex = 0 } = displaySetOptions;\r\n const reuseDisplaySetUID =\r\n id &&\r\n displaySetSelectorMap[\r\n `${activeStudyUID}:${id}:${matchedDisplaySetsIndex || 0}`\r\n ];\r\n const viewportDisplaySetMain = this.displaySetMatchDetails.get(id);\r\n\r\n const viewportDisplaySet = this.findDeduplicatedMatchDetails(\r\n viewportDisplaySetMain,\r\n matchedDisplaySetsIndex,\r\n options\r\n );\r\n\r\n // Use the display set provided instead\r\n if (reuseDisplaySetUID) {\r\n if (viewportOptions.allowUnmatchedView !== true) {\r\n this.validateDisplaySetSelectMatch(\r\n viewportDisplaySet,\r\n id,\r\n reuseDisplaySetUID\r\n );\r\n }\r\n const displaySetInfo: HangingProtocol.DisplaySetInfo = {\r\n displaySetInstanceUID: reuseDisplaySetUID,\r\n displaySetOptions,\r\n };\r\n\r\n displaySetsInfo.push(displaySetInfo);\r\n return;\r\n }\r\n\r\n // Use the display set index to allow getting the \"next\" match, eg\r\n // matching all display sets, and get the matchedDisplaySetsIndex'th item\r\n if (viewportDisplaySet) {\r\n const { displaySetInstanceUID } = viewportDisplaySet;\r\n\r\n const displaySetInfo: HangingProtocol.DisplaySetInfo = {\r\n displaySetInstanceUID,\r\n displaySetOptions,\r\n };\r\n\r\n displaySetsInfo.push(displaySetInfo);\r\n } else {\r\n console.warn(\r\n `\r\n The hanging protocol viewport is requesting to display ${id} displaySet that is not\r\n matched based on the provided criteria (e.g. matching rules).\r\n `\r\n );\r\n }\r\n });\r\n return {\r\n viewportOptions,\r\n displaySetsInfo,\r\n };\r\n }\r\n\r\n private _validateViewportSpecificMatch(\r\n displaySetAndViewportOptions: HangingProtocol.DisplaySetAndViewportOptions,\r\n protocolViewport: HangingProtocol.Viewport,\r\n displaySetSelectors: Record\r\n ): void {\r\n const { displaySetService } = this._servicesManager.services;\r\n const protocolViewportDisplaySets = protocolViewport.displaySets;\r\n const numDisplaySetsToSet =\r\n displaySetAndViewportOptions.displaySetInstanceUIDs.length;\r\n\r\n if (\r\n protocolViewportDisplaySets.length > 0 &&\r\n numDisplaySetsToSet !== protocolViewportDisplaySets.length\r\n ) {\r\n throw new Error(\r\n `The number of displaySets to set ${numDisplaySetsToSet} does not match the number of displaySets in the protocol ${protocolViewportDisplaySets} - not currently implemented`\r\n );\r\n }\r\n\r\n displaySetAndViewportOptions.displaySetInstanceUIDs.forEach(\r\n displaySetInstanceUID => {\r\n const displaySet = displaySetService.getDisplaySetByUID(\r\n displaySetInstanceUID\r\n );\r\n\r\n const { displaySets: displaySetsInfo } = protocolViewport;\r\n\r\n for (const displaySetInfo of displaySetsInfo) {\r\n const displaySetSelector = displaySetSelectors[displaySetInfo.id];\r\n\r\n if (!displaySetSelector) {\r\n continue;\r\n }\r\n this._validateRequiredSelectors(displaySetSelector, displaySet);\r\n }\r\n }\r\n );\r\n }\r\n\r\n private _validateRequiredSelectors(\r\n displaySetSelector: HangingProtocol.DisplaySetSelector,\r\n displaySet: any\r\n ) {\r\n const { seriesMatchingRules } = displaySetSelector;\r\n\r\n // only match the required rules\r\n const requiredRules = seriesMatchingRules.filter(rule => rule.required);\r\n if (requiredRules.length) {\r\n const matched = this.protocolEngine.findMatch(displaySet, requiredRules);\r\n\r\n if (!matched || matched.score === 0) {\r\n throw new Error(\r\n `The displaySetInstanceUID ${displaySet.displaySetInstanceUID} does not satisfy the required seriesMatching criteria for the protocol`\r\n );\r\n }\r\n }\r\n }\r\n\r\n _validateOptions(options: HangingProtocol.SetProtocolOptions): void {\r\n const { displaySetService } = this._servicesManager.services;\r\n const { displaySetSelectorMap } = options;\r\n if (displaySetSelectorMap) {\r\n Object.entries(displaySetSelectorMap).forEach(\r\n ([key, displaySetInstanceUID]) => {\r\n const displaySet = displaySetService.getDisplaySetByUID(\r\n displaySetInstanceUID\r\n );\r\n\r\n if (!displaySet) {\r\n throw new Error(\r\n `The displaySetInstanceUID ${displaySetInstanceUID} is not found in the displaySetService`\r\n );\r\n }\r\n }\r\n );\r\n }\r\n }\r\n\r\n // Match images given a list of Studies and a Viewport's image matching reqs\r\n protected _matchImages(displaySetRules) {\r\n // TODO: matching is applied on study and series level, instance\r\n // level matching needs to be added in future\r\n\r\n // Todo: handle fusion viewports by not taking the first displaySet rule for the viewport\r\n const {\r\n id,\r\n studyMatchingRules = [],\r\n seriesMatchingRules,\r\n } = displaySetRules;\r\n\r\n const matchingScores = [];\r\n let highestSeriesMatchingScore = 0;\r\n\r\n // console.log(\r\n // 'ProtocolEngine::matchImages',\r\n // studyMatchingRules,\r\n // seriesMatchingRules\r\n // );\r\n\r\n const matchActiveOnly = this.protocol.numberOfPriorsReferenced === -1;\r\n this.studies.forEach(study => {\r\n // Skip non-active if active only\r\n if (matchActiveOnly && this.activeStudy !== study) return;\r\n const studyDisplaySets = this.displaySets.filter(\r\n it => it.StudyInstanceUID === study.StudyInstanceUID\r\n );\r\n const studyMatchDetails = this.protocolEngine.findMatch(\r\n study,\r\n studyMatchingRules,\r\n { studies: this.studies, displaySets: studyDisplaySets }\r\n );\r\n\r\n // Prevent bestMatch from being updated if the matchDetails' required attribute check has failed\r\n if (studyMatchDetails.requiredFailed === true) {\r\n return;\r\n }\r\n\r\n this.debug(\r\n 'study',\r\n study.StudyInstanceUID,\r\n 'display sets #',\r\n studyDisplaySets.length\r\n );\r\n studyDisplaySets.forEach(displaySet => {\r\n const {\r\n StudyInstanceUID,\r\n SeriesInstanceUID,\r\n displaySetInstanceUID,\r\n } = displaySet;\r\n const seriesMatchDetails = this.protocolEngine.findMatch(\r\n displaySet,\r\n seriesMatchingRules,\r\n // Todo: why we have images here since the matching type does not have it\r\n { studies: this.studies, instance: displaySet.images?.[0] }\r\n );\r\n\r\n // Prevent bestMatch from being updated if the matchDetails' required attribute check has failed\r\n if (seriesMatchDetails.requiredFailed === true) {\r\n this.debug(\r\n 'Display set required failed',\r\n displaySet,\r\n seriesMatchingRules\r\n );\r\n return;\r\n }\r\n\r\n this.debug('Found displaySet for rules', displaySet);\r\n highestSeriesMatchingScore = Math.max(\r\n seriesMatchDetails.score,\r\n highestSeriesMatchingScore\r\n );\r\n\r\n const matchDetails = {\r\n passed: [],\r\n failed: [],\r\n };\r\n\r\n matchDetails.passed = matchDetails.passed.concat(\r\n seriesMatchDetails.details.passed\r\n );\r\n matchDetails.passed = matchDetails.passed.concat(\r\n studyMatchDetails.details.passed\r\n );\r\n\r\n matchDetails.failed = matchDetails.failed.concat(\r\n seriesMatchDetails.details.failed\r\n );\r\n matchDetails.failed = matchDetails.failed.concat(\r\n studyMatchDetails.details.failed\r\n );\r\n\r\n const totalMatchScore =\r\n seriesMatchDetails.score + studyMatchDetails.score;\r\n\r\n const imageDetails = {\r\n StudyInstanceUID,\r\n SeriesInstanceUID,\r\n displaySetInstanceUID,\r\n matchingScore: totalMatchScore,\r\n matchDetails: matchDetails,\r\n sortingInfo: {\r\n score: totalMatchScore,\r\n study: study.StudyInstanceUID,\r\n series: parseInt(displaySet.SeriesNumber),\r\n },\r\n };\r\n\r\n this.debug('Adding display set', displaySet, imageDetails);\r\n matchingScores.push(imageDetails);\r\n });\r\n });\r\n\r\n if (matchingScores.length === 0) {\r\n console.log('No match found', id);\r\n }\r\n\r\n // Sort the matchingScores\r\n const sortingFunction = sortBy(\r\n {\r\n name: 'score',\r\n reverse: true,\r\n },\r\n {\r\n name: 'study',\r\n reverse: true,\r\n },\r\n {\r\n name: 'series',\r\n }\r\n );\r\n matchingScores.sort((a, b) =>\r\n sortingFunction(a.sortingInfo, b.sortingInfo)\r\n );\r\n\r\n const bestMatch = matchingScores[0];\r\n\r\n // console.log(\r\n // 'ProtocolEngine::matchImages bestMatch',\r\n // bestMatch,\r\n // matchingScores\r\n // );\r\n\r\n return {\r\n bestMatch,\r\n matchingScores,\r\n };\r\n }\r\n\r\n /**\r\n * Check if the next stage is available\r\n * @return {Boolean} True if next stage is available or false otherwise\r\n */\r\n _isNextStageAvailable() {\r\n const numberOfStages = this._getNumProtocolStages();\r\n\r\n return this.stageIndex + 1 < numberOfStages;\r\n }\r\n\r\n /**\r\n * Check if the previous stage is available\r\n * @return {Boolean} True if previous stage is available or false otherwise\r\n */\r\n _isPreviousStageAvailable(): boolean {\r\n return this.stageIndex - 1 >= 0;\r\n }\r\n\r\n /**\r\n * Changes the current stage to a new stage index in the display set sequence.\r\n * It checks if the next stage exists.\r\n *\r\n * @param {Integer} stageAction An integer value specifying whether next (1) or previous (-1) stage\r\n * @return {Boolean} True if new stage has set or false, otherwise\r\n */\r\n _setCurrentProtocolStage(\r\n stageAction: number,\r\n options: HangingProtocol.SetProtocolOptions\r\n ): boolean {\r\n // Check if previous or next stage is available\r\n let i;\r\n for (\r\n i = this.stageIndex + stageAction;\r\n i >= 0 && i < this.protocol.stages.length;\r\n i += stageAction\r\n ) {\r\n if (this.protocol.stages[i].status !== 'disabled') {\r\n break;\r\n }\r\n }\r\n if (i < 0 || i >= this.protocol.stages.length) {\r\n return false;\r\n }\r\n\r\n // Sets the new stage\r\n this.stageIndex = i;\r\n\r\n // Log the new stage\r\n this.debug(\r\n `ProtocolEngine::setCurrentProtocolStage stage = ${this.stageIndex}`\r\n );\r\n\r\n // Since stage has changed, we need to update the viewports\r\n // and redo matchings\r\n this._updateViewports(options);\r\n\r\n // Everything went well, broadcast the update, exactly identical to\r\n // HP applied\r\n this._broadcastEvent(this.EVENTS.PROTOCOL_CHANGED, {\r\n viewportMatchDetails: this.viewportMatchDetails,\r\n displaySetMatchDetails: this.displaySetMatchDetails,\r\n protocol: this.protocol,\r\n stageIdx: this.stageIndex,\r\n stage: this.protocol.stages[this.stageIndex],\r\n });\r\n return true;\r\n }\r\n\r\n /** Set this.debugLogging to true to show debug level logging - needed\r\n * to be able to figure out why hanging protocols are or are not applying.\r\n */\r\n debug(...args): void {\r\n if (this.debugLogging) {\r\n console.log(...args);\r\n }\r\n }\r\n\r\n _copyProtocol(protocol: Protocol) {\r\n return JSON.parse(JSON.stringify(protocol));\r\n }\r\n}\r\n","import HangingProtocolService from './HangingProtocolService';\r\n\r\nexport default HangingProtocolService;\r\n","import log from '../../log';\r\nimport guid from '../../utils/guid';\r\nimport { PubSubService } from '../_shared/pubSubServiceInterface';\r\n\r\n/**\r\n * Measurement source schema\r\n *\r\n * @typedef {Object} MeasurementSource\r\n * @property {number} id -\r\n * @property {string} name -\r\n * @property {string} version -\r\n */\r\n\r\n/**\r\n * Measurement schema\r\n *\r\n * @typedef {Object} Measurement\r\n * @property {number} uid -\r\n * @property {string} SOPInstanceUID -\r\n * @property {string} FrameOfReferenceUID -\r\n * @property {string} referenceSeriesUID -\r\n * @property {string} label -\r\n * @property {string} description -\r\n * @property {string} type -\r\n * @property {string} unit -\r\n * @property {number} area -\r\n * @property {Array} points -\r\n * @property {MeasurementSource} source -\r\n * @property {boolean} selected -\r\n */\r\n\r\n/* Measurement schema keys for object validation. */\r\nconst MEASUREMENT_SCHEMA_KEYS = [\r\n 'uid',\r\n 'data',\r\n 'getReport',\r\n 'displayText',\r\n 'SOPInstanceUID',\r\n 'FrameOfReferenceUID',\r\n 'referenceStudyUID',\r\n 'referenceSeriesUID',\r\n 'frameNumber',\r\n 'displaySetInstanceUID',\r\n 'label',\r\n 'description',\r\n 'type',\r\n 'unit',\r\n 'points',\r\n 'source',\r\n 'toolName',\r\n 'metadata',\r\n // Todo: we shouldn't need to have all these here.\r\n 'area', // TODO: Add concept names instead (descriptor)\r\n 'mean',\r\n 'stdDev',\r\n 'length',\r\n 'shortestDiameter',\r\n 'longestDiameter',\r\n 'cachedStats',\r\n 'selected',\r\n];\r\n\r\nconst EVENTS = {\r\n MEASUREMENT_UPDATED: 'event::measurement_updated',\r\n INTERNAL_MEASUREMENT_UPDATED: 'event:internal_measurement_updated',\r\n MEASUREMENT_ADDED: 'event::measurement_added',\r\n RAW_MEASUREMENT_ADDED: 'event::raw_measurement_added',\r\n MEASUREMENT_REMOVED: 'event::measurement_removed',\r\n MEASUREMENTS_CLEARED: 'event::measurements_cleared',\r\n JUMP_TO_MEASUREMENT: 'event:jump_to_measurement',\r\n};\r\n\r\nconst VALUE_TYPES = {\r\n ANGLE: 'value_type::polyline',\r\n POLYLINE: 'value_type::polyline',\r\n POINT: 'value_type::point',\r\n BIDIRECTIONAL: 'value_type::shortAxisLongAxis', // TODO -> Discuss with Danny. => just using SCOORD values isn't enough here.\r\n ELLIPSE: 'value_type::ellipse',\r\n RECTANGLE: 'value_type::rectangle',\r\n MULTIPOINT: 'value_type::multipoint',\r\n CIRCLE: 'value_type::circle',\r\n ROI_THRESHOLD: 'value_type::roiThreshold',\r\n ROI_THRESHOLD_MANUAL: 'value_type::roiThresholdManual',\r\n};\r\n\r\n/**\r\n * MeasurementService class that supports source management and measurement management.\r\n * Sources can be any library that can provide \"annotations\" (e.g. cornerstone-tools, cornerstone, etc.)\r\n * The flow, is that by creating a source and mappings (annotation <-> measurement), we\r\n * can convert back and forth between the two. MeasurementPanel in OHIF uses the measurement service\r\n * to manage the measurements, and any edit to the measurements will be reflected back at the\r\n * library level state (e.g. cornerstone-tools, cornerstone, etc.) by converting the\r\n * edited measurements back to the original annotations and then updating the annotations.\r\n *\r\n * Note and Todo: We should be able to support measurements that are composed of multiple\r\n * annotations, but that is not the case at the moment.\r\n */\r\nclass MeasurementService extends PubSubService {\r\n public static REGISTRATION = {\r\n name: 'measurementService',\r\n altName: 'MeasurementService',\r\n create: ({ configuration = {} }) => {\r\n return new MeasurementService();\r\n },\r\n };\r\n\r\n public static VALUE_TYPES = VALUE_TYPES;\r\n public readonly VALUE_TYPES = VALUE_TYPES;\r\n\r\n constructor() {\r\n super(EVENTS);\r\n this.sources = {};\r\n this.mappings = {};\r\n this.measurements = {};\r\n this._jumpToMeasurementCache = {};\r\n }\r\n\r\n /**\r\n * Adds the given schema to the measurement service schema list.\r\n * This method should be used to add custom tool schema to the measurement service.\r\n * @param {Array} schema schema for validation\r\n */\r\n addMeasurementSchemaKeys(schema) {\r\n if (!Array.isArray(schema)) {\r\n schema = [schema];\r\n }\r\n\r\n MEASUREMENT_SCHEMA_KEYS.push(...schema);\r\n }\r\n\r\n /**\r\n * Adds the given valueType to the measurement service valueType object.\r\n * This method should be used to add custom valueType to the measurement service.\r\n * @param {*} valueType\r\n * @returns\r\n */\r\n addValueType(valueType) {\r\n if (VALUE_TYPES[valueType]) {\r\n return;\r\n }\r\n\r\n // check if valuetype is valid , and if values are strings\r\n if (!valueType || typeof valueType !== 'object') {\r\n console.warn(\r\n `MeasurementService: addValueType: invalid valueType: ${valueType}`\r\n );\r\n return;\r\n }\r\n\r\n Object.keys(valueType).forEach(key => {\r\n if (!VALUE_TYPES[key]) {\r\n VALUE_TYPES[key] = valueType[key];\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Get all measurements.\r\n *\r\n * @return {Measurement[]} Array of measurements\r\n */\r\n getMeasurements() {\r\n const measurements = this._arrayOfObjects(this.measurements);\r\n return (\r\n measurements &&\r\n measurements.map(m => this.measurements[Object.keys(m)[0]])\r\n );\r\n }\r\n\r\n /**\r\n * Get specific measurement by its uid.\r\n *\r\n * @param {string} uid measurement uid\r\n * @return {Measurement} Measurement instance\r\n */\r\n getMeasurement(measurementUID) {\r\n let measurement = null;\r\n const measurements = this.measurements[measurementUID];\r\n\r\n if (measurements && Object.keys(measurements).length > 0) {\r\n measurement = this.measurements[measurementUID];\r\n }\r\n\r\n return measurement;\r\n }\r\n\r\n setMeasurementSelected(measurementUID, selected) {\r\n const measurement = this.getMeasurement(measurementUID);\r\n if (!measurement) {\r\n return;\r\n }\r\n\r\n measurement.selected = selected;\r\n\r\n this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {\r\n source: measurement.source,\r\n measurement,\r\n notYetUpdatedAtSource: false,\r\n });\r\n }\r\n\r\n /**\r\n * Create a new source.\r\n *\r\n * @param {string} name Name of the source\r\n * @param {string} version Source name\r\n * @return {MeasurementSource} Measurement source instance\r\n */\r\n createSource(name, version) {\r\n if (!name) {\r\n throw new Error('Source name not provided.');\r\n }\r\n\r\n if (!version) {\r\n throw new Error('Source version not provided.');\r\n }\r\n\r\n // Go over all the keys inside the sources and check if the source\r\n // name and version matches with the existing sources.\r\n const sourceKeys = Object.keys(this.sources);\r\n\r\n for (let i = 0; i < sourceKeys.length; i++) {\r\n const source = this.sources[sourceKeys[i]];\r\n if (source.name === name && source.version === version) {\r\n return source;\r\n }\r\n }\r\n\r\n const uid = guid();\r\n const source = {\r\n uid,\r\n name,\r\n version,\r\n };\r\n\r\n source.annotationToMeasurement = (\r\n annotationType,\r\n annotation,\r\n isUpdate = false\r\n ) => {\r\n return this.annotationToMeasurement(\r\n source,\r\n annotationType,\r\n annotation,\r\n isUpdate\r\n );\r\n };\r\n\r\n source.remove = (measurementUID, eventDetails) => {\r\n return this.remove(measurementUID, source, eventDetails);\r\n };\r\n\r\n source.getAnnotation = (annotationType, measurementId) => {\r\n return this.getAnnotation(source, annotationType, measurementId);\r\n };\r\n\r\n log.info(`New '${name}@${version}' source added.`);\r\n this.sources[uid] = source;\r\n\r\n return source;\r\n }\r\n\r\n getSource(name, version) {\r\n const { sources } = this;\r\n const uid = this._getSourceUID(name, version);\r\n\r\n return sources[uid];\r\n }\r\n\r\n getSourceMappings(name, version) {\r\n const { mappings } = this;\r\n const uid = this._getSourceUID(name, version);\r\n\r\n return mappings[uid];\r\n }\r\n\r\n /**\r\n * Add a new measurement matching criteria along with mapping functions.\r\n *\r\n * @param {MeasurementSource} source Measurement source instance\r\n * @param {string} annotationType annotation type to match which can be e.g., Length, Bidirecional, etc.\r\n * @param {MatchingCriteria} matchingCriteria The matching criteria\r\n * @param {Function} toAnnotationSchema Mapping function to annotation schema\r\n * @param {Function} toMeasurementSchema Mapping function to measurement schema\r\n * @return void\r\n */\r\n addMapping(\r\n source,\r\n annotationType,\r\n matchingCriteria,\r\n toAnnotationSchema,\r\n toMeasurementSchema\r\n ) {\r\n if (!this._isValidSource(source)) {\r\n throw new Error('Invalid source.');\r\n }\r\n\r\n if (!matchingCriteria) {\r\n throw new Error('Matching criteria not provided.');\r\n }\r\n\r\n if (!annotationType) {\r\n throw new Error('annotationType not provided.');\r\n }\r\n\r\n if (!toAnnotationSchema) {\r\n throw new Error('Mapping function to source schema not provided.');\r\n }\r\n\r\n if (!toMeasurementSchema) {\r\n throw new Error('Measurement mapping function not provided.');\r\n }\r\n\r\n const mapping = {\r\n matchingCriteria,\r\n annotationType,\r\n toAnnotationSchema,\r\n toMeasurementSchema,\r\n };\r\n\r\n if (Array.isArray(this.mappings[source.uid])) {\r\n this.mappings[source.uid].push(mapping);\r\n } else {\r\n this.mappings[source.uid] = [mapping];\r\n }\r\n\r\n log.info(\r\n `New measurement mapping added to source '${this._getSourceToString(\r\n source\r\n )}'.`\r\n );\r\n }\r\n\r\n /**\r\n * Get annotation for specific source.\r\n *\r\n * @param {MeasurementSource} source Measurement source instance\r\n * @param {string} annotationType The source annotationType\r\n * @param {string} measurementUID The measurement service measurement uid\r\n * @return {Object} Source measurement schema\r\n */\r\n getAnnotation(source, annotationType, measurementUID) {\r\n if (!this._isValidSource(source)) {\r\n log.warn('Invalid source. Exiting early.');\r\n return;\r\n }\r\n\r\n if (!annotationType) {\r\n log.warn('No source annotationType provided. Exiting early.');\r\n return;\r\n }\r\n\r\n const measurement = this.getMeasurement(measurementUID);\r\n const mapping = this._getMappingByMeasurementSource(\r\n measurement,\r\n annotationType\r\n );\r\n\r\n if (mapping) {\r\n return mapping.toAnnotationSchema(measurement, annotationType);\r\n }\r\n\r\n const matchingMapping = this._getMatchingMapping(\r\n source,\r\n annotationType,\r\n measurement\r\n );\r\n\r\n if (matchingMapping) {\r\n log.info('Matching mapping found:', matchingMapping);\r\n const { toAnnotationSchema, annotationType } = matchingMapping;\r\n return toAnnotationSchema(measurement, annotationType);\r\n }\r\n }\r\n\r\n update(measurementUID, measurement, notYetUpdatedAtSource = false) {\r\n if (!this.measurements[measurementUID]) {\r\n return;\r\n }\r\n\r\n const updatedMeasurement = {\r\n ...measurement,\r\n modifiedTimestamp: Math.floor(Date.now() / 1000),\r\n };\r\n\r\n log.info(\r\n `Updating internal measurement representation...`,\r\n updatedMeasurement\r\n );\r\n\r\n this.measurements[measurementUID] = updatedMeasurement;\r\n\r\n this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {\r\n source: measurement.source,\r\n measurement: updatedMeasurement,\r\n notYetUpdatedAtSource,\r\n });\r\n\r\n return updatedMeasurement.uid;\r\n }\r\n\r\n /**\r\n * Add a raw measurement into a source so that it may be\r\n * Converted to/from annotation in the same way. E.g. import serialized data\r\n * of the same form as the measurement source.\r\n * @param {MeasurementSource} source The measurement source instance.\r\n * @param {string} annotationType The source annotationType you want to add the measurement to.\r\n * @param {object} data The data you wish to add to the source.\r\n * @param {function} toMeasurementSchema A function to get the `data` into the same shape as the source annotationType.\r\n */\r\n addRawMeasurement(\r\n source,\r\n annotationType,\r\n data,\r\n toMeasurementSchema,\r\n dataSource = {}\r\n ) {\r\n if (!this._isValidSource(source)) {\r\n log.warn('Invalid source. Exiting early.');\r\n return;\r\n }\r\n\r\n const sourceInfo = this._getSourceToString(source);\r\n\r\n if (!annotationType) {\r\n log.warn('No source annotationType provided. Exiting early.');\r\n return;\r\n }\r\n\r\n if (!this._sourceHasMappings(source)) {\r\n log.warn(\r\n `No measurement mappings found for '${sourceInfo}' source. Exiting early.`\r\n );\r\n return;\r\n }\r\n\r\n let measurement = {};\r\n try {\r\n measurement = toMeasurementSchema(data);\r\n measurement.source = source;\r\n } catch (error) {\r\n log.warn(\r\n `Failed to map '${sourceInfo}' measurement for annotationType ${annotationType}:`,\r\n error.message\r\n );\r\n return;\r\n }\r\n\r\n if (!this._isValidMeasurement(measurement)) {\r\n log.warn(\r\n `Attempting to add or update a invalid measurement provided by '${sourceInfo}'. Exiting early.`\r\n );\r\n return;\r\n }\r\n\r\n let internalUID = data.id;\r\n if (!internalUID) {\r\n internalUID = guid();\r\n log.warn(`Measurement ID not found. Generating UID: ${internalUID}`);\r\n }\r\n\r\n const annotationData = data.annotation.data;\r\n\r\n const newMeasurement = {\r\n finding: annotationData.finding,\r\n findingSites: annotationData.findingSites,\r\n site: annotationData.findingSites?.[0],\r\n ...measurement,\r\n modifiedTimestamp: Math.floor(Date.now() / 1000),\r\n uid: internalUID,\r\n };\r\n\r\n if (this.measurements[internalUID]) {\r\n this.measurements[internalUID] = newMeasurement;\r\n this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {\r\n source,\r\n measurement: newMeasurement,\r\n });\r\n } else {\r\n log.info('Measurement added', newMeasurement);\r\n this.measurements[internalUID] = newMeasurement;\r\n this._broadcastEvent(this.EVENTS.RAW_MEASUREMENT_ADDED, {\r\n source,\r\n measurement: newMeasurement,\r\n data,\r\n dataSource,\r\n });\r\n }\r\n\r\n return newMeasurement.id;\r\n }\r\n\r\n /**\r\n * Adds or update persisted measurements.\r\n *\r\n * @param {MeasurementSource} source The measurement source instance\r\n * @param {string} annotationType The source annotationType\r\n * @param {EventDetail} sourceAnnotationDetail for the annotation event\r\n * @param {boolean} isUpdate is this an update or an add/completed instead?\r\n * @return {string} A measurement uid\r\n */\r\n annotationToMeasurement(\r\n source,\r\n annotationType,\r\n sourceAnnotationDetail,\r\n isUpdate = false\r\n ) {\r\n if (!this._isValidSource(source)) {\r\n throw new Error('Invalid source.');\r\n }\r\n\r\n if (!annotationType) {\r\n throw new Error('No source annotationType provided.');\r\n }\r\n\r\n const sourceInfo = this._getSourceToString(source);\r\n\r\n if (!this._sourceHasMappings(source)) {\r\n throw new Error(\r\n `No measurement mappings found for '${sourceInfo}' source. Exiting early.`\r\n );\r\n }\r\n\r\n let measurement = {};\r\n try {\r\n const sourceMappings = this.mappings[source.uid];\r\n const sourceMapping = sourceMappings.find(\r\n mapping => mapping.annotationType === annotationType\r\n );\r\n if (!sourceMapping) {\r\n console.log('No source mapping', source);\r\n return;\r\n }\r\n const { toMeasurementSchema } = sourceMapping;\r\n\r\n /* Convert measurement */\r\n measurement = toMeasurementSchema(sourceAnnotationDetail);\r\n measurement.source = source;\r\n } catch (error) {\r\n console.log('Failed to map', error);\r\n throw new Error(\r\n `Failed to map '${sourceInfo}' measurement for annotationType ${annotationType}: ${error.message}`\r\n );\r\n }\r\n\r\n if (!this._isValidMeasurement(measurement)) {\r\n throw new Error(\r\n `Attempting to add or update a invalid measurement provided by '${sourceInfo}'. Exiting early.`\r\n );\r\n }\r\n\r\n // Todo: we are using uid on the eventDetail, it should be uid of annotation\r\n let internalUID = sourceAnnotationDetail.uid;\r\n if (!internalUID) {\r\n internalUID = guid();\r\n log.info(\r\n `Anotação does not have UID, Generating UID for the created Measurement: ${internalUID}`\r\n );\r\n }\r\n\r\n const oldMeasurement = this.measurements[internalUID];\r\n\r\n const newMeasurement = {\r\n ...oldMeasurement,\r\n ...measurement,\r\n modifiedTimestamp: Math.floor(Date.now() / 1000),\r\n uid: internalUID,\r\n };\r\n\r\n if (oldMeasurement) {\r\n // TODO: Ultimately, each annotation should have a selected flag right from the soure.\r\n // For now, it is just added in OHIF here and in setMeasurementSelected.\r\n this.measurements[internalUID] = newMeasurement;\r\n if (isUpdate) {\r\n this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {\r\n source,\r\n measurement: newMeasurement,\r\n notYetUpdatedAtSource: false,\r\n });\r\n } else {\r\n log.info('Measurement added.', newMeasurement);\r\n this._broadcastEvent(this.EVENTS.MEASUREMENT_ADDED, {\r\n source,\r\n measurement: newMeasurement,\r\n });\r\n }\r\n } else {\r\n log.info('Measurement started.', newMeasurement);\r\n this.measurements[internalUID] = newMeasurement;\r\n }\r\n\r\n return newMeasurement.uid;\r\n }\r\n\r\n /**\r\n * Removes a measurement and broadcasts the removed event.\r\n *\r\n * @param {string} measurementUID The measurement uid\r\n * @param {MeasurementSource} source The measurement source instance\r\n */\r\n remove(measurementUID, source, eventDetails) {\r\n if (!measurementUID || !this.measurements[measurementUID]) {\r\n log.warn(`No uid provided, or unable to find measurement by uid.`);\r\n return;\r\n }\r\n\r\n delete this.measurements[measurementUID];\r\n this._broadcastEvent(this.EVENTS.MEASUREMENT_REMOVED, {\r\n source,\r\n measurement: measurementUID,\r\n ...eventDetails,\r\n });\r\n }\r\n\r\n clearMeasurements() {\r\n // Make a copy of the measurements\r\n const measurements = { ...this.measurements };\r\n this.measurements = {};\r\n this._jumpToMeasurementCache = {};\r\n this._broadcastEvent(this.EVENTS.MEASUREMENTS_CLEARED, { measurements });\r\n }\r\n\r\n /**\r\n * Called after the mode.onModeExit is called to reset the state.\r\n * To store measurements for later use, store them in the mode.onModeExit\r\n * and restore them in the mode onModeEnter.\r\n */\r\n onModeExit() {\r\n this.clearMeasurements();\r\n }\r\n\r\n jumpToMeasurement(viewportIndex, measurementUID) {\r\n const measurement = this.measurements[measurementUID];\r\n\r\n if (!measurement) {\r\n log.warn(`No measurement uid, or unable to find by uid.`);\r\n return;\r\n }\r\n this._addJumpToMeasurement(viewportIndex, measurementUID);\r\n\r\n this._broadcastEvent(this.EVENTS.JUMP_TO_MEASUREMENT, {\r\n viewportIndex,\r\n measurement,\r\n });\r\n }\r\n\r\n getJumpToMeasurement(viewportIndex) {\r\n return this._jumpToMeasurementCache[viewportIndex];\r\n }\r\n\r\n removeJumpToMeasurement(viewportIndex) {\r\n delete this._jumpToMeasurementCache[viewportIndex];\r\n }\r\n\r\n _getSourceUID(name, version) {\r\n const { sources } = this;\r\n\r\n const sourceUID = Object.keys(sources).find(sourceUID => {\r\n const source = sources[sourceUID];\r\n\r\n return source.name === name && source.version === version;\r\n });\r\n\r\n return sourceUID;\r\n }\r\n\r\n _addJumpToMeasurement(viewportIndex, measurementUID) {\r\n this._jumpToMeasurementCache[viewportIndex] = measurementUID;\r\n }\r\n\r\n _getMappingByMeasurementSource(measurement, annotationType) {\r\n if (this._isValidSource(measurement.source)) {\r\n return this.mappings[measurement.source.uid].find(\r\n m => m.annotationType === annotationType\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Get measurement mapping function if matching criteria.\r\n *\r\n * @param {MeasurementSource} source Measurement source instance\r\n * @param {string} annotationType The source annotationType\r\n * @param {Measurement} measurement The measurement service measurement\r\n * @return {Object} The mapping based on matched criteria\r\n */\r\n _getMatchingMapping(source, annotationType, measurement) {\r\n const sourceMappings = this.mappings[source.uid];\r\n\r\n const sourceMappingsByDefinition = sourceMappings.filter(\r\n mapping => mapping.annotationType === annotationType\r\n );\r\n\r\n /* Criteria Matching */\r\n return sourceMappingsByDefinition.find(({ matchingCriteria }) => {\r\n return (\r\n measurement.points &&\r\n measurement.points.length === matchingCriteria.points\r\n );\r\n });\r\n }\r\n\r\n /**\r\n * Returns formatted string with source info.\r\n *\r\n * @param {MeasurementSource} source Measurement source\r\n * @return {string} Source information\r\n */\r\n _getSourceToString(source) {\r\n return `${source.name}@${source.version}`;\r\n }\r\n\r\n /**\r\n * Checks if given source is valid.\r\n *\r\n * @param {MeasurementSource} source Measurement source\r\n * @return {boolean} Measurement source validation\r\n */\r\n _isValidSource(source) {\r\n return source && this.sources[source.uid];\r\n }\r\n\r\n /**\r\n * Checks if a given source has mappings.\r\n *\r\n * @param {MeasurementSource} source The measurement source\r\n * @return {boolean} Validation if source has mappings\r\n */\r\n _sourceHasMappings(source) {\r\n return (\r\n Array.isArray(this.mappings[source.uid]) &&\r\n this.mappings[source.uid].length\r\n );\r\n }\r\n\r\n /**\r\n * Check if a given measurement data is valid.\r\n *\r\n * @param {Measurement} measurementData Measurement data\r\n * @return {boolean} Measurement validation\r\n */\r\n _isValidMeasurement(measurementData) {\r\n Object.keys(measurementData).forEach(key => {\r\n if (!MEASUREMENT_SCHEMA_KEYS.includes(key)) {\r\n log.warn(`Invalid measurement key: ${key}`);\r\n return false;\r\n }\r\n });\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Check if a given measurement service event is valid.\r\n *\r\n * @param {string} eventName The name of the event\r\n * @return {boolean} Event name validation\r\n // */\r\n // _isValidEvent(eventName) {\r\n // return Object.values(this.EVENTS).includes(eventName);\r\n // }\r\n\r\n /**\r\n * Converts object of objects to array.\r\n *\r\n * @return {Array} Array of objects\r\n */\r\n _arrayOfObjects = obj => {\r\n return Object.entries(obj).map(e => ({ [e[0]]: e[1] }));\r\n };\r\n}\r\n\r\nexport default MeasurementService;\r\nexport { EVENTS, VALUE_TYPES };\r\n","import MeasurementService from './MeasurementService';\r\n\r\nexport default MeasurementService;\r\n","import { ActivatePanelTriggers } from '../../types';\r\nimport { Subscription } from '../../types/IPubSub';\r\nimport { PubSubService } from '../_shared/pubSubServiceInterface';\r\n\r\nexport const EVENTS = {\r\n ACTIVATE_PANEL: 'event::panelService:activatePanel',\r\n};\r\n\r\nexport default class PanelService extends PubSubService {\r\n public static REGISTRATION = {\r\n name: 'panelService',\r\n create: (): PanelService => {\r\n return new PanelService();\r\n },\r\n };\r\n\r\n constructor() {\r\n super(EVENTS);\r\n }\r\n\r\n /**\r\n * Activates the panel with the given id. If the forceActive flag is false\r\n * then it is up to the component containing the panel whether to activate\r\n * it immediately or not. For instance, the panel might not be activated when\r\n * the forceActive flag is false in the case where the user might have\r\n * activated/displayed and then closed the panel already.\r\n * Note that this method simply fires a broadcast event: ActivatePanelEvent.\r\n * @param panelId the panel's id\r\n * @param forceActive optional flag indicating if the panel should be forced to be activated or not\r\n */\r\n activatePanel(panelId: string, forceActive = false): void {\r\n this._broadcastEvent(EVENTS.ACTIVATE_PANEL, { panelId, forceActive });\r\n }\r\n\r\n /**\r\n * Adds a mapping of events (activatePanelTriggers.sourceEvents) broadcast by\r\n * activatePanelTrigger.sourcePubSubService that\r\n * when fired/broadcasted must in turn activate the panel with the given id.\r\n * The subscriptions created are returned such that they can be managed and unsubscribed\r\n * as appropriate.\r\n * @param panelId the id of the panel to activate\r\n * @param activatePanelTriggers an array of triggers\r\n * @param forceActive optional flag indicating if the panel should be forced to be activated or not\r\n * @returns an array of the subscriptions subscribed to\r\n */\r\n addActivatePanelTriggers(\r\n panelId: string,\r\n activatePanelTriggers: ActivatePanelTriggers[],\r\n forceActive = false\r\n ): Subscription[] {\r\n return activatePanelTriggers\r\n .map(trigger =>\r\n trigger.sourceEvents.map(eventName =>\r\n trigger.sourcePubSubService.subscribe(eventName, () =>\r\n this.activatePanel(panelId, forceActive)\r\n )\r\n )\r\n )\r\n .flat();\r\n }\r\n}\r\n","import PanelService from './PanelService';\r\n\r\nexport default PanelService;\r\n","import log from './../log.js';\r\nimport Services from '../types/Services';\r\nimport CommandsManager from '../classes/CommandsManager';\r\n\r\nexport default class ServicesManager {\r\n public services: Services = {};\r\n\r\n constructor(commandsManager: CommandsManager) {\r\n this._commandsManager = commandsManager;\r\n this.services = {};\r\n this.registeredServiceNames = [];\r\n }\r\n\r\n /**\r\n * Registers a new service.\r\n *\r\n * @param {Object} service\r\n * @param {Object} configuration\r\n */\r\n registerService(service, configuration = {}) {\r\n if (!service) {\r\n log.warn(\r\n 'Attempting to register a null/undefined service. Exiting early.'\r\n );\r\n return;\r\n }\r\n\r\n if (!service.name) {\r\n log.warn(`Service name not set. Exiting early.`);\r\n return;\r\n }\r\n\r\n if (this.registeredServiceNames.includes(service.name)) {\r\n log.warn(\r\n `Service name ${service.name} has already been registered. Exiting before duplicating services.`\r\n );\r\n return;\r\n }\r\n\r\n if (service.create) {\r\n this.services[service.name] = service.create({\r\n configuration,\r\n commandsManager: this._commandsManager,\r\n servicesManager: this,\r\n });\r\n if (service.altName) {\r\n console.log('Registering old name', service.altName);\r\n this.services[service.altName] = this.services[service.name];\r\n }\r\n } else {\r\n log.warn(`Service create factory function not defined. Exiting early.`);\r\n return;\r\n }\r\n\r\n /* Track service registration */\r\n this.registeredServiceNames.push(service.name);\r\n }\r\n\r\n /**\r\n * An array of services, or an array of arrays that contains service\r\n * configuration pairs.\r\n *\r\n * @param {Object[]} services - Array of services\r\n */\r\n registerServices(services) {\r\n services.forEach(service => {\r\n const hasConfiguration = Array.isArray(service);\r\n\r\n if (hasConfiguration) {\r\n const [ohifService, configuration] = service;\r\n this.registerService(ohifService, configuration);\r\n } else {\r\n this.registerService(service);\r\n }\r\n });\r\n }\r\n}\r\n","import { PubSubService } from '../_shared/pubSubServiceInterface';\r\nimport { ExtensionManager } from '../../extensions';\r\n\r\nconst EVENTS = {};\r\n\r\ntype Obj = Record;\r\n\r\ntype StateConfig = {\r\n /** clearOnModeExit defines state configuraion that is cleared automatically on\r\n * exiting a mode. This clearing occurs after the mode onModeExit,\r\n * so it is possible to preserve desired state during exit to be restored\r\n * later.\r\n */\r\n clearOnModeExit?: boolean;\r\n};\r\n\r\ntype States = {\r\n [key: string]: Obj;\r\n};\r\n\r\n/**\r\n */\r\nexport default class StateSyncService extends PubSubService {\r\n public static REGISTRATION = {\r\n name: 'stateSyncService',\r\n create: ({ configuration = {}, commandsManager }) => {\r\n return new StateSyncService({ configuration, commandsManager });\r\n },\r\n };\r\n\r\n extensionManager: ExtensionManager;\r\n configuration: Obj;\r\n registeredStateSets: {\r\n [id: string]: StateConfig;\r\n } = {};\r\n state: States = {};\r\n\r\n constructor({ configuration }) {\r\n super(EVENTS);\r\n this.configuration = configuration || {};\r\n }\r\n\r\n public init(extensionManager: ExtensionManager): void { }\r\n\r\n /** Registers a new sync store called `id`. The state\r\n * defines how the state is stored, and any default clearing of the\r\n * state.\r\n * A default store has the lifetime of the application.\r\n * The other available store is cleared `onModeExit`\r\n */\r\n public register(id: string, config: StateConfig): void {\r\n this.registeredStateSets[id] = config;\r\n this.store({ [id]: {} });\r\n }\r\n\r\n public getState(): Record {\r\n // TODO - return a proxy to this which is not writable in dev mode\r\n return this.state;\r\n }\r\n\r\n /**\r\n * Stores all the new state values contained in states.\r\n *\r\n * @param states - is an object containing replacement values to store\r\n * @returns\r\n */\r\n public store(states: States): States {\r\n Object.keys(states).forEach(stateKey => {\r\n if (!this.registeredStateSets[stateKey]) {\r\n throw new Error(`No state ${stateKey} registered`);\r\n }\r\n });\r\n this.state = { ...this.state, ...states };\r\n return states;\r\n }\r\n\r\n public onModeExit(): void {\r\n const toReduce = {};\r\n for (const [key, value] of Object.entries(this.registeredStateSets)) {\r\n if (value.clearOnModeExit) {\r\n toReduce[key] = {};\r\n }\r\n }\r\n this.store(toReduce);\r\n }\r\n}\r\n","import StateSyncService from './StateSyncService';\r\n\r\nexport default StateSyncService;\r\n","import merge from 'lodash.merge';\r\nimport { CommandsManager } from '../../classes';\r\nimport { ExtensionManager } from '../../extensions';\r\nimport { PubSubService } from '../_shared/pubSubServiceInterface';\r\n\r\nconst EVENTS = {\r\n TOOL_BAR_MODIFIED: 'event::toolBarService:toolBarModified',\r\n TOOL_BAR_STATE_MODIFIED: 'event::toolBarService:toolBarStateModified',\r\n};\r\n\r\nexport default class ToolbarService extends PubSubService {\r\n public static REGISTRATION = {\r\n name: 'toolbarService',\r\n // Note the old name is ToolBarService, with an upper B\r\n altName: 'ToolBarService',\r\n create: ({ commandsManager }) => {\r\n return new ToolbarService(commandsManager);\r\n },\r\n };\r\n\r\n buttons: Record = {};\r\n state: {\r\n primaryToolId: string;\r\n toggles: Record;\r\n groups: Record;\r\n } = { primaryToolId: 'WindowLevel', toggles: {}, groups: {} };\r\n buttonSections: Record = {\r\n /**\r\n * primary: ['Zoom', 'Wwwc'],\r\n * secondary: ['Length', 'RectangleRoi']\r\n */\r\n };\r\n _commandsManager: CommandsManager;\r\n extensionManager: ExtensionManager;\r\n\r\n constructor(commandsManager: CommandsManager) {\r\n super(EVENTS);\r\n this._commandsManager = commandsManager;\r\n }\r\n\r\n public init(extensionManager: ExtensionManager): void {\r\n this.extensionManager = extensionManager;\r\n }\r\n\r\n public reset(): void {\r\n this.unsubscriptions.forEach(unsub => unsub());\r\n this.state = {\r\n primaryToolId: 'WindowLevel',\r\n toggles: {},\r\n groups: {},\r\n };\r\n this.unsubscriptions = [];\r\n this.buttonSections = {};\r\n this.buttons = {};\r\n }\r\n\r\n onModeEnter() {\r\n this.reset();\r\n }\r\n\r\n /**\r\n *\r\n * @param {*} interaction - can be undefined to run nothing\r\n * @param {*} options is an optional set of extra commandOptions\r\n * used for calling the specified interaction. That is, the command is\r\n * called with {...commandOptions,...options}\r\n */\r\n recordInteraction(interaction, options?: Record) {\r\n if (!interaction) return;\r\n const commandsManager = this._commandsManager;\r\n const { groupId, itemId, interactionType, commands } = interaction;\r\n\r\n switch (interactionType) {\r\n case 'action': {\r\n commands.forEach(({ commandName, commandOptions, context }) => {\r\n if (commandName) {\r\n commandsManager.runCommand(\r\n commandName,\r\n {\r\n ...commandOptions,\r\n ...options,\r\n },\r\n context\r\n );\r\n }\r\n });\r\n break;\r\n }\r\n case 'tool': {\r\n try {\r\n commands.forEach(\r\n ({ commandName = 'setToolActive', commandOptions, context }) => {\r\n commandsManager.runCommand(commandName, commandOptions, context);\r\n }\r\n );\r\n\r\n // only set the primary tool if no error was thrown\r\n this.state.primaryToolId = itemId;\r\n } catch (error) {\r\n console.warn(error);\r\n }\r\n\r\n break;\r\n }\r\n case 'toggle': {\r\n const { commands } = interaction;\r\n let commandExecuted;\r\n\r\n // only toggle if a command was executed\r\n this.state.toggles[itemId] =\r\n this.state.toggles[itemId] === undefined\r\n ? true\r\n : !this.state.toggles[itemId];\r\n\r\n if (!commands) {\r\n break;\r\n }\r\n\r\n commands.forEach(({ commandName, commandOptions, context }) => {\r\n if (!commandOptions) {\r\n commandOptions = {};\r\n }\r\n\r\n if (commandName) {\r\n commandOptions.toggledState = this.state.toggles[itemId];\r\n\r\n try {\r\n commandsManager.runCommand(commandName, commandOptions, context);\r\n commandExecuted = true;\r\n } catch (error) {\r\n console.warn(error);\r\n }\r\n }\r\n });\r\n\r\n if (!commandExecuted) {\r\n // If no command was executed, we need to toggle the state back\r\n this.state.toggles[itemId] = !this.state.toggles[itemId];\r\n }\r\n\r\n break;\r\n }\r\n default:\r\n throw new Error(`Invalid interaction type: ${interactionType}`);\r\n }\r\n\r\n // Todo: comment out for now\r\n // Executar command if there's one associated\r\n //\r\n // NOTE: Should probably just do this for tools as well?\r\n // But would be nice if we could enforce at least the command name?\r\n // let unsubscribe;\r\n // if (commandName) {\r\n // unsubscribe = commandsManager.runCommand(commandName, commandOptions);\r\n // }\r\n\r\n // // Storing the unsubscribe for later reseting\r\n // if (unsubscribe && typeof unsubscribe === 'function') {\r\n // if (this.unsubscriptions.indexOf(unsubscribe) === -1) {\r\n // this.unsubscriptions.push(unsubscribe);\r\n // }\r\n // }\r\n\r\n // Track last touched id for each group\r\n if (groupId) {\r\n this.state.groups[groupId] = itemId;\r\n }\r\n\r\n this._broadcastEvent(this.EVENTS.TOOL_BAR_STATE_MODIFIED, {});\r\n }\r\n\r\n getButtons() {\r\n return this.buttons;\r\n }\r\n\r\n getActiveTools() {\r\n return [this.state.primaryToolId, ...Object.keys(this.state.toggles)];\r\n }\r\n\r\n /** Sets the toggle state of a button to the isActive state */\r\n public setActive(id: string, isActive: boolean): void {\r\n if (isActive) {\r\n this.state.toggles[id] = true;\r\n } else {\r\n delete this.state.toggles[id];\r\n }\r\n }\r\n\r\n setButton(id, button) {\r\n if (this.buttons[id]) {\r\n this.buttons[id] = merge(this.buttons[id], button);\r\n this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {\r\n buttons: this.buttons,\r\n button: this.buttons[id],\r\n buttonSections: this.buttonSections,\r\n });\r\n }\r\n }\r\n\r\n getButton(id) {\r\n return this.buttons[id];\r\n }\r\n\r\n setButtons(buttons) {\r\n this.buttons = buttons;\r\n this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {\r\n buttons: this.buttons,\r\n buttonSections: this.buttonSections,\r\n });\r\n }\r\n\r\n _buttonTypes() {\r\n const buttonTypes = {};\r\n const registeredToolbarModules = this.extensionManager.modules[\r\n 'toolbarModule'\r\n ];\r\n\r\n if (\r\n Array.isArray(registeredToolbarModules) &&\r\n registeredToolbarModules.length\r\n ) {\r\n registeredToolbarModules.forEach(toolbarModule =>\r\n toolbarModule.module.forEach(def => {\r\n buttonTypes[def.name] = def;\r\n })\r\n );\r\n }\r\n\r\n return buttonTypes;\r\n }\r\n\r\n createButtonSection(key, buttons) {\r\n // Maybe do this mapping at time of return, instead of time of create\r\n // Props check important for validation here...\r\n\r\n this.buttonSections[key] = buttons;\r\n this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {});\r\n }\r\n\r\n /**\r\n *\r\n * Finds a button section by it's name, then maps the list of string name\r\n * identifiers to schema/values that can be used to render the buttons.\r\n *\r\n * @param {string} key\r\n * @param {*} props\r\n */\r\n getButtonSection(key, props) {\r\n const buttonSectionIds = this.buttonSections[key];\r\n const buttonsInSection = [];\r\n\r\n if (buttonSectionIds && buttonSectionIds.length !== 0) {\r\n buttonSectionIds.forEach(btnId => {\r\n const btn = this.buttons[btnId];\r\n const metadata = {};\r\n const mappedBtn = this._mapButtonToDisplay(btn, key, metadata, props);\r\n\r\n buttonsInSection.push(mappedBtn);\r\n });\r\n }\r\n\r\n return buttonsInSection;\r\n }\r\n\r\n /**\r\n *\r\n * @param {object[]} buttons\r\n * @param {string} buttons[].id\r\n */\r\n addButtons(buttons) {\r\n buttons.forEach(button => {\r\n if (!this.buttons[button.id]) {\r\n this.buttons[button.id] = button;\r\n }\r\n });\r\n\r\n this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {});\r\n }\r\n\r\n /**\r\n *\r\n * @param {*} btn\r\n * @param {*} btnSection\r\n * @param {*} metadata\r\n * @param {*} props - Props set by the Viewer layer\r\n */\r\n _mapButtonToDisplay(btn, btnSection, metadata, props) {\r\n const { id, type, component } = btn;\r\n const buttonType = this._buttonTypes()[type];\r\n\r\n if (!buttonType) {\r\n return;\r\n }\r\n\r\n return {\r\n id,\r\n Component: component || buttonType.defaultComponent,\r\n componentProps: Object.assign({}, btn.props, props),\r\n };\r\n }\r\n\r\n getButtonComponentForUIType(uiType: string) {\r\n return uiType\r\n ? this._buttonTypes()[uiType]?.defaultComponent ?? null\r\n : null;\r\n }\r\n}\r\n","import ToolbarService from './ToolbarService';\r\n\r\nexport default ToolbarService;\r\n","/**\r\n * A UI Element Position\r\n *\r\n * @typedef {Object} ElementPosition\r\n * @property {number} top -\r\n * @property {number} left -\r\n * @property {number} right -\r\n * @property {number} bottom -\r\n */\r\n\r\n/**\r\n * UI Dialog\r\n *\r\n * @typedef {Object} DialogProps\r\n * @property {string} id The dialog id.\r\n * @property {ReactElement|HTMLElement} content The dialog content.\r\n * @property {Object} contentProps The dialog content props.\r\n * @property {boolean} [isDraggable=true] Controls if dialog content is draggable or not.\r\n * @property {boolean} [showOverlay=false] Controls dialog overlay.\r\n * @property {boolean} [centralize=false] Center the dialog on the screen.\r\n * @property {boolean} [preservePosition=true] Use last position instead of default.\r\n * @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at.\r\n * @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.\r\n * @property {Function} onStop Called when dragging stops.\r\n * @property {Function} onDrag Called while dragging.\r\n */\r\n\r\nconst name = 'uiDialogService';\r\n\r\nconst publicAPI = {\r\n name,\r\n dismiss: _dismiss,\r\n dismissAll: _dismissAll,\r\n create: _create,\r\n setServiceImplementation,\r\n};\r\n\r\nconst serviceImplementation = {\r\n _dismiss: () => console.warn('dismiss() NOT IMPLEMENTED'),\r\n _dismissAll: () => console.warn('dismissAll() NOT IMPLEMENTED'),\r\n _create: () => console.warn('create() NOT IMPLEMENTED'),\r\n};\r\n\r\n/**\r\n * Show a new UI dialog;\r\n *\r\n * @param {DialogProps} props { id, content, contentProps, onStart, onDrag, onStop, centralize, isDraggable, showOverlay, preservePosition, defaultPosition }\r\n */\r\nfunction _create({\r\n id,\r\n content,\r\n contentProps,\r\n onStart,\r\n onDrag,\r\n onStop,\r\n centralize = false,\r\n preservePosition = true,\r\n isDraggable = true,\r\n showOverlay = false,\r\n onClickOutside,\r\n defaultPosition,\r\n}) {\r\n return serviceImplementation._create({\r\n id,\r\n content,\r\n contentProps,\r\n onStart,\r\n onDrag,\r\n onStop,\r\n centralize,\r\n preservePosition,\r\n isDraggable,\r\n onClickOutside,\r\n showOverlay,\r\n defaultPosition,\r\n });\r\n}\r\n\r\n/**\r\n * Destroys all dialogs, if any\r\n *\r\n * @returns void\r\n */\r\nfunction _dismissAll() {\r\n return serviceImplementation._dismissAll();\r\n}\r\n\r\n/**\r\n * Destroy the dialog, if currently created\r\n *\r\n * @returns void\r\n */\r\nfunction _dismiss({ id }) {\r\n return serviceImplementation._dismiss({ id });\r\n}\r\n\r\n/**\r\n *\r\n *\r\n * @param {*} {\r\n * dismiss: dismissImplementation,\r\n * dismissAll: dismissAllImplementation,\r\n * create: createImplementation,\r\n * }\r\n */\r\nfunction setServiceImplementation({\r\n dismiss: dismissImplementation,\r\n dismissAll: dismissAllImplementation,\r\n create: createImplementation,\r\n}) {\r\n if (dismissImplementation) {\r\n serviceImplementation._dismiss = dismissImplementation;\r\n }\r\n if (dismissAllImplementation) {\r\n serviceImplementation._dismissAll = dismissAllImplementation;\r\n }\r\n if (createImplementation) {\r\n serviceImplementation._create = createImplementation;\r\n }\r\n}\r\n\r\n// TODO - export type here\r\nexport default {\r\n REGISTRATION: {\r\n name,\r\n altName: 'UIDialogService',\r\n create: ({ configuration = {} }) => {\r\n return publicAPI;\r\n },\r\n },\r\n};\r\n","/**\r\n * UI Modal\r\n *\r\n * @typedef {Object} ModalProps\r\n * @property {ReactElement|HTMLElement} [content=null] Modal content.\r\n * @property {Object} [contentProps=null] Modal content props.\r\n * @property {boolean} [shouldCloseOnEsc=false] Modal is dismissible via the esc key.\r\n * @property {boolean} [isOpen=true] Make the Modal visible or hidden.\r\n * @property {boolean} [closeButton=true] Should the modal body render the close button.\r\n * @property {string} [title=null] Should the modal render the title independently of the body content.\r\n * @property {string} [customClassName=null] The custom class to style the modal.\r\n */\r\n\r\nconst name = 'uiModalService';\r\n\r\nconst publicAPI = {\r\n name,\r\n hide: _hide,\r\n show: _show,\r\n setServiceImplementation,\r\n};\r\n\r\nconst serviceImplementation = {\r\n _hide: () => console.warn('hide() NOT IMPLEMENTED'),\r\n _show: () => console.warn('show() NOT IMPLEMENTED'),\r\n};\r\n\r\n/**\r\n * Show a new UI modal;\r\n *\r\n * @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, closeButton, title, customClassName }\r\n */\r\nfunction _show({\r\n content = null,\r\n contentProps = null,\r\n shouldCloseOnEsc = true,\r\n isOpen = true,\r\n closeButton = true,\r\n title = null,\r\n customClassName = null,\r\n maximizable = false\r\n}) {\r\n return serviceImplementation._show({\r\n content,\r\n contentProps,\r\n shouldCloseOnEsc,\r\n isOpen,\r\n closeButton,\r\n title,\r\n customClassName,\r\n maximizable\r\n });\r\n}\r\n\r\n/**\r\n * Hides/dismisses the modal, if currently shown\r\n *\r\n * @returns void\r\n */\r\nfunction _hide() {\r\n return serviceImplementation._hide();\r\n}\r\n\r\n/**\r\n *\r\n *\r\n * @param {*} {\r\n * hide: hideImplementation,\r\n * show: showImplementation,\r\n * }\r\n */\r\nfunction setServiceImplementation({\r\n hide: hideImplementation,\r\n show: showImplementation,\r\n}) {\r\n if (hideImplementation) {\r\n serviceImplementation._hide = hideImplementation;\r\n }\r\n if (showImplementation) {\r\n serviceImplementation._show = showImplementation;\r\n }\r\n}\r\n\r\n// TODO - export TS Type\r\nexport default {\r\n REGISTRATION: {\r\n name,\r\n altName: 'UIModalService',\r\n create: ({ configuration = {} }) => {\r\n return publicAPI;\r\n },\r\n },\r\n};\r\n","/**\r\n * A UI Notification\r\n *\r\n * @typedef {Object} Notification\r\n * @property {string} title -\r\n * @property {string} message -\r\n * @property {number} [duration=5000] - in ms\r\n * @property {string} [position=\"bottomRight\"] -\"topLeft\" | \"topCenter | \"topRight\" | \"bottomLeft\" | \"bottomCenter\" | \"bottomRight\"\r\n * @property {string} [type=\"info\"] - \"info\" | \"error\" | \"warning\" | \"success\"\r\n * @property {boolean} [autoClose=true]\r\n */\r\n\r\nconst name = 'uiNotificationService';\r\n\r\nconst serviceShowRequestQueue = [];\r\n\r\nconst publicAPI = {\r\n name,\r\n hide: _hide,\r\n show: _show,\r\n setServiceImplementation,\r\n};\r\n\r\nconst serviceImplementation = {\r\n _hide: () => console.warn('hide() NOT IMPLEMENTED'),\r\n _show: showArguments => {\r\n serviceShowRequestQueue.push(showArguments);\r\n\r\n console.warn('show() NOT IMPLEMENTED');\r\n },\r\n};\r\n\r\n/**\r\n * Create and show a new UI notification; returns the\r\n * ID of the created notification.\r\n *\r\n * @param {Notification} notification { title, message, duration, position, type, autoClose}\r\n * @returns {number} id\r\n */\r\nfunction _show({\r\n title,\r\n message,\r\n duration = 5000,\r\n position = 'bottomRight',\r\n type = 'info',\r\n autoClose = true,\r\n}) {\r\n return serviceImplementation._show({\r\n title,\r\n message,\r\n duration,\r\n position,\r\n type,\r\n autoClose,\r\n });\r\n}\r\n\r\n/**\r\n * Hides/dismisses the notification, if currently shown\r\n *\r\n * @param {number} id - id of the notification to hide/dismiss\r\n * @returns undefined\r\n */\r\nfunction _hide(id) {\r\n return serviceImplementation._hide({ id });\r\n}\r\n\r\n/**\r\n *\r\n *\r\n * @param {*} {\r\n * hide: hideImplementation,\r\n * show: showImplementation,\r\n * }\r\n */\r\nfunction setServiceImplementation({\r\n hide: hideImplementation,\r\n show: showImplementation,\r\n}) {\r\n if (hideImplementation) {\r\n serviceImplementation._hide = hideImplementation;\r\n }\r\n if (showImplementation) {\r\n serviceImplementation._show = showImplementation;\r\n\r\n while (serviceShowRequestQueue.length > 0) {\r\n const showArguments = serviceShowRequestQueue.pop();\r\n serviceImplementation._show(showArguments);\r\n }\r\n }\r\n}\r\n\r\nexport default {\r\n REGISTRATION: {\r\n name,\r\n altName: 'UINotificationService',\r\n create: ({ configuration = {} }) => {\r\n return publicAPI;\r\n },\r\n },\r\n};\r\n","/**\r\n * Viewport Dialog\r\n *\r\n * @typedef {Object} ViewportDialogProps\r\n * @property {ReactElement|HTMLElement} [content=null] Modal content.\r\n * @property {Object} [contentProps=null] Modal content props.\r\n * @property {boolean} [viewportIndex=false] Modal is dismissible via the esc key.\r\n */\r\n\r\nconst name = 'uiViewportDialogService';\r\n\r\nconst publicAPI = {\r\n name,\r\n hide: _hide,\r\n show: _show,\r\n setServiceImplementation,\r\n};\r\n\r\nconst serviceImplementation = {\r\n _hide: () => console.warn('hide() NOT IMPLEMENTED'),\r\n _show: () => console.warn('show() NOT IMPLEMENTED'),\r\n};\r\n\r\n/**\r\n * Show a new UI viewport dialog on the specified viewportIndex;\r\n *\r\n * @param {ViewportDialogProps} props { content, contentProps, viewportIndex }\r\n */\r\nfunction _show({\r\n viewportIndex,\r\n id,\r\n type,\r\n message,\r\n actions,\r\n onSubmit,\r\n onOutsideClick,\r\n maximizable\r\n}) {\r\n return serviceImplementation._show({\r\n viewportIndex,\r\n id,\r\n type,\r\n message,\r\n actions,\r\n onSubmit,\r\n onOutsideClick,\r\n maximizable\r\n });\r\n}\r\n\r\n/**\r\n * Hides/dismisses the viewport dialog, if currently shown\r\n */\r\nfunction _hide() {\r\n return serviceImplementation._hide();\r\n}\r\n\r\n/**\r\n *\r\n *\r\n * @param {*} {\r\n * hide: hideImplementation,\r\n * show: showImplementation,\r\n * viewportIndex,\r\n * }\r\n */\r\nfunction setServiceImplementation({\r\n hide: hideImplementation,\r\n show: showImplementation,\r\n}) {\r\n if (hideImplementation) {\r\n serviceImplementation._hide = hideImplementation;\r\n }\r\n if (showImplementation) {\r\n serviceImplementation._show = showImplementation;\r\n }\r\n}\r\n\r\nexport default {\r\n REGISTRATION: {\r\n name,\r\n altName: 'UIViewportDialogService',\r\n create: ({ configuration = {} }) => {\r\n return publicAPI;\r\n },\r\n },\r\n};\r\n","const name = 'userAuthenticationService';\r\n\r\nconst publicAPI = {\r\n name,\r\n getState: _getState,\r\n setUser: _setUser,\r\n getUser: _getUser,\r\n getAuthorizationHeader: _getAuthorizationHeader,\r\n handleUnauthenticated: _handleUnauthenticated,\r\n setServiceImplementation,\r\n reset: _reset,\r\n set: _set,\r\n};\r\n\r\nconst serviceImplementation = {\r\n _getState: () => console.warn('getState() NOT IMPLEMENTED'),\r\n _setUser: () => console.warn('_setUser() NOT IMPLEMENTED'),\r\n _getUser: () => console.warn('_setUser() NOT IMPLEMENTED'),\r\n _getAuthorizationHeader: () => {}, // TODO: have enabled/disabled state?\r\n //console.warn('_getAuthorizationHeader() NOT IMPLEMENTED'),\r\n _handleUnauthenticated: () =>\r\n console.warn('_handleUnauthenticated() NOT IMPLEMENTED'),\r\n _reset: () => console.warn('reset() NOT IMPLEMENTED'),\r\n _set: () => console.warn('set() NOT IMPLEMENTED'),\r\n};\r\n\r\nfunction _getState() {\r\n return serviceImplementation._getState();\r\n}\r\n\r\nfunction _setUser(user) {\r\n return serviceImplementation._setUser(user);\r\n}\r\n\r\nfunction _getUser() {\r\n return serviceImplementation._getUser();\r\n}\r\n\r\nfunction _getAuthorizationHeader() {\r\n return serviceImplementation._getAuthorizationHeader();\r\n}\r\n\r\nfunction _handleUnauthenticated() {\r\n return serviceImplementation._handleUnauthenticated();\r\n}\r\n\r\nfunction _set(state) {\r\n return serviceImplementation._set(state);\r\n}\r\n\r\nfunction _reset() {\r\n return serviceImplementation._reset({});\r\n}\r\n\r\nfunction setServiceImplementation({\r\n getState: getStateImplementation,\r\n setUser: setUserImplementation,\r\n getUser: getUserImplementation,\r\n getAuthorizationHeader: getAuthorizationHeaderImplementation,\r\n handleUnauthenticated: handleUnauthenticatedImplementation,\r\n reset: resetImplementation,\r\n set: setImplementation,\r\n}) {\r\n if (getStateImplementation) {\r\n serviceImplementation._getState = getStateImplementation;\r\n }\r\n if (setUserImplementation) {\r\n serviceImplementation._setUser = setUserImplementation;\r\n }\r\n if (getUserImplementation) {\r\n serviceImplementation._getUser = getUserImplementation;\r\n }\r\n if (getAuthorizationHeaderImplementation) {\r\n serviceImplementation._getAuthorizationHeader = getAuthorizationHeaderImplementation;\r\n }\r\n if (handleUnauthenticatedImplementation) {\r\n serviceImplementation._handleUnauthenticated = handleUnauthenticatedImplementation;\r\n }\r\n if (resetImplementation) {\r\n serviceImplementation._reset = resetImplementation;\r\n }\r\n if (setImplementation) {\r\n serviceImplementation._set = setImplementation;\r\n }\r\n}\r\n\r\nexport default {\r\n REGISTRATION: {\r\n name,\r\n altName: 'UserAuthenticationService',\r\n create: ({ configuration = {} }) => {\r\n return publicAPI;\r\n },\r\n },\r\n};\r\n","import UserAuthenticationService from './UserAuthenticationService';\r\n\r\nexport default UserAuthenticationService;\r\n","import { PubSubService } from '../_shared/pubSubServiceInterface';\r\n\r\nconst EVENTS = {\r\n ACTIVE_VIEWPORT_INDEX_CHANGED: 'event::activeviewportindexchanged',\r\n};\r\n\r\nclass ViewportGridService extends PubSubService {\r\n public static REGISTRATION = {\r\n name: 'viewportGridService',\r\n altName: 'ViewportGridService',\r\n create: ({ configuration = {} }) => {\r\n return new ViewportGridService();\r\n },\r\n };\r\n public static EVENTS = EVENTS;\r\n\r\n serviceImplementation = {};\r\n\r\n constructor() {\r\n super(EVENTS);\r\n this.serviceImplementation = {};\r\n }\r\n\r\n public setServiceImplementation({\r\n getState: getStateImplementation,\r\n setActiveViewportIndex: setActiveViewportIndexImplementation,\r\n setDisplaySetsForViewport: setDisplaySetsForViewportImplementation,\r\n setDisplaySetsForViewports: setDisplaySetsForViewportsImplementation,\r\n setLayout: setLayoutImplementation,\r\n reset: resetImplementation,\r\n onModeExit: onModeExitImplementation,\r\n set: setImplementation,\r\n getNumViewportPanes: getNumViewportPanesImplementation,\r\n }): void {\r\n if (getStateImplementation) {\r\n this.serviceImplementation._getState = getStateImplementation;\r\n }\r\n if (setActiveViewportIndexImplementation) {\r\n this.serviceImplementation._setActiveViewportIndex = setActiveViewportIndexImplementation;\r\n }\r\n if (setDisplaySetsForViewportImplementation) {\r\n this.serviceImplementation._setDisplaySetsForViewport = setDisplaySetsForViewportImplementation;\r\n }\r\n if (setDisplaySetsForViewportsImplementation) {\r\n this.serviceImplementation._setDisplaySetsForViewports = setDisplaySetsForViewportsImplementation;\r\n }\r\n if (setLayoutImplementation) {\r\n this.serviceImplementation._setLayout = setLayoutImplementation;\r\n }\r\n if (resetImplementation) {\r\n this.serviceImplementation._reset = resetImplementation;\r\n }\r\n if (onModeExitImplementation) {\r\n this.serviceImplementation._onModeExit = onModeExitImplementation;\r\n }\r\n if (setImplementation) {\r\n this.serviceImplementation._set = setImplementation;\r\n }\r\n if (getNumViewportPanesImplementation) {\r\n this.serviceImplementation._getNumViewportPanes = getNumViewportPanesImplementation;\r\n }\r\n }\r\n\r\n public setActiveViewportIndex(index) {\r\n this.serviceImplementation._setActiveViewportIndex(index);\r\n const state = this.getState();\r\n const viewportId = state.viewports[index]?.viewportOptions?.viewportId;\r\n this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED, {\r\n viewportIndex: index,\r\n viewportId,\r\n });\r\n }\r\n\r\n public getState() {\r\n return this.serviceImplementation._getState();\r\n }\r\n\r\n public setDisplaySetsForViewport({\r\n viewportIndex,\r\n displaySetInstanceUIDs,\r\n viewportOptions,\r\n displaySetOptions,\r\n }) {\r\n this.serviceImplementation._setDisplaySetsForViewport({\r\n viewportIndex,\r\n displaySetInstanceUIDs,\r\n viewportOptions,\r\n displaySetOptions,\r\n });\r\n }\r\n\r\n public setDisplaySetsForViewports(viewports) {\r\n this.serviceImplementation._setDisplaySetsForViewports(viewports);\r\n }\r\n\r\n /**\r\n *\r\n * @param numCols, numRows - the number of columns and rows to apply\r\n * @param findOrCreateViewport is a function which takes the\r\n * index position of the viewport, the position id, and a set of\r\n * options that is initially provided as {} (eg to store intermediate state)\r\n * The function returns a viewport object to use at the given position.\r\n */\r\n public setLayout({ numCols, numRows, findOrCreateViewport = undefined }) {\r\n this.serviceImplementation._setLayout({\r\n numCols,\r\n numRows,\r\n findOrCreateViewport,\r\n });\r\n }\r\n\r\n public reset() {\r\n this.serviceImplementation._reset();\r\n }\r\n\r\n /**\r\n * The onModeExit must set the state of the viewport grid to a standard/clean\r\n * state. To implement store/recover of the viewport grid, perform\r\n * a state store in the mode or extension onModeExit, and recover that\r\n * data if appropriate in the onModeEnter of the mode or extension.\r\n */\r\n public onModeExit(): void {\r\n this.serviceImplementation._onModeExit();\r\n }\r\n\r\n public set(state) {\r\n this.serviceImplementation._set(state);\r\n }\r\n\r\n public getNumViewportPanes() {\r\n return this.serviceImplementation._getNumViewportPanes();\r\n }\r\n}\r\n\r\nexport default ViewportGridService;\r\n","import ViewportGridService from './ViewportGridService';\r\n\r\nexport default ViewportGridService;\r\n","import guid from '../../utils/guid';\r\n\r\n/**\r\n * Consumer must implement:\r\n * this.listeners = {}\r\n * this.EVENTS = { \"EVENT_KEY\": \"EVENT_VALUE\" }\r\n */\r\nexport default {\r\n subscribe,\r\n _broadcastEvent,\r\n _unsubscribe,\r\n _isValidEvent,\r\n};\r\n\r\n/**\r\n * Subscribe to updates.\r\n *\r\n * @param {string} eventName The name of the event\r\n * @param {Function} callback Events callback\r\n * @return {Object} Observable object with actions\r\n */\r\nfunction subscribe(eventName, callback) {\r\n if (this._isValidEvent(eventName)) {\r\n const listenerId = guid();\r\n const subscription = { id: listenerId, callback };\r\n\r\n // console.info(`Subscribing to '${eventName}'.`);\r\n if (Array.isArray(this.listeners[eventName])) {\r\n this.listeners[eventName].push(subscription);\r\n } else {\r\n this.listeners[eventName] = [subscription];\r\n }\r\n\r\n return {\r\n unsubscribe: () => this._unsubscribe(eventName, listenerId),\r\n };\r\n } else {\r\n throw new Error(`Event ${eventName} not supported.`);\r\n }\r\n}\r\n\r\n/**\r\n * Unsubscribe to measurement updates.\r\n *\r\n * @param {string} eventName The name of the event\r\n * @param {string} listenerId The listeners id\r\n * @return void\r\n */\r\nfunction _unsubscribe(eventName, listenerId) {\r\n if (!this.listeners[eventName]) {\r\n return;\r\n }\r\n\r\n const listeners = this.listeners[eventName];\r\n if (Array.isArray(listeners)) {\r\n this.listeners[eventName] = listeners.filter(({ id }) => id !== listenerId);\r\n } else {\r\n this.listeners[eventName] = undefined;\r\n }\r\n}\r\n\r\n/**\r\n * Check if a given event is valid.\r\n *\r\n * @param {string} eventName The name of the event\r\n * @return {boolean} Event name validation\r\n */\r\nfunction _isValidEvent(eventName) {\r\n return Object.values(this.EVENTS).includes(eventName);\r\n}\r\n\r\n/**\r\n * Broadcasts changes.\r\n *\r\n * @param {string} eventName - The event name\r\n * @param {func} callbackProps - Properties to pass callback\r\n * @return void\r\n */\r\nfunction _broadcastEvent(eventName, callbackProps) {\r\n const hasListeners = Object.keys(this.listeners).length > 0;\r\n const hasCallbacks = Array.isArray(this.listeners[eventName]);\r\n\r\n if (hasListeners && hasCallbacks) {\r\n this.listeners[eventName].forEach(listener => {\r\n listener.callback(callbackProps);\r\n });\r\n }\r\n}\r\n\r\n/** Export a PubSubService class to be used instead of the individual items */\r\nexport class PubSubService {\r\n constructor(EVENTS) {\r\n this.EVENTS = EVENTS;\r\n this.subscribe = subscribe;\r\n this._broadcastEvent = _broadcastEvent;\r\n this._unsubscribe = _unsubscribe;\r\n this._isValidEvent = _isValidEvent;\r\n this.listeners = {};\r\n this.unsubscriptions = [];\r\n }\r\n\r\n reset() {\r\n this.unsubscriptions.forEach(unsub => unsub());\r\n this.unsubscriptions = [];\r\n }\r\n}\r\n","/* Enabled JPEG images downloading on IE11. */\r\nconst b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {\r\n const byteCharacters = atob(b64Data);\r\n const byteArrays = [];\r\n\r\n for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {\r\n const slice = byteCharacters.slice(offset, offset + sliceSize);\r\n\r\n const byteNumbers = new Array(slice.length);\r\n for (let i = 0; i < slice.length; i++) {\r\n byteNumbers[i] = slice.charCodeAt(i);\r\n }\r\n\r\n const byteArray = new Uint8Array(byteNumbers);\r\n byteArrays.push(byteArray);\r\n }\r\n\r\n const blob = new Blob(byteArrays, { type: contentType });\r\n return blob;\r\n};\r\n\r\nexport default b64toBlob;\r\n","/**\r\n * Create a random GUID\r\n *\r\n * @return {string}\r\n */\r\nconst guid = () => {\r\n const getFourRandomValues = () => {\r\n return Math.floor((1 + Math.random()) * 0x10000)\r\n .toString(16)\r\n .substring(1);\r\n };\r\n return (\r\n getFourRandomValues() +\r\n getFourRandomValues() +\r\n '-' +\r\n getFourRandomValues() +\r\n '-' +\r\n getFourRandomValues() +\r\n '-' +\r\n getFourRandomValues() +\r\n '-' +\r\n getFourRandomValues() +\r\n getFourRandomValues() +\r\n getFourRandomValues()\r\n );\r\n};\r\n\r\nexport default guid;\r\n","/**\r\n * adds a pause and unpause method to Mousetrap\r\n * this allows you to enable or disable keyboard shortcuts\r\n * without having to reset Mousetrap and rebind everything\r\n *\r\n * https://github.com/ccampbell/mousetrap/blob/master/plugins/pause/mousetrap-pause.js\r\n */\r\nexport default function pausePlugin(Mousetrap) {\r\n var _originalStopCallback = Mousetrap.prototype.stopCallback;\r\n\r\n Mousetrap.prototype.stopCallback = function(e, element, combo) {\r\n var self = this;\r\n\r\n if (self.paused) {\r\n return true;\r\n }\r\n\r\n return _originalStopCallback.call(self, e, element, combo);\r\n };\r\n\r\n Mousetrap.prototype.pause = function() {\r\n var self = this;\r\n self.paused = true;\r\n };\r\n\r\n Mousetrap.prototype.unpause = function() {\r\n var self = this;\r\n self.paused = false;\r\n };\r\n\r\n Mousetrap.init();\r\n}\r\n","/**\r\n * This extension allows you to record a sequence using Mousetrap.\r\n *\r\n * @author Dan Tao \r\n */\r\nexport default function recordPlugin(Mousetrap, options = { timeout: 100 }) {\r\n /**\r\n * the sequence currently being recorded\r\n *\r\n * @type {Array}\r\n */\r\n var _recordedSequence = [],\r\n /**\r\n * a callback to invoke after recording a sequence\r\n *\r\n * @type {Function|null}\r\n */\r\n _recordedSequenceCallback = null,\r\n /**\r\n * a list of all of the keys currently held down\r\n *\r\n * @type {Array}\r\n */\r\n _currentRecordedKeys = [],\r\n /**\r\n * temporary state where we remember if we've already captured a\r\n * character key in the current combo\r\n *\r\n * @type {boolean}\r\n */\r\n _recordedCharacterKey = false,\r\n /**\r\n * a handle for the timer of the current recording\r\n *\r\n * @type {null|number}\r\n */\r\n _recordTimer = null,\r\n /**\r\n * the original handleKey method to override when Mousetrap.record() is\r\n * called\r\n *\r\n * @type {Function}\r\n */\r\n _origHandleKey = Mousetrap.prototype.handleKey;\r\n\r\n /**\r\n * handles a character key event\r\n *\r\n * @param {string} character\r\n * @param {Array} modifiers\r\n * @param {Event} e\r\n * @returns void\r\n */\r\n function _handleKey(character, modifiers, e) {\r\n var self = this;\r\n\r\n if (!self.recording) {\r\n _origHandleKey.apply(self, arguments);\r\n return;\r\n }\r\n\r\n // remember this character if we're currently recording a sequence\r\n if (e.type == 'keydown') {\r\n if (character.length === 1 && _recordedCharacterKey) {\r\n _recordCurrentCombo();\r\n }\r\n\r\n for (let i = 0; i < modifiers.length; ++i) {\r\n _recordKey(modifiers[i]);\r\n }\r\n _recordKey(character);\r\n\r\n // once a key is released, all keys that were held down at the time\r\n // count as a keypress\r\n } else if (e.type == 'keyup' && _currentRecordedKeys.length > 0) {\r\n _recordCurrentCombo();\r\n }\r\n }\r\n\r\n /**\r\n * marks a character key as held down while recording a sequence\r\n *\r\n * @param {string} key\r\n * @returns void\r\n */\r\n function _recordKey(key) {\r\n // one-off implementation of Array.indexOf, since IE6-9 don't support it\r\n for (let i = 0; i < _currentRecordedKeys.length; ++i) {\r\n if (_currentRecordedKeys[i] === key) {\r\n return;\r\n }\r\n }\r\n\r\n _currentRecordedKeys.push(key);\r\n\r\n if (key.length === 1) {\r\n _recordedCharacterKey = true;\r\n }\r\n }\r\n\r\n /**\r\n * marks whatever key combination that's been recorded so far as finished\r\n * and gets ready for the next combo\r\n *\r\n * @returns void\r\n */\r\n function _recordCurrentCombo() {\r\n _recordedSequence.push(_currentRecordedKeys);\r\n _currentRecordedKeys = [];\r\n _recordedCharacterKey = false;\r\n _restartRecordTimer();\r\n }\r\n\r\n /**\r\n * ensures each combo in a sequence is in a predictable order and formats\r\n * key combos to be '+'-delimited\r\n *\r\n * modifies the sequence in-place\r\n *\r\n * @param {Array} sequence\r\n * @returns void\r\n */\r\n function _normalizeSequence(sequence) {\r\n for (let i = 0; i < sequence.length; ++i) {\r\n sequence[i].sort(function(x, y) {\r\n // modifier keys always come first, in alphabetical order\r\n if (x.length > 1 && y.length === 1) {\r\n return -1;\r\n } else if (x.length === 1 && y.length > 1) {\r\n return 1;\r\n }\r\n\r\n // character keys come next (list should contain no duplicates,\r\n // so no need for equality check)\r\n return x > y ? 1 : -1;\r\n });\r\n\r\n sequence[i] = sequence[i].join('+');\r\n }\r\n }\r\n\r\n /**\r\n * finishes the current recording, passes the recorded sequence to the stored\r\n * callback, and sets Mousetrap.handleKey back to its original function\r\n *\r\n * @returns void\r\n */\r\n function _finishRecording() {\r\n if (_recordedSequenceCallback) {\r\n _normalizeSequence(_recordedSequence);\r\n _recordedSequenceCallback(_recordedSequence);\r\n }\r\n\r\n // reset all recorded state\r\n _recordedSequence = [];\r\n _recordedSequenceCallback = null;\r\n _currentRecordedKeys = [];\r\n }\r\n\r\n /**\r\n * called to set a 1 second timeout on the current recording\r\n *\r\n * this is so after each key press in the sequence the recording will wait for\r\n * 1 more second before executing the callback\r\n *\r\n * @returns void\r\n */\r\n function _restartRecordTimer() {\r\n clearTimeout(_recordTimer);\r\n _recordTimer = setTimeout(_finishRecording, options.timeout);\r\n }\r\n\r\n /**\r\n * records the next sequence and passes it to a callback once it's\r\n * completed\r\n *\r\n * @param {Function} callback\r\n * @returns void\r\n */\r\n Mousetrap.prototype.record = function(callback) {\r\n var self = this;\r\n self.recording = true;\r\n _recordedSequenceCallback = function() {\r\n self.recording = false;\r\n callback.apply(self, arguments);\r\n };\r\n };\r\n\r\n /**\r\n * stop recording\r\n *\r\n * @param {Function} callback\r\n * @returns void\r\n */\r\n Mousetrap.prototype.stopRecord = function() {\r\n var self = this;\r\n self.recording = false;\r\n };\r\n\r\n /**\r\n * start recording\r\n *\r\n * @param {Function} callback\r\n * @returns void\r\n */\r\n Mousetrap.prototype.startRecording = function() {\r\n var self = this;\r\n self.recording = true;\r\n };\r\n Mousetrap.prototype.handleKey = function() {\r\n var self = this;\r\n _handleKey.apply(self, arguments);\r\n };\r\n\r\n Mousetrap.init();\r\n}\r\n","import Mousetrap from 'mousetrap';\r\nimport pausePlugin from './pausePlugin';\r\nimport recordPlugin from './recordPlugin';\r\n\r\nrecordPlugin(Mousetrap);\r\npausePlugin(Mousetrap);\r\n\r\nexport default Mousetrap;\r\n","/**\r\n * Removes the data loader scheme from the imageId\r\n *\r\n * @param {string} imageId Image ID\r\n * @returns {string} imageId without the data loader scheme\r\n * @memberof Cache\r\n */\r\nexport default function imageIdToURI(imageId) {\r\n const colonIndex = imageId.indexOf(':');\r\n\r\n return imageId.substring(colonIndex + 1);\r\n}\r\n","export class ObjectPath {\r\n /**\r\n * Set an object property based on \"path\" (namespace) supplied creating\r\n * ... intermediary objects if they do not exist.\r\n * @param object {Object} An object where the properties specified on path should be set.\r\n * @param path {String} A string representing the property to be set, e.g. \"user.study.series.timepoint\".\r\n * @param value {Any} The value of the property that will be set.\r\n * @return {Boolean} Returns \"true\" on success, \"false\" if any intermediate component of the supplied path\r\n * ... is not a valid Object, in which case the property cannot be set. No excpetions are thrown.\r\n */\r\n static set(object, path, value) {\r\n let components = ObjectPath.getPathComponents(path),\r\n length = components !== null ? components.length : 0,\r\n result = false;\r\n\r\n if (length > 0 && ObjectPath.isValidObject(object)) {\r\n let i = 0,\r\n last = length - 1,\r\n currentObject = object;\r\n\r\n while (i < last) {\r\n let field = components[i];\r\n\r\n if (field in currentObject) {\r\n if (!ObjectPath.isValidObject(currentObject[field])) {\r\n break;\r\n }\r\n } else {\r\n currentObject[field] = {};\r\n }\r\n\r\n currentObject = currentObject[field];\r\n i++;\r\n }\r\n\r\n if (i === last) {\r\n currentObject[components[last]] = value;\r\n result = true;\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Get an object property based on \"path\" (namespace) supplied traversing the object\r\n * ... tree as necessary.\r\n * @param object {Object} An object where the properties specified might exist.\r\n * @param path {String} A string representing the property to be searched for, e.g. \"user.study.series.timepoint\".\r\n * @return {Any} The value of the property if found. By default, returns the special type \"undefined\".\r\n */\r\n static get(object, path) {\r\n let found, // undefined by default\r\n components = ObjectPath.getPathComponents(path),\r\n length = components !== null ? components.length : 0;\r\n\r\n if (length > 0 && ObjectPath.isValidObject(object)) {\r\n let i = 0,\r\n last = length - 1,\r\n currentObject = object;\r\n\r\n while (i < last) {\r\n let field = components[i];\r\n\r\n const isValid = ObjectPath.isValidObject(currentObject[field]);\r\n if (field in currentObject && isValid) {\r\n currentObject = currentObject[field];\r\n i++;\r\n } else {\r\n break;\r\n }\r\n }\r\n\r\n if (i === last && components[last] in currentObject) {\r\n found = currentObject[components[last]];\r\n }\r\n }\r\n\r\n return found;\r\n }\r\n\r\n /**\r\n * Check if the supplied argument is a real JavaScript Object instance.\r\n * @param object {Any} The subject to be tested.\r\n * @return {Boolean} Returns \"true\" if the object is a real Object instance and \"false\" otherwise.\r\n */\r\n static isValidObject(object) {\r\n return (\r\n typeof object === 'object' && object !== null && object instanceof Object\r\n );\r\n }\r\n\r\n static getPathComponents(path) {\r\n return typeof path === 'string' ? path.split('.') : null;\r\n }\r\n}\r\n\r\nexport default ObjectPath;\r\n","const absoluteUrl = path => {\r\n let absolutePath = '/';\r\n\r\n if (!path) return absolutePath;\r\n\r\n // TODO: Find another way to get root url\r\n const absoluteUrl = window.location.origin;\r\n const absoluteUrlParts = absoluteUrl.split('/');\r\n\r\n if (absoluteUrlParts.length > 4) {\r\n const rootUrlPrefixIndex = absoluteUrl.indexOf(absoluteUrlParts[3]);\r\n absolutePath += absoluteUrl.substring(rootUrlPrefixIndex) + path;\r\n } else {\r\n absolutePath += path;\r\n }\r\n\r\n return absolutePath.replace(/\\/\\/+/g, '/');\r\n};\r\n\r\nexport default absoluteUrl;\r\n","import lib from 'query-string';\r\n\r\nconst PARAM_SEPARATOR = ';';\r\nconst PARAM_PATTERN_IDENTIFIER = ':';\r\n\r\nfunction toLowerCaseFirstLetter(word) {\r\n return word[0].toLowerCase() + word.slice(1);\r\n}\r\nconst getQueryFilters = (location = {}) => {\r\n const { search } = location;\r\n\r\n if (!search) {\r\n return;\r\n }\r\n\r\n const searchParameters = parse(search);\r\n const filters = {};\r\n\r\n Object.entries(searchParameters).forEach(([key, value]) => {\r\n filters[toLowerCaseFirstLetter(key)] = value;\r\n });\r\n\r\n return filters;\r\n};\r\n\r\nconst decode = (strToDecode = '') => {\r\n try {\r\n const decoded = window.atob(strToDecode);\r\n return decoded;\r\n } catch (e) {\r\n return strToDecode;\r\n }\r\n};\r\n\r\nconst parse = toParse => {\r\n if (toParse) {\r\n return lib.parse(toParse);\r\n }\r\n\r\n return {};\r\n};\r\nconst parseParam = paramStr => {\r\n const _paramDecoded = decode(paramStr);\r\n if (_paramDecoded && typeof _paramDecoded === 'string') {\r\n return _paramDecoded.split(PARAM_SEPARATOR);\r\n }\r\n};\r\n\r\nconst replaceParam = (path = '', paramKey, paramValue) => {\r\n const paramPattern = `${PARAM_PATTERN_IDENTIFIER}${paramKey}`;\r\n if (paramValue) {\r\n return path.replace(paramPattern, paramValue);\r\n }\r\n\r\n return path;\r\n};\r\n\r\nconst isValidPath = path => {\r\n const paramPatternPiece = `/${PARAM_PATTERN_IDENTIFIER}`;\r\n return path.indexOf(paramPatternPiece) < 0;\r\n};\r\n\r\nconst queryString = {\r\n getQueryFilters,\r\n};\r\n\r\nconst paramString = {\r\n isValidPath,\r\n parseParam,\r\n replaceParam,\r\n};\r\n\r\nconst urlUtil = { parse, queryString, paramString };\r\n\r\nexport default urlUtil;\r\n","export default function makeDeferred() {\r\n let reject,\r\n resolve,\r\n promise = new Promise(function(res, rej) {\r\n resolve = res;\r\n reject = rej;\r\n });\r\n return Object.freeze({ promise, resolve, reject });\r\n}\r\n","export default class Queue {\r\n constructor(limit) {\r\n this.limit = limit;\r\n this.size = 0;\r\n this.awaiting = null;\r\n }\r\n\r\n /**\r\n * Creates a new \"proxy\" function associated with the current execution queue\r\n * instance. When the returned function is invoked, the queue limit is checked\r\n * to make sure the limit of scheduled tasks is repected (throwing an\r\n * exception when the limit has been reached and before calling the original\r\n * function). The original function is only invoked after all the previously\r\n * scheduled tasks have finished executing (their returned promises have\r\n * resolved/rejected);\r\n *\r\n * @param {function} task The function whose execution will be associated\r\n * with the current Queue instance;\r\n * @returns {function} The \"proxy\" function bound to the current Queue\r\n * instance;\r\n */\r\n bind(task) {\r\n return bind(this, task);\r\n }\r\n\r\n bindSafe(task, onError) {\r\n const boundTask = bind(this, task);\r\n return async function safeTask(...args) {\r\n try {\r\n return await boundTask(...args);\r\n } catch (e) {\r\n onError(e);\r\n }\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Utils\r\n */\r\n\r\nfunction bind(queue, task) {\r\n const cleaner = clean.bind(null, queue);\r\n return async function boundTask(...args) {\r\n if (queue.size >= queue.limit) {\r\n throw new Error('Queue limit reached');\r\n }\r\n const promise = chain(queue.awaiting, task, args);\r\n queue.awaiting = promise.then(cleaner, cleaner);\r\n queue.size++;\r\n return promise;\r\n };\r\n}\r\n\r\nfunction clean(queue) {\r\n if (queue.size > 0 && --queue.size === 0) {\r\n queue.awaiting = null;\r\n }\r\n}\r\n\r\nasync function chain(prev, task, args) {\r\n await prev;\r\n return task(...args);\r\n}\r\n","/**\r\n * Constants\r\n */\r\n\r\nconst SEPARATOR = '/';\r\n\r\n/**\r\n * API\r\n */\r\n\r\n/**\r\n * Add values to a list hierarchically.\r\n * @ For example:\r\n * addToList([], 'a', 'b', 'c');\r\n * will add the following hierarchy to the list:\r\n * a > b > c\r\n * resulting in the following array:\r\n * [['a', [['b', ['c']]]]]\r\n * @param {Array} list The target list;\r\n * @param {...string} values The values to be hierarchically added to the list;\r\n * @returns {Array} Returns the provided list possibly updated with the given\r\n * values or null when a bad list (not an actual array) is provided\r\n */\r\nfunction addToList(list, ...values) {\r\n if (Array.isArray(list)) {\r\n if (values.length > 0) {\r\n addValuesToList(list, values);\r\n }\r\n return list;\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Iterates through the provided hierarchical list executing the callback\r\n * once for each leaf-node of the tree. The ancestors of the leaf-node being\r\n * visited are passed to the callback function along with the leaf-node in\r\n * the exact same order they appear on the tree (from root to leaf);\r\n * @ For example, if the hierachy `a > b > c` appears on the tree (\"a\" being\r\n * the root and \"c\" being the leaf) the callback function will be called as:\r\n * callback('a', 'b', 'c');\r\n * @param {Array} list The hierarchical list to be iterated\r\n * @param {function} callback The callback which will be exected once for\r\n * each leaf-node of the hierarchical list;\r\n * @returns {Array} Returns the provided list or null for bad arguments;\r\n */\r\nfunction forEach(list, callback) {\r\n if (Array.isArray(list)) {\r\n if (typeof callback === 'function') {\r\n forEachValue(list, callback);\r\n }\r\n return list;\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Retrieves an item from the given hierarchical list based on an index (number)\r\n * or a path (string).\r\n * @ For example:\r\n * getItem(list, '1/0/4')\r\n * will retrieve the fourth grandchild, from the first child of the second\r\n * element of the list;\r\n * @param {Array} list The source list;\r\n * @param {string|number} indexOrPath The index of the element inside list\r\n * (number) or the path to reach the desired element (string). The slash \"/\"\r\n * character is cosidered the path separator;\r\n */\r\nfunction getItem(list, indexOrPath) {\r\n if (Array.isArray(list)) {\r\n let subpath = null;\r\n let index = typeof indexOrPath === 'number' ? indexOrPath : -1;\r\n if (typeof indexOrPath === 'string') {\r\n const separator = indexOrPath.indexOf(SEPARATOR);\r\n if (separator > 0) {\r\n index = parseInt(indexOrPath.slice(0, separator), 10);\r\n if (separator + 1 < indexOrPath.length) {\r\n subpath = indexOrPath.slice(separator + 1, indexOrPath.length);\r\n }\r\n } else {\r\n index = parseInt(indexOrPath, 10);\r\n }\r\n }\r\n if (index >= 0 && index < list.length) {\r\n const item = list[index];\r\n if (isSublist(item)) {\r\n if (subpath !== null) {\r\n return getItem(item[1], subpath);\r\n }\r\n return item[0];\r\n }\r\n return item;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Pretty-print the provided hierarchical list;\r\n * @param {Array} list The source list;\r\n * @returns {string} The textual representation of the hierarchical list;\r\n */\r\nfunction print(list) {\r\n let text = '';\r\n if (Array.isArray(list)) {\r\n let prev = [];\r\n forEachValue(list, function(...args) {\r\n let prevLen = prev.length;\r\n for (let i = 0, l = args.length; i < l; ++i) {\r\n if (i < prevLen && args[i] === prev[i]) {\r\n continue;\r\n }\r\n text += ' '.repeat(i) + args[i] + '\\n';\r\n }\r\n prev = args;\r\n });\r\n }\r\n return text;\r\n}\r\n\r\n/**\r\n * Utils\r\n */\r\n\r\nfunction forEachValue(list, callback) {\r\n for (let i = 0, l = list.length; i < l; ++i) {\r\n let item = list[i];\r\n if (isSublist(item)) {\r\n if (item[1].length > 0) {\r\n forEachValue(item[1], callback.bind(null, item[0]));\r\n continue;\r\n }\r\n item = item[0];\r\n }\r\n callback(item);\r\n }\r\n}\r\n\r\nfunction addValuesToList(list, values) {\r\n let value = values.shift();\r\n let index = add(list, value);\r\n if (index >= 0) {\r\n if (values.length > 0) {\r\n let sublist = list[index];\r\n if (!isSublist(sublist)) {\r\n sublist = toSublist(value);\r\n list[index] = sublist;\r\n }\r\n return addValuesToList(sublist[1], values);\r\n }\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nfunction add(list, value) {\r\n let index = find(list, value);\r\n if (index === -2) {\r\n index = list.push(value) - 1;\r\n }\r\n return index;\r\n}\r\n\r\nfunction find(list, value) {\r\n if (typeof value === 'string') {\r\n for (let i = 0, l = list.length; i < l; ++i) {\r\n let item = list[i];\r\n if (item === value || (isSublist(item) && item[0] === value)) {\r\n return i;\r\n }\r\n }\r\n return -2;\r\n }\r\n return -1;\r\n}\r\n\r\nfunction isSublist(subject) {\r\n return (\r\n Array.isArray(subject) &&\r\n subject.length === 2 &&\r\n typeof subject[0] === 'string' &&\r\n Array.isArray(subject[1])\r\n );\r\n}\r\n\r\nfunction toSublist(value) {\r\n return [value + '', []];\r\n}\r\n\r\n/**\r\n * Exports\r\n */\r\n\r\nconst hierarchicalListUtils = { addToList, getItem, forEach, print };\r\nexport { addToList, getItem, forEach, print };\r\nexport default hierarchicalListUtils;\r\n","import makeDeferred from './makeDeferred';\r\n\r\n/**\r\n * Constants\r\n */\r\n\r\nconst TYPE = Symbol('Type');\r\nconst TASK = Symbol('Task');\r\nconst LIST = Symbol('List');\r\n\r\n/**\r\n * Public Methods\r\n */\r\n\r\n/**\r\n * Creates an instance of a task list\r\n * @returns {Object} A task list object\r\n */\r\nfunction createList() {\r\n return objectWithType(LIST, {\r\n head: null,\r\n named: Object.create(null),\r\n observers: [],\r\n });\r\n}\r\n\r\n/**\r\n * Checks if the given argument is a List instance\r\n * @param {any} subject The value to be tested\r\n * @returns {boolean} true if a valid List instance is given, false otherwise\r\n */\r\nfunction isList(subject) {\r\n return isOfType(LIST, subject);\r\n}\r\n\r\n/**\r\n * Creates an instance of a task\r\n * @param {Object} list The List instance related to this task\r\n * @param {Object} next The next Task instance to link to\r\n * @returns {Object} A task object\r\n */\r\nfunction createTask(list, next) {\r\n return objectWithType(TASK, {\r\n list: isList(list) ? list : null,\r\n next: isTask(next) ? next : null,\r\n failed: false,\r\n awaiting: null,\r\n progress: 0.0,\r\n });\r\n}\r\n\r\n/**\r\n * Checks if the given argument is a Task instance\r\n * @param {any} subject The value to be tested\r\n * @returns {boolean} true if a valid Task instance is given, false otherwise\r\n */\r\nfunction isTask(subject) {\r\n return isOfType(TASK, subject);\r\n}\r\n\r\n/**\r\n * Appends a new Task to the given List instance and notifies the list observers\r\n * @param {Object} list A List instance\r\n * @returns {Object} The new Task instance appended to the List or null if the\r\n * given List instanc is not valid\r\n */\r\nfunction increaseList(list) {\r\n if (isList(list)) {\r\n const task = createTask(list, list.head);\r\n list.head = task;\r\n notify(list, getOverallProgress(list));\r\n return task;\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Updates the internal progress value of the given Task instance and notifies\r\n * the observers of the associated list.\r\n * @param {Object} task The Task instance to be updated\r\n * @param {number} value A number between 0 (inclusive) and 1 (exclusive)\r\n * indicating the progress of the task;\r\n * @returns {void} Nothing is returned\r\n */\r\nfunction update(task, value) {\r\n if (isTask(task) && isValidProgress(value) && value < 1.0) {\r\n if (task.progress !== value) {\r\n task.progress = value;\r\n if (isList(task.list)) {\r\n notify(task.list, getOverallProgress(task.list));\r\n }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Sets a Task instance as finished (progress = 1.0), freezes it in order to\r\n * prevent further modifications and notifies the observers of the associated\r\n * list.\r\n * @param {Object} task The Task instance to be finalized\r\n * @returns {void} Nothing is returned\r\n */\r\nfunction finish(task) {\r\n if (isTask(task)) {\r\n task.progress = 1.0;\r\n task.awaiting = null;\r\n Object.freeze(task);\r\n if (isList(task.list)) {\r\n notify(task.list, getOverallProgress(task.list));\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Generate a summarized snapshot of the current status of the given task List\r\n * @param {Object} list The List instance to be scanned\r\n * @returns {Object} An obeject representing the summarized status of the list\r\n */\r\nfunction getOverallProgress(list) {\r\n const status = createStatus();\r\n if (isList(list)) {\r\n let task = list.head;\r\n while (isTask(task)) {\r\n status.total++;\r\n if (isValidProgress(task.progress)) {\r\n status.partial += task.progress;\r\n if (task.progress === 1.0 && task.failed) status.failures++;\r\n }\r\n task = task.next;\r\n }\r\n }\r\n if (status.total > 0) {\r\n status.progress = status.partial / status.total;\r\n }\r\n return Object.freeze(status);\r\n}\r\n\r\n/**\r\n * Adds a Task instance to the given list that waits on a given \"thenable\". When\r\n * the thenable resolves the \"finish\" method is called on the newly created\r\n * instance thus notifying the observers of the list.\r\n * @param {Object} list The List instance to which the new task will be added\r\n * @param {Object|Promise} thenable The thenable to be waited on\r\n * @returns {Object} A reference to the newly created Task;\r\n */\r\nfunction waitOn(list, thenable) {\r\n const task = increaseList(list);\r\n if (isTask(task)) {\r\n task.awaiting = Promise.resolve(thenable).then(\r\n function() {\r\n finish(task);\r\n },\r\n function() {\r\n task.failed = true;\r\n finish(task);\r\n }\r\n );\r\n return task;\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Adds a Task instance to the given list using a deferred (a Promise that can\r\n * be externally resolved) notifying the observers of the list.\r\n * @param {Object} list The List instance to which the new task will be added\r\n * @returns {Object} An object with references to the created deferred and task\r\n */\r\nfunction addDeferred(list) {\r\n const deferred = makeDeferred();\r\n const task = waitOn(list, deferred.promise);\r\n return Object.freeze({\r\n deferred,\r\n task,\r\n });\r\n}\r\n\r\n/**\r\n * Assigns a name to a specific task of the list\r\n * @param {Object} list The List instance whose task will be named\r\n * @param {Object} task The specified Task instance\r\n * @param {string} name The name of the task\r\n * @returns {boolean} Returns true on success, false otherwise\r\n */\r\nfunction setTaskName(list, task, name) {\r\n if (\r\n contains(list, task) &&\r\n list.named !== null &&\r\n typeof list.named === 'object' &&\r\n typeof name === 'string'\r\n ) {\r\n list.named[name] = task;\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * Retrieves a task by name\r\n * @param {Object} list The List instance whose task will be retrieved\r\n * @param {string} name The name of the task to be retrieved\r\n * @returns {Object} The Task instance or null if not found\r\n */\r\nfunction getTaskByName(list, name) {\r\n if (\r\n isList(list) &&\r\n list.named !== null &&\r\n typeof list.named === 'object' &&\r\n typeof name === 'string'\r\n ) {\r\n const task = list.named[name];\r\n if (isTask(task)) {\r\n return task;\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Adds an observer (callback function) to a given List instance\r\n * @param {Object} list The List instance to which the observer will be appended\r\n * @param {Function} observer The observer (function) that will be executed\r\n * every time a change happens within the list\r\n * @returns {boolean} Returns true on success and false otherewise\r\n */\r\nfunction addObserver(list, observer) {\r\n if (\r\n isList(list) &&\r\n Array.isArray(list.observers) &&\r\n typeof observer === 'function'\r\n ) {\r\n list.observers.push(observer);\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * Removes an observer (callback function) from a given List instance\r\n * @param {Object} list The instance List from which the observer will removed\r\n * @param {Function} observer The observer function to be removed\r\n * @returns {boolean} Returns true on success and false otherewise\r\n */\r\nfunction removeObserver(list, observer) {\r\n if (\r\n isList(list) &&\r\n Array.isArray(list.observers) &&\r\n list.observers.length > 0\r\n ) {\r\n const index = list.observers.indexOf(observer);\r\n if (index >= 0) {\r\n list.observers.splice(index, 1);\r\n return true;\r\n }\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * Utils\r\n */\r\n\r\nfunction createStatus() {\r\n return Object.seal({\r\n total: 0,\r\n partial: 0.0,\r\n progress: 0.0,\r\n failures: 0,\r\n });\r\n}\r\n\r\nfunction objectWithType(type, object) {\r\n return Object.seal(Object.defineProperty(object, TYPE, { value: type }));\r\n}\r\n\r\nfunction isOfType(type, subject) {\r\n return (\r\n subject !== null && typeof subject === 'object' && subject[TYPE] === type\r\n );\r\n}\r\n\r\nfunction isValidProgress(value) {\r\n return typeof value === 'number' && value >= 0.0 && value <= 1.0;\r\n}\r\n\r\nfunction contains(list, task) {\r\n if (isList(list) && isTask(task)) {\r\n let item = list.head;\r\n while (isTask(item)) {\r\n if (item === task) {\r\n return true;\r\n }\r\n item = item.next;\r\n }\r\n }\r\n return false;\r\n}\r\n\r\nfunction notify(list, data) {\r\n if (\r\n isList(list) &&\r\n Array.isArray(list.observers) &&\r\n list.observers.length > 0\r\n ) {\r\n list.observers.slice().forEach(function(observer) {\r\n if (typeof observer === 'function') {\r\n try {\r\n observer(data, list);\r\n } catch (e) {\r\n /* Oops! */\r\n }\r\n }\r\n });\r\n }\r\n}\r\n\r\n/**\r\n * Exports\r\n */\r\n\r\nconst progressTrackingUtils = {\r\n createList,\r\n isList,\r\n createTask,\r\n isTask,\r\n increaseList,\r\n update,\r\n finish,\r\n getOverallProgress,\r\n waitOn,\r\n addDeferred,\r\n setTaskName,\r\n getTaskByName,\r\n addObserver,\r\n removeObserver,\r\n};\r\n\r\nexport default progressTrackingUtils;\r\n","// Returns a function, that, as long as it continues to be invoked, will not\r\n// be triggered. The function will be called after it stops being called for\r\n// N milliseconds. If `immediate` is passed, trigger the function on the\r\n// leading edge, instead of the trailing.\r\nfunction debounce(func, wait, immediate) {\r\n var timeout;\r\n return function() {\r\n var context = this,\r\n args = arguments;\r\n var later = function() {\r\n timeout = null;\r\n if (!immediate) func.apply(context, args);\r\n };\r\n var callNow = immediate && !timeout;\r\n clearTimeout(timeout);\r\n timeout = setTimeout(later, wait);\r\n if (callNow) func.apply(context, args);\r\n };\r\n}\r\n\r\nexport default debounce;\r\n","function _round(value, decimals) {\r\n return Number(value.toFixed(decimals));\r\n}\r\n\r\nexport default _round;\r\n","import ObjectPath from './objectPath';\r\nimport absoluteUrl from './absoluteUrl';\r\nimport guid from './guid';\r\nimport sortBy from './sortBy.js';\r\nimport writeScript from './writeScript.js';\r\nimport b64toBlob from './b64toBlob.js';\r\n//import loadAndCacheDerivedDisplaySets from './loadAndCacheDerivedDisplaySets.js';\r\nimport urlUtil from './urlUtil';\r\nimport makeDeferred from './makeDeferred';\r\nimport makeCancelable from './makeCancelable';\r\nimport hotkeys from './hotkeys';\r\nimport Queue from './Queue';\r\nimport isDicomUid from './isDicomUid';\r\nimport formatDate from './formatDate';\r\nimport formatPN from './formatPN';\r\nimport resolveObjectPath from './resolveObjectPath';\r\nimport hierarchicalListUtils from './hierarchicalListUtils';\r\nimport progressTrackingUtils from './progressTrackingUtils';\r\nimport isLowPriorityModality from './isLowPriorityModality';\r\nimport { isImage } from './isImage';\r\nimport isDisplaySetReconstructable from './isDisplaySetReconstructable';\r\nimport sortInstancesByPosition from './sortInstancesByPosition';\r\nimport imageIdToURI from './imageIdToURI';\r\nimport debounce from './debounce';\r\nimport roundNumber from './roundNumber';\r\nimport downloadCSVReport from './downloadCSVReport';\r\nimport isEqualWithin from './isEqualWithin';\r\nimport {\r\n sortStudy,\r\n sortStudySeries,\r\n sortStudyInstances,\r\n sortingCriteria,\r\n seriesSortCriteria,\r\n} from './sortStudy';\r\n\r\n// Commented out unused functionality.\r\n// Need to implement new mechanism for derived displaySets using the displaySetManager.\r\n\r\nconst utils = {\r\n guid,\r\n ObjectPath,\r\n absoluteUrl,\r\n sortBy,\r\n sortBySeriesDate: sortStudySeries,\r\n sortStudy,\r\n sortStudySeries,\r\n sortStudyInstances,\r\n sortingCriteria,\r\n seriesSortCriteria,\r\n writeScript,\r\n formatDate,\r\n formatPN,\r\n b64toBlob,\r\n urlUtil,\r\n imageIdToURI,\r\n //loadAndCacheDerivedDisplaySets,\r\n makeDeferred,\r\n makeCancelable,\r\n hotkeys,\r\n Queue,\r\n isDicomUid,\r\n isEqualWithin,\r\n resolveObjectPath,\r\n hierarchicalListUtils,\r\n progressTrackingUtils,\r\n isLowPriorityModality,\r\n isImage,\r\n isDisplaySetReconstructable,\r\n debounce,\r\n roundNumber,\r\n downloadCSVReport,\r\n};\r\n\r\nexport {\r\n guid,\r\n ObjectPath,\r\n absoluteUrl,\r\n sortBy,\r\n formatDate,\r\n writeScript,\r\n b64toBlob,\r\n urlUtil,\r\n //loadAndCacheDerivedDisplaySets,\r\n makeDeferred,\r\n makeCancelable,\r\n hotkeys,\r\n Queue,\r\n isDicomUid,\r\n isEqualWithin,\r\n resolveObjectPath,\r\n hierarchicalListUtils,\r\n progressTrackingUtils,\r\n isLowPriorityModality,\r\n isImage,\r\n isDisplaySetReconstructable,\r\n sortInstancesByPosition,\r\n imageIdToURI,\r\n debounce,\r\n roundNumber,\r\n downloadCSVReport,\r\n};\r\n\r\nexport default utils;\r\n","/* jshint -W060 */\r\nimport absoluteUrl from './absoluteUrl';\r\n\r\nexport default function writeScript(fileName, callback) {\r\n const script = document.createElement('script');\r\n script.src = absoluteUrl(fileName);\r\n script.onload = () => {\r\n if (typeof callback === 'function') {\r\n callback(script);\r\n }\r\n };\r\n\r\n document.body.appendChild(script);\r\n}\r\n","import moment from 'moment';\r\n\r\n/**\r\n * Format date\r\n *\r\n * @param {string} date Date to be formatted\r\n * @param {string} format Desired date format\r\n * @returns {string} Formatted date\r\n */\r\nexport default (date, format = 'DD-MMM-YYYY') => {\r\n return moment(date).format(format);\r\n};\r\n","/**\r\n * Formats a patient name for display purposes\r\n */\r\nexport default function formatPN(name) {\r\n if (!name) {\r\n return;\r\n }\r\n const nameToUse = name.Alphabetic ?? name;\r\n //CORREÇÃO DEV-2327\r\n try {\r\n // Convert the first ^ to a ', '. String.replace() only affects\r\n // the first appearance of the character.\r\n const commaBetweenFirstAndLast = nameToUse.replace('^', ', ');\r\n // Replace any remaining '^' characters with spaces\r\n const cleaned = commaBetweenFirstAndLast.replace(/\\^/g, ' ');\r\n // Trim any extraneous whitespace\r\n return cleaned.trim();\r\n } catch (error) {\r\n console.log(error);\r\n return \"\";\r\n }\r\n}\r\n","export default function makeCancelable(thenable) {\r\n let isCanceled = false;\r\n const promise = Promise.resolve(thenable).then(\r\n function(result) {\r\n if (isCanceled) throw Object.freeze({ isCanceled });\r\n return result;\r\n },\r\n function(error) {\r\n if (isCanceled) throw Object.freeze({ isCanceled, error });\r\n throw error;\r\n }\r\n );\r\n return Object.assign(Object.create(promise), {\r\n then: promise.then.bind(promise),\r\n cancel() {\r\n isCanceled = true;\r\n },\r\n });\r\n}\r\n","export default function isDicomUid(subject) {\r\n const regex = /^\\d+(?:\\.\\d+)*$/;\r\n return typeof subject === 'string' && regex.test(subject.trim());\r\n}\r\n","/**\r\n * returns equal if the two arrays are identical within the\r\n * given tolerance.\r\n *\r\n * @param v1 - The first array of values\r\n * @param v2 - The second array of values.\r\n * @param tolerance - The acceptable tolerance, the default is 0.00001\r\n *\r\n * @returns True if the two values are within the tolerance levels.\r\n */\r\nexport default function isEqualWithin(\r\n v1: number[] | Float32Array,\r\n v2: number[] | Float32Array,\r\n tolerance = 1e-5\r\n): boolean {\r\n if (v1.length !== v2.length) {\r\n return false;\r\n }\r\n\r\n for (let i = 0; i < v1.length; i++) {\r\n if (Math.abs(v1[i] - v2[i]) > tolerance) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n","export default function resolveObjectPath(root, path, defaultValue) {\r\n if (root !== null && typeof root === 'object' && typeof path === 'string') {\r\n let value,\r\n separator = path.indexOf('.');\r\n if (separator >= 0) {\r\n return resolveObjectPath(\r\n root[path.slice(0, separator)],\r\n path.slice(separator + 1, path.length),\r\n defaultValue\r\n );\r\n }\r\n value = root[path];\r\n return value === undefined && defaultValue !== undefined\r\n ? defaultValue\r\n : value;\r\n }\r\n}\r\n","import { DicomMetadataStore } from '../services/DicomMetadataStore/DicomMetadataStore';\r\n\r\nexport default function downloadCSVReport(measurementData) {\r\n if (measurementData.length === 0) {\r\n // Prevent download of report with no measurements.\r\n return;\r\n }\r\n\r\n const columns = [\r\n 'Patient ID',\r\n 'Patient Name',\r\n 'StudyInstanceUID',\r\n 'SeriesInstanceUID',\r\n 'SOPInstanceUID',\r\n 'Label',\r\n ];\r\n\r\n const reportMap = {};\r\n measurementData.forEach(measurement => {\r\n const {\r\n referenceStudyUID,\r\n referenceSeriesUID,\r\n getReport,\r\n uid,\r\n } = measurement;\r\n\r\n if (!getReport) {\r\n console.warn('Measurement does not have a getReport function');\r\n return;\r\n }\r\n\r\n const seriesMetadata = DicomMetadataStore.getSeries(\r\n referenceStudyUID,\r\n referenceSeriesUID\r\n );\r\n\r\n const commonRowItems = _getCommonRowItems(measurement, seriesMetadata);\r\n const report = getReport(measurement);\r\n\r\n reportMap[uid] = {\r\n report,\r\n commonRowItems,\r\n };\r\n });\r\n\r\n // get columns names inside the report from each measurement and\r\n // add them to the rows array (this way we can add columns for any custom\r\n // measurements that may be added in the future)\r\n Object.keys(reportMap).forEach(id => {\r\n const { report } = reportMap[id];\r\n report.columns.forEach(column => {\r\n if (!columns.includes(column)) {\r\n columns.push(column);\r\n }\r\n });\r\n });\r\n\r\n const results = _mapReportsToRowArray(reportMap, columns);\r\n\r\n let csvContent =\r\n 'data:text/csv;charset=utf-8,' +\r\n results.map(res => res.join(',')).join('\\n');\r\n\r\n _createAndDownloadFile(csvContent);\r\n}\r\n\r\nfunction _mapReportsToRowArray(reportMap, columns) {\r\n const results = [columns];\r\n Object.keys(reportMap).forEach(id => {\r\n const { report, commonRowItems } = reportMap[id];\r\n const row = [];\r\n // For commonRowItems, find the correct index and add the value to the\r\n // correct row in the results array\r\n Object.keys(commonRowItems).forEach(key => {\r\n const index = columns.indexOf(key);\r\n const value = commonRowItems[key];\r\n row[index] = value;\r\n });\r\n\r\n // For each annotation data, find the correct index and add the value to the\r\n // correct row in the results array\r\n report.columns.forEach((column, index) => {\r\n const colIndex = columns.indexOf(column);\r\n const value = report.values[index];\r\n row[colIndex] = value;\r\n });\r\n\r\n results.push(row);\r\n });\r\n\r\n return results;\r\n}\r\n\r\nfunction _getCommonRowItems(measurement, seriesMetadata) {\r\n const firstInstance = seriesMetadata.instances[0];\r\n\r\n return {\r\n 'Patient ID': firstInstance.PatientID, // Patient ID\r\n 'Patient Name': firstInstance.PatientName.Alphabetic, // PatientName\r\n StudyInstanceUID: measurement.referenceStudyUID, // StudyInstanceUID\r\n SeriesInstanceUID: measurement.referenceSeriesUID, // SeriesInstanceUID\r\n SOPInstanceUID: measurement.SOPInstanceUID, // SOPInstanceUID\r\n Label: measurement.label || '', // Label\r\n };\r\n}\r\n\r\nfunction _createAndDownloadFile(csvContent) {\r\n const encodedUri = encodeURI(csvContent);\r\n\r\n const link = document.createElement('a');\r\n link.setAttribute('href', encodedUri);\r\n link.setAttribute('download', 'MeasurementReport.csv');\r\n document.body.appendChild(link);\r\n link.click();\r\n}\r\n","import toNumber from './toNumber';\r\nimport sortInstancesByPosition from './sortInstancesByPosition';\r\n\r\n// TODO: Is 10% a reasonable spacingTolerance for spacing?\r\nconst spacingTolerance = 0.2;\r\nconst iopTolerance = 0.01;\r\n\r\n/**\r\n * Checks if a series is reconstructable to a 3D volume.\r\n *\r\n * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.\r\n */\r\nexport default function isDisplaySetReconstructable(instances) {\r\n if (!instances.length) {\r\n return { value: false };\r\n }\r\n\r\n const firstInstance = instances[0];\r\n\r\n const Modality = firstInstance.Modality;\r\n const isMultiframe = firstInstance.NumberOfFrames > 1;\r\n\r\n if (!constructableModalities.includes(Modality)) {\r\n return { value: false };\r\n }\r\n\r\n // Can't reconstruct if we only have one image.\r\n if (!isMultiframe && instances.length === 1) {\r\n return { value: false };\r\n }\r\n\r\n // Can't reconstruct if all instances don't have the ImagePositionPatient.\r\n if (!instances.every(instance => !!instance.ImagePositionPatient)) {\r\n return { value: false };\r\n }\r\n\r\n const sortedInstances = sortInstancesByPosition(instances);\r\n\r\n if (isMultiframe) {\r\n return processMultiframe(sortedInstances[0]);\r\n } else {\r\n return processSingleframe(sortedInstances);\r\n }\r\n}\r\n\r\nfunction processMultiframe(multiFrameInstance) {\r\n const {\r\n PerFrameFunctionalGroupsSequence,\r\n SharedFunctionalGroupsSequence,\r\n } = multiFrameInstance;\r\n\r\n // If we don't have the PixelMeasuresSequence, then the pixel spacing and\r\n // slice thickness isn't specified or is changing and we can't reconstruct\r\n // the dataset.\r\n if (\r\n !SharedFunctionalGroupsSequence ||\r\n !SharedFunctionalGroupsSequence[0].PixelMeasuresSequence\r\n ) {\r\n return { value: false };\r\n }\r\n\r\n // Check that the orientation is either shared or with the allowed\r\n // difference amount\r\n const {\r\n PlaneOrientationSequence: sharedOrientation,\r\n } = SharedFunctionalGroupsSequence;\r\n\r\n if (!sharedOrientation) {\r\n const {\r\n PlaneOrientationSequence: firstOrientation,\r\n } = PerFrameFunctionalGroupsSequence[0];\r\n\r\n if (!firstOrientation) {\r\n console.log('No orientation information');\r\n return { value: false };\r\n }\r\n // TODO - check orientation consistency\r\n }\r\n\r\n const frame0 = PerFrameFunctionalGroupsSequence[0];\r\n const firstPosition =\r\n frame0.PlanePositionSequence || frame0.CTPositionSequence;\r\n if (!firstPosition) {\r\n console.log('No image position information, not reconstructable');\r\n return { value: false };\r\n }\r\n // TODO - check spacing consistency\r\n\r\n return { value: true };\r\n}\r\n\r\nfunction processSingleframe(instances) {\r\n const firstImage = instances[0];\r\n const firstImageRows = toNumber(firstImage.Rows);\r\n const firstImageColumns = toNumber(firstImage.Columns);\r\n const firstImageSamplesPerPixel = toNumber(firstImage.SamplesPerPixel);\r\n const firstImageOrientationPatient = toNumber(\r\n firstImage.ImageOrientationPatient\r\n );\r\n const firstImagePositionPatient = toNumber(firstImage.ImagePositionPatient);\r\n\r\n // Can't reconstruct if we:\r\n // -- Have a different dimensions within a displaySet.\r\n // -- Have a different number of components within a displaySet.\r\n // -- Have different orientations within a displaySet.\r\n for (let i = 1; i < instances.length; i++) {\r\n const instance = instances[i];\r\n const {\r\n Rows,\r\n Columns,\r\n SamplesPerPixel,\r\n ImageOrientationPatient,\r\n } = instance;\r\n\r\n const imageOrientationPatient = toNumber(ImageOrientationPatient);\r\n\r\n if (\r\n Rows !== firstImageRows ||\r\n Columns !== firstImageColumns ||\r\n SamplesPerPixel !== firstImageSamplesPerPixel ||\r\n !_isSameOrientation(imageOrientationPatient, firstImageOrientationPatient)\r\n ) {\r\n return { value: false };\r\n }\r\n }\r\n\r\n let missingFrames = 0;\r\n\r\n // Check if frame spacing is approximately equal within a spacingTolerance.\r\n // If spacing is on a uniform grid but we are missing frames,\r\n // Allow reconstruction, but pass back the number of missing frames.\r\n if (instances.length > 2) {\r\n const lastIpp = toNumber(\r\n instances[instances.length - 1].ImagePositionPatient\r\n );\r\n\r\n // We can't reconstruct if we are missing ImagePositionPatient values\r\n if (!firstImagePositionPatient || !lastIpp) {\r\n return { value: false };\r\n }\r\n\r\n const averageSpacingBetweenFrames =\r\n _getPerpendicularDistance(firstImagePositionPatient, lastIpp) /\r\n (instances.length - 1);\r\n\r\n let previousImagePositionPatient = firstImagePositionPatient;\r\n\r\n for (let i = 1; i < instances.length; i++) {\r\n const instance = instances[i];\r\n // Todo: get metadata from OHIF.MetadataProvider\r\n const imagePositionPatient = toNumber(instance.ImagePositionPatient);\r\n\r\n const spacingBetweenFrames = _getPerpendicularDistance(\r\n imagePositionPatient,\r\n previousImagePositionPatient\r\n );\r\n const spacingIssue = _getSpacingIssue(\r\n spacingBetweenFrames,\r\n averageSpacingBetweenFrames\r\n );\r\n\r\n if (spacingIssue) {\r\n const issue = spacingIssue.issue;\r\n\r\n if (issue === reconstructionIssues.MISSING_FRAMES) {\r\n missingFrames += spacingIssue.missingFrames;\r\n } else if (issue === reconstructionIssues.IRREGULAR_SPACING) {\r\n return { value: false };\r\n }\r\n }\r\n\r\n previousImagePositionPatient = imagePositionPatient;\r\n }\r\n }\r\n\r\n return { value: true, missingFrames };\r\n}\r\n\r\nfunction _isSameOrientation(iop1, iop2) {\r\n if (iop1 === undefined || !iop2 === undefined) {\r\n return;\r\n }\r\n\r\n return (\r\n Math.abs(iop1[0] - iop2[0]) < iopTolerance &&\r\n Math.abs(iop1[1] - iop2[1]) < iopTolerance &&\r\n Math.abs(iop1[2] - iop2[2]) < iopTolerance\r\n );\r\n}\r\n\r\n/**\r\n * Checks for spacing issues.\r\n *\r\n * @param {number} spacing The spacing between two frames.\r\n * @param {number} averageSpacing The average spacing between all frames.\r\n *\r\n * @returns {Object} An object containing the issue and extra information if necessary.\r\n */\r\nfunction _getSpacingIssue(spacing, averageSpacing) {\r\n const equalWithinTolerance =\r\n Math.abs(spacing - averageSpacing) < averageSpacing * spacingTolerance;\r\n\r\n if (equalWithinTolerance) {\r\n return;\r\n }\r\n\r\n const multipleOfAverageSpacing = spacing / averageSpacing;\r\n\r\n const numberOfSpacings = Math.round(multipleOfAverageSpacing);\r\n\r\n const errorForEachSpacing =\r\n Math.abs(spacing - numberOfSpacings * averageSpacing) / numberOfSpacings;\r\n\r\n if (errorForEachSpacing < spacingTolerance * averageSpacing) {\r\n return {\r\n issue: reconstructionIssues.MISSING_FRAMES,\r\n missingFrames: numberOfSpacings - 1,\r\n };\r\n }\r\n\r\n return { issue: reconstructionIssues.IRREGULAR_SPACING };\r\n}\r\n\r\nfunction _getPerpendicularDistance(a, b) {\r\n return Math.sqrt(\r\n Math.pow(a[0] - b[0], 2) +\r\n Math.pow(a[1] - b[1], 2) +\r\n Math.pow(a[2] - b[2], 2)\r\n );\r\n}\r\n\r\nconst constructableModalities = ['MR', 'CT', 'PT', 'NM'];\r\nconst reconstructionIssues = {\r\n MISSING_FRAMES: 'missingframes',\r\n IRREGULAR_SPACING: 'irregularspacing',\r\n};\r\n","import { vec3 } from 'gl-matrix';\r\n\r\n/**\r\n * Given an array of imageIds, sort them based on their imagePositionPatient, and\r\n * also returns the spacing between images and the origin of the reference image\r\n *\r\n * @param imageIds - array of imageIds\r\n * @param scanAxisNormal - [x, y, z] array or gl-matrix vec3\r\n *\r\n * @returns The sortedImageIds, zSpacing, and origin of the first image in the series.\r\n */\r\nexport default function sortInstances(instances: Array) {\r\n // Return if only one instance e.g., multiframe\r\n if (instances.length <= 1) {\r\n return instances;\r\n }\r\n\r\n const {\r\n ImagePositionPatient: referenceImagePositionPatient,\r\n ImageOrientationPatient,\r\n } = instances[Math.floor(instances.length / 2)]; // this prevents getting scout image as test image\r\n\r\n if (!referenceImagePositionPatient || !ImageOrientationPatient) {\r\n return instances;\r\n }\r\n\r\n const rowCosineVec = vec3.fromValues(\r\n ImageOrientationPatient[0],\r\n ImageOrientationPatient[1],\r\n ImageOrientationPatient[2]\r\n );\r\n const colCosineVec = vec3.fromValues(\r\n ImageOrientationPatient[3],\r\n ImageOrientationPatient[4],\r\n ImageOrientationPatient[5]\r\n );\r\n\r\n const scanAxisNormal = vec3.cross(vec3.create(), rowCosineVec, colCosineVec);\r\n\r\n const refIppVec = vec3.set(\r\n vec3.create(),\r\n referenceImagePositionPatient[0],\r\n referenceImagePositionPatient[1],\r\n referenceImagePositionPatient[2]\r\n );\r\n\r\n const distanceInstancePairs = instances.map(instance => {\r\n const imagePositionPatient = instance.ImagePositionPatient;\r\n\r\n const positionVector = vec3.create();\r\n\r\n vec3.sub(\r\n positionVector,\r\n referenceImagePositionPatient,\r\n imagePositionPatient\r\n );\r\n\r\n const distance = vec3.dot(positionVector, scanAxisNormal);\r\n\r\n return {\r\n distance,\r\n instance,\r\n };\r\n });\r\n\r\n distanceInstancePairs.sort((a, b) => b.distance - a.distance);\r\n\r\n const sortedInstances = distanceInstancePairs.map(a => a.instance);\r\n\r\n return sortedInstances;\r\n}\r\n","import { sopClassDictionary } from './sopClassDictionary';\r\n\r\nconst imagesTypes = [\r\n sopClassDictionary.ComputedRadiographyImageStorage,\r\n sopClassDictionary.DigitalXRayImageStorageForPresentation,\r\n sopClassDictionary.DigitalXRayImageStorageForProcessing,\r\n sopClassDictionary.DigitalMammographyXRayImageStorageForPresentation,\r\n sopClassDictionary.DigitalMammographyXRayImageStorageForProcessing,\r\n sopClassDictionary.DigitalIntraOralXRayImageStorageForPresentation,\r\n sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing,\r\n sopClassDictionary.CTImageStorage,\r\n sopClassDictionary.EnhancedCTImageStorage,\r\n sopClassDictionary.LegacyConvertedEnhancedCTImageStorage,\r\n sopClassDictionary.UltrasoundMultiframeImageStorage,\r\n sopClassDictionary.EnhancedUSVolumeStorage,\r\n sopClassDictionary.MRImageStorage,\r\n sopClassDictionary.EnhancedMRImageStorage,\r\n sopClassDictionary.EnhancedMRColorImageStorage,\r\n sopClassDictionary.LegacyConvertedEnhancedMRImageStorage,\r\n sopClassDictionary.UltrasoundImageStorage,\r\n sopClassDictionary.SecondaryCaptureImageStorage,\r\n sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage,\r\n sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage,\r\n sopClassDictionary.MultiframeGrayscaleWordSecondaryCaptureImageStorage,\r\n sopClassDictionary.MultiframeTrueColorSecondaryCaptureImageStorage,\r\n sopClassDictionary.XRayAngiographicImageStorage,\r\n sopClassDictionary.EnhancedXAImageStorage,\r\n sopClassDictionary.XRayRadiofluoroscopicImageStorage,\r\n sopClassDictionary.EnhancedXRFImageStorage,\r\n sopClassDictionary.XRay3DAngiographicImageStorage,\r\n sopClassDictionary.XRay3DCraniofacialImageStorage,\r\n sopClassDictionary.BreastTomosynthesisImageStorage,\r\n sopClassDictionary.BreastProjectionXRayImageStorageForPresentation,\r\n sopClassDictionary.BreastProjectionXRayImageStorageForProcessing,\r\n sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation,\r\n sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForProcessing,\r\n sopClassDictionary.NuclearMedicineImageStorage,\r\n sopClassDictionary.VLEndoscopicImageStorage,\r\n sopClassDictionary.VideoEndoscopicImageStorage,\r\n sopClassDictionary.VLMicroscopicImageStorage,\r\n sopClassDictionary.VideoMicroscopicImageStorage,\r\n sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage,\r\n sopClassDictionary.VLPhotographicImageStorage,\r\n sopClassDictionary.VideoPhotographicImageStorage,\r\n sopClassDictionary.OphthalmicPhotography8BitImageStorage,\r\n sopClassDictionary.OphthalmicPhotography16BitImageStorage,\r\n sopClassDictionary.OphthalmicTomographyImageStorage,\r\n sopClassDictionary.VLWholeSlideMicroscopyImageStorage,\r\n sopClassDictionary.PositronEmissionTomographyImageStorage,\r\n sopClassDictionary.EnhancedPETImageStorage,\r\n sopClassDictionary.LegacyConvertedEnhancedPETImageStorage,\r\n sopClassDictionary.RTImageStorage,\r\n];\r\n\r\n/**\r\n * Checks whether dicom files with specified SOP Class UID have image data\r\n * @param {string} SOPClassUID - SOP Class UID to be checked\r\n * @returns {boolean} - true if it has image data\r\n */\r\nexport const isImage = SOPClassUID => {\r\n if (!SOPClassUID) return false;\r\n return imagesTypes.indexOf(SOPClassUID) !== -1;\r\n};\r\n","const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR', 'SR', 'RTSTRUCT']);\r\n\r\nexport default function isLowPriorityModality(Modality) {\r\n return LOW_PRIORITY_MODALITIES.includes(Modality);\r\n}\r\n","// TODO: Deprecate since we have the same thing in dcmjs?\r\nexport const sopClassDictionary = {\r\n ComputedRadiographyImageStorage: '1.2.840.10008.5.1.4.1.1.1',\r\n DigitalXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.1.1',\r\n DigitalXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.1.1.1',\r\n DigitalMammographyXRayImageStorageForPresentation:\r\n '1.2.840.10008.5.1.4.1.1.1.2',\r\n DigitalMammographyXRayImageStorageForProcessing:\r\n '1.2.840.10008.5.1.4.1.1.1.2.1',\r\n DigitalIntraOralXRayImageStorageForPresentation:\r\n '1.2.840.10008.5.1.4.1.1.1.3',\r\n DigitalIntraOralXRayImageStorageForProcessing:\r\n '1.2.840.10008.5.1.4.1.1.1.3.1',\r\n CTImageStorage: '1.2.840.10008.5.1.4.1.1.2',\r\n EnhancedCTImageStorage: '1.2.840.10008.5.1.4.1.1.2.1',\r\n LegacyConvertedEnhancedCTImageStorage: '1.2.840.10008.5.1.4.1.1.2.2',\r\n UltrasoundMultiframeImageStorage: '1.2.840.10008.5.1.4.1.1.3.1',\r\n MRImageStorage: '1.2.840.10008.5.1.4.1.1.4',\r\n EnhancedMRImageStorage: '1.2.840.10008.5.1.4.1.1.4.1',\r\n MRSpectroscopyStorage: '1.2.840.10008.5.1.4.1.1.4.2',\r\n EnhancedMRColorImageStorage: '1.2.840.10008.5.1.4.1.1.4.3',\r\n LegacyConvertedEnhancedMRImageStorage: '1.2.840.10008.5.1.4.1.1.4.4',\r\n UltrasoundImageStorage: '1.2.840.10008.5.1.4.1.1.6.1',\r\n EnhancedUSVolumeStorage: '1.2.840.10008.5.1.4.1.1.6.2',\r\n SecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7',\r\n MultiframeSingleBitSecondaryCaptureImageStorage:\r\n '1.2.840.10008.5.1.4.1.1.7.1',\r\n MultiframeGrayscaleByteSecondaryCaptureImageStorage:\r\n '1.2.840.10008.5.1.4.1.1.7.2',\r\n MultiframeGrayscaleWordSecondaryCaptureImageStorage:\r\n '1.2.840.10008.5.1.4.1.1.7.3',\r\n MultiframeTrueColorSecondaryCaptureImageStorage:\r\n '1.2.840.10008.5.1.4.1.1.7.4',\r\n Sop12LeadECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.1',\r\n GeneralECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.2',\r\n AmbulatoryECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.3',\r\n HemodynamicWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.2.1',\r\n CardiacElectrophysiologyWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.3.1',\r\n BasicVoiceAudioWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.4.1',\r\n GeneralAudioWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.4.2',\r\n ArterialPulseWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.5.1',\r\n RespiratoryWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.6.1',\r\n GrayscaleSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.1',\r\n ColorSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.2',\r\n PseudoColorSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.3',\r\n BlendingSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.4',\r\n XAXRFGrayscaleSoftcopyPresentationStateStorage:\r\n '1.2.840.10008.5.1.4.1.1.11.5',\r\n XRayAngiographicImageStorage: '1.2.840.10008.5.1.4.1.1.12.1',\r\n EnhancedXAImageStorage: '1.2.840.10008.5.1.4.1.1.12.1.1',\r\n XRayRadiofluoroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.12.2',\r\n EnhancedXRFImageStorage: '1.2.840.10008.5.1.4.1.1.12.2.1',\r\n XRay3DAngiographicImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.1',\r\n XRay3DCraniofacialImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.2',\r\n BreastTomosynthesisImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.3',\r\n BreastProjectionXRayImageStorageForPresentation:\r\n '1.2.840.10008.5.1.4.1.1.13.1.4',\r\n BreastProjectionXRayImageStorageForProcessing:\r\n '1.2.840.10008.5.1.4.1.1.13.1.5',\r\n IntravascularOpticalCoherenceTomographyImageStorageForPresentation:\r\n '1.2.840.10008.5.1.4.1.1.14.1',\r\n IntravascularOpticalCoherenceTomographyImageStorageForProcessing:\r\n '1.2.840.10008.5.1.4.1.1.14.2',\r\n NuclearMedicineImageStorage: '1.2.840.10008.5.1.4.1.1.20',\r\n RawDataStorage: '1.2.840.10008.5.1.4.1.1.66',\r\n SpatialRegistrationStorage: '1.2.840.10008.5.1.4.1.1.66.1',\r\n SpatialFiducialsStorage: '1.2.840.10008.5.1.4.1.1.66.2',\r\n DeformableSpatialRegistrationStorage: '1.2.840.10008.5.1.4.1.1.66.3',\r\n SegmentationStorage: '1.2.840.10008.5.1.4.1.1.66.4',\r\n SurfaceSegmentationStorage: '1.2.840.10008.5.1.4.1.1.66.5',\r\n RealWorldValueMappingStorage: '1.2.840.10008.5.1.4.1.1.67',\r\n SurfaceScanMeshStorage: '1.2.840.10008.5.1.4.1.1.68.1',\r\n SurfaceScanPointCloudStorage: '1.2.840.10008.5.1.4.1.1.68.2',\r\n VLEndoscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.1',\r\n VideoEndoscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.1.1',\r\n VLMicroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.2',\r\n VideoMicroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.2.1',\r\n VLSlideCoordinatesMicroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.3',\r\n VLPhotographicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.4',\r\n VideoPhotographicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.4.1',\r\n OphthalmicPhotography8BitImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.1',\r\n OphthalmicPhotography16BitImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.2',\r\n StereometricRelationshipStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.3',\r\n OphthalmicTomographyImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.4',\r\n VLWholeSlideMicroscopyImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.6',\r\n LensometryMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.1',\r\n AutorefractionMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.2',\r\n KeratometryMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.3',\r\n SubjectiveRefractionMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.4',\r\n VisualAcuityMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.5',\r\n SpectaclePrescriptionReportStorage: '1.2.840.10008.5.1.4.1.1.78.6',\r\n OphthalmicAxialMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.7',\r\n IntraocularLensCalculationsStorage: '1.2.840.10008.5.1.4.1.1.78.8',\r\n MacularGridThicknessandVolumeReport: '1.2.840.10008.5.1.4.1.1.79.1',\r\n OphthalmicVisualFieldStaticPerimetryMeasurementsStorage:\r\n '1.2.840.10008.5.1.4.1.1.80.1',\r\n OphthalmicThicknessMapStorage: '1.2.840.10008.5.1.4.1.1.81.1',\r\n CornealTopographyMapStorage: '1.2.840.10008.5.1.4.1.1.82.1',\r\n BasicTextSR: '1.2.840.10008.5.1.4.1.1.88.11',\r\n EnhancedSR: '1.2.840.10008.5.1.4.1.1.88.22',\r\n ComprehensiveSR: '1.2.840.10008.5.1.4.1.1.88.33',\r\n Comprehensive3DSR: '1.2.840.10008.5.1.4.1.1.88.34',\r\n ProcedureLog: '1.2.840.10008.5.1.4.1.1.88.40',\r\n MammographyCADSR: '1.2.840.10008.5.1.4.1.1.88.50',\r\n KeyObjectSelection: '1.2.840.10008.5.1.4.1.1.88.59',\r\n ChestCADSR: '1.2.840.10008.5.1.4.1.1.88.65',\r\n XRayRadiationDoseSR: '1.2.840.10008.5.1.4.1.1.88.67',\r\n RadiopharmaceuticalRadiationDoseSR: '1.2.840.10008.5.1.4.1.1.88.68',\r\n ColonCADSR: '1.2.840.10008.5.1.4.1.1.88.69',\r\n ImplantationPlanSRDocumentStorage: '1.2.840.10008.5.1.4.1.1.88.70',\r\n EncapsulatedPDFStorage: '1.2.840.10008.5.1.4.1.1.104.1',\r\n EncapsulatedCDAStorage: '1.2.840.10008.5.1.4.1.1.104.2',\r\n PositronEmissionTomographyImageStorage: '1.2.840.10008.5.1.4.1.1.128',\r\n EnhancedPETImageStorage: '1.2.840.10008.5.1.4.1.1.130',\r\n LegacyConvertedEnhancedPETImageStorage: '1.2.840.10008.5.1.4.1.1.128.1',\r\n BasicStructuredDisplayStorage: '1.2.840.10008.5.1.4.1.1.131',\r\n RTImageStorage: '1.2.840.10008.5.1.4.1.1.481.1',\r\n RTDoseStorage: '1.2.840.10008.5.1.4.1.1.481.2',\r\n RTStructureSetStorage: '1.2.840.10008.5.1.4.1.1.481.3',\r\n RTBeamsTreatmentRecordStorage: '1.2.840.10008.5.1.4.1.1.481.4',\r\n RTPlanStorage: '1.2.840.10008.5.1.4.1.1.481.5',\r\n RTBrachyTreatmentRecordStorage: '1.2.840.10008.5.1.4.1.1.481.6',\r\n RTTreatmentSummaryRecordStorage: '1.2.840.10008.5.1.4.1.1.481.7',\r\n RTIonPlanStorage: '1.2.840.10008.5.1.4.1.1.481.8',\r\n RTIonBeamsTreatmentRecordStorage: '1.2.840.10008.5.1.4.1.1.481.9',\r\n RTBeamsDeliveryInstructionStorage: '1.2.840.10008.5.1.4.34.7',\r\n GenericImplantTemplateStorage: '1.2.840.10008.5.1.4.43.1',\r\n ImplantAssemblyTemplateStorage: '1.2.840.10008.5.1.4.44.1',\r\n ImplantTemplateGroupStorage: '1.2.840.10008.5.1.4.45.1',\r\n};\r\n\r\nexport default sopClassDictionary;\r\n","// Return the array sorting function for its object's properties\r\nexport default function sortBy() {\r\n var fields = [].slice.call(arguments),\r\n n_fields = fields.length;\r\n\r\n return function(A, B) {\r\n var a, b, field, key, reverse, result, i;\r\n\r\n for (i = 0; i < n_fields; i++) {\r\n result = 0;\r\n field = fields[i];\r\n\r\n key = typeof field === 'string' ? field : field.name;\r\n\r\n a = A[key];\r\n b = B[key];\r\n\r\n if (typeof field.primer !== 'undefined') {\r\n a = field.primer(a);\r\n b = field.primer(b);\r\n }\r\n\r\n reverse = field.reverse ? -1 : 1;\r\n\r\n if (a < b) {\r\n result = reverse * -1;\r\n }\r\n\r\n if (a > b) {\r\n result = reverse * 1;\r\n }\r\n\r\n if (result !== 0) {\r\n break;\r\n }\r\n }\r\n\r\n return result;\r\n };\r\n}\r\n","import isLowPriorityModality from './isLowPriorityModality';\r\n\r\nconst compareSeriesDateTime = (a, b) => {\r\n const seriesDateA = Date.parse(\r\n `${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}`\r\n );\r\n const seriesDateB = Date.parse(\r\n `${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}`\r\n );\r\n return seriesDateA - seriesDateB;\r\n};\r\n\r\nconst defaultSeriesSort = (a, b) => {\r\n const seriesNumberA = a.SeriesNumber ?? a.seriesNumber;\r\n const seriesNumberB = b.SeriesNumber ?? b.seriesNumber;\r\n if (seriesNumberA === seriesNumberB) return compareSeriesDateTime(a, b);\r\n return seriesNumberA - seriesNumberB;\r\n};\r\n\r\n/**\r\n * Series sorting criteria: series considered low priority are moved to the end\r\n * of the list and series number is used to break ties\r\n * @param {Object} firstSeries\r\n * @param {Object} secondSeries\r\n */\r\nfunction seriesInfoSortingCriteria(firstSeries, secondSeries) {\r\n const aLowPriority = isLowPriorityModality(\r\n firstSeries.Modality ?? firstSeries.modality\r\n );\r\n const bLowPriority = isLowPriorityModality(\r\n secondSeries.Modality ?? secondSeries.modality\r\n );\r\n\r\n if (aLowPriority) {\r\n return bLowPriority ? defaultSeriesSort(secondSeries, firstSeries) : 1;\r\n } else if (bLowPriority) {\r\n return -1;\r\n }\r\n\r\n return defaultSeriesSort(firstSeries, secondSeries);\r\n}\r\n\r\nconst seriesSortCriteria = {\r\n default: seriesInfoSortingCriteria,\r\n seriesInfoSortingCriteria,\r\n};\r\n\r\nconst instancesSortCriteria = {\r\n default: (a, b) => parseInt(a.InstanceNumber) - parseInt(b.InstanceNumber),\r\n};\r\n\r\nconst sortingCriteria = {\r\n seriesSortCriteria,\r\n instancesSortCriteria,\r\n};\r\n\r\n/**\r\n * Sorts given series (given param is modified)\r\n * The default criteria is based on series number in ascending order.\r\n *\r\n * @param {Array} series List of series\r\n * @param {function} seriesSortingCriteria method for sorting\r\n * @returns {Array} sorted series object\r\n */\r\nconst sortStudySeries = (\r\n series,\r\n seriesSortingCriteria = seriesSortCriteria.default,\r\n sortFunction = null\r\n) => {\r\n if (typeof sortFunction === 'function') return sortFunction(series);\r\n else return series.sort(seriesSortingCriteria);\r\n};\r\n\r\n/**\r\n * Sorts given instancesList (given param is modified)\r\n * The default criteria is based on instance number in ascending order.\r\n *\r\n * @param {Array} instancesList List of series\r\n * @param {function} instancesSortingCriteria method for sorting\r\n * @returns {Array} sorted instancesList object\r\n */\r\nconst sortStudyInstances = (\r\n instancesList,\r\n instancesSortingCriteria = instancesSortCriteria.default\r\n) => {\r\n return instancesList.sort(instancesSortingCriteria);\r\n};\r\n\r\n/**\r\n * Sorts the series and instances (by default) inside a study instance based on sortingCriteria (given param is modified)\r\n * The default criteria is based on series and instance numbers in ascending order.\r\n *\r\n * @param {Object} study The study instance\r\n * @param {boolean} [deepSort = true] to sort instance also\r\n * @param {function} [seriesSortingCriteria = seriesSortCriteria.default] method for sorting series\r\n * @param {function} [instancesSortingCriteria = instancesSortCriteria.default] method for sorting instances\r\n * @returns {Object} sorted study object\r\n */\r\nexport default function sortStudy(\r\n study,\r\n deepSort = true,\r\n seriesSortingCriteria = seriesSortCriteria.default,\r\n instancesSortingCriteria = instancesSortCriteria.default\r\n) {\r\n if (!study || !study.series) {\r\n throw new Error('Insufficient study data was provided to sortStudy');\r\n }\r\n\r\n sortStudySeries(study.series, seriesSortingCriteria);\r\n\r\n if (deepSort) {\r\n study.series.forEach(series => {\r\n sortStudyInstances(series.instances, instancesSortingCriteria);\r\n });\r\n }\r\n\r\n return study;\r\n}\r\n\r\nexport {\r\n sortStudy,\r\n sortStudySeries,\r\n sortStudyInstances,\r\n sortingCriteria,\r\n seriesSortCriteria,\r\n};\r\n","/**\r\n * Returns the values as an array of javascript numbers\r\n *\r\n * @param val - The javascript object for the specified element in the metadata\r\n * @returns {*}\r\n */\r\nexport default function toNumber(val) {\r\n if (Array.isArray(val)) {\r\n return val.map(v => (v !== undefined ? Number(v) : v));\r\n } else {\r\n return val !== undefined ? Number(val) : val;\r\n }\r\n}\r\n","import React from 'react';\r\nimport PropTypes from 'prop-types';\r\nimport Typography from '../Typography';\r\nimport Icon from '../Icon';\r\n\r\nconst ContextMenu = ({ items, ...props }) => {\r\n if (!items) {\r\n console.warn('No items for context menu');\r\n return null;\r\n }\r\n return (\r\n
\r\n );\r\n };\r\n\r\n let result = dragableItem();\r\n\r\n if (showOverlay) {\r\n result = withOverlay(result);\r\n }\r\n\r\n if (typeof onClickOutside === 'function') {\r\n result = (\r\n \r\n {result}\r\n \r\n );\r\n }\r\n\r\n return result;\r\n });\r\n\r\n /**\r\n * Update the last dialog position to be used as the new default position.\r\n *\r\n * @returns void\r\n */\r\n const _updateLastDialogPosition = dialogId => {\r\n const draggableItemBounds = document\r\n .querySelector(`#draggableItem-${dialogId}`)\r\n .getBoundingClientRect();\r\n setLastDialogPosition({\r\n x: draggableItemBounds.x,\r\n y: draggableItemBounds.y,\r\n });\r\n };\r\n\r\n const onKeyDownHandler = event => {\r\n if (event.key === 'Escape') {\r\n dismissAll();\r\n }\r\n };\r\n\r\n const validCallback = callback => callback && typeof callback === 'function';\r\n\r\n return (\r\n \r\n {!isEmpty() && (\r\n
\r\n {renderDialogs()}\r\n
\r\n )}\r\n {children}\r\n \r\n );\r\n};\r\n\r\n/**\r\n *\r\n * High Order Component to use the dialog methods through a Class Component\r\n *\r\n */\r\nexport const withDialog = Component => {\r\n return function WrappedComponent(props) {\r\n const { create, dismiss, dismissAll, isEmpty } = useDialog();\r\n return (\r\n \r\n );\r\n };\r\n};\r\n\r\nDialogProvider.defaultProps = {\r\n service: null,\r\n};\r\n\r\nDialogProvider.propTypes = {\r\n children: PropTypes.oneOfType([\r\n PropTypes.arrayOf(PropTypes.node),\r\n PropTypes.node,\r\n PropTypes.func,\r\n ]).isRequired,\r\n service: PropTypes.shape({\r\n setServiceImplementation: PropTypes.func,\r\n }),\r\n};\r\n\r\nexport default DialogProvider;\r\n\r\nfunction OutsideAlerter(props) {\r\n const wrapperRef = useRef(null);\r\n\r\n useEffect(() => {\r\n /**\r\n * Alert if clicked on outside of element\r\n */\r\n function handleInteractionOutside(event) {\r\n if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {\r\n props.onClickOutside();\r\n }\r\n }\r\n\r\n // Bind the event listener\r\n document.addEventListener('mousedown', handleInteractionOutside);\r\n document.addEventListener('touchstart', handleInteractionOutside);\r\n return () => {\r\n // Unbind the event listener on clean up\r\n document.removeEventListener('mousedown', handleInteractionOutside);\r\n document.removeEventListener('touchstart', handleInteractionOutside);\r\n };\r\n }, [wrapperRef]);\r\n\r\n return
{props.children}
;\r\n}\r\n","/**\r\n * Use invariant() to assert state which your program assumes to be true.\r\n *\r\n * Provide sprintf-style format (only %s is supported) and arguments\r\n * to provide information about what broke and what you were\r\n * expecting.\r\n *\r\n * The invariant message will be stripped in production, but the invariant\r\n * will remain to ensure logic does not differ in production.\r\n */\nfunction invariant(condition, format) {\n for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n\n if (process.env.NODE_ENV !== 'production') {\n if (format === undefined) {\n throw new Error('invariant requires an error message argument');\n }\n }\n\n if (!condition) {\n var error;\n\n if (format === undefined) {\n error = new Error('Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.');\n } else {\n var argIndex = 0;\n error = new Error(format.replace(/%s/g, function () {\n return args[argIndex++];\n }));\n error.name = 'Invariant Violation';\n }\n\n error.framesToPop = 1; // we don't care about invariant's own frame\n\n throw error;\n }\n}\n\nexport { invariant };\n//# sourceMappingURL=invariant.esm.js.map\n","export var INIT_COORDS = 'dnd-core/INIT_COORDS';\nexport var BEGIN_DRAG = 'dnd-core/BEGIN_DRAG';\nexport var PUBLISH_DRAG_SOURCE = 'dnd-core/PUBLISH_DRAG_SOURCE';\nexport var HOVER = 'dnd-core/HOVER';\nexport var DROP = 'dnd-core/DROP';\nexport var END_DRAG = 'dnd-core/END_DRAG';","import { INIT_COORDS } from '../types';\nexport function setClientOffset(clientOffset, sourceClientOffset) {\n return {\n type: INIT_COORDS,\n payload: {\n sourceClientOffset: sourceClientOffset || null,\n clientOffset: clientOffset || null\n }\n };\n}","function _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n// cheap lodash replacements\n\n/**\n * drop-in replacement for _.get\n * @param obj\n * @param path\n * @param defaultValue\n */\nexport function get(obj, path, defaultValue) {\n return path.split('.').reduce(function (a, c) {\n return a && a[c] ? a[c] : defaultValue || null;\n }, obj);\n}\n/**\n * drop-in replacement for _.without\n */\n\nexport function without(items, item) {\n return items.filter(function (i) {\n return i !== item;\n });\n}\n/**\n * drop-in replacement for _.isString\n * @param input\n */\n\nexport function isString(input) {\n return typeof input === 'string';\n}\n/**\n * drop-in replacement for _.isString\n * @param input\n */\n\nexport function isObject(input) {\n return _typeof(input) === 'object';\n}\n/**\n * repalcement for _.xor\n * @param itemsA\n * @param itemsB\n */\n\nexport function xor(itemsA, itemsB) {\n var map = new Map();\n\n var insertItem = function insertItem(item) {\n map.set(item, map.has(item) ? map.get(item) + 1 : 1);\n };\n\n itemsA.forEach(insertItem);\n itemsB.forEach(insertItem);\n var result = [];\n map.forEach(function (count, key) {\n if (count === 1) {\n result.push(key);\n }\n });\n return result;\n}\n/**\n * replacement for _.intersection\n * @param itemsA\n * @param itemsB\n */\n\nexport function intersection(itemsA, itemsB) {\n return itemsA.filter(function (t) {\n return itemsB.indexOf(t) > -1;\n });\n}","import { invariant } from '@react-dnd/invariant';\nimport { setClientOffset } from './local/setClientOffset';\nimport { isObject } from '../../utils/js_utils';\nimport { BEGIN_DRAG, INIT_COORDS } from './types';\nvar ResetCoordinatesAction = {\n type: INIT_COORDS,\n payload: {\n clientOffset: null,\n sourceClientOffset: null\n }\n};\nexport function createBeginDrag(manager) {\n return function beginDrag() {\n var sourceIds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n publishSource: true\n };\n var _options$publishSourc = options.publishSource,\n publishSource = _options$publishSourc === void 0 ? true : _options$publishSourc,\n clientOffset = options.clientOffset,\n getSourceClientOffset = options.getSourceClientOffset;\n var monitor = manager.getMonitor();\n var registry = manager.getRegistry(); // Initialize the coordinates using the client offset\n\n manager.dispatch(setClientOffset(clientOffset));\n verifyInvariants(sourceIds, monitor, registry); // Get the draggable source\n\n var sourceId = getDraggableSource(sourceIds, monitor);\n\n if (sourceId === null) {\n manager.dispatch(ResetCoordinatesAction);\n return;\n } // Get the source client offset\n\n\n var sourceClientOffset = null;\n\n if (clientOffset) {\n if (!getSourceClientOffset) {\n throw new Error('getSourceClientOffset must be defined');\n }\n\n verifyGetSourceClientOffsetIsFunction(getSourceClientOffset);\n sourceClientOffset = getSourceClientOffset(sourceId);\n } // Initialize the full coordinates\n\n\n manager.dispatch(setClientOffset(clientOffset, sourceClientOffset));\n var source = registry.getSource(sourceId);\n var item = source.beginDrag(monitor, sourceId); // If source.beginDrag returns null, this is an indicator to cancel the drag\n\n if (item == null) {\n return undefined;\n }\n\n verifyItemIsObject(item);\n registry.pinSource(sourceId);\n var itemType = registry.getSourceType(sourceId);\n return {\n type: BEGIN_DRAG,\n payload: {\n itemType: itemType,\n item: item,\n sourceId: sourceId,\n clientOffset: clientOffset || null,\n sourceClientOffset: sourceClientOffset || null,\n isSourcePublic: !!publishSource\n }\n };\n };\n}\n\nfunction verifyInvariants(sourceIds, monitor, registry) {\n invariant(!monitor.isDragging(), 'Cannot call beginDrag while dragging.');\n sourceIds.forEach(function (sourceId) {\n invariant(registry.getSource(sourceId), 'Expected sourceIds to be registered.');\n });\n}\n\nfunction verifyGetSourceClientOffsetIsFunction(getSourceClientOffset) {\n invariant(typeof getSourceClientOffset === 'function', 'When clientOffset is provided, getSourceClientOffset must be a function.');\n}\n\nfunction verifyItemIsObject(item) {\n invariant(isObject(item), 'Item must be an object.');\n}\n\nfunction getDraggableSource(sourceIds, monitor) {\n var sourceId = null;\n\n for (var i = sourceIds.length - 1; i >= 0; i--) {\n if (monitor.canDragSource(sourceIds[i])) {\n sourceId = sourceIds[i];\n break;\n }\n }\n\n return sourceId;\n}","import { PUBLISH_DRAG_SOURCE } from './types';\nexport function createPublishDragSource(manager) {\n return function publishDragSource() {\n var monitor = manager.getMonitor();\n\n if (monitor.isDragging()) {\n return {\n type: PUBLISH_DRAG_SOURCE\n };\n }\n };\n}","export function matchesType(targetType, draggedItemType) {\n if (draggedItemType === null) {\n return targetType === null;\n }\n\n return Array.isArray(targetType) ? targetType.some(function (t) {\n return t === draggedItemType;\n }) : targetType === draggedItemType;\n}","import { invariant } from '@react-dnd/invariant';\nimport { matchesType } from '../../utils/matchesType';\nimport { HOVER } from './types';\nexport function createHover(manager) {\n return function hover(targetIdsArg) {\n var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},\n clientOffset = _ref.clientOffset;\n\n verifyTargetIdsIsArray(targetIdsArg);\n var targetIds = targetIdsArg.slice(0);\n var monitor = manager.getMonitor();\n var registry = manager.getRegistry();\n checkInvariants(targetIds, monitor, registry);\n var draggedItemType = monitor.getItemType();\n removeNonMatchingTargetIds(targetIds, registry, draggedItemType);\n hoverAllTargets(targetIds, monitor, registry);\n return {\n type: HOVER,\n payload: {\n targetIds: targetIds,\n clientOffset: clientOffset || null\n }\n };\n };\n}\n\nfunction verifyTargetIdsIsArray(targetIdsArg) {\n invariant(Array.isArray(targetIdsArg), 'Expected targetIds to be an array.');\n}\n\nfunction checkInvariants(targetIds, monitor, registry) {\n invariant(monitor.isDragging(), 'Cannot call hover while not dragging.');\n invariant(!monitor.didDrop(), 'Cannot call hover after drop.');\n\n for (var i = 0; i < targetIds.length; i++) {\n var targetId = targetIds[i];\n invariant(targetIds.lastIndexOf(targetId) === i, 'Expected targetIds to be unique in the passed array.');\n var target = registry.getTarget(targetId);\n invariant(target, 'Expected targetIds to be registered.');\n }\n}\n\nfunction removeNonMatchingTargetIds(targetIds, registry, draggedItemType) {\n // Remove those targetIds that don't match the targetType. This\n // fixes shallow isOver which would only be non-shallow because of\n // non-matching targets.\n for (var i = targetIds.length - 1; i >= 0; i--) {\n var targetId = targetIds[i];\n var targetType = registry.getTargetType(targetId);\n\n if (!matchesType(targetType, draggedItemType)) {\n targetIds.splice(i, 1);\n }\n }\n}\n\nfunction hoverAllTargets(targetIds, monitor, registry) {\n // Finally call hover on all matching targets.\n targetIds.forEach(function (targetId) {\n var target = registry.getTarget(targetId);\n target.hover(monitor, targetId);\n });\n}","function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nimport { invariant } from '@react-dnd/invariant';\nimport { DROP } from './types';\nimport { isObject } from '../../utils/js_utils';\nexport function createDrop(manager) {\n return function drop() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n var monitor = manager.getMonitor();\n var registry = manager.getRegistry();\n verifyInvariants(monitor);\n var targetIds = getDroppableTargets(monitor); // Multiple actions are dispatched here, which is why this doesn't return an action\n\n targetIds.forEach(function (targetId, index) {\n var dropResult = determineDropResult(targetId, index, registry, monitor);\n var action = {\n type: DROP,\n payload: {\n dropResult: _objectSpread(_objectSpread({}, options), dropResult)\n }\n };\n manager.dispatch(action);\n });\n };\n}\n\nfunction verifyInvariants(monitor) {\n invariant(monitor.isDragging(), 'Cannot call drop while not dragging.');\n invariant(!monitor.didDrop(), 'Cannot call drop twice during one drag operation.');\n}\n\nfunction determineDropResult(targetId, index, registry, monitor) {\n var target = registry.getTarget(targetId);\n var dropResult = target ? target.drop(monitor, targetId) : undefined;\n verifyDropResultType(dropResult);\n\n if (typeof dropResult === 'undefined') {\n dropResult = index === 0 ? {} : monitor.getDropResult();\n }\n\n return dropResult;\n}\n\nfunction verifyDropResultType(dropResult) {\n invariant(typeof dropResult === 'undefined' || isObject(dropResult), 'Drop result must either be an object or undefined.');\n}\n\nfunction getDroppableTargets(monitor) {\n var targetIds = monitor.getTargetIds().filter(monitor.canDropOnTarget, monitor);\n targetIds.reverse();\n return targetIds;\n}","import { invariant } from '@react-dnd/invariant';\nimport { END_DRAG } from './types';\nexport function createEndDrag(manager) {\n return function endDrag() {\n var monitor = manager.getMonitor();\n var registry = manager.getRegistry();\n verifyIsDragging(monitor);\n var sourceId = monitor.getSourceId();\n\n if (sourceId != null) {\n var source = registry.getSource(sourceId, true);\n source.endDrag(monitor, sourceId);\n registry.unpinSource();\n }\n\n return {\n type: END_DRAG\n };\n };\n}\n\nfunction verifyIsDragging(monitor) {\n invariant(monitor.isDragging(), 'Cannot call endDrag while not dragging.');\n}","function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nimport { createDragDropActions } from '../actions/dragDrop';\nexport var DragDropManagerImpl = /*#__PURE__*/function () {\n function DragDropManagerImpl(store, monitor) {\n var _this = this;\n\n _classCallCheck(this, DragDropManagerImpl);\n\n this.isSetUp = false;\n\n this.handleRefCountChange = function () {\n var shouldSetUp = _this.store.getState().refCount > 0;\n\n if (_this.backend) {\n if (shouldSetUp && !_this.isSetUp) {\n _this.backend.setup();\n\n _this.isSetUp = true;\n } else if (!shouldSetUp && _this.isSetUp) {\n _this.backend.teardown();\n\n _this.isSetUp = false;\n }\n }\n };\n\n this.store = store;\n this.monitor = monitor;\n store.subscribe(this.handleRefCountChange);\n }\n\n _createClass(DragDropManagerImpl, [{\n key: \"receiveBackend\",\n value: function receiveBackend(backend) {\n this.backend = backend;\n }\n }, {\n key: \"getMonitor\",\n value: function getMonitor() {\n return this.monitor;\n }\n }, {\n key: \"getBackend\",\n value: function getBackend() {\n return this.backend;\n }\n }, {\n key: \"getRegistry\",\n value: function getRegistry() {\n return this.monitor.registry;\n }\n }, {\n key: \"getActions\",\n value: function getActions() {\n /* eslint-disable-next-line @typescript-eslint/no-this-alias */\n var manager = this;\n var dispatch = this.store.dispatch;\n\n function bindActionCreator(actionCreator) {\n return function () {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n var action = actionCreator.apply(manager, args);\n\n if (typeof action !== 'undefined') {\n dispatch(action);\n }\n };\n }\n\n var actions = createDragDropActions(this);\n return Object.keys(actions).reduce(function (boundActions, key) {\n var action = actions[key];\n boundActions[key] = bindActionCreator(action);\n return boundActions;\n }, {});\n }\n }, {\n key: \"dispatch\",\n value: function dispatch(action) {\n this.store.dispatch(action);\n }\n }]);\n\n return DragDropManagerImpl;\n}();","import { createBeginDrag } from './beginDrag';\nimport { createPublishDragSource } from './publishDragSource';\nimport { createHover } from './hover';\nimport { createDrop } from './drop';\nimport { createEndDrag } from './endDrag';\nexport * from './types';\nexport function createDragDropActions(manager) {\n return {\n beginDrag: createBeginDrag(manager),\n publishDragSource: createPublishDragSource(manager),\n hover: createHover(manager),\n drop: createDrop(manager),\n endDrag: createEndDrag(manager)\n };\n}","import _objectSpread from '@babel/runtime/helpers/esm/objectSpread2';\n\n/**\n * Adapted from React: https://github.com/facebook/react/blob/master/packages/shared/formatProdErrorMessage.js\n *\n * Do not require this module directly! Use normal throw error calls. These messages will be replaced with error codes\n * during build.\n * @param {number} code\n */\nfunction formatProdErrorMessage(code) {\n return \"Minified Redux error #\" + code + \"; visit https://redux.js.org/Errors?code=\" + code + \" for the full message or \" + 'use the non-minified dev environment for full errors. ';\n}\n\n// Inlined version of the `symbol-observable` polyfill\nvar $$observable = (function () {\n return typeof Symbol === 'function' && Symbol.observable || '@@observable';\n})();\n\n/**\n * These are private action types reserved by Redux.\n * For any unknown actions, you must return the current state.\n * If the current state is undefined, you must return the initial state.\n * Do not reference these action types directly in your code.\n */\nvar randomString = function randomString() {\n return Math.random().toString(36).substring(7).split('').join('.');\n};\n\nvar ActionTypes = {\n INIT: \"@@redux/INIT\" + randomString(),\n REPLACE: \"@@redux/REPLACE\" + randomString(),\n PROBE_UNKNOWN_ACTION: function PROBE_UNKNOWN_ACTION() {\n return \"@@redux/PROBE_UNKNOWN_ACTION\" + randomString();\n }\n};\n\n/**\n * @param {any} obj The object to inspect.\n * @returns {boolean} True if the argument appears to be a plain object.\n */\nfunction isPlainObject(obj) {\n if (typeof obj !== 'object' || obj === null) return false;\n var proto = obj;\n\n while (Object.getPrototypeOf(proto) !== null) {\n proto = Object.getPrototypeOf(proto);\n }\n\n return Object.getPrototypeOf(obj) === proto;\n}\n\n// Inlined / shortened version of `kindOf` from https://github.com/jonschlinkert/kind-of\nfunction miniKindOf(val) {\n if (val === void 0) return 'undefined';\n if (val === null) return 'null';\n var type = typeof val;\n\n switch (type) {\n case 'boolean':\n case 'string':\n case 'number':\n case 'symbol':\n case 'function':\n {\n return type;\n }\n }\n\n if (Array.isArray(val)) return 'array';\n if (isDate(val)) return 'date';\n if (isError(val)) return 'error';\n var constructorName = ctorName(val);\n\n switch (constructorName) {\n case 'Symbol':\n case 'Promise':\n case 'WeakMap':\n case 'WeakSet':\n case 'Map':\n case 'Set':\n return constructorName;\n } // other\n\n\n return type.slice(8, -1).toLowerCase().replace(/\\s/g, '');\n}\n\nfunction ctorName(val) {\n return typeof val.constructor === 'function' ? val.constructor.name : null;\n}\n\nfunction isError(val) {\n return val instanceof Error || typeof val.message === 'string' && val.constructor && typeof val.constructor.stackTraceLimit === 'number';\n}\n\nfunction isDate(val) {\n if (val instanceof Date) return true;\n return typeof val.toDateString === 'function' && typeof val.getDate === 'function' && typeof val.setDate === 'function';\n}\n\nfunction kindOf(val) {\n var typeOfVal = typeof val;\n\n if (process.env.NODE_ENV !== 'production') {\n typeOfVal = miniKindOf(val);\n }\n\n return typeOfVal;\n}\n\n/**\n * @deprecated\n *\n * **We recommend using the `configureStore` method\n * of the `@reduxjs/toolkit` package**, which replaces `createStore`.\n *\n * Redux Toolkit is our recommended approach for writing Redux logic today,\n * including store setup, reducers, data fetching, and more.\n *\n * **For more details, please read this Redux docs page:**\n * **https://redux.js.org/introduction/why-rtk-is-redux-today**\n *\n * `configureStore` from Redux Toolkit is an improved version of `createStore` that\n * simplifies setup and helps avoid common bugs.\n *\n * You should not be using the `redux` core package by itself today, except for learning purposes.\n * The `createStore` method from the core `redux` package will not be removed, but we encourage\n * all users to migrate to using Redux Toolkit for all Redux code.\n *\n * If you want to use `createStore` without this visual deprecation warning, use\n * the `legacy_createStore` import instead:\n *\n * `import { legacy_createStore as createStore} from 'redux'`\n *\n */\n\nfunction createStore(reducer, preloadedState, enhancer) {\n var _ref2;\n\n if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(0) : 'It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.');\n }\n\n if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {\n enhancer = preloadedState;\n preloadedState = undefined;\n }\n\n if (typeof enhancer !== 'undefined') {\n if (typeof enhancer !== 'function') {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(1) : \"Expected the enhancer to be a function. Instead, received: '\" + kindOf(enhancer) + \"'\");\n }\n\n return enhancer(createStore)(reducer, preloadedState);\n }\n\n if (typeof reducer !== 'function') {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(2) : \"Expected the root reducer to be a function. Instead, received: '\" + kindOf(reducer) + \"'\");\n }\n\n var currentReducer = reducer;\n var currentState = preloadedState;\n var currentListeners = [];\n var nextListeners = currentListeners;\n var isDispatching = false;\n /**\n * This makes a shallow copy of currentListeners so we can use\n * nextListeners as a temporary list while dispatching.\n *\n * This prevents any bugs around consumers calling\n * subscribe/unsubscribe in the middle of a dispatch.\n */\n\n function ensureCanMutateNextListeners() {\n if (nextListeners === currentListeners) {\n nextListeners = currentListeners.slice();\n }\n }\n /**\n * Reads the state tree managed by the store.\n *\n * @returns {any} The current state tree of your application.\n */\n\n\n function getState() {\n if (isDispatching) {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(3) : 'You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.');\n }\n\n return currentState;\n }\n /**\n * Adds a change listener. It will be called any time an action is dispatched,\n * and some part of the state tree may potentially have changed. You may then\n * call `getState()` to read the current state tree inside the callback.\n *\n * You may call `dispatch()` from a change listener, with the following\n * caveats:\n *\n * 1. The subscriptions are snapshotted just before every `dispatch()` call.\n * If you subscribe or unsubscribe while the listeners are being invoked, this\n * will not have any effect on the `dispatch()` that is currently in progress.\n * However, the next `dispatch()` call, whether nested or not, will use a more\n * recent snapshot of the subscription list.\n *\n * 2. The listener should not expect to see all state changes, as the state\n * might have been updated multiple times during a nested `dispatch()` before\n * the listener is called. It is, however, guaranteed that all subscribers\n * registered before the `dispatch()` started will be called with the latest\n * state by the time it exits.\n *\n * @param {Function} listener A callback to be invoked on every dispatch.\n * @returns {Function} A function to remove this change listener.\n */\n\n\n function subscribe(listener) {\n if (typeof listener !== 'function') {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(4) : \"Expected the listener to be a function. Instead, received: '\" + kindOf(listener) + \"'\");\n }\n\n if (isDispatching) {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(5) : 'You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api/store#subscribelistener for more details.');\n }\n\n var isSubscribed = true;\n ensureCanMutateNextListeners();\n nextListeners.push(listener);\n return function unsubscribe() {\n if (!isSubscribed) {\n return;\n }\n\n if (isDispatching) {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(6) : 'You may not unsubscribe from a store listener while the reducer is executing. ' + 'See https://redux.js.org/api/store#subscribelistener for more details.');\n }\n\n isSubscribed = false;\n ensureCanMutateNextListeners();\n var index = nextListeners.indexOf(listener);\n nextListeners.splice(index, 1);\n currentListeners = null;\n };\n }\n /**\n * Dispatches an action. It is the only way to trigger a state change.\n *\n * The `reducer` function, used to create the store, will be called with the\n * current state tree and the given `action`. Its return value will\n * be considered the **next** state of the tree, and the change listeners\n * will be notified.\n *\n * The base implementation only supports plain object actions. If you want to\n * dispatch a Promise, an Observable, a thunk, or something else, you need to\n * wrap your store creating function into the corresponding middleware. For\n * example, see the documentation for the `redux-thunk` package. Even the\n * middleware will eventually dispatch plain object actions using this method.\n *\n * @param {Object} action A plain object representing “what changed”. It is\n * a good idea to keep actions serializable so you can record and replay user\n * sessions, or use the time travelling `redux-devtools`. An action must have\n * a `type` property which may not be `undefined`. It is a good idea to use\n * string constants for action types.\n *\n * @returns {Object} For convenience, the same action object you dispatched.\n *\n * Note that, if you use a custom middleware, it may wrap `dispatch()` to\n * return something else (for example, a Promise you can await).\n */\n\n\n function dispatch(action) {\n if (!isPlainObject(action)) {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(7) : \"Actions must be plain objects. Instead, the actual type was: '\" + kindOf(action) + \"'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.\");\n }\n\n if (typeof action.type === 'undefined') {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(8) : 'Actions may not have an undefined \"type\" property. You may have misspelled an action type string constant.');\n }\n\n if (isDispatching) {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(9) : 'Reducers may not dispatch actions.');\n }\n\n try {\n isDispatching = true;\n currentState = currentReducer(currentState, action);\n } finally {\n isDispatching = false;\n }\n\n var listeners = currentListeners = nextListeners;\n\n for (var i = 0; i < listeners.length; i++) {\n var listener = listeners[i];\n listener();\n }\n\n return action;\n }\n /**\n * Replaces the reducer currently used by the store to calculate the state.\n *\n * You might need this if your app implements code splitting and you want to\n * load some of the reducers dynamically. You might also need this if you\n * implement a hot reloading mechanism for Redux.\n *\n * @param {Function} nextReducer The reducer for the store to use instead.\n * @returns {void}\n */\n\n\n function replaceReducer(nextReducer) {\n if (typeof nextReducer !== 'function') {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(10) : \"Expected the nextReducer to be a function. Instead, received: '\" + kindOf(nextReducer));\n }\n\n currentReducer = nextReducer; // This action has a similiar effect to ActionTypes.INIT.\n // Any reducers that existed in both the new and old rootReducer\n // will receive the previous state. This effectively populates\n // the new state tree with any relevant data from the old one.\n\n dispatch({\n type: ActionTypes.REPLACE\n });\n }\n /**\n * Interoperability point for observable/reactive libraries.\n * @returns {observable} A minimal observable of state changes.\n * For more information, see the observable proposal:\n * https://github.com/tc39/proposal-observable\n */\n\n\n function observable() {\n var _ref;\n\n var outerSubscribe = subscribe;\n return _ref = {\n /**\n * The minimal observable subscription method.\n * @param {Object} observer Any object that can be used as an observer.\n * The observer object should have a `next` method.\n * @returns {subscription} An object with an `unsubscribe` method that can\n * be used to unsubscribe the observable from the store, and prevent further\n * emission of values from the observable.\n */\n subscribe: function subscribe(observer) {\n if (typeof observer !== 'object' || observer === null) {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(11) : \"Expected the observer to be an object. Instead, received: '\" + kindOf(observer) + \"'\");\n }\n\n function observeState() {\n if (observer.next) {\n observer.next(getState());\n }\n }\n\n observeState();\n var unsubscribe = outerSubscribe(observeState);\n return {\n unsubscribe: unsubscribe\n };\n }\n }, _ref[$$observable] = function () {\n return this;\n }, _ref;\n } // When a store is created, an \"INIT\" action is dispatched so that every\n // reducer returns their initial state. This effectively populates\n // the initial state tree.\n\n\n dispatch({\n type: ActionTypes.INIT\n });\n return _ref2 = {\n dispatch: dispatch,\n subscribe: subscribe,\n getState: getState,\n replaceReducer: replaceReducer\n }, _ref2[$$observable] = observable, _ref2;\n}\n/**\n * Creates a Redux store that holds the state tree.\n *\n * **We recommend using `configureStore` from the\n * `@reduxjs/toolkit` package**, which replaces `createStore`:\n * **https://redux.js.org/introduction/why-rtk-is-redux-today**\n *\n * The only way to change the data in the store is to call `dispatch()` on it.\n *\n * There should only be a single store in your app. To specify how different\n * parts of the state tree respond to actions, you may combine several reducers\n * into a single reducer function by using `combineReducers`.\n *\n * @param {Function} reducer A function that returns the next state tree, given\n * the current state tree and the action to handle.\n *\n * @param {any} [preloadedState] The initial state. You may optionally specify it\n * to hydrate the state from the server in universal apps, or to restore a\n * previously serialized user session.\n * If you use `combineReducers` to produce the root reducer function, this must be\n * an object with the same shape as `combineReducers` keys.\n *\n * @param {Function} [enhancer] The store enhancer. You may optionally specify it\n * to enhance the store with third-party capabilities such as middleware,\n * time travel, persistence, etc. The only store enhancer that ships with Redux\n * is `applyMiddleware()`.\n *\n * @returns {Store} A Redux store that lets you read the state, dispatch actions\n * and subscribe to changes.\n */\n\nvar legacy_createStore = createStore;\n\n/**\n * Prints a warning in the console if it exists.\n *\n * @param {String} message The warning message.\n * @returns {void}\n */\nfunction warning(message) {\n /* eslint-disable no-console */\n if (typeof console !== 'undefined' && typeof console.error === 'function') {\n console.error(message);\n }\n /* eslint-enable no-console */\n\n\n try {\n // This error was thrown as a convenience so that if you enable\n // \"break on all exceptions\" in your console,\n // it would pause the execution at this line.\n throw new Error(message);\n } catch (e) {} // eslint-disable-line no-empty\n\n}\n\nfunction getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {\n var reducerKeys = Object.keys(reducers);\n var argumentName = action && action.type === ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer';\n\n if (reducerKeys.length === 0) {\n return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.';\n }\n\n if (!isPlainObject(inputState)) {\n return \"The \" + argumentName + \" has unexpected type of \\\"\" + kindOf(inputState) + \"\\\". Expected argument to be an object with the following \" + (\"keys: \\\"\" + reducerKeys.join('\", \"') + \"\\\"\");\n }\n\n var unexpectedKeys = Object.keys(inputState).filter(function (key) {\n return !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key];\n });\n unexpectedKeys.forEach(function (key) {\n unexpectedKeyCache[key] = true;\n });\n if (action && action.type === ActionTypes.REPLACE) return;\n\n if (unexpectedKeys.length > 0) {\n return \"Unexpected \" + (unexpectedKeys.length > 1 ? 'keys' : 'key') + \" \" + (\"\\\"\" + unexpectedKeys.join('\", \"') + \"\\\" found in \" + argumentName + \". \") + \"Expected to find one of the known reducer keys instead: \" + (\"\\\"\" + reducerKeys.join('\", \"') + \"\\\". Unexpected keys will be ignored.\");\n }\n}\n\nfunction assertReducerShape(reducers) {\n Object.keys(reducers).forEach(function (key) {\n var reducer = reducers[key];\n var initialState = reducer(undefined, {\n type: ActionTypes.INIT\n });\n\n if (typeof initialState === 'undefined') {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(12) : \"The slice reducer for key \\\"\" + key + \"\\\" returned undefined during initialization. \" + \"If the state passed to the reducer is undefined, you must \" + \"explicitly return the initial state. The initial state may \" + \"not be undefined. If you don't want to set a value for this reducer, \" + \"you can use null instead of undefined.\");\n }\n\n if (typeof reducer(undefined, {\n type: ActionTypes.PROBE_UNKNOWN_ACTION()\n }) === 'undefined') {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(13) : \"The slice reducer for key \\\"\" + key + \"\\\" returned undefined when probed with a random type. \" + (\"Don't try to handle '\" + ActionTypes.INIT + \"' or other actions in \\\"redux/*\\\" \") + \"namespace. They are considered private. Instead, you must return the \" + \"current state for any unknown actions, unless it is undefined, \" + \"in which case you must return the initial state, regardless of the \" + \"action type. The initial state may not be undefined, but can be null.\");\n }\n });\n}\n/**\n * Turns an object whose values are different reducer functions, into a single\n * reducer function. It will call every child reducer, and gather their results\n * into a single state object, whose keys correspond to the keys of the passed\n * reducer functions.\n *\n * @param {Object} reducers An object whose values correspond to different\n * reducer functions that need to be combined into one. One handy way to obtain\n * it is to use ES6 `import * as reducers` syntax. The reducers may never return\n * undefined for any action. Instead, they should return their initial state\n * if the state passed to them was undefined, and the current state for any\n * unrecognized action.\n *\n * @returns {Function} A reducer function that invokes every reducer inside the\n * passed object, and builds a state object with the same shape.\n */\n\n\nfunction combineReducers(reducers) {\n var reducerKeys = Object.keys(reducers);\n var finalReducers = {};\n\n for (var i = 0; i < reducerKeys.length; i++) {\n var key = reducerKeys[i];\n\n if (process.env.NODE_ENV !== 'production') {\n if (typeof reducers[key] === 'undefined') {\n warning(\"No reducer provided for key \\\"\" + key + \"\\\"\");\n }\n }\n\n if (typeof reducers[key] === 'function') {\n finalReducers[key] = reducers[key];\n }\n }\n\n var finalReducerKeys = Object.keys(finalReducers); // This is used to make sure we don't warn about the same\n // keys multiple times.\n\n var unexpectedKeyCache;\n\n if (process.env.NODE_ENV !== 'production') {\n unexpectedKeyCache = {};\n }\n\n var shapeAssertionError;\n\n try {\n assertReducerShape(finalReducers);\n } catch (e) {\n shapeAssertionError = e;\n }\n\n return function combination(state, action) {\n if (state === void 0) {\n state = {};\n }\n\n if (shapeAssertionError) {\n throw shapeAssertionError;\n }\n\n if (process.env.NODE_ENV !== 'production') {\n var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);\n\n if (warningMessage) {\n warning(warningMessage);\n }\n }\n\n var hasChanged = false;\n var nextState = {};\n\n for (var _i = 0; _i < finalReducerKeys.length; _i++) {\n var _key = finalReducerKeys[_i];\n var reducer = finalReducers[_key];\n var previousStateForKey = state[_key];\n var nextStateForKey = reducer(previousStateForKey, action);\n\n if (typeof nextStateForKey === 'undefined') {\n var actionType = action && action.type;\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(14) : \"When called with an action of type \" + (actionType ? \"\\\"\" + String(actionType) + \"\\\"\" : '(unknown type)') + \", the slice reducer for key \\\"\" + _key + \"\\\" returned undefined. \" + \"To ignore an action, you must explicitly return the previous state. \" + \"If you want this reducer to hold no value, you can return null instead of undefined.\");\n }\n\n nextState[_key] = nextStateForKey;\n hasChanged = hasChanged || nextStateForKey !== previousStateForKey;\n }\n\n hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;\n return hasChanged ? nextState : state;\n };\n}\n\nfunction bindActionCreator(actionCreator, dispatch) {\n return function () {\n return dispatch(actionCreator.apply(this, arguments));\n };\n}\n/**\n * Turns an object whose values are action creators, into an object with the\n * same keys, but with every function wrapped into a `dispatch` call so they\n * may be invoked directly. This is just a convenience method, as you can call\n * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.\n *\n * For convenience, you can also pass an action creator as the first argument,\n * and get a dispatch wrapped function in return.\n *\n * @param {Function|Object} actionCreators An object whose values are action\n * creator functions. One handy way to obtain it is to use ES6 `import * as`\n * syntax. You may also pass a single function.\n *\n * @param {Function} dispatch The `dispatch` function available on your Redux\n * store.\n *\n * @returns {Function|Object} The object mimicking the original object, but with\n * every action creator wrapped into the `dispatch` call. If you passed a\n * function as `actionCreators`, the return value will also be a single\n * function.\n */\n\n\nfunction bindActionCreators(actionCreators, dispatch) {\n if (typeof actionCreators === 'function') {\n return bindActionCreator(actionCreators, dispatch);\n }\n\n if (typeof actionCreators !== 'object' || actionCreators === null) {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(16) : \"bindActionCreators expected an object or a function, but instead received: '\" + kindOf(actionCreators) + \"'. \" + \"Did you write \\\"import ActionCreators from\\\" instead of \\\"import * as ActionCreators from\\\"?\");\n }\n\n var boundActionCreators = {};\n\n for (var key in actionCreators) {\n var actionCreator = actionCreators[key];\n\n if (typeof actionCreator === 'function') {\n boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);\n }\n }\n\n return boundActionCreators;\n}\n\n/**\n * Composes single-argument functions from right to left. The rightmost\n * function can take multiple arguments as it provides the signature for\n * the resulting composite function.\n *\n * @param {...Function} funcs The functions to compose.\n * @returns {Function} A function obtained by composing the argument functions\n * from right to left. For example, compose(f, g, h) is identical to doing\n * (...args) => f(g(h(...args))).\n */\nfunction compose() {\n for (var _len = arguments.length, funcs = new Array(_len), _key = 0; _key < _len; _key++) {\n funcs[_key] = arguments[_key];\n }\n\n if (funcs.length === 0) {\n return function (arg) {\n return arg;\n };\n }\n\n if (funcs.length === 1) {\n return funcs[0];\n }\n\n return funcs.reduce(function (a, b) {\n return function () {\n return a(b.apply(void 0, arguments));\n };\n });\n}\n\n/**\n * Creates a store enhancer that applies middleware to the dispatch method\n * of the Redux store. This is handy for a variety of tasks, such as expressing\n * asynchronous actions in a concise manner, or logging every action payload.\n *\n * See `redux-thunk` package as an example of the Redux middleware.\n *\n * Because middleware is potentially asynchronous, this should be the first\n * store enhancer in the composition chain.\n *\n * Note that each middleware will be given the `dispatch` and `getState` functions\n * as named arguments.\n *\n * @param {...Function} middlewares The middleware chain to be applied.\n * @returns {Function} A store enhancer applying the middleware.\n */\n\nfunction applyMiddleware() {\n for (var _len = arguments.length, middlewares = new Array(_len), _key = 0; _key < _len; _key++) {\n middlewares[_key] = arguments[_key];\n }\n\n return function (createStore) {\n return function () {\n var store = createStore.apply(void 0, arguments);\n\n var _dispatch = function dispatch() {\n throw new Error(process.env.NODE_ENV === \"production\" ? formatProdErrorMessage(15) : 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');\n };\n\n var middlewareAPI = {\n getState: store.getState,\n dispatch: function dispatch() {\n return _dispatch.apply(void 0, arguments);\n }\n };\n var chain = middlewares.map(function (middleware) {\n return middleware(middlewareAPI);\n });\n _dispatch = compose.apply(void 0, chain)(store.dispatch);\n return _objectSpread(_objectSpread({}, store), {}, {\n dispatch: _dispatch\n });\n };\n };\n}\n\nexport { ActionTypes as __DO_NOT_USE__ActionTypes, applyMiddleware, bindActionCreators, combineReducers, compose, createStore, legacy_createStore };\n","export var strictEquality = function strictEquality(a, b) {\n return a === b;\n};\n/**\n * Determine if two cartesian coordinate offsets are equal\n * @param offsetA\n * @param offsetB\n */\n\nexport function areCoordsEqual(offsetA, offsetB) {\n if (!offsetA && !offsetB) {\n return true;\n } else if (!offsetA || !offsetB) {\n return false;\n } else {\n return offsetA.x === offsetB.x && offsetA.y === offsetB.y;\n }\n}\n/**\n * Determines if two arrays of items are equal\n * @param a The first array of items\n * @param b The second array of items\n */\n\nexport function areArraysEqual(a, b) {\n var isEqual = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : strictEquality;\n\n if (a.length !== b.length) {\n return false;\n }\n\n for (var i = 0; i < a.length; ++i) {\n if (!isEqual(a[i], b[i])) {\n return false;\n }\n }\n\n return true;\n}","function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nimport { INIT_COORDS, BEGIN_DRAG, HOVER, END_DRAG, DROP } from '../actions/dragDrop';\nimport { areCoordsEqual } from '../utils/equality';\nvar initialState = {\n initialSourceClientOffset: null,\n initialClientOffset: null,\n clientOffset: null\n};\nexport function reduce() {\n var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;\n var action = arguments.length > 1 ? arguments[1] : undefined;\n var payload = action.payload;\n\n switch (action.type) {\n case INIT_COORDS:\n case BEGIN_DRAG:\n return {\n initialSourceClientOffset: payload.sourceClientOffset,\n initialClientOffset: payload.clientOffset,\n clientOffset: payload.clientOffset\n };\n\n case HOVER:\n if (areCoordsEqual(state.clientOffset, payload.clientOffset)) {\n return state;\n }\n\n return _objectSpread(_objectSpread({}, state), {}, {\n clientOffset: payload.clientOffset\n });\n\n case END_DRAG:\n case DROP:\n return initialState;\n\n default:\n return state;\n }\n}","export var ADD_SOURCE = 'dnd-core/ADD_SOURCE';\nexport var ADD_TARGET = 'dnd-core/ADD_TARGET';\nexport var REMOVE_SOURCE = 'dnd-core/REMOVE_SOURCE';\nexport var REMOVE_TARGET = 'dnd-core/REMOVE_TARGET';\nexport function addSource(sourceId) {\n return {\n type: ADD_SOURCE,\n payload: {\n sourceId: sourceId\n }\n };\n}\nexport function addTarget(targetId) {\n return {\n type: ADD_TARGET,\n payload: {\n targetId: targetId\n }\n };\n}\nexport function removeSource(sourceId) {\n return {\n type: REMOVE_SOURCE,\n payload: {\n sourceId: sourceId\n }\n };\n}\nexport function removeTarget(targetId) {\n return {\n type: REMOVE_TARGET,\n payload: {\n targetId: targetId\n }\n };\n}","function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nimport { BEGIN_DRAG, PUBLISH_DRAG_SOURCE, HOVER, END_DRAG, DROP } from '../actions/dragDrop';\nimport { REMOVE_TARGET } from '../actions/registry';\nimport { without } from '../utils/js_utils';\nvar initialState = {\n itemType: null,\n item: null,\n sourceId: null,\n targetIds: [],\n dropResult: null,\n didDrop: false,\n isSourcePublic: null\n};\nexport function reduce() {\n var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;\n var action = arguments.length > 1 ? arguments[1] : undefined;\n var payload = action.payload;\n\n switch (action.type) {\n case BEGIN_DRAG:\n return _objectSpread(_objectSpread({}, state), {}, {\n itemType: payload.itemType,\n item: payload.item,\n sourceId: payload.sourceId,\n isSourcePublic: payload.isSourcePublic,\n dropResult: null,\n didDrop: false\n });\n\n case PUBLISH_DRAG_SOURCE:\n return _objectSpread(_objectSpread({}, state), {}, {\n isSourcePublic: true\n });\n\n case HOVER:\n return _objectSpread(_objectSpread({}, state), {}, {\n targetIds: payload.targetIds\n });\n\n case REMOVE_TARGET:\n if (state.targetIds.indexOf(payload.targetId) === -1) {\n return state;\n }\n\n return _objectSpread(_objectSpread({}, state), {}, {\n targetIds: without(state.targetIds, payload.targetId)\n });\n\n case DROP:\n return _objectSpread(_objectSpread({}, state), {}, {\n dropResult: payload.dropResult,\n didDrop: true,\n targetIds: []\n });\n\n case END_DRAG:\n return _objectSpread(_objectSpread({}, state), {}, {\n itemType: null,\n item: null,\n sourceId: null,\n dropResult: null,\n didDrop: false,\n isSourcePublic: null,\n targetIds: []\n });\n\n default:\n return state;\n }\n}","import { ADD_SOURCE, ADD_TARGET, REMOVE_SOURCE, REMOVE_TARGET } from '../actions/registry';\nexport function reduce() {\n var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;\n var action = arguments.length > 1 ? arguments[1] : undefined;\n\n switch (action.type) {\n case ADD_SOURCE:\n case ADD_TARGET:\n return state + 1;\n\n case REMOVE_SOURCE:\n case REMOVE_TARGET:\n return state - 1;\n\n default:\n return state;\n }\n}","import { intersection } from './js_utils';\nexport var NONE = [];\nexport var ALL = [];\nNONE.__IS_NONE__ = true;\nALL.__IS_ALL__ = true;\n/**\n * Determines if the given handler IDs are dirty or not.\n *\n * @param dirtyIds The set of dirty handler ids\n * @param handlerIds The set of handler ids to check\n */\n\nexport function areDirty(dirtyIds, handlerIds) {\n if (dirtyIds === NONE) {\n return false;\n }\n\n if (dirtyIds === ALL || typeof handlerIds === 'undefined') {\n return true;\n }\n\n var commonIds = intersection(handlerIds, dirtyIds);\n return commonIds.length > 0;\n}","import { BEGIN_DRAG, PUBLISH_DRAG_SOURCE, HOVER, END_DRAG, DROP } from '../actions/dragDrop';\nimport { ADD_SOURCE, ADD_TARGET, REMOVE_SOURCE, REMOVE_TARGET } from '../actions/registry';\nimport { areArraysEqual } from '../utils/equality';\nimport { NONE, ALL } from '../utils/dirtiness';\nimport { xor } from '../utils/js_utils';\nexport function reduce() {\n var _state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : NONE;\n\n var action = arguments.length > 1 ? arguments[1] : undefined;\n\n switch (action.type) {\n case HOVER:\n break;\n\n case ADD_SOURCE:\n case ADD_TARGET:\n case REMOVE_TARGET:\n case REMOVE_SOURCE:\n return NONE;\n\n case BEGIN_DRAG:\n case PUBLISH_DRAG_SOURCE:\n case END_DRAG:\n case DROP:\n default:\n return ALL;\n }\n\n var _action$payload = action.payload,\n _action$payload$targe = _action$payload.targetIds,\n targetIds = _action$payload$targe === void 0 ? [] : _action$payload$targe,\n _action$payload$prevT = _action$payload.prevTargetIds,\n prevTargetIds = _action$payload$prevT === void 0 ? [] : _action$payload$prevT;\n var result = xor(targetIds, prevTargetIds);\n var didChange = result.length > 0 || !areArraysEqual(targetIds, prevTargetIds);\n\n if (!didChange) {\n return NONE;\n } // Check the target ids at the innermost position. If they are valid, add them\n // to the result\n\n\n var prevInnermostTargetId = prevTargetIds[prevTargetIds.length - 1];\n var innermostTargetId = targetIds[targetIds.length - 1];\n\n if (prevInnermostTargetId !== innermostTargetId) {\n if (prevInnermostTargetId) {\n result.push(prevInnermostTargetId);\n }\n\n if (innermostTargetId) {\n result.push(innermostTargetId);\n }\n }\n\n return result;\n}","export function reduce() {\n var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;\n return state + 1;\n}","function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nimport { reduce as dragOffset } from './dragOffset';\nimport { reduce as dragOperation } from './dragOperation';\nimport { reduce as refCount } from './refCount';\nimport { reduce as dirtyHandlerIds } from './dirtyHandlerIds';\nimport { reduce as stateId } from './stateId';\nimport { get } from '../utils/js_utils';\nexport function reduce() {\n var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n var action = arguments.length > 1 ? arguments[1] : undefined;\n return {\n dirtyHandlerIds: dirtyHandlerIds(state.dirtyHandlerIds, {\n type: action.type,\n payload: _objectSpread(_objectSpread({}, action.payload), {}, {\n prevTargetIds: get(state, 'dragOperation.targetIds', [])\n })\n }),\n dragOffset: dragOffset(state.dragOffset, action),\n refCount: refCount(state.refCount, action),\n dragOperation: dragOperation(state.dragOperation, action),\n stateId: stateId(state.stateId)\n };\n}","/**\n * Coordinate addition\n * @param a The first coordinate\n * @param b The second coordinate\n */\nexport function add(a, b) {\n return {\n x: a.x + b.x,\n y: a.y + b.y\n };\n}\n/**\n * Coordinate subtraction\n * @param a The first coordinate\n * @param b The second coordinate\n */\n\nexport function subtract(a, b) {\n return {\n x: a.x - b.x,\n y: a.y - b.y\n };\n}\n/**\n * Returns the cartesian distance of the drag source component's position, based on its position\n * at the time when the current drag operation has started, and the movement difference.\n *\n * Returns null if no item is being dragged.\n *\n * @param state The offset state to compute from\n */\n\nexport function getSourceClientOffset(state) {\n var clientOffset = state.clientOffset,\n initialClientOffset = state.initialClientOffset,\n initialSourceClientOffset = state.initialSourceClientOffset;\n\n if (!clientOffset || !initialClientOffset || !initialSourceClientOffset) {\n return null;\n }\n\n return subtract(add(clientOffset, initialSourceClientOffset), initialClientOffset);\n}\n/**\n * Determines the x,y offset between the client offset and the initial client offset\n *\n * @param state The offset state to compute from\n */\n\nexport function getDifferenceFromInitialOffset(state) {\n var clientOffset = state.clientOffset,\n initialClientOffset = state.initialClientOffset;\n\n if (!clientOffset || !initialClientOffset) {\n return null;\n }\n\n return subtract(clientOffset, initialClientOffset);\n}","function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nimport { invariant } from '@react-dnd/invariant';\nimport { matchesType } from '../utils/matchesType';\nimport { getSourceClientOffset as _getSourceClientOffset, getDifferenceFromInitialOffset as _getDifferenceFromInitialOffset } from '../utils/coords';\nimport { areDirty } from '../utils/dirtiness';\nexport var DragDropMonitorImpl = /*#__PURE__*/function () {\n function DragDropMonitorImpl(store, registry) {\n _classCallCheck(this, DragDropMonitorImpl);\n\n this.store = store;\n this.registry = registry;\n }\n\n _createClass(DragDropMonitorImpl, [{\n key: \"subscribeToStateChange\",\n value: function subscribeToStateChange(listener) {\n var _this = this;\n\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n handlerIds: undefined\n };\n var handlerIds = options.handlerIds;\n invariant(typeof listener === 'function', 'listener must be a function.');\n invariant(typeof handlerIds === 'undefined' || Array.isArray(handlerIds), 'handlerIds, when specified, must be an array of strings.');\n var prevStateId = this.store.getState().stateId;\n\n var handleChange = function handleChange() {\n var state = _this.store.getState();\n\n var currentStateId = state.stateId;\n\n try {\n var canSkipListener = currentStateId === prevStateId || currentStateId === prevStateId + 1 && !areDirty(state.dirtyHandlerIds, handlerIds);\n\n if (!canSkipListener) {\n listener();\n }\n } finally {\n prevStateId = currentStateId;\n }\n };\n\n return this.store.subscribe(handleChange);\n }\n }, {\n key: \"subscribeToOffsetChange\",\n value: function subscribeToOffsetChange(listener) {\n var _this2 = this;\n\n invariant(typeof listener === 'function', 'listener must be a function.');\n var previousState = this.store.getState().dragOffset;\n\n var handleChange = function handleChange() {\n var nextState = _this2.store.getState().dragOffset;\n\n if (nextState === previousState) {\n return;\n }\n\n previousState = nextState;\n listener();\n };\n\n return this.store.subscribe(handleChange);\n }\n }, {\n key: \"canDragSource\",\n value: function canDragSource(sourceId) {\n if (!sourceId) {\n return false;\n }\n\n var source = this.registry.getSource(sourceId);\n invariant(source, \"Expected to find a valid source. sourceId=\".concat(sourceId));\n\n if (this.isDragging()) {\n return false;\n }\n\n return source.canDrag(this, sourceId);\n }\n }, {\n key: \"canDropOnTarget\",\n value: function canDropOnTarget(targetId) {\n // undefined on initial render\n if (!targetId) {\n return false;\n }\n\n var target = this.registry.getTarget(targetId);\n invariant(target, \"Expected to find a valid target. targetId=\".concat(targetId));\n\n if (!this.isDragging() || this.didDrop()) {\n return false;\n }\n\n var targetType = this.registry.getTargetType(targetId);\n var draggedItemType = this.getItemType();\n return matchesType(targetType, draggedItemType) && target.canDrop(this, targetId);\n }\n }, {\n key: \"isDragging\",\n value: function isDragging() {\n return Boolean(this.getItemType());\n }\n }, {\n key: \"isDraggingSource\",\n value: function isDraggingSource(sourceId) {\n // undefined on initial render\n if (!sourceId) {\n return false;\n }\n\n var source = this.registry.getSource(sourceId, true);\n invariant(source, \"Expected to find a valid source. sourceId=\".concat(sourceId));\n\n if (!this.isDragging() || !this.isSourcePublic()) {\n return false;\n }\n\n var sourceType = this.registry.getSourceType(sourceId);\n var draggedItemType = this.getItemType();\n\n if (sourceType !== draggedItemType) {\n return false;\n }\n\n return source.isDragging(this, sourceId);\n }\n }, {\n key: \"isOverTarget\",\n value: function isOverTarget(targetId) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n shallow: false\n };\n\n // undefined on initial render\n if (!targetId) {\n return false;\n }\n\n var shallow = options.shallow;\n\n if (!this.isDragging()) {\n return false;\n }\n\n var targetType = this.registry.getTargetType(targetId);\n var draggedItemType = this.getItemType();\n\n if (draggedItemType && !matchesType(targetType, draggedItemType)) {\n return false;\n }\n\n var targetIds = this.getTargetIds();\n\n if (!targetIds.length) {\n return false;\n }\n\n var index = targetIds.indexOf(targetId);\n\n if (shallow) {\n return index === targetIds.length - 1;\n } else {\n return index > -1;\n }\n }\n }, {\n key: \"getItemType\",\n value: function getItemType() {\n return this.store.getState().dragOperation.itemType;\n }\n }, {\n key: \"getItem\",\n value: function getItem() {\n return this.store.getState().dragOperation.item;\n }\n }, {\n key: \"getSourceId\",\n value: function getSourceId() {\n return this.store.getState().dragOperation.sourceId;\n }\n }, {\n key: \"getTargetIds\",\n value: function getTargetIds() {\n return this.store.getState().dragOperation.targetIds;\n }\n }, {\n key: \"getDropResult\",\n value: function getDropResult() {\n return this.store.getState().dragOperation.dropResult;\n }\n }, {\n key: \"didDrop\",\n value: function didDrop() {\n return this.store.getState().dragOperation.didDrop;\n }\n }, {\n key: \"isSourcePublic\",\n value: function isSourcePublic() {\n return Boolean(this.store.getState().dragOperation.isSourcePublic);\n }\n }, {\n key: \"getInitialClientOffset\",\n value: function getInitialClientOffset() {\n return this.store.getState().dragOffset.initialClientOffset;\n }\n }, {\n key: \"getInitialSourceClientOffset\",\n value: function getInitialSourceClientOffset() {\n return this.store.getState().dragOffset.initialSourceClientOffset;\n }\n }, {\n key: \"getClientOffset\",\n value: function getClientOffset() {\n return this.store.getState().dragOffset.clientOffset;\n }\n }, {\n key: \"getSourceClientOffset\",\n value: function getSourceClientOffset() {\n return _getSourceClientOffset(this.store.getState().dragOffset);\n }\n }, {\n key: \"getDifferenceFromInitialOffset\",\n value: function getDifferenceFromInitialOffset() {\n return _getDifferenceFromInitialOffset(this.store.getState().dragOffset);\n }\n }]);\n\n return DragDropMonitorImpl;\n}();","export var HandlerRole;\n\n(function (HandlerRole) {\n HandlerRole[\"SOURCE\"] = \"SOURCE\";\n HandlerRole[\"TARGET\"] = \"TARGET\";\n})(HandlerRole || (HandlerRole = {}));","var nextUniqueId = 0;\nexport function getNextUniqueId() {\n return nextUniqueId++;\n}","function _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nimport { invariant } from '@react-dnd/invariant';\nexport function validateSourceContract(source) {\n invariant(typeof source.canDrag === 'function', 'Expected canDrag to be a function.');\n invariant(typeof source.beginDrag === 'function', 'Expected beginDrag to be a function.');\n invariant(typeof source.endDrag === 'function', 'Expected endDrag to be a function.');\n}\nexport function validateTargetContract(target) {\n invariant(typeof target.canDrop === 'function', 'Expected canDrop to be a function.');\n invariant(typeof target.hover === 'function', 'Expected hover to be a function.');\n invariant(typeof target.drop === 'function', 'Expected beginDrag to be a function.');\n}\nexport function validateType(type, allowArray) {\n if (allowArray && Array.isArray(type)) {\n type.forEach(function (t) {\n return validateType(t, false);\n });\n return;\n }\n\n invariant(typeof type === 'string' || _typeof(type) === 'symbol', allowArray ? 'Type can only be a string, a symbol, or an array of either.' : 'Type can only be a string or a symbol.');\n}","// Safari 6 and 6.1 for desktop, iPad, and iPhone are the only browsers that\n// have WebKitMutationObserver but not un-prefixed MutationObserver.\n// Must use `global` or `self` instead of `window` to work in both frames and web\n// workers. `global` is a provision of Browserify, Mr, Mrs, or Mop.\n/* globals self */ const scope = typeof global !== 'undefined' ? global : self;\nconst BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver;\nexport function makeRequestCallFromTimer(callback) {\n return function requestCall() {\n // We dispatch a timeout with a specified delay of 0 for engines that\n // can reliably accommodate that request. This will usually be snapped\n // to a 4 milisecond delay, but once we're flushing, there's no delay\n // between events.\n const timeoutHandle = setTimeout(handleTimer, 0);\n // However, since this timer gets frequently dropped in Firefox\n // workers, we enlist an interval handle that will try to fire\n // an event 20 times per second until it succeeds.\n const intervalHandle = setInterval(handleTimer, 50);\n function handleTimer() {\n // Whichever timer succeeds will cancel both timers and\n // execute the callback.\n clearTimeout(timeoutHandle);\n clearInterval(intervalHandle);\n callback();\n }\n };\n}\n// To request a high priority event, we induce a mutation observer by toggling\n// the text of a text node between \"1\" and \"-1\".\nexport function makeRequestCallFromMutationObserver(callback) {\n let toggle = 1;\n const observer = new BrowserMutationObserver(callback);\n const node = document.createTextNode('');\n observer.observe(node, {\n characterData: true\n });\n return function requestCall() {\n toggle = -toggle;\n node.data = toggle;\n };\n}\nexport const makeRequestCall = typeof BrowserMutationObserver === 'function' ? // reliably everywhere they are implemented.\n// They are implemented in all modern browsers.\n//\n// - Android 4-4.3\n// - Chrome 26-34\n// - Firefox 14-29\n// - Internet Explorer 11\n// - iPad Safari 6-7.1\n// - iPhone Safari 7-7.1\n// - Safari 6-7\nmakeRequestCallFromMutationObserver : // task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera\n// 11-12, and in web workers in many engines.\n// Although message channels yield to any queued rendering and IO tasks, they\n// would be better than imposing the 4ms delay of timers.\n// However, they do not work reliably in Internet Explorer or Safari.\n// Internet Explorer 10 is the only browser that has setImmediate but does\n// not have MutationObservers.\n// Although setImmediate yields to the browser's renderer, it would be\n// preferrable to falling back to setTimeout since it does not have\n// the minimum 4ms penalty.\n// Unfortunately there appears to be a bug in Internet Explorer 10 Mobile (and\n// Desktop to a lesser extent) that renders both setImmediate and\n// MessageChannel useless for the purposes of ASAP.\n// https://github.com/kriskowal/q/issues/396\n// Timers are implemented universally.\n// We fall back to timers in workers in most engines, and in foreground\n// contexts in the following browsers.\n// However, note that even this simple case requires nuances to operate in a\n// broad spectrum of browsers.\n//\n// - Firefox 3-13\n// - Internet Explorer 6-9\n// - iPad Safari 4.3\n// - Lynx 2.8.7\nmakeRequestCallFromTimer;\n\n//# sourceMappingURL=makeRequestCall.mjs.map","// `call`, just like a function.\nexport class RawTask {\n call() {\n try {\n this.task && this.task();\n } catch (error) {\n this.onError(error);\n } finally{\n this.task = null;\n this.release(this);\n }\n }\n constructor(onError, release){\n this.onError = onError;\n this.release = release;\n this.task = null;\n }\n}\n\n//# sourceMappingURL=RawTask.mjs.map","import { AsapQueue } from './AsapQueue.mjs';\nimport { TaskFactory } from './TaskFactory.mjs';\nconst asapQueue = new AsapQueue();\nconst taskFactory = new TaskFactory(asapQueue.registerPendingError);\n/**\n * Calls a task as soon as possible after returning, in its own event, with priority\n * over other events like animation, reflow, and repaint. An error thrown from an\n * event will not interrupt, nor even substantially slow down the processing of\n * other events, but will be rather postponed to a lower priority event.\n * @param {{call}} task A callable object, typically a function that takes no\n * arguments.\n */ export function asap(task) {\n asapQueue.enqueueTask(taskFactory.create(task));\n}\n\n//# sourceMappingURL=asap.mjs.map","import { makeRequestCall, makeRequestCallFromTimer } from './makeRequestCall.mjs';\nexport class AsapQueue {\n // Use the fastest means possible to execute a task in its own turn, with\n // priority over other events including IO, animation, reflow, and redraw\n // events in browsers.\n //\n // An exception thrown by a task will permanently interrupt the processing of\n // subsequent tasks. The higher level `asap` function ensures that if an\n // exception is thrown by a task, that the task queue will continue flushing as\n // soon as possible, but if you use `rawAsap` directly, you are responsible to\n // either ensure that no exceptions are thrown from your task, or to manually\n // call `rawAsap.requestFlush` if an exception is thrown.\n enqueueTask(task) {\n const { queue: q , requestFlush } = this;\n if (!q.length) {\n requestFlush();\n this.flushing = true;\n }\n // Equivalent to push, but avoids a function call.\n q[q.length] = task;\n }\n constructor(){\n this.queue = [];\n // We queue errors to ensure they are thrown in right order (FIFO).\n // Array-as-queue is good enough here, since we are just dealing with exceptions.\n this.pendingErrors = [];\n // Once a flush has been requested, no further calls to `requestFlush` are\n // necessary until the next `flush` completes.\n // @ts-ignore\n this.flushing = false;\n // The position of the next task to execute in the task queue. This is\n // preserved between calls to `flush` so that it can be resumed if\n // a task throws an exception.\n this.index = 0;\n // If a task schedules additional tasks recursively, the task queue can grow\n // unbounded. To prevent memory exhaustion, the task queue will periodically\n // truncate already-completed tasks.\n this.capacity = 1024;\n // The flush function processes all tasks that have been scheduled with\n // `rawAsap` unless and until one of those tasks throws an exception.\n // If a task throws an exception, `flush` ensures that its state will remain\n // consistent and will resume where it left off when called again.\n // However, `flush` does not make any arrangements to be called again if an\n // exception is thrown.\n this.flush = ()=>{\n const { queue: q } = this;\n while(this.index < q.length){\n const currentIndex = this.index;\n // Advance the index before calling the task. This ensures that we will\n // begin flushing on the next task the task throws an error.\n this.index++;\n q[currentIndex].call();\n // Prevent leaking memory for long chains of recursive calls to `asap`.\n // If we call `asap` within tasks scheduled by `asap`, the queue will\n // grow, but to avoid an O(n) walk for every task we execute, we don't\n // shift tasks off the queue after they have been executed.\n // Instead, we periodically shift 1024 tasks off the queue.\n if (this.index > this.capacity) {\n // Manually shift all values starting at the index back to the\n // beginning of the queue.\n for(let scan = 0, newLength = q.length - this.index; scan < newLength; scan++){\n q[scan] = q[scan + this.index];\n }\n q.length -= this.index;\n this.index = 0;\n }\n }\n q.length = 0;\n this.index = 0;\n this.flushing = false;\n };\n // In a web browser, exceptions are not fatal. However, to avoid\n // slowing down the queue of pending tasks, we rethrow the error in a\n // lower priority turn.\n this.registerPendingError = (err)=>{\n this.pendingErrors.push(err);\n this.requestErrorThrow();\n };\n // `requestFlush` requests that the high priority event queue be flushed as\n // soon as possible.\n // This is useful to prevent an error thrown in a task from stalling the event\n // queue if the exception handled by Node.js’s\n // `process.on(\"uncaughtException\")` or by a domain.\n // `requestFlush` is implemented using a strategy based on data collected from\n // every available SauceLabs Selenium web driver worker at time of writing.\n // https://docs.google.com/spreadsheets/d/1mG-5UYGup5qxGdEMWkhP6BWCz053NUb2E1QoUTU16uA/edit#gid=783724593\n this.requestFlush = makeRequestCall(this.flush);\n this.requestErrorThrow = makeRequestCallFromTimer(()=>{\n // Throw first error\n if (this.pendingErrors.length) {\n throw this.pendingErrors.shift();\n }\n });\n }\n} // The message channel technique was discovered by Malte Ubl and was the\n // original foundation for this library.\n // http://www.nonblocking.io/2011/06/windownexttick.html\n // Safari 6.0.5 (at least) intermittently fails to create message ports on a\n // page's first load. Thankfully, this version of Safari supports\n // MutationObservers, so we don't need to fall back in that case.\n // function makeRequestCallFromMessageChannel(callback) {\n // var channel = new MessageChannel();\n // channel.port1.onmessage = callback;\n // return function requestCall() {\n // channel.port2.postMessage(0);\n // };\n // }\n // For reasons explained above, we are also unable to use `setImmediate`\n // under any circumstances.\n // Even if we were, there is another bug in Internet Explorer 10.\n // It is not sufficient to assign `setImmediate` to `requestFlush` because\n // `setImmediate` must be called *by name* and therefore must be wrapped in a\n // closure.\n // Never forget.\n // function makeRequestCallFromSetImmediate(callback) {\n // return function requestCall() {\n // setImmediate(callback);\n // };\n // }\n // Safari 6.0 has a problem where timers will get lost while the user is\n // scrolling. This problem does not impact ASAP because Safari 6.0 supports\n // mutation observers, so that implementation is used instead.\n // However, if we ever elect to use timers in Safari, the prevalent work-around\n // is to add a scroll event listener that calls for a flush.\n // `setTimeout` does not call the passed callback if the delay is less than\n // approximately 7 in web workers in Firefox 8 through 18, and sometimes not\n // even then.\n // This is for `asap.js` only.\n // Its name will be periodically randomized to break any code that depends on\n // // its existence.\n // rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer\n // ASAP was originally a nextTick shim included in Q. This was factored out\n // into this ASAP package. It was later adapted to RSVP which made further\n // amendments. These decisions, particularly to marginalize MessageChannel and\n // to capture the MutationObserver implementation in a closure, were integrated\n // back into ASAP proper.\n // https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js\n\n//# sourceMappingURL=AsapQueue.mjs.map","import { RawTask } from './RawTask.mjs';\nexport class TaskFactory {\n create(task) {\n const tasks = this.freeTasks;\n const t1 = tasks.length ? tasks.pop() : new RawTask(this.onError, (t)=>tasks[tasks.length] = t\n );\n t1.task = task;\n return t1;\n }\n constructor(onError){\n this.onError = onError;\n this.freeTasks = [];\n }\n}\n\n//# sourceMappingURL=TaskFactory.mjs.map","function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\nimport { invariant } from '@react-dnd/invariant';\nimport { addSource as _addSource, addTarget as _addTarget, removeSource as _removeSource, removeTarget as _removeTarget } from '../actions/registry';\nimport { getNextUniqueId } from '../utils/getNextUniqueId';\nimport { HandlerRole } from '../interfaces';\nimport { validateSourceContract, validateTargetContract, validateType } from '../contracts';\nimport { asap } from '@react-dnd/asap';\n\nfunction getNextHandlerId(role) {\n var id = getNextUniqueId().toString();\n\n switch (role) {\n case HandlerRole.SOURCE:\n return \"S\".concat(id);\n\n case HandlerRole.TARGET:\n return \"T\".concat(id);\n\n default:\n throw new Error(\"Unknown Handler Role: \".concat(role));\n }\n}\n\nfunction parseRoleFromHandlerId(handlerId) {\n switch (handlerId[0]) {\n case 'S':\n return HandlerRole.SOURCE;\n\n case 'T':\n return HandlerRole.TARGET;\n\n default:\n invariant(false, \"Cannot parse handler ID: \".concat(handlerId));\n }\n}\n\nfunction mapContainsValue(map, searchValue) {\n var entries = map.entries();\n var isDone = false;\n\n do {\n var _entries$next = entries.next(),\n done = _entries$next.done,\n _entries$next$value = _slicedToArray(_entries$next.value, 2),\n value = _entries$next$value[1];\n\n if (value === searchValue) {\n return true;\n }\n\n isDone = !!done;\n } while (!isDone);\n\n return false;\n}\n\nexport var HandlerRegistryImpl = /*#__PURE__*/function () {\n function HandlerRegistryImpl(store) {\n _classCallCheck(this, HandlerRegistryImpl);\n\n this.types = new Map();\n this.dragSources = new Map();\n this.dropTargets = new Map();\n this.pinnedSourceId = null;\n this.pinnedSource = null;\n this.store = store;\n }\n\n _createClass(HandlerRegistryImpl, [{\n key: \"addSource\",\n value: function addSource(type, source) {\n validateType(type);\n validateSourceContract(source);\n var sourceId = this.addHandler(HandlerRole.SOURCE, type, source);\n this.store.dispatch(_addSource(sourceId));\n return sourceId;\n }\n }, {\n key: \"addTarget\",\n value: function addTarget(type, target) {\n validateType(type, true);\n validateTargetContract(target);\n var targetId = this.addHandler(HandlerRole.TARGET, type, target);\n this.store.dispatch(_addTarget(targetId));\n return targetId;\n }\n }, {\n key: \"containsHandler\",\n value: function containsHandler(handler) {\n return mapContainsValue(this.dragSources, handler) || mapContainsValue(this.dropTargets, handler);\n }\n }, {\n key: \"getSource\",\n value: function getSource(sourceId) {\n var includePinned = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n invariant(this.isSourceId(sourceId), 'Expected a valid source ID.');\n var isPinned = includePinned && sourceId === this.pinnedSourceId;\n var source = isPinned ? this.pinnedSource : this.dragSources.get(sourceId);\n return source;\n }\n }, {\n key: \"getTarget\",\n value: function getTarget(targetId) {\n invariant(this.isTargetId(targetId), 'Expected a valid target ID.');\n return this.dropTargets.get(targetId);\n }\n }, {\n key: \"getSourceType\",\n value: function getSourceType(sourceId) {\n invariant(this.isSourceId(sourceId), 'Expected a valid source ID.');\n return this.types.get(sourceId);\n }\n }, {\n key: \"getTargetType\",\n value: function getTargetType(targetId) {\n invariant(this.isTargetId(targetId), 'Expected a valid target ID.');\n return this.types.get(targetId);\n }\n }, {\n key: \"isSourceId\",\n value: function isSourceId(handlerId) {\n var role = parseRoleFromHandlerId(handlerId);\n return role === HandlerRole.SOURCE;\n }\n }, {\n key: \"isTargetId\",\n value: function isTargetId(handlerId) {\n var role = parseRoleFromHandlerId(handlerId);\n return role === HandlerRole.TARGET;\n }\n }, {\n key: \"removeSource\",\n value: function removeSource(sourceId) {\n var _this = this;\n\n invariant(this.getSource(sourceId), 'Expected an existing source.');\n this.store.dispatch(_removeSource(sourceId));\n asap(function () {\n _this.dragSources.delete(sourceId);\n\n _this.types.delete(sourceId);\n });\n }\n }, {\n key: \"removeTarget\",\n value: function removeTarget(targetId) {\n invariant(this.getTarget(targetId), 'Expected an existing target.');\n this.store.dispatch(_removeTarget(targetId));\n this.dropTargets.delete(targetId);\n this.types.delete(targetId);\n }\n }, {\n key: \"pinSource\",\n value: function pinSource(sourceId) {\n var source = this.getSource(sourceId);\n invariant(source, 'Expected an existing source.');\n this.pinnedSourceId = sourceId;\n this.pinnedSource = source;\n }\n }, {\n key: \"unpinSource\",\n value: function unpinSource() {\n invariant(this.pinnedSource, 'No source is pinned at the time.');\n this.pinnedSourceId = null;\n this.pinnedSource = null;\n }\n }, {\n key: \"addHandler\",\n value: function addHandler(role, type, handler) {\n var id = getNextHandlerId(role);\n this.types.set(id, type);\n\n if (role === HandlerRole.SOURCE) {\n this.dragSources.set(id, handler);\n } else if (role === HandlerRole.TARGET) {\n this.dropTargets.set(id, handler);\n }\n\n return id;\n }\n }]);\n\n return HandlerRegistryImpl;\n}();","import { DragDropManagerImpl } from './classes/DragDropManagerImpl';\nimport { createStore } from 'redux';\nimport { reduce } from './reducers';\nimport { DragDropMonitorImpl } from './classes/DragDropMonitorImpl';\nimport { HandlerRegistryImpl } from './classes/HandlerRegistryImpl';\nexport function createDragDropManager(backendFactory) {\n var globalContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;\n var backendOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};\n var debugMode = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;\n var store = makeStoreInstance(debugMode);\n var monitor = new DragDropMonitorImpl(store, new HandlerRegistryImpl(store));\n var manager = new DragDropManagerImpl(store, monitor);\n var backend = backendFactory(manager, globalContext, backendOptions);\n manager.receiveBackend(backend);\n return manager;\n}\n\nfunction makeStoreInstance(debugMode) {\n // TODO: if we ever make a react-native version of this,\n // we'll need to consider how to pull off dev-tooling\n var reduxDevTools = typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__;\n return createStore(reduce, debugMode && reduxDevTools && reduxDevTools({\n name: 'dnd-core',\n instanceId: 'dnd-core'\n }));\n}","import { createContext } from 'react';\n/**\n * Create the React Context\n */\n\nexport var DndContext = createContext({\n dragDropManager: undefined\n});","function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nimport { useEffect, memo } from 'react';\nimport { createDragDropManager } from 'dnd-core';\nimport { DndContext } from './DndContext';\nvar refCount = 0;\nvar INSTANCE_SYM = Symbol.for('__REACT_DND_CONTEXT_INSTANCE__');\n/**\n * A React component that provides the React-DnD context\n */\n\nexport var DndProvider = memo(function DndProvider(_ref) {\n var children = _ref.children,\n props = _objectWithoutProperties(_ref, [\"children\"]);\n\n var _getDndContextValue = getDndContextValue(props),\n _getDndContextValue2 = _slicedToArray(_getDndContextValue, 2),\n manager = _getDndContextValue2[0],\n isGlobalInstance = _getDndContextValue2[1]; // memoized from props\n\n /**\n * If the global context was used to store the DND context\n * then where theres no more references to it we should\n * clean it up to avoid memory leaks\n */\n\n\n useEffect(function () {\n if (isGlobalInstance) {\n var context = getGlobalContext();\n ++refCount;\n return function () {\n if (--refCount === 0) {\n context[INSTANCE_SYM] = null;\n }\n };\n }\n }, []);\n return _jsx(DndContext.Provider, Object.assign({\n value: manager\n }, {\n children: children\n }), void 0);\n});\n\nfunction getDndContextValue(props) {\n if ('manager' in props) {\n var _manager = {\n dragDropManager: props.manager\n };\n return [_manager, false];\n }\n\n var manager = createSingletonDndContext(props.backend, props.context, props.options, props.debugMode);\n var isGlobalInstance = !props.context;\n return [manager, isGlobalInstance];\n}\n\nfunction createSingletonDndContext(backend) {\n var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : getGlobalContext();\n var options = arguments.length > 2 ? arguments[2] : undefined;\n var debugMode = arguments.length > 3 ? arguments[3] : undefined;\n var ctx = context;\n\n if (!ctx[INSTANCE_SYM]) {\n ctx[INSTANCE_SYM] = {\n dragDropManager: createDragDropManager(backend, context, options, debugMode)\n };\n }\n\n return ctx[INSTANCE_SYM];\n}\n\nfunction getGlobalContext() {\n return typeof global !== 'undefined' ? global : window;\n}","// cheap lodash replacements\nexport function memoize(fn) {\n var result = null;\n\n var memoized = function memoized() {\n if (result == null) {\n result = fn();\n }\n\n return result;\n };\n\n return memoized;\n}\n/**\n * drop-in replacement for _.without\n */\n\nexport function without(items, item) {\n return items.filter(function (i) {\n return i !== item;\n });\n}\nexport function union(itemsA, itemsB) {\n var set = new Set();\n\n var insertItem = function insertItem(item) {\n return set.add(item);\n };\n\n itemsA.forEach(insertItem);\n itemsB.forEach(insertItem);\n var result = [];\n set.forEach(function (key) {\n return result.push(key);\n });\n return result;\n}","function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nimport { union, without } from './utils/js_utils';\nexport var EnterLeaveCounter = /*#__PURE__*/function () {\n function EnterLeaveCounter(isNodeInDocument) {\n _classCallCheck(this, EnterLeaveCounter);\n\n this.entered = [];\n this.isNodeInDocument = isNodeInDocument;\n }\n\n _createClass(EnterLeaveCounter, [{\n key: \"enter\",\n value: function enter(enteringNode) {\n var _this = this;\n\n var previousLength = this.entered.length;\n\n var isNodeEntered = function isNodeEntered(node) {\n return _this.isNodeInDocument(node) && (!node.contains || node.contains(enteringNode));\n };\n\n this.entered = union(this.entered.filter(isNodeEntered), [enteringNode]);\n return previousLength === 0 && this.entered.length > 0;\n }\n }, {\n key: \"leave\",\n value: function leave(leavingNode) {\n var previousLength = this.entered.length;\n this.entered = without(this.entered.filter(this.isNodeInDocument), leavingNode);\n return previousLength > 0 && this.entered.length === 0;\n }\n }, {\n key: \"reset\",\n value: function reset() {\n this.entered = [];\n }\n }]);\n\n return EnterLeaveCounter;\n}();","import { memoize } from './utils/js_utils';\nexport var isFirefox = memoize(function () {\n return /firefox/i.test(navigator.userAgent);\n});\nexport var isSafari = memoize(function () {\n return Boolean(window.safari);\n});","function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nexport var MonotonicInterpolant = /*#__PURE__*/function () {\n function MonotonicInterpolant(xs, ys) {\n _classCallCheck(this, MonotonicInterpolant);\n\n var length = xs.length; // Rearrange xs and ys so that xs is sorted\n\n var indexes = [];\n\n for (var i = 0; i < length; i++) {\n indexes.push(i);\n }\n\n indexes.sort(function (a, b) {\n return xs[a] < xs[b] ? -1 : 1;\n }); // Get consecutive differences and slopes\n\n var dys = [];\n var dxs = [];\n var ms = [];\n var dx;\n var dy;\n\n for (var _i = 0; _i < length - 1; _i++) {\n dx = xs[_i + 1] - xs[_i];\n dy = ys[_i + 1] - ys[_i];\n dxs.push(dx);\n dys.push(dy);\n ms.push(dy / dx);\n } // Get degree-1 coefficients\n\n\n var c1s = [ms[0]];\n\n for (var _i2 = 0; _i2 < dxs.length - 1; _i2++) {\n var m2 = ms[_i2];\n var mNext = ms[_i2 + 1];\n\n if (m2 * mNext <= 0) {\n c1s.push(0);\n } else {\n dx = dxs[_i2];\n var dxNext = dxs[_i2 + 1];\n var common = dx + dxNext;\n c1s.push(3 * common / ((common + dxNext) / m2 + (common + dx) / mNext));\n }\n }\n\n c1s.push(ms[ms.length - 1]); // Get degree-2 and degree-3 coefficients\n\n var c2s = [];\n var c3s = [];\n var m;\n\n for (var _i3 = 0; _i3 < c1s.length - 1; _i3++) {\n m = ms[_i3];\n var c1 = c1s[_i3];\n var invDx = 1 / dxs[_i3];\n\n var _common = c1 + c1s[_i3 + 1] - m - m;\n\n c2s.push((m - c1 - _common) * invDx);\n c3s.push(_common * invDx * invDx);\n }\n\n this.xs = xs;\n this.ys = ys;\n this.c1s = c1s;\n this.c2s = c2s;\n this.c3s = c3s;\n }\n\n _createClass(MonotonicInterpolant, [{\n key: \"interpolate\",\n value: function interpolate(x) {\n var xs = this.xs,\n ys = this.ys,\n c1s = this.c1s,\n c2s = this.c2s,\n c3s = this.c3s; // The rightmost point in the dataset should give an exact result\n\n var i = xs.length - 1;\n\n if (x === xs[i]) {\n return ys[i];\n } // Search for the interval x is in, returning the corresponding y if x is one of the original xs\n\n\n var low = 0;\n var high = c3s.length - 1;\n var mid;\n\n while (low <= high) {\n mid = Math.floor(0.5 * (low + high));\n var xHere = xs[mid];\n\n if (xHere < x) {\n low = mid + 1;\n } else if (xHere > x) {\n high = mid - 1;\n } else {\n return ys[mid];\n }\n }\n\n i = Math.max(0, high); // Interpolate\n\n var diff = x - xs[i];\n var diffSq = diff * diff;\n return ys[i] + c1s[i] * diff + c2s[i] * diffSq + c3s[i] * diff * diffSq;\n }\n }]);\n\n return MonotonicInterpolant;\n}();","import { isSafari, isFirefox } from './BrowserDetector';\nimport { MonotonicInterpolant } from './MonotonicInterpolant';\nvar ELEMENT_NODE = 1;\nexport function getNodeClientOffset(node) {\n var el = node.nodeType === ELEMENT_NODE ? node : node.parentElement;\n\n if (!el) {\n return null;\n }\n\n var _el$getBoundingClient = el.getBoundingClientRect(),\n top = _el$getBoundingClient.top,\n left = _el$getBoundingClient.left;\n\n return {\n x: left,\n y: top\n };\n}\nexport function getEventClientOffset(e) {\n return {\n x: e.clientX,\n y: e.clientY\n };\n}\n\nfunction isImageNode(node) {\n var _document$documentEle;\n\n return node.nodeName === 'IMG' && (isFirefox() || !((_document$documentEle = document.documentElement) !== null && _document$documentEle !== void 0 && _document$documentEle.contains(node)));\n}\n\nfunction getDragPreviewSize(isImage, dragPreview, sourceWidth, sourceHeight) {\n var dragPreviewWidth = isImage ? dragPreview.width : sourceWidth;\n var dragPreviewHeight = isImage ? dragPreview.height : sourceHeight; // Work around @2x coordinate discrepancies in browsers\n\n if (isSafari() && isImage) {\n dragPreviewHeight /= window.devicePixelRatio;\n dragPreviewWidth /= window.devicePixelRatio;\n }\n\n return {\n dragPreviewWidth: dragPreviewWidth,\n dragPreviewHeight: dragPreviewHeight\n };\n}\n\nexport function getDragPreviewOffset(sourceNode, dragPreview, clientOffset, anchorPoint, offsetPoint) {\n // The browsers will use the image intrinsic size under different conditions.\n // Firefox only cares if it's an image, but WebKit also wants it to be detached.\n var isImage = isImageNode(dragPreview);\n var dragPreviewNode = isImage ? sourceNode : dragPreview;\n var dragPreviewNodeOffsetFromClient = getNodeClientOffset(dragPreviewNode);\n var offsetFromDragPreview = {\n x: clientOffset.x - dragPreviewNodeOffsetFromClient.x,\n y: clientOffset.y - dragPreviewNodeOffsetFromClient.y\n };\n var sourceWidth = sourceNode.offsetWidth,\n sourceHeight = sourceNode.offsetHeight;\n var anchorX = anchorPoint.anchorX,\n anchorY = anchorPoint.anchorY;\n\n var _getDragPreviewSize = getDragPreviewSize(isImage, dragPreview, sourceWidth, sourceHeight),\n dragPreviewWidth = _getDragPreviewSize.dragPreviewWidth,\n dragPreviewHeight = _getDragPreviewSize.dragPreviewHeight;\n\n var calculateYOffset = function calculateYOffset() {\n var interpolantY = new MonotonicInterpolant([0, 0.5, 1], [// Dock to the top\n offsetFromDragPreview.y, // Align at the center\n offsetFromDragPreview.y / sourceHeight * dragPreviewHeight, // Dock to the bottom\n offsetFromDragPreview.y + dragPreviewHeight - sourceHeight]);\n var y = interpolantY.interpolate(anchorY); // Work around Safari 8 positioning bug\n\n if (isSafari() && isImage) {\n // We'll have to wait for @3x to see if this is entirely correct\n y += (window.devicePixelRatio - 1) * dragPreviewHeight;\n }\n\n return y;\n };\n\n var calculateXOffset = function calculateXOffset() {\n // Interpolate coordinates depending on anchor point\n // If you know a simpler way to do this, let me know\n var interpolantX = new MonotonicInterpolant([0, 0.5, 1], [// Dock to the left\n offsetFromDragPreview.x, // Align at the center\n offsetFromDragPreview.x / sourceWidth * dragPreviewWidth, // Dock to the right\n offsetFromDragPreview.x + dragPreviewWidth - sourceWidth]);\n return interpolantX.interpolate(anchorX);\n }; // Force offsets if specified in the options.\n\n\n var offsetX = offsetPoint.offsetX,\n offsetY = offsetPoint.offsetY;\n var isManualOffsetX = offsetX === 0 || offsetX;\n var isManualOffsetY = offsetY === 0 || offsetY;\n return {\n x: isManualOffsetX ? offsetX : calculateXOffset(),\n y: isManualOffsetY ? offsetY : calculateYOffset()\n };\n}","export var FILE = '__NATIVE_FILE__';\nexport var URL = '__NATIVE_URL__';\nexport var TEXT = '__NATIVE_TEXT__';\nexport var HTML = '__NATIVE_HTML__';","var _nativeTypesConfig;\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nimport * as NativeTypes from '../NativeTypes';\nimport { getDataFromDataTransfer } from './getDataFromDataTransfer';\nexport var nativeTypesConfig = (_nativeTypesConfig = {}, _defineProperty(_nativeTypesConfig, NativeTypes.FILE, {\n exposeProperties: {\n files: function files(dataTransfer) {\n return Array.prototype.slice.call(dataTransfer.files);\n },\n items: function items(dataTransfer) {\n return dataTransfer.items;\n }\n },\n matchesTypes: ['Files']\n}), _defineProperty(_nativeTypesConfig, NativeTypes.HTML, {\n exposeProperties: {\n html: function html(dataTransfer, matchesTypes) {\n return getDataFromDataTransfer(dataTransfer, matchesTypes, '');\n }\n },\n matchesTypes: ['Html', 'text/html']\n}), _defineProperty(_nativeTypesConfig, NativeTypes.URL, {\n exposeProperties: {\n urls: function urls(dataTransfer, matchesTypes) {\n return getDataFromDataTransfer(dataTransfer, matchesTypes, '').split('\\n');\n }\n },\n matchesTypes: ['Url', 'text/uri-list']\n}), _defineProperty(_nativeTypesConfig, NativeTypes.TEXT, {\n exposeProperties: {\n text: function text(dataTransfer, matchesTypes) {\n return getDataFromDataTransfer(dataTransfer, matchesTypes, '');\n }\n },\n matchesTypes: ['Text', 'text/plain']\n}), _nativeTypesConfig);","export function getDataFromDataTransfer(dataTransfer, typesToTry, defaultValue) {\n var result = typesToTry.reduce(function (resultSoFar, typeToTry) {\n return resultSoFar || dataTransfer.getData(typeToTry);\n }, '');\n return result != null ? result : defaultValue;\n}","function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nexport var NativeDragSource = /*#__PURE__*/function () {\n function NativeDragSource(config) {\n _classCallCheck(this, NativeDragSource);\n\n this.config = config;\n this.item = {};\n this.initializeExposedProperties();\n }\n\n _createClass(NativeDragSource, [{\n key: \"initializeExposedProperties\",\n value: function initializeExposedProperties() {\n var _this = this;\n\n Object.keys(this.config.exposeProperties).forEach(function (property) {\n Object.defineProperty(_this.item, property, {\n configurable: true,\n enumerable: true,\n get: function get() {\n // eslint-disable-next-line no-console\n console.warn(\"Browser doesn't allow reading \\\"\".concat(property, \"\\\" until the drop event.\"));\n return null;\n }\n });\n });\n }\n }, {\n key: \"loadDataTransfer\",\n value: function loadDataTransfer(dataTransfer) {\n var _this2 = this;\n\n if (dataTransfer) {\n var newProperties = {};\n Object.keys(this.config.exposeProperties).forEach(function (property) {\n newProperties[property] = {\n value: _this2.config.exposeProperties[property](dataTransfer, _this2.config.matchesTypes),\n configurable: true,\n enumerable: true\n };\n });\n Object.defineProperties(this.item, newProperties);\n }\n }\n }, {\n key: \"canDrag\",\n value: function canDrag() {\n return true;\n }\n }, {\n key: \"beginDrag\",\n value: function beginDrag() {\n return this.item;\n }\n }, {\n key: \"isDragging\",\n value: function isDragging(monitor, handle) {\n return handle === monitor.getSourceId();\n }\n }, {\n key: \"endDrag\",\n value: function endDrag() {// empty\n }\n }]);\n\n return NativeDragSource;\n}();","import { nativeTypesConfig } from './nativeTypesConfig';\nimport { NativeDragSource } from './NativeDragSource';\nexport function createNativeDragSource(type, dataTransfer) {\n var result = new NativeDragSource(nativeTypesConfig[type]);\n result.loadDataTransfer(dataTransfer);\n return result;\n}\nexport function matchNativeItemType(dataTransfer) {\n if (!dataTransfer) {\n return null;\n }\n\n var dataTransferTypes = Array.prototype.slice.call(dataTransfer.types || []);\n return Object.keys(nativeTypesConfig).filter(function (nativeItemType) {\n var matchesTypes = nativeTypesConfig[nativeItemType].matchesTypes;\n return matchesTypes.some(function (t) {\n return dataTransferTypes.indexOf(t) > -1;\n });\n })[0] || null;\n}","function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nexport var OptionsReader = /*#__PURE__*/function () {\n function OptionsReader(globalContext, options) {\n _classCallCheck(this, OptionsReader);\n\n this.ownerDocument = null;\n this.globalContext = globalContext;\n this.optionsArgs = options;\n }\n\n _createClass(OptionsReader, [{\n key: \"window\",\n get: function get() {\n if (this.globalContext) {\n return this.globalContext;\n } else if (typeof window !== 'undefined') {\n return window;\n }\n\n return undefined;\n }\n }, {\n key: \"document\",\n get: function get() {\n var _this$globalContext;\n\n if ((_this$globalContext = this.globalContext) !== null && _this$globalContext !== void 0 && _this$globalContext.document) {\n return this.globalContext.document;\n } else if (this.window) {\n return this.window.document;\n } else {\n return undefined;\n }\n }\n }, {\n key: \"rootElement\",\n get: function get() {\n var _this$optionsArgs;\n\n return ((_this$optionsArgs = this.optionsArgs) === null || _this$optionsArgs === void 0 ? void 0 : _this$optionsArgs.rootElement) || this.window;\n }\n }]);\n\n return OptionsReader;\n}();","function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nimport { EnterLeaveCounter } from './EnterLeaveCounter';\nimport { getNodeClientOffset, getEventClientOffset, getDragPreviewOffset } from './OffsetUtils';\nimport { createNativeDragSource, matchNativeItemType } from './NativeDragSources';\nimport * as NativeTypes from './NativeTypes';\nimport { OptionsReader } from './OptionsReader';\nexport var HTML5BackendImpl = /*#__PURE__*/function () {\n function HTML5BackendImpl(manager, globalContext, options) {\n var _this = this;\n\n _classCallCheck(this, HTML5BackendImpl);\n\n this.sourcePreviewNodes = new Map();\n this.sourcePreviewNodeOptions = new Map();\n this.sourceNodes = new Map();\n this.sourceNodeOptions = new Map();\n this.dragStartSourceIds = null;\n this.dropTargetIds = [];\n this.dragEnterTargetIds = [];\n this.currentNativeSource = null;\n this.currentNativeHandle = null;\n this.currentDragSourceNode = null;\n this.altKeyPressed = false;\n this.mouseMoveTimeoutTimer = null;\n this.asyncEndDragFrameId = null;\n this.dragOverTargetIds = null;\n\n this.getSourceClientOffset = function (sourceId) {\n var source = _this.sourceNodes.get(sourceId);\n\n return source && getNodeClientOffset(source) || null;\n };\n\n this.endDragNativeItem = function () {\n if (!_this.isDraggingNativeItem()) {\n return;\n }\n\n _this.actions.endDrag();\n\n if (_this.currentNativeHandle) {\n _this.registry.removeSource(_this.currentNativeHandle);\n }\n\n _this.currentNativeHandle = null;\n _this.currentNativeSource = null;\n };\n\n this.isNodeInDocument = function (node) {\n // Check the node either in the main document or in the current context\n return Boolean(node && _this.document && _this.document.body && document.body.contains(node));\n };\n\n this.endDragIfSourceWasRemovedFromDOM = function () {\n var node = _this.currentDragSourceNode;\n\n if (node == null || _this.isNodeInDocument(node)) {\n return;\n }\n\n if (_this.clearCurrentDragSourceNode() && _this.monitor.isDragging()) {\n _this.actions.endDrag();\n }\n };\n\n this.handleTopDragStartCapture = function () {\n _this.clearCurrentDragSourceNode();\n\n _this.dragStartSourceIds = [];\n };\n\n this.handleTopDragStart = function (e) {\n if (e.defaultPrevented) {\n return;\n }\n\n var dragStartSourceIds = _this.dragStartSourceIds;\n _this.dragStartSourceIds = null;\n var clientOffset = getEventClientOffset(e); // Avoid crashing if we missed a drop event or our previous drag died\n\n if (_this.monitor.isDragging()) {\n _this.actions.endDrag();\n } // Don't publish the source just yet (see why below)\n\n\n _this.actions.beginDrag(dragStartSourceIds || [], {\n publishSource: false,\n getSourceClientOffset: _this.getSourceClientOffset,\n clientOffset: clientOffset\n });\n\n var dataTransfer = e.dataTransfer;\n var nativeType = matchNativeItemType(dataTransfer);\n\n if (_this.monitor.isDragging()) {\n if (dataTransfer && typeof dataTransfer.setDragImage === 'function') {\n // Use custom drag image if user specifies it.\n // If child drag source refuses drag but parent agrees,\n // use parent's node as drag image. Neither works in IE though.\n var sourceId = _this.monitor.getSourceId();\n\n var sourceNode = _this.sourceNodes.get(sourceId);\n\n var dragPreview = _this.sourcePreviewNodes.get(sourceId) || sourceNode;\n\n if (dragPreview) {\n var _this$getCurrentSourc = _this.getCurrentSourcePreviewNodeOptions(),\n anchorX = _this$getCurrentSourc.anchorX,\n anchorY = _this$getCurrentSourc.anchorY,\n offsetX = _this$getCurrentSourc.offsetX,\n offsetY = _this$getCurrentSourc.offsetY;\n\n var anchorPoint = {\n anchorX: anchorX,\n anchorY: anchorY\n };\n var offsetPoint = {\n offsetX: offsetX,\n offsetY: offsetY\n };\n var dragPreviewOffset = getDragPreviewOffset(sourceNode, dragPreview, clientOffset, anchorPoint, offsetPoint);\n dataTransfer.setDragImage(dragPreview, dragPreviewOffset.x, dragPreviewOffset.y);\n }\n }\n\n try {\n // Firefox won't drag without setting data\n dataTransfer === null || dataTransfer === void 0 ? void 0 : dataTransfer.setData('application/json', {});\n } catch (err) {// IE doesn't support MIME types in setData\n } // Store drag source node so we can check whether\n // it is removed from DOM and trigger endDrag manually.\n\n\n _this.setCurrentDragSourceNode(e.target); // Now we are ready to publish the drag source.. or are we not?\n\n\n var _this$getCurrentSourc2 = _this.getCurrentSourcePreviewNodeOptions(),\n captureDraggingState = _this$getCurrentSourc2.captureDraggingState;\n\n if (!captureDraggingState) {\n // Usually we want to publish it in the next tick so that browser\n // is able to screenshot the current (not yet dragging) state.\n //\n // It also neatly avoids a situation where render() returns null\n // in the same tick for the source element, and browser freaks out.\n setTimeout(function () {\n return _this.actions.publishDragSource();\n }, 0);\n } else {\n // In some cases the user may want to override this behavior, e.g.\n // to work around IE not supporting custom drag previews.\n //\n // When using a custom drag layer, the only way to prevent\n // the default drag preview from drawing in IE is to screenshot\n // the dragging state in which the node itself has zero opacity\n // and height. In this case, though, returning null from render()\n // will abruptly end the dragging, which is not obvious.\n //\n // This is the reason such behavior is strictly opt-in.\n _this.actions.publishDragSource();\n }\n } else if (nativeType) {\n // A native item (such as URL) dragged from inside the document\n _this.beginDragNativeItem(nativeType);\n } else if (dataTransfer && !dataTransfer.types && (e.target && !e.target.hasAttribute || !e.target.hasAttribute('draggable'))) {\n // Looks like a Safari bug: dataTransfer.types is null, but there was no draggable.\n // Just let it drag. It's a native type (URL or text) and will be picked up in\n // dragenter handler.\n return;\n } else {\n // If by this time no drag source reacted, tell browser not to drag.\n e.preventDefault();\n }\n };\n\n this.handleTopDragEndCapture = function () {\n if (_this.clearCurrentDragSourceNode() && _this.monitor.isDragging()) {\n // Firefox can dispatch this event in an infinite loop\n // if dragend handler does something like showing an alert.\n // Only proceed if we have not handled it already.\n _this.actions.endDrag();\n }\n };\n\n this.handleTopDragEnterCapture = function (e) {\n _this.dragEnterTargetIds = [];\n\n var isFirstEnter = _this.enterLeaveCounter.enter(e.target);\n\n if (!isFirstEnter || _this.monitor.isDragging()) {\n return;\n }\n\n var dataTransfer = e.dataTransfer;\n var nativeType = matchNativeItemType(dataTransfer);\n\n if (nativeType) {\n // A native item (such as file or URL) dragged from outside the document\n _this.beginDragNativeItem(nativeType, dataTransfer);\n }\n };\n\n this.handleTopDragEnter = function (e) {\n var dragEnterTargetIds = _this.dragEnterTargetIds;\n _this.dragEnterTargetIds = [];\n\n if (!_this.monitor.isDragging()) {\n // This is probably a native item type we don't understand.\n return;\n }\n\n _this.altKeyPressed = e.altKey; // If the target changes position as the result of `dragenter`, `dragover` might still\n // get dispatched despite target being no longer there. The easy solution is to check\n // whether there actually is a target before firing `hover`.\n\n if (dragEnterTargetIds.length > 0) {\n _this.actions.hover(dragEnterTargetIds, {\n clientOffset: getEventClientOffset(e)\n });\n }\n\n var canDrop = dragEnterTargetIds.some(function (targetId) {\n return _this.monitor.canDropOnTarget(targetId);\n });\n\n if (canDrop) {\n // IE requires this to fire dragover events\n e.preventDefault();\n\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = _this.getCurrentDropEffect();\n }\n }\n };\n\n this.handleTopDragOverCapture = function () {\n _this.dragOverTargetIds = [];\n };\n\n this.handleTopDragOver = function (e) {\n var dragOverTargetIds = _this.dragOverTargetIds;\n _this.dragOverTargetIds = [];\n\n if (!_this.monitor.isDragging()) {\n // This is probably a native item type we don't understand.\n // Prevent default \"drop and blow away the whole document\" action.\n e.preventDefault();\n\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = 'none';\n }\n\n return;\n }\n\n _this.altKeyPressed = e.altKey;\n\n _this.actions.hover(dragOverTargetIds || [], {\n clientOffset: getEventClientOffset(e)\n });\n\n var canDrop = (dragOverTargetIds || []).some(function (targetId) {\n return _this.monitor.canDropOnTarget(targetId);\n });\n\n if (canDrop) {\n // Show user-specified drop effect.\n e.preventDefault();\n\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = _this.getCurrentDropEffect();\n }\n } else if (_this.isDraggingNativeItem()) {\n // Don't show a nice cursor but still prevent default\n // \"drop and blow away the whole document\" action.\n e.preventDefault();\n } else {\n e.preventDefault();\n\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = 'none';\n }\n }\n };\n\n this.handleTopDragLeaveCapture = function (e) {\n if (_this.isDraggingNativeItem()) {\n e.preventDefault();\n }\n\n var isLastLeave = _this.enterLeaveCounter.leave(e.target);\n\n if (!isLastLeave) {\n return;\n }\n\n if (_this.isDraggingNativeItem()) {\n setTimeout(function () {\n return _this.endDragNativeItem();\n }, 0);\n }\n };\n\n this.handleTopDropCapture = function (e) {\n _this.dropTargetIds = [];\n\n if (_this.isDraggingNativeItem()) {\n var _this$currentNativeSo;\n\n e.preventDefault();\n (_this$currentNativeSo = _this.currentNativeSource) === null || _this$currentNativeSo === void 0 ? void 0 : _this$currentNativeSo.loadDataTransfer(e.dataTransfer);\n }\n\n _this.enterLeaveCounter.reset();\n };\n\n this.handleTopDrop = function (e) {\n var dropTargetIds = _this.dropTargetIds;\n _this.dropTargetIds = [];\n\n _this.actions.hover(dropTargetIds, {\n clientOffset: getEventClientOffset(e)\n });\n\n _this.actions.drop({\n dropEffect: _this.getCurrentDropEffect()\n });\n\n if (_this.isDraggingNativeItem()) {\n _this.endDragNativeItem();\n } else if (_this.monitor.isDragging()) {\n _this.actions.endDrag();\n }\n };\n\n this.handleSelectStart = function (e) {\n var target = e.target; // Only IE requires us to explicitly say\n // we want drag drop operation to start\n\n if (typeof target.dragDrop !== 'function') {\n return;\n } // Inputs and textareas should be selectable\n\n\n if (target.tagName === 'INPUT' || target.tagName === 'SELECT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\n return;\n } // For other targets, ask IE\n // to enable drag and drop\n\n\n e.preventDefault();\n target.dragDrop();\n };\n\n this.options = new OptionsReader(globalContext, options);\n this.actions = manager.getActions();\n this.monitor = manager.getMonitor();\n this.registry = manager.getRegistry();\n this.enterLeaveCounter = new EnterLeaveCounter(this.isNodeInDocument);\n }\n /**\n * Generate profiling statistics for the HTML5Backend.\n */\n\n\n _createClass(HTML5BackendImpl, [{\n key: \"profile\",\n value: function profile() {\n var _this$dragStartSource, _this$dragOverTargetI;\n\n return {\n sourcePreviewNodes: this.sourcePreviewNodes.size,\n sourcePreviewNodeOptions: this.sourcePreviewNodeOptions.size,\n sourceNodeOptions: this.sourceNodeOptions.size,\n sourceNodes: this.sourceNodes.size,\n dragStartSourceIds: ((_this$dragStartSource = this.dragStartSourceIds) === null || _this$dragStartSource === void 0 ? void 0 : _this$dragStartSource.length) || 0,\n dropTargetIds: this.dropTargetIds.length,\n dragEnterTargetIds: this.dragEnterTargetIds.length,\n dragOverTargetIds: ((_this$dragOverTargetI = this.dragOverTargetIds) === null || _this$dragOverTargetI === void 0 ? void 0 : _this$dragOverTargetI.length) || 0\n };\n } // public for test\n\n }, {\n key: \"window\",\n get: function get() {\n return this.options.window;\n }\n }, {\n key: \"document\",\n get: function get() {\n return this.options.document;\n }\n /**\n * Get the root element to use for event subscriptions\n */\n\n }, {\n key: \"rootElement\",\n get: function get() {\n return this.options.rootElement;\n }\n }, {\n key: \"setup\",\n value: function setup() {\n var root = this.rootElement;\n\n if (root === undefined) {\n return;\n }\n\n if (root.__isReactDndBackendSetUp) {\n throw new Error('Cannot have two HTML5 backends at the same time.');\n }\n\n root.__isReactDndBackendSetUp = true;\n this.addEventListeners(root);\n }\n }, {\n key: \"teardown\",\n value: function teardown() {\n var root = this.rootElement;\n\n if (root === undefined) {\n return;\n }\n\n root.__isReactDndBackendSetUp = false;\n this.removeEventListeners(this.rootElement);\n this.clearCurrentDragSourceNode();\n\n if (this.asyncEndDragFrameId) {\n var _this$window;\n\n (_this$window = this.window) === null || _this$window === void 0 ? void 0 : _this$window.cancelAnimationFrame(this.asyncEndDragFrameId);\n }\n }\n }, {\n key: \"connectDragPreview\",\n value: function connectDragPreview(sourceId, node, options) {\n var _this2 = this;\n\n this.sourcePreviewNodeOptions.set(sourceId, options);\n this.sourcePreviewNodes.set(sourceId, node);\n return function () {\n _this2.sourcePreviewNodes.delete(sourceId);\n\n _this2.sourcePreviewNodeOptions.delete(sourceId);\n };\n }\n }, {\n key: \"connectDragSource\",\n value: function connectDragSource(sourceId, node, options) {\n var _this3 = this;\n\n this.sourceNodes.set(sourceId, node);\n this.sourceNodeOptions.set(sourceId, options);\n\n var handleDragStart = function handleDragStart(e) {\n return _this3.handleDragStart(e, sourceId);\n };\n\n var handleSelectStart = function handleSelectStart(e) {\n return _this3.handleSelectStart(e);\n };\n\n node.setAttribute('draggable', 'true');\n node.addEventListener('dragstart', handleDragStart);\n node.addEventListener('selectstart', handleSelectStart);\n return function () {\n _this3.sourceNodes.delete(sourceId);\n\n _this3.sourceNodeOptions.delete(sourceId);\n\n node.removeEventListener('dragstart', handleDragStart);\n node.removeEventListener('selectstart', handleSelectStart);\n node.setAttribute('draggable', 'false');\n };\n }\n }, {\n key: \"connectDropTarget\",\n value: function connectDropTarget(targetId, node) {\n var _this4 = this;\n\n var handleDragEnter = function handleDragEnter(e) {\n return _this4.handleDragEnter(e, targetId);\n };\n\n var handleDragOver = function handleDragOver(e) {\n return _this4.handleDragOver(e, targetId);\n };\n\n var handleDrop = function handleDrop(e) {\n return _this4.handleDrop(e, targetId);\n };\n\n node.addEventListener('dragenter', handleDragEnter);\n node.addEventListener('dragover', handleDragOver);\n node.addEventListener('drop', handleDrop);\n return function () {\n node.removeEventListener('dragenter', handleDragEnter);\n node.removeEventListener('dragover', handleDragOver);\n node.removeEventListener('drop', handleDrop);\n };\n }\n }, {\n key: \"addEventListeners\",\n value: function addEventListeners(target) {\n // SSR Fix (https://github.com/react-dnd/react-dnd/pull/813\n if (!target.addEventListener) {\n return;\n }\n\n target.addEventListener('dragstart', this.handleTopDragStart);\n target.addEventListener('dragstart', this.handleTopDragStartCapture, true);\n target.addEventListener('dragend', this.handleTopDragEndCapture, true);\n target.addEventListener('dragenter', this.handleTopDragEnter);\n target.addEventListener('dragenter', this.handleTopDragEnterCapture, true);\n target.addEventListener('dragleave', this.handleTopDragLeaveCapture, true);\n target.addEventListener('dragover', this.handleTopDragOver);\n target.addEventListener('dragover', this.handleTopDragOverCapture, true);\n target.addEventListener('drop', this.handleTopDrop);\n target.addEventListener('drop', this.handleTopDropCapture, true);\n }\n }, {\n key: \"removeEventListeners\",\n value: function removeEventListeners(target) {\n // SSR Fix (https://github.com/react-dnd/react-dnd/pull/813\n if (!target.removeEventListener) {\n return;\n }\n\n target.removeEventListener('dragstart', this.handleTopDragStart);\n target.removeEventListener('dragstart', this.handleTopDragStartCapture, true);\n target.removeEventListener('dragend', this.handleTopDragEndCapture, true);\n target.removeEventListener('dragenter', this.handleTopDragEnter);\n target.removeEventListener('dragenter', this.handleTopDragEnterCapture, true);\n target.removeEventListener('dragleave', this.handleTopDragLeaveCapture, true);\n target.removeEventListener('dragover', this.handleTopDragOver);\n target.removeEventListener('dragover', this.handleTopDragOverCapture, true);\n target.removeEventListener('drop', this.handleTopDrop);\n target.removeEventListener('drop', this.handleTopDropCapture, true);\n }\n }, {\n key: \"getCurrentSourceNodeOptions\",\n value: function getCurrentSourceNodeOptions() {\n var sourceId = this.monitor.getSourceId();\n var sourceNodeOptions = this.sourceNodeOptions.get(sourceId);\n return _objectSpread({\n dropEffect: this.altKeyPressed ? 'copy' : 'move'\n }, sourceNodeOptions || {});\n }\n }, {\n key: \"getCurrentDropEffect\",\n value: function getCurrentDropEffect() {\n if (this.isDraggingNativeItem()) {\n // It makes more sense to default to 'copy' for native resources\n return 'copy';\n }\n\n return this.getCurrentSourceNodeOptions().dropEffect;\n }\n }, {\n key: \"getCurrentSourcePreviewNodeOptions\",\n value: function getCurrentSourcePreviewNodeOptions() {\n var sourceId = this.monitor.getSourceId();\n var sourcePreviewNodeOptions = this.sourcePreviewNodeOptions.get(sourceId);\n return _objectSpread({\n anchorX: 0.5,\n anchorY: 0.5,\n captureDraggingState: false\n }, sourcePreviewNodeOptions || {});\n }\n }, {\n key: \"isDraggingNativeItem\",\n value: function isDraggingNativeItem() {\n var itemType = this.monitor.getItemType();\n return Object.keys(NativeTypes).some(function (key) {\n return NativeTypes[key] === itemType;\n });\n }\n }, {\n key: \"beginDragNativeItem\",\n value: function beginDragNativeItem(type, dataTransfer) {\n this.clearCurrentDragSourceNode();\n this.currentNativeSource = createNativeDragSource(type, dataTransfer);\n this.currentNativeHandle = this.registry.addSource(type, this.currentNativeSource);\n this.actions.beginDrag([this.currentNativeHandle]);\n }\n }, {\n key: \"setCurrentDragSourceNode\",\n value: function setCurrentDragSourceNode(node) {\n var _this5 = this;\n\n this.clearCurrentDragSourceNode();\n this.currentDragSourceNode = node; // A timeout of > 0 is necessary to resolve Firefox issue referenced\n // See:\n // * https://github.com/react-dnd/react-dnd/pull/928\n // * https://github.com/react-dnd/react-dnd/issues/869\n\n var MOUSE_MOVE_TIMEOUT = 1000; // Receiving a mouse event in the middle of a dragging operation\n // means it has ended and the drag source node disappeared from DOM,\n // so the browser didn't dispatch the dragend event.\n //\n // We need to wait before we start listening for mousemove events.\n // This is needed because the drag preview needs to be drawn or else it fires an 'mousemove' event\n // immediately in some browsers.\n //\n // See:\n // * https://github.com/react-dnd/react-dnd/pull/928\n // * https://github.com/react-dnd/react-dnd/issues/869\n //\n\n this.mouseMoveTimeoutTimer = setTimeout(function () {\n var _this5$rootElement;\n\n return (_this5$rootElement = _this5.rootElement) === null || _this5$rootElement === void 0 ? void 0 : _this5$rootElement.addEventListener('mousemove', _this5.endDragIfSourceWasRemovedFromDOM, true);\n }, MOUSE_MOVE_TIMEOUT);\n }\n }, {\n key: \"clearCurrentDragSourceNode\",\n value: function clearCurrentDragSourceNode() {\n if (this.currentDragSourceNode) {\n this.currentDragSourceNode = null;\n\n if (this.rootElement) {\n var _this$window2;\n\n (_this$window2 = this.window) === null || _this$window2 === void 0 ? void 0 : _this$window2.clearTimeout(this.mouseMoveTimeoutTimer || undefined);\n this.rootElement.removeEventListener('mousemove', this.endDragIfSourceWasRemovedFromDOM, true);\n }\n\n this.mouseMoveTimeoutTimer = null;\n return true;\n }\n\n return false;\n }\n }, {\n key: \"handleDragStart\",\n value: function handleDragStart(e, sourceId) {\n if (e.defaultPrevented) {\n return;\n }\n\n if (!this.dragStartSourceIds) {\n this.dragStartSourceIds = [];\n }\n\n this.dragStartSourceIds.unshift(sourceId);\n }\n }, {\n key: \"handleDragEnter\",\n value: function handleDragEnter(e, targetId) {\n this.dragEnterTargetIds.unshift(targetId);\n }\n }, {\n key: \"handleDragOver\",\n value: function handleDragOver(e, targetId) {\n if (this.dragOverTargetIds === null) {\n this.dragOverTargetIds = [];\n }\n\n this.dragOverTargetIds.unshift(targetId);\n }\n }, {\n key: \"handleDrop\",\n value: function handleDrop(e, targetId) {\n this.dropTargetIds.unshift(targetId);\n }\n }]);\n\n return HTML5BackendImpl;\n}();","import { HTML5BackendImpl } from './HTML5BackendImpl';\nimport * as NativeTypes from './NativeTypes';\nexport { getEmptyImage } from './getEmptyImage';\nexport { NativeTypes };\nexport var HTML5Backend = function createBackend(manager, context, options) {\n return new HTML5BackendImpl(manager, context, options);\n};","import React from 'react';\r\nimport PropTypes from 'prop-types';\r\nimport { DndProvider } from 'react-dnd';\r\nimport { HTML5Backend } from 'react-dnd-html5-backend';\r\n\r\n// import TouchBackend from 'react-dnd-touch-backend';\r\n\r\n// TODO: this is false when it should not be :thinking:\r\nconst isTouchDevice =\r\n typeof window !== `undefined` &&\r\n !!('ontouchstart' in window || navigator.maxTouchPoints);\r\n\r\n/**\r\n * Relevant:\r\n * https://github.com/react-dnd/react-dnd/issues/186#issuecomment-335429067\r\n * https://github.com/react-dnd/react-dnd/issues/186#issuecomment-282789420\r\n *\r\n * Docs:\r\n * http://react-dnd.github.io/react-dnd/docs/api/drag-drop-context\r\n */\r\nfunction DragAndDropProvider({ children }) {\r\n const backend = HTML5Backend; // isTouchDevice ? TouchBackend : HTML5Backend;\r\n const opts = {}; // isTouchDevice ? { enableMouseEvents: true } : {};\r\n\r\n console.log('using... touch backend?', isTouchDevice);\r\n\r\n return (\r\n \r\n {children}\r\n \r\n );\r\n}\r\n\r\nDragAndDropProvider.propTypes = {\r\n children: PropTypes.any,\r\n};\r\n\r\nexport default DragAndDropProvider;\r\n","import React, {\r\n useState,\r\n createContext,\r\n useContext,\r\n useEffect,\r\n useCallback,\r\n} from 'react';\r\nimport PropTypes from 'prop-types';\r\nimport classNames from 'classnames';\r\nimport { useTranslation } from 'react-i18next';\r\n\r\nconst ModalContext = createContext(null);\r\nconst { Provider } = ModalContext;\r\n\r\nexport const useModal = () => useContext(ModalContext);\r\n\r\n/**\r\n * UI Modal\r\n *\r\n * @typedef {Object} ModalProps\r\n * @property {ReactElement|HTMLElement} [content=null] Modal content.\r\n * @property {Object} [contentProps=null] Modal content props.\r\n * @property {boolean} [shouldCloseOnEsc=true] Modal is dismissible via the esc key.\r\n * @property {boolean} [isOpen=true] Make the Modal visible or hidden.\r\n * @property {boolean} [closeButton=true] Should the modal body render the close button.\r\n * @property {string} [title=null] Should the modal render the title independently of the body content.\r\n * @property {string} [customClassName=null] The custom class to style the modal.\r\n */\r\n\r\nconst ModalProvider = ({ children, modal: Modal, service }) => {\r\n const DEFAULT_OPTIONS = {\r\n content: null,\r\n contentProps: null,\r\n shouldCloseOnEsc: true,\r\n isOpen: true,\r\n closeButton: true,\r\n title: null,\r\n customClassName: '',\r\n maximizable: false\r\n };\r\n const { t } = useTranslation('Modals');\r\n\r\n\r\n const [options, setOptions] = useState(DEFAULT_OPTIONS);\r\n\r\n /**\r\n * Show the modal and override its configuration props.\r\n *\r\n * @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, closeButton, title, customClassName }\r\n * @returns void\r\n */\r\n const show = useCallback(props => setOptions({ ...options, ...props }), [\r\n options,\r\n ]);\r\n\r\n /**\r\n * Hide the modal and set its properties to default.\r\n *\r\n * @returns void\r\n */\r\n const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), [\r\n DEFAULT_OPTIONS,\r\n ]);\r\n\r\n /**\r\n * Sets the implementation of a modal service that can be used by extensions.\r\n *\r\n * @returns void\r\n */\r\n useEffect(() => {\r\n if (service) {\r\n service.setServiceImplementation({ hide, show });\r\n }\r\n }, [hide, service, show]);\r\n\r\n const {\r\n content: ModalContent,\r\n contentProps,\r\n isOpen,\r\n title,\r\n customClassName,\r\n shouldCloseOnEsc,\r\n closeButton,\r\n maximizable\r\n } = options;\r\n\r\n return (\r\n \r\n {ModalContent && (\r\n \r\n \r\n \r\n )}\r\n {children}\r\n \r\n );\r\n};\r\n\r\n/**\r\n * Higher Order Component to use the modal methods through a Class Component.\r\n *\r\n * @returns\r\n */\r\nexport const withModal = Component => {\r\n return function WrappedComponent(props) {\r\n const { show, hide } = useModal();\r\n return ;\r\n };\r\n};\r\n\r\nModalProvider.defaultProps = {\r\n service: null,\r\n};\r\n\r\nModalProvider.propTypes = {\r\n children: PropTypes.oneOfType([\r\n PropTypes.arrayOf(PropTypes.node),\r\n PropTypes.node,\r\n ]).isRequired,\r\n modal: PropTypes.oneOfType([\r\n PropTypes.arrayOf(PropTypes.node),\r\n PropTypes.node,\r\n PropTypes.func,\r\n ]).isRequired,\r\n service: PropTypes.shape({\r\n setServiceImplementation: PropTypes.func,\r\n }),\r\n};\r\n\r\nexport default ModalProvider;\r\n\r\nexport const ModalConsumer = ModalContext.Consumer;\r\n","import React, { createContext, useContext, useReducer, useMemo } from 'react';\r\n\r\n// export const IMAGE_VIEWER_DEFAULT_VALUE = {\r\n// StudyInstanceUIDs: [],\r\n// setImageViewer: () => {},\r\n// };\r\n\r\nexport const ImageViewerContext = createContext();\r\n\r\nexport function ImageViewerProvider({\r\n StudyInstanceUIDs,\r\n reducer,\r\n initialState,\r\n children,\r\n}) {\r\n const value = useMemo(() => {\r\n return { StudyInstanceUIDs };\r\n }, [StudyInstanceUIDs]);\r\n\r\n return (\r\n \r\n {children}\r\n \r\n );\r\n}\r\n\r\nexport const useImageViewer = () => useContext(ImageViewerContext);\r\n","import React, {\r\n createContext,\r\n useCallback,\r\n useContext,\r\n useEffect,\r\n useReducer,\r\n} from 'react';\r\nimport PropTypes from 'prop-types';\r\n\r\nconst DEFAULT_STATE = {\r\n isCineEnabled: false,\r\n cines: {\r\n /*\r\n * 1: { isPlaying: false, frameRate: 24 };\r\n */\r\n },\r\n};\r\n\r\nconst DEFAULT_CINE = { isPlaying: false, frameRate: 24 };\r\n\r\nexport const CineContext = createContext(DEFAULT_STATE);\r\n\r\nexport default function CineProvider({ children, service }) {\r\n const reducer = (state, action) => {\r\n switch (action.type) {\r\n case 'SET_CINE': {\r\n const { id, frameRate, isPlaying = undefined } = action.payload;\r\n const cines = state.cines;\r\n\r\n if (!cines[id]) cines[id] = { id, ...DEFAULT_CINE };\r\n cines[id].frameRate = frameRate || cines[id].frameRate;\r\n cines[id].isPlaying =\r\n isPlaying !== undefined ? isPlaying : cines[id].isPlaying;\r\n\r\n return { ...state, ...{ cines } };\r\n }\r\n case 'SET_IS_CINE_ENABLED': {\r\n return { ...state, ...{ isCineEnabled: action.payload } };\r\n }\r\n default:\r\n return action.payload;\r\n }\r\n };\r\n\r\n const [state, dispatch] = useReducer(reducer, DEFAULT_STATE);\r\n\r\n const getState = useCallback(() => state, [state]);\r\n\r\n const setIsCineEnabled = useCallback(\r\n isCineEnabled =>\r\n dispatch({ type: 'SET_IS_CINE_ENABLED', payload: isCineEnabled }),\r\n [dispatch]\r\n );\r\n\r\n const setCine = useCallback(\r\n ({ id, frameRate, isPlaying }) =>\r\n dispatch({\r\n type: 'SET_CINE',\r\n payload: {\r\n id,\r\n frameRate,\r\n isPlaying,\r\n },\r\n }),\r\n [dispatch]\r\n );\r\n\r\n /**\r\n * Sets the implementation of a modal service that can be used by extensions.\r\n *\r\n * @returns void\r\n */\r\n useEffect(() => {\r\n if (service) {\r\n service.setServiceImplementation({ getState, setIsCineEnabled, setCine });\r\n }\r\n }, [getState, service, setCine, setIsCineEnabled]);\r\n\r\n const api = {\r\n getState,\r\n setCine,\r\n setIsCineEnabled,\r\n playClip: (element, playClipOptions) =>\r\n service.playClip(element, playClipOptions),\r\n stopClip: element => service.stopClip(element),\r\n };\r\n\r\n return (\r\n {children}\r\n );\r\n}\r\n\r\nCineProvider.propTypes = {\r\n children: PropTypes.any,\r\n service: PropTypes.shape({\r\n setServiceImplementation: PropTypes.func,\r\n }).isRequired,\r\n};\r\n\r\nexport const useCine = () => useContext(CineContext);\r\n","export default {\r\n INFO: 'info',\r\n WARNING: 'warning',\r\n SUCCESS: 'success',\r\n ERROR: 'error',\r\n};\r\n","import React, { useEffect } from 'react';\r\nimport classNames from 'classnames';\r\nimport Icon from '../Icon';\r\n\r\nimport SnackbarTypes from './SnackbarTypes';\r\n\r\nconst iconClasses = {\r\n [SnackbarTypes.INFO]: 'notifications-info',\r\n [SnackbarTypes.WARNING]: 'notifications-warning',\r\n [SnackbarTypes.SUCCESS]: 'notifications-success',\r\n [SnackbarTypes.ERROR]: 'notifications-error',\r\n};\r\n\r\nconst SnackbarItem = ({ options, onClose }) => {\r\n const handleClose = () => onClose(options.id);\r\n\r\n useEffect(() => {\r\n if (options.autoClose) {\r\n setTimeout(() => handleClose(), options.duration);\r\n }\r\n }, []);\r\n\r\n const typeClasses = {\r\n [SnackbarTypes.INFO]: 'bg-[#bed1db]',\r\n [SnackbarTypes.WARNING]: 'bg-[#ebe5c4]',\r\n [SnackbarTypes.SUCCESS]: 'bg-[#c6d9bf]',\r\n [SnackbarTypes.ERROR]: 'bg-[#dabdbe]',\r\n };\r\n\r\n const hidden =\r\n 'duration-300 transition-all ease-in-out h-0 opacity-0 pt-0 mb-0 pb-0';\r\n\r\n return (\r\n
\r\n {/* TODO:\r\n This is tricky. Our \"no-wrap\" in truncate means this has a hard\r\n length. The overflow forces ellipse. If we don't set max width\r\n appropriately, this causes the ActionBar to overflow.\r\n Can clean up by setting percentage widths + calc on parent\r\n containers\r\n */}\r\n